From 26294173207cd5e3c586ca71a88fdd3a09fc7345 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 10 Nov 2023 10:02:05 -0800 Subject: [PATCH 001/724] CI: codecov for multiprocessing (#30432) --- .github/workflows/selfdrive_tests.yaml | 2 ++ pyproject.toml | 3 +++ selfdrive/athena/tests/test_athenad.py | 17 +++++++++-------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index bb5b0a137a..aba630f67c 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -213,6 +213,7 @@ jobs: run: | ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ chmod -R 777 /tmp/comma_download_cache && \ + coverage combine && \ coverage xml" - name: Print diff id: print-diff @@ -282,6 +283,7 @@ jobs: run: | ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ + coverage combine && \ coverage xml" - name: Run unit tests timeout-minutes: 4 diff --git a/pyproject.toml b/pyproject.toml index 71b677fbb7..16dad2c643 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,3 +199,6 @@ flake8-implicit-str-concat.allow-multiline=false "system".msg = "Use openpilot.system" "third_party".msg = "Use openpilot.third_party" "tools".msg = "Use openpilot.tools" + +[tool.coverage.run] +concurrency = ["multiprocessing", "thread"] \ No newline at end of file diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index e81753a6a0..8829ebfbf1 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import json +import multiprocessing import os import requests import shutil @@ -12,7 +13,6 @@ from datetime import datetime, timedelta from parameterized import parameterized from typing import Optional -from multiprocessing import Process from pympler.tracker import SummaryTracker from unittest import mock from websocket import ABNF @@ -75,24 +75,25 @@ class TestAthenadMethods(unittest.TestCase): with self.assertRaises(TimeoutError) as _: dispatcher["getMessage"]("controlsState") - def send_deviceState(): - messaging.context = messaging.Context() - pub_sock = messaging.pub_sock("deviceState") - start = time.time() + end_event = multiprocessing.Event() - while time.time() - start < 1: + pub_sock = messaging.pub_sock("deviceState") + + def send_deviceState(): + while not end_event.is_set(): msg = messaging.new_message('deviceState') pub_sock.send(msg.to_bytes()) time.sleep(0.01) - p = Process(target=send_deviceState) + p = multiprocessing.Process(target=send_deviceState) p.start() time.sleep(0.1) try: deviceState = dispatcher["getMessage"]("deviceState") assert deviceState['deviceState'] finally: - p.terminate() + end_event.set() + p.join() def test_listDataDirectory(self): route = '2021-03-29--13-32-47' From 5f7143df02b857201e2615240ccd1be06daa32fe Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 10 Nov 2023 16:55:25 -0800 Subject: [PATCH 002/724] jenkins: use build.py to manage cache size (#30440) * jenkins: use build.py to manage cache size * label * set pp * double * cleanup * non mimimal build on PC --------- Co-authored-by: Justin Newberry --- Jenkinsfile | 4 ++-- selfdrive/manager/build.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 314170341e..6857ce36e1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -85,7 +85,7 @@ def pcStage(String stageName, Closure body) { checkout scm - def dockerArgs = '--user=batman -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache'; + def dockerArgs = "--user=batman -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache -e PYTHONPATH=${env.WORKSPACE}"; docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .").inside(dockerArgs) { timeout(time: 20, unit: 'MINUTES') { try { @@ -228,7 +228,7 @@ node { }, 'car tests': { pcStage("car tests") { - sh "scons -j30" + sh label: "build", script: "selfdrive/manager/build.py" sh label: "test_models.py", script: "INTERNAL_SEG_CNT=250 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \ pytest -n42 --dist=loadscope selfdrive/car/tests/test_models.py" sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n42 selfdrive/car/tests/test_car_interfaces.py" diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index be37b3e7e4..247f3c8fb8 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -19,19 +19,21 @@ TOTAL_SCONS_NODES = 2560 MAX_BUILD_PROGRESS = 100 PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) -def build(spinner: Spinner, dirty: bool = False) -> None: +def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: env = os.environ.copy() env['SCONS_PROGRESS'] = "1" nproc = os.cpu_count() if nproc is None: nproc = 2 + extra_args = ["--minimal"] if minimal else [] + # building with all cores can result in using too # much memory, so retry with less parallelism compile_output: List[bytes] = [] for n in (nproc, nproc/2, 1): compile_output.clear() - scons: subprocess.Popen = subprocess.Popen(["scons", f"-j{int(n)}", "--cache-populate", "--minimal"], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) + scons: subprocess.Popen = subprocess.Popen(["scons", f"-j{int(n)}", "--cache-populate", *extra_args], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) assert scons.stderr is not None # Read progress from stderr and update spinner @@ -86,4 +88,4 @@ def build(spinner: Spinner, dirty: bool = False) -> None: if __name__ == "__main__" and not PREBUILT: spinner = Spinner() spinner.update_progress(0, 100) - build(spinner, is_dirty()) + build(spinner, is_dirty(), minimal = AGNOS) From 252531e1bbe0ea9ad2eaf97bdf52e0d3f5d77629 Mon Sep 17 00:00:00 2001 From: Hoya Date: Sat, 11 Nov 2023 13:58:59 +0900 Subject: [PATCH 003/724] Hyundai: Add FW for Genesis G80 2018 (#30425) * Update values.py * Update selfdrive/car/hyundai/values.py --------- Co-authored-by: Shane Smiskol --- selfdrive/car/hyundai/values.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index e20bbe279b..edd4f5177c 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1327,20 +1327,24 @@ FW_VERSIONS = { CAR.GENESIS_G80: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ', + b'\xf1\x00DH__ SCC FHCUP 1.00 1.01 96400-B1110 ', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713', b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810', b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014', + b'\xf1\x00DH LKAS AT KOR LHD 1.01 1.02 95895-B1500 170810', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9', b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n', b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c', b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G33KH2\xae\xde\xd5!', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640A4051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.GENESIS_G90: { From 816c18a4ec08441e037f54053fe3da8ecd13b06c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 10 Nov 2023 20:59:14 -0800 Subject: [PATCH 004/724] bump panda (#30437) * bump * bump --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 5e22e87cf6..5fe1d67b77 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 5e22e87cf649ddbef7267bfeb1b7b0793b46dc6c +Subproject commit 5fe1d67b7729c843df7dd15df5d375d08ed410c6 From 9e06525642eb46892a58371cc8daa70fba95f02a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 13 Nov 2023 02:54:08 +0800 Subject: [PATCH 005/724] replay: remove deprecated support for `PANDA_STATE_D_E_P_R_E_C_A_T_E_D` (#30447) --- tools/replay/replay.cc | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index b83f657e39..81ecca1b8b 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -7,7 +7,6 @@ #include "cereal/services.h" #include "common/params.h" #include "common/timing.h" -#include "system/hardware/hw.h" #include "tools/replay/util.h" Replay::Replay(QString route, QStringList allow, QStringList block, QStringList base_blacklist, SubMaster *sm_, uint32_t flags, QString data_dir, QObject *parent) @@ -37,9 +36,6 @@ Replay::Replay(QString route, QStringList allow, QStringList block, QStringList // the following events are needed for replay to work properly. allow_list.insert(cereal::Event::Which::INIT_DATA); allow_list.insert(cereal::Event::Which::CAR_PARAMS); - if (sockets_[cereal::Event::Which::PANDA_STATES] != nullptr) { - allow_list.insert(cereal::Event::Which::PANDA_STATE_D_E_P_R_E_C_A_T_E_D); - } } qDebug() << "services " << s; @@ -418,16 +414,6 @@ void Replay::stream() { cur_mono_time_ = evt->mono_time; setCurrentSegment(toSeconds(cur_mono_time_) / 60); - // migration for pandaState -> pandaStates to keep UI working for old segments - if (cur_which == cereal::Event::Which::PANDA_STATE_D_E_P_R_E_C_A_T_E_D && - sockets_[cereal::Event::Which::PANDA_STATES] != nullptr) { - MessageBuilder msg; - auto ps = msg.initEvent().initPandaStates(1); - ps[0].setIgnitionLine(true); - ps[0].setPandaType(cereal::PandaState::PandaType::DOS); - pm->send(sockets_[cereal::Event::Which::PANDA_STATES], msg); - } - if (cur_which < sockets_.size() && sockets_[cur_which] != nullptr) { // keep time long etime = (cur_mono_time_ - evt_start_ts) / speed_; From d3b91f266ac98b1f2a86c1eafef30bb969fdbaa8 Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Mon, 13 Nov 2023 10:19:23 -0700 Subject: [PATCH 006/724] C3: add gps_vs_llk plotjuggler layout (#30448) add gps_vs_llk plotjuggler layout --- tools/plotjuggler/layouts/gps_vs_llk.xml | 83 ++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tools/plotjuggler/layouts/gps_vs_llk.xml diff --git a/tools/plotjuggler/layouts/gps_vs_llk.xml b/tools/plotjuggler/layouts/gps_vs_llk.xml new file mode 100644 index 0000000000..44980712ed --- /dev/null +++ b/tools/plotjuggler/layouts/gps_vs_llk.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + R = 6378.137 -- Radius of earth in KM + -- Compute the Haversine distance between +-- two points defined by latitude and longitude. +-- Return the distance in meters +lat1, lon1 = value, v1 +lat2, lon2 = v2, v3 +dLat = (lat2 - lat1) * math.pi / 180 +dLon = (lon2 - lon1) * math.pi / 180 +a = math.sin(dLat/2) * math.sin(dLat/2) + +math.cos(lat1 * math.pi / 180) * math.cos(lat2 * math.pi / 180) * +math.sin(dLon/2) * math.sin(dLon/2) +c = 2 * math.atan(math.sqrt(a), math.sqrt(1-a)) +d = R * c +distance = d * 1000 -- meters +return distance + /gpsLocationExternal/latitude + + /gpsLocationExternal/longitude + /liveLocationKalman/positionGeodetic/value/0 + /liveLocationKalman/positionGeodetic/value/1 + + + + + + + From da95fd3019473b1f0dd8d671c22c1e618092cdb2 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 14 Nov 2023 01:19:39 +0800 Subject: [PATCH 007/724] replay: simplify the code for allow/block list (#30449) simplify allow/block list --- tools/cabana/streams/replaystream.cc | 2 +- tools/replay/SConscript | 16 ++---- tools/replay/logreader.cc | 17 ++---- tools/replay/logreader.h | 6 +- tools/replay/main.cc | 5 +- tools/replay/replay.cc | 83 ++++++++++------------------ tools/replay/replay.h | 8 +-- tools/replay/route.cc | 9 +-- tools/replay/route.h | 4 +- tools/replay/tests/test_replay.cc | 2 +- 10 files changed, 50 insertions(+), 102 deletions(-) diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index e94aefec2b..c61c81d56f 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -46,7 +46,7 @@ void ReplayStream::mergeSegments() { bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags) { replay.reset(new Replay(route, {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, - {}, {}, nullptr, replay_flags, data_dir, this)); + {}, nullptr, replay_flags, data_dir, this)); replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->installEventFilter(event_filter, this); QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo); diff --git a/tools/replay/SConscript b/tools/replay/SConscript index bce7512e44..db8447003b 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -1,25 +1,21 @@ -import os -Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', - 'cereal', 'transformations') +Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') base_frameworks = qt_env['FRAMEWORKS'] -base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', - 'capnp', 'kj', 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] +base_libs = [common, messaging, cereal, visionipc, 'zmq', + 'capnp', 'kj', 'm', 'ssl', 'crypto', 'pthread', 'qt_util'] + qt_env["LIBS"] if arch == "Darwin": base_frameworks.append('OpenCL') else: base_libs.append('OpenCL') -qt_libs = ['qt_util'] + base_libs qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc"] - -replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=qt_libs, FRAMEWORKS=base_frameworks) +replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + qt_libs +replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + base_libs qt_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): - qt_env.Program('tests/test_replay', ['tests/test_runner.cc', 'tests/test_replay.cc'], LIBS=[replay_libs, qt_libs]) + qt_env.Program('tests/test_replay', ['tests/test_runner.cc', 'tests/test_replay.cc'], LIBS=[replay_libs, base_libs]) diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index 74aebceae5..c92ff4753f 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -1,6 +1,7 @@ #include "tools/replay/logreader.h" #include +#include "tools/replay/filereader.h" #include "tools/replay/util.h" Event::Event(const kj::ArrayPtr &amsg, bool frame) : reader(amsg), frame(frame) { @@ -40,9 +41,7 @@ LogReader::~LogReader() { } } -bool LogReader::load(const std::string &url, std::atomic *abort, - const std::set &allow, - bool local_cache, int chunk_size, int retries) { +bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { raw_ = FileReader(local_cache, chunk_size, retries).read(url, abort); if (raw_.empty()) return false; @@ -50,15 +49,15 @@ bool LogReader::load(const std::string &url, std::atomic *abort, raw_ = decompressBZ2(raw_, abort); if (raw_.empty()) return false; } - return parse(allow, abort); + return parse(abort); } bool LogReader::load(const std::byte *data, size_t size, std::atomic *abort) { raw_.assign((const char *)data, size); - return parse({}, abort); + return parse(abort); } -bool LogReader::parse(const std::set &allow, std::atomic *abort) { +bool LogReader::parse(std::atomic *abort) { try { kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); while (words.size() > 0 && !(abort && *abort)) { @@ -67,12 +66,6 @@ bool LogReader::parse(const std::set &allow, std::atomicwhich) == allow.end()) { - words = kj::arrayPtr(evt->reader.getEnd(), words.end()); - delete evt; - continue; - } - // Add encodeIdx packet again as a frame packet for the video stream if (evt->which == cereal::Event::ROAD_ENCODE_IDX || evt->which == cereal::Event::DRIVER_ENCODE_IDX || diff --git a/tools/replay/logreader.h b/tools/replay/logreader.h index 77d751a91b..73f822d16c 100644 --- a/tools/replay/logreader.h +++ b/tools/replay/logreader.h @@ -6,13 +6,11 @@ #endif #include -#include #include #include #include "cereal/gen/cpp/log.capnp.h" #include "system/camerad/cameras/camera_common.h" -#include "tools/replay/filereader.h" const CameraType ALL_CAMERAS[] = {RoadCam, DriverCam, WideRoadCam}; const int MAX_CAMERAS = std::size(ALL_CAMERAS); @@ -55,13 +53,13 @@ class LogReader { public: LogReader(size_t memory_pool_block_size = DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE); ~LogReader(); - bool load(const std::string &url, std::atomic *abort = nullptr, const std::set &allow = {}, + bool load(const std::string &url, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); bool load(const std::byte *data, size_t size, std::atomic *abort = nullptr); std::vector events; private: - bool parse(const std::set &allow, std::atomic *abort); + bool parse(std::atomic *abort); std::string raw_; #ifdef HAS_MEMORY_RESOURCE std::unique_ptr mbr_; diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 98a0bb3333..945cb4cd09 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -13,7 +13,6 @@ int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); - const QStringList base_blacklist = {"uiDebug", "userFlag"}; const std::tuple flags[] = { {"dcam", REPLAY_FLAG_DCAM, "load driver camera"}, {"ecam", REPLAY_FLAG_ECAM, "load wide road camera"}, @@ -22,7 +21,7 @@ int main(int argc, char *argv[]) { {"qcam", REPLAY_FLAG_QCAMERA, "load qcamera"}, {"no-hw-decoder", REPLAY_FLAG_NO_HW_DECODER, "disable HW video decoding"}, {"no-vipc", REPLAY_FLAG_NO_VIPC, "do not output video"}, - {"all", REPLAY_FLAG_ALL_SERVICES, "do output all messages including " + base_blacklist.join(", ") + + {"all", REPLAY_FLAG_ALL_SERVICES, "do output all messages including uiDebug, userFlag" ". this may causes issues when used along with UI"} }; @@ -64,7 +63,7 @@ int main(int argc, char *argv[]) { op_prefix.reset(new OpenpilotPrefix(prefix.toStdString())); } - Replay *replay = new Replay(route, allow, block, base_blacklist, nullptr, replay_flags, parser.value("data_dir"), &app); + Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app); if (!parser.value("c").isEmpty()) { replay->setSegmentCacheLimit(parser.value("c").toInt()); } diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 81ecca1b8b..1ec484d677 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -9,35 +9,23 @@ #include "common/timing.h" #include "tools/replay/util.h" -Replay::Replay(QString route, QStringList allow, QStringList block, QStringList base_blacklist, SubMaster *sm_, uint32_t flags, QString data_dir, QObject *parent) - : sm(sm_), flags_(flags), QObject(parent) { - std::vector s; +Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *sm_, + uint32_t flags, QString data_dir, QObject *parent) : sm(sm_), flags_(flags), QObject(parent) { + if (!(flags_ & REPLAY_FLAG_ALL_SERVICES)) { + block << "uiDebug" << "userFlag"; + } auto event_struct = capnp::Schema::from().asStruct(); sockets_.resize(event_struct.getUnionFields().size()); - for (const auto &it : services) { - auto name = it.second.name.c_str(); - uint16_t which = event_struct.getFieldByName(name).getProto().getDiscriminantValue(); - if ((which == cereal::Event::Which::UI_DEBUG || which == cereal::Event::Which::USER_FLAG) && - !(flags & REPLAY_FLAG_ALL_SERVICES) && - !allow.contains(name)) { - continue; - } - - if ((allow.empty() || allow.contains(name)) && !block.contains(name)) { - sockets_[which] = name; - if (!allow.empty() || !block.empty()) { - allow_list.insert((cereal::Event::Which)which); - } - s.push_back(name); + for (const auto &[name, _] : services) { + if (!block.contains(name.c_str()) && (allow.empty() || allow.contains(name.c_str()))) { + uint16_t which = event_struct.getFieldByName(name).getProto().getDiscriminantValue(); + sockets_[which] = name.c_str(); } } - if (!allow_list.empty()) { - // the following events are needed for replay to work properly. - allow_list.insert(cereal::Event::Which::INIT_DATA); - allow_list.insert(cereal::Event::Which::CAR_PARAMS); - } - + std::vector s; + std::copy_if(sockets_.begin(), sockets_.end(), std::back_inserter(s), + [](const char *name) { return name != nullptr; }); qDebug() << "services " << s; qDebug() << "loading route " << route; @@ -150,7 +138,7 @@ void Replay::buildTimeline() { const auto &route_segments = route_->segments(); for (auto it = route_segments.cbegin(); it != route_segments.cend() && !exit_; ++it) { std::shared_ptr log(new LogReader()); - if (!log->load(it->second.qlog.toStdString(), &exit_, {}, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; + if (!log->load(it->second.qlog.toStdString(), &exit_, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; for (const Event *e : log->events) { if (e->which == cereal::Event::Which::CONTROLS_STATE) { @@ -233,30 +221,17 @@ void Replay::segmentLoadFinished(bool success) { } void Replay::queueSegment() { - if (segments_.empty()) return; - - SegmentMap::iterator begin, cur; - begin = cur = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first)); - int distance = std::max(std::ceil(segment_cache_limit / 2.0) - 1, segment_cache_limit - std::distance(cur, segments_.end())); - for (int i = 0; begin != segments_.begin() && i < distance; ++i) { - --begin; - } - auto end = begin; - for (int i = 0; end != segments_.end() && i < segment_cache_limit; ++i) { - ++end; - } + auto cur = segments_.lower_bound(current_segment_.load()); + if (cur == segments_.end()) return; + auto begin = std::prev(cur, std::min(segment_cache_limit / 2, std::distance(segments_.begin(), cur))); + auto end = std::next(begin, std::min(segment_cache_limit, segments_.size())); // load one segment at a time - for (auto it = cur; it != end; ++it) { - auto &[n, seg] = *it; - if ((seg && !seg->isLoaded()) || !seg) { - if (!seg) { - rDebug("loading segment %d...", n); - seg = std::make_unique(n, route_->at(n), flags_, allow_list); - QObject::connect(seg.get(), &Segment::loadFinished, this, &Replay::segmentLoadFinished); - } - break; - } + auto it = std::find_if(cur, end, [](auto &it) { return !it.second || !it.second->isLoaded(); }); + if (it != end && !it->second) { + rDebug("loading segment %d...", it->first); + it->second = std::make_unique(it->first, route_->at(it->first), flags_); + QObject::connect(it->second.get(), &Segment::loadFinished, this, &Replay::segmentLoadFinished); } mergeSegments(begin, end); @@ -293,13 +268,11 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap:: new_events_->clear(); new_events_->reserve(new_events_size); for (int n : segments_need_merge) { - const auto &e = segments_[n]->log->events; - if (e.size() > 0) { - auto insert_from = e.begin(); - if (new_events_->size() > 0 && (*insert_from)->which == cereal::Event::Which::INIT_DATA) ++insert_from; - auto middle = new_events_->insert(new_events_->end(), insert_from, e.end()); - std::inplace_merge(new_events_->begin(), middle, new_events_->end(), Event::lessThan()); - } + size_t size = new_events_->size(); + const auto &events = segments_[n]->log->events; + std::copy_if(events.begin(), events.end(), std::back_inserter(*new_events_), + [this](auto e) { return e->which < sockets_.size() && sockets_[e->which] != nullptr; }); + std::inplace_merge(new_events_->begin(), new_events_->begin() + size, new_events_->end(), Event::lessThan()); } if (stream_thread_) { @@ -414,7 +387,7 @@ void Replay::stream() { cur_mono_time_ = evt->mono_time; setCurrentSegment(toSeconds(cur_mono_time_) / 60); - if (cur_which < sockets_.size() && sockets_[cur_which] != nullptr) { + if (sockets_[cur_which] != nullptr) { // keep time long etime = (cur_mono_time_ - evt_start_ts) / speed_; long rtime = nanos_since_boot() - loop_start_ts; diff --git a/tools/replay/replay.h b/tools/replay/replay.h index e26ef883b6..1144da2601 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -50,8 +49,8 @@ class Replay : public QObject { Q_OBJECT public: - Replay(QString route, QStringList allow, QStringList block, QStringList base_blacklist, SubMaster *sm = nullptr, - uint32_t flags = REPLAY_FLAG_NONE, QString data_dir = "", QObject *parent = 0); + Replay(QString route, QStringList allow, QStringList block, SubMaster *sm = nullptr, + uint32_t flags = REPLAY_FLAG_NONE, QString data_dir = "", QObject *parent = 0); ~Replay(); bool load(); void start(int seconds = 0); @@ -114,8 +113,6 @@ protected: } QThread *stream_thread_ = nullptr; - - // logs std::mutex stream_lock_; std::condition_variable stream_cv_; std::atomic updating_events_ = false; @@ -142,7 +139,6 @@ protected: std::mutex timeline_lock; QFuture timeline_future; std::vector> timeline; - std::set allow_list; std::string car_fingerprint_; std::atomic speed_ = 1.0; replayEventFilter event_filter = nullptr; diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 15a57e5e42..9168d25b35 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -7,9 +7,6 @@ #include #include #include -#include -#include -#include #include "selfdrive/ui/qt/api.h" #include "system/hardware/hw.h" @@ -102,9 +99,7 @@ void Route::addFileToSegment(int n, const QString &file) { // class Segment -Segment::Segment(int n, const SegmentFile &files, uint32_t flags, - const std::set &allow) - : seg_num(n), flags(flags), allow(allow) { +Segment::Segment(int n, const SegmentFile &files, uint32_t flags) : seg_num(n), flags(flags) { // [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog const std::array file_list = { (flags & REPLAY_FLAG_QCAMERA) || files.road_cam.isEmpty() ? files.qcamera : files.road_cam, @@ -135,7 +130,7 @@ void Segment::loadFile(int id, const std::string file) { success = frames[id]->load(file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3); } else { log = std::make_unique(); - success = log->load(file, &abort_, allow, local_cache, 0, 3); + success = log->load(file, &abort_, local_cache, 0, 3); } if (!success) { diff --git a/tools/replay/route.h b/tools/replay/route.h index 7207ff4f5c..4dad7a1f37 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -55,7 +54,7 @@ class Segment : public QObject { Q_OBJECT public: - Segment(int n, const SegmentFile &files, uint32_t flags, const std::set &allow = {}); + Segment(int n, const SegmentFile &files, uint32_t flags); ~Segment(); inline bool isLoaded() const { return !loading_ && !abort_; } @@ -73,5 +72,4 @@ protected: std::atomic loading_ = 0; QFutureSynchronizer synchronizer_; uint32_t flags; - std::set allow; }; diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index d0b6a9db97..1873daaf4b 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -161,7 +161,7 @@ TEST_CASE("Remote route") { // helper class for unit tests class TestReplay : public Replay { public: - TestReplay(const QString &route, uint32_t flags = REPLAY_FLAG_NO_FILE_CACHE | REPLAY_FLAG_NO_VIPC) : Replay(route, {}, {}, {}, nullptr, flags) {} + TestReplay(const QString &route, uint32_t flags = REPLAY_FLAG_NO_FILE_CACHE | REPLAY_FLAG_NO_VIPC) : Replay(route, {}, {}, nullptr, flags) {} void test_seek(); void testSeekTo(int seek_to); }; From 2ad82cbfb064676401876a20e49e3965c40344a0 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 13 Nov 2023 10:45:24 -0800 Subject: [PATCH 008/724] Pytest: default settings on PC improvements (#30441) * always multithreaded * multithreaded * pkill correct process * escape more * load for interfaces --- .github/workflows/selfdrive_tests.yaml | 9 ++++----- Jenkinsfile | 12 +++++++----- pyproject.toml | 4 ++-- release/files_common | 1 + selfdrive/test/pytest-tici.ini | 5 +++++ system/sensord/tests/test_sensord.py | 2 +- 6 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 selfdrive/test/pytest-tici.ini diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index aba630f67c..6ab55f6add 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -27,7 +27,6 @@ env: RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 - XDIST: -n auto --dist=loadscope jobs: build_release: @@ -58,7 +57,7 @@ jobs: run: | cd $STRIPPED_DIR ${{ env.RUN }} "release/check-dirty.sh && \ - MAX_EXAMPLES=5 $PYTEST $XDIST selfdrive/car" + MAX_EXAMPLES=5 $PYTEST selfdrive/car" - name: pre-commit timeout-minutes: 3 run: | @@ -176,7 +175,7 @@ jobs: - name: Run unit tests timeout-minutes: 15 run: | - ${{ env.RUN }} "$PYTEST $XDIST --timeout 30 -o cpp_files=test_* -m 'not slow' && \ + ${{ env.RUN }} "$PYTEST --timeout 30 -m 'not slow' && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py && \ @@ -258,7 +257,7 @@ jobs: - name: Run regen timeout-minutes: 30 run: | - ${{ env.RUN_CL }} "ONNXCPU=1 $PYTEST $XDIST selfdrive/test/process_replay/test_regen.py && \ + ${{ env.RUN_CL }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \ chmod -R 777 /tmp/comma_download_cache" test_modeld: @@ -318,7 +317,7 @@ jobs: - name: Test car models timeout-minutes: 25 run: | - ${{ env.RUN }} "$PYTEST $XDIST selfdrive/car/tests/test_models.py && \ + ${{ env.RUN }} "$PYTEST selfdrive/car/tests/test_models.py && \ chmod -R 777 /tmp/comma_download_cache" env: NUM_JOBS: 5 diff --git a/Jenkinsfile b/Jenkinsfile index 6857ce36e1..1e1d485b10 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,6 +14,8 @@ export GIT_BRANCH=${env.GIT_BRANCH} export GIT_COMMIT=${env.GIT_COMMIT} export AZURE_TOKEN='${env.AZURE_TOKEN}' export MAPBOX_TOKEN='${env.MAPBOX_TOKEN}' +export PYTEST_ADDOPTS="-c selfdrive/test/pytest-tici.ini --rootdir ." + export GIT_SSH_COMMAND="ssh -i /data/gitkey" @@ -159,7 +161,7 @@ node { ["build openpilot", "cd selfdrive/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"], - ["time to onroad", "cd selfdrive/test/ && pytest test_time_to_onroad.py"], + ["time to onroad", "pytest selfdrive/test/test_time_to_onroad.py"], ]) }, 'HW + Unit Tests': { @@ -194,17 +196,17 @@ node { 'sensord': { deviceStage("LSM + MMC", "tici-lsmc", ["UNSAFE=1"], [ ["build", "cd selfdrive/manager && ./build.py"], - ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], + ["test sensord", "pytest system/sensord/tests/test_sensord.py"], ]) deviceStage("BMX + LSM", "tici-bmx-lsm", ["UNSAFE=1"], [ ["build", "cd selfdrive/manager && ./build.py"], - ["test sensord", "cd system/sensord/tests && pytest test_sensord.py"], + ["test sensord", "pytest system/sensord/tests/test_sensord.py"], ]) }, 'replay': { deviceStage("tici", "tici-replay", ["UNSAFE=1"], [ ["build", "cd selfdrive/manager && ./build.py"], - ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], + ["model replay", "selfdrive/test/process_replay/model_replay.py"], ]) }, 'tizi': { @@ -231,7 +233,7 @@ node { sh label: "build", script: "selfdrive/manager/build.py" sh label: "test_models.py", script: "INTERNAL_SEG_CNT=250 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \ pytest -n42 --dist=loadscope selfdrive/car/tests/test_models.py" - sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n42 selfdrive/car/tests/test_car_interfaces.py" + sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n42 --dist=load selfdrive/car/tests/test_car_interfaces.py" } }, diff --git a/pyproject.toml b/pyproject.toml index 16dad2c643..4e494e65fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/ -Werror --strict-config --strict-markers --durations=10" -#cpp_files = "test_*" # uncomment when agnos has pytest-cpp and remove from CI +addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadscope" +cpp_files = "test_*" python_files = "test_*.py" #timeout = "30" # you get this long by default markers = [ diff --git a/release/files_common b/release/files_common index 5a076f2b1a..b2623e0113 100644 --- a/release/files_common +++ b/release/files_common @@ -293,6 +293,7 @@ selfdrive/test/helpers.py selfdrive/test/setup_device_ci.sh selfdrive/test/test_onroad.py selfdrive/test/test_time_to_onroad.py +selfdrive/test/pytest-tici.ini selfdrive/ui/.gitignore selfdrive/ui/SConscript diff --git a/selfdrive/test/pytest-tici.ini b/selfdrive/test/pytest-tici.ini new file mode 100644 index 0000000000..a553018309 --- /dev/null +++ b/selfdrive/test/pytest-tici.ini @@ -0,0 +1,5 @@ +[pytest] +addopts = -Werror --strict-config --strict-markers +markers = + slow: tests that take awhile to run and can be skipped with -m 'not slow' + tici: tests that are only meant to run on the C3/C3X diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index 0905248071..3075c8a343 100755 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -106,7 +106,7 @@ class TestSensord(unittest.TestCase): os.environ["LSM_SELF_TEST"] = "1" # read initial sensor values every test case can use - os.system("pkill -f ./sensord") + os.system("pkill -f \\\\./sensord") try: managed_processes["sensord"].start() cls.sample_secs = int(os.getenv("SAMPLE_SECS", "10")) From 90c873ab1def0756f6f91fcb7f2d6fad503e3f49 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 13 Nov 2023 13:50:23 -0800 Subject: [PATCH 009/724] Process Replay: move to pytest (#30260) * process replay pytest * enable long diff * readd job name * make it executable * cleanup imports * retrigger --- .github/workflows/selfdrive_tests.yaml | 26 +-- common/prefix.py | 1 + selfdrive/test/process_replay/conftest.py | 37 +++ selfdrive/test/process_replay/helpers.py | 150 +++++++++++++ selfdrive/test/process_replay/regen_all.py | 3 +- .../test/process_replay/test_processes.py | 212 ++++-------------- 6 files changed, 239 insertions(+), 190 deletions(-) create mode 100644 selfdrive/test/process_replay/conftest.py create mode 100755 selfdrive/test/process_replay/helpers.py diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 6ab55f6add..4e622f68a5 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -208,30 +208,20 @@ jobs: run: | ${{ env.RUN }} "scons -j$(nproc)" - name: Run replay + id: run-replay timeout-minutes: 30 run: | - ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ - chmod -R 777 /tmp/comma_download_cache && \ - coverage combine && \ - coverage xml" - - name: Print diff - id: print-diff - if: always() - run: cat selfdrive/test/process_replay/diff.txt - - uses: actions/upload-artifact@v3 - if: always() - continue-on-error: true - with: - name: process_replay_diff.txt - path: selfdrive/test/process_replay/diff.txt - - name: Upload reference logs - if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} - run: | - ${{ env.RUN }} "unset PYTHONWARNINGS && CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" + ${{ env.RUN }} "CI=1 $PYTEST -n auto --dist=loadscope selfdrive/test/process_replay/test_processes.py --long-diff && \ + chmod -R 777 /tmp/comma_download_cache" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v3 with: name: ${{ github.job }} + - name: Upload reference logs + if: ${{ failure() && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} + run: | + ${{ env.RUN }} "unset PYTHONWARNINGS && CI=1 AZURE_TOKEN='$AZURE_TOKEN' \ + pytest -n auto --dist=loadscope selfdrive/test/process_replay/test_processes.py --upload-only" regen: name: regen diff --git a/common/prefix.py b/common/prefix.py index c1744e8ff7..c5ae4393cd 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -21,6 +21,7 @@ class OpenpilotPrefix: except FileExistsError: pass os.makedirs(Paths.log_root(), exist_ok=True) + os.makedirs(Paths.download_cache_root(), exist_ok=True) return self diff --git a/selfdrive/test/process_replay/conftest.py b/selfdrive/test/process_replay/conftest.py new file mode 100644 index 0000000000..f3794d26ac --- /dev/null +++ b/selfdrive/test/process_replay/conftest.py @@ -0,0 +1,37 @@ +import pytest + +from openpilot.selfdrive.test.process_replay.helpers import ALL_PROCS +from openpilot.selfdrive.test.process_replay.test_processes import ALL_CARS + + +def pytest_addoption(parser: pytest.Parser): + parser.addoption("--whitelist-procs", type=str, nargs="*", default=ALL_PROCS, + help="Whitelist given processes from the test (e.g. controlsd)") + parser.addoption("--whitelist-cars", type=str, nargs="*", default=ALL_CARS, + help="Whitelist given cars from the test (e.g. HONDA)") + parser.addoption("--blacklist-procs", type=str, nargs="*", default=[], + help="Blacklist given processes from the test (e.g. controlsd)") + parser.addoption("--blacklist-cars", type=str, nargs="*", default=[], + help="Blacklist given cars from the test (e.g. HONDA)") + parser.addoption("--ignore-fields", type=str, nargs="*", default=[], + help="Extra fields or msgs to ignore (e.g. carState.events)") + parser.addoption("--ignore-msgs", type=str, nargs="*", default=[], + help="Msgs to ignore (e.g. carEvents)") + parser.addoption("--update-refs", action="store_true", + help="Updates reference logs using current commit") + parser.addoption("--upload-only", action="store_true", + help="Skips testing processes and uploads logs from previous test run") + parser.addoption("--long-diff", action="store_true", + help="Outputs diff in long format") + + +@pytest.fixture(scope="class", autouse=True) +def process_replay_test_arguments(request): + if hasattr(request.cls, "segment"): # check if a subclass of TestProcessReplayBase + request.cls.tested_procs = list(set(request.config.getoption("--whitelist-procs")) - set(request.config.getoption("--blacklist-procs"))) + request.cls.tested_cars = list({c.upper() for c in set(request.config.getoption("--whitelist-cars")) - set(request.config.getoption("--blacklist-cars"))}) + request.cls.ignore_fields = request.config.getoption("--ignore-fields") + request.cls.ignore_msgs = request.config.getoption("--ignore-msgs") + request.cls.upload_only = request.config.getoption("--upload-only") + request.cls.update_refs = request.config.getoption("--update-refs") + request.cls.long_diff = request.config.getoption("--long-diff") \ No newline at end of file diff --git a/selfdrive/test/process_replay/helpers.py b/selfdrive/test/process_replay/helpers.py new file mode 100755 index 0000000000..0952a01870 --- /dev/null +++ b/selfdrive/test/process_replay/helpers.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest + +from parameterized import parameterized +from typing import Optional, Union, List + + +from openpilot.selfdrive.test.openpilotci import get_url, upload_file +from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_process_diff +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process +from openpilot.system.version import get_commit +from openpilot.tools.lib.filereader import FileReader +from openpilot.tools.lib.helpers import save_log +from openpilot.tools.lib.logreader import LogReader, LogIterable + + +BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" +REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") +EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"} + + +def get_log_data(segment): + r, n = segment.rsplit("--", 1) + with FileReader(get_url(r, n)) as f: + return f.read() + + +ALL_PROCS = sorted({cfg.proc_name for cfg in CONFIGS if cfg.proc_name not in EXCLUDED_PROCS}) +PROC_TO_CFG = {cfg.proc_name: cfg for cfg in CONFIGS} + +cpu_count = os.cpu_count() or 1 + + +class TestProcessReplayBase(unittest.TestCase): + """ + Base class that replays all processes within test_proceses from a segment, + and puts the log messages in self.log_msgs for analysis by other tests. + """ + segment: Optional[Union[str, LogIterable]] = None + tested_procs: List[str] = ALL_PROCS + + @classmethod + def setUpClass(cls, create_logs=True): + if "Base" in cls.__name__: + raise unittest.SkipTest("skipping base class") + + if isinstance(cls.segment, str): + cls.log_reader = LogReader.from_bytes(get_log_data(cls.segment)) + else: + cls.log_reader = cls.segment + + if create_logs: + cls._create_log_msgs() + + @classmethod + def _run_replay(cls, cfg): + try: + return replay_process(cfg, cls.log_reader, disable_progress=True) + except Exception as e: + raise Exception(f"failed on segment: {cls.segment} \n{e}") from e + + @classmethod + def _create_log_msgs(cls): + cls.log_msgs = {} + cls.proc_cfg = {} + + for proc in cls.tested_procs: + cfg = PROC_TO_CFG[proc] + + log_msgs = cls._run_replay(cfg) + + cls.log_msgs[proc] = log_msgs + cls.proc_cfg[proc] = cfg + + +class TestProcessReplayDiffBase(TestProcessReplayBase): + """ + Base class for checking for diff between process outputs. + """ + update_refs = False + upload_only = False + long_diff = False + ignore_msgs: List[str] = [] + ignore_fields: List[str] = [] + + def setUp(self): + super().setUp() + if self.upload_only: + raise unittest.SkipTest("skipping test, uploading only") + + @classmethod + def setUpClass(cls): + super().setUpClass(not cls.upload_only) + + if cls.long_diff: + cls.maxDiff = None + + os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True) + + cls.cur_commit = get_commit() + cls.assertNotEqual(cls.cur_commit, None, "Couldn't get current commit") + + cls.upload = cls.update_refs or cls.upload_only + + try: + with open(REF_COMMIT_FN) as f: + cls.ref_commit = f.read().strip() + except FileNotFoundError: + print("Couldn't find reference commit") + sys.exit(1) + + cls._create_ref_log_msgs() + + @classmethod + def _create_ref_log_msgs(cls): + cls.ref_log_msgs = {} + + for proc in cls.tested_procs: + cur_log_fn = os.path.join(FAKEDATA, f"{cls.segment}_{proc}_{cls.cur_commit}.bz2") + if cls.update_refs: # reference logs will not exist if routes were just regenerated + ref_log_path = get_url(*cls.segment.rsplit("--", 1)) + else: + ref_log_fn = os.path.join(FAKEDATA, f"{cls.segment}_{proc}_{cls.ref_commit}.bz2") + ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) + + if not cls.upload_only: + save_log(cur_log_fn, cls.log_msgs[proc]) + cls.ref_log_msgs[proc] = list(LogReader(ref_log_path)) + + if cls.upload: + assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}" + upload_file(cur_log_fn, os.path.basename(cur_log_fn)) + os.remove(cur_log_fn) + + @parameterized.expand(ALL_PROCS) + def test_process_diff(self, proc): + if proc not in self.tested_procs: + raise unittest.SkipTest(f"{proc} was not requested to be tested") + + cfg = self.proc_cfg[proc] + log_msgs = self.log_msgs[proc] + ref_log_msgs = self.ref_log_msgs[proc] + + diff = compare_logs(ref_log_msgs, log_msgs, self.ignore_fields + cfg.ignore, self.ignore_msgs) + + diff_short, diff_long = format_process_diff(diff) + + self.assertEqual(len(diff), 0, "\n" + diff_long if self.long_diff else diff_short) \ No newline at end of file diff --git a/selfdrive/test/process_replay/regen_all.py b/selfdrive/test/process_replay/regen_all.py index 656a5b89e1..070cb5f783 100755 --- a/selfdrive/test/process_replay/regen_all.py +++ b/selfdrive/test/process_replay/regen_all.py @@ -8,7 +8,8 @@ from tqdm import tqdm from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.test.process_replay.regen import regen_and_save -from openpilot.selfdrive.test.process_replay.test_processes import FAKEDATA, source_segments as segments +from openpilot.selfdrive.test.process_replay.process_replay import FAKEDATA +from openpilot.selfdrive.test.process_replay.test_processes import source_segments as segments from openpilot.tools.lib.route import SegmentName diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 5429c9b63e..efdd166cac 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -1,20 +1,15 @@ #!/usr/bin/env python3 -import argparse -import concurrent.futures -import os +import unittest +import pytest import sys -from collections import defaultdict -from tqdm import tqdm -from typing import Any, DefaultDict, Dict + +from parameterized import parameterized_class +from typing import List, Optional from openpilot.selfdrive.car.car_helpers import interface_names -from openpilot.selfdrive.test.openpilotci import get_url, upload_file -from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff -from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, check_openpilot_enabled, replay_process -from openpilot.system.version import get_commit -from openpilot.tools.lib.filereader import FileReader -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.helpers import save_log +from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled +from openpilot.selfdrive.test.process_replay.helpers import TestProcessReplayDiffBase + source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY @@ -63,166 +58,41 @@ segments = [ # dashcamOnly makes don't need to be tested until a full port is done excluded_interfaces = ["mock", "tesla"] -BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" -REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") -EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"} +ALL_CARS = sorted({car for car, _ in segments}) -def run_test_process(data): - segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data - res = None - if not args.upload_only: - lr = LogReader.from_bytes(lr_dat) - res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) - # save logs so we can upload when updating refs - save_log(cur_log_fn, log_msgs) +@pytest.mark.slow +@parameterized_class(('case_name', 'segment'), segments) +class TestCarProcessReplay(TestProcessReplayDiffBase): + """ + Runs a replay diff on a segment for each car. + """ - if args.update_refs or args.upload_only: - print(f'Uploading: {os.path.basename(cur_log_fn)}') - assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}" - upload_file(cur_log_fn, os.path.basename(cur_log_fn)) - os.remove(cur_log_fn) - return (segment, cfg.proc_name, res) + case_name: Optional[str] = None + tested_cars: List[str] = ALL_CARS + + @classmethod + def setUpClass(cls): + if cls.case_name not in cls.tested_cars: + raise unittest.SkipTest(f"{cls.case_name} was not requested to be tested") + super().setUpClass() + + def test_all_makes_are_tested(self): + if set(self.tested_cars) != set(ALL_CARS): + raise unittest.SkipTest("skipping check because some cars were skipped via command line") + + # check to make sure all car brands are tested + untested = (set(interface_names) - set(excluded_interfaces)) - {c.lower() for c in self.tested_cars} + self.assertEqual(len(untested), 0, f"Cars missing routes: {str(untested)}") + + def test_controlsd_engaged(self): + if "controlsd" not in self.tested_procs: + raise unittest.SkipTest("controlsd was not requested to be tested") + + # check to make sure openpilot is engaged in the route + log_msgs = self.log_msgs["controlsd"] + self.assertTrue(check_openpilot_enabled(log_msgs), f"Route did not enable at all or for long enough: {self.segment}") -def get_log_data(segment): - r, n = segment.rsplit("--", 1) - with FileReader(get_url(r, n)) as f: - return (segment, f.read()) - - -def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None): - if ignore_fields is None: - ignore_fields = [] - if ignore_msgs is None: - ignore_msgs = [] - - ref_log_msgs = list(LogReader(ref_log_path)) - - try: - log_msgs = replay_process(cfg, lr, disable_progress=True) - except Exception as e: - raise Exception("failed on segment: " + segment) from e - - # check to make sure openpilot is engaged in the route - if cfg.proc_name == "controlsd": - if not check_openpilot_enabled(log_msgs): - return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs - - try: - return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance), log_msgs - except Exception as e: - return str(e), log_msgs - - -if __name__ == "__main__": - all_cars = {car for car, _ in segments} - all_procs = {cfg.proc_name for cfg in CONFIGS if cfg.proc_name not in EXCLUDED_PROCS} - - cpu_count = os.cpu_count() or 1 - - parser = argparse.ArgumentParser(description="Regression test to identify changes in a process's output") - parser.add_argument("--whitelist-procs", type=str, nargs="*", default=all_procs, - help="Whitelist given processes from the test (e.g. controlsd)") - parser.add_argument("--whitelist-cars", type=str, nargs="*", default=all_cars, - help="Whitelist given cars from the test (e.g. HONDA)") - parser.add_argument("--blacklist-procs", type=str, nargs="*", default=[], - help="Blacklist given processes from the test (e.g. controlsd)") - parser.add_argument("--blacklist-cars", type=str, nargs="*", default=[], - help="Blacklist given cars from the test (e.g. HONDA)") - parser.add_argument("--ignore-fields", type=str, nargs="*", default=[], - help="Extra fields or msgs to ignore (e.g. carState.events)") - parser.add_argument("--ignore-msgs", type=str, nargs="*", default=[], - help="Msgs to ignore (e.g. carEvents)") - parser.add_argument("--update-refs", action="store_true", - help="Updates reference logs using current commit") - parser.add_argument("--upload-only", action="store_true", - help="Skips testing processes and uploads logs from previous test run") - parser.add_argument("-j", "--jobs", type=int, default=max(cpu_count - 2, 1), - help="Max amount of parallel jobs") - args = parser.parse_args() - - tested_procs = set(args.whitelist_procs) - set(args.blacklist_procs) - tested_cars = set(args.whitelist_cars) - set(args.blacklist_cars) - tested_cars = {c.upper() for c in tested_cars} - - full_test = (tested_procs == all_procs) and (tested_cars == all_cars) and all(len(x) == 0 for x in (args.ignore_fields, args.ignore_msgs)) - upload = args.update_refs or args.upload_only - os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True) - - if upload: - assert full_test, "Need to run full test when updating refs" - - try: - ref_commit = open(REF_COMMIT_FN).read().strip() - except FileNotFoundError: - print("Couldn't find reference commit") - sys.exit(1) - - cur_commit = get_commit() - if cur_commit is None: - raise Exception("Couldn't get current commit") - - print(f"***** testing against commit {ref_commit} *****") - - # check to make sure all car brands are tested - if full_test: - untested = (set(interface_names) - set(excluded_interfaces)) - {c.lower() for c in tested_cars} - assert len(untested) == 0, f"Cars missing routes: {str(untested)}" - - log_paths: DefaultDict[str, Dict[str, Dict[str, str]]] = defaultdict(lambda: defaultdict(dict)) - with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: - if not args.upload_only: - download_segments = [seg for car, seg in segments if car in tested_cars] - log_data: Dict[str, LogReader] = {} - p1 = pool.map(get_log_data, download_segments) - for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)): - log_data[segment] = lr - - pool_args: Any = [] - for car_brand, segment in segments: - if car_brand not in tested_cars: - continue - - for cfg in CONFIGS: - if cfg.proc_name not in tested_procs: - continue - - cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.bz2") - if args.update_refs: # reference logs will not exist if routes were just regenerated - ref_log_path = get_url(*segment.rsplit("--", 1)) - else: - ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.bz2") - ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) - - dat = None if args.upload_only else log_data[segment] - pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, dat)) - - log_paths[segment][cfg.proc_name]['ref'] = ref_log_path - log_paths[segment][cfg.proc_name]['new'] = cur_log_fn - - results: Any = defaultdict(dict) - p2 = pool.map(run_test_process, pool_args) - for (segment, proc, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)): - if not args.upload_only: - results[segment][proc] = result - - diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit) - if not upload: - with open(os.path.join(PROC_REPLAY_DIR, "diff.txt"), "w") as f: - f.write(diff_long) - print(diff_short) - - if failed: - print("TEST FAILED") - print("\n\nTo push the new reference logs for this commit run:") - print("./test_processes.py --upload-only") - else: - print("TEST SUCCEEDED") - - else: - with open(REF_COMMIT_FN, "w") as f: - f.write(cur_commit) - print(f"\n\nUpdated reference logs for commit: {cur_commit}") - - sys.exit(int(failed)) +if __name__ == '__main__': + pytest.main([*sys.argv[1:], __file__]) \ No newline at end of file From 32a0b1098369bfe43aae853eda3b5e9164dc7a93 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 13 Nov 2023 15:43:25 -0800 Subject: [PATCH 010/724] Toyota: bump Camry model years to 2024 3d35748a8816ca62 --- docs/CARS.md | 2 +- selfdrive/car/toyota/values.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index b7f083b8e8..769099cea7 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -210,7 +210,7 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2021-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry 2018-20|All|Stock|0 mph[8](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2021-23|All|openpilot|0 mph[8](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry 2021-24|All|openpilot|0 mph[8](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index bac1fb84b9..c2649b2c2b 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -123,7 +123,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), ], CAR.CAMRY_TSS2: [ - ToyotaCarInfo("Toyota Camry 2021-23", footnotes=[Footnote.CAMRY]), + ToyotaCarInfo("Toyota Camry 2021-24", footnotes=[Footnote.CAMRY]), ToyotaCarInfo("Toyota Camry Hybrid 2021-24"), ], CAR.CHR: [ From 95c6d5140aee76e6fd28990b0b58e1a3dcc5ee62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 13 Nov 2023 19:52:57 -0500 Subject: [PATCH 011/724] Rm more laika references (#30451) Rm more laikad references --- system/sensord/rawgps/compare.py | 66 ------ system/ubloxd/tests/test_ublox_processing.py | 117 ----------- tools/gpstest/.gitignore | 4 - tools/gpstest/README.md | 33 --- tools/gpstest/fuzzy_testing.py | 111 ---------- tools/gpstest/helper.py | 53 ----- tools/gpstest/patches/hackrf.patch | 44 ---- .../limeGPS/inc_ephem_array_size.patch | 13 -- tools/gpstest/patches/limeGPS/makefile.patch | 11 - .../gpstest/patches/limeSuite/mcu_error.patch | 13 -- .../patches/limeSuite/reference_print.patch | 13 -- tools/gpstest/run_unittest.sh | 16 -- tools/gpstest/setup.sh | 25 --- tools/gpstest/setup_hackrf.sh | 21 -- tools/gpstest/simulate_gps_signal.py | 151 -------------- tools/gpstest/test_gps.py | 189 ------------------ tools/gpstest/test_gps_qcom.py | 78 -------- tools/sim/Dockerfile.sim | 2 - 18 files changed, 960 deletions(-) delete mode 100755 system/sensord/rawgps/compare.py delete mode 100755 system/ubloxd/tests/test_ublox_processing.py delete mode 100644 tools/gpstest/.gitignore delete mode 100644 tools/gpstest/README.md delete mode 100755 tools/gpstest/fuzzy_testing.py delete mode 100644 tools/gpstest/helper.py delete mode 100644 tools/gpstest/patches/hackrf.patch delete mode 100644 tools/gpstest/patches/limeGPS/inc_ephem_array_size.patch delete mode 100644 tools/gpstest/patches/limeGPS/makefile.patch delete mode 100644 tools/gpstest/patches/limeSuite/mcu_error.patch delete mode 100644 tools/gpstest/patches/limeSuite/reference_print.patch delete mode 100755 tools/gpstest/run_unittest.sh delete mode 100755 tools/gpstest/setup.sh delete mode 100755 tools/gpstest/setup_hackrf.sh delete mode 100755 tools/gpstest/simulate_gps_signal.py delete mode 100755 tools/gpstest/test_gps.py delete mode 100755 tools/gpstest/test_gps_qcom.py diff --git a/system/sensord/rawgps/compare.py b/system/sensord/rawgps/compare.py deleted file mode 100755 index e1daa7f918..0000000000 --- a/system/sensord/rawgps/compare.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -import cereal.messaging as messaging -from laika import constants - -if __name__ == "__main__": - sm = messaging.SubMaster(['ubloxGnss', 'qcomGnss']) - - meas = None - while 1: - sm.update() - if sm['ubloxGnss'].which() == "measurementReport": - meas = sm['ubloxGnss'].measurementReport.measurements - if not sm.updated['qcomGnss'] or meas is None: - continue - report = sm['qcomGnss'].measurementReport - if report.source not in [0, 1]: - continue - GLONASS = report.source == 1 - recv_time = report.milliseconds / 1000 - - car = [] - print("qcom has ", sorted([x.svId for x in report.sv])) - print("ublox has", sorted([x.svId for x in meas if x.gnssId == (6 if GLONASS else 0)])) - for i in report.sv: - # match to ublox - tm = None - for m in meas: - if i.svId == m.svId and m.gnssId == 0 and m.sigId == 0 and not GLONASS: - tm = m - if (i.svId-64) == m.svId and m.gnssId == 6 and m.sigId == 0 and GLONASS: - tm = m - if tm is None: - continue - - if not i.measurementStatus.measurementNotUsable and i.measurementStatus.satelliteTimeIsKnown: - sat_time = (i.unfilteredMeasurementIntegral + i.unfilteredMeasurementFraction + i.latency) / 1000 - ublox_psuedorange = tm.pseudorange - qcom_psuedorange = (recv_time - sat_time)*constants.SPEED_OF_LIGHT - if GLONASS: - glonass_freq = tm.glonassFrequencyIndex - 7 - ublox_speed = -(constants.SPEED_OF_LIGHT / (constants.GLONASS_L1 + glonass_freq*constants.GLONASS_L1_DELTA)) * (tm.doppler) - else: - ublox_speed = -(constants.SPEED_OF_LIGHT / constants.GPS_L1) * tm.doppler - qcom_speed = i.unfilteredSpeed - car.append((i.svId, tm.pseudorange, ublox_speed, qcom_psuedorange, qcom_speed, tm.cno)) - - if len(car) == 0: - print("nothing to compare") - continue - - pr_err, speed_err = 0., 0. - for c in car: - ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed = c[1:5] - pr_err += ublox_psuedorange - qcom_psuedorange - speed_err += ublox_speed - qcom_speed - pr_err /= len(car) - speed_err /= len(car) - print("avg psuedorange err %f avg speed err %f" % (pr_err, speed_err)) - for c in sorted(car, key=lambda x: abs(x[1] - x[3] - pr_err)): - svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed, cno = c - print("svid: %3d pseudorange: %10.2f m speed: %8.2f m/s meas: %12.2f speed: %10.2f meas_err: %10.3f speed_err: %8.3f cno: %d" % - (svid, ublox_psuedorange, ublox_speed, qcom_psuedorange, qcom_speed, - ublox_psuedorange - qcom_psuedorange - pr_err, ublox_speed - qcom_speed - speed_err, cno)) - - - diff --git a/system/ubloxd/tests/test_ublox_processing.py b/system/ubloxd/tests/test_ublox_processing.py deleted file mode 100755 index 311604881a..0000000000 --- a/system/ubloxd/tests/test_ublox_processing.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python3 -import unittest -import time -import numpy as np - -from laika import AstroDog -from laika.helpers import ConstellationId -from laika.raw_gnss import correct_measurements, process_measurements, read_raw_ublox -from laika.opt import calc_pos_fix -from openpilot.selfdrive.test.openpilotci import get_url -from openpilot.system.hardware.hw import Paths -from openpilot.tools.lib.logreader import LogReader -from openpilot.selfdrive.test.helpers import with_processes -import cereal.messaging as messaging - -def get_gnss_measurements(log_reader): - gnss_measurements = [] - for msg in log_reader: - if msg.which() == "ubloxGnss": - ublox_msg = msg.ubloxGnss - if ublox_msg.which == 'measurementReport': - report = ublox_msg.measurementReport - if len(report.measurements) > 0: - gnss_measurements.append(read_raw_ublox(report)) - return gnss_measurements - -def get_ublox_raw(log_reader): - ublox_raw = [] - for msg in log_reader: - if msg.which() == "ubloxRaw": - ublox_raw.append(msg) - return ublox_raw - -class TestUbloxProcessing(unittest.TestCase): - NUM_TEST_PROCESS_MEAS = 10 - - @classmethod - def setUpClass(cls): - lr = LogReader(get_url("4cf7a6ad03080c90|2021-09-29--13-46-36", 0)) - cls.gnss_measurements = get_gnss_measurements(lr) - - # test gps ephemeris continuity check (drive has ephemeris issues with cutover data) - lr = LogReader(get_url("37b6542f3211019a|2023-01-15--23-45-10", 14)) - cls.ublox_raw = get_ublox_raw(lr) - - def test_read_ublox_raw(self): - count_gps = 0 - count_glonass = 0 - for measurements in self.gnss_measurements: - for m in measurements: - if m.constellation_id == ConstellationId.GPS: - count_gps += 1 - elif m.constellation_id == ConstellationId.GLONASS: - count_glonass += 1 - - self.assertEqual(count_gps, 5036) - self.assertEqual(count_glonass, 3651) - - def test_get_fix(self): - dog = AstroDog(cache_dir=Paths.download_cache_root()) - position_fix_found = 0 - count_processed_measurements = 0 - count_corrected_measurements = 0 - position_fix_found_after_correcting = 0 - - pos_ests = [] - for measurements in self.gnss_measurements[:self.NUM_TEST_PROCESS_MEAS]: - processed_meas = process_measurements(measurements, dog) - count_processed_measurements += len(processed_meas) - pos_fix = calc_pos_fix(processed_meas) - if len(pos_fix) > 0 and all(p != 0 for p in pos_fix[0]): - position_fix_found += 1 - - corrected_meas = correct_measurements(processed_meas, pos_fix[0][:3], dog) - count_corrected_measurements += len(corrected_meas) - - pos_fix = calc_pos_fix(corrected_meas) - if len(pos_fix) > 0 and all(p != 0 for p in pos_fix[0]): - pos_ests.append(pos_fix[0]) - position_fix_found_after_correcting += 1 - - mean_fix = np.mean(np.array(pos_ests)[:, :3], axis=0) - np.testing.assert_allclose(mean_fix, [-2452306.662377, -4778343.136806, 3428550.090557], rtol=0, atol=1) - - # Note that can happen that there are less corrected measurements compared to processed when they are invalid. - # However, not for the current segment - self.assertEqual(position_fix_found, self.NUM_TEST_PROCESS_MEAS) - self.assertEqual(position_fix_found_after_correcting, self.NUM_TEST_PROCESS_MEAS) - self.assertEqual(count_processed_measurements, 69) - self.assertEqual(count_corrected_measurements, 69) - - @with_processes(['ubloxd']) - def test_ublox_gps_cutover(self): - time.sleep(2) - ugs = messaging.sub_sock("ubloxGnss", timeout=0.1) - ur_pm = messaging.PubMaster(['ubloxRaw']) - - def replay_segment(): - rcv_msgs = [] - for msg in self.ublox_raw: - ur_pm.send(msg.which(), msg.as_builder()) - time.sleep(0.001) - rcv_msgs += messaging.drain_sock(ugs) - - time.sleep(0.1) - rcv_msgs += messaging.drain_sock(ugs) - return rcv_msgs - - # replay twice to enforce cutover data on rewind - rcv_msgs = replay_segment() - rcv_msgs += replay_segment() - - ephems_cnt = sum(m.ubloxGnss.which() == 'ephemeris' for m in rcv_msgs) - self.assertEqual(ephems_cnt, 15) - -if __name__ == "__main__": - unittest.main() diff --git a/tools/gpstest/.gitignore b/tools/gpstest/.gitignore deleted file mode 100644 index 992088ef34..0000000000 --- a/tools/gpstest/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -LimeGPS/ -LimeSuite/ -hackrf/ -gps-sdr-sim/ diff --git a/tools/gpstest/README.md b/tools/gpstest/README.md deleted file mode 100644 index 01f44df0ce..0000000000 --- a/tools/gpstest/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# GPS test setup -Testing the GPS receiver using GPS spoofing. At the moment only -static location relpay is supported. - -# Usage -on C3 run `rpc_server.py`, on host PC run `fuzzy_testing.py` - -`simulate_gps_signal.py` downloads the latest ephemeris file from -https://cddis.nasa.gov/archive/gnss/data/daily/20xx/brdc/. - - -# Hardware Setup -* [LimeSDR USB](https://wiki.myriadrf.org/LimeSDR-USB) -* Asus AX58BT antenna - -# Software Setup -* https://github.com/myriadrf/LimeSuite -To communicate with LimeSDR the LimeSuite is needed it abstracts the direct -communication. It also contains examples for a quick start. - -The latest stable version (22.09) does not have the corresponding firmware -download available at https://downloads.myriadrf.org/project/limesuite. Therefore -version 20.10 was chosen. - -* https://github.com/osqzss/LimeGPS -Built on top of LimeSuite (libLimeSuite.so.20.10-1), generates the GPS signal. - -``` -./LimeGPS -e -l - -# Example -./LimeGPS -e /pathTo/brdc2660.22n -l 47.202028,15.740394,100 -``` diff --git a/tools/gpstest/fuzzy_testing.py b/tools/gpstest/fuzzy_testing.py deleted file mode 100755 index 532fd2d34c..0000000000 --- a/tools/gpstest/fuzzy_testing.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import multiprocessing -import rpyc -from collections import defaultdict - -from helper import download_rinex, exec_LimeGPS_bin -from helper import get_random_coords, get_continuous_coords - -#------------------------------------------------------------------------------ -# this script is supposed to run on HOST PC -# limeSDR is unreliable via c3 USB -#------------------------------------------------------------------------------ - - -def run_lime_gps(rinex_file: str, location: str, timeout: int): - # needs to run longer than the checker - timeout += 10 - print(f"LimeGPS {location} {timeout}") - p = multiprocessing.Process(target=exec_LimeGPS_bin, - args=(rinex_file, location, timeout)) - p.start() - return p - -con = None -def run_remote_checker(lat, lon, alt, duration, ip_addr): - global con - try: - con = rpyc.connect(ip_addr, 18861) - con._config['sync_request_timeout'] = duration+20 - except ConnectionRefusedError: - print("could not run remote checker is 'rpc_server.py' running???") - return False, None, None - - matched, log, info = con.root.exposed_run_checker(lat, lon, alt, - timeout=duration) - con.close() # TODO: might wanna fetch more logs here - con = None - - print(f"Remote Checker: {log} {info}") - return matched, log, info - - -stats = defaultdict(int) # type: ignore -keys = ['success', 'failed', 'ublox_fail', 'proc_crash', 'checker_crash'] - -def print_report(): - print("\nFuzzy testing report summary:") - for k in keys: - print(f" {k}: {stats[k]}") - - -def update_stats(matched, log, info): - if matched: - stats['success'] += 1 - return - - stats['failed'] += 1 - if log == "PROC CRASH": - stats['proc_crash'] += 1 - if log == "CHECKER CRASHED": - stats['checker_crash'] += 1 - if log == "TIMEOUT": - stats['ublox_fail'] += 1 - - -def main(ip_addr, continuous_mode, timeout, pos): - rinex_file = download_rinex() - - lat, lon, alt = pos - if lat == 0 and lon == 0 and alt == 0: - lat, lon, alt = get_random_coords(47.2020, 15.7403) - - try: - while True: - # spoof random location - spoof_proc = run_lime_gps(rinex_file, f"{lat},{lon},{alt}", timeout) - - # remote checker execs blocking - matched, log, info = run_remote_checker(lat, lon, alt, timeout, ip_addr) - update_stats(matched, log, info) - spoof_proc.terminate() - spoof_proc = None - - if continuous_mode: - lat, lon, alt = get_continuous_coords(lat, lon, alt) - else: - lat, lon, alt = get_random_coords(lat, lon) - except KeyboardInterrupt: - if spoof_proc is not None: - spoof_proc.terminate() - - if con is not None and not con.closed: - con.root.exposed_kill_procs() - con.close() - - print_report() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Fuzzy test GPS stack with random locations.") - parser.add_argument("ip_addr", type=str) - parser.add_argument("-c", "--contin", type=bool, nargs='?', default=False, help='Continous location change') - parser.add_argument("-t", "--timeout", type=int, nargs='?', default=180, help='Timeout to get location') - - # for replaying a location - parser.add_argument("lat", type=float, nargs='?', default=0) - parser.add_argument("lon", type=float, nargs='?', default=0) - parser.add_argument("alt", type=float, nargs='?', default=0) - args = parser.parse_args() - main(args.ip_addr, args.contin, args.timeout, (args.lat, args.lon, args.alt)) diff --git a/tools/gpstest/helper.py b/tools/gpstest/helper.py deleted file mode 100644 index 4f62e60db0..0000000000 --- a/tools/gpstest/helper.py +++ /dev/null @@ -1,53 +0,0 @@ -import random -import datetime as dt -import subprocess as sp -from typing import Tuple - -from laika.downloader import download_nav -from laika.gps_time import GPSTime -from laika.helpers import ConstellationId - - -def download_rinex(): - # TODO: check if there is a better way to get the full brdc file for LimeGPS - gps_time = GPSTime.from_datetime(dt.datetime.utcnow()) - utc_time = dt.datetime.utcnow() - dt.timedelta(1) - gps_time = GPSTime.from_datetime(dt.datetime(utc_time.year, utc_time.month, utc_time.day)) - return download_nav(gps_time, '/tmp/gpstest/', ConstellationId.GPS) - - -def exec_LimeGPS_bin(rinex_file: str, location: str, duration: int): - # this functions should never return, cause return means, timeout is - # reached or it crashed - try: - cmd = ["LimeGPS/LimeGPS", "-e", rinex_file, "-l", location] - sp.check_output(cmd, timeout=duration) - except sp.TimeoutExpired: - print("LimeGPS timeout reached!") - except Exception as e: - print(f"LimeGPS crashed: {str(e)}") - - -def get_random_coords(lat, lon) -> Tuple[float, float, int]: - # jump around the world - # max values, lat: -90 to 90, lon: -180 to 180 - - lat_add = random.random()*20 + 10 - lon_add = random.random()*20 + 20 - alt = random.randint(-10**3, 4*10**3) - - lat = ((lat + lat_add + 90) % 180) - 90 - lon = ((lon + lon_add + 180) % 360) - 180 - return round(lat, 5), round(lon, 5), alt - - -def get_continuous_coords(lat, lon, alt) -> Tuple[float, float, int]: - # continuously move around the world - lat_add = random.random()*0.01 - lon_add = random.random()*0.01 - alt_add = random.randint(-100, 100) - - lat = ((lat + lat_add + 90) % 180) - 90 - lon = ((lon + lon_add + 180) % 360) - 180 - alt += alt_add - return round(lat, 5), round(lon, 5), alt diff --git a/tools/gpstest/patches/hackrf.patch b/tools/gpstest/patches/hackrf.patch deleted file mode 100644 index afc9ac437b..0000000000 --- a/tools/gpstest/patches/hackrf.patch +++ /dev/null @@ -1,44 +0,0 @@ -diff --git a/host/hackrf-tools/src/CMakeLists.txt b/host/hackrf-tools/src/CMakeLists.txt -index 7115151c..a51388ba 100644 ---- a/host/hackrf-tools/src/CMakeLists.txt -+++ b/host/hackrf-tools/src/CMakeLists.txt -@@ -23,20 +23,20 @@ - - set(INSTALL_DEFAULT_BINDIR "bin" CACHE STRING "Appended to CMAKE_INSTALL_PREFIX") - --find_package(FFTW REQUIRED) --include_directories(${FFTW_INCLUDES}) --get_filename_component(FFTW_LIBRARY_DIRS ${FFTW_LIBRARIES} DIRECTORY) --link_directories(${FFTW_LIBRARY_DIRS}) -+#find_package(FFTW REQUIRED) -+#include_directories(${FFTW_INCLUDES}) -+#get_filename_component(FFTW_LIBRARY_DIRS ${FFTW_LIBRARIES} DIRECTORY) -+#link_directories(${FFTW_LIBRARY_DIRS}) - - SET(TOOLS - hackrf_transfer -- hackrf_spiflash -- hackrf_cpldjtag -+ #hackrf_spiflash -+ #hackrf_cpldjtag - hackrf_info -- hackrf_debug -- hackrf_clock -- hackrf_sweep -- hackrf_operacake -+ #hackrf_debug -+ #hackrf_clock -+ #hackrf_sweep -+ #hackrf_operacake - ) - - if(MSVC) -@@ -45,7 +45,7 @@ if(MSVC) - ) - LIST(APPEND TOOLS_LINK_LIBS ${FFTW_LIBRARIES}) - else() -- LIST(APPEND TOOLS_LINK_LIBS m fftw3f) -+ LIST(APPEND TOOLS_LINK_LIBS m)# fftw3f) - endif() - - if(NOT libhackrf_SOURCE_DIR) diff --git a/tools/gpstest/patches/limeGPS/inc_ephem_array_size.patch b/tools/gpstest/patches/limeGPS/inc_ephem_array_size.patch deleted file mode 100644 index 9a3525d346..0000000000 --- a/tools/gpstest/patches/limeGPS/inc_ephem_array_size.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/gpssim.h b/gpssim.h -index c30b227..2ae0802 100644 ---- a/gpssim.h -+++ b/gpssim.h -@@ -75,7 +75,7 @@ - #define SC08 (8) - #define SC16 (16) - --#define EPHEM_ARRAY_SIZE (13) // for daily GPS broadcast ephemers file (brdc) -+#define EPHEM_ARRAY_SIZE (20) // for daily GPS broadcast ephemers file (brdc) - - /*! \brief Structure representing GPS time */ - typedef struct diff --git a/tools/gpstest/patches/limeGPS/makefile.patch b/tools/gpstest/patches/limeGPS/makefile.patch deleted file mode 100644 index f99ce551db..0000000000 --- a/tools/gpstest/patches/limeGPS/makefile.patch +++ /dev/null @@ -1,11 +0,0 @@ -diff --git a/makefile b/makefile -index 51bfabf..d0ea1eb 100644 ---- a/makefile -+++ b/makefile -@@ -1,5 +1,4 @@ - CC=gcc -O2 -Wall - - all: limegps.c gpssim.c -- $(CC) -o LimeGPS limegps.c gpssim.c -lm -lpthread -lLimeSuite -- -+ $(CC) -o LimeGPS limegps.c gpssim.c -lm -lpthread -lLimeSuite -I../LimeSuite/src -L../LimeSuite/builddir/src -Wl,-rpath="$(PWD)/../LimeSuite/builddir/src" diff --git a/tools/gpstest/patches/limeSuite/mcu_error.patch b/tools/gpstest/patches/limeSuite/mcu_error.patch deleted file mode 100644 index 91790a4a2b..0000000000 --- a/tools/gpstest/patches/limeSuite/mcu_error.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/lms7002m/LMS7002M_RxTxCalibrations.cpp b/src/lms7002m/LMS7002M_RxTxCalibrations.cpp -index 41a37044..ac29c6b6 100644 ---- a/src/lms7002m/LMS7002M_RxTxCalibrations.cpp -+++ b/src/lms7002m/LMS7002M_RxTxCalibrations.cpp -@@ -254,7 +254,7 @@ int LMS7002M::CalibrateTx(float_type bandwidth_Hz, bool useExtLoopback) - mcuControl->RunProcedure(useExtLoopback ? MCU_FUNCTION_CALIBRATE_TX_EXTLOOPB : MCU_FUNCTION_CALIBRATE_TX); - status = mcuControl->WaitForMCU(1000); - if(status != MCU_BD::MCU_NO_ERROR) -- return ReportError(EINVAL, "Tx Calibration: MCU error %i (%s)", status, MCU_BD::MCUStatusMessage(status)); -+ return -1; //ReportError(EINVAL, "Tx Calibration: MCU error %i (%s)", status, MCU_BD::MCUStatusMessage(status)); - } - - //sync registers to cache diff --git a/tools/gpstest/patches/limeSuite/reference_print.patch b/tools/gpstest/patches/limeSuite/reference_print.patch deleted file mode 100644 index 5bd7cdf1ed..0000000000 --- a/tools/gpstest/patches/limeSuite/reference_print.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/FPGA_common/FPGA_common.cpp b/src/FPGA_common/FPGA_common.cpp -index 4e81f33e..7381c475 100644 ---- a/src/FPGA_common/FPGA_common.cpp -+++ b/src/FPGA_common/FPGA_common.cpp -@@ -946,7 +946,7 @@ double FPGA::DetectRefClk(double fx3Clk) - - if (i == 0) - return -1; -- lime::info("Reference clock %1.2f MHz", clkTbl[i - 1] / 1e6); -+ //lime::info("Reference clock %1.2f MHz", clkTbl[i - 1] / 1e6); - return clkTbl[i - 1]; - } - diff --git a/tools/gpstest/run_unittest.sh b/tools/gpstest/run_unittest.sh deleted file mode 100755 index e0ca017a6d..0000000000 --- a/tools/gpstest/run_unittest.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# NOTE: can only run inside limeGPS test box! - -# run limeGPS with random static location -timeout 300 ./simulate_gps_signal.py 32.7518 -117.1962 & -gps_PID=$(ps -aux | grep -m 1 "timeout 300" | awk '{print $2}') - -echo "starting limeGPS..." -sleep 10 - -# run unit tests (skipped when module not present) -python -m unittest test_gps.py -python -m unittest test_gps_qcom.py - -kill $gps_PID diff --git a/tools/gpstest/setup.sh b/tools/gpstest/setup.sh deleted file mode 100755 index ddf41dd260..0000000000 --- a/tools/gpstest/setup.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -set -e - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd $DIR - -if [ ! -d LimeSuite ]; then - git clone https://github.com/myriadrf/LimeSuite.git - cd LimeSuite - # checkout latest version which has firmware updates available - git checkout v20.10.0 - git apply ../patches/limeSuite/* - mkdir builddir && cd builddir - cmake -DCMAKE_BUILD_TYPE=Release .. - make -j4 - cd ../.. -fi - -if [ ! -d LimeGPS ]; then - git clone https://github.com/osqzss/LimeGPS.git - cd LimeGPS - git apply ../patches/limeGPS/* - make - cd .. -fi diff --git a/tools/gpstest/setup_hackrf.sh b/tools/gpstest/setup_hackrf.sh deleted file mode 100755 index e504ec9447..0000000000 --- a/tools/gpstest/setup_hackrf.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -set -e - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd $DIR - -if [ ! -d gps-sdr-sim ]; then - git clone https://github.com/osqzss/gps-sdr-sim.git - cd gps-sdr-sim - make - cd .. -fi - -if [ ! -d hackrf ]; then - git clone https://github.com/greatscottgadgets/hackrf.git - cd hackrf/host - git apply ../../patches/hackrf.patch - cmake . - make -fi - diff --git a/tools/gpstest/simulate_gps_signal.py b/tools/gpstest/simulate_gps_signal.py deleted file mode 100755 index da0f64eaca..0000000000 --- a/tools/gpstest/simulate_gps_signal.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 -import os -import random -import argparse -import datetime as dt -import subprocess as sp -from typing import Tuple - -from laika.downloader import download_nav -from laika.gps_time import GPSTime -from laika.helpers import ConstellationId - -cache_dir = '/tmp/gpstest/' - - -def download_rinex(): - # TODO: check if there is a better way to get the full brdc file for LimeGPS - gps_time = GPSTime.from_datetime(dt.datetime.utcnow()) - utc_time = dt.datetime.utcnow()# - dt.timedelta(1) - gps_time = GPSTime.from_datetime(dt.datetime(utc_time.year, utc_time.month, utc_time.day)) - return download_nav(gps_time, cache_dir, ConstellationId.GPS) - -def get_coords(lat, lon, s1, s2, o1=0, o2=0) -> Tuple[int, int]: - lat_add = random.random()*s1 + o1 - lon_add = random.random()*s2 + o2 - - lat = ((lat + lat_add + 90) % 180) - 90 - lon = ((lon + lon_add + 180) % 360) - 180 - return round(lat, 5), round(lon, 5) - -def get_continuous_coords(lat, lon) -> Tuple[int, int]: - # continuously move around the world - return get_coords(lat, lon, 0.01, 0.01) - -def get_random_coords(lat, lon) -> Tuple[int, int]: - # jump around the world - return get_coords(lat, lon, 20, 20, 10, 20) - -def run_limeSDR_loop(lat, lon, alt, contin_sim, rinex_file, timeout): - while True: - try: - # TODO: add starttime setting and altitude - # -t 2023/01/15,00:00:00 -T 2023/01/15,00:00:00 - # this needs to match the date of the navigation file - print(f"starting LimeGPS, Location: {lat} {lon} {alt}") - cmd = ["LimeGPS/LimeGPS", "-e", rinex_file, "-l", f"{lat},{lon},{alt}"] - print(f"CMD: {cmd}") - sp.check_output(cmd, stderr=sp.PIPE, timeout=timeout) - except KeyboardInterrupt: - print("stopping LimeGPS") - return - except sp.TimeoutExpired: - print("LimeGPS timeout reached!") - except Exception as e: - out_stderr = e.stderr.decode('utf-8')# pylint:disable=no-member - if "Device is busy." in out_stderr: - print("GPS simulation is already running, Device is busy!") - return - - print(f"LimeGPS crashed: {str(e)}") - print(f"stderr:\n{e.stderr.decode('utf-8')}")# pylint:disable=no-member - return - - if contin_sim: - lat, lon = get_continuous_coords(lat, lon) - else: - lat, lon = get_random_coords(lat, lon) - -def run_hackRF_loop(lat, lon, rinex_file, timeout): - - if timeout is not None: - print("no jump mode for hackrf!") - return - - try: - print(f"starting gps-sdr-sim, Location: {lat},{lon}") - # create 30second file and replay with hackrf endless - cmd = ["gps-sdr-sim/gps-sdr-sim", "-e", rinex_file, "-l", f"{lat},{lon},-200", "-d", "30"] - sp.check_output(cmd, stderr=sp.PIPE, timeout=timeout) - # created in current working directory - except Exception: - print("Failed to generate gpssim.bin") - - try: - print("starting hackrf_transfer") - # create 30second file and replay with hackrf endless - cmd = ["hackrf/host/hackrf-tools/src/hackrf_transfer", "-t", "gpssim.bin", - "-f", "1575420000", "-s", "2600000", "-a", "1", "-R"] - sp.check_output(cmd, stderr=sp.PIPE, timeout=timeout) - except KeyboardInterrupt: - print("stopping hackrf_transfer") - return - except Exception as e: - print(f"hackrf_transfer crashed:{str(e)}") - - -def main(lat, lon, alt, jump_sim, contin_sim, hackrf_mode): - - if hackrf_mode: - if not os.path.exists('hackrf'): - print("hackrf not found run 'setup_hackrf.sh' first") - return - - if not os.path.exists('gps-sdr-sim'): - print("gps-sdr-sim not found run 'setup_hackrf.sh' first") - return - - output = sp.check_output(["hackrf/host/hackrf-tools/src/hackrf_info"]) - if output.strip() == b"" or b"No HackRF boards found." in output: - print("No HackRF boards found!") - return - - else: - if not os.path.exists('LimeGPS'): - print("LimeGPS not found run 'setup.sh' first") - return - - if not os.path.exists('LimeSuite'): - print("LimeSuite not found run 'setup.sh' first") - return - - output = sp.check_output(["LimeSuite/builddir/LimeUtil/LimeUtil", "--find"]) - if output.strip() == b"": - print("No LimeSDR device found!") - return - print(f"Device: {output.strip().decode('utf-8')}") - - if lat == 0 and lon == 0: - lat, lon = get_random_coords(47.2020, 15.7403) - - rinex_file = download_rinex() - - timeout = None - if jump_sim: - timeout = 30 - - if hackrf_mode: - run_hackRF_loop(lat, lon, rinex_file, timeout) - else: - run_limeSDR_loop(lat, lon, alt, contin_sim, rinex_file, timeout) - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Simulate static [or random jumping] GPS signal.") - parser.add_argument("lat", type=float, nargs='?', default=0) - parser.add_argument("lon", type=float, nargs='?', default=0) - parser.add_argument("alt", type=float, nargs='?', default=0) - parser.add_argument("--jump", action="store_true", help="signal that jumps around the world") - parser.add_argument("--contin", action="store_true", help="continuously/slowly moving around the world") - parser.add_argument("--hackrf", action="store_true", help="hackrf mode (DEFAULT: LimeSDR)") - args = parser.parse_args() - main(args.lat, args.lon, args.alt, args.jump, args.contin, args.hackrf) diff --git a/tools/gpstest/test_gps.py b/tools/gpstest/test_gps.py deleted file mode 100755 index bbd53ebfff..0000000000 --- a/tools/gpstest/test_gps.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import time -import unittest -import struct - -from openpilot.common.params import Params -import cereal.messaging as messaging -import openpilot.system.sensord.pigeond as pd -from openpilot.selfdrive.test.helpers import with_processes - - -def read_events(service, duration_sec): - service_sock = messaging.sub_sock(service, timeout=0.1) - start_time_sec = time.monotonic() - events = [] - while time.monotonic() - start_time_sec < duration_sec: - events += messaging.drain_sock(service_sock) - time.sleep(0.1) - - assert len(events) != 0, f"No '{service}'events collected!" - return events - - -def create_backup(pigeon): - # controlled GNSS stop - pigeon.send(b"\xB5\x62\x06\x04\x04\x00\x00\x00\x08\x00\x16\x74") - - # store almanac in flash - pigeon.send(b"\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC") - try: - if not pigeon.wait_for_ack(ack=pd.UBLOX_SOS_ACK, nack=pd.UBLOX_SOS_NACK): - raise RuntimeError("Could not store almanac") - except TimeoutError: - pass - - -def verify_ubloxgnss_data(socket: messaging.SubSocket, max_time: int): - start_time = 0 - end_time = 0 - events = messaging.drain_sock(socket) - assert len(events) != 0, "no ublxGnss measurements" - - for event in events: - if event.ubloxGnss.which() != "measurementReport": - continue - - if start_time == 0: - start_time = event.logMonoTime - - if event.ubloxGnss.measurementReport.numMeas != 0: - end_time = event.logMonoTime - break - - assert end_time != 0, "no ublox measurements received!" - - ttfm = (end_time - start_time)/1e9 - assert ttfm < max_time, f"Time to first measurement > {max_time}s, {ttfm}" - - # check for satellite count in measurements - sat_count = [] - end_id = events.index(event)# pylint:disable=undefined-loop-variable - for event in events[end_id:]: - if event.ubloxGnss.which() == "measurementReport": - sat_count.append(event.ubloxGnss.measurementReport.numMeas) - - num_sat = int(sum(sat_count)/len(sat_count)) - assert num_sat >= 5, f"Not enough satellites {num_sat} (TestBox setup!)" - - -def verify_gps_location(socket: messaging.SubSocket, max_time: int): - events = messaging.drain_sock(socket) - assert len(events) != 0, "no gpsLocationExternal measurements" - - start_time = events[0].logMonoTime - end_time = 0 - for event in events: - gps_valid = event.gpsLocationExternal.flags % 2 - - if gps_valid: - end_time = event.logMonoTime - break - - assert end_time != 0, "GPS location never converged!" - - ttfl = (end_time - start_time)/1e9 - assert ttfl < max_time, f"Time to first location > {max_time}s, {ttfl}" - - hacc = events[-1].gpsLocationExternal.accuracy - vacc = events[-1].gpsLocationExternal.verticalAccuracy - assert hacc < 20, f"Horizontal accuracy too high, {hacc}" - assert vacc < 45, f"Vertical accuracy too high, {vacc}" - - -def verify_time_to_first_fix(pigeon): - # get time to first fix from nav status message - nav_status = b"" - while True: - pigeon.send(b"\xb5\x62\x01\x03\x00\x00\x04\x0d") - nav_status = pigeon.receive() - if nav_status[:4] == b"\xb5\x62\x01\x03": - break - - values = struct.unpack(" 40s, {ttff}" - - -@pytest.mark.tici -class TestGPS(unittest.TestCase): - @classmethod - def setUpClass(cls): - ublox_available = Params().get_bool("UbloxAvailable") - if not ublox_available: - raise unittest.SkipTest - - - def tearDown(self): - pd.set_power(False) - - @with_processes(['ubloxd']) - def test_a_ublox_reset(self): - - pigeon, pm = pd.create_pigeon() - pd.init_baudrate(pigeon) - assert pigeon.reset_device(), "Could not reset device!" - - pd.initialize_pigeon(pigeon) - - ugs = messaging.sub_sock("ubloxGnss", timeout=0.1) - gle = messaging.sub_sock("gpsLocationExternal", timeout=0.1) - - # receive some messages (restart after cold start takes up to 30seconds) - pd.run_receiving(pigeon, pm, 60) - - # store almanac for next test - create_backup(pigeon) - - verify_ubloxgnss_data(ugs, 60) - verify_gps_location(gle, 60) - - # skip for now, this might hang for a while - #verify_time_to_first_fix(pigeon) - - - @with_processes(['ubloxd']) - def test_b_ublox_almanac(self): - pigeon, pm = pd.create_pigeon() - pd.init_baudrate(pigeon) - - # device cold start - pigeon.send(b"\xb5\x62\x06\x04\x04\x00\xff\xff\x00\x00\x0c\x5d") - time.sleep(1) # wait for cold start - pd.init_baudrate(pigeon) - - # clear configuration - pigeon.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x00\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\x5b") - - # restoring almanac backup - pigeon.send(b"\xB5\x62\x09\x14\x00\x00\x1D\x60") - status = pigeon.wait_for_backup_restore_status() - assert status == 2, "Could not restore almanac backup" - - pd.initialize_pigeon(pigeon) - - ugs = messaging.sub_sock("ubloxGnss", timeout=0.1) - gle = messaging.sub_sock("gpsLocationExternal", timeout=0.1) - - pd.run_receiving(pigeon, pm, 15) - verify_ubloxgnss_data(ugs, 15) - verify_gps_location(gle, 20) - - - @with_processes(['ubloxd']) - def test_c_ublox_startup(self): - pigeon, pm = pd.create_pigeon() - pd.init_baudrate(pigeon) - pd.initialize_pigeon(pigeon) - - ugs = messaging.sub_sock("ubloxGnss", timeout=0.1) - gle = messaging.sub_sock("gpsLocationExternal", timeout=0.1) - pd.run_receiving(pigeon, pm, 10) - verify_ubloxgnss_data(ugs, 10) - verify_gps_location(gle, 10) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/gpstest/test_gps_qcom.py b/tools/gpstest/test_gps_qcom.py deleted file mode 100755 index 2ea5556684..0000000000 --- a/tools/gpstest/test_gps_qcom.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -import pytest -import time -import unittest -import subprocess as sp - -from openpilot.common.params import Params -import cereal.messaging as messaging -from openpilot.selfdrive.manager.process_config import managed_processes - - -def exec_mmcli(cmd): - cmd = "mmcli -m 0 " + cmd - p = sp.Popen(cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) - return p.communicate() - - -def wait_for_location(socket, timeout): - while True: - events = messaging.drain_sock(socket) - for event in events: - if event.gpsLocation.flags % 2: - return False - - timeout -= 1 - if timeout <= 0: - return True - - time.sleep(0.1) - continue - - -@pytest.mark.tici -class TestGPS(unittest.TestCase): - @classmethod - def setUpClass(cls): - ublox_available = Params().get_bool("UbloxAvailable") - if ublox_available: - raise unittest.SkipTest - - def test_a_quectel_cold_start(self): - # delete assistance data to enforce cold start for GNSS - # testing shows that this takes up to 20min - - _, err = exec_mmcli("--command='AT+QGPSDEL=0'") - assert len(err) == 0, f"GPSDEL failed: {err}" - - managed_processes['rawgpsd'].start() - start_time = time.monotonic() - glo = messaging.sub_sock("gpsLocation", timeout=0.1) - - timeout = 10*60*3 # 3 minute - timedout = wait_for_location(glo, timeout) - managed_processes['rawgpsd'].stop() - - assert timedout is False, "Waiting for location timed out (3min)!" - - duration = time.monotonic() - start_time - assert duration < 60, f"Received GPS location {duration}!" - - - def test_b_quectel_startup(self): - managed_processes['rawgpsd'].start() - start_time = time.monotonic() - glo = messaging.sub_sock("gpsLocation", timeout=0.1) - - timeout = 10*60 # 1 minute - timedout = wait_for_location(glo, timeout) - managed_processes['rawgpsd'].stop() - - assert timedout is False, "Waiting for location timed out (3min)!" - - duration = time.monotonic() - start_time - assert duration < 60, f"Received GPS location {duration}!" - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index 7dffa7b5e5..c2873c187d 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -22,8 +22,6 @@ COPY ./body ${OPENPILOT_PATH}/body COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons COPY ./rednose ${OPENPILOT_PATH}/rednose -COPY ./laika_repo ${OPENPILOT_PATH}/laika_repo -RUN ln -s ${OPENPILOT_PATH}/laika_repo/laika/ ${OPENPILOT_PATH}/laika COPY ./common ${OPENPILOT_PATH}/common COPY ./opendbc ${OPENPILOT_PATH}/opendbc COPY ./cereal ${OPENPILOT_PATH}/cereal From c0942dbf9fd0d6be501bf4eec99eba003c3a3e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 13 Nov 2023 23:38:24 -0500 Subject: [PATCH 012/724] Rm laika deps in rawgpsd (#30452) * Rm more laikad references * rawgpsd usage of laika * bugfix --- system/sensord/rawgps/rawgpsd.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/system/sensord/rawgps/rawgpsd.py b/system/sensord/rawgps/rawgpsd.py index b947c54872..787a9316d3 100755 --- a/system/sensord/rawgps/rawgpsd.py +++ b/system/sensord/rawgps/rawgpsd.py @@ -8,7 +8,7 @@ import time import pycurl import shutil import subprocess -from datetime import datetime +import datetime from multiprocessing import Process, Event from typing import NoReturn, Optional from struct import unpack_from, calcsize, pack @@ -16,9 +16,6 @@ from struct import unpack_from, calcsize, pack from cereal import log import cereal.messaging as messaging from openpilot.common.gpio import gpio_init, gpio_set -from laika.gps_time import GPSTime, utc_to_gpst, get_leap_seconds -from laika.helpers import get_prn_from_nmea_id -from laika.constants import SECS_IN_HR, SECS_IN_DAY, SECS_IN_WEEK from openpilot.system.hardware.tici.pins import GPIO from openpilot.system.swaglog import cloudlog from openpilot.system.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv @@ -211,7 +208,7 @@ def setup_quectel(diag: ModemDiag) -> bool: inject_assistance() os.remove(ASSIST_DATA_FILE) #at_cmd("AT+QGPSXTRADATA?") - time_str = datetime.utcnow().strftime("%Y/%m/%d,%H:%M:%S") + time_str = datetime.datetime.utcnow().strftime("%Y/%m/%d,%H:%M:%S") at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000") at_cmd("AT+QGPSCFG=\"outport\",\"usbnmea\"") @@ -293,7 +290,6 @@ def main() -> NoReturn: diag = ModemDiag() r = setup_quectel(diag) want_assistance = not r - current_gps_time = utc_to_gpst(GPSTime.from_datetime(datetime.utcnow())) cloudlog.warning("quectel setup done") gpio_init(GPIO.GNSS_PWR_EN, True) gpio_set(GPIO.GNSS_PWR_EN, True) @@ -366,8 +362,6 @@ def main() -> NoReturn: setattr(sv.measurementStatus, kk, bool(v & (1< NoReturn: gps.altitude = report["q_FltFinalPosAlt"] gps.speed = math.sqrt(sum([x**2 for x in vNED])) gps.bearingDeg = report["q_FltHeadingRad"] * 180/math.pi - gps.unixTimestampMillis = GPSTime(report['w_GpsWeekNumber'], - 1e-3*report['q_GpsFixTimeMs']).as_unix_timestamp()*1e3 + + # TODO needs update if there is another leap second, after june 2024? + dt_timestamp = (datetime.datetime(1980, 1, 6, 0, 0, 0, 0, None) + + datetime.timedelta(weeks=report['w_GpsWeekNumber']) + + datetime.timedelta(seconds=(1e-3*report['q_GpsFixTimeMs'] - 18))) + gps.unixTimestampMillis = dt_timestamp.timestamp()*1e3 gps.source = log.GpsLocationData.SensorSource.qcomdiag gps.vNED = vNED gps.verticalAccuracy = report["q_FltVdop"] @@ -395,8 +393,6 @@ def main() -> NoReturn: if gps.flags: want_assistance = False stop_download_event.set() - - pm.send('gpsLocation', msg) elif log_type == LOG_GNSS_OEMDRE_SVPOLY_REPORT: @@ -415,6 +411,10 @@ def main() -> NoReturn: else: setattr(poly, k, v) + ''' + # Timestamp glonass polys with GPSTime + from laika.gps_time import GPSTime, utc_to_gpst, get_leap_seconds + from laika.helpers import get_prn_from_nmea_id prn = get_prn_from_nmea_id(poly.svId) if prn[0] == 'R': epoch = GPSTime(current_gps_time.week, (poly.t0 - 3*SECS_IN_HR + SECS_IN_DAY) % (SECS_IN_WEEK) + get_leap_seconds(current_gps_time)) @@ -429,6 +429,7 @@ def main() -> NoReturn: poly.gpsWeek = epoch.week poly.gpsTow = epoch.tow + ''' pm.send('qcomGnss', msg) elif log_type in [LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT]: From cc32a6bf9fc83259dee086cc7030eb3af1f997f5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 13 Nov 2023 21:33:22 -0800 Subject: [PATCH 013/724] tici: fix leftover APN config from AT&T (#30458) --- system/hardware/tici/hardware.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index d3375831ad..7460e49f6d 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -533,8 +533,9 @@ class Tici(HardwareBase): pass # blue prime config - if sim_id.startswith('8901410'): - os.system('mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn=Broadband"') + blue_prime = sim_id.startswith('8901410') + initial_apn = "Broadband" if blue_prime else "" + os.system(f'mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn={initial_apn}"') def get_networks(self): r = {} From e3231912999d22ae8d65dbfd61ceb957b06cd83b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 13 Nov 2023 21:38:22 -0800 Subject: [PATCH 014/724] new sim connection settings (#30457) * new sim connection settings * fix att conflict * sim id * clenaup --------- Co-authored-by: Comma Device --- system/hardware/tici/esim.nmconnection | 28 ++++++++++++++++++++++++++ system/hardware/tici/hardware.py | 15 +++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 system/hardware/tici/esim.nmconnection diff --git a/system/hardware/tici/esim.nmconnection b/system/hardware/tici/esim.nmconnection new file mode 100644 index 0000000000..d170f9d066 --- /dev/null +++ b/system/hardware/tici/esim.nmconnection @@ -0,0 +1,28 @@ +[connection] +id=esim +uuid=fff6553c-3284-4707-a6b1-acc021caaafb +type=gsm +permissions= +autoconnect=true +autoconnect-retries=100 + +[gsm] +apn= +home-only=false +auto-config=true +sim-id= + +[ipv4] +route-metric=1000 +dns-priority=1000 +dns-search= +method=auto + +[ipv6] +ddr-gen-mode=stable-privacy +dns-search= +route-metric=1000 +dns-priority=1000 +method=auto + +[proxy] diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 7460e49f6d..8b1f9abe74 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -3,6 +3,7 @@ import math import os import subprocess import time +import tempfile from enum import IntEnum from functools import cached_property, lru_cache from pathlib import Path @@ -532,11 +533,23 @@ class Tici(HardwareBase): except Exception: pass - # blue prime config + # blue prime blue_prime = sim_id.startswith('8901410') initial_apn = "Broadband" if blue_prime else "" os.system(f'mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn={initial_apn}"') + # eSIM prime + if sim_id.startswith('8985235'): + with open('/data/openpilot/system/hardware/tici/esim.nmconnection') as f, tempfile.NamedTemporaryFile(mode='w') as tf: + dat = f.read() + dat = dat.replace("sim-id=", f"sim-id={sim_id}") + tf.write(dat) + tf.flush() + + # needs to be root + os.system(f"sudo cp {tf.name} /data/etc/NetworkManager/system-connections/esim.nmconnection") + os.system("sudo nmcli con reload") + def get_networks(self): r = {} From 2647a4b4a983104ed74dee880fed8f575a154a0e Mon Sep 17 00:00:00 2001 From: Calvin Chui <35386445+snyperifle@users.noreply.github.com> Date: Tue, 14 Nov 2023 04:31:27 -0600 Subject: [PATCH 015/724] Toyota: Add Lexus GS F 2016 (#30245) * added GSF ECU values * Added Lexus GSF data * added route for GSF * test RC code * revert fingerprints back from RC test to GSF * fixed type for route * specify route segment for GSF * added GSF to torque substitute * removed year designation for GSF * added MY back to all "Lexus GSF" * removed route, don't think it does anything * move GSF ecu values to better place * remove whitespace from toyota > interface.py * added in public route for LEXUS_GSF * added GSF to docs * update test route * update test route * update 266 Supported cars * trying different dbc * updated to working route * clean up * add to releases * updated test route * new route * docs * slightly better torque params --------- Co-authored-by: cchui Co-authored-by: Shane Smiskol --- RELEASES.md | 1 + docs/CARS.md | 3 ++- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/override.yaml | 1 + selfdrive/car/toyota/interface.py | 6 ++++++ selfdrive/car/toyota/values.py | 25 ++++++++++++++++++++++++- 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 1d88480278..ec1e67e3a7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,6 +13,7 @@ Version 0.9.5 (2023-11-16) * Kia K8 Hybrid (with HDA II) 2023 support thanks to sunnyhaibin! * Kia Sorento Hybrid 2023 support thanks to sunnyhaibin! * Kia Optima Hybrid 2019 support +* Lexus GS F 2016 support thanks to snyperifle! * Lexus IS 2023 support thanks to L3R5! Version 0.9.4 (2023-07-27) diff --git a/docs/CARS.md b/docs/CARS.md index 769099cea7..d8de1491f5 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 267 Supported Cars +# 268 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -153,6 +153,7 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 8cf5f3debd..dc9fa32b97 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -193,6 +193,7 @@ routes = [ CarTestRoute("da23c367491f53e2|2021-05-21--09-09-11", TOYOTA.LEXUS_CTH, segment=3), CarTestRoute("37041c500fd30100|2020-12-30--12-17-24", TOYOTA.LEXUS_ESH), CarTestRoute("32696cea52831b02|2021-11-19--18-13-30", TOYOTA.LEXUS_RC), + CarTestRoute("ab9b64a5e5960cba|2023-10-24--17-32-08", TOYOTA.LEXUS_GS_F), CarTestRoute("886fcd8408d570e9|2020-01-29--02-18-55", TOYOTA.LEXUS_RX), CarTestRoute("d27ad752e9b08d4f|2021-05-26--19-39-51", TOYOTA.LEXUS_RXH), CarTestRoute("01b22eb2ed121565|2020-02-02--11-25-51", TOYOTA.LEXUS_RX_TSS2), diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 29e616b102..12b483cfa7 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -63,6 +63,7 @@ HYUNDAI AZERA 6TH GEN: [1.8, 1.8, 0.1] HYUNDAI AZERA HYBRID 6TH GEN: [1.8, 1.8, 0.1] KIA K8 HYBRID 1ST GEN: [2.5, 2.5, 0.1] HYUNDAI CUSTIN 1ST GEN: [2.5, 2.5, 0.1] +LEXUS GS F 2016: [2.5, 2.5, 0.08] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 42ab5cf257..9cf86d69ab 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -163,6 +163,12 @@ class CarInterface(CarInterfaceBase): ret.tireStiffnessFactor = 0.444 ret.mass = 3736.8 * CV.LB_TO_KG + elif candidate == CAR.LEXUS_GS_F: + ret.wheelbase = 2.84988 + ret.steerRatio = 13.3 + ret.tireStiffnessFactor = 0.444 + ret.mass = 4034. * CV.LB_TO_KG + elif candidate == CAR.LEXUS_CTH: stop_and_go = True ret.wheelbase = 2.60 diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index c2649b2c2b..cd79ef0cf7 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -86,6 +86,7 @@ class CAR(StrEnum): LEXUS_RX = "LEXUS RX 2016" LEXUS_RXH = "LEXUS RX HYBRID 2017" LEXUS_RX_TSS2 = "LEXUS RX 2020" + LEXUS_GS_F = "LEXUS GS F 2016" class Footnote(Enum): @@ -194,6 +195,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ], CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), CAR.LEXUS_IS_TSS2: ToyotaCarInfo("Lexus IS 2022-23"), + CAR.LEXUS_GS_F: ToyotaCarInfo("Lexus GS F 2016"), CAR.LEXUS_NX: [ ToyotaCarInfo("Lexus NX 2018-19"), ToyotaCarInfo("Lexus NX Hybrid 2018-19"), @@ -1717,6 +1719,26 @@ FW_VERSIONS = { b'8646F3302200\x00\x00\x00\x00', ], }, + CAR.LEXUS_GS_F: { + (Ecu.engine, 0x7E0, None): [ + b'\x0233075200\x00\x00\x00\x00\x00\x00\x00\x00530B9000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152630700\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881513016200\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B30551\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702000\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F3002100\x00\x00\x00\x00', + ], + }, CAR.LEXUS_NX: { (Ecu.engine, 0x700, None): [ b'\x01896637850000\x00\x00\x00\x00', @@ -2081,6 +2103,7 @@ DBC = { CAR.PRIUS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.MIRAI: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.ALPHARD_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), + CAR.LEXUS_GS_F: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), } # These cars have non-standard EPS torque scale factors. All others are 73 @@ -2094,7 +2117,7 @@ TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.COROLLA_T NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CAMRY} # the DSU uses the AEB message for longitudinal on these cars -UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC} +UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC, CAR.LEXUS_GS_F} # these cars have a radar which sends ACC messages instead of the camera RADAR_ACC_CAR = {CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2} From 62d4f464a0db44ecb2ce0e7837d4081a0038e844 Mon Sep 17 00:00:00 2001 From: Vadim Pronin Date: Tue, 14 Nov 2023 12:43:11 +0200 Subject: [PATCH 016/724] VW MQB: Add FW for 2018 and 2022 Volkswagen T-Roc (#30315) * Added ECU values for VW T-Roc 2022 * Added ECU values for VW T-Roc 2018 * fix docs --------- Co-authored-by: Shane Smiskol --- docs/CARS.md | 2 +- selfdrive/car/volkswagen/values.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index d8de1491f5..5bc30b036b 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -268,7 +268,7 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| |Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| |Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| +|Volkswagen|T-Roc 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| |Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 3c9aeb55d7..cc552a281d 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -247,7 +247,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Volkswagen Caravelle 2020"), VWCarInfo("Volkswagen California 2021-23"), ], - CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_MQB_A0]), + CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2018-22", footnotes=[Footnote.VW_MQB_A0]), CAR.AUDI_A3_MK3: [ VWCarInfo("Audi A3 2014-19"), VWCarInfo("Audi A3 Sportback e-tron 2017-18"), @@ -955,20 +955,29 @@ FW_VERSIONS = { CAR.TROC_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705E906018AT\xf1\x899640', + b'\xf1\x8705E906018CK\xf1\x890863', + b'\xf1\x8705E906018P \xf1\x896020', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300050J \xf1\x891911', b'\xf1\x870CW300051M \xf1\x891925', + b'\xf1\x870CW300051M \xf1\x891928', + b'\xf1\x870CW300041S \xf1\x891615', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1311110012333300314240681152119333463100', b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x13111100123333003142404M1152119333613100', + b'\xf1\x875Q0959655CF\xf1\x890421\xf1\x82\x1311110012333300314240021150119333613100', + b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1111001111001105111111052900', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521060405A1', + b'\xf1\x875WA907144M \xf1\x891051\xf1\x82\x001T06081T7N', + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521060403A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x872Q0907572M \xf1\x890233', ], }, CAR.AUDI_A3_MK3: { From 5b50cc29540f94a997483949aff9bdac34de6082 Mon Sep 17 00:00:00 2001 From: Saber <81108166+Saber422@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:47:04 +0800 Subject: [PATCH 017/724] VW MQB: Add FW for 2023 Kamiq (#30376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * VW MQB: Add FW for 2023 Kamiq route name:0bbe367c98fa1538|2023-11-04--12-56-37--0 * update docs --------- Co-authored-by: Shane Smiskol --- docs/CARS.md | 2 +- selfdrive/car/volkswagen/values.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 5bc30b036b..3fe18dd3fa 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -191,7 +191,7 @@ A supported vehicle is one that just works when you install a comma device. All |Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Škoda|Fabia 2022-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Škoda|Kamiq 2021[9,11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| +|Škoda|Kamiq 2021-23[9,11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| |Škoda|Karoq 2019-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Škoda|Kodiaq 2017-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Škoda|Octavia 2015-19[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index cc552a281d..cc2c47c94c 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -259,7 +259,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018"), CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20"), CAR.SKODA_FABIA_MK4: VWCarInfo("Škoda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0]), - CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), + CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021-23", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-23"), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2017-23"), CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), @@ -1195,18 +1195,23 @@ FW_VERSIONS = { CAR.SKODA_KAMIQ_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705C906032M \xf1\x891333', + b'\xf1\x8705E906013CK\xf1\x892540', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300020 \xf1\x891906', + b'\xf1\x870CW300020T \xf1\x892204', ], (Ecu.srs, 0x715, None): [ b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\0222221042111042121040404042E2711152H14', + b'\xf1\x872Q0959655BJ\xf1\x890412\xf1\x82\x132223042111042121040404042B251215391423', ], (Ecu.eps, 0x712, None): [ b'\xf1\x872Q1909144M \xf1\x896041', + b'\xf1\x872Q1909144AB\xf1\x896050', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x872Q0907572AA\xf1\x890396', ], }, CAR.SKODA_KAROQ_MK1: { From 919303ec089e334cb63e544be134cf0ade6d80bc Mon Sep 17 00:00:00 2001 From: ohber <44625404+ohber@users.noreply.github.com> Date: Tue, 14 Nov 2023 05:56:04 -0500 Subject: [PATCH 018/724] Kia: add 2023 Stinger FW (#30344) * Added 2023 Kia Stinger Fingerprint * Fixed type on FP value * Fixed typo on fingerprint value * update docs --------- Co-authored-by: Shane Smiskol --- docs/CARS.md | 2 +- selfdrive/car/hyundai/values.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 3fe18dd3fa..044db1f0ec 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -146,7 +146,7 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index edd4f5177c..2d64d7a100 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -267,7 +267,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", car_parts=CarParts.common([CarHarness.hyundai_c])), - CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), + CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", car_parts=CarParts.common([CarHarness.hyundai_e])), CAR.KIA_EV6: [ HyundaiCarInfo("Kia EV6 (Southeast Asia only) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_p])), @@ -1130,21 +1130,26 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ', b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5500 ', + b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5600 ', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640N2051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x81HM6M1_0a0_H00', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5300 4C2CL503', + b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622', b'\xf1\x00CK MFC AT KOR LHD 1.00 1.00 99211-J5500 210622', + b'\xf1\x00CK MFC AT USA LHD 1.00 1.00 99211-J5500 210622', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', + b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1', b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T25KH2B\xfbI\xe2', ], }, From 78ccef28a8ca58e075ea3c34c04b887c550ed848 Mon Sep 17 00:00:00 2001 From: Saber <81108166+Saber422@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:58:20 +0800 Subject: [PATCH 019/724] VW MQB: Add FW for 2020 Golf (#30375) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit route name:0bbe367c98fa1538|2023-10-25--11-23-54--0 --- selfdrive/car/volkswagen/values.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index cc2c47c94c..26d5141b44 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -491,6 +491,7 @@ FW_VERSIONS = { b'\xf1\x870CW300047E \xf1\x895261', b'\xf1\x870CW300048J \xf1\x890611', b'\xf1\x870CW300049H \xf1\x890905', + b'\xf1\x870CW300050G \xf1\x891905', b'\xf1\x870D9300012 \xf1\x894904', b'\xf1\x870D9300012 \xf1\x894913', b'\xf1\x870D9300012 \xf1\x894937', @@ -532,6 +533,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2251229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', + b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211', From b643aca8d683617a03e376902b76c1979d0e289e Mon Sep 17 00:00:00 2001 From: Erich Moraga <33645296+ErichMoraga@users.noreply.github.com> Date: Tue, 14 Nov 2023 05:03:56 -0600 Subject: [PATCH 020/724] Lexus: add several ECU versions for 2018 Lexus RC (#30322) `OQO0#8618` 2018 Lexus RC DongleID/route 01762a5235a24145|2023-10-24--17-44-27 --- selfdrive/car/toyota/values.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index cd79ef0cf7..4a1011982c 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1816,6 +1816,7 @@ FW_VERSIONS = { }, CAR.LEXUS_RC: { (Ecu.engine, 0x700, None): [ + b'\x01896632461100\x00\x00\x00\x00', b'\x01896632478200\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ @@ -1826,17 +1827,20 @@ FW_VERSIONS = { b'F152624221\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ + b'881512404100\x00\x00\x00\x00', b'881512407000\x00\x00\x00\x00', b'881512409100\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B24081\x00\x00\x00\x00\x00\x00', + b'8965B24240\x00\x00\x00\x00\x00\x00', b'8965B24320\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F4702300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F2401100\x00\x00\x00\x00', b'8646F2401200\x00\x00\x00\x00', b'8646F2402200\x00\x00\x00\x00', ], From 6ce66dee4fea3a3b28fe42cbb79f32888eaa7bda Mon Sep 17 00:00:00 2001 From: Viktor Hansson Date: Tue, 14 Nov 2023 12:13:33 +0100 Subject: [PATCH 021/724] VW: Add FW versions for Passat GTE 2018 (#30450) * VW: Add fw-versions for Passat GTE 2018 * Remove duplicate fw-versions for PASSAT_MK8 * Removed duplicate fw version for PASSAT_MK8 One of them included \023 and the other \x13 which was interpreted as the same character in python * Apply suggestions from code review --------- Co-authored-by: Shane Smiskol --- selfdrive/car/volkswagen/values.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 26d5141b44..ea8d833c01 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -660,6 +660,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8703N906026E \xf1\x892114', b'\xf1\x8704E906023AH\xf1\x893379', + b'\xf1\x8704E906023BM\xf1\x894522', b'\xf1\x8704L906026DP\xf1\x891538', b'\xf1\x8704L906026ET\xf1\x891990', b'\xf1\x8704L906026FP\xf1\x892012', @@ -675,6 +676,7 @@ FW_VERSIONS = { b'\xf1\x870D9300014L \xf1\x895002', b'\xf1\x870D9300041A \xf1\x894801', b'\xf1\x870DD300045T \xf1\x891601', + b'\xf1\x870DD300046H \xf1\x891601', b'\xf1\x870DL300011H \xf1\x895201', b'\xf1\x870CW300042H \xf1\x891601', b'\xf1\x870CW300042H \xf1\x891607', From 86a5ac383e938d16b19b69bb43e507abe8cdd63b Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 14 Nov 2023 19:23:33 +0800 Subject: [PATCH 022/724] cabana: highlight dowloaded segments (#30459) highlight dowloaded segments --- tools/cabana/videowidget.cc | 20 +++++++++++++++----- tools/cabana/videowidget.h | 1 - 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 1ecdc8da1c..a6fd0b2b64 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -14,7 +14,6 @@ #include #include "tools/cabana/streams/replaystream.h" -#include "tools/cabana/util.h" const int MIN_VIDEO_HEIGHT = 100; const int THUMBNAIL_MARGIN = 3; @@ -290,12 +289,23 @@ void Slider::paintEvent(QPaintEvent *ev) { double min = minimum() / factor; double max = maximum() / factor; - for (auto [begin, end, type] : qobject_cast(can)->getReplay()->getTimeline()) { - if (begin > max || end < min) - continue; + auto fillRange = [&](double begin, double end, const QColor &color) { + if (begin > max || end < min) return; r.setLeft(((std::max(min, begin) - min) / (max - min)) * width()); r.setRight(((std::min(max, end) - min) / (max - min)) * width()); - p.fillRect(r, timeline_colors[(int)type]); + p.fillRect(r, color); + }; + + const auto replay = qobject_cast(can)->getReplay(); + for (auto [begin, end, type] : replay->getTimeline()) { + fillRange(begin, end, timeline_colors[(int)type]); + } + + QColor empty_color = palette().color(QPalette::Window); + empty_color.setAlpha(160); + for (const auto &[n, seg] : replay->segments()) { + if (!(seg && seg->isLoaded())) + fillRange(n * 60.0, (n + 1) * 60.0, empty_color); } QStyleOptionSlider opt; diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index e163241fb1..67c2c8a29f 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -8,7 +8,6 @@ #include #include #include -#include #include "selfdrive/ui/qt/widgets/cameraview.h" #include "tools/cabana/util.h" From 06fbaacefec1cbfa788cebcfd859f317931b2982 Mon Sep 17 00:00:00 2001 From: Saber <81108166+Saber422@users.noreply.github.com> Date: Tue, 14 Nov 2023 19:29:57 +0800 Subject: [PATCH 023/724] VW MQB: Add FW for 2021 Tiguan (#30401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * VW MQB: Add FW for 2021 Tiguan route name:f3946aa3bdb4bfc7|2023-11-06--14-53-07--0 The carParams always show transmission as "Hyundai", cant get VW format * add correct transmission --------- Co-authored-by: Shane Smiskol --- selfdrive/car/volkswagen/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index ea8d833c01..c8e6daaef8 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -830,6 +830,7 @@ FW_VERSIONS = { b'\xf1\x8704E906027NB\xf1\x899504', b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906027G \xf1\x899893', + b'\xf1\x8705E906018BS\xf1\x890914', b'\xf1\x875N0906259 \xf1\x890002', b'\xf1\x875NA906259H \xf1\x890002', b'\xf1\x875NA907115E \xf1\x890003', @@ -861,6 +862,7 @@ FW_VERSIONS = { b'\xf1\x870DL300014C \xf1\x893703', b'\xf1\x870DD300046K \xf1\x892302', b'\xf1\x870GC300013P \xf1\x892401', + b'\xf1\x870GC300046Q \xf1\x892802', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100', @@ -895,6 +897,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572AB\xf1\x890397', b'\xf1\x872Q0907572J \xf1\x890156', b'\xf1\x872Q0907572M \xf1\x890233', b'\xf1\x872Q0907572Q \xf1\x890342', From 96eee1383628c4dd76a2220ff6804d847cbac2da Mon Sep 17 00:00:00 2001 From: Tam Chau Date: Tue, 14 Nov 2023 18:36:26 +0700 Subject: [PATCH 024/724] Mazda: add CX5 2022 fingerprint values (VN 2023) (#30342) Add mazda cx5 VN 2023 values --- selfdrive/car/mazda/values.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 849cf229a3..1547f69b04 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -93,6 +93,7 @@ FW_VERSIONS = { b'PX85-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXFG-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -113,6 +114,7 @@ FW_VERSIONS = { b'PXDL-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXFG-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXFG-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PG69-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.CX5: { From 51207aaae250f76af6d32c4a5105309bf8ad5fe3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 14 Nov 2023 15:18:42 -0800 Subject: [PATCH 025/724] Update Python packages and pre-commit hooks (#30463) Co-authored-by: adeebshihadeh --- .pre-commit-config.yaml | 2 +- poetry.lock | 330 ++++++++++++++++++++-------------------- 2 files changed, 166 insertions(+), 166 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e584758ecb..2e023229e8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,7 +41,7 @@ repos: args: ['--explicit-package-bases'] exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.5 hooks: - id: ruff exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' diff --git a/poetry.lock b/poetry.lock index 2c91470f54..99d7fe6fd2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -350,13 +350,13 @@ msal-extensions = ">=0.3.0,<2.0.0" [[package]] name = "azure-storage-blob" -version = "12.18.3" +version = "12.19.0" description = "Microsoft Azure Blob Storage Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "azure-storage-blob-12.18.3.tar.gz", hash = "sha256:d8ced0deee3367fa3d4f3d1a03cd9edadf4440c0a371f503d623fa6c807554ee"}, - {file = "azure_storage_blob-12.18.3-py3-none-any.whl", hash = "sha256:c278dde2ac41857a68d615c9f2b36d894ba877a7e84d62795603c7e79d0bb5e9"}, + {file = "azure-storage-blob-12.19.0.tar.gz", hash = "sha256:26c0a4320a34a3c2a1b74528ba6812ebcb632a04cd67b1c7377232c4b01a5897"}, + {file = "azure_storage_blob-12.19.0-py3-none-any.whl", hash = "sha256:7bbc2c9c16678f7a420367fef6b172ba8730a7e66df7f4d7a55d5b3c8216615b"}, ] [package.dependencies] @@ -1183,53 +1183,53 @@ files = [ [[package]] name = "fonttools" -version = "4.44.0" +version = "4.44.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1cd1c6bb097e774d68402499ff66185190baaa2629ae2f18515a2c50b93db0c"}, - {file = "fonttools-4.44.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9eab7f9837fdaa2a10a524fbcc2ec24bf60637c044b6e4a59c3f835b90f0fae"}, - {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f412954275e594f7a51c16f3b3edd850acb0d842fefc33856b63a17e18499a5"}, - {file = "fonttools-4.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50d25893885e80a5955186791eed5579f1e75921751539cc1dc3ffd1160b48cf"}, - {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:22ea8aa7b3712450b42b044702bd3a64fd118006bad09a6f94bd1b227088492e"}, - {file = "fonttools-4.44.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df40daa6c03b98652ffe8110ae014fe695437f6e1cb5a07e16ea37f40e73ac86"}, - {file = "fonttools-4.44.0-cp310-cp310-win32.whl", hash = "sha256:bca49da868e8bde569ef36f0cc1b6de21d56bf9c3be185c503b629c19a185287"}, - {file = "fonttools-4.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:dbac86d83d96099890e731cc2af97976ff2c98f4ba432fccde657c5653a32f1c"}, - {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8ff7d19a6804bfd561cfcec9b4200dd1788e28f7de4be70189801530c47c1b3"}, - {file = "fonttools-4.44.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8a1fa9a718de0bc026979c93e1e9b55c5efde60d76f91561fd713387573817d"}, - {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05064f95aacdfc06f21e55096c964b2228d942b8675fa26995a2551f6329d2d"}, - {file = "fonttools-4.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b38528f25bc662401e6ffae14b3eb7f1e820892fd80369a37155e3b636a2f4"}, - {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:05d7c4d2c95b9490e669f3cb83918799bf1c838619ac6d3bad9ea017cfc63f2e"}, - {file = "fonttools-4.44.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6999e80a125b0cd8e068d0210b63323f17338038c2ecd2e11b9209ec430fe7f2"}, - {file = "fonttools-4.44.0-cp311-cp311-win32.whl", hash = "sha256:a7aec7f5d14dfcd71fb3ebc299b3f000c21fdc4043079101777ed2042ba5b7c5"}, - {file = "fonttools-4.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:518a945dbfe337744bfff31423c1430303b8813c5275dffb0f2577f0734a1189"}, - {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:59b6ad83cce067d10f4790c037a5904424f45bebb5e7be2eb2db90402f288267"}, - {file = "fonttools-4.44.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c2de1fb18198acd400c45ffe2aef5420c8d55fde903e91cba705596099550f3b"}, - {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f308b7a8d28208d54315d11d35f9888d6d607673dd4d42d60b463682ee0400"}, - {file = "fonttools-4.44.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66bc6efd829382f7a7e6cf33c2fb32b13edc8a239eb15f32acbf197dce7a0165"}, - {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a8b99713d3a0d0e876b6aecfaada5e7dc9fe979fcd90ef9fa0ba1d9b9aed03f2"}, - {file = "fonttools-4.44.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b63da598d9cbc52e2381f922da0e94d60c0429f92207bd3fb04d112fc82ea7cb"}, - {file = "fonttools-4.44.0-cp312-cp312-win32.whl", hash = "sha256:f611c97678604e302b725f71626edea113a5745a7fb557c958b39edb6add87d5"}, - {file = "fonttools-4.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:58af428746fa73a2edcbf26aff33ac4ef3c11c8d75bb200eaea2f7e888d2de4e"}, - {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9ee8692e23028564c13d924004495f284df8ac016a19f17a87251210e1f1f928"}, - {file = "fonttools-4.44.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dab3d00d27b1a79ae4d4a240e8ceea8af0ff049fd45f05adb4f860d93744110d"}, - {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f53526668beccdb3409c6055a4ffe50987a7f05af6436fa55d61f5e7bd450219"}, - {file = "fonttools-4.44.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3da036b016c975c2d8c69005bdc4d5d16266f948a7fab950244e0f58301996a"}, - {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b99fe8ef4093f672d00841569d2d05691e50334d79f4d9c15c1265d76d5580d2"}, - {file = "fonttools-4.44.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d16d9634ff1e5cea2cf4a8cbda9026f766e4b5f30b48f8180f0e99133d3abfc"}, - {file = "fonttools-4.44.0-cp38-cp38-win32.whl", hash = "sha256:3d29509f6e05e8d725db59c2d8c076223d793e4e35773040be6632a0349f2f97"}, - {file = "fonttools-4.44.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4fa4f4bc8fd86579b8cdbe5e948f35d82c0eda0091c399d009b2a5a6b61c040"}, - {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c794de4086f06ae609b71ac944ec7deb09f34ecf73316fddc041087dd24bba39"}, - {file = "fonttools-4.44.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2db63941fee3122e31a21dd0f5b2138ce9906b661a85b63622421d3654a74ae2"}, - {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb01c49c8aa035d5346f46630209923d4927ed15c2493db38d31da9f811eb70d"}, - {file = "fonttools-4.44.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c79af80a835410874683b5779b6c1ec1d5a285e11c45b5193e79dd691eb111"}, - {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6e6aa2d066f8dafd06d8d0799b4944b5d5a1f015dd52ac01bdf2895ebe169a0"}, - {file = "fonttools-4.44.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63a3112f753baef8c6ac2f5f574bb9ac8001b86c8c0c0380039db47a7f512d20"}, - {file = "fonttools-4.44.0-cp39-cp39-win32.whl", hash = "sha256:54efed22b2799a85475e6840e907c402ba49892c614565dc770aa97a53621b2b"}, - {file = "fonttools-4.44.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e91e19b583961979e2e5a701269d3cfc07418963bee717f8160b0a24332826b"}, - {file = "fonttools-4.44.0-py3-none-any.whl", hash = "sha256:b9beb0fa6ff3ea808ad4a6962d68ac0f140ddab080957b20d9e268e4d67fb335"}, - {file = "fonttools-4.44.0.tar.gz", hash = "sha256:4e90dd81b6e0d97ebfe52c0d12a17a9ef7f305d6bfbb93081265057d6092f252"}, + {file = "fonttools-4.44.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8acf50c20bae9880169ff133768a54f587d956676d28894401835a28f450935e"}, + {file = "fonttools-4.44.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02939e423540e05843a5c2b84704f45d307144f761a42a299d9b0b481e497225"}, + {file = "fonttools-4.44.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d96cc1395dbf82dedfd4eb127d320004088df6007383c25db676e5f42fe414"}, + {file = "fonttools-4.44.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aff3c12fba2525e5b7f7ba73fab10ddac386f8019b6cf2b8701239cf9f3e2a8a"}, + {file = "fonttools-4.44.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3675499e11a2332a867c1ce98792d29615ac143186c1c1d3e1bb7a13f1929b52"}, + {file = "fonttools-4.44.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fcf6c0c2bf2b0baeb55b5e44e9d6f2b71ede808949b8ab4daca077cc3f9cfff5"}, + {file = "fonttools-4.44.1-cp310-cp310-win32.whl", hash = "sha256:147c9f5fbe12486fa186b5ccdc64a537d581e4f9bbddfbc40f2a15a55c66f54e"}, + {file = "fonttools-4.44.1-cp310-cp310-win_amd64.whl", hash = "sha256:0bd45092788dbfb781fae299905695a3fe5c1956a515ee331c9f034da3a9d0e5"}, + {file = "fonttools-4.44.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d22d9a4cb3f0c96991d4dccab66c7377302c9ca09dcf0cbce968d73919585120"}, + {file = "fonttools-4.44.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0c1ac78c60b155ce709b50e28321baa3813dfae648bf55ac80d5a97c70d088e0"}, + {file = "fonttools-4.44.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6065578bdf96d4b69dd53d8b49ff02412b2a46d461b0d1ee5eddb81c3a953a46"}, + {file = "fonttools-4.44.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17702266ba92cef9a0d7418609f8f8b8e019192c62e8014f10b89a485af9d8ce"}, + {file = "fonttools-4.44.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c1d22a61a247262f178819f0331e0692e27c88be5770bf1c2404d0d52799f711"}, + {file = "fonttools-4.44.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:61b794e6a9208e7ee3abf11a9f56b9c1409967817dfd732f97b44812686cab1d"}, + {file = "fonttools-4.44.1-cp311-cp311-win32.whl", hash = "sha256:a02747ac741abe1fe994ac55b143432637d136e4a5a472e7a90574a015b57dc4"}, + {file = "fonttools-4.44.1-cp311-cp311-win_amd64.whl", hash = "sha256:8dd81470227c53ab78788f1b21b7e655b2c3aa66f0f670d9011f2deb64bed034"}, + {file = "fonttools-4.44.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:63efb6b66c275cb2c750576ed743f9995b92bcd733b72699599c6f74dce277c6"}, + {file = "fonttools-4.44.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25504368ce3dbdc5df1e6bee1980674b60216c543ad2647c85333f8daf5e9dd2"}, + {file = "fonttools-4.44.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4acf7f1684d234d788cbb1976fbced4e1ae7c51abaf4314e11d1d86498ba76"}, + {file = "fonttools-4.44.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6698abcbb43f68ecfe473169d5928adf0a09ab8e6439322f80bc10a80ab9195d"}, + {file = "fonttools-4.44.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6dce674ba22419a9f3822f9c1b6bd823fce11d3a34372c580216167b6d9e232b"}, + {file = "fonttools-4.44.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8493c84ac86fdc5dde68b720959b79865cf5216a5c1ee9b4a161eac8c56dc310"}, + {file = "fonttools-4.44.1-cp312-cp312-win32.whl", hash = "sha256:a4a8734ddb91647d9545caae4dfb4633045c5dccb2fccb2d6c2a09424d975ef1"}, + {file = "fonttools-4.44.1-cp312-cp312-win_amd64.whl", hash = "sha256:7debaae9f267702ac4e89944bbfc4e55bc2d0ef891aa6c18d6afd9184a14554a"}, + {file = "fonttools-4.44.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:748d21764665209d5e0729ce8386fd01c92258699db732c7dbe4c9abf9e7c392"}, + {file = "fonttools-4.44.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:753de1235ac308111f80eacb9d92b328088adfec7147fd101692cc49ad53a3fe"}, + {file = "fonttools-4.44.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c142c11d28af532c7edddf191367d6acf2a2884bb4e2ba329c265f58ca865d0a"}, + {file = "fonttools-4.44.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd10d40028bf71e9604279875e4c258a36b2a42fff349fdf20141813d83cc61"}, + {file = "fonttools-4.44.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8e4961e26423ddd713672746c110e708c0094deae74493e21198d85f54f7d88c"}, + {file = "fonttools-4.44.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af89a9370dc463ffed3010af6fad1aa58998ce5eb5d94c2c2688768e6b108cc8"}, + {file = "fonttools-4.44.1-cp38-cp38-win32.whl", hash = "sha256:6d2d0e0d64a21b07c30206d500f8e77f4beaf80e7cc0ffd65a304a9ae1c0e197"}, + {file = "fonttools-4.44.1-cp38-cp38-win_amd64.whl", hash = "sha256:dc16e26668ec2ae01a37ff8293ce0326cf8c043e24fcf86fc38e6c25ddd98926"}, + {file = "fonttools-4.44.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a04ad39ac67c523c9f8f102706ac05d7e5cee3148a3519c6afc6ffbb3f0af7aa"}, + {file = "fonttools-4.44.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:11e9cba26fd658a491c82fdf5dc5bdb8078ca69ca70ba5724f63a66d486fa8b3"}, + {file = "fonttools-4.44.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fa904fb50c9f1b3ffbe352c7c4ed35eb16558933f011ff74f86f33504358e4d"}, + {file = "fonttools-4.44.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caf014bcc24673b681e7f768180f063691b301e2eccd9a53c43b5eebfb448bd8"}, + {file = "fonttools-4.44.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:97fb6f806694268d0c35abfc1f33662a1a96d12875a790b2b69d7b8d4fadbea5"}, + {file = "fonttools-4.44.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac523156bf227f009102cf33c116bcc18d6b9a05ea9a3a6eaa57e3adb42620a9"}, + {file = "fonttools-4.44.1-cp39-cp39-win32.whl", hash = "sha256:50aacecec89ca07ba97a63a949f9b273ccbdc105602ec4426c8a9a143f9e6aa3"}, + {file = "fonttools-4.44.1-cp39-cp39-win_amd64.whl", hash = "sha256:994c62a46cb2cfd670edc360d87c902ee475790fbddb267abd9fd8a83199423a"}, + {file = "fonttools-4.44.1-py3-none-any.whl", hash = "sha256:e775851c6884c16ed3831e461a0d5e271d9ebcd05204122d3a21ca2465a5d8c1"}, + {file = "fonttools-4.44.1.tar.gz", hash = "sha256:0d8ed83815a125b25c10404736a2cd43d60eb6479fe2d68373418cd1822ec330"}, ] [package.extras] @@ -1370,13 +1370,13 @@ rewrite = ["tokenize-rt (>=3)"] [[package]] name = "geopandas" -version = "0.14.0" +version = "0.14.1" description = "Geographic pandas extensions" optional = false python-versions = ">=3.9" files = [ - {file = "geopandas-0.14.0-py3-none-any.whl", hash = "sha256:a402a565e727642cb44a500c911f226eea26c1b1247c6586827031e3d7a9403a"}, - {file = "geopandas-0.14.0.tar.gz", hash = "sha256:ea6c031889e1e1888aecaa6e182ca620d78f63551c49b3002a998bcbb280531f"}, + {file = "geopandas-0.14.1-py3-none-any.whl", hash = "sha256:ed5a7cae7874bfc3238fb05e0501cc1760e1b7b11e5b76ecad29da644ca305da"}, + {file = "geopandas-0.14.1.tar.gz", hash = "sha256:4853ff89ecb6d1cfc43e7b3671092c8160e8a46a3dd7368f25906283314e42bb"}, ] [package.dependencies] @@ -2481,38 +2481,38 @@ files = [ [[package]] name = "mypy" -version = "1.6.1" +version = "1.7.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"}, - {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"}, - {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"}, - {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"}, - {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"}, - {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, - {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, - {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, - {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, - {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, - {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, - {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, - {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, - {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, - {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, - {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"}, - {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"}, - {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"}, - {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"}, - {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"}, - {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"}, - {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"}, - {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"}, - {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"}, - {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"}, - {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, - {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, + {file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"}, + {file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"}, + {file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"}, + {file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"}, + {file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"}, + {file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"}, + {file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"}, + {file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"}, + {file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"}, + {file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"}, + {file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"}, + {file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"}, + {file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"}, + {file = "mypy-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cddee95dea7990e2215576fae95f6b78a8c12f4c089d7e4367564704e99118d3"}, + {file = "mypy-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d01921dbd691c4061a3e2ecdbfbfad029410c5c2b1ee88946bf45c62c6c91210"}, + {file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"}, + {file = "mypy-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7b1e399c47b18feb6f8ad4a3eef3813e28c1e871ea7d4ea5d444b2ac03c418"}, + {file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"}, + {file = "mypy-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d0fa29919d2e720c8dbaf07d5578f93d7b313c3e9954c8ec05b6d83da592e5d9"}, + {file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"}, + {file = "mypy-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1b06b4b109e342f7dccc9efda965fc3970a604db70f8560ddfdee7ef19afb05"}, + {file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"}, + {file = "mypy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551d4a0cdcbd1d2cccdcc7cb516bb4ae888794929f5b040bb51aae1846062901"}, + {file = "mypy-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55d28d7963bef00c330cb6461db80b0b72afe2f3c4e2963c99517cf06454e665"}, + {file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"}, + {file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"}, + {file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"}, ] [package.dependencies] @@ -2522,6 +2522,7 @@ typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] @@ -2734,35 +2735,35 @@ reference = ["Pillow", "google-re2"] [[package]] name = "onnxruntime" -version = "1.16.1" +version = "1.16.2" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime-1.16.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:28b2c7f444b4119950b69370801cd66067f403d19cbaf2a444735d7c269cce4a"}, - {file = "onnxruntime-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c24e04f33e7899f6aebb03ed51e51d346c1f906b05c5569d58ac9a12d38a2f58"}, - {file = "onnxruntime-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fa93b166f2d97063dc9f33c5118c5729a4a5dd5617296b6dbef42f9047b3e81"}, - {file = "onnxruntime-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042dd9201b3016ee18f8f8bc4609baf11ff34ca1ff489c0a46bcd30919bf883d"}, - {file = "onnxruntime-1.16.1-cp310-cp310-win32.whl", hash = "sha256:c20aa0591f305012f1b21aad607ed96917c86ae7aede4a4dd95824b3d124ceb7"}, - {file = "onnxruntime-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:5581873e578917bea76d6434ee7337e28195d03488dcf72d161d08e9398c6249"}, - {file = "onnxruntime-1.16.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:ef8c0c8abf5f309aa1caf35941380839dc5f7a2fa53da533be4a3f254993f120"}, - {file = "onnxruntime-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e680380bea35a137cbc3efd67a17486e96972901192ad3026ee79c8d8fe264f7"}, - {file = "onnxruntime-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e62cc38ce1a669013d0a596d984762dc9c67c56f60ecfeee0d5ad36da5863f6"}, - {file = "onnxruntime-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:025c7a4d57bd2e63b8a0f84ad3df53e419e3df1cc72d63184f2aae807b17c13c"}, - {file = "onnxruntime-1.16.1-cp311-cp311-win32.whl", hash = "sha256:9ad074057fa8d028df248b5668514088cb0937b6ac5954073b7fb9b2891ffc8c"}, - {file = "onnxruntime-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:d5e43a3478bffc01f817ecf826de7b25a2ca1bca8547d70888594ab80a77ad24"}, - {file = "onnxruntime-1.16.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3aef4d70b0930e29a8943eab248cd1565664458d3a62b2276bd11181f28fd0a3"}, - {file = "onnxruntime-1.16.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:55a7b843a57c8ca0c8ff169428137958146081d5d76f1a6dd444c4ffcd37c3c2"}, - {file = "onnxruntime-1.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c631af1941bf3b5f7d063d24c04aacce8cff0794e157c497e315e89ac5ad7b"}, - {file = "onnxruntime-1.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671f296c3d5c233f601e97a10ab5a1dd8e65ba35c7b7b0c253332aba9dff330"}, - {file = "onnxruntime-1.16.1-cp38-cp38-win32.whl", hash = "sha256:eb3802305023dd05e16848d4e22b41f8147247894309c0c27122aaa08793b3d2"}, - {file = "onnxruntime-1.16.1-cp38-cp38-win_amd64.whl", hash = "sha256:fecfb07443d09d271b1487f401fbdf1ba0c829af6fd4fe8f6af25f71190e7eb9"}, - {file = "onnxruntime-1.16.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:de3e12094234db6545c67adbf801874b4eb91e9f299bda34c62967ef0050960f"}, - {file = "onnxruntime-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff723c2a5621b5e7103f3be84d5aae1e03a20621e72219dddceae81f65f240af"}, - {file = "onnxruntime-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14a7fb3073aaf6b462e3d7fb433320f7700558a8892e5021780522dc4574292a"}, - {file = "onnxruntime-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:963159f1f699b0454cd72fcef3276c8a1aab9389a7b301bcd8e320fb9d9e8597"}, - {file = "onnxruntime-1.16.1-cp39-cp39-win32.whl", hash = "sha256:85771adb75190db9364b25ddec353ebf07635b83eb94b64ed014f1f6d57a3857"}, - {file = "onnxruntime-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:d32d2b30799c1f950123c60ae8390818381fd5f88bdf3627eeca10071c155dc5"}, + {file = "onnxruntime-1.16.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:e19316bb15c29ca0397e78861ee7cdb4db763ac5c53eaa83169bcdcb1149878c"}, + {file = "onnxruntime-1.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:773f6d99d1e6a58936a55a4933c66674241dace9ec4bab71664cdfa170a7cd87"}, + {file = "onnxruntime-1.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b8df9583a6e874f1983b85a361d22c205c96e926626eb486d3e69d72642f79"}, + {file = "onnxruntime-1.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceef600de846997e3ef5f9af956ae87c88d84d6e925c3e9d435ce17ea223568f"}, + {file = "onnxruntime-1.16.2-cp310-cp310-win32.whl", hash = "sha256:4fed41edb766c6adea6c34f1eb63a344d697fd4625133e5e48f23950bce60803"}, + {file = "onnxruntime-1.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:9fc410ec220804fb384e7cb4fd68c474d89da11a1b68184db2001d64ba1477a9"}, + {file = "onnxruntime-1.16.2-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:aa09d8d9d9a4dc2f6647b5135bb540da36e2d78206aaf14140ba73e05928c4f8"}, + {file = "onnxruntime-1.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68f8d3347f11fcc6256266c562e4314b8c6da3e30fc275052a2ab693540b17fd"}, + {file = "onnxruntime-1.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16217fa87d3482300a91036f9b499c85215a3b495de1ef9a68cbcf3df1a7c548"}, + {file = "onnxruntime-1.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6b7046005442fcd09b86647bdc9a85d60c1367cb36ce7f16b942744cf27fe4"}, + {file = "onnxruntime-1.16.2-cp311-cp311-win32.whl", hash = "sha256:773c231e526f815b8a3f3549d216cd8fed4c9e226e9e16e86af1b69a4bd29b58"}, + {file = "onnxruntime-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:90e83a93b3d946c4a1d9dcbae286350accb0d80512d7c1b85953a444d19c0058"}, + {file = "onnxruntime-1.16.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:8616f56905775dd8beeae11cf145542fff06c38cd97bfe9afe0c4a66142fc6d5"}, + {file = "onnxruntime-1.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9f5e1d5ca5560044896edb2ad79113f863dc7daa804a26787c7b21c2a96d41e7"}, + {file = "onnxruntime-1.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97ce538ffb668c4897e7500a586c150a045869876e0234e0611c4e4f428be63"}, + {file = "onnxruntime-1.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cadf175baa782599f36586c23f84fe12b02702ceb59be57dbd8eefc6cc13cc4"}, + {file = "onnxruntime-1.16.2-cp38-cp38-win32.whl", hash = "sha256:0ffd3b8a3039be713476b8783d254564976664c9b51ec70e7fb5d3e2832bf0f0"}, + {file = "onnxruntime-1.16.2-cp38-cp38-win_amd64.whl", hash = "sha256:e2211f336e83819edbf174dcf56de35b0dcbfc6c92d3b685c8d85fba19bdf97d"}, + {file = "onnxruntime-1.16.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:98a49bda980bcf819f8d9be880e3e7ba8a1df66aa5ce4fc7bb68ba9acf1fc7ad"}, + {file = "onnxruntime-1.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f1e90fa0f43e988cd043e5a4b1eb77eda6cbd7523f316d93d36b33ff1ceb91f"}, + {file = "onnxruntime-1.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0cbdb7df8078b2e8d9804de948963961eb8c6f417ef35ed243455162a9a065c"}, + {file = "onnxruntime-1.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93c1cbd885c5fe0018b982c9dabe3cc3531416a3b50d0958a291605b32fe3ce"}, + {file = "onnxruntime-1.16.2-cp39-cp39-win32.whl", hash = "sha256:713101b65d74438f380f5ea2475ce4f6026171e6229100e5be2baa92519fca17"}, + {file = "onnxruntime-1.16.2-cp39-cp39-win_amd64.whl", hash = "sha256:3382934f9d86060b6bacd3eb4633c5ff904be2c99d3a7fb7faf2828381b15928"}, ] [package.dependencies] @@ -2775,19 +2776,19 @@ sympy = "*" [[package]] name = "onnxruntime-gpu" -version = "1.16.1" +version = "1.16.2" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime_gpu-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4f4b964c15832bf414bbdec272de070c6be3b1fe97f2b7fdcb18a9a1143ac0"}, - {file = "onnxruntime_gpu-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:55e3713e57203d2785f9ec3b23883a3ed13fcf795f26aa615db0e6a4a9cf0f91"}, - {file = "onnxruntime_gpu-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4de0d8ffe43339f8b0a8b528629cb7b5615786673cde526a092bfebe821fd7b"}, - {file = "onnxruntime_gpu-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:532bad0e89ec072881c5236da0a458ab2a56d333075d1a656dce206a3100775d"}, - {file = "onnxruntime_gpu-1.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a255af5f5b3e630fe0061f55da707923aa80821e24841043f69d3faf49060bf"}, - {file = "onnxruntime_gpu-1.16.1-cp38-cp38-win_amd64.whl", hash = "sha256:6158b08ac0b9abf59cf72e36d31fc0b2ed2067f627459bb878338017699841d7"}, - {file = "onnxruntime_gpu-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17c35c845831e3f4929e4e6a7c91ead0f1ceeb5dcf1bdf2680d359d03ee718d2"}, - {file = "onnxruntime_gpu-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8eb2221dff94db1815278c84cc5e84f104bbe49a2269749bc68baaa73f3583f"}, + {file = "onnxruntime_gpu-1.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cfcac6d84ca94b1785c95c6412edc7d7a894055c2fbb854c75f875fb44b2df9"}, + {file = "onnxruntime_gpu-1.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:6713feab198ad67db44c9354c052603872b926a4175e496a7b5069224d7b75bc"}, + {file = "onnxruntime_gpu-1.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f10399d051dae1c496ee812e388da74bf03888ad0e01dcac4859eed4750f8c42"}, + {file = "onnxruntime_gpu-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:bae364c4bb281c654e5ee5f74fd1779ca3517ab1801cd81cfd3014aae490592e"}, + {file = "onnxruntime_gpu-1.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2638e46d4139c8e1eb08246661ae51ef206c90178f1d8872695681e157b8a44"}, + {file = "onnxruntime_gpu-1.16.2-cp38-cp38-win_amd64.whl", hash = "sha256:65848e0a01546a78207824c44e893dc03eead71ce22bf70530f53fbe358b0e3d"}, + {file = "onnxruntime_gpu-1.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a692c53604558dc80880401875f6e975c544af141f650c295b970a3fa59c723"}, + {file = "onnxruntime_gpu-1.16.2-cp39-cp39-win_amd64.whl", hash = "sha256:42ba53e866efa59d03e9ef5b21b1fb4e11222600b8843b91c488138e091dfca7"}, ] [package.dependencies] @@ -2966,36 +2967,36 @@ test = ["pylint (==2.4.*)", "pytest", "pytest-pylint"] [[package]] name = "pandas" -version = "2.1.2" +version = "2.1.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:24057459f19db9ebb02984c6fdd164a970b31a95f38e4a49cf7615b36a1b532c"}, - {file = "pandas-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6cf8fcc8a63d333970b950a7331a30544cf59b1a97baf0a7409e09eafc1ac38"}, - {file = "pandas-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ae6ffbd9d614c20d028c7117ee911fc4e266b4dca2065d5c5909e401f8ff683"}, - {file = "pandas-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff794eeb7883c5aefb1ed572e7ff533ae779f6c6277849eab9e77986e352688"}, - {file = "pandas-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02954e285e8e2f4006b6f22be6f0df1f1c3c97adbb7ed211c6b483426f20d5c8"}, - {file = "pandas-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:5b40c9f494e1f27588c369b9e4a6ca19cd924b3a0e1ef9ef1a8e30a07a438f43"}, - {file = "pandas-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08d287b68fd28906a94564f15118a7ca8c242e50ae7f8bd91130c362b2108a81"}, - {file = "pandas-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bbd98dcdcd32f408947afdb3f7434fade6edd408c3077bbce7bd840d654d92c6"}, - {file = "pandas-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90c95abb3285d06f6e4feedafc134306a8eced93cb78e08cf50e224d5ce22e2"}, - {file = "pandas-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52867d69a54e71666cd184b04e839cff7dfc8ed0cd6b936995117fdae8790b69"}, - {file = "pandas-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8d0382645ede2fde352da2a885aac28ec37d38587864c0689b4b2361d17b1d4c"}, - {file = "pandas-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:65177d1c519b55e5b7f094c660ed357bb7d86e799686bb71653b8a4803d8ff0d"}, - {file = "pandas-2.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5aa6b86802e8cf7716bf4b4b5a3c99b12d34e9c6a9d06dad254447a620437931"}, - {file = "pandas-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d594e2ce51b8e0b4074e6644758865dc2bb13fd654450c1eae51201260a539f1"}, - {file = "pandas-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3223f997b6d2ebf9c010260cf3d889848a93f5d22bb4d14cd32638b3d8bba7ad"}, - {file = "pandas-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4944dc004ca6cc701dfa19afb8bdb26ad36b9bed5bcec617d2a11e9cae6902"}, - {file = "pandas-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3f76280ce8ec216dde336e55b2b82e883401cf466da0fe3be317c03fb8ee7c7d"}, - {file = "pandas-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:7ad20d24acf3a0042512b7e8d8fdc2e827126ed519d6bd1ed8e6c14ec8a2c813"}, - {file = "pandas-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:021f09c15e1381e202d95d4a21ece8e7f2bf1388b6d7e9cae09dfe27bd2043d1"}, - {file = "pandas-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7f12b2de0060b0b858cfec0016e7d980ae5bae455a1746bfcc70929100ee633"}, - {file = "pandas-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c166b9bb27c1715bed94495d9598a7f02950b4749dba9349c1dd2cbf10729d"}, - {file = "pandas-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25c9976c17311388fcd953cb3d0697999b2205333f4e11e669d90ff8d830d429"}, - {file = "pandas-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:851b5afbb0d62f6129ae891b533aa508cc357d5892c240c91933d945fff15731"}, - {file = "pandas-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:e78507adcc730533619de07bfdd1c62b2918a68cd4419ea386e28abf7f6a1e5c"}, - {file = "pandas-2.1.2.tar.gz", hash = "sha256:52897edc2774d2779fbeb6880d2cfb305daa0b1a29c16b91f531a18918a6e0f3"}, + {file = "pandas-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f"}, + {file = "pandas-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb"}, + {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb"}, + {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4"}, + {file = "pandas-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03"}, + {file = "pandas-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e"}, + {file = "pandas-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82"}, + {file = "pandas-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042"}, + {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef"}, + {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58"}, + {file = "pandas-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2"}, + {file = "pandas-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f"}, + {file = "pandas-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140"}, + {file = "pandas-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d"}, + {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e"}, + {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683"}, + {file = "pandas-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00"}, + {file = "pandas-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549"}, + {file = "pandas-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8"}, + {file = "pandas-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d"}, + {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a"}, + {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2"}, + {file = "pandas-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a"}, + {file = "pandas-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71"}, + {file = "pandas-2.1.3.tar.gz", hash = "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f"}, ] [package.dependencies] @@ -3005,7 +3006,7 @@ pytz = ">=2020.1" tzdata = ">=2022.1" [package.extras] -all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] +all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] aws = ["s3fs (>=2022.05.0)"] clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] compression = ["zstandard (>=0.17.0)"] @@ -3025,7 +3026,7 @@ plot = ["matplotlib (>=3.6.1)"] postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] spss = ["pyreadstat (>=1.1.5)"] sql-other = ["SQLAlchemy (>=1.4.36)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.8.0)"] [[package]] @@ -3919,13 +3920,13 @@ pytest = ">=3.1" [[package]] name = "pytest-xdist" -version = "3.3.1" +version = "3.4.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, - {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, + {file = "pytest-xdist-3.4.0.tar.gz", hash = "sha256:3a94a931dd9e268e0b871a877d09fe2efb6175c2c23d60d56a6001359002b832"}, + {file = "pytest_xdist-3.4.0-py3-none-any.whl", hash = "sha256:e513118bf787677a427e025606f55e95937565e06dfaac8d87f55301e57ae607"}, ] [package.dependencies] @@ -4189,28 +4190,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.1.4" +version = "0.1.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.4-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:864958706b669cce31d629902175138ad8a069d99ca53514611521f532d91495"}, - {file = "ruff-0.1.4-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:9fdd61883bb34317c788af87f4cd75dfee3a73f5ded714b77ba928e418d6e39e"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4eaca8c9cc39aa7f0f0d7b8fe24ecb51232d1bb620fc4441a61161be4a17539"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a9a1301dc43cbf633fb603242bccd0aaa34834750a14a4c1817e2e5c8d60de17"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e8db8ab6f100f02e28b3d713270c857d370b8d61871d5c7d1702ae411df683"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:80fea754eaae06335784b8ea053d6eb8e9aac75359ebddd6fee0858e87c8d510"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bc02a480d4bfffd163a723698da15d1a9aec2fced4c06f2a753f87f4ce6969c"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862811b403063765b03e716dac0fda8fdbe78b675cd947ed5873506448acea4"}, - {file = "ruff-0.1.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58826efb8b3efbb59bb306f4b19640b7e366967a31c049d49311d9eb3a4c60cb"}, - {file = "ruff-0.1.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:fdfd453fc91d9d86d6aaa33b1bafa69d114cf7421057868f0b79104079d3e66e"}, - {file = "ruff-0.1.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e8791482d508bd0b36c76481ad3117987301b86072158bdb69d796503e1c84a8"}, - {file = "ruff-0.1.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01206e361021426e3c1b7fba06ddcb20dbc5037d64f6841e5f2b21084dc51800"}, - {file = "ruff-0.1.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:645591a613a42cb7e5c2b667cbefd3877b21e0252b59272ba7212c3d35a5819f"}, - {file = "ruff-0.1.4-py3-none-win32.whl", hash = "sha256:99908ca2b3b85bffe7e1414275d004917d1e0dfc99d497ccd2ecd19ad115fd0d"}, - {file = "ruff-0.1.4-py3-none-win_amd64.whl", hash = "sha256:1dfd6bf8f6ad0a4ac99333f437e0ec168989adc5d837ecd38ddb2cc4a2e3db8a"}, - {file = "ruff-0.1.4-py3-none-win_arm64.whl", hash = "sha256:d98ae9ebf56444e18a3e3652b3383204748f73e247dea6caaf8b52d37e6b32da"}, - {file = "ruff-0.1.4.tar.gz", hash = "sha256:21520ecca4cc555162068d87c747b8f95e1e95f8ecfcbbe59e8dd00710586315"}, + {file = "ruff-0.1.5-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:32d47fc69261c21a4c48916f16ca272bf2f273eb635d91c65d5cd548bf1f3d96"}, + {file = "ruff-0.1.5-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:171276c1df6c07fa0597fb946139ced1c2978f4f0b8254f201281729981f3c17"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ef33cd0bb7316ca65649fc748acc1406dfa4da96a3d0cde6d52f2e866c7b39"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2c205827b3f8c13b4a432e9585750b93fd907986fe1aec62b2a02cf4401eee6"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb408e3a2ad8f6881d0f2e7ad70cddb3ed9f200eb3517a91a245bbe27101d379"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f20dc5e5905ddb407060ca27267c7174f532375c08076d1a953cf7bb016f5a24"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aafb9d2b671ed934998e881e2c0f5845a4295e84e719359c71c39a5363cccc91"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4894dddb476597a0ba4473d72a23151b8b3b0b5f958f2cf4d3f1c572cdb7af7"}, + {file = "ruff-0.1.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00a7ec893f665ed60008c70fe9eeb58d210e6b4d83ec6654a9904871f982a2a"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8c11206b47f283cbda399a654fd0178d7a389e631f19f51da15cbe631480c5b"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fa29e67b3284b9a79b1a85ee66e293a94ac6b7bb068b307a8a373c3d343aa8ec"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9b97fd6da44d6cceb188147b68db69a5741fbc736465b5cea3928fdac0bc1aeb"}, + {file = "ruff-0.1.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:721f4b9d3b4161df8dc9f09aa8562e39d14e55a4dbaa451a8e55bdc9590e20f4"}, + {file = "ruff-0.1.5-py3-none-win32.whl", hash = "sha256:f80c73bba6bc69e4fdc73b3991db0b546ce641bdcd5b07210b8ad6f64c79f1ab"}, + {file = "ruff-0.1.5-py3-none-win_amd64.whl", hash = "sha256:c21fe20ee7d76206d290a76271c1af7a5096bc4c73ab9383ed2ad35f852a0087"}, + {file = "ruff-0.1.5-py3-none-win_arm64.whl", hash = "sha256:82bfcb9927e88c1ed50f49ac6c9728dab3ea451212693fe40d08d314663e412f"}, + {file = "ruff-0.1.5.tar.gz", hash = "sha256:5cbec0ef2ae1748fb194f420fb03fb2c25c3258c86129af7172ff8f198f125ab"}, ] [[package]] @@ -4978,18 +4979,17 @@ files = [ [[package]] name = "urllib3" -version = "2.0.7" +version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, - {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] From b3c7a0c8712907b877a53b571dcfce8f5cf8ace8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 14 Nov 2023 15:43:57 -0800 Subject: [PATCH 026/724] remove mui --- selfdrive/ui/.gitignore | 1 - selfdrive/ui/SConscript | 3 - selfdrive/ui/mui.cc | 132 ---------------------------------------- 3 files changed, 136 deletions(-) delete mode 100644 selfdrive/ui/mui.cc diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index cb95652191..8ad5d39493 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -3,7 +3,6 @@ moc_* translations/main_test_en.* -_mui watch3 installer/installers/* qt/text diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 036164d6cc..916f1017a3 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -99,9 +99,6 @@ if GetOption('extras') and arch != "Darwin": # build updater UI qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) - # build mui - qt_env.Program("_mui", ["mui.cc"], LIBS=qt_libs) - # build installers senv = qt_env.Clone() senv['LINKFLAGS'].append('-Wl,-strip-debug') diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc deleted file mode 100644 index e2b4358b65..0000000000 --- a/selfdrive/ui/mui.cc +++ /dev/null @@ -1,132 +0,0 @@ -#include -#include -#include -#include - -#include "cereal/messaging/messaging.h" -#include "selfdrive/ui/ui.h" -#include "selfdrive/ui/qt/qt_window.h" - -class StatusBar : public QGraphicsRectItem { - private: - QLinearGradient linear_gradient; - QRadialGradient radial_gradient; - QTimer animation_timer; - const int animation_length = 10; - int animation_index = 0; - - public: - StatusBar(double x, double y, double width, double height) : QGraphicsRectItem {x, y, width, height} { - linear_gradient = QLinearGradient(0, 0, 0, height/2); - linear_gradient.setSpread(QGradient::ReflectSpread); - - radial_gradient = QRadialGradient(width/2, height/2, width/8); - QObject::connect(&animation_timer, &QTimer::timeout, [=]() { - animation_index++; - animation_index %= animation_length; - }); - animation_timer.start(50); - } - - void solidColor(QColor color) { - QColor dark_color = QColor(color); - dark_color.setAlphaF(0.5); - - linear_gradient.setColorAt(0, dark_color); - linear_gradient.setColorAt(1, color); - setBrush(QBrush(linear_gradient)); - } - - // these need to be called continuously for the animations to work. - // can probably clean that up with some more abstractions - void blinkingColor(QColor color) { - QColor dark_color = QColor(color); - dark_color.setAlphaF(0.1); - - int radius = (rect().width() / animation_length) * animation_index; - QPoint center = QPoint(rect().width()/2, rect().height()/2); - radial_gradient.setCenter(center); - radial_gradient.setFocalPoint(center); - radial_gradient.setRadius(radius); - - radial_gradient.setColorAt(1, dark_color); - radial_gradient.setColorAt(0, color); - setBrush(QBrush(radial_gradient)); - } - - void laneChange(cereal::LateralPlan::LaneChangeDirection direction) { - QColor dark_color = QColor(bg_colors[STATUS_ENGAGED]); - dark_color.setAlphaF(0.1); - - int x = (rect().width() / animation_length) * animation_index; - QPoint center = QPoint(((direction == cereal::LateralPlan::LaneChangeDirection::RIGHT) ? x : (rect().width() - x)), rect().height()/2); - radial_gradient.setCenter(center); - radial_gradient.setFocalPoint(center); - radial_gradient.setRadius(rect().width()/5); - - radial_gradient.setColorAt(1, dark_color); - radial_gradient.setColorAt(0, bg_colors[STATUS_ENGAGED]); - setBrush(QBrush(radial_gradient)); - } - - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override { - painter->setPen(QPen()); - painter->setBrush(brush()); - - double rounding_radius = rect().height()/2; - painter->drawRoundedRect(rect(), rounding_radius, rounding_radius); - } -}; - -int main(int argc, char *argv[]) { - QApplication a(argc, argv); - QWidget w; - setMainWindow(&w); - - w.setStyleSheet("background-color: black;"); - - // our beautiful UI - QVBoxLayout *layout = new QVBoxLayout(&w); - - QGraphicsScene *scene = new QGraphicsScene(); - StatusBar *status_bar = new StatusBar(0, 0, 1000, 50); - scene->addItem(status_bar); - - QGraphicsView *graphics_view = new QGraphicsView(scene); - layout->insertSpacing(0, 400); - layout->addWidget(graphics_view, 0, Qt::AlignCenter); - - QTimer timer; - QObject::connect(&timer, &QTimer::timeout, [=]() { - static SubMaster sm({"deviceState", "controlsState", "lateralPlan"}); - - bool onroad_prev = sm.allAliveAndValid({"deviceState"}) && - sm["deviceState"].getDeviceState().getStarted(); - sm.update(0); - - bool onroad = sm.allAliveAndValid({"deviceState"}) && - sm["deviceState"].getDeviceState().getStarted(); - - if (onroad) { - auto cs = sm["controlsState"].getControlsState(); - UIStatus status = cs.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; - auto lp = sm["lateralPlan"].getLateralPlan(); - if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::PRE_LANE_CHANGE) { - status_bar->blinkingColor(bg_colors[status]); - } else if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::LANE_CHANGE_STARTING || - lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::LANE_CHANGE_FINISHING) { - status_bar->laneChange(lp.getLaneChangeDirection()); - } else { - status_bar->solidColor(bg_colors[status]); - } - } - - if ((onroad != onroad_prev) || sm.frame < 2) { - Hardware::set_brightness(50); - Hardware::set_display_power(onroad); - } - }); - timer.start(50); - - return a.exec(); -} From f99939426f5f654ee7c59ab39689454f58905451 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 14 Nov 2023 16:57:34 -0800 Subject: [PATCH 027/724] thermald: recover from modemmanager restart (#30464) * test modem recovery * static analysis * also test lte reload * this should just be try catched as well * maybe we can find a better way than this * rm the test * make it clear --------- Co-authored-by: Comma Device --- system/hardware/tici/hardware.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 8b1f9abe74..3b5647cc56 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -105,7 +105,7 @@ class Tici(HardwareBase): def nm(self): return self.bus.get_object(NM, '/org/freedesktop/NetworkManager') - @cached_property + @property # this should not be cached, in case the modemmanager restarts def mm(self): return self.bus.get_object(MM, '/org/freedesktop/ModemManager1') @@ -211,8 +211,8 @@ class Tici(HardwareBase): return str(self.get_modem().Get(MM_MODEM, 'EquipmentIdentifier', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)) def get_network_info(self): - modem = self.get_modem() try: + modem = self.get_modem() info = modem.Command("AT+QNWINFO", math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT) extra = modem.Command('AT+QENG="servingcell"', math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT) state = modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) From 31c682d40743f4b102a70d72e6993b383d85a040 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 14 Nov 2023 17:33:47 -0800 Subject: [PATCH 028/724] Pytest: load by group by default (#30466) * wip * use a class marker * this one should be grouped too --- conftest.py | 15 ++++++++++++++- pyproject.toml | 2 +- selfdrive/car/tests/test_models.py | 3 ++- .../locationd/test/test_locationd_scenarios.py | 2 ++ selfdrive/test/process_replay/test_processes.py | 1 + 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/conftest.py b/conftest.py index 08ddb0d1ea..9aaf04d798 100644 --- a/conftest.py +++ b/conftest.py @@ -37,8 +37,21 @@ def openpilot_class_fixture(): os.environ.update(starting_env) +@pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(config, items): skipper = pytest.mark.skip(reason="Skipping tici test on PC") for item in items: if not TICI and "tici" in item.keywords: - item.add_marker(skipper) \ No newline at end of file + item.add_marker(skipper) + + if "xdist_group_class_property" in item.keywords: + class_property = item.get_closest_marker('xdist_group_class_property').args[0] + item.add_marker(pytest.mark.xdist_group(getattr(item.cls, class_property))) + + +@pytest.hookimpl(trylast=True) +def pytest_configure(config): + config_line = ( + "xdist_group_class_property: group tests by a property of the class that contains them" + ) + config.addinivalue_line("markers", config_line) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4e494e65fc..8a5769bcc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadscope" +addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" cpp_files = "test_*" python_files = "test_*.py" #timeout = "30" # you get this long by default diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index b9622437a0..9b07dc20e3 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -50,7 +50,7 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: for i, c in enumerate(sorted(all_known_cars())): if i % NUM_JOBS == JOB_ID: - test_cases.extend(sorted((c, r) for r in routes_by_car.get(c, (None,)))) + test_cases.extend(sorted((c.value, r) for r in routes_by_car.get(c, (None,)))) else: with open(os.path.join(BASEDIR, INTERNAL_SEG_LIST), "r") as f: @@ -385,6 +385,7 @@ class TestCarModelBase(unittest.TestCase): @parameterized_class(('car_model', 'test_route'), get_test_cases()) +@pytest.mark.xdist_group_class_property('car_model') class TestCarModel(TestCarModelBase): pass diff --git a/selfdrive/locationd/test/test_locationd_scenarios.py b/selfdrive/locationd/test/test_locationd_scenarios.py index d2455ef9e0..b1792e0fd8 100755 --- a/selfdrive/locationd/test/test_locationd_scenarios.py +++ b/selfdrive/locationd/test/test_locationd_scenarios.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import pytest import unittest import numpy as np from collections import defaultdict @@ -97,6 +98,7 @@ def run_scenarios(scenario, logs): return get_select_fields_data(logs), get_select_fields_data(replayed_logs) +@pytest.mark.xdist_group("test_locationd_scenarios") class TestLocationdScenarios(unittest.TestCase): """ Test locationd with different scenarios. In all these scenarios, we expect the following: diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index efdd166cac..eb01e50e33 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -63,6 +63,7 @@ ALL_CARS = sorted({car for car, _ in segments}) @pytest.mark.slow @parameterized_class(('case_name', 'segment'), segments) +@pytest.mark.xdist_group_class_property('case_name') class TestCarProcessReplay(TestProcessReplayDiffBase): """ Runs a replay diff on a segment for each car. From 102687e1acc658e2a25df58d67b635551a3bd503 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 14 Nov 2023 18:45:31 -0800 Subject: [PATCH 029/724] Sort car ports --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index ec1e67e3a7..c95816b2f8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -11,8 +11,8 @@ Version 0.9.5 (2023-11-16) * Hyundai Ioniq 6 2023 support thanks to sunnyhaibin, alamo3, and sshane! * Hyundai Kona Electric 2023 (Korean version) support thanks to sunnyhaibin and haram-KONA! * Kia K8 Hybrid (with HDA II) 2023 support thanks to sunnyhaibin! -* Kia Sorento Hybrid 2023 support thanks to sunnyhaibin! * Kia Optima Hybrid 2019 support +* Kia Sorento Hybrid 2023 support thanks to sunnyhaibin! * Lexus GS F 2016 support thanks to snyperifle! * Lexus IS 2023 support thanks to L3R5! From 7ded7a072027ce235b5166e50f695dd184585481 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 15 Nov 2023 13:44:22 +0800 Subject: [PATCH 030/724] cabana: fix double comparisons (#30468) compare double with epsilon --- tools/cabana/chart/chart.cc | 7 +++++-- tools/cabana/streams/abstractstream.cc | 5 ++++- tools/cabana/streams/abstractstream.h | 4 ++-- tools/cabana/streams/livestream.h | 1 - tools/cabana/streams/replaystream.h | 1 - 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 4cd8eaf8b4..6f08a9f20b 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -22,7 +22,9 @@ // ChartAxisElement's padding is 4 (https://codebrowser.dev/qt5/qtcharts/src/charts/axis/chartaxiselement_p.h.html) const int AXIS_X_TOP_MARGIN = 4; -static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; } +// Define a small value of epsilon to compare double values +const float EPSILON = 0.000001; +static inline bool xLessThan(const QPointF &p, float x) { return p.x() < (x - EPSILON); } ChartView::ChartView(const std::pair &x_range, ChartsWidget *parent) : charts_widget(parent), QChartView(parent) { @@ -768,7 +770,8 @@ void ChartView::drawSignalValue(QPainter *painter) { painter->setPen(chart()->legend()->labelColor()); int i = 0; for (auto &s : sigs) { - auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x; }); + auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, + [](auto &p, double x) { return p.x() > x + EPSILON; }); QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? s.sig->formatValue(it->y()) : "--"; QRectF marker_rect = legend_markers[i++]->sceneBoundingRect(); QRectF value_rect(marker_rect.bottomLeft() - QPoint(0, 1), marker_rect.size()); diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc index c68259ed8b..8a20086b5c 100644 --- a/tools/cabana/streams/abstractstream.cc +++ b/tools/cabana/streams/abstractstream.cc @@ -91,7 +91,9 @@ void AbstractStream::updateLastMessages() { { std::lock_guard lk(mutex_); for (const auto &id : new_msgs_) { - last_msgs[id] = messages_[id]; + const auto &can_data = messages_[id]; + current_sec_ = std::max(current_sec_, can_data.ts); + last_msgs[id] = can_data; sources.insert(id.source); } msgs = std::move(new_msgs_); @@ -128,6 +130,7 @@ void AbstractStream::updateLastMsgsTo(double sec) { new_msgs_.clear(); messages_.clear(); + current_sec_ = sec; uint64_t last_ts = (sec + routeStartTime()) * 1e9; for (const auto &[id, ev] : events_) { auto it = std::upper_bound(ev.begin(), ev.end(), last_ts, CompareCanEvent()); diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 16d4040d62..6c9c025129 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -69,7 +69,7 @@ public: virtual QString carFingerprint() const { return ""; } virtual QDateTime beginDateTime() const { return {}; } virtual double routeStartTime() const { return 0; } - virtual double currentSec() const = 0; + inline double currentSec() const { return current_sec_; } virtual double totalSeconds() const { return lastEventMonoTime() / 1e9 - routeStartTime(); } virtual void setSpeed(float speed) {} virtual double getSpeed() { return 1; } @@ -113,6 +113,7 @@ private: void updateLastMsgsTo(double sec); void updateMasks(); + double current_sec_ = 0; MessageEventsMap events_; std::unordered_map last_msgs; std::unique_ptr event_buffer_; @@ -140,7 +141,6 @@ public: DummyStream(QObject *parent) : AbstractStream(parent) {} QString routeName() const override { return tr("No Stream"); } void start() override { emit streamStarted(); } - double currentSec() const override { return 0; } }; class StreamNotifier : public QObject { diff --git a/tools/cabana/streams/livestream.h b/tools/cabana/streams/livestream.h index 719ea15c24..7a0f8cd834 100644 --- a/tools/cabana/streams/livestream.h +++ b/tools/cabana/streams/livestream.h @@ -16,7 +16,6 @@ public: void start() override; inline QDateTime beginDateTime() const { return begin_date_time; } inline double routeStartTime() const override { return begin_event_ts / 1e9; } - inline double currentSec() const override { return (current_event_ts - begin_event_ts) / 1e9; } void setSpeed(float speed) override { speed_ = speed; } double getSpeed() override { return speed_; } bool isPaused() const override { return paused_; } diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index 95fb632628..b4e4be4db6 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -25,7 +25,6 @@ public: double totalSeconds() const override { return replay->totalSeconds(); } inline QDateTime beginDateTime() const { return replay->route()->datetime(); } inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; } - inline double currentSec() const override { return replay->currentSeconds(); } inline const Route *route() const { return replay->route(); } inline void setSpeed(float speed) override { replay->setSpeed(speed); } inline float getSpeed() const { return replay->getSpeed(); } From 2de13bea91d7c095491d9f3f21cb71d995c63bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Wed, 15 Nov 2023 01:06:37 -0800 Subject: [PATCH 031/724] Remove laika submodule (#30453) * Remove laika submodule * Remove laika check for minimal builds * Fix minimal build check * Bring back exclude for opendbc in codespell * Fix typo * Copy GNSS indices from laika * Delete laika dependencies --- .gitmodules | 3 - .pre-commit-config.yaml | 6 +- Dockerfile.openpilot | 2 - SConstruct | 2 +- docs/Makefile | 2 +- docs/overview.rst | 8 -- laika | 1 - laika_repo | 1 - poetry.lock | 123 +-------------------- pyproject.toml | 7 +- release/files_common | 1 - selfdrive/locationd/models/gnss_helpers.py | 36 ++++-- 12 files changed, 33 insertions(+), 159 deletions(-) delete mode 120000 laika delete mode 160000 laika_repo diff --git a/.gitmodules b/.gitmodules index 26f93ef164..4ba149cb2d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "opendbc"] path = opendbc url = ../../commaai/opendbc.git -[submodule "laika_repo"] - path = laika_repo - url = ../../commaai/laika.git [submodule "cereal"] path = cereal url = ../../commaai/cereal.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e023229e8..c1c24d1096 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: rev: v2.2.6 hooks: - id: codespell - exclude: '^(third_party/)|(body/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' + exclude: '^(third_party/)|(body/)|(cereal/)|(panda/)|(opendbc/)|(rednose/)|(rednose_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' args: # if you've got a short variable name that's getting flagged, add it here - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints @@ -39,12 +39,12 @@ repos: language: system types: [python] args: ['--explicit-package-bases'] - exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' + exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.5 hooks: - id: ruff - exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' + exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' - repo: local hooks: - id: cppcheck diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index 3541be92b0..27b1e246bd 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -13,8 +13,6 @@ COPY SConstruct ${OPENPILOT_PATH} COPY ./openpilot ${OPENPILOT_PATH}/openpilot COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons -COPY ./laika ${OPENPILOT_PATH}/laika -COPY ./laika_repo ${OPENPILOT_PATH}/laika_repo COPY ./rednose ${OPENPILOT_PATH}/rednose COPY ./tools ${OPENPILOT_PATH}/tools COPY ./release ${OPENPILOT_PATH}/release diff --git a/SConstruct b/SConstruct index b96347d9b2..e72e344516 100644 --- a/SConstruct +++ b/SConstruct @@ -56,7 +56,7 @@ AddOption('--pc-thneed', AddOption('--minimal', action='store_false', dest='extras', - default=os.path.islink(Dir('#laika/').abspath), + default=os.path.islink(Dir('#rednose/').abspath), # minimal by default on release branch (where rednose is not a link) help='the minimum build to run openpilot. no tests, tools, etc.') ## Architecture name breakdown (arch) diff --git a/docs/Makefile b/docs/Makefile index dee660f770..1eaae656bb 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -41,7 +41,7 @@ clean: @echo "Building rst files..." sphinx-apidoc -o "$(DOCSBUILDDIR)" ../ \ - ../xx ../laika_repo ../rednose_repo ../notebooks ../panda_jungle \ + ../xx ../rednose_repo ../notebooks ../panda_jungle \ ../third_party \ ../panda/examples \ ../scripts \ diff --git a/docs/overview.rst b/docs/overview.rst index b9a5700528..8c552077f3 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -18,14 +18,6 @@ cereal cereal/README.md cereal/messaging/msgq.md -laika -========= - -.. toctree:: - :maxdepth: 4 - - laika_repo/README.md - models ========= diff --git a/laika b/laika deleted file mode 120000 index 1c7429f4ac..0000000000 --- a/laika +++ /dev/null @@ -1 +0,0 @@ -laika_repo/laika/ \ No newline at end of file diff --git a/laika_repo b/laika_repo deleted file mode 160000 index 8861844c9b..0000000000 --- a/laika_repo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8861844c9b577ff7de7d03fab9f4d7f560415fc9 diff --git a/poetry.lock b/poetry.lock index 99d7fe6fd2..3b4fdea759 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1549,27 +1549,6 @@ all = ["flake8", "numpy", "pylint", "pytest", "pytest-cov"] numpy = ["numpy"] test = ["flake8", "pylint", "pytest", "pytest-cov"] -[[package]] -name = "hatanaka" -version = "2.4.0" -description = "Effortlessly compress / decompress any RINEX file" -optional = false -python-versions = ">=3.6" -files = [ - {file = "hatanaka-2.4.0-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:ef594d63473782fac46df5b0c92a59211a3efea1d47c1a964244a0abffc9f3f6"}, - {file = "hatanaka-2.4.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:8fda4aa56f27313de75a806a2f5aa83ed5bb2dc7561bebab856a774d06cf1ee7"}, - {file = "hatanaka-2.4.0-py3-none-win_amd64.whl", hash = "sha256:5a0624f6812b13abb4c996398a60338566885c1786841c4c04de9b1b91da28d2"}, - {file = "hatanaka-2.4.0.tar.gz", hash = "sha256:c22970b99169bddaf22e5239672e856a6bc9602c435f8793d26ad49619a70a99"}, -] - -[package.dependencies] -importlib-resources = "*" -ncompress = "*" - -[package.extras] -dev = ["pytest"] -tests = ["pytest"] - [[package]] name = "hexdump" version = "3.3" @@ -1688,21 +1667,6 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] -[[package]] -name = "importlib-resources" -version = "6.1.1" -description = "Read resources from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_resources-6.1.1-py3-none-any.whl", hash = "sha256:e8bf90d8213b486f428c9c39714b920041cb02c184686a3dee24905aaa8105d6"}, - {file = "importlib_resources-6.1.1.tar.gz", hash = "sha256:3893a00122eafde6894c59914446a512f728a0c1a45f9bb9b63721b6bacf0b4a"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] - [[package]] name = "iniconfig" version = "2.0.0" @@ -2162,16 +2126,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2577,70 +2531,6 @@ files = [ fast = ["fastnumbers (>=2.0.0)"] icu = ["PyICU (>=1.0.0)"] -[[package]] -name = "ncompress" -version = "1.0.1" -description = "LZW compression and decompression" -optional = false -python-versions = ">=3.8" -files = [ - {file = "ncompress-1.0.1-cp310-cp310-macosx_10_14_universal2.whl", hash = "sha256:487f680e369ef2f7cdf26f888dd83dc2bdf36c5764d7be204300db2a8e176e50"}, - {file = "ncompress-1.0.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:ac44c41811b5c1105974f92858cf607de7cd2a9a57ad2e511c603fc90d4dd034"}, - {file = "ncompress-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e37c183901042dbb29f9d24ce5a1239e9c57afdb9eb1a6a9deb5e036cca7149d"}, - {file = "ncompress-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723308a5782bed3ddcfba9e0c3ce6aecfad01e3d7644b44995e2cecc7317685f"}, - {file = "ncompress-1.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ae50e6e7f15aad30cd73d9261742e683fb660e9598564b14f05fe87e026e65"}, - {file = "ncompress-1.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85feb2f4ea85ac8ac9708c9a912e93f75e899d88eab2af6e89aae7a79120e105"}, - {file = "ncompress-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7eb3eb16d26acc64e3175e8aec9dc102fe3355e45a701bf8c273c9d9af55b2d0"}, - {file = "ncompress-1.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:497be1959fa0c0c4249f546c059ee0a98e4f86d2bafe846a69acdbcd3bab849b"}, - {file = "ncompress-1.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:cfba2d01010ee437425c0dd029e27429a6595e3e9447750b5821cb1908d347dc"}, - {file = "ncompress-1.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:062d8fb3ea0534a96de052eb68343da831c88a607685a87b0dc144ad1466e9ff"}, - {file = "ncompress-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:13a719822859605a798cf96e678b31e1dd66541369be48cc3ef544f2c6e14f08"}, - {file = "ncompress-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:7719b8659a9f61b3cfacbbd9c1bb07a3656945aed0bf2c73e34cd1122edc9ff2"}, - {file = "ncompress-1.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:62a95f693d6964d1b25110afa7b2111a6a471c62727b5efa2e3a8e5e3c5922ff"}, - {file = "ncompress-1.0.1-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:a94fc9e3e047199c29ada85fa3104de6a7b486859337a05259b8ae24c1686e29"}, - {file = "ncompress-1.0.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:938eee75b6084e5fb3cf4fb6a79ba86fb261cade319639893f369401c53687e4"}, - {file = "ncompress-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:016a3c60f6a25c50323cbcf4faf7c6ccd1992468eb6c6348a9d164cca3b53083"}, - {file = "ncompress-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f49a6aa4e1757a0963bbadea6219c0369afb07265ace6e5c4201c974f5e271a3"}, - {file = "ncompress-1.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8101d647f5334c4afb2a11e0c18602a9bdddb4260ee0a4f8ec6241930c89124d"}, - {file = "ncompress-1.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39ced149de176fda05198e4d0ede9d88bb2a4b717f559b4826a169dd61ab41d9"}, - {file = "ncompress-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f7c85fb141308e806fecc47791aaa5d03efd05f2b6d0d9bdf0f7ce021f60799"}, - {file = "ncompress-1.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c242d28f5679319733cf0f9a5342c3a94161214fb6ab4ef0fb15a31c65d69114"}, - {file = "ncompress-1.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:149464d41c24fa66e1497b8e2b460f312dbe017624b46af1514bf0276485a99f"}, - {file = "ncompress-1.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ba638721c66c1946c32bbb86e9413c8a101b0db09c82afe96dfe1a2201783cab"}, - {file = "ncompress-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a9345d3825776440bba31b1998bd11a377e3eb98bb79377ae91739b5e7464c1c"}, - {file = "ncompress-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:0459002f93a2ed501fc76b6cd8806216f242cd86f11dac6df88932214345dd42"}, - {file = "ncompress-1.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:2039fd029b0c7b7bec320ab5450cdc93ef786bad9ba7a87162aa2892dd7156f9"}, - {file = "ncompress-1.0.1-cp38-cp38-macosx_10_14_universal2.whl", hash = "sha256:cd1d7c3458f4c345dc47ede17e7777b1a7d6a26f18d08962b0a0804b142fdc1e"}, - {file = "ncompress-1.0.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:213eedd6cf7bf550cce2ff6e72791c8343396d169a248d1b422b17f68cd387ed"}, - {file = "ncompress-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:068bb0356d74afcf8f38b231e76773116e7346c76a6face3e25ec7cec4d60795"}, - {file = "ncompress-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87ea419d904f44833620a14a9815d3aaa70e4b9739a743953c963a56be64d711"}, - {file = "ncompress-1.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df2afc9e5ac6a72bed94fd75967cdacf826d94b7b29fe841210fefb1a7c6a6bb"}, - {file = "ncompress-1.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bcb8f79fcaba867768178b98e25106c63a79f077f1ea22ec59e7945236a8ab7"}, - {file = "ncompress-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73a1025f501ea3d0b729da59f557ab67f6533a97ebe0082fd762018132cf92a7"}, - {file = "ncompress-1.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbf462b9cc620b56bf824aa20aa9d75de04444d1e43f1b228037d1ecf01ce6be"}, - {file = "ncompress-1.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2df08321ed9c8d0bbba34b2859b8a5a2833a3a315f131b81603eb1dc7423c0ba"}, - {file = "ncompress-1.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:95a846e1704852c95c7ec347f09fc4bea3dfd3581020e3c694a1081a8fcd3ea1"}, - {file = "ncompress-1.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3c48aac59135cd521ba7a8ee0f6dd9d03946a3f2d2e02d4534dabe7423fd3420"}, - {file = "ncompress-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:47ade5c18733da749ba7371ac27e68ba806d67d430e5eb37c48bf338bb975382"}, - {file = "ncompress-1.0.1-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:29e7fce7e246de8871dc94349a1abf21da675e249acdaac76d95601a7c3da928"}, - {file = "ncompress-1.0.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:c34316051bfc1305b3f51bf03cb78e115d23b153a34d8a1a9d04459e42857f84"}, - {file = "ncompress-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7c19380caa250289260576ca5c29558ed00f516ba9844283d6c9a5b7e6a0475f"}, - {file = "ncompress-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8207723ff3ba3f27a4bdb7632d5be05a6de5e0e163c4495dd66856d713f96dec"}, - {file = "ncompress-1.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8308fb769bb5eb063badd92904f4abe8c0f1c023d71a7cda168a033cbcc60c88"}, - {file = "ncompress-1.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d26a0b26e958ea7ffbc41fa6fdfa9e8f1c72b45134ca0ad198ea3f61701ba0b4"}, - {file = "ncompress-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ce207383fa1c1fb47320f3fb593a360ad30cc04b5ffcd868c020210eb57b798"}, - {file = "ncompress-1.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c62cd047119d897b789955b082c9da76efae5b47f84b0ebe68fbe9e37f932f7b"}, - {file = "ncompress-1.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:64bf39b339350f099811ab8eaf7a8aece5629a4555838c9744c81cdc39a1ad15"}, - {file = "ncompress-1.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:4a707c302c008498afd23959ba6d64b7e4aa3a292d78e10dbb5c35f80b57ad64"}, - {file = "ncompress-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4abc4469666f6cf7f7e27fc8c1e629088c075ef5dfb22ad55cb80a193d745fdc"}, - {file = "ncompress-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:a1f9b16445da8af82073b5e7a9b33c024bd4299bfecfba1da636d32da5c9c399"}, - {file = "ncompress-1.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:7be5cca28b1a7c8df5d989d90c80d316b486bc8d32c67aec2029b27b9589c5df"}, - {file = "ncompress-1.0.1.tar.gz", hash = "sha256:a27a54b572da8f14ac0c0689660f13cddb02a2822b03048d3c2ce3d28fb11027"}, -] - -[package.extras] -tests = ["pytest"] - [[package]] name = "nodeenv" version = "1.8.0" @@ -4897,17 +4787,6 @@ files = [ {file = "types_atomicwrites-1.4.5.1-py3-none-any.whl", hash = "sha256:2f1febbdc78b55453b189fa5b136dce34bab7d1d82319163d470e404aab55c83"}, ] -[[package]] -name = "types-certifi" -version = "2021.10.8.3" -description = "Typing stubs for certifi" -optional = false -python-versions = "*" -files = [ - {file = "types-certifi-2021.10.8.3.tar.gz", hash = "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f"}, - {file = "types_certifi-2021.10.8.3-py3-none-any.whl", hash = "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a"}, -] - [[package]] name = "types-pycurl" version = "7.45.2.5" @@ -5150,4 +5029,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "9538e574ca03437994b7b0a0b6cb41842256162a2f14abfd0da26587709f145a" +content-hash = "f0abc0f53443c3b98eb06fc4dd0ff736e39197bc68c79db9aeffb4736657b676" diff --git a/pyproject.toml b/pyproject.toml index 8a5769bcc9..38c3337c6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=laika_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" +addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" cpp_files = "test_*" python_files = "test_*.py" #timeout = "30" # you get this long by default @@ -38,8 +38,6 @@ exclude = [ "cereal/", "opendbc/", "panda/", - "laika/", - "laika_repo/", "rednose/", "rednose_repo/", "tinygrad/", @@ -82,7 +80,6 @@ crcmod = "*" cryptography = "*" Cython = "*" future-fstrings = "*" # for acados -hatanaka = "==2.4" hexdump = "*" Jinja2 = "*" json-rpc = "*" @@ -159,7 +156,6 @@ sphinx-sitemap = "*" tabulate = "*" tenacity = "*" types-atomicwrites = "*" -types-certifi = "*" types-pycurl = "*" types-PyYAML = "*" types-requests = "*" @@ -187,7 +183,6 @@ target-version="py311" exclude = [ "panda", "opendbc", - "laika_repo", "rednose_repo", "tinygrad_repo", "third_party", diff --git a/release/files_common b/release/files_common index b2623e0113..a1a164bfbf 100644 --- a/release/files_common +++ b/release/files_common @@ -448,7 +448,6 @@ scripts/stop_updater.sh rednose/.gitignore rednose/** -laika/** body/.gitignore body/board/SConscript diff --git a/selfdrive/locationd/models/gnss_helpers.py b/selfdrive/locationd/models/gnss_helpers.py index b6c1771ec6..f412fafda9 100644 --- a/selfdrive/locationd/models/gnss_helpers.py +++ b/selfdrive/locationd/models/gnss_helpers.py @@ -1,19 +1,35 @@ import numpy as np -from laika.raw_gnss import GNSSMeasurement + + +# source: GNSSMeasurement (https://github.com/commaai/laika/blob/master/laika/raw_gnss.py) +class RawGNSSMeasurementIndices: + PRN = 0 + RECV_TIME_WEEK = 1 + RECV_TIME_SEC = 2 + GLONASS_FREQ = 3 + + PR = 4 + PR_STD = 5 + PRR = 6 + PRR_STD = 7 + + SAT_POS = slice(8, 11) + SAT_VEL = slice(11, 14) + def parse_prr(m): - sat_pos_vel_i = np.concatenate((m[GNSSMeasurement.SAT_POS], - m[GNSSMeasurement.SAT_VEL])) - R_i = np.atleast_2d(m[GNSSMeasurement.PRR_STD]**2) - z_i = m[GNSSMeasurement.PRR] + sat_pos_vel_i = np.concatenate((m[RawGNSSMeasurementIndices.SAT_POS], + m[RawGNSSMeasurementIndices.SAT_VEL])) + R_i = np.atleast_2d(m[RawGNSSMeasurementIndices.PRR_STD]**2) + z_i = m[RawGNSSMeasurementIndices.PRR] return z_i, R_i, sat_pos_vel_i + def parse_pr(m): - pseudorange = m[GNSSMeasurement.PR] - pseudorange_stdev = m[GNSSMeasurement.PR_STD] - sat_pos_freq_i = np.concatenate((m[GNSSMeasurement.SAT_POS], - np.array([m[GNSSMeasurement.GLONASS_FREQ]]))) + pseudorange = m[RawGNSSMeasurementIndices.PR] + pseudorange_stdev = m[RawGNSSMeasurementIndices.PR_STD] + sat_pos_freq_i = np.concatenate((m[RawGNSSMeasurementIndices.SAT_POS], + np.array([m[RawGNSSMeasurementIndices.GLONASS_FREQ]]))) z_i = np.atleast_1d(pseudorange) R_i = np.atleast_2d(pseudorange_stdev**2) return z_i, R_i, sat_pos_freq_i - From a5d99e0ebbfdad405ec86ca4907325926f3e0bfb Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 15 Nov 2023 10:16:51 -0800 Subject: [PATCH 032/724] Pytest: run tests in random order (#30134) * enable random order * logprint info * revert that * better logging * go to dfu * try to flash every panda if not up to date * revert panda debugging --- pyproject.toml | 2 +- selfdrive/athena/tests/test_athenad.py | 4 ++-- selfdrive/controls/tests/test_state_machine.py | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 38c3337c6f..1decad4d08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" +addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup --random-order" cpp_files = "test_*" python_files = "test_*.py" #timeout = "30" # you get this long by default diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 8829ebfbf1..a31e510ff0 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -34,6 +34,8 @@ class TestAthenadMethods(unittest.TestCase): athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} def setUp(self): + dispatcher["listUploadQueue"]() # ensure queue is empty at start + MockParams.restore_defaults() athenad.upload_queue = queue.Queue() athenad.cur_upload_items.clear() @@ -46,8 +48,6 @@ class TestAthenadMethods(unittest.TestCase): else: os.unlink(p) - dispatcher["listUploadQueue"]() # ensure queue is empty at start - # *** test helpers *** @staticmethod diff --git a/selfdrive/controls/tests/test_state_machine.py b/selfdrive/controls/tests/test_state_machine.py index bdeed9fb7a..d626f94015 100755 --- a/selfdrive/controls/tests/test_state_machine.py +++ b/selfdrive/controls/tests/test_state_machine.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import copy import unittest +from unittest import mock from cereal import car, log from openpilot.common.realtime import DT_CTRL @@ -17,6 +19,7 @@ ALL_STATES = tuple(State.schema.enumerants.values()) # The event types checked in DISABLED section of state machine ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL) +EVENTS = copy.copy(EVENTS) def make_event(event_types): event = {} @@ -27,6 +30,7 @@ def make_event(event_types): return 0 +@mock.patch("openpilot.selfdrive.controls.lib.events.EVENTS", EVENTS) class TestStateMachine(unittest.TestCase): def setUp(self): From ba3067a560dca8d94ebf2c63b0d5fab7922c56e4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 15 Nov 2023 10:41:11 -0800 Subject: [PATCH 033/724] Revert "Pytest: run tests in random order" (#30469) Revert "Pytest: run tests in random order (#30134)" This reverts commit a5d99e0ebbfdad405ec86ca4907325926f3e0bfb. --- pyproject.toml | 2 +- selfdrive/athena/tests/test_athenad.py | 4 ++-- selfdrive/controls/tests/test_state_machine.py | 4 ---- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1decad4d08..38c3337c6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup --random-order" +addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" cpp_files = "test_*" python_files = "test_*.py" #timeout = "30" # you get this long by default diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index a31e510ff0..8829ebfbf1 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -34,8 +34,6 @@ class TestAthenadMethods(unittest.TestCase): athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} def setUp(self): - dispatcher["listUploadQueue"]() # ensure queue is empty at start - MockParams.restore_defaults() athenad.upload_queue = queue.Queue() athenad.cur_upload_items.clear() @@ -48,6 +46,8 @@ class TestAthenadMethods(unittest.TestCase): else: os.unlink(p) + dispatcher["listUploadQueue"]() # ensure queue is empty at start + # *** test helpers *** @staticmethod diff --git a/selfdrive/controls/tests/test_state_machine.py b/selfdrive/controls/tests/test_state_machine.py index d626f94015..bdeed9fb7a 100755 --- a/selfdrive/controls/tests/test_state_machine.py +++ b/selfdrive/controls/tests/test_state_machine.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import copy import unittest -from unittest import mock from cereal import car, log from openpilot.common.realtime import DT_CTRL @@ -19,7 +17,6 @@ ALL_STATES = tuple(State.schema.enumerants.values()) # The event types checked in DISABLED section of state machine ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL) -EVENTS = copy.copy(EVENTS) def make_event(event_types): event = {} @@ -30,7 +27,6 @@ def make_event(event_types): return 0 -@mock.patch("openpilot.selfdrive.controls.lib.events.EVENTS", EVENTS) class TestStateMachine(unittest.TestCase): def setUp(self): From 0f0acc190bab0f402d1d85a4ab730ad4a27bd68c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Nov 2023 17:04:07 -0800 Subject: [PATCH 034/724] ui: add purple prime def --- selfdrive/ui/ui.h | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 2ba0775676..dc180eebfb 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -103,6 +103,7 @@ enum PrimeType { LITE = 2, BLUE = 3, MAGENTA_NEW = 4, + PURPLE = 5, }; const QColor bg_colors [] = { From 361ffb5e5438dc76c847c2042cf99923ef599aff Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 15 Nov 2023 17:48:43 -0800 Subject: [PATCH 035/724] CI: separate image for docker cache (#30413) just registry cache --- selfdrive/test/docker_build.sh | 10 +++++++++- selfdrive/test/docker_common.sh | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh index e0ba54f058..d5a05fecad 100755 --- a/selfdrive/test/docker_build.sh +++ b/selfdrive/test/docker_build.sh @@ -17,7 +17,15 @@ fi source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" -DOCKER_BUILDKIT=1 docker buildx build --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR +if [ -n "$PUSH_IMAGE" ]; then + REG_CACHE="--cache-to type=registry,ref=$REMOTE_CACHE_TAG,mode=max" +fi + +DOCKER_BUILDKIT=1 docker buildx build --platform $PLATFORM --load \ + --cache-from type=registry,ref=$REMOTE_CACHE_TAG \ + --cache-from type=registry,ref=$REMOTE_TAG \ + $REG_CACHE \ + -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR if [ -n "$PUSH_IMAGE" ]; then docker push $REMOTE_TAG diff --git a/selfdrive/test/docker_common.sh b/selfdrive/test/docker_common.sh index 92da71ba66..f1422bf643 100644 --- a/selfdrive/test/docker_common.sh +++ b/selfdrive/test/docker_common.sh @@ -22,3 +22,4 @@ TAG_SUFFIX=$2 LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA +REMOTE_CACHE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:cache From edf90f3e7647bf0929f903bbcf3fc67a80ec87e9 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 15 Nov 2023 17:53:00 -0800 Subject: [PATCH 036/724] CI: enable buildx for registry cache (#30473) * just registry cache * enable buildx --- .github/workflows/setup/action.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index 8bb1ccc376..495c4745ee 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -68,8 +68,7 @@ runs: find . -type f -executable -not -perm 755 -exec chmod 755 {} \; find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \; - id: setup-buildx-action - if: contains(runner.name, 'buildjet') - name: Set up Docker Buildx on buildjet to ensure a consistent cache + name: setup docker buildx for registry cache uses: docker/setup-buildx-action@v2 with: driver: docker-container From 291c04163a0724ce67aca6188cab092d6cecad02 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 15 Nov 2023 18:21:57 -0800 Subject: [PATCH 037/724] CI: disable registry caching (#30474) * Revert "CI: separate image for docker cache (#30413)" This reverts commit 361ffb5e5438dc76c847c2042cf99923ef599aff. * Revert "CI: enable buildx for registry cache (#30473)" This reverts commit edf90f3e7647bf0929f903bbcf3fc67a80ec87e9. --- .github/workflows/setup/action.yaml | 3 ++- selfdrive/test/docker_build.sh | 10 +--------- selfdrive/test/docker_common.sh | 1 - 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index 495c4745ee..8bb1ccc376 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -68,7 +68,8 @@ runs: find . -type f -executable -not -perm 755 -exec chmod 755 {} \; find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \; - id: setup-buildx-action - name: setup docker buildx for registry cache + if: contains(runner.name, 'buildjet') + name: Set up Docker Buildx on buildjet to ensure a consistent cache uses: docker/setup-buildx-action@v2 with: driver: docker-container diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh index d5a05fecad..e0ba54f058 100755 --- a/selfdrive/test/docker_build.sh +++ b/selfdrive/test/docker_build.sh @@ -17,15 +17,7 @@ fi source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" -if [ -n "$PUSH_IMAGE" ]; then - REG_CACHE="--cache-to type=registry,ref=$REMOTE_CACHE_TAG,mode=max" -fi - -DOCKER_BUILDKIT=1 docker buildx build --platform $PLATFORM --load \ - --cache-from type=registry,ref=$REMOTE_CACHE_TAG \ - --cache-from type=registry,ref=$REMOTE_TAG \ - $REG_CACHE \ - -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR +DOCKER_BUILDKIT=1 docker buildx build --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR if [ -n "$PUSH_IMAGE" ]; then docker push $REMOTE_TAG diff --git a/selfdrive/test/docker_common.sh b/selfdrive/test/docker_common.sh index f1422bf643..92da71ba66 100644 --- a/selfdrive/test/docker_common.sh +++ b/selfdrive/test/docker_common.sh @@ -22,4 +22,3 @@ TAG_SUFFIX=$2 LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA -REMOTE_CACHE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:cache From e44d84a7f998022c6bbe8543c69ea294ec1313ae Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Nov 2023 18:50:39 -0800 Subject: [PATCH 038/724] test_models: remove ignored address check list (#30475) * remove ignore_addr_checks_valid * lessgoooo --- selfdrive/car/tests/test_models.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 9b07dc20e3..8fe81a2f09 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -14,9 +14,7 @@ from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car.fingerprints import all_known_cars from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces -from openpilot.selfdrive.car.gm.values import CAR as GM from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH -from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute from openpilot.selfdrive.controls.controlsd import Controls from openpilot.selfdrive.test.openpilotci import get_url @@ -34,11 +32,6 @@ JOB_ID = int(os.environ.get("JOB_ID", "0")) INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) -ignore_addr_checks_valid = [ - GM.BUICK_REGAL, - HYUNDAI.GENESIS_G70_2020, -] - def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: # build list of test cases @@ -250,17 +243,16 @@ class TestCarModelBase(unittest.TestCase): failed_addrs[hex(msg.address)] += 1 # ensure all msgs defined in the addr checks are valid - if self.car_model not in ignore_addr_checks_valid: - self.safety.safety_tick_current_rx_checks() - if t > 1e6: - self.assertTrue(self.safety.addr_checks_valid()) + self.safety.safety_tick_current_rx_checks() + if t > 1e6: + self.assertTrue(self.safety.addr_checks_valid()) - # No need to check relay malfunction on disabled routes (relay closed), - # or before fingerprinting is done (1s of tolerance to exit silent mode) - if self.openpilot_enabled and t / 1e4 > (self.elm_frame + 100): - self.assertFalse(self.safety.get_relay_malfunction()) - else: - self.safety.set_relay_malfunction(False) + # No need to check relay malfunction on disabled routes (relay closed), + # or before fingerprinting is done (1s of tolerance to exit silent mode) + if self.openpilot_enabled and t / 1e4 > (self.elm_frame + 100): + self.assertFalse(self.safety.get_relay_malfunction()) + else: + self.safety.set_relay_malfunction(False) self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}") From fc8cfe128046e741d28b127bac03b7a691aa39f3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Nov 2023 18:57:37 -0800 Subject: [PATCH 039/724] test_models: check if in relay-breaking safety mode (#30476) * remove initData, remove tolerance * can check faster now * don't --- selfdrive/car/tests/test_models.py | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 8fe81a2f09..7481695fcd 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -68,6 +68,7 @@ class TestCarModelBase(unittest.TestCase): can_msgs: List[capnp.lib.capnp._DynamicStructReader] elm_frame: Optional[int] + car_safety_mode_frame: Optional[int] @classmethod def setUpClass(cls): @@ -103,10 +104,9 @@ class TestCarModelBase(unittest.TestCase): car_fw = [] can_msgs = [] cls.elm_frame = None + cls.car_safety_mode_frame = None fingerprint = defaultdict(dict) experimental_long = False - enabled_toggle = True - dashcam_only = False for msg in lr: if msg.which() == "can": can_msgs.append(msg) @@ -117,26 +117,26 @@ class TestCarModelBase(unittest.TestCase): elif msg.which() == "carParams": car_fw = msg.carParams.carFw - dashcam_only = msg.carParams.dashcamOnly if msg.carParams.openpilotLongitudinalControl: experimental_long = True if cls.car_model is None and not cls.ci: cls.car_model = msg.carParams.carFingerprint - elif msg.which() == 'initData': - for param in msg.initData.params.entries: - if param.key == 'OpenpilotEnabledToggle': - enabled_toggle = param.value.strip(b'\x00') == b'1' - # Log which can frame the panda safety mode left ELM327, for CAN validity checks - if msg.which() == 'pandaStates': + elif msg.which() == 'pandaStates': for ps in msg.pandaStates: if cls.elm_frame is None and ps.safetyModel != SafetyModel.elm327: cls.elm_frame = len(can_msgs) + if cls.car_safety_mode_frame is None and ps.safetyModel not in \ + (SafetyModel.elm327, SafetyModel.noOutput): + cls.car_safety_mode_frame = len(can_msgs) elif msg.which() == 'pandaStateDEPRECATED': if cls.elm_frame is None and msg.pandaStateDEPRECATED.safetyModel != SafetyModel.elm327: cls.elm_frame = len(can_msgs) + if cls.car_safety_mode_frame is None and msg.pandaStateDEPRECATED.safetyModel not in \ + (SafetyModel.elm327, SafetyModel.noOutput): + cls.car_safety_mode_frame = len(can_msgs) if len(can_msgs) > int(50 / DT_CTRL): break @@ -144,7 +144,7 @@ class TestCarModelBase(unittest.TestCase): raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded?") # if relay is expected to be open in the route - cls.openpilot_enabled = enabled_toggle and not dashcam_only + cls.openpilot_enabled = cls.car_safety_mode_frame is not None cls.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime) @@ -247,12 +247,12 @@ class TestCarModelBase(unittest.TestCase): if t > 1e6: self.assertTrue(self.safety.addr_checks_valid()) - # No need to check relay malfunction on disabled routes (relay closed), - # or before fingerprinting is done (1s of tolerance to exit silent mode) - if self.openpilot_enabled and t / 1e4 > (self.elm_frame + 100): - self.assertFalse(self.safety.get_relay_malfunction()) - else: - self.safety.set_relay_malfunction(False) + # Don't check relay malfunction on disabled routes (relay closed), + # or before fingerprinting is done (elm327 and noOutput) + if self.openpilot_enabled and t / 1e4 > self.car_safety_mode_frame: + self.assertFalse(self.safety.get_relay_malfunction()) + else: + self.safety.set_relay_malfunction(False) self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}") From 9cd60de0b911e5c374c253f4af8b20a2c417089a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Nov 2023 19:56:02 -0800 Subject: [PATCH 040/724] we don't go in the release notes --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index c95816b2f8..495a54388d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,7 +8,7 @@ Version 0.9.5 (2023-11-16) * Hyundai Azera 2022 support thanks to sunnyhaibin! * Hyundai Azera Hybrid 2020 support thanks to chanhojung and haram-KONA! * Hyundai Custin 2023 support thanks to sunnyhaibin and Saber422! -* Hyundai Ioniq 6 2023 support thanks to sunnyhaibin, alamo3, and sshane! +* Hyundai Ioniq 6 2023 support thanks to sunnyhaibin and alamo3! * Hyundai Kona Electric 2023 (Korean version) support thanks to sunnyhaibin and haram-KONA! * Kia K8 Hybrid (with HDA II) 2023 support thanks to sunnyhaibin! * Kia Optima Hybrid 2019 support From 0addee2b4b9f4ad64d1cea12f4a6badf50e85e9c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Nov 2023 21:15:10 -0800 Subject: [PATCH 041/724] openpilot CI: fix azure bucket names (#30477) fix azure bucket names --- selfdrive/test/openpilotci.py | 7 ++++--- selfdrive/test/update_ci_routes.py | 14 +++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/selfdrive/test/openpilotci.py b/selfdrive/test/openpilotci.py index f590b66e54..0fc763002b 100755 --- a/selfdrive/test/openpilotci.py +++ b/selfdrive/test/openpilotci.py @@ -7,8 +7,9 @@ from typing import IO, Union DATA_CI_ACCOUNT = "commadataci" DATA_CI_ACCOUNT_URL = f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net" -DATA_CI_CONTAINER = "openpilotci" -BASE_URL = f"{DATA_CI_ACCOUNT_URL}/{DATA_CI_CONTAINER}/" +OPENPILOT_CI_CONTAINER = "openpilotci" +DATA_CI_CONTAINER = "commadataci" +BASE_URL = f"{DATA_CI_ACCOUNT_URL}/{OPENPILOT_CI_CONTAINER}/" TOKEN_PATH = Path("/data/azure_token") @@ -51,7 +52,7 @@ def upload_bytes(data: Union[bytes, IO], blob_name: str) -> str: from azure.storage.blob import BlobClient blob = BlobClient( account_url=DATA_CI_ACCOUNT_URL, - container_name=DATA_CI_CONTAINER, + container_name=OPENPILOT_CI_CONTAINER, blob_name=blob_name, credential=get_azure_credential(), ) diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 326cb4fa56..4ec8b689a5 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -9,8 +9,8 @@ from tqdm import tqdm from openpilot.selfdrive.car.tests.routes import routes as test_car_models_routes from openpilot.selfdrive.test.process_replay.test_processes import source_segments as replay_segments -from openpilot.selfdrive.test.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, DATA_CI_CONTAINER, - get_azure_credential, get_container_sas) +from openpilot.selfdrive.test.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, OPENPILOT_CI_CONTAINER, + DATA_CI_CONTAINER, get_azure_credential, get_container_sas) DATA_PROD_ACCOUNT = "commadata2" DATA_PROD_CONTAINER = "commadata2" @@ -23,15 +23,15 @@ SOURCES = [ @lru_cache def get_azure_keys(): - dest_container = ContainerClient(DATA_CI_ACCOUNT_URL, DATA_CI_CONTAINER, credential=get_azure_credential()) - dest_key = get_container_sas(DATA_CI_ACCOUNT, DATA_CI_CONTAINER) + dest_container = ContainerClient(DATA_CI_ACCOUNT_URL, OPENPILOT_CI_CONTAINER, credential=get_azure_credential()) + dest_key = get_container_sas(DATA_CI_ACCOUNT, OPENPILOT_CI_CONTAINER) source_keys = [get_container_sas(*s) for s in SOURCES] return dest_container, dest_key, source_keys def upload_route(path: str, exclude_patterns: Optional[Iterable[str]] = None) -> None: # TODO: use azure-storage-blob instead of azcopy, simplifies auth - dest_key = get_container_sas(DATA_CI_ACCOUNT, DATA_CI_CONTAINER) + dest_key = get_container_sas(DATA_CI_ACCOUNT, OPENPILOT_CI_CONTAINER) if exclude_patterns is None: exclude_patterns = ['*/dcamera.hevc'] @@ -42,7 +42,7 @@ def upload_route(path: str, exclude_patterns: Optional[Iterable[str]] = None) -> "azcopy", "copy", f"{path}/*", - f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net/{DATA_CI_CONTAINER}/{destpath}?{dest_key}", + f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net/{OPENPILOT_CI_CONTAINER}/{destpath}?{dest_key}", "--recursive=false", "--overwrite=false", ] + [f"--exclude-pattern={p}" for p in exclude_patterns] @@ -65,7 +65,7 @@ def sync_to_ci_public(route: str) -> bool: "azcopy", "copy", f"https://{source_account}.blob.core.windows.net/{source_bucket}/{key_prefix}?{source_key}", - f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net/{DATA_CI_CONTAINER}/{dongle_id}?{dest_key}", + f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net/{OPENPILOT_CI_CONTAINER}/{dongle_id}?{dest_key}", "--recursive=true", "--overwrite=false", "--exclude-pattern=*/dcamera.hevc", From c2af245b85cb9a168b86446a41e665dc680f7242 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Nov 2023 21:22:14 -0800 Subject: [PATCH 042/724] CI: use azure-storage-blob for uploading (#30478) * use azure-storage-blob * fix pattern * nsp --- selfdrive/test/openpilotci.py | 1 + selfdrive/test/update_ci_routes.py | 21 ++++++++------------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/selfdrive/test/openpilotci.py b/selfdrive/test/openpilotci.py index 0fc763002b..4e0da1bccf 100755 --- a/selfdrive/test/openpilotci.py +++ b/selfdrive/test/openpilotci.py @@ -55,6 +55,7 @@ def upload_bytes(data: Union[bytes, IO], blob_name: str) -> str: container_name=OPENPILOT_CI_CONTAINER, blob_name=blob_name, credential=get_azure_credential(), + overwrite=False, ) blob.upload_blob(data) return BASE_URL + blob_name diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 4ec8b689a5..ccaa7f9b84 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +import os +import re import subprocess import sys from functools import lru_cache @@ -10,7 +12,7 @@ from tqdm import tqdm from openpilot.selfdrive.car.tests.routes import routes as test_car_models_routes from openpilot.selfdrive.test.process_replay.test_processes import source_segments as replay_segments from openpilot.selfdrive.test.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, OPENPILOT_CI_CONTAINER, - DATA_CI_CONTAINER, get_azure_credential, get_container_sas) + DATA_CI_CONTAINER, get_azure_credential, get_container_sas, upload_file) DATA_PROD_ACCOUNT = "commadata2" DATA_PROD_CONTAINER = "commadata2" @@ -30,23 +32,16 @@ def get_azure_keys(): def upload_route(path: str, exclude_patterns: Optional[Iterable[str]] = None) -> None: - # TODO: use azure-storage-blob instead of azcopy, simplifies auth - dest_key = get_container_sas(DATA_CI_ACCOUNT, OPENPILOT_CI_CONTAINER) if exclude_patterns is None: - exclude_patterns = ['*/dcamera.hevc'] + exclude_patterns = [r'dcamera\.hevc'] r, n = path.rsplit("--", 1) r = '/'.join(r.split('/')[-2:]) # strip out anything extra in the path destpath = f"{r}/{n}" - cmd = [ - "azcopy", - "copy", - f"{path}/*", - f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net/{OPENPILOT_CI_CONTAINER}/{destpath}?{dest_key}", - "--recursive=false", - "--overwrite=false", - ] + [f"--exclude-pattern={p}" for p in exclude_patterns] - subprocess.check_call(cmd) + for file in os.listdir(path): + if any(re.search(pattern, file) for pattern in exclude_patterns): + continue + upload_file(os.path.join(path, file), f"{destpath}/{file}") def sync_to_ci_public(route: str) -> bool: From 3d9f394533c3d82fc1fb5493f07fe90b366e5d88 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 16 Nov 2023 15:43:59 +0800 Subject: [PATCH 043/724] cabana: update message count after filtering (#30461) update message count after filtering --- tools/cabana/messageswidget.cc | 21 ++++++++++++++------- tools/cabana/messageswidget.h | 10 +--------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 7d4938e427..aba655d66f 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,6 +1,7 @@ #include "tools/cabana/messageswidget.h" #include +#include #include #include @@ -52,18 +53,18 @@ MessagesWidget::MessagesWidget(QWidget *parent) : menu(new QMenu(this)), QWidget // signals/slots QObject::connect(menu, &QMenu::aboutToShow, this, &MessagesWidget::menuAboutToShow); - QObject::connect(header, &MessageViewHeader::filtersUpdated, model, &MessageListModel::setFilterStrings); QObject::connect(header, &MessageViewHeader::customContextMenuRequested, this, &MessagesWidget::headerContextMenuEvent); QObject::connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, header, &MessageViewHeader::updateHeaderPositions); QObject::connect(suppress_defined_signals, &QCheckBox::stateChanged, can, &AbstractStream::suppressDefinedSignals); QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); - QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MessagesWidget::dbcModified); - QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MessagesWidget::dbcModified); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::dbcModified); + QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, model, &MessageListModel::dbcModified); QObject::connect(model, &MessageListModel::modelReset, [this]() { if (current_msg_id) { selectMessage(*current_msg_id); } view->updateBytesSectionSize(); + updateTitle(); }); QObject::connect(view->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { if (current.isValid() && current.row() < model->items_.size()) { @@ -103,9 +104,15 @@ QToolBar *MessagesWidget::createToolBar() { return toolbar; } -void MessagesWidget::dbcModified() { - num_msg_label->setText(tr("%1 Messages, %2 Signals").arg(dbc()->msgCount()).arg(dbc()->signalCount())); - model->dbcModified(); +void MessagesWidget::updateTitle() { + auto stats = std::accumulate( + model->items_.begin(), model->items_.end(), std::pair(), + [](const auto &pair, const auto &item) { + auto m = dbc()->msg(item.id); + return m ? std::make_pair(pair.first + 1, pair.second + m->sigs.size()) : pair; + }); + num_msg_label->setText(tr("%1 Messages (%2 DBC Messages, %3 Signals)") + .arg(model->items_.size()).arg(stats.first).arg(stats.second)); } void MessagesWidget::selectMessage(const MessageId &msg_id) { @@ -398,7 +405,7 @@ void MessageViewHeader::updateFilters() { filters[i] = editors[i]->text(); } } - emit filtersUpdated(filters); + qobject_cast(model())->setFilterStrings(filters); } void MessageViewHeader::updateHeaderPositions() { diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 2452dcf212..4f54941c64 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -80,11 +79,6 @@ public: void updateHeaderPositions(); void updateGeometries() override; QSize sizeHint() const override; - -signals: - void filtersUpdated(const QMap &filters); - -private: void updateFilters(); QMap editors; @@ -100,9 +94,6 @@ public: bool restoreHeaderState(const QByteArray &state) const { return view->header()->restoreState(state); } void suppressHighlighted(); -public slots: - void dbcModified(); - signals: void msgSelectionChanged(const MessageId &message_id); @@ -111,6 +102,7 @@ protected: void headerContextMenuEvent(const QPoint &pos); void menuAboutToShow(); void setMultiLineBytes(bool multi); + void updateTitle(); MessageView *view; MessageViewHeader *header; From 30ab9f8482154a96a5899704900b82f453bc31be Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 16 Nov 2023 14:19:02 -0800 Subject: [PATCH 044/724] tici: use BASEDIR to lookup nm connection path --- system/hardware/tici/hardware.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 3b5647cc56..d85477ada5 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -9,6 +9,7 @@ from functools import cached_property, lru_cache from pathlib import Path from cereal import log +from openpilot.common.basedir import BASEDIR from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.system.hardware.base import HardwareBase, ThermalConfig from openpilot.system.hardware.tici import iwlist @@ -540,7 +541,7 @@ class Tici(HardwareBase): # eSIM prime if sim_id.startswith('8985235'): - with open('/data/openpilot/system/hardware/tici/esim.nmconnection') as f, tempfile.NamedTemporaryFile(mode='w') as tf: + with open(os.path.join(BASEDIR, 'system/hardware/tici/esim.nmconnection')) as f, tempfile.NamedTemporaryFile(mode='w') as tf: dat = f.read() dat = dat.replace("sim-id=", f"sim-id={sim_id}") tf.write(dat) From e6e073d099009154eb6e70989113c77111750cb4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 16 Nov 2023 14:20:55 -0800 Subject: [PATCH 045/724] Revert "tici: use BASEDIR to lookup nm connection path" This reverts commit 30ab9f8482154a96a5899704900b82f453bc31be. --- system/hardware/tici/hardware.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index d85477ada5..3b5647cc56 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -9,7 +9,6 @@ from functools import cached_property, lru_cache from pathlib import Path from cereal import log -from openpilot.common.basedir import BASEDIR from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.system.hardware.base import HardwareBase, ThermalConfig from openpilot.system.hardware.tici import iwlist @@ -541,7 +540,7 @@ class Tici(HardwareBase): # eSIM prime if sim_id.startswith('8985235'): - with open(os.path.join(BASEDIR, 'system/hardware/tici/esim.nmconnection')) as f, tempfile.NamedTemporaryFile(mode='w') as tf: + with open('/data/openpilot/system/hardware/tici/esim.nmconnection') as f, tempfile.NamedTemporaryFile(mode='w') as tf: dat = f.read() dat = dat.replace("sim-id=", f"sim-id={sim_id}") tf.write(dat) From 460d9af5b748475c027da6918aa5a1834133212e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 16 Nov 2023 19:12:36 -0800 Subject: [PATCH 046/724] more esim config (#30480) * load single conn * high priority --------- Co-authored-by: Comma Device --- system/hardware/tici/esim.nmconnection | 1 + system/hardware/tici/hardware.py | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/system/hardware/tici/esim.nmconnection b/system/hardware/tici/esim.nmconnection index d170f9d066..302ed28ed6 100644 --- a/system/hardware/tici/esim.nmconnection +++ b/system/hardware/tici/esim.nmconnection @@ -5,6 +5,7 @@ type=gsm permissions= autoconnect=true autoconnect-retries=100 +autoconnect-priority=2 [gsm] apn= diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 3b5647cc56..5f8267a461 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -540,15 +540,16 @@ class Tici(HardwareBase): # eSIM prime if sim_id.startswith('8985235'): - with open('/data/openpilot/system/hardware/tici/esim.nmconnection') as f, tempfile.NamedTemporaryFile(mode='w') as tf: + dest = "/etc/NetworkManager/system-connections/esim.nmconnection" + with open(Path(__file__).parent/'esim.nmconnection') as f, tempfile.NamedTemporaryFile(mode='w') as tf: dat = f.read() dat = dat.replace("sim-id=", f"sim-id={sim_id}") tf.write(dat) tf.flush() # needs to be root - os.system(f"sudo cp {tf.name} /data/etc/NetworkManager/system-connections/esim.nmconnection") - os.system("sudo nmcli con reload") + os.system(f"sudo cp {tf.name} {dest}") + os.system(f"sudo nmcli con load {dest}") def get_networks(self): r = {} @@ -617,5 +618,6 @@ class Tici(HardwareBase): if __name__ == "__main__": t = Tici() + t.configure_modem() t.initialize_hardware() t.set_power_save(False) From 3b1e9017c560499786d8a0e46aaaeea65037acac Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 16 Nov 2023 19:17:36 -0800 Subject: [PATCH 047/724] Update RELEASES.md --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 495a54388d..1cb6105680 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,4 @@ -Version 0.9.5 (2023-11-16) +Version 0.9.5 (2023-11-17) ======================== * New driving model * Improved navigate on openpilot performance using navigation instructions as an additional model input From 05e4d908dfbac3444466263639ec6ac5f5d8f235 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Fri, 17 Nov 2023 22:53:16 +0100 Subject: [PATCH 048/724] Enhance SConstruct by enabling arbitrary compiler flags (#30484) * Update SConstruct allow no-inline Allows disabling inline functions when compiling to help with the debugging process * Update SConstruct typos * Update SConstruct --- SConstruct | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/SConstruct b/SConstruct index e72e344516..eb9cd737e2 100644 --- a/SConstruct +++ b/SConstruct @@ -37,6 +37,12 @@ AddOption('--clazy', AddOption('--compile_db', action='store_true', help='build clang compilation database') + +AddOption('--ccflags', + action='store', + type='string', + default='', + help='pass arbitrary flags over the command line') AddOption('--snpe', action='store_true', @@ -170,6 +176,10 @@ if arch != "Darwin": cflags += ['-DSWAGLOG="\\"common/swaglog.h\\""'] cxxflags += ['-DSWAGLOG="\\"common/swaglog.h\\""'] +ccflags_option = GetOption('ccflags') +if ccflags_option: + ccflags += ccflags_option.split(' ') + env = Environment( ENV=lenv, CCFLAGS=[ From d3aad9ca4601ae0a448ed971c1cd151c7c1eb690 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 17 Nov 2023 15:29:52 -0800 Subject: [PATCH 049/724] esim is metered (#30486) * esim is metered * it's an enum --------- Co-authored-by: Comma Device --- release/files_common | 1 + system/hardware/tici/esim.nmconnection | 1 + 2 files changed, 2 insertions(+) diff --git a/release/files_common b/release/files_common index a1a164bfbf..53720aa05a 100644 --- a/release/files_common +++ b/release/files_common @@ -214,6 +214,7 @@ system/hardware/tici/agnos.json system/hardware/tici/amplifier.py system/hardware/tici/updater system/hardware/tici/iwlist.py +system/hardware/tici/esim.nmconnection system/hardware/pc/__init__.py system/hardware/pc/hardware.h system/hardware/pc/hardware.py diff --git a/system/hardware/tici/esim.nmconnection b/system/hardware/tici/esim.nmconnection index 302ed28ed6..74f6f8e82c 100644 --- a/system/hardware/tici/esim.nmconnection +++ b/system/hardware/tici/esim.nmconnection @@ -6,6 +6,7 @@ permissions= autoconnect=true autoconnect-retries=100 autoconnect-priority=2 +metered=1 [gsm] apn= From fa353401f44751d88ffdc583449177451d726d63 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 17 Nov 2023 18:44:05 -0800 Subject: [PATCH 050/724] bump to 0.9.6 --- RELEASES.md | 3 +++ common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 1cb6105680..4c12971c54 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,6 @@ +Version 0.9.6 (2023-XX-XX) +======================== + Version 0.9.5 (2023-11-17) ======================== * New driving model diff --git a/common/version.h b/common/version.h index 946f6a1fec..787bc897d7 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.5" +#define COMMA_VERSION "0.9.6" From 99fbd4b150421102cba217791e1c81f8ff94ece0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 18 Nov 2023 21:55:56 -0800 Subject: [PATCH 051/724] updated: wait 1m before first cycle (#30492) --- selfdrive/updated.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 1e128fb11c..fabe28920f 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -428,6 +428,9 @@ def main() -> None: # invalidate old finalized update set_consistent_flag(False) + # wait a bit before first cycle + wait_helper.sleep(60) + # Run the update loop while True: wait_helper.ready_event.clear() From 3527c1da6759e54e42f80ab7cfde038a8ae96680 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 19 Nov 2023 14:11:13 +0800 Subject: [PATCH 052/724] loggerd: c++ LoggerState (#25869) * c++ LoggerState * trigger ci * trigger ci * merge master --------- Co-authored-by: Adeeb Shihadeh --- system/loggerd/logger.cc | 182 +++++----------------------- system/loggerd/logger.h | 62 +++------- system/loggerd/loggerd.cc | 51 ++++---- system/loggerd/tests/test_logger.cc | 101 +++------------ 4 files changed, 86 insertions(+), 310 deletions(-) diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index 1474552a13..3af2c50aa1 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -1,21 +1,7 @@ #include "system/loggerd/logger.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include -#include #include #include "common/params.h" @@ -108,159 +94,51 @@ std::string logger_get_route_name() { return route_name; } -void log_init_data(LoggerState *s) { - auto bytes = s->init_data.asBytes(); - logger_log(s, bytes.begin(), bytes.size(), s->has_qlog); -} - - -static void lh_log_sentinel(LoggerHandle *h, SentinelType type) { +static void log_sentinel(LoggerState *log, SentinelType type, int eixt_signal = 0) { MessageBuilder msg; auto sen = msg.initEvent().initSentinel(); sen.setType(type); - sen.setSignal(h->exit_signal); - auto bytes = msg.toBytes(); - - lh_log(h, bytes.begin(), bytes.size(), true); + sen.setSignal(eixt_signal); + log->write(msg.toBytes(), true); } -// ***** logging functions ***** - -void logger_init(LoggerState *s, bool has_qlog) { - pthread_mutex_init(&s->lock, NULL); - - s->part = -1; - s->has_qlog = has_qlog; - s->route_name = logger_get_route_name(); - s->init_data = logger_build_init_data(); +LoggerState::LoggerState(const std::string &log_root) { + route_name = logger_get_route_name(); + route_path = log_root + "/" + route_name; + init_data = logger_build_init_data(); } -static LoggerHandle* logger_open(LoggerState *s, const char* root_path) { - LoggerHandle *h = NULL; - for (int i=0; ihandles[i].refcnt == 0) { - h = &s->handles[i]; - break; - } +LoggerState::~LoggerState() { + if (rlog) { + log_sentinel(this, SentinelType::END_OF_ROUTE, exit_signal); + std::remove(lock_file.c_str()); } - assert(h); - - snprintf(h->segment_path, sizeof(h->segment_path), - "%s/%s--%d", root_path, s->route_name.c_str(), s->part); - - snprintf(h->log_path, sizeof(h->log_path), "%s/rlog", h->segment_path); - snprintf(h->qlog_path, sizeof(h->qlog_path), "%s/qlog", h->segment_path); - snprintf(h->lock_path, sizeof(h->lock_path), "%s.lock", h->log_path); - h->end_sentinel_type = SentinelType::END_OF_SEGMENT; - h->exit_signal = 0; - - if (!util::create_directories(h->segment_path, 0775)) return nullptr; - - FILE* lock_file = fopen(h->lock_path, "wb"); - if (lock_file == NULL) return NULL; - fclose(lock_file); - - h->log = std::make_unique(h->log_path); - if (s->has_qlog) { - h->q_log = std::make_unique(h->qlog_path); - } - - pthread_mutex_init(&h->lock, NULL); - h->refcnt++; - return h; } -int logger_next(LoggerState *s, const char* root_path, - char* out_segment_path, size_t out_segment_path_len, - int* out_part) { - bool is_start_of_route = !s->cur_handle; - - pthread_mutex_lock(&s->lock); - s->part++; - - LoggerHandle* next_h = logger_open(s, root_path); - if (!next_h) { - pthread_mutex_unlock(&s->lock); - return -1; +bool LoggerState::next() { + if (rlog) { + log_sentinel(this, SentinelType::END_OF_SEGMENT); + std::remove(lock_file.c_str()); } - if (s->cur_handle) { - lh_close(s->cur_handle); - } - s->cur_handle = next_h; + segment_path = route_path + "--" + std::to_string(++part); + bool ret = util::create_directories(segment_path, 0775); + assert(ret == true); - if (out_segment_path) { - snprintf(out_segment_path, out_segment_path_len, "%s", next_h->segment_path); - } - if (out_part) { - *out_part = s->part; - } + const std::string rlog_path = segment_path + "/rlog"; + lock_file = rlog_path + ".lock"; + std::ofstream{lock_file}; - pthread_mutex_unlock(&s->lock); + rlog.reset(new RawFile(rlog_path)); + qlog.reset(new RawFile(segment_path + "/qlog")); - // write beginning of log metadata - log_init_data(s); - lh_log_sentinel(s->cur_handle, is_start_of_route ? SentinelType::START_OF_ROUTE : SentinelType::START_OF_SEGMENT); - return 0; + // log init data & sentinel type. + write(init_data.asBytes(), true); + log_sentinel(this, part > 0 ? SentinelType::START_OF_SEGMENT : SentinelType::START_OF_ROUTE); + return true; } -LoggerHandle* logger_get_handle(LoggerState *s) { - pthread_mutex_lock(&s->lock); - LoggerHandle* h = s->cur_handle; - if (h) { - pthread_mutex_lock(&h->lock); - h->refcnt++; - pthread_mutex_unlock(&h->lock); - } - pthread_mutex_unlock(&s->lock); - return h; -} - -void logger_log(LoggerState *s, uint8_t* data, size_t data_size, bool in_qlog) { - pthread_mutex_lock(&s->lock); - if (s->cur_handle) { - lh_log(s->cur_handle, data, data_size, in_qlog); - } - pthread_mutex_unlock(&s->lock); -} - -void logger_close(LoggerState *s, ExitHandler *exit_handler) { - pthread_mutex_lock(&s->lock); - if (s->cur_handle) { - s->cur_handle->exit_signal = exit_handler && exit_handler->signal.load(); - s->cur_handle->end_sentinel_type = SentinelType::END_OF_ROUTE; - lh_close(s->cur_handle); - } - pthread_mutex_unlock(&s->lock); -} - -void lh_log(LoggerHandle* h, uint8_t* data, size_t data_size, bool in_qlog) { - pthread_mutex_lock(&h->lock); - assert(h->refcnt > 0); - h->log->write(data, data_size); - if (in_qlog && h->q_log) { - h->q_log->write(data, data_size); - } - pthread_mutex_unlock(&h->lock); -} - -void lh_close(LoggerHandle* h) { - pthread_mutex_lock(&h->lock); - assert(h->refcnt > 0); - if (h->refcnt == 1) { - // a very ugly hack. only here can guarantee sentinel is the last msg - pthread_mutex_unlock(&h->lock); - lh_log_sentinel(h, h->end_sentinel_type); - pthread_mutex_lock(&h->lock); - } - h->refcnt--; - if (h->refcnt == 0) { - h->log.reset(nullptr); - h->q_log.reset(nullptr); - unlink(h->lock_path); - pthread_mutex_unlock(&h->lock); - pthread_mutex_destroy(&h->lock); - return; - } - pthread_mutex_unlock(&h->lock); +void LoggerState::write(uint8_t* data, size_t size, bool in_qlog) { + rlog->write(data, size); + if (in_qlog) qlog->write(data, size); } diff --git a/system/loggerd/logger.h b/system/loggerd/logger.h index 06b11e72f9..76a12b9e87 100644 --- a/system/loggerd/logger.h +++ b/system/loggerd/logger.h @@ -1,27 +1,17 @@ #pragma once -#include - #include -#include -#include #include #include -#include -#include - #include "cereal/messaging/messaging.h" #include "common/util.h" -#include "common/swaglog.h" #include "system/hardware/hw.h" -#define LOGGER_MAX_HANDLES 16 - class RawFile { public: - RawFile(const char* path) { - file = util::safe_fopen(path, "wb"); + RawFile(const std::string &path) { + file = util::safe_fopen(path.c_str(), "wb"); assert(file != nullptr); } ~RawFile() { @@ -41,39 +31,25 @@ class RawFile { typedef cereal::Sentinel::SentinelType SentinelType; -typedef struct LoggerHandle { - pthread_mutex_t lock; - SentinelType end_sentinel_type; - int exit_signal; - int refcnt; - char segment_path[4096]; - char log_path[4096]; - char qlog_path[4096]; - char lock_path[4096]; - std::unique_ptr log, q_log; -} LoggerHandle; -typedef struct LoggerState { - pthread_mutex_t lock; - int part; +class LoggerState { +public: + LoggerState(const std::string& log_root = Path::log_root()); + ~LoggerState(); + bool next(); + void write(uint8_t* data, size_t size, bool in_qlog); + inline int segment() const { return part; } + inline const std::string& segmentPath() const { return segment_path; } + inline const std::string& routeName() const { return route_name; } + inline void write(kj::ArrayPtr bytes, bool in_qlog) { write(bytes.begin(), bytes.size(), in_qlog); } + inline void setExitSignal(int signal) { exit_signal = signal; } + +protected: + int part = -1, exit_signal = 0; + std::string route_path, route_name, segment_path, lock_file; kj::Array init_data; - std::string route_name; - char log_name[64]; - bool has_qlog; - - LoggerHandle handles[LOGGER_MAX_HANDLES]; - LoggerHandle* cur_handle; -} LoggerState; + std::unique_ptr rlog, qlog; +}; kj::Array logger_build_init_data(); std::string logger_get_route_name(); -void logger_init(LoggerState *s, bool has_qlog); -int logger_next(LoggerState *s, const char* root_path, - char* out_segment_path, size_t out_segment_path_len, - int* out_part); -LoggerHandle* logger_get_handle(LoggerState *s); -void logger_close(LoggerState *s, ExitHandler *exit_handler=nullptr); -void logger_log(LoggerState *s, uint8_t* data, size_t data_size, bool in_qlog); - -void lh_log(LoggerHandle* h, uint8_t* data, size_t data_size, bool in_qlog); -void lh_close(LoggerHandle* h); diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index adb24c913c..8d5fcb95ac 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -13,9 +13,7 @@ ExitHandler do_exit; struct LoggerdState { - LoggerState logger = {}; - char segment_path[4096]; - std::atomic rotate_segment; + LoggerState logger; std::atomic last_camera_seen_tms; std::atomic ready_to_rotate; // count of encoders ready to rotate int max_waiting = 0; @@ -23,13 +21,11 @@ struct LoggerdState { }; void logger_rotate(LoggerdState *s) { - int segment = -1; - int err = logger_next(&s->logger, Path::log_root().c_str(), s->segment_path, sizeof(s->segment_path), &segment); - assert(err == 0); - s->rotate_segment = segment; + bool ret =s->logger.next(); + assert(ret); s->ready_to_rotate = 0; s->last_rotate_tms = millis_since_boot(); - LOGW((s->logger.part == 0) ? "logging to %s" : "rotated to %s", s->segment_path); + LOGW((s->logger.segment() == 0) ? "logging to %s" : "rotated to %s", s->logger.segmentPath().c_str()); } void rotate_if_needed(LoggerdState *s) { @@ -85,16 +81,16 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct } int offset_segment_num = idx.getSegmentNum() - re.encoderd_segment_offset; - if (offset_segment_num == s->rotate_segment) { + if (offset_segment_num == s->logger.segment()) { // loggerd is now on the segment that matches this packet // if this is a new segment, we close any possible old segments, move to the new, and process any queued packets - if (re.current_segment != s->rotate_segment) { + if (re.current_segment != s->logger.segment()) { if (re.recording) { re.writer.reset(); re.recording = false; } - re.current_segment = s->rotate_segment; + re.current_segment = s->logger.segment(); re.marked_ready_to_rotate = false; // we are in this segment now, process any queued messages before this one if (!re.q.empty()) { @@ -117,7 +113,7 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct // if we aren't actually recording, don't create the writer if (encoder_info.record) { assert(encoder_info.filename != NULL); - re.writer.reset(new VideoWriter(s->segment_path, + re.writer.reset(new VideoWriter(s->logger.segmentPath().c_str(), encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C, encoder_info.frame_width, encoder_info.frame_height, encoder_info.fps, idx.getType())); // write the header @@ -149,28 +145,28 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct evt.setLogMonoTime(event.getLogMonoTime()); (evt.*(encoder_info.set_encode_idx_func))(idx); auto new_msg = bmsg.toBytes(); - logger_log(&s->logger, (uint8_t *)new_msg.begin(), new_msg.size(), true); // always in qlog? + s->logger.write((uint8_t *)new_msg.begin(), new_msg.size(), true); // always in qlog? bytes_count += new_msg.size(); // free the message, we used it delete msg; - } else if (offset_segment_num > s->rotate_segment) { + } else if (offset_segment_num > s->logger.segment()) { // encoderd packet has a newer segment, this means encoderd has rolled over if (!re.marked_ready_to_rotate) { re.marked_ready_to_rotate = true; ++s->ready_to_rotate; LOGD("rotate %d -> %d ready %d/%d for %s", - s->rotate_segment.load(), offset_segment_num, + s->logger.segment(), offset_segment_num, s->ready_to_rotate.load(), s->max_waiting, name.c_str()); } // queue up all the new segment messages, they go in after the rotate re.q.push_back(msg); } else { - LOGE("%s: encoderd packet has a older segment!!! idx.getSegmentNum():%d s->rotate_segment:%d re.encoderd_segment_offset:%d", - name.c_str(), idx.getSegmentNum(), s->rotate_segment.load(), re.encoderd_segment_offset); + LOGE("%s: encoderd packet has a older segment!!! idx.getSegmentNum():%d s->logger.segment():%d re.encoderd_segment_offset:%d", + name.c_str(), idx.getSegmentNum(), s->logger.segment(), re.encoderd_segment_offset); // free the message, it's useless. this should never happen // actually, this can happen if you restart encoderd - re.encoderd_segment_offset = -s->rotate_segment.load(); + re.encoderd_segment_offset = -s->logger.segment(); delete msg; } @@ -179,19 +175,19 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct void handle_user_flag(LoggerdState *s) { static int prev_segment = -1; - if (s->rotate_segment == prev_segment) return; + if (s->logger.segment() == prev_segment) return; - LOGW("preserving %s", s->segment_path); + LOGW("preserving %s", s->logger.segmentPath().c_str()); #ifdef __APPLE__ - int ret = setxattr(s->segment_path, PRESERVE_ATTR_NAME, &PRESERVE_ATTR_VALUE, 1, 0, 0); + int ret = setxattr(s->logger.segmentPath().c_str(), PRESERVE_ATTR_NAME, &PRESERVE_ATTR_VALUE, 1, 0, 0); #else - int ret = setxattr(s->segment_path, PRESERVE_ATTR_NAME, &PRESERVE_ATTR_VALUE, 1, 0); + int ret = setxattr(s->logger.segmentPath().c_str(), PRESERVE_ATTR_NAME, &PRESERVE_ATTR_VALUE, 1, 0); #endif if (ret) { - LOGE("setxattr %s failed for %s: %s", PRESERVE_ATTR_NAME, s->segment_path, strerror(errno)); + LOGE("setxattr %s failed for %s: %s", PRESERVE_ATTR_NAME, s->logger.segmentPath().c_str(), strerror(errno)); } - prev_segment = s->rotate_segment.load(); + prev_segment = s->logger.segment(); } void loggerd_thread() { @@ -228,9 +224,8 @@ void loggerd_thread() { LoggerdState s; // init logger - logger_init(&s.logger, true); logger_rotate(&s); - Params().put("CurrentRoute", s.logger.route_name); + Params().put("CurrentRoute", s.logger.routeName()); std::map encoder_infos_dict; for (const auto &cam : cameras_logged) { @@ -261,7 +256,7 @@ void loggerd_thread() { s.last_camera_seen_tms = millis_since_boot(); bytes_count += handle_encoder_msg(&s, msg, service.name, remote_encoders[sock], encoder_infos_dict[service.name]); } else { - logger_log(&s.logger, (uint8_t *)msg->getData(), msg->getSize(), in_qlog); + s.logger.write((uint8_t *)msg->getData(), msg->getSize(), in_qlog); bytes_count += msg->getSize(); delete msg; } @@ -283,7 +278,7 @@ void loggerd_thread() { } LOGW("closing logger"); - logger_close(&s.logger, &do_exit); + s.logger.setExitSignal(do_exit.signal); if (do_exit.power_failure) { LOGE("power failure"); diff --git a/system/loggerd/tests/test_logger.cc b/system/loggerd/tests/test_logger.cc index 9f815c2189..2dae136e13 100644 --- a/system/loggerd/tests/test_logger.cc +++ b/system/loggerd/tests/test_logger.cc @@ -1,16 +1,5 @@ -#include - -#include -#include -#include -#include -#include - #include "catch2/catch.hpp" -#include "cereal/messaging/messaging.h" -#include "common/util.h" #include "system/loggerd/logger.h" -#include "tools/replay/util.h" typedef cereal::Sentinel::SentinelType SentinelType; @@ -57,91 +46,29 @@ void verify_segment(const std::string &route_path, int segment, int max_segment, } } -void write_msg(LoggerHandle *logger) { +void write_msg(LoggerState *logger) { MessageBuilder msg; msg.initEvent().initClocks(); - auto bytes = msg.toBytes(); - lh_log(logger, bytes.begin(), bytes.size(), true); + logger->write(msg.toBytes(), true); } TEST_CASE("logger") { + const int segment_cnt = 100; const std::string log_root = "/tmp/test_logger"; system(("rm " + log_root + " -rf").c_str()); - - ExitHandler do_exit; - - LoggerState logger = {}; - logger_init(&logger, true); - char segment_path[PATH_MAX] = {}; - int segment = -1; - - SECTION("single thread logging & rotation(100 segments, one thread)") { - const int segment_cnt = 100; + std::string route_name; + { + LoggerState logger(log_root); + route_name = logger.routeName(); for (int i = 0; i < segment_cnt; ++i) { - REQUIRE(logger_next(&logger, log_root.c_str(), segment_path, sizeof(segment_path), &segment) == 0); - REQUIRE(util::file_exists(std::string(segment_path) + "/rlog.lock")); - REQUIRE(segment == i); - write_msg(logger.cur_handle); - } - do_exit = true; - do_exit.signal = 1; - logger_close(&logger, &do_exit); - for (int i = 0; i < segment_cnt; ++i) { - verify_segment(log_root + "/" + logger.route_name, i, segment_cnt, 1); + REQUIRE(logger.next()); + REQUIRE(util::file_exists(logger.segmentPath() + "/rlog.lock")); + REQUIRE(logger.segment() == i); + write_msg(&logger); } + logger.setExitSignal(1); } - SECTION("multiple threads logging & rotation(100 segments, 10 threads") { - const int segment_cnt = 100, thread_cnt = 10; - std::atomic event_cnt[segment_cnt] = {}; - std::atomic main_segment = -1; - - auto logging_thread = [&]() -> void { - LoggerHandle *lh = logger_get_handle(&logger); - assert(lh != nullptr); - int segment = main_segment; - int delayed_cnt = 0; - while (!do_exit) { - // write 2 more messages in the current segment and then rotate to the new segment. - if (main_segment > segment && ++delayed_cnt == 2) { - lh_close(lh); - lh = logger_get_handle(&logger); - segment = main_segment; - delayed_cnt = 0; - } - write_msg(lh); - event_cnt[segment] += 1; - usleep(1); - } - lh_close(lh); - }; - - // start logging - std::vector threads; - for (int i = 0; i < segment_cnt; ++i) { - REQUIRE(logger_next(&logger, log_root.c_str(), segment_path, sizeof(segment_path), &segment) == 0); - REQUIRE(segment == i); - main_segment = segment; - if (i == 0) { - for (int j = 0; j < thread_cnt; ++j) { - threads.push_back(std::thread(logging_thread)); - } - } - for (int j = 0; j < 100; ++j) { - write_msg(logger.cur_handle); - usleep(1); - } - event_cnt[segment] += 100; - } - - // end logging - for (auto &t : threads) t.join(); - do_exit = true; - do_exit.signal = 1; - logger_close(&logger, &do_exit); - REQUIRE(logger.cur_handle->refcnt == 0); - - for (int i = 0; i < segment_cnt; ++i) { - verify_segment(log_root + "/" + logger.route_name, i, segment_cnt, event_cnt[i]); - } + for (int i = 0; i < segment_cnt; ++i) { + verify_segment(log_root + "/" + route_name, i, segment_cnt, 1); } } From c5f73a748e6ea9742001d771b96346e3b5eddef9 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 19 Nov 2023 14:12:35 +0800 Subject: [PATCH 053/724] replay: support for setting a custom playback speed (#30462) * support for setting a custom playback speed * rename to playback speed --- tools/replay/consoleui.cc | 21 +++++++++++++-------- tools/replay/consoleui.h | 1 + tools/replay/main.cc | 6 ++++++ tools/replay/replay.cc | 6 +++--- tools/replay/replay.h | 1 - 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 719bbd69ca..3332aa9c90 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -29,7 +29,7 @@ const std::initializer_list> keyboard_shortc }, { {"enter", "Enter seek request"}, - {"x", "+/-Replay speed"}, + {"+/-", "Playback speed"}, {"q", "Exit"}, }, }; @@ -331,13 +331,18 @@ void ConsoleUI::handleKey(char c) { refresh(); getch_timer.start(1000, this); - } else if (c == 'x') { - if (replay->hasFlag(REPLAY_FLAG_FULL_SPEED)) { - replay->removeFlag(REPLAY_FLAG_FULL_SPEED); - rWarning("replay at normal speed"); - } else { - replay->addFlag(REPLAY_FLAG_FULL_SPEED); - rWarning("replay at full speed"); + } else if (c == '+' || c == '=') { + auto it = std::upper_bound(speed_array.begin(), speed_array.end(), replay->getSpeed()); + if (it != speed_array.end()) { + rWarning("playback speed: %.1fx", *it); + replay->setSpeed(*it); + } + } else if (c == '_' || c == '-') { + auto it = std::lower_bound(speed_array.begin(), speed_array.end(), replay->getSpeed()); + if (it != speed_array.begin()) { + auto prev = std::prev(it); + rWarning("playback speed: %.1fx", *prev); + replay->setSpeed(*prev); } } else if (c == 'e') { replay->seekToFlag(FindFlag::nextEngagement); diff --git a/tools/replay/consoleui.h b/tools/replay/consoleui.h index 20e07524dd..6ed44bc623 100644 --- a/tools/replay/consoleui.h +++ b/tools/replay/consoleui.h @@ -16,6 +16,7 @@ class ConsoleUI : public QObject { public: ConsoleUI(Replay *replay, QObject *parent = 0); ~ConsoleUI(); + inline static const std::array speed_array = {0.2f, 0.5f, 1.0f, 2.0f, 3.0f}; private: void initWindows(); diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 945cb4cd09..a0c072438d 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -33,6 +33,8 @@ int main(int argc, char *argv[]) { parser.addOption({{"b", "block"}, "blacklist of services to send", "block"}); parser.addOption({{"c", "cache"}, "cache segments in memory. default is 5", "n"}); parser.addOption({{"s", "start"}, "start from ", "seconds"}); + parser.addOption({"x", QString("playback . between %1 - %2") + .arg(ConsoleUI::speed_array.front()).arg(ConsoleUI::speed_array.back()), "speed"}); parser.addOption({"demo", "use a demo route instead of providing your own"}); parser.addOption({"data_dir", "local directory with routes", "data_dir"}); parser.addOption({"prefix", "set OPENPILOT_PREFIX", "prefix"}); @@ -67,6 +69,10 @@ int main(int argc, char *argv[]) { if (!parser.value("c").isEmpty()) { replay->setSegmentCacheLimit(parser.value("c").toInt()); } + if (!parser.value("x").isEmpty()) { + replay->setSpeed(std::clamp(parser.value("x").toFloat(), + ConsoleUI::speed_array.front(), ConsoleUI::speed_array.back())); + } if (!replay->load()) { return 0; } diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 1ec484d677..9657375be7 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -363,7 +363,7 @@ void Replay::publishFrame(const Event *e) { void Replay::stream() { cereal::Event::Which cur_which = cereal::Event::Which::INIT_DATA; - double prev_replay_speed = 1.0; + double prev_replay_speed = speed_; std::unique_lock lk(stream_lock_); while (true) { @@ -398,14 +398,14 @@ void Replay::stream() { evt_start_ts = cur_mono_time_; loop_start_ts = nanos_since_boot(); prev_replay_speed = speed_; - } else if (behind_ns > 0 && !hasFlag(REPLAY_FLAG_FULL_SPEED)) { + } else if (behind_ns > 0) { precise_nano_sleep(behind_ns); } if (!evt->frame) { publishMessage(evt); } else if (camera_server_) { - if (hasFlag(REPLAY_FLAG_FULL_SPEED)) { + if (speed_ > 1.0) { camera_server_->waitForSent(); } publishFrame(evt); diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 1144da2601..1a74b69c3d 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -27,7 +27,6 @@ enum REPLAY_FLAGS { REPLAY_FLAG_NO_FILE_CACHE = 0x0020, REPLAY_FLAG_QCAMERA = 0x0040, REPLAY_FLAG_NO_HW_DECODER = 0x0100, - REPLAY_FLAG_FULL_SPEED = 0x0200, REPLAY_FLAG_NO_VIPC = 0x0400, REPLAY_FLAG_ALL_SERVICES = 0x0800, }; From 17cab9bb508c74471d0ec66c4a3358122afe52aa Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 19 Nov 2023 14:14:05 +0800 Subject: [PATCH 054/724] ui/controls.cc: fix QLabel leak (#30382) fix QLabel leak --- selfdrive/ui/qt/widgets/controls.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 87304a4585..18a79b5983 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -12,13 +12,14 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons hlayout->setSpacing(20); // left icon - icon_label = new QLabel(); + icon_label = new QLabel(this); + hlayout->addWidget(icon_label); if (!icon.isEmpty()) { icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); icon_label->setPixmap(icon_pixmap); icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); - hlayout->addWidget(icon_label); } + icon_label->setVisible(!icon.isEmpty()); // title title_label = new QPushButton(title); From 3d36a0f70d2cd64dc8e6002b803f844f470d0731 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 18 Nov 2023 22:20:10 -0800 Subject: [PATCH 055/724] rm old loggerd debug scripts --- system/loggerd/tools/mark_all_uploaded.py | 8 -------- system/loggerd/tools/mark_unuploaded.py | 8 -------- 2 files changed, 16 deletions(-) delete mode 100644 system/loggerd/tools/mark_all_uploaded.py delete mode 100755 system/loggerd/tools/mark_unuploaded.py diff --git a/system/loggerd/tools/mark_all_uploaded.py b/system/loggerd/tools/mark_all_uploaded.py deleted file mode 100644 index 0502184380..0000000000 --- a/system/loggerd/tools/mark_all_uploaded.py +++ /dev/null @@ -1,8 +0,0 @@ -import os -from openpilot.system.hardware.hw import Paths -from openpilot.system.loggerd.uploader import UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE - -for folder in os.walk(Paths.log_root()): - for file1 in folder[2]: - full_path = os.path.join(folder[0], file1) - os.setxattr(full_path, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE) diff --git a/system/loggerd/tools/mark_unuploaded.py b/system/loggerd/tools/mark_unuploaded.py deleted file mode 100755 index ef50280441..0000000000 --- a/system/loggerd/tools/mark_unuploaded.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -from openpilot.system.loggerd.uploader import UPLOAD_ATTR_NAME - -for fn in sys.argv[1:]: - print(f"unmarking {fn}") - os.removexattr(fn, UPLOAD_ATTR_NAME) From c055fdc8539c3c89ffa6b71b82fe70a599fac768 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 18 Nov 2023 22:22:31 -0800 Subject: [PATCH 056/724] one more --- system/loggerd/tests/fill.py | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100755 system/loggerd/tests/fill.py diff --git a/system/loggerd/tests/fill.py b/system/loggerd/tests/fill.py deleted file mode 100755 index 5e6f887b12..0000000000 --- a/system/loggerd/tests/fill.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -"""Script to fill up storage with fake data""" - -from pathlib import Path - -from openpilot.system.hardware.hw import Paths -from openpilot.system.loggerd.config import get_available_percent -from openpilot.system.loggerd.tests.loggerd_tests_common import create_random_file - - -if __name__ == "__main__": - segment_idx = 0 - while True: - seg_name = f"1970-01-01--00-00-00--{segment_idx}" - seg_path = Path(Paths.log_root()) / seg_name - - print(seg_path) - - create_random_file(seg_path / "fcamera.hevc", 36) - create_random_file(seg_path / "rlog.bz2", 2) - - segment_idx += 1 - - # Fill up to 99 percent - available_percent = get_available_percent() - if available_percent < 1.0: - break From 335237aea0a854b41960c32b2f54b6c5af63b707 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 19 Nov 2023 14:45:26 +0800 Subject: [PATCH 057/724] modeld: remove modeldata.h (#30423) * remove modeldata.h * remove TRAJECTORY_SIZE --- common/modeldata.h | 38 ------------------------- release/files_common | 1 - selfdrive/ui/ui.cc | 6 ++-- selfdrive/ui/ui.h | 17 +++++++++-- system/camerad/cameras/camera_common.cc | 1 - 5 files changed, 17 insertions(+), 46 deletions(-) delete mode 100644 common/modeldata.h diff --git a/common/modeldata.h b/common/modeldata.h deleted file mode 100644 index 6dc02cc792..0000000000 --- a/common/modeldata.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include "common/mat.h" -#include "system/hardware/hw.h" - -const int TRAJECTORY_SIZE = 33; -const int LAT_MPC_N = 16; -const int LON_MPC_N = 32; -const float MIN_DRAW_DISTANCE = 10.0; -const float MAX_DRAW_DISTANCE = 100.0; - -const float RYG_GREEN = 0.01165; -const float RYG_YELLOW = 0.06157; - -template -constexpr std::array build_idxs(float max_val) { - std::array result{}; - for (int i = 0; i < size; ++i) { - result[i] = max_val * ((i / (double)(size - 1)) * (i / (double)(size - 1))); - } - return result; -} - -constexpr auto T_IDXS = build_idxs(10.0); -constexpr auto T_IDXS_FLOAT = build_idxs(10.0); -constexpr auto X_IDXS = build_idxs(192.0); -constexpr auto X_IDXS_FLOAT = build_idxs(192.0); - -const mat3 FCAM_INTRINSIC_MATRIX = (mat3){{2648.0, 0.0, 1928.0 / 2, - 0.0, 2648.0, 1208.0 / 2, - 0.0, 0.0, 1.0}}; - -// tici ecam focal probably wrong? magnification is not consistent across frame -// Need to retrain model before this can be changed -const mat3 ECAM_INTRINSIC_MATRIX = (mat3){{567.0, 0.0, 1928.0 / 2, - 0.0, 567.0, 1208.0 / 2, - 0.0, 0.0, 1.0}}; diff --git a/release/files_common b/release/files_common index 53720aa05a..46c2c26a5b 100644 --- a/release/files_common +++ b/release/files_common @@ -163,7 +163,6 @@ common/ratekeeper.h common/watchdog.cc common/watchdog.h -common/modeldata.h common/mat.h common/timing.h diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 03df2cf57a..7b2d24b53a 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -38,7 +38,7 @@ static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height) { const auto line_x = line.getX(); int max_idx = 0; - for (int i = 1; i < TRAJECTORY_SIZE && line_x[i] <= path_height; ++i) { + for (int i = 1; i < line_x.size() && line_x[i] <= path_height; ++i) { max_idx = i; } return max_idx; @@ -84,10 +84,10 @@ void update_model(UIState *s, const cereal::UiPlan::Reader &plan) { UIScene &scene = s->scene; auto plan_position = plan.getPosition(); - if (plan_position.getX().size() < TRAJECTORY_SIZE){ + if (plan_position.getX().size() < model.getPosition().getX().size()) { plan_position = model.getPosition(); } - float max_distance = std::clamp(plan_position.getX()[TRAJECTORY_SIZE - 1], + float max_distance = std::clamp(*(plan_position.getX().end() - 1), MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); // update lane lines diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index dc180eebfb..d9a03344b6 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -13,9 +12,10 @@ #include #include "cereal/messaging/messaging.h" -#include "common/modeldata.h" +#include "common/mat.h" #include "common/params.h" #include "common/timing.h" +#include "system/hardware/hw.h" const int UI_BORDER_SIZE = 30; const int UI_HEADER_HEIGHT = 420; @@ -24,7 +24,18 @@ const int UI_FREQ = 20; // Hz const int BACKLIGHT_OFFROAD = 50; typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; -const mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; +const float MIN_DRAW_DISTANCE = 10.0; +const float MAX_DRAW_DISTANCE = 100.0; +constexpr mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; +constexpr mat3 FCAM_INTRINSIC_MATRIX = (mat3){{2648.0, 0.0, 1928.0 / 2, + 0.0, 2648.0, 1208.0 / 2, + 0.0, 0.0, 1.0}}; +// tici ecam focal probably wrong? magnification is not consistent across frame +// Need to retrain model before this can be changed +constexpr mat3 ECAM_INTRINSIC_MATRIX = (mat3){{567.0, 0.0, 1928.0 / 2, + 0.0, 567.0, 1208.0 / 2, + 0.0, 0.0, 1.0}}; + constexpr vec3 default_face_kpts_3d[] = { {-5.98, -51.20, 8.00}, {-17.64, -49.14, 8.00}, {-23.81, -46.40, 8.00}, {-29.98, -40.91, 8.00}, {-32.04, -37.49, 8.00}, diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 23b28357da..d2c7a35f6e 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -13,7 +13,6 @@ #include "system/camerad/imgproc/utils.h" #include "common/clutil.h" -#include "common/modeldata.h" #include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" From 5b2bcf6bf220f88e11978e04a77226127b520efb Mon Sep 17 00:00:00 2001 From: Logan Lasiter Date: Sat, 18 Nov 2023 22:47:11 -0800 Subject: [PATCH 058/724] adding --local-partial-types mypy opt (#30491) --- .pre-commit-config.yaml | 2 +- scripts/code_stats.py | 2 +- tools/bodyteleop/web.py | 7 +++++-- tools/sim/lib/manual_ctrl.py | 20 ++++++++++---------- tools/sim/tests/test_carla_bridge.py | 4 +++- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c1c24d1096..90e55e7c7d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,7 +38,7 @@ repos: entry: mypy language: system types: [python] - args: ['--explicit-package-bases'] + args: ['--explicit-package-bases', '--local-partial-types'] exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.5 diff --git a/scripts/code_stats.py b/scripts/code_stats.py index 59b5724a68..3189cd7cc1 100755 --- a/scripts/code_stats.py +++ b/scripts/code_stats.py @@ -13,7 +13,7 @@ for d in ["cereal", "common", "scripts", "selfdrive", "tools"]: if f.endswith(".py"): pyf.append(os.path.join(root, f)) -imps = set() +imps: set[str] = set() class Analyzer(ast.NodeVisitor): def visit_Import(self, node): diff --git a/tools/bodyteleop/web.py b/tools/bodyteleop/web.py index 717afdeaf8..929cfa26fe 100644 --- a/tools/bodyteleop/web.py +++ b/tools/bodyteleop/web.py @@ -18,11 +18,14 @@ import cereal.messaging as messaging from openpilot.common.basedir import BASEDIR from openpilot.tools.bodyteleop.bodyav import BodyMic, WebClientSpeaker, force_codec, play_sound, MediaBlackhole, EncodedBodyVideo +from typing import Optional + logger = logging.getLogger("pc") logging.basicConfig(level=logging.INFO) -pcs = set() -pm, sm = None, None +pcs: set[RTCPeerConnection] = set() +pm: Optional[messaging.PubMaster] = None +sm: Optional[messaging.SubMaster] = None TELEOPDIR = f"{BASEDIR}/tools/bodyteleop" diff --git a/tools/sim/lib/manual_ctrl.py b/tools/sim/lib/manual_ctrl.py index 1687a2e6ba..04e228c18c 100755 --- a/tools/sim/lib/manual_ctrl.py +++ b/tools/sim/lib/manual_ctrl.py @@ -4,7 +4,7 @@ import array import os import struct from fcntl import ioctl -from typing import NoReturn +from typing import NoReturn, Dict, List # Iterate over the joystick devices. print('Available devices:') @@ -13,8 +13,8 @@ for fn in os.listdir('/dev/input'): print(f' /dev/input/{fn}') # We'll store the states here. -axis_states = {} -button_states = {} +axis_states: Dict[str, float] = {} +button_states: Dict[str, float] = {} # These constants were borrowed from linux/input.h axis_names = { @@ -88,8 +88,8 @@ button_names = { 0x2c3 : 'dpad_down', } -axis_map = [] -button_map = [] +axis_name_list: List[str] = [] +button_name_list: List[str] = [] def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: # Open the joystick device. @@ -119,7 +119,7 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: for _axis in buf[:num_axes]: axis_name = axis_names.get(_axis, f'unknown(0x{_axis:02x})') - axis_map.append(axis_name) + axis_name_list.append(axis_name) axis_states[axis_name] = 0.0 # Get the button map. @@ -128,11 +128,11 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: for btn in buf[:num_buttons]: btn_name = button_names.get(btn, f'unknown(0x{btn:03x})') - button_map.append(btn_name) + button_name_list.append(btn_name) button_states[btn_name] = 0 - print('%d axes found: %s' % (num_axes, ', '.join(axis_map))) - print('%d buttons found: %s' % (num_buttons, ', '.join(button_map))) + print('%d axes found: %s' % (num_axes, ', '.join(axis_name_list))) + print('%d buttons found: %s' % (num_buttons, ', '.join(button_name_list))) # Enable FF import evdev @@ -147,7 +147,7 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: value, mtype, number = struct.unpack('4xhBB', evbuf) # print(mtype, number, value) if mtype & 0x02: # wheel & paddles - axis = axis_map[number] + axis = axis_name_list[number] if axis == "z": # gas fvalue = value / 32767.0 diff --git a/tools/sim/tests/test_carla_bridge.py b/tools/sim/tests/test_carla_bridge.py index dcf2b9c6e2..3a654c993a 100755 --- a/tools/sim/tests/test_carla_bridge.py +++ b/tools/sim/tests/test_carla_bridge.py @@ -2,18 +2,20 @@ import subprocess import time import unittest +from subprocess import Popen from openpilot.selfdrive.manager.helpers import unblock_stdout from openpilot.tools.sim.run_bridge import parse_args from openpilot.tools.sim.bridge.carla.carla_bridge import CarlaBridge from openpilot.tools.sim.tests.test_sim_bridge import SIM_DIR, TestSimBridgeBase +from typing import Optional class TestCarlaBridge(TestSimBridgeBase): """ Tests need Carla simulator to run """ - carla_process = None + carla_process: Optional[Popen] = None def setUp(self): super().setUp() From 2a9986d9480063bb9b53d69a0cca47692c52b6ac Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 19 Nov 2023 08:05:10 +0100 Subject: [PATCH 059/724] [cabana] fix clang17 warning (#30490) [cabana] Improve signal timing calculation in findsingal.cc The variables `first_sec` and `last_sec` have been separately initialized first before being used in `std::minmax` for signal timings. This resolves compilation warnings and potential risks of using temporary values of these variables. --- tools/cabana/tools/findsignal.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index 5155ad91d2..387a912787 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -214,7 +214,9 @@ void FindSignalDlg::setInitialSignals() { sig.factor = factor_edit->text().toDouble(); sig.offset = offset_edit->text().toDouble(); - auto [first_sec, last_sec] = std::minmax(first_time_edit->text().toDouble(), last_time_edit->text().toDouble()); + double first_time_val = first_time_edit->text().toDouble(); + double last_time_val = last_time_edit->text().toDouble(); + auto [first_sec, last_sec] = std::minmax(first_time_val, last_time_val); uint64_t first_time = (can->routeStartTime() + first_sec) * 1e9; model->last_time = std::numeric_limits::max(); if (last_sec > 0) { From 4cae08e6369c0f63229222c0b3815da983928787 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 18 Nov 2023 23:23:16 -0800 Subject: [PATCH 060/724] rename rawgpsd to qcomgpsd (#30493) * rename rawgpsd to qcomgpsd * fix more paths * that moved * forgot the d --- Jenkinsfile | 2 +- release/files_tici | 2 +- selfdrive/manager/process_config.py | 2 +- selfdrive/test/test_onroad.py | 2 +- .../{sensord/rawgps => qcomgpsd}/modemdiag.py | 0 .../{sensord/rawgps => qcomgpsd}/nmeaport.py | 6 ++-- .../rawgpsd.py => qcomgpsd/qcomgpsd.py} | 8 +++--- .../{sensord/rawgps => qcomgpsd}/structs.py | 0 .../tests/test_qcomgpsd.py} | 28 +++++++++---------- 9 files changed, 25 insertions(+), 25 deletions(-) rename system/{sensord/rawgps => qcomgpsd}/modemdiag.py (100%) rename system/{sensord/rawgps => qcomgpsd}/nmeaport.py (96%) rename system/{sensord/rawgps/rawgpsd.py => qcomgpsd/qcomgpsd.py} (98%) rename system/{sensord/rawgps => qcomgpsd}/structs.py (100%) rename system/{sensord/rawgps/test_rawgps.py => qcomgpsd/tests/test_qcomgpsd.py} (84%) diff --git a/Jenkinsfile b/Jenkinsfile index 1e1d485b10..5859920a56 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -216,7 +216,7 @@ node { ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], ["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"], ["test hw", "pytest system/hardware/tici/tests/test_hardware.py"], - ["test rawgpsd", "pytest system/sensord/rawgps/test_rawgps.py"], + ["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"], ]) }, diff --git a/release/files_tici b/release/files_tici index ab49de34f6..1e5adab498 100644 --- a/release/files_tici +++ b/release/files_tici @@ -12,7 +12,7 @@ system/camerad/cameras/camera_util.cc system/camerad/cameras/camera_util.h system/camerad/cameras/real_debayer.cl -system/sensord/rawgps/* +system/qcomgpsd/* selfdrive/ui/qt/spinner_larch64 selfdrive/ui/qt/text_larch64 diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 320958d344..6f974b0687 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -68,7 +68,7 @@ procs = [ PythonProcess("controlsd", "selfdrive.controls.controlsd", only_onroad), PythonProcess("deleter", "system.loggerd.deleter", always_run), PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", driverview, enabled=(not PC or WEBCAM)), - PythonProcess("rawgpsd", "system.sensord.rawgps.rawgpsd", qcomgps, enabled=TICI), + PythonProcess("qcomgpsd", "system.qcomgpsd.qcomgpsd", qcomgps, enabled=TICI), PythonProcess("navd", "selfdrive.navd.navd", only_onroad), PythonProcess("pandad", "selfdrive.boardd.pandad", always_run), PythonProcess("paramsd", "selfdrive.locationd.paramsd", only_onroad), diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 0e9d207da3..17026dc69f 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -69,7 +69,7 @@ PROCS.update({ }, "tizi": { "./boardd": 19.0, - "system.sensord.rawgps.rawgpsd": 1.0, + "system.qcomgpsd.qcomgpsd": 1.0, } }.get(HARDWARE.get_device_type(), {})) diff --git a/system/sensord/rawgps/modemdiag.py b/system/qcomgpsd/modemdiag.py similarity index 100% rename from system/sensord/rawgps/modemdiag.py rename to system/qcomgpsd/modemdiag.py diff --git a/system/sensord/rawgps/nmeaport.py b/system/qcomgpsd/nmeaport.py similarity index 96% rename from system/sensord/rawgps/nmeaport.py rename to system/qcomgpsd/nmeaport.py index 01b9b179b9..231096fc5d 100644 --- a/system/sensord/rawgps/nmeaport.py +++ b/system/qcomgpsd/nmeaport.py @@ -120,11 +120,11 @@ def process_nmea_port_messages(device:str="/dev/ttyUSB1") -> NoReturn: def main() -> NoReturn: from openpilot.common.gpio import gpio_init, gpio_set from openpilot.system.hardware.tici.pins import GPIO - from openpilot.system.sensord.rawgps.rawgpsd import at_cmd + from openpilot.system.qcomgpsd.qcomgpsd import at_cmd try: - check_output(["pidof", "rawgpsd"]) - print("rawgpsd is running, please kill openpilot before running this script! (aborted)") + check_output(["pidof", "qcomgpsd"]) + print("qcomgpsd is running, please kill openpilot before running this script! (aborted)") sys.exit(1) except CalledProcessError as e: if e.returncode != 1: # 1 == no process found (boardd not running) diff --git a/system/sensord/rawgps/rawgpsd.py b/system/qcomgpsd/qcomgpsd.py similarity index 98% rename from system/sensord/rawgps/rawgpsd.py rename to system/qcomgpsd/qcomgpsd.py index 787a9316d3..bfd81e4b32 100755 --- a/system/sensord/rawgps/rawgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -18,8 +18,8 @@ import cereal.messaging as messaging from openpilot.common.gpio import gpio_init, gpio_set from openpilot.system.hardware.tici.pins import GPIO from openpilot.system.swaglog import cloudlog -from openpilot.system.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv -from openpilot.system.sensord.rawgps.structs import (dict_unpacker, position_report, relist, +from openpilot.system.qcomgpsd.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv +from openpilot.system.qcomgpsd.structs import (dict_unpacker, position_report, relist, gps_measurement_report, gps_measurement_report_sv, glonass_measurement_report, glonass_measurement_report_sv, oemdre_measurement_report, oemdre_measurement_report_sv, oemdre_svpoly_report, @@ -101,7 +101,7 @@ def at_cmd(cmd: str) -> Optional[str]: try: return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') except subprocess.CalledProcessError: - cloudlog.exception("rawgps.mmcli_command_failed") + cloudlog.exception("qcomgps.mmcli_command_failed") time.sleep(1.0) raise Exception(f"failed to execute mmcli command {cmd=}") @@ -163,7 +163,7 @@ def inject_assistance(): return except subprocess.CalledProcessError as e: cloudlog.event( - "rawgps.assistance_loading_failed", + "qcomgps.assistance_loading_failed", error=True, cmd=e.cmd, output=e.output, diff --git a/system/sensord/rawgps/structs.py b/system/qcomgpsd/structs.py similarity index 100% rename from system/sensord/rawgps/structs.py rename to system/qcomgpsd/structs.py diff --git a/system/sensord/rawgps/test_rawgps.py b/system/qcomgpsd/tests/test_qcomgpsd.py similarity index 84% rename from system/sensord/rawgps/test_rawgps.py rename to system/qcomgpsd/tests/test_qcomgpsd.py index 3642438132..8291f2cc32 100755 --- a/system/sensord/rawgps/test_rawgps.py +++ b/system/qcomgpsd/tests/test_qcomgpsd.py @@ -8,7 +8,7 @@ import unittest import subprocess import cereal.messaging as messaging -from openpilot.system.sensord.rawgps.rawgpsd import at_cmd, wait_for_modem +from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem from openpilot.selfdrive.manager.process_config import managed_processes GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0'))) @@ -24,7 +24,7 @@ class TestRawgpsd(unittest.TestCase): @classmethod def tearDownClass(cls): - managed_processes['rawgpsd'].stop() + managed_processes['qcomgpsd'].stop() os.system("sudo systemctl restart systemd-resolved") os.system("sudo systemctl restart ModemManager lte") @@ -33,7 +33,7 @@ class TestRawgpsd(unittest.TestCase): self.sm = messaging.SubMaster(['qcomGnss', 'gpsLocation', 'gnssMeasurements']) def tearDown(self): - managed_processes['rawgpsd'].stop() + managed_processes['qcomgpsd'].stop() os.system("sudo systemctl restart systemd-resolved") def _wait_for_output(self, t): @@ -51,7 +51,7 @@ class TestRawgpsd(unittest.TestCase): def test_wait_for_modem(self): os.system("sudo systemctl stop ModemManager") - managed_processes['rawgpsd'].start() + managed_processes['qcomgpsd'].start() assert not self._wait_for_output(5) os.system("sudo systemctl restart ModemManager") @@ -62,15 +62,15 @@ class TestRawgpsd(unittest.TestCase): if not internet: os.system("sudo systemctl stop systemd-resolved") with self.subTest(internet=internet): - managed_processes['rawgpsd'].start() + managed_processes['qcomgpsd'].start() assert self._wait_for_output(7) - managed_processes['rawgpsd'].stop() + managed_processes['qcomgpsd'].stop() def test_turns_off_gnss(self): for s in (0.1, 1, 5): - managed_processes['rawgpsd'].start() + managed_processes['qcomgpsd'].start() time.sleep(s) - managed_processes['rawgpsd'].stop() + managed_processes['qcomgpsd'].stop() ls = subprocess.check_output("mmcli -m any --location-status --output-json", shell=True, encoding='utf-8') loc_status = json.loads(ls) @@ -94,29 +94,29 @@ class TestRawgpsd(unittest.TestCase): assert valid_duration == '0' def test_assistance_loading(self): - managed_processes['rawgpsd'].start() + managed_processes['qcomgpsd'].start() assert self._wait_for_output(10) - managed_processes['rawgpsd'].stop() + managed_processes['qcomgpsd'].stop() self.check_assistance(True) def test_no_assistance_loading(self): os.system("sudo systemctl stop systemd-resolved") - managed_processes['rawgpsd'].start() + managed_processes['qcomgpsd'].start() assert self._wait_for_output(10) - managed_processes['rawgpsd'].stop() + managed_processes['qcomgpsd'].stop() self.check_assistance(False) def test_late_assistance_loading(self): os.system("sudo systemctl stop systemd-resolved") - managed_processes['rawgpsd'].start() + managed_processes['qcomgpsd'].start() self._wait_for_output(17) assert self.sm.updated['qcomGnss'] os.system("sudo systemctl restart systemd-resolved") time.sleep(15) - managed_processes['rawgpsd'].stop() + managed_processes['qcomgpsd'].stop() self.check_assistance(True) if __name__ == "__main__": From 31ef352234b3348aa96417e17d49a0cea5be6450 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 18 Nov 2023 23:31:33 -0800 Subject: [PATCH 061/724] CI: manage stale PRs (#29367) * CI: manage stale PRs * before_close * rename to ignore stale * no push * prod mode! --------- Co-authored-by: Justin Newberry --- .github/workflows/stale.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/stale.yaml diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 0000000000..f2a24e4368 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,28 @@ +name: stale +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: + +env: + DAYS_BEFORE_PR_CLOSE: 7 + DAYS_BEFORE_PR_STALE: 30 + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 + with: + exempt-milestones: true + + # pull request config + stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.' + close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.' + delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo + exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale + days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }} + days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }} + + # issue config + days-before-issue-stale: -1 # ignore issues for now From 631691c2122b15b989f18b6379c9efde713dee0b Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 20 Nov 2023 10:56:50 -0800 Subject: [PATCH 062/724] SDProfiler: update readme (#30479) * update instructions * fix that * and that too --- selfdrive/debug/profiling/snapdragon/.gitignore | 2 +- selfdrive/debug/profiling/snapdragon/README | 7 ------- selfdrive/debug/profiling/snapdragon/README.md | 13 +++++++++++++ .../debug/profiling/snapdragon/setup-profiler.sh | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 8 deletions(-) delete mode 100644 selfdrive/debug/profiling/snapdragon/README create mode 100644 selfdrive/debug/profiling/snapdragon/README.md create mode 100755 selfdrive/debug/profiling/snapdragon/setup-profiler.sh diff --git a/selfdrive/debug/profiling/snapdragon/.gitignore b/selfdrive/debug/profiling/snapdragon/.gitignore index 5d3033efb3..80078842a0 100644 --- a/selfdrive/debug/profiling/snapdragon/.gitignore +++ b/selfdrive/debug/profiling/snapdragon/.gitignore @@ -1 +1 @@ -SnapdragonProfiler/ +SnapdragonProfiler/ \ No newline at end of file diff --git a/selfdrive/debug/profiling/snapdragon/README b/selfdrive/debug/profiling/snapdragon/README deleted file mode 100644 index ee826b413a..0000000000 --- a/selfdrive/debug/profiling/snapdragon/README +++ /dev/null @@ -1,7 +0,0 @@ -snapdragon profiler --------- - -* download from https://developer.qualcomm.com/software/snapdragon-profiler -* unzip to selfdrive/debug/profiling/snapdragon/SnapdragonProfiler -* run ./setup-agnos.sh -* run selfdrive/debug/adb.sh on device diff --git a/selfdrive/debug/profiling/snapdragon/README.md b/selfdrive/debug/profiling/snapdragon/README.md new file mode 100644 index 0000000000..0bf6cd99e6 --- /dev/null +++ b/selfdrive/debug/profiling/snapdragon/README.md @@ -0,0 +1,13 @@ +snapdragon profiler +-------- + + +* download from https://developer.qualcomm.com/software/snapdragon-profiler/tools-archive (need a qc developer account) + * choose v2021.5 (verified working with 20.04) +* unzip to selfdrive/debug/profiling/snapdragon/SnapdragonProfiler +* run ```./setup-profiler.sh``` +* run ```./setup-agnos.sh``` +* run ```selfdrive/debug/adb.sh``` on device +* run the ```adb connect xxx``` command that was given to you on local pc (if you changed adb path in previous step, run that version of adb) +* cd to SnapdragonProfiler and run ```./run_sdp.sh``` +* connect to device -> choose device you just setup \ No newline at end of file diff --git a/selfdrive/debug/profiling/snapdragon/setup-profiler.sh b/selfdrive/debug/profiling/snapdragon/setup-profiler.sh new file mode 100755 index 0000000000..aaec1bf756 --- /dev/null +++ b/selfdrive/debug/profiling/snapdragon/setup-profiler.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# install depends +sudo apt update +sudo apt-get install libc++1 libc++abi1 default-jre android-tools-adb gtk-sharp2 + +# setup mono +sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF +sudo apt install apt-transport-https ca-certificates +echo "deb https://download.mono-project.com/repo/ubuntu stable-xenial main" | sudo tee /etc/apt/sources.list.d/mono-official-stable.list +sudo apt update +sudo apt-get install -y mono-complete + +echo "Setup successful, you should now be able to run the profiler with cd SnapdragonProfiler and ./run_sdp.sh" \ No newline at end of file From 7a4a8c9a4ac3b8ed464d3b270d6c3803cb2750c1 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 20 Nov 2023 13:30:16 -0800 Subject: [PATCH 063/724] update profiler docs again (#30503) update SD docs again --- selfdrive/debug/profiling/snapdragon/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/debug/profiling/snapdragon/README.md b/selfdrive/debug/profiling/snapdragon/README.md index 0bf6cd99e6..664814b611 100644 --- a/selfdrive/debug/profiling/snapdragon/README.md +++ b/selfdrive/debug/profiling/snapdragon/README.md @@ -3,11 +3,11 @@ snapdragon profiler * download from https://developer.qualcomm.com/software/snapdragon-profiler/tools-archive (need a qc developer account) - * choose v2021.5 (verified working with 20.04) + * choose v2021.5 (verified working with 20.04 dev environment) * unzip to selfdrive/debug/profiling/snapdragon/SnapdragonProfiler * run ```./setup-profiler.sh``` * run ```./setup-agnos.sh``` * run ```selfdrive/debug/adb.sh``` on device -* run the ```adb connect xxx``` command that was given to you on local pc (if you changed adb path in previous step, run that version of adb) +* run the ```adb connect xxx``` command that was given to you on local pc * cd to SnapdragonProfiler and run ```./run_sdp.sh``` -* connect to device -> choose device you just setup \ No newline at end of file +* connect to device -> choose device you just setup From afd61352d0a09c7ff169cda5b3b1bb6a94af4d38 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 20 Nov 2023 14:45:51 -0800 Subject: [PATCH 064/724] bump panda (#30501) * bump panda * bump * bump * fix renamed variables --------- Co-authored-by: Shane Smiskol --- panda | 2 +- selfdrive/car/tests/test_models.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/panda b/panda index 5fe1d67b77..c345bc3cae 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 5fe1d67b7729c843df7dd15df5d375d08ed410c6 +Subproject commit c345bc3cae34f096fdece3c36ae7e2634023ce82 diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 7481695fcd..abc9bbc13a 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -243,9 +243,9 @@ class TestCarModelBase(unittest.TestCase): failed_addrs[hex(msg.address)] += 1 # ensure all msgs defined in the addr checks are valid - self.safety.safety_tick_current_rx_checks() + self.safety.safety_tick_current_safety_config() if t > 1e6: - self.assertTrue(self.safety.addr_checks_valid()) + self.assertTrue(self.safety.safety_config_valid()) # Don't check relay malfunction on disabled routes (relay closed), # or before fingerprinting is done (elm327 and noOutput) From 8e883669556c5dcd045d5b6e182f7524f76e800a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 21 Nov 2023 14:12:39 -0500 Subject: [PATCH 065/724] Qcomgpsd: Dont use system timezone (#30508) --- system/qcomgpsd/qcomgpsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index bfd81e4b32..ac862052d3 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -379,7 +379,7 @@ def main() -> NoReturn: gps.bearingDeg = report["q_FltHeadingRad"] * 180/math.pi # TODO needs update if there is another leap second, after june 2024? - dt_timestamp = (datetime.datetime(1980, 1, 6, 0, 0, 0, 0, None) + + dt_timestamp = (datetime.datetime(1980, 1, 6, 0, 0, 0, 0, datetime.timezone.utc) + datetime.timedelta(weeks=report['w_GpsWeekNumber']) + datetime.timedelta(seconds=(1e-3*report['q_GpsFixTimeMs'] - 18))) gps.unixTimestampMillis = dt_timestamp.timestamp()*1e3 From 2a55ff4f5534dfb48a524cfb11f947a67b975817 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Nov 2023 11:50:35 -0800 Subject: [PATCH 066/724] Update Python packages and pre-commit hooks (#30509) Co-authored-by: adeebshihadeh --- .pre-commit-config.yaml | 2 +- poetry.lock | 717 ++++++++++++++++++++-------------------- 2 files changed, 361 insertions(+), 358 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 90e55e7c7d..6992141556 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,7 +41,7 @@ repos: args: ['--explicit-package-bases', '--local-partial-types'] exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.5 + rev: v0.1.6 hooks: - id: ruff exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' diff --git a/poetry.lock b/poetry.lock index 3b4fdea759..78b88ebb61 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,111 +2,98 @@ [[package]] name = "aiohttp" -version = "3.8.6" +version = "3.9.0" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41d55fc043954cddbbd82503d9cc3f4814a40bcef30b3569bc7b5e34130718c1"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d84166673694841d8953f0a8d0c90e1087739d24632fe86b1a08819168b4566"}, - {file = "aiohttp-3.8.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:253bf92b744b3170eb4c4ca2fa58f9c4b87aeb1df42f71d4e78815e6e8b73c9e"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fd194939b1f764d6bb05490987bfe104287bbf51b8d862261ccf66f48fb4096"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c5f938d199a6fdbdc10bbb9447496561c3a9a565b43be564648d81e1102ac22"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2817b2f66ca82ee699acd90e05c95e79bbf1dc986abb62b61ec8aaf851e81c93"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fa375b3d34e71ccccf172cab401cd94a72de7a8cc01847a7b3386204093bb47"}, - {file = "aiohttp-3.8.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9de50a199b7710fa2904be5a4a9b51af587ab24c8e540a7243ab737b45844543"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1d8cb0b56b3587c5c01de3bf2f600f186da7e7b5f7353d1bf26a8ddca57f965"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8e31e9db1bee8b4f407b77fd2507337a0a80665ad7b6c749d08df595d88f1cf5"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7bc88fc494b1f0311d67f29fee6fd636606f4697e8cc793a2d912ac5b19aa38d"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ec00c3305788e04bf6d29d42e504560e159ccaf0be30c09203b468a6c1ccd3b2"}, - {file = "aiohttp-3.8.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad1407db8f2f49329729564f71685557157bfa42b48f4b93e53721a16eb813ed"}, - {file = "aiohttp-3.8.6-cp310-cp310-win32.whl", hash = "sha256:ccc360e87341ad47c777f5723f68adbb52b37ab450c8bc3ca9ca1f3e849e5fe2"}, - {file = "aiohttp-3.8.6-cp310-cp310-win_amd64.whl", hash = "sha256:93c15c8e48e5e7b89d5cb4613479d144fda8344e2d886cf694fd36db4cc86865"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2f9cc8e5328f829f6e1fb74a0a3a939b14e67e80832975e01929e320386b34"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6a00ffcc173e765e200ceefb06399ba09c06db97f401f920513a10c803604ca"}, - {file = "aiohttp-3.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:41bdc2ba359032e36c0e9de5a3bd00d6fb7ea558a6ce6b70acedf0da86458321"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14cd52ccf40006c7a6cd34a0f8663734e5363fd981807173faf3a017e202fec9"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d5b785c792802e7b275c420d84f3397668e9d49ab1cb52bd916b3b3ffcf09ad"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1bed815f3dc3d915c5c1e556c397c8667826fbc1b935d95b0ad680787896a358"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96603a562b546632441926cd1293cfcb5b69f0b4159e6077f7c7dbdfb686af4d"}, - {file = "aiohttp-3.8.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d76e8b13161a202d14c9584590c4df4d068c9567c99506497bdd67eaedf36403"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3f1e3f1a1751bb62b4a1b7f4e435afcdade6c17a4fd9b9d43607cebd242924a"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:76b36b3124f0223903609944a3c8bf28a599b2cc0ce0be60b45211c8e9be97f8"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:a2ece4af1f3c967a4390c284797ab595a9f1bc1130ef8b01828915a05a6ae684"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:16d330b3b9db87c3883e565340d292638a878236418b23cc8b9b11a054aaa887"}, - {file = "aiohttp-3.8.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42c89579f82e49db436b69c938ab3e1559e5a4409eb8639eb4143989bc390f2f"}, - {file = "aiohttp-3.8.6-cp311-cp311-win32.whl", hash = "sha256:efd2fcf7e7b9d7ab16e6b7d54205beded0a9c8566cb30f09c1abe42b4e22bdcb"}, - {file = "aiohttp-3.8.6-cp311-cp311-win_amd64.whl", hash = "sha256:3b2ab182fc28e7a81f6c70bfbd829045d9480063f5ab06f6e601a3eddbbd49a0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fdee8405931b0615220e5ddf8cd7edd8592c606a8e4ca2a00704883c396e4479"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d25036d161c4fe2225d1abff2bd52c34ed0b1099f02c208cd34d8c05729882f0"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d791245a894be071d5ab04bbb4850534261a7d4fd363b094a7b9963e8cdbd31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0cccd1de239afa866e4ce5c789b3032442f19c261c7d8a01183fd956b1935349"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f13f60d78224f0dace220d8ab4ef1dbc37115eeeab8c06804fec11bec2bbd07"}, - {file = "aiohttp-3.8.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a9b5a0606faca4f6cc0d338359d6fa137104c337f489cd135bb7fbdbccb1e39"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:13da35c9ceb847732bf5c6c5781dcf4780e14392e5d3b3c689f6d22f8e15ae31"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:4d4cbe4ffa9d05f46a28252efc5941e0462792930caa370a6efaf491f412bc66"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:229852e147f44da0241954fc6cb910ba074e597f06789c867cb7fb0621e0ba7a"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:713103a8bdde61d13490adf47171a1039fd880113981e55401a0f7b42c37d071"}, - {file = "aiohttp-3.8.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:45ad816b2c8e3b60b510f30dbd37fe74fd4a772248a52bb021f6fd65dff809b6"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win32.whl", hash = "sha256:2b8d4e166e600dcfbff51919c7a3789ff6ca8b3ecce16e1d9c96d95dd569eb4c"}, - {file = "aiohttp-3.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:0912ed87fee967940aacc5306d3aa8ba3a459fcd12add0b407081fbefc931e53"}, - {file = "aiohttp-3.8.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e2a988a0c673c2e12084f5e6ba3392d76c75ddb8ebc6c7e9ead68248101cd446"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf3fd9f141700b510d4b190094db0ce37ac6361a6806c153c161dc6c041ccda"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3161ce82ab85acd267c8f4b14aa226047a6bee1e4e6adb74b798bd42c6ae1f80"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95fc1bf33a9a81469aa760617b5971331cdd74370d1214f0b3109272c0e1e3c"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c43ecfef7deaf0617cee936836518e7424ee12cb709883f2c9a1adda63cc460"}, - {file = "aiohttp-3.8.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca80e1b90a05a4f476547f904992ae81eda5c2c85c66ee4195bb8f9c5fb47f28"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:90c72ebb7cb3a08a7f40061079817133f502a160561d0675b0a6adf231382c92"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bb54c54510e47a8c7c8e63454a6acc817519337b2b78606c4e840871a3e15349"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:de6a1c9f6803b90e20869e6b99c2c18cef5cc691363954c93cb9adeb26d9f3ae"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3628b6c7b880b181a3ae0a0683698513874df63783fd89de99b7b7539e3e8a8"}, - {file = "aiohttp-3.8.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:fc37e9aef10a696a5a4474802930079ccfc14d9f9c10b4662169671ff034b7df"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win32.whl", hash = "sha256:f8ef51e459eb2ad8e7a66c1d6440c808485840ad55ecc3cafefadea47d1b1ba2"}, - {file = "aiohttp-3.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:b2fe42e523be344124c6c8ef32a011444e869dc5f883c591ed87f84339de5976"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9e2ee0ac5a1f5c7dd3197de309adfb99ac4617ff02b0603fd1e65b07dc772e4b"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01770d8c04bd8db568abb636c1fdd4f7140b284b8b3e0b4584f070180c1e5c62"}, - {file = "aiohttp-3.8.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3c68330a59506254b556b99a91857428cab98b2f84061260a67865f7f52899f5"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89341b2c19fb5eac30c341133ae2cc3544d40d9b1892749cdd25892bbc6ac951"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71783b0b6455ac8f34b5ec99d83e686892c50498d5d00b8e56d47f41b38fbe04"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f628dbf3c91e12f4d6c8b3f092069567d8eb17814aebba3d7d60c149391aee3a"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b04691bc6601ef47c88f0255043df6f570ada1a9ebef99c34bd0b72866c217ae"}, - {file = "aiohttp-3.8.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee912f7e78287516df155f69da575a0ba33b02dd7c1d6614dbc9463f43066e3"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9c19b26acdd08dd239e0d3669a3dddafd600902e37881f13fbd8a53943079dbc"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:99c5ac4ad492b4a19fc132306cd57075c28446ec2ed970973bbf036bcda1bcc6"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f0f03211fd14a6a0aed2997d4b1c013d49fb7b50eeb9ffdf5e51f23cfe2c77fa"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:8d399dade330c53b4106160f75f55407e9ae7505263ea86f2ccca6bfcbdb4921"}, - {file = "aiohttp-3.8.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ec4fd86658c6a8964d75426517dc01cbf840bbf32d055ce64a9e63a40fd7b771"}, - {file = "aiohttp-3.8.6-cp38-cp38-win32.whl", hash = "sha256:33164093be11fcef3ce2571a0dccd9041c9a93fa3bde86569d7b03120d276c6f"}, - {file = "aiohttp-3.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:bdf70bfe5a1414ba9afb9d49f0c912dc524cf60141102f3a11143ba3d291870f"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d52d5dc7c6682b720280f9d9db41d36ebe4791622c842e258c9206232251ab2b"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ac39027011414dbd3d87f7edb31680e1f430834c8cef029f11c66dad0670aa5"}, - {file = "aiohttp-3.8.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3f5c7ce535a1d2429a634310e308fb7d718905487257060e5d4598e29dc17f0b"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30e963f9e0d52c28f284d554a9469af073030030cef8693106d918b2ca92f54"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:918810ef188f84152af6b938254911055a72e0f935b5fbc4c1a4ed0b0584aed1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:002f23e6ea8d3dd8d149e569fd580c999232b5fbc601c48d55398fbc2e582e8c"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fcf3eabd3fd1a5e6092d1242295fa37d0354b2eb2077e6eb670accad78e40e1"}, - {file = "aiohttp-3.8.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:255ba9d6d5ff1a382bb9a578cd563605aa69bec845680e21c44afc2670607a95"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d67f8baed00870aa390ea2590798766256f31dc5ed3ecc737debb6e97e2ede78"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:86f20cee0f0a317c76573b627b954c412ea766d6ada1a9fcf1b805763ae7feeb"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:39a312d0e991690ccc1a61f1e9e42daa519dcc34ad03eb6f826d94c1190190dd"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e827d48cf802de06d9c935088c2924e3c7e7533377d66b6f31ed175c1620e05e"}, - {file = "aiohttp-3.8.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bd111d7fc5591ddf377a408ed9067045259ff2770f37e2d94e6478d0f3fc0c17"}, - {file = "aiohttp-3.8.6-cp39-cp39-win32.whl", hash = "sha256:caf486ac1e689dda3502567eb89ffe02876546599bbf915ec94b1fa424eeffd4"}, - {file = "aiohttp-3.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:3f0e27e5b733803333bb2371249f41cf42bae8884863e8e8965ec69bebe53132"}, - {file = "aiohttp-3.8.6.tar.gz", hash = "sha256:b0cf2a4501bff9330a8a5248b4ce951851e415bdcce9dc158e76cfd55e15085c"}, + {file = "aiohttp-3.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6896b8416be9ada4d22cd359d7cb98955576ce863eadad5596b7cdfbf3e17c6c"}, + {file = "aiohttp-3.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1736d87dad8ef46a8ec9cddd349fa9f7bd3a064c47dd6469c0d6763d3d49a4fc"}, + {file = "aiohttp-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c9e5f4d7208cda1a2bb600e29069eecf857e6980d0ccc922ccf9d1372c16f4b"}, + {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8488519aa05e636c5997719fe543c8daf19f538f4fa044f3ce94bee608817cff"}, + {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ab16c254e2312efeb799bc3c06897f65a133b38b69682bf75d1f1ee1a9c43a9"}, + {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a94bde005a8f926d0fa38b88092a03dea4b4875a61fbcd9ac6f4351df1b57cd"}, + {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b777c9286b6c6a94f50ddb3a6e730deec327e9e2256cb08b5530db0f7d40fd8"}, + {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:571760ad7736b34d05597a1fd38cbc7d47f7b65deb722cb8e86fd827404d1f6b"}, + {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:deac0a32aec29608eb25d730f4bc5a261a65b6c48ded1ed861d2a1852577c932"}, + {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4ee1b4152bc3190cc40ddd6a14715e3004944263ea208229ab4c297712aa3075"}, + {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:3607375053df58ed6f23903aa10cf3112b1240e8c799d243bbad0f7be0666986"}, + {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:65b0a70a25456d329a5e1426702dde67be0fb7a4ead718005ba2ca582d023a94"}, + {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a2eb5311a37fe105aa35f62f75a078537e1a9e4e1d78c86ec9893a3c97d7a30"}, + {file = "aiohttp-3.9.0-cp310-cp310-win32.whl", hash = "sha256:2cbc14a13fb6b42d344e4f27746a4b03a2cb0c1c3c5b932b0d6ad8881aa390e3"}, + {file = "aiohttp-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac9669990e2016d644ba8ae4758688534aabde8dbbc81f9af129c3f5f01ca9cd"}, + {file = "aiohttp-3.9.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f8e05f5163528962ce1d1806fce763ab893b1c5b7ace0a3538cd81a90622f844"}, + {file = "aiohttp-3.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4afa8f71dba3a5a2e1e1282a51cba7341ae76585345c43d8f0e624882b622218"}, + {file = "aiohttp-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f929f4c9b9a00f3e6cc0587abb95ab9c05681f8b14e0fe1daecfa83ea90f8318"}, + {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28185e36a78d247c55e9fbea2332d16aefa14c5276a582ce7a896231c6b1c208"}, + {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a486ddf57ab98b6d19ad36458b9f09e6022de0381674fe00228ca7b741aacb2f"}, + {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70e851f596c00f40a2f00a46126c95c2e04e146015af05a9da3e4867cfc55911"}, + {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5b7bf8fe4d39886adc34311a233a2e01bc10eb4e842220235ed1de57541a896"}, + {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c67a51ea415192c2e53e4e048c78bab82d21955b4281d297f517707dc836bf3d"}, + {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:694df243f394629bcae2d8ed94c589a181e8ba8604159e6e45e7b22e58291113"}, + {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3dd8119752dd30dd7bca7d4bc2a92a59be6a003e4e5c2cf7e248b89751b8f4b7"}, + {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:eb6dfd52063186ac97b4caa25764cdbcdb4b10d97f5c5f66b0fa95052e744eb7"}, + {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d97c3e286d0ac9af6223bc132dc4bad6540b37c8d6c0a15fe1e70fb34f9ec411"}, + {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:816f4db40555026e4cdda604a1088577c1fb957d02f3f1292e0221353403f192"}, + {file = "aiohttp-3.9.0-cp311-cp311-win32.whl", hash = "sha256:3abf0551874fecf95f93b58f25ef4fc9a250669a2257753f38f8f592db85ddea"}, + {file = "aiohttp-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:e18d92c3e9e22553a73e33784fcb0ed484c9874e9a3e96c16a8d6a1e74a0217b"}, + {file = "aiohttp-3.9.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:99ae01fb13a618b9942376df77a1f50c20a281390dad3c56a6ec2942e266220d"}, + {file = "aiohttp-3.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:05857848da443c8c12110d99285d499b4e84d59918a21132e45c3f0804876994"}, + {file = "aiohttp-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:317719d7f824eba55857fe0729363af58e27c066c731bc62cd97bc9c3d9c7ea4"}, + {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1e3b3c107ccb0e537f309f719994a55621acd2c8fdf6d5ce5152aed788fb940"}, + {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45820ddbb276113ead8d4907a7802adb77548087ff5465d5c554f9aa3928ae7d"}, + {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a183f1978802588711aed0dea31e697d760ce9055292db9dc1604daa9a8ded"}, + {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a4cd44788ea0b5e6bb8fa704597af3a30be75503a7ed1098bc5b8ffdf6c982"}, + {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673343fbc0c1ac44d0d2640addc56e97a052504beacd7ade0dc5e76d3a4c16e8"}, + {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e8a3b79b6d186a9c99761fd4a5e8dd575a48d96021f220ac5b5fa856e5dd029"}, + {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6777a390e41e78e7c45dab43a4a0196c55c3b8c30eebe017b152939372a83253"}, + {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7ae5f99a32c53731c93ac3075abd3e1e5cfbe72fc3eaac4c27c9dd64ba3b19fe"}, + {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f1e4f254e9c35d8965d377e065c4a8a55d396fe87c8e7e8429bcfdeeb229bfb3"}, + {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11ca808f9a6b63485059f5f6e164ef7ec826483c1212a44f268b3653c91237d8"}, + {file = "aiohttp-3.9.0-cp312-cp312-win32.whl", hash = "sha256:de3cc86f4ea8b4c34a6e43a7306c40c1275e52bfa9748d869c6b7d54aa6dad80"}, + {file = "aiohttp-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca4fddf84ac7d8a7d0866664936f93318ff01ee33e32381a115b19fb5a4d1202"}, + {file = "aiohttp-3.9.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f09960b5bb1017d16c0f9e9f7fc42160a5a49fa1e87a175fd4a2b1a1833ea0af"}, + {file = "aiohttp-3.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8303531e2c17b1a494ffaeba48f2da655fe932c4e9a2626c8718403c83e5dd2b"}, + {file = "aiohttp-3.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4790e44f46a4aa07b64504089def5744d3b6780468c4ec3a1a36eb7f2cae9814"}, + {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1d7edf74a36de0e5ca50787e83a77cf352f5504eb0ffa3f07000a911ba353fb"}, + {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94697c7293199c2a2551e3e3e18438b4cba293e79c6bc2319f5fd652fccb7456"}, + {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1b66dbb8a7d5f50e9e2ea3804b01e766308331d0cac76eb30c563ac89c95985"}, + {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9623cfd9e85b76b83ef88519d98326d4731f8d71869867e47a0b979ffec61c73"}, + {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f32c86dc967ab8c719fd229ce71917caad13cc1e8356ee997bf02c5b368799bf"}, + {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f50b4663c3e0262c3a361faf440761fbef60ccdde5fe8545689a4b3a3c149fb4"}, + {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dcf71c55ec853826cd70eadb2b6ac62ec577416442ca1e0a97ad875a1b3a0305"}, + {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:42fe4fd9f0dfcc7be4248c162d8056f1d51a04c60e53366b0098d1267c4c9da8"}, + {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76a86a9989ebf82ee61e06e2bab408aec4ea367dc6da35145c3352b60a112d11"}, + {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f9e09a1c83521d770d170b3801eea19b89f41ccaa61d53026ed111cb6f088887"}, + {file = "aiohttp-3.9.0-cp38-cp38-win32.whl", hash = "sha256:a00ce44c21612d185c5275c5cba4bab8d7c1590f248638b667ed8a782fa8cd6f"}, + {file = "aiohttp-3.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:d5b9345ab92ebe6003ae11d8092ce822a0242146e6fa270889b9ba965457ca40"}, + {file = "aiohttp-3.9.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98d21092bf2637c5fa724a428a69e8f5955f2182bff61f8036827cf6ce1157bf"}, + {file = "aiohttp-3.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:35a68cd63ca6aaef5707888f17a70c36efe62b099a4e853d33dc2e9872125be8"}, + {file = "aiohttp-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7f6235c7475658acfc1769d968e07ab585c79f6ca438ddfecaa9a08006aee2"}, + {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db04d1de548f7a62d1dd7e7cdf7c22893ee168e22701895067a28a8ed51b3735"}, + {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:536b01513d67d10baf6f71c72decdf492fb7433c5f2f133e9a9087379d4b6f31"}, + {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c8b0a6487e8109427ccf638580865b54e2e3db4a6e0e11c02639231b41fc0f"}, + {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7276fe0017664414fdc3618fca411630405f1aaf0cc3be69def650eb50441787"}, + {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23170247ef89ffa842a02bbfdc425028574d9e010611659abeb24d890bc53bb8"}, + {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b1a2ea8252cacc7fd51df5a56d7a2bb1986ed39be9397b51a08015727dfb69bd"}, + {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d71abc15ff7047412ef26bf812dfc8d0d1020d664617f4913df2df469f26b76"}, + {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:2d820162c8c2bdbe97d328cd4f417c955ca370027dce593345e437b2e9ffdc4d"}, + {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:2779f5e7c70f7b421915fd47db332c81de365678180a9f3ab404088f87ba5ff9"}, + {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:366bc870d7ac61726f32a489fbe3d1d8876e87506870be66b01aeb84389e967e"}, + {file = "aiohttp-3.9.0-cp39-cp39-win32.whl", hash = "sha256:1df43596b826022b14998f0460926ce261544fedefe0d2f653e1b20f49e96454"}, + {file = "aiohttp-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:9c196b30f1b1aa3363a69dd69079ae9bec96c2965c4707eaa6914ba099fb7d4f"}, + {file = "aiohttp-3.9.0.tar.gz", hash = "sha256:09f23292d29135025e19e8ff4f0a68df078fe4ee013bca0105b2e803989de92d"}, ] [package.dependencies] aiosignal = ">=1.1.2" -async-timeout = ">=4.0.0a3,<5.0" attrs = ">=17.3.0" -charset-normalizer = ">=2.0,<4.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "cchardet"] +speedups = ["Brotli", "aiodns", "brotlicffi"] [[package]] name = "aioice" @@ -220,17 +207,6 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] -[[package]] -name = "async-timeout" -version = "4.0.3" -description = "Timeout context manager for asyncio programs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, -] - [[package]] name = "atomicwrites" version = "1.4.1" @@ -468,13 +444,13 @@ numpy = "*" [[package]] name = "certifi" -version = "2023.7.22" +version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] [[package]] @@ -1183,53 +1159,53 @@ files = [ [[package]] name = "fonttools" -version = "4.44.1" +version = "4.45.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.44.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8acf50c20bae9880169ff133768a54f587d956676d28894401835a28f450935e"}, - {file = "fonttools-4.44.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02939e423540e05843a5c2b84704f45d307144f761a42a299d9b0b481e497225"}, - {file = "fonttools-4.44.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d96cc1395dbf82dedfd4eb127d320004088df6007383c25db676e5f42fe414"}, - {file = "fonttools-4.44.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aff3c12fba2525e5b7f7ba73fab10ddac386f8019b6cf2b8701239cf9f3e2a8a"}, - {file = "fonttools-4.44.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3675499e11a2332a867c1ce98792d29615ac143186c1c1d3e1bb7a13f1929b52"}, - {file = "fonttools-4.44.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fcf6c0c2bf2b0baeb55b5e44e9d6f2b71ede808949b8ab4daca077cc3f9cfff5"}, - {file = "fonttools-4.44.1-cp310-cp310-win32.whl", hash = "sha256:147c9f5fbe12486fa186b5ccdc64a537d581e4f9bbddfbc40f2a15a55c66f54e"}, - {file = "fonttools-4.44.1-cp310-cp310-win_amd64.whl", hash = "sha256:0bd45092788dbfb781fae299905695a3fe5c1956a515ee331c9f034da3a9d0e5"}, - {file = "fonttools-4.44.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d22d9a4cb3f0c96991d4dccab66c7377302c9ca09dcf0cbce968d73919585120"}, - {file = "fonttools-4.44.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0c1ac78c60b155ce709b50e28321baa3813dfae648bf55ac80d5a97c70d088e0"}, - {file = "fonttools-4.44.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6065578bdf96d4b69dd53d8b49ff02412b2a46d461b0d1ee5eddb81c3a953a46"}, - {file = "fonttools-4.44.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17702266ba92cef9a0d7418609f8f8b8e019192c62e8014f10b89a485af9d8ce"}, - {file = "fonttools-4.44.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c1d22a61a247262f178819f0331e0692e27c88be5770bf1c2404d0d52799f711"}, - {file = "fonttools-4.44.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:61b794e6a9208e7ee3abf11a9f56b9c1409967817dfd732f97b44812686cab1d"}, - {file = "fonttools-4.44.1-cp311-cp311-win32.whl", hash = "sha256:a02747ac741abe1fe994ac55b143432637d136e4a5a472e7a90574a015b57dc4"}, - {file = "fonttools-4.44.1-cp311-cp311-win_amd64.whl", hash = "sha256:8dd81470227c53ab78788f1b21b7e655b2c3aa66f0f670d9011f2deb64bed034"}, - {file = "fonttools-4.44.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:63efb6b66c275cb2c750576ed743f9995b92bcd733b72699599c6f74dce277c6"}, - {file = "fonttools-4.44.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25504368ce3dbdc5df1e6bee1980674b60216c543ad2647c85333f8daf5e9dd2"}, - {file = "fonttools-4.44.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc4acf7f1684d234d788cbb1976fbced4e1ae7c51abaf4314e11d1d86498ba76"}, - {file = "fonttools-4.44.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6698abcbb43f68ecfe473169d5928adf0a09ab8e6439322f80bc10a80ab9195d"}, - {file = "fonttools-4.44.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6dce674ba22419a9f3822f9c1b6bd823fce11d3a34372c580216167b6d9e232b"}, - {file = "fonttools-4.44.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8493c84ac86fdc5dde68b720959b79865cf5216a5c1ee9b4a161eac8c56dc310"}, - {file = "fonttools-4.44.1-cp312-cp312-win32.whl", hash = "sha256:a4a8734ddb91647d9545caae4dfb4633045c5dccb2fccb2d6c2a09424d975ef1"}, - {file = "fonttools-4.44.1-cp312-cp312-win_amd64.whl", hash = "sha256:7debaae9f267702ac4e89944bbfc4e55bc2d0ef891aa6c18d6afd9184a14554a"}, - {file = "fonttools-4.44.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:748d21764665209d5e0729ce8386fd01c92258699db732c7dbe4c9abf9e7c392"}, - {file = "fonttools-4.44.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:753de1235ac308111f80eacb9d92b328088adfec7147fd101692cc49ad53a3fe"}, - {file = "fonttools-4.44.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c142c11d28af532c7edddf191367d6acf2a2884bb4e2ba329c265f58ca865d0a"}, - {file = "fonttools-4.44.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd10d40028bf71e9604279875e4c258a36b2a42fff349fdf20141813d83cc61"}, - {file = "fonttools-4.44.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8e4961e26423ddd713672746c110e708c0094deae74493e21198d85f54f7d88c"}, - {file = "fonttools-4.44.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af89a9370dc463ffed3010af6fad1aa58998ce5eb5d94c2c2688768e6b108cc8"}, - {file = "fonttools-4.44.1-cp38-cp38-win32.whl", hash = "sha256:6d2d0e0d64a21b07c30206d500f8e77f4beaf80e7cc0ffd65a304a9ae1c0e197"}, - {file = "fonttools-4.44.1-cp38-cp38-win_amd64.whl", hash = "sha256:dc16e26668ec2ae01a37ff8293ce0326cf8c043e24fcf86fc38e6c25ddd98926"}, - {file = "fonttools-4.44.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a04ad39ac67c523c9f8f102706ac05d7e5cee3148a3519c6afc6ffbb3f0af7aa"}, - {file = "fonttools-4.44.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:11e9cba26fd658a491c82fdf5dc5bdb8078ca69ca70ba5724f63a66d486fa8b3"}, - {file = "fonttools-4.44.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fa904fb50c9f1b3ffbe352c7c4ed35eb16558933f011ff74f86f33504358e4d"}, - {file = "fonttools-4.44.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caf014bcc24673b681e7f768180f063691b301e2eccd9a53c43b5eebfb448bd8"}, - {file = "fonttools-4.44.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:97fb6f806694268d0c35abfc1f33662a1a96d12875a790b2b69d7b8d4fadbea5"}, - {file = "fonttools-4.44.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac523156bf227f009102cf33c116bcc18d6b9a05ea9a3a6eaa57e3adb42620a9"}, - {file = "fonttools-4.44.1-cp39-cp39-win32.whl", hash = "sha256:50aacecec89ca07ba97a63a949f9b273ccbdc105602ec4426c8a9a143f9e6aa3"}, - {file = "fonttools-4.44.1-cp39-cp39-win_amd64.whl", hash = "sha256:994c62a46cb2cfd670edc360d87c902ee475790fbddb267abd9fd8a83199423a"}, - {file = "fonttools-4.44.1-py3-none-any.whl", hash = "sha256:e775851c6884c16ed3831e461a0d5e271d9ebcd05204122d3a21ca2465a5d8c1"}, - {file = "fonttools-4.44.1.tar.gz", hash = "sha256:0d8ed83815a125b25c10404736a2cd43d60eb6479fe2d68373418cd1822ec330"}, + {file = "fonttools-4.45.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18138744540413eb2ebeff6ce8b9d617926f1ed08da5d1676f99f1966988264e"}, + {file = "fonttools-4.45.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3d240933045b9dbbe6e8c1e28ffe89be72c9be927b6e572e55be5e2b2604f7"}, + {file = "fonttools-4.45.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5853263505f407b69c0d1cbf3ed1c30f985b9505523989b20aa18a5231d4a08a"}, + {file = "fonttools-4.45.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c21f306f1e71146cf7587916d6de5e9c4bf26057aad602a6c7fad4b6e05bf1f"}, + {file = "fonttools-4.45.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1bb4f01018b9f4e2d7b07c2bf79e2ef498acb6f99321b72b5c44b1333481f569"}, + {file = "fonttools-4.45.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d0e6603c3b00604574d84fabbcf9dee81efa7c89d38ed4dcbf4e6c654e1ebb99"}, + {file = "fonttools-4.45.0-cp310-cp310-win32.whl", hash = "sha256:c3e676e50a0713c9a1e46185b806967d3c012643d1936ca814eb9ab62027c090"}, + {file = "fonttools-4.45.0-cp310-cp310-win_amd64.whl", hash = "sha256:e819f14d315024750b1ad2842da605051444b944cd983ea987359ace5213bcb9"}, + {file = "fonttools-4.45.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a6d68b97b967a3361e0ddf14425e4fe030c9f19462b445ce0b190c4a6834eb46"}, + {file = "fonttools-4.45.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:619227c7b9088add4d4e5959bf0fa3c29a71c191baa8b989bf532645876b2168"}, + {file = "fonttools-4.45.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cafe001811ad1ac2a5d357fc99c490357d758569f69511d14da0311c02814e15"}, + {file = "fonttools-4.45.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:756c6f4324fd4bb4d7517462d7237ff65e45da7a002f9e6e08a48c25a11bf073"}, + {file = "fonttools-4.45.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ecc97628be1bf7fa93463e1e791915da66de51df8e655a5a6c846fd9b8ceaa98"}, + {file = "fonttools-4.45.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:636177ffc4479bc824a356c00a3c9a74a2ce591fa6057109321e0a0ffd126e40"}, + {file = "fonttools-4.45.0-cp311-cp311-win32.whl", hash = "sha256:cac462dbd9058778c89bc608ac29ba93ab3fbc37f305d260aa2d771cfb0fa694"}, + {file = "fonttools-4.45.0-cp311-cp311-win_amd64.whl", hash = "sha256:2bd3f33a5d5630cc20cf3f8631074cac6eafdb2aa3ac8745966c3b4bf93656b4"}, + {file = "fonttools-4.45.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5b3da7791a58c97763d1704c2b76a9d654b8f2ef233e64248960bd2c6e669fe4"}, + {file = "fonttools-4.45.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4fbc3d8acb578ba0932fcabc01a962f23b0dd254ab103dd0606128fff0175095"}, + {file = "fonttools-4.45.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f69e07ebfd89d96485349dc10bd81a027fc0e927f988fa31bd9cd06359e06ed"}, + {file = "fonttools-4.45.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b122fe802839bfc8f9603233e5fcbdc98b5e27876f7945b426adfea983918a7b"}, + {file = "fonttools-4.45.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8f8736e885700ae22970519b8f5c7f4c2f29c6e9459d05c649c4e99012c20b23"}, + {file = "fonttools-4.45.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:70f047ba37c6aac3d331b71bb784373e251bd86678da72526edc8585e79418e1"}, + {file = "fonttools-4.45.0-cp312-cp312-win32.whl", hash = "sha256:fdb43f68bce545f494fed1bfb60d2c32b53f410758919112923c2d600cb9a24c"}, + {file = "fonttools-4.45.0-cp312-cp312-win_amd64.whl", hash = "sha256:102a7ca8700a078265c002c29d2ae498edeb14b636375ceec2425b561ce08037"}, + {file = "fonttools-4.45.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4b154cbf93515e4eb477f5cf99de79b46c17229781f321907940bdbabbd64708"}, + {file = "fonttools-4.45.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f546a1b676622638a12c721d89cfb513ad7189548eadac885cdd555e35021557"}, + {file = "fonttools-4.45.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c5a0612049e0d06b467c3a0837d9efe37934acab64ba922f00e1d07c1555a7"}, + {file = "fonttools-4.45.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27166d00e0cd3ea49461b053f55e75676f1109e5483170a14d70c397d082a4c"}, + {file = "fonttools-4.45.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:980ba4e673439db22a00501fac087957ce0731351b042816f6c02df81cadc612"}, + {file = "fonttools-4.45.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:83b70b99f3f55b046cb51ca20fc15702567710233b2cd757a79e1916c25a25f8"}, + {file = "fonttools-4.45.0-cp38-cp38-win32.whl", hash = "sha256:fe8ad943f62bf16273154ebcdf855c44a3b46eac36abea338c209209439b4eb6"}, + {file = "fonttools-4.45.0-cp38-cp38-win_amd64.whl", hash = "sha256:6fb1fdcee2b36e012283805ef0380e4508dbb504950b1c94d0343f8dbbad7d7e"}, + {file = "fonttools-4.45.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5bbcb75ffcea64543ab8203e132e2019b226f59a4a6958637e78c21f9ca560ff"}, + {file = "fonttools-4.45.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ade07953b35ebf66c445a5e02f28ecd038ea588dc7673c555afe319b6e3c5168"}, + {file = "fonttools-4.45.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54ac8be3f693062fc08550213edd40db8f4fe1dd095a1246ed18e887fc254d76"}, + {file = "fonttools-4.45.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc991712aaea9d545b13ec480aaf2ebd12ccdea180fce864dd9863f5134f5a06"}, + {file = "fonttools-4.45.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:420139743e21d832de230757fb7b0c285d8024d602af8064d9506fa055bb62ae"}, + {file = "fonttools-4.45.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:58da830a52c05f18a7cc8a279a8bdddf2e45cdc685b360699653fb3405557459"}, + {file = "fonttools-4.45.0-cp39-cp39-win32.whl", hash = "sha256:715e18f67f0587a16476c7f62b8ff9a165ddceb8c2a262fb08df9f71c7790f0e"}, + {file = "fonttools-4.45.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd26fda8c879558216458a801c1dba52f35dca0e96640fd9c75e86b6574cf1c3"}, + {file = "fonttools-4.45.0-py3-none-any.whl", hash = "sha256:835cf5d0e1b37bbed1d64c286611cc4da9ff19df952400f191ba9142b3cb97f6"}, + {file = "fonttools-4.45.0.tar.gz", hash = "sha256:c1c79d7d4093396892575115c214b24f09e68997cb5c0ab2d99bfdaff74c64b6"}, ] [package.extras] @@ -1603,13 +1579,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"] [[package]] name = "identify" -version = "2.5.31" +version = "2.5.32" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"}, - {file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"}, + {file = "identify-2.5.32-py2.py3-none-any.whl", hash = "sha256:0b7656ef6cba81664b783352c73f8c24b39cf82f926f78f4550eda928e5e0545"}, + {file = "identify-2.5.32.tar.gz", hash = "sha256:5d9979348ec1a21c768ae07e0a652924538e8bce67313a73cb0f681cf08ba407"}, ] [package.extras] @@ -2126,6 +2102,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2160,39 +2146,39 @@ files = [ [[package]] name = "matplotlib" -version = "3.8.1" +version = "3.8.2" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e11ab864323fa73ac1b7849688d9671c47a2665242e899785b4db1a375b547e1"}, - {file = "matplotlib-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43a9d40feb63c9e31a0b8b069dcbd74a912f59bdc0095d187126694cd26977e4"}, - {file = "matplotlib-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:608ea2951838d391e45dec2e644888db6899c752d3c29e157af9dcefb3d7d8d5"}, - {file = "matplotlib-3.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82ec95b02e894561c21e066bd0c716e4b410df141ce9441aa5af6cd937e4ade2"}, - {file = "matplotlib-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e3ad1759ad4a5245172c6d32b8ada603a6020d03211524c39d78d25c9a7dc0d2"}, - {file = "matplotlib-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:20a0fdfd3ee836179047f3782be060057b878ad37f5abe29edf006a1ff3ecd73"}, - {file = "matplotlib-3.8.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7658b7073c1d6a2922ecc0ed41602410fae88586cb8a54f7a2063d537b6beaf7"}, - {file = "matplotlib-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf6889643d4560fcc56f9f0941f078e4df0d72a6c3e4ca548841fc13c5642664"}, - {file = "matplotlib-3.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff842e27bc6a80de08c40e0bfdce460bd08080e8a94af131162b6a1b8948f2cc"}, - {file = "matplotlib-3.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f99d07c0e753717775be7be39ab383453b4d8b629c9fa174596b970c6555890"}, - {file = "matplotlib-3.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f34b46dbb1db1f09bfa937cd5853e5f2af232caeeff509c3ab6e43fd33780eae"}, - {file = "matplotlib-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:1fcb49b6baf0375281979cbf26695ec10bd1cada1e311893e89533b3b70143e7"}, - {file = "matplotlib-3.8.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e17674ee127f78f26fea237e7f4d5cf910a8be82beb6260fedf358b88075b823"}, - {file = "matplotlib-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d921c0270647ab11c3ef283efaaa3d46fd005ba233bfb3aea75231cdf3656de8"}, - {file = "matplotlib-3.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2afe7d2f8c9e35e94fbcfcfd9b28f29cb32f0a9068cba469cf907428379c8db9"}, - {file = "matplotlib-3.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5a504ff40f81d6233603475a45497a6dca37a873393fa20ae6f7dd6596ef72b"}, - {file = "matplotlib-3.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cd54bbf089953140905768ed4626d7223e1ad1d7e2a138410a9c4d3b865ccd80"}, - {file = "matplotlib-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:27502d2452208ae784c19504644f09f83742809143bbeae147617640930aa344"}, - {file = "matplotlib-3.8.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:f55fb5ff02d999a100be28bf6ffe826e1867a54c7b465409685332c9dd48ffa5"}, - {file = "matplotlib-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:afb72822ae410d62aa1a2920c6563cb5680de9078358f0e9474396c6c3e06be2"}, - {file = "matplotlib-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43cf368a4a1d8cbc426944806e5e183cead746647a64d2cdb786441546235967"}, - {file = "matplotlib-3.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c54c55457c7f5ea4dfdba0020004fc7667f5c10c8d9b8010d735345acc06c9b8"}, - {file = "matplotlib-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e3bb809b743653b5aab5d72ee45c8c937c28e147b0846b0826a54bece898608c"}, - {file = "matplotlib-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:c1b0ecaa0d1f4fe1e30f625a2347f0034a89a7d17c39efbb502e554d92ee2f61"}, - {file = "matplotlib-3.8.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca84deaa38cb64b7dd160ca2046b45f7b5dbff2b0179642e1339fadc337446c9"}, - {file = "matplotlib-3.8.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed3b29f54f6bbf3eaca4cbd23bc260155153ace63b7f597c474fa6fc6f386530"}, - {file = "matplotlib-3.8.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d24c47a1bb47e392fbcd26fe322e4ff3431653ac1e8718e4e147d450ae97a44"}, - {file = "matplotlib-3.8.1.tar.gz", hash = "sha256:044df81c1f6f3a8e52d70c4cfcb44e77ea9632a10929932870dfaa90de94365d"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, + {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, + {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, + {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, + {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, + {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, + {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, + {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, + {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, + {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, + {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, + {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, + {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, + {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, + {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, + {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, + {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, + {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, + {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, ] [package.dependencies] @@ -2625,35 +2611,35 @@ reference = ["Pillow", "google-re2"] [[package]] name = "onnxruntime" -version = "1.16.2" +version = "1.16.3" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime-1.16.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:e19316bb15c29ca0397e78861ee7cdb4db763ac5c53eaa83169bcdcb1149878c"}, - {file = "onnxruntime-1.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:773f6d99d1e6a58936a55a4933c66674241dace9ec4bab71664cdfa170a7cd87"}, - {file = "onnxruntime-1.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b8df9583a6e874f1983b85a361d22c205c96e926626eb486d3e69d72642f79"}, - {file = "onnxruntime-1.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceef600de846997e3ef5f9af956ae87c88d84d6e925c3e9d435ce17ea223568f"}, - {file = "onnxruntime-1.16.2-cp310-cp310-win32.whl", hash = "sha256:4fed41edb766c6adea6c34f1eb63a344d697fd4625133e5e48f23950bce60803"}, - {file = "onnxruntime-1.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:9fc410ec220804fb384e7cb4fd68c474d89da11a1b68184db2001d64ba1477a9"}, - {file = "onnxruntime-1.16.2-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:aa09d8d9d9a4dc2f6647b5135bb540da36e2d78206aaf14140ba73e05928c4f8"}, - {file = "onnxruntime-1.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68f8d3347f11fcc6256266c562e4314b8c6da3e30fc275052a2ab693540b17fd"}, - {file = "onnxruntime-1.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16217fa87d3482300a91036f9b499c85215a3b495de1ef9a68cbcf3df1a7c548"}, - {file = "onnxruntime-1.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6b7046005442fcd09b86647bdc9a85d60c1367cb36ce7f16b942744cf27fe4"}, - {file = "onnxruntime-1.16.2-cp311-cp311-win32.whl", hash = "sha256:773c231e526f815b8a3f3549d216cd8fed4c9e226e9e16e86af1b69a4bd29b58"}, - {file = "onnxruntime-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:90e83a93b3d946c4a1d9dcbae286350accb0d80512d7c1b85953a444d19c0058"}, - {file = "onnxruntime-1.16.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:8616f56905775dd8beeae11cf145542fff06c38cd97bfe9afe0c4a66142fc6d5"}, - {file = "onnxruntime-1.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9f5e1d5ca5560044896edb2ad79113f863dc7daa804a26787c7b21c2a96d41e7"}, - {file = "onnxruntime-1.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97ce538ffb668c4897e7500a586c150a045869876e0234e0611c4e4f428be63"}, - {file = "onnxruntime-1.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cadf175baa782599f36586c23f84fe12b02702ceb59be57dbd8eefc6cc13cc4"}, - {file = "onnxruntime-1.16.2-cp38-cp38-win32.whl", hash = "sha256:0ffd3b8a3039be713476b8783d254564976664c9b51ec70e7fb5d3e2832bf0f0"}, - {file = "onnxruntime-1.16.2-cp38-cp38-win_amd64.whl", hash = "sha256:e2211f336e83819edbf174dcf56de35b0dcbfc6c92d3b685c8d85fba19bdf97d"}, - {file = "onnxruntime-1.16.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:98a49bda980bcf819f8d9be880e3e7ba8a1df66aa5ce4fc7bb68ba9acf1fc7ad"}, - {file = "onnxruntime-1.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f1e90fa0f43e988cd043e5a4b1eb77eda6cbd7523f316d93d36b33ff1ceb91f"}, - {file = "onnxruntime-1.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0cbdb7df8078b2e8d9804de948963961eb8c6f417ef35ed243455162a9a065c"}, - {file = "onnxruntime-1.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93c1cbd885c5fe0018b982c9dabe3cc3531416a3b50d0958a291605b32fe3ce"}, - {file = "onnxruntime-1.16.2-cp39-cp39-win32.whl", hash = "sha256:713101b65d74438f380f5ea2475ce4f6026171e6229100e5be2baa92519fca17"}, - {file = "onnxruntime-1.16.2-cp39-cp39-win_amd64.whl", hash = "sha256:3382934f9d86060b6bacd3eb4633c5ff904be2c99d3a7fb7faf2828381b15928"}, + {file = "onnxruntime-1.16.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:3bc41f323ac77acfed190be8ffdc47a6a75e4beeb3473fbf55eeb075ccca8df2"}, + {file = "onnxruntime-1.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:212741b519ee61a4822c79c47147d63a8b0ffde25cd33988d3d7be9fbd51005d"}, + {file = "onnxruntime-1.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f91f5497fe3df4ceee2f9e66c6148d9bfeb320cd6a71df361c66c5b8bac985a"}, + {file = "onnxruntime-1.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b1fc269cabd27f129fb9058917d6fdc89b188c49ed8700f300b945c81f889"}, + {file = "onnxruntime-1.16.3-cp310-cp310-win32.whl", hash = "sha256:f36b56a593b49a3c430be008c2aea6658d91a3030115729609ec1d5ffbaab1b6"}, + {file = "onnxruntime-1.16.3-cp310-cp310-win_amd64.whl", hash = "sha256:3c467eaa3d2429c026b10c3d17b78b7f311f718ef9d2a0d6938e5c3c2611b0cf"}, + {file = "onnxruntime-1.16.3-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:a225bb683991001d111f75323d355b3590e75e16b5e0f07a0401e741a0143ea1"}, + {file = "onnxruntime-1.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9aded21fe3d898edd86be8aa2eb995aa375e800ad3dfe4be9f618a20b8ee3630"}, + {file = "onnxruntime-1.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00cccc37a5195c8fca5011b9690b349db435986bd508eb44c9fce432da9228a4"}, + {file = "onnxruntime-1.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e253e572021563226a86f1c024f8f70cdae28f2fb1cc8c3a9221e8b1ce37db5"}, + {file = "onnxruntime-1.16.3-cp311-cp311-win32.whl", hash = "sha256:a82a8f0b4c978d08f9f5c7a6019ae51151bced9fd91e5aaa0c20a9e4ac7a60b6"}, + {file = "onnxruntime-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:78d81d9af457a1dc90db9a7da0d09f3ccb1288ea1236c6ab19f0ca61f3eee2d3"}, + {file = "onnxruntime-1.16.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:04ebcd29c20473596a1412e471524b2fb88d55e6301c40b98dd2407b5911595f"}, + {file = "onnxruntime-1.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9996bab0f202a6435ab867bc55598f15210d0b72794d5de83712b53d564084ae"}, + {file = "onnxruntime-1.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b8f5083f903408238883821dd8c775f8120cb4a604166dbdabe97f4715256d5"}, + {file = "onnxruntime-1.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c2dcf1b70f8434abb1116fe0975c00e740722aaf321997195ea3618cc00558e"}, + {file = "onnxruntime-1.16.3-cp38-cp38-win32.whl", hash = "sha256:d4a0151e1accd04da6711f6fd89024509602f82c65a754498e960b032359b02d"}, + {file = "onnxruntime-1.16.3-cp38-cp38-win_amd64.whl", hash = "sha256:e8aa5bba78afbd4d8a2654b14ec7462ff3ce4a6aad312a3c2d2c2b65009f2541"}, + {file = "onnxruntime-1.16.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:6829dc2a79d48c911fedaf4c0f01e03c86297d32718a3fdee7a282766dfd282a"}, + {file = "onnxruntime-1.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:76f876c53bfa912c6c242fc38213a6f13f47612d4360bc9d599bd23753e53161"}, + {file = "onnxruntime-1.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4137e5d443e2dccebe5e156a47f1d6d66f8077b03587c35f11ee0c7eda98b533"}, + {file = "onnxruntime-1.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56695c1a343c7c008b647fff3df44da63741fbe7b6003ef576758640719be7b"}, + {file = "onnxruntime-1.16.3-cp39-cp39-win32.whl", hash = "sha256:985a029798744ce4743fcf8442240fed35c8e4d4d30ec7d0c2cdf1388cd44408"}, + {file = "onnxruntime-1.16.3-cp39-cp39-win_amd64.whl", hash = "sha256:28ff758b17ce3ca6bcad3d936ec53bd7f5482e7630a13f6dcae518eba8f71d85"}, ] [package.dependencies] @@ -2666,19 +2652,19 @@ sympy = "*" [[package]] name = "onnxruntime-gpu" -version = "1.16.2" +version = "1.16.3" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime_gpu-1.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cfcac6d84ca94b1785c95c6412edc7d7a894055c2fbb854c75f875fb44b2df9"}, - {file = "onnxruntime_gpu-1.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:6713feab198ad67db44c9354c052603872b926a4175e496a7b5069224d7b75bc"}, - {file = "onnxruntime_gpu-1.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f10399d051dae1c496ee812e388da74bf03888ad0e01dcac4859eed4750f8c42"}, - {file = "onnxruntime_gpu-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:bae364c4bb281c654e5ee5f74fd1779ca3517ab1801cd81cfd3014aae490592e"}, - {file = "onnxruntime_gpu-1.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2638e46d4139c8e1eb08246661ae51ef206c90178f1d8872695681e157b8a44"}, - {file = "onnxruntime_gpu-1.16.2-cp38-cp38-win_amd64.whl", hash = "sha256:65848e0a01546a78207824c44e893dc03eead71ce22bf70530f53fbe358b0e3d"}, - {file = "onnxruntime_gpu-1.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a692c53604558dc80880401875f6e975c544af141f650c295b970a3fa59c723"}, - {file = "onnxruntime_gpu-1.16.2-cp39-cp39-win_amd64.whl", hash = "sha256:42ba53e866efa59d03e9ef5b21b1fb4e11222600b8843b91c488138e091dfca7"}, + {file = "onnxruntime_gpu-1.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14bc735ad2b2286be9eadeea09bc190df38e8bce17e37b601761019cc7cc24f"}, + {file = "onnxruntime_gpu-1.16.3-cp310-cp310-win_amd64.whl", hash = "sha256:8de5ccfc005ea5ec50fbd104b7210c97623a9f8c13de6e64ce559b55956b757f"}, + {file = "onnxruntime_gpu-1.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5703454521a9c080ff3ac79b5d266e959cc735d442a1d8796763c7f92d6069dc"}, + {file = "onnxruntime_gpu-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:48bb615aed61f5620d1ad46b9005614e1a14de60f8218a1448cc9a643f23d399"}, + {file = "onnxruntime_gpu-1.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2811c8ea209aaedcc2600ca828025279c1b1242344af603122d28c2ea8ab26a4"}, + {file = "onnxruntime_gpu-1.16.3-cp38-cp38-win_amd64.whl", hash = "sha256:2e5a92770c9232776739f378804bf6fea20bae02878a50b7fe0f81e77a47ee92"}, + {file = "onnxruntime_gpu-1.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9305c7fc5981d7e04ad2afef1a403475fb84d658898567c91aa5a41c20ead356"}, + {file = "onnxruntime_gpu-1.16.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3ad8e7fbb22493267c23d61e997a6b2ac6236a08aa6b58a3a91848124c9b037"}, ] [package.dependencies] @@ -3124,22 +3110,22 @@ files = [ [[package]] name = "protobuf" -version = "4.25.0" +version = "4.25.1" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.0-cp310-abi3-win32.whl", hash = "sha256:5c1203ac9f50e4853b0a0bfffd32c67118ef552a33942982eeab543f5c634395"}, - {file = "protobuf-4.25.0-cp310-abi3-win_amd64.whl", hash = "sha256:c40ff8f00aa737938c5378d461637d15c442a12275a81019cc2fef06d81c9419"}, - {file = "protobuf-4.25.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:cf21faba64cd2c9a3ed92b7a67f226296b10159dbb8fbc5e854fc90657d908e4"}, - {file = "protobuf-4.25.0-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:32ac2100b0e23412413d948c03060184d34a7c50b3e5d7524ee96ac2b10acf51"}, - {file = "protobuf-4.25.0-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:683dc44c61f2620b32ce4927de2108f3ebe8ccf2fd716e1e684e5a50da154054"}, - {file = "protobuf-4.25.0-cp38-cp38-win32.whl", hash = "sha256:1a3ba712877e6d37013cdc3476040ea1e313a6c2e1580836a94f76b3c176d575"}, - {file = "protobuf-4.25.0-cp38-cp38-win_amd64.whl", hash = "sha256:b2cf8b5d381f9378afe84618288b239e75665fe58d0f3fd5db400959274296e9"}, - {file = "protobuf-4.25.0-cp39-cp39-win32.whl", hash = "sha256:63714e79b761a37048c9701a37438aa29945cd2417a97076048232c1df07b701"}, - {file = "protobuf-4.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:d94a33db8b7ddbd0af7c467475fb9fde0c705fb315a8433c0e2020942b863a1f"}, - {file = "protobuf-4.25.0-py3-none-any.whl", hash = "sha256:1a53d6f64b00eecf53b65ff4a8c23dc95df1fa1e97bb06b8122e5a64f49fc90a"}, - {file = "protobuf-4.25.0.tar.gz", hash = "sha256:68f7caf0d4f012fd194a301420cf6aa258366144d814f358c5b32558228afa7c"}, + {file = "protobuf-4.25.1-cp310-abi3-win32.whl", hash = "sha256:193f50a6ab78a970c9b4f148e7c750cfde64f59815e86f686c22e26b4fe01ce7"}, + {file = "protobuf-4.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:3497c1af9f2526962f09329fd61a36566305e6c72da2590ae0d7d1322818843b"}, + {file = "protobuf-4.25.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:0bf384e75b92c42830c0a679b0cd4d6e2b36ae0cf3dbb1e1dfdda48a244f4bcd"}, + {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:0f881b589ff449bf0b931a711926e9ddaad3b35089cc039ce1af50b21a4ae8cb"}, + {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:ca37bf6a6d0046272c152eea90d2e4ef34593aaa32e8873fc14c16440f22d4b7"}, + {file = "protobuf-4.25.1-cp38-cp38-win32.whl", hash = "sha256:abc0525ae2689a8000837729eef7883b9391cd6aa7950249dcf5a4ede230d5dd"}, + {file = "protobuf-4.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:1484f9e692091450e7edf418c939e15bfc8fc68856e36ce399aed6889dae8bb0"}, + {file = "protobuf-4.25.1-cp39-cp39-win32.whl", hash = "sha256:8bdbeaddaac52d15c6dce38c71b03038ef7772b977847eb6d374fc86636fa510"}, + {file = "protobuf-4.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:becc576b7e6b553d22cbdf418686ee4daa443d7217999125c045ad56322dda10"}, + {file = "protobuf-4.25.1-py3-none-any.whl", hash = "sha256:a19731d5e83ae4737bb2a089605e636077ac001d18781b3cf489b9546c7c80d6"}, + {file = "protobuf-4.25.1.tar.gz", hash = "sha256:57d65074b4f5baa4ab5da1605c02be90ac20c8b40fb137d6a8df9f416b0d0ce2"}, ] [[package]] @@ -3391,17 +3377,18 @@ files = [ [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pygments-2.17.1-py3-none-any.whl", hash = "sha256:1b37f1b1e1bff2af52ecaf28cc601e2ef7077000b227a0675da25aef85784bc4"}, + {file = "pygments-2.17.1.tar.gz", hash = "sha256:e45a0e74bf9c530f564ca81b8952343be986a29f6afe7f5ad95c5f06b7bdf5e8"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" @@ -4080,62 +4067,62 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.1.5" +version = "0.1.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.5-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:32d47fc69261c21a4c48916f16ca272bf2f273eb635d91c65d5cd548bf1f3d96"}, - {file = "ruff-0.1.5-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:171276c1df6c07fa0597fb946139ced1c2978f4f0b8254f201281729981f3c17"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ef33cd0bb7316ca65649fc748acc1406dfa4da96a3d0cde6d52f2e866c7b39"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2c205827b3f8c13b4a432e9585750b93fd907986fe1aec62b2a02cf4401eee6"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb408e3a2ad8f6881d0f2e7ad70cddb3ed9f200eb3517a91a245bbe27101d379"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f20dc5e5905ddb407060ca27267c7174f532375c08076d1a953cf7bb016f5a24"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aafb9d2b671ed934998e881e2c0f5845a4295e84e719359c71c39a5363cccc91"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4894dddb476597a0ba4473d72a23151b8b3b0b5f958f2cf4d3f1c572cdb7af7"}, - {file = "ruff-0.1.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00a7ec893f665ed60008c70fe9eeb58d210e6b4d83ec6654a9904871f982a2a"}, - {file = "ruff-0.1.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8c11206b47f283cbda399a654fd0178d7a389e631f19f51da15cbe631480c5b"}, - {file = "ruff-0.1.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fa29e67b3284b9a79b1a85ee66e293a94ac6b7bb068b307a8a373c3d343aa8ec"}, - {file = "ruff-0.1.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9b97fd6da44d6cceb188147b68db69a5741fbc736465b5cea3928fdac0bc1aeb"}, - {file = "ruff-0.1.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:721f4b9d3b4161df8dc9f09aa8562e39d14e55a4dbaa451a8e55bdc9590e20f4"}, - {file = "ruff-0.1.5-py3-none-win32.whl", hash = "sha256:f80c73bba6bc69e4fdc73b3991db0b546ce641bdcd5b07210b8ad6f64c79f1ab"}, - {file = "ruff-0.1.5-py3-none-win_amd64.whl", hash = "sha256:c21fe20ee7d76206d290a76271c1af7a5096bc4c73ab9383ed2ad35f852a0087"}, - {file = "ruff-0.1.5-py3-none-win_arm64.whl", hash = "sha256:82bfcb9927e88c1ed50f49ac6c9728dab3ea451212693fe40d08d314663e412f"}, - {file = "ruff-0.1.5.tar.gz", hash = "sha256:5cbec0ef2ae1748fb194f420fb03fb2c25c3258c86129af7172ff8f198f125ab"}, + {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:88b8cdf6abf98130991cbc9f6438f35f6e8d41a02622cc5ee130a02a0ed28703"}, + {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c549ed437680b6105a1299d2cd30e4964211606eeb48a0ff7a93ef70b902248"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf5f701062e294f2167e66d11b092bba7af6a057668ed618a9253e1e90cfd76"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05991ee20d4ac4bb78385360c684e4b417edd971030ab12a4fbd075ff535050e"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87455a0c1f739b3c069e2f4c43b66479a54dea0276dd5d4d67b091265f6fd1dc"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:683aa5bdda5a48cb8266fcde8eea2a6af4e5700a392c56ea5fb5f0d4bfdc0240"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:137852105586dcbf80c1717facb6781555c4e99f520c9c827bd414fac67ddfb6"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd98138a98d48a1c36c394fd6b84cd943ac92a08278aa8ac8c0fdefcf7138f35"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0cd909d25f227ac5c36d4e7e681577275fb74ba3b11d288aff7ec47e3ae745"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8fd1c62a47aa88a02707b5dd20c5ff20d035d634aa74826b42a1da77861b5ff"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd89b45d374935829134a082617954120d7a1470a9f0ec0e7f3ead983edc48cc"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:491262006e92f825b145cd1e52948073c56560243b55fb3b4ecb142f6f0e9543"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea284789861b8b5ca9d5443591a92a397ac183d4351882ab52f6296b4fdd5462"}, + {file = "ruff-0.1.6-py3-none-win32.whl", hash = "sha256:1610e14750826dfc207ccbcdd7331b6bd285607d4181df9c1c6ae26646d6848a"}, + {file = "ruff-0.1.6-py3-none-win_amd64.whl", hash = "sha256:4558b3e178145491e9bc3b2ee3c4b42f19d19384eaa5c59d10acf6e8f8b57e33"}, + {file = "ruff-0.1.6-py3-none-win_arm64.whl", hash = "sha256:03910e81df0d8db0e30050725a5802441c2022ea3ae4fe0609b76081731accbc"}, + {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] [[package]] name = "scipy" -version = "1.11.3" +version = "1.11.4" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = "<3.13,>=3.9" +python-versions = ">=3.9" files = [ - {file = "scipy-1.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:370f569c57e1d888304052c18e58f4a927338eafdaef78613c685ca2ea0d1fa0"}, - {file = "scipy-1.11.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:9885e3e4f13b2bd44aaf2a1a6390a11add9f48d5295f7a592393ceb8991577a3"}, - {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04aa19acc324a1a076abb4035dabe9b64badb19f76ad9c798bde39d41025cdc"}, - {file = "scipy-1.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e1a8a4657673bfae1e05e1e1d6e94b0cabe5ed0c7c144c8aa7b7dbb774ce5c1"}, - {file = "scipy-1.11.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7abda0e62ef00cde826d441485e2e32fe737bdddee3324e35c0e01dee65e2a88"}, - {file = "scipy-1.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:033c3fd95d55012dd1148b201b72ae854d5086d25e7c316ec9850de4fe776929"}, - {file = "scipy-1.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:925c6f09d0053b1c0f90b2d92d03b261e889b20d1c9b08a3a51f61afc5f58165"}, - {file = "scipy-1.11.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5664e364f90be8219283eeb844323ff8cd79d7acbd64e15eb9c46b9bc7f6a42a"}, - {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f325434b6424952fbb636506f0567898dca7b0f7654d48f1c382ea338ce9a3"}, - {file = "scipy-1.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f290cf561a4b4edfe8d1001ee4be6da60c1c4ea712985b58bf6bc62badee221"}, - {file = "scipy-1.11.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:91770cb3b1e81ae19463b3c235bf1e0e330767dca9eb4cd73ba3ded6c4151e4d"}, - {file = "scipy-1.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1f97cd89c0fe1a0685f8f89d85fa305deb3067d0668151571ba50913e445820"}, - {file = "scipy-1.11.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dfcc1552add7cb7c13fb70efcb2389d0624d571aaf2c80b04117e2755a0c5d15"}, - {file = "scipy-1.11.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0d3a136ae1ff0883fffbb1b05b0b2fea251cb1046a5077d0b435a1839b3e52b7"}, - {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bae66a2d7d5768eaa33008fa5a974389f167183c87bf39160d3fefe6664f8ddc"}, - {file = "scipy-1.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2f6dee6cbb0e263b8142ed587bc93e3ed5e777f1f75448d24fb923d9fd4dce6"}, - {file = "scipy-1.11.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:74e89dc5e00201e71dd94f5f382ab1c6a9f3ff806c7d24e4e90928bb1aafb280"}, - {file = "scipy-1.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:90271dbde4be191522b3903fc97334e3956d7cfb9cce3f0718d0ab4fd7d8bfd6"}, - {file = "scipy-1.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a63d1ec9cadecce838467ce0631c17c15c7197ae61e49429434ba01d618caa83"}, - {file = "scipy-1.11.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:5305792c7110e32ff155aed0df46aa60a60fc6e52cd4ee02cdeb67eaccd5356e"}, - {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ea7f579182d83d00fed0e5c11a4aa5ffe01460444219dedc448a36adf0c3917"}, - {file = "scipy-1.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c77da50c9a91e23beb63c2a711ef9e9ca9a2060442757dffee34ea41847d8156"}, - {file = "scipy-1.11.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:15f237e890c24aef6891c7d008f9ff7e758c6ef39a2b5df264650eb7900403c0"}, - {file = "scipy-1.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:4b4bb134c7aa457e26cc6ea482b016fef45db71417d55cc6d8f43d799cdf9ef2"}, - {file = "scipy-1.11.3.tar.gz", hash = "sha256:bba4d955f54edd61899776bad459bf7326e14b9fa1c552181f0479cc60a568cd"}, + {file = "scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710"}, + {file = "scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41"}, + {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4"}, + {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56"}, + {file = "scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446"}, + {file = "scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3"}, + {file = "scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be"}, + {file = "scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8"}, + {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c"}, + {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff"}, + {file = "scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993"}, + {file = "scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd"}, + {file = "scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6"}, + {file = "scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d"}, + {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4"}, + {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79"}, + {file = "scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660"}, + {file = "scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97"}, + {file = "scipy-1.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7"}, + {file = "scipy-1.11.4-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec"}, + {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea"}, + {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937"}, + {file = "scipy-1.11.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd"}, + {file = "scipy-1.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65"}, + {file = "scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa"}, ] [package.dependencies] @@ -4148,13 +4135,13 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo [[package]] name = "scons" -version = "4.5.2" +version = "4.6.0" description = "Open Source next-generation build tool." optional = false python-versions = ">=3.6" files = [ - {file = "SCons-4.5.2-py3-none-any.whl", hash = "sha256:2f66a1c5c485068a496c12356583eefb2d79e17177278c7334b12b460f0503ce"}, - {file = "SCons-4.5.2.tar.gz", hash = "sha256:813360b2bce476bc9cc12a0f3a22d46ce520796b352557202cb07d3e402f5458"}, + {file = "SCons-4.6.0-py3-none-any.whl", hash = "sha256:07cc8ed992561c9a3af3d5b65a4635cb05b2a8c0695be90c4879ac6018d84ccc"}, + {file = "SCons-4.6.0.tar.gz", hash = "sha256:7db28958b188b800f803c287d0680cc3ac7c422ed0b1cf9895042c52567803ec"}, ] [package.dependencies] @@ -4346,17 +4333,17 @@ test = ["pytest"] [[package]] name = "setuptools" -version = "68.2.2" +version = "69.0.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, - {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, + {file = "setuptools-69.0.1-py3-none-any.whl", hash = "sha256:6875bbd06382d857b1b90cd07cee6a2df701a164f241095706b5192bc56c5c62"}, + {file = "setuptools-69.0.1.tar.gz", hash = "sha256:f25195d54deb649832182d6455bffba7ac3d8fe71d35185e738d2198a4310044"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] @@ -4926,85 +4913,101 @@ tomli = ">=2.0.1" [[package]] name = "yarl" -version = "1.9.2" +version = "1.9.3" description = "Yet another URL library" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, - {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, - {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, - {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, - {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, - {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, - {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, - {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, - {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, - {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, - {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, - {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, - {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, - {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, - {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, - {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, - {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, - {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, - {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, - {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, - {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, - {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, - {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, - {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, - {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, - {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, - {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32435d134414e01d937cd9d6cc56e8413a8d4741dea36af5840c7750f04d16ab"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a5211de242754b5e612557bca701f39f8b1a9408dff73c6db623f22d20f470e"}, + {file = "yarl-1.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:525cd69eff44833b01f8ef39aa33a9cc53a99ff7f9d76a6ef6a9fb758f54d0ff"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc94441bcf9cb8c59f51f23193316afefbf3ff858460cb47b5758bf66a14d130"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e36021db54b8a0475805acc1d6c4bca5d9f52c3825ad29ae2d398a9d530ddb88"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0f17d1df951336a02afc8270c03c0c6e60d1f9996fcbd43a4ce6be81de0bd9d"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f3faeb8100a43adf3e7925d556801d14b5816a0ac9e75e22948e787feec642"}, + {file = "yarl-1.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aed37db837ecb5962469fad448aaae0f0ee94ffce2062cf2eb9aed13328b5196"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:721ee3fc292f0d069a04016ef2c3a25595d48c5b8ddc6029be46f6158d129c92"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b8bc5b87a65a4e64bc83385c05145ea901b613d0d3a434d434b55511b6ab0067"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:dd952b9c64f3b21aedd09b8fe958e4931864dba69926d8a90c90d36ac4e28c9a"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:c405d482c320a88ab53dcbd98d6d6f32ada074f2d965d6e9bf2d823158fa97de"}, + {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9df9a0d4c5624790a0dea2e02e3b1b3c69aed14bcb8650e19606d9df3719e87d"}, + {file = "yarl-1.9.3-cp310-cp310-win32.whl", hash = "sha256:d34c4f80956227f2686ddea5b3585e109c2733e2d4ef12eb1b8b4e84f09a2ab6"}, + {file = "yarl-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:cf7a4e8de7f1092829caef66fd90eaf3710bc5efd322a816d5677b7664893c93"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d61a0ca95503867d4d627517bcfdc28a8468c3f1b0b06c626f30dd759d3999fd"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73cc83f918b69110813a7d95024266072d987b903a623ecae673d1e71579d566"}, + {file = "yarl-1.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d81657b23e0edb84b37167e98aefb04ae16cbc5352770057893bd222cdc6e45f"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a1a8443091c7fbc17b84a0d9f38de34b8423b459fb853e6c8cdfab0eacf613"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe34befb8c765b8ce562f0200afda3578f8abb159c76de3ab354c80b72244c41"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c757f64afe53a422e45e3e399e1e3cf82b7a2f244796ce80d8ca53e16a49b9f"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a57b41a0920b9a220125081c1e191b88a4cdec13bf9d0649e382a822705c65"}, + {file = "yarl-1.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:632c7aeb99df718765adf58eacb9acb9cbc555e075da849c1378ef4d18bf536a"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b0b8c06afcf2bac5a50b37f64efbde978b7f9dc88842ce9729c020dc71fae4ce"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1d93461e2cf76c4796355494f15ffcb50a3c198cc2d601ad8d6a96219a10c363"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4003f380dac50328c85e85416aca6985536812c082387255c35292cb4b41707e"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4d6d74a97e898c1c2df80339aa423234ad9ea2052f66366cef1e80448798c13d"}, + {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b61e64b06c3640feab73fa4ff9cb64bd8182de52e5dc13038e01cfe674ebc321"}, + {file = "yarl-1.9.3-cp311-cp311-win32.whl", hash = "sha256:29beac86f33d6c7ab1d79bd0213aa7aed2d2f555386856bb3056d5fdd9dab279"}, + {file = "yarl-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:f7271d6bd8838c49ba8ae647fc06469137e1c161a7ef97d778b72904d9b68696"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:dd318e6b75ca80bff0b22b302f83a8ee41c62b8ac662ddb49f67ec97e799885d"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c4b1efb11a8acd13246ffb0bee888dd0e8eb057f8bf30112e3e21e421eb82d4a"}, + {file = "yarl-1.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f034386e5550b5dc8ded90b5e2ff7db21f0f5c7de37b6efc5dac046eb19c10"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd49a908cb6d387fc26acee8b7d9fcc9bbf8e1aca890c0b2fdfd706057546080"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa4643635f26052401750bd54db911b6342eb1a9ac3e74f0f8b58a25d61dfe41"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e741bd48e6a417bdfbae02e088f60018286d6c141639359fb8df017a3b69415a"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c86d0d0919952d05df880a1889a4f0aeb6868e98961c090e335671dea5c0361"}, + {file = "yarl-1.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d5434b34100b504aabae75f0622ebb85defffe7b64ad8f52b8b30ec6ef6e4b9"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79e1df60f7c2b148722fb6cafebffe1acd95fd8b5fd77795f56247edaf326752"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:44e91a669c43f03964f672c5a234ae0d7a4d49c9b85d1baa93dec28afa28ffbd"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3cfa4dbe17b2e6fca1414e9c3bcc216f6930cb18ea7646e7d0d52792ac196808"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:88d2c3cc4b2f46d1ba73d81c51ec0e486f59cc51165ea4f789677f91a303a9a7"}, + {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cccdc02e46d2bd7cb5f38f8cc3d9db0d24951abd082b2f242c9e9f59c0ab2af3"}, + {file = "yarl-1.9.3-cp312-cp312-win32.whl", hash = "sha256:96758e56dceb8a70f8a5cff1e452daaeff07d1cc9f11e9b0c951330f0a2396a7"}, + {file = "yarl-1.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:c4472fe53ebf541113e533971bd8c32728debc4c6d8cc177f2bff31d011ec17e"}, + {file = "yarl-1.9.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:126638ab961633f0940a06e1c9d59919003ef212a15869708dcb7305f91a6732"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c99ddaddb2fbe04953b84d1651149a0d85214780e4d0ee824e610ab549d98d92"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dab30b21bd6fb17c3f4684868c7e6a9e8468078db00f599fb1c14e324b10fca"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:828235a2a169160ee73a2fcfb8a000709edf09d7511fccf203465c3d5acc59e4"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc391e3941045fd0987c77484b2799adffd08e4b6735c4ee5f054366a2e1551d"}, + {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51382c72dd5377861b573bd55dcf680df54cea84147c8648b15ac507fbef984d"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:28a108cb92ce6cf867690a962372996ca332d8cda0210c5ad487fe996e76b8bb"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8f18a7832ff85dfcd77871fe677b169b1bc60c021978c90c3bb14f727596e0ae"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:7eaf13af79950142ab2bbb8362f8d8d935be9aaf8df1df89c86c3231e4ff238a"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:66a6dbf6ca7d2db03cc61cafe1ee6be838ce0fbc97781881a22a58a7c5efef42"}, + {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a0a4f3aaa18580038cfa52a7183c8ffbbe7d727fe581300817efc1e96d1b0e9"}, + {file = "yarl-1.9.3-cp37-cp37m-win32.whl", hash = "sha256:946db4511b2d815979d733ac6a961f47e20a29c297be0d55b6d4b77ee4b298f6"}, + {file = "yarl-1.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2dad8166d41ebd1f76ce107cf6a31e39801aee3844a54a90af23278b072f1ccf"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bb72d2a94481e7dc7a0c522673db288f31849800d6ce2435317376a345728225"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a172c3d5447b7da1680a1a2d6ecdf6f87a319d21d52729f45ec938a7006d5d8"}, + {file = "yarl-1.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2dc72e891672343b99db6d497024bf8b985537ad6c393359dc5227ef653b2f17"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8d51817cf4b8d545963ec65ff06c1b92e5765aa98831678d0e2240b6e9fd281"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53ec65f7eee8655bebb1f6f1607760d123c3c115a324b443df4f916383482a67"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cfd77e8e5cafba3fb584e0f4b935a59216f352b73d4987be3af51f43a862c403"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e73db54c967eb75037c178a54445c5a4e7461b5203b27c45ef656a81787c0c1b"}, + {file = "yarl-1.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09c19e5f4404574fcfb736efecf75844ffe8610606f3fccc35a1515b8b6712c4"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6280353940f7e5e2efaaabd686193e61351e966cc02f401761c4d87f48c89ea4"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c25ec06e4241e162f5d1f57c370f4078797ade95c9208bd0c60f484834f09c96"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7217234b10c64b52cc39a8d82550342ae2e45be34f5bff02b890b8c452eb48d7"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4ce77d289f8d40905c054b63f29851ecbfd026ef4ba5c371a158cfe6f623663e"}, + {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f74b015c99a5eac5ae589de27a1201418a5d9d460e89ccb3366015c6153e60a"}, + {file = "yarl-1.9.3-cp38-cp38-win32.whl", hash = "sha256:8a2538806be846ea25e90c28786136932ec385c7ff3bc1148e45125984783dc6"}, + {file = "yarl-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:6465d36381af057d0fab4e0f24ef0e80ba61f03fe43e6eeccbe0056e74aadc70"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2f3c8822bc8fb4a347a192dd6a28a25d7f0ea3262e826d7d4ef9cc99cd06d07e"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7831566595fe88ba17ea80e4b61c0eb599f84c85acaa14bf04dd90319a45b90"}, + {file = "yarl-1.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff34cb09a332832d1cf38acd0f604c068665192c6107a439a92abfd8acf90fe2"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe8080b4f25dfc44a86bedd14bc4f9d469dfc6456e6f3c5d9077e81a5fedfba7"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8535e111a064f3bdd94c0ed443105934d6f005adad68dd13ce50a488a0ad1bf3"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d155a092bf0ebf4a9f6f3b7a650dc5d9a5bbb585ef83a52ed36ba46f55cc39d"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:778df71c8d0c8c9f1b378624b26431ca80041660d7be7c3f724b2c7a6e65d0d6"}, + {file = "yarl-1.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f9cafaf031c34d95c1528c16b2fa07b710e6056b3c4e2e34e9317072da5d1a"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ca6b66f69e30f6e180d52f14d91ac854b8119553b524e0e28d5291a724f0f423"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0e7e83f31e23c5d00ff618045ddc5e916f9e613d33c5a5823bc0b0a0feb522f"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:af52725c7c39b0ee655befbbab5b9a1b209e01bb39128dce0db226a10014aacc"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0ab5baaea8450f4a3e241ef17e3d129b2143e38a685036b075976b9c415ea3eb"}, + {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d350388ba1129bc867c6af1cd17da2b197dff0d2801036d2d7d83c2d771a682"}, + {file = "yarl-1.9.3-cp39-cp39-win32.whl", hash = "sha256:e2a16ef5fa2382af83bef4a18c1b3bcb4284c4732906aa69422cf09df9c59f1f"}, + {file = "yarl-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:d92d897cb4b4bf915fbeb5e604c7911021a8456f0964f3b8ebbe7f9188b9eabb"}, + {file = "yarl-1.9.3-py3-none-any.whl", hash = "sha256:271d63396460b6607b588555ea27a1a02b717ca2e3f2cf53bdde4013d7790929"}, + {file = "yarl-1.9.3.tar.gz", hash = "sha256:4a14907b597ec55740f63e52d7fee0e9ee09d5b9d57a4f399a7423268e457b57"}, ] [package.dependencies] From 7f14bdfb22d25abed6af275c4006461bcceb5f99 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 22 Nov 2023 12:44:32 -0800 Subject: [PATCH 067/724] allow onscreen cpu rendering (#30510) * spinner POC cpu * should be single threaded * how tf did that get added * allow non offscreen --------- Co-authored-by: Comma Device --- selfdrive/navd/main.cc | 2 +- system/hardware/base.h | 2 +- system/hardware/pc/hardware.h | 6 ++++-- system/hardware/tici/hardware.h | 6 ++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/selfdrive/navd/main.cc b/selfdrive/navd/main.cc index 5251b046fb..2e7b4d3b60 100644 --- a/selfdrive/navd/main.cc +++ b/selfdrive/navd/main.cc @@ -11,7 +11,7 @@ #include "system/hardware/hw.h" int main(int argc, char *argv[]) { - Hardware::config_cpu_rendering(); + Hardware::config_cpu_rendering(true); qInstallMessageHandler(swagLogMessageHandler); setpriority(PRIO_PROCESS, 0, -20); diff --git a/system/hardware/base.h b/system/hardware/base.h index 43f5db023d..890a743ea0 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -34,7 +34,7 @@ public: static bool get_ssh_enabled() { return false; } static void set_ssh_enabled(bool enabled) {} - static void config_cpu_rendering(); + static void config_cpu_rendering(bool offscreen); static bool PC() { return false; } static bool TICI() { return false; } diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h index 7a7ddd60b7..189adbbbee 100644 --- a/system/hardware/pc/hardware.h +++ b/system/hardware/pc/hardware.h @@ -21,8 +21,10 @@ public: std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); } - static void config_cpu_rendering() { - setenv("QT_QPA_PLATFORM", "offscreen", 1); + static void config_cpu_rendering(bool offscreen) { + if (offscreen) { + setenv("QT_QPA_PLATFORM", "offscreen", 1); + } setenv("__GLX_VENDOR_LIBRARY_NAME", "mesa", 1); setenv("LP_NUM_THREADS", "0", 1); // disable threading so we stay on our assigned CPU } diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index fe0a20ae58..0a00aca5be 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -104,8 +104,10 @@ public: static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); } static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); } - static void config_cpu_rendering() { - setenv("QT_QPA_PLATFORM", "eglfs", 1); // offscreen doesn't work with EGL/GLES + static void config_cpu_rendering(bool offscreen) { + if (offscreen) { + setenv("QT_QPA_PLATFORM", "eglfs", 1); // offscreen doesn't work with EGL/GLES + } setenv("LP_NUM_THREADS", "0", 1); // disable threading so we stay on our assigned CPU } }; From f65e6bc30e7a3cce8b073f9ae0155facfe6c7646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Wed, 22 Nov 2023 13:25:29 -0800 Subject: [PATCH 068/724] locationd: remove models unused in openpilot (#30481) * Remove filters used exclusively by xx * Update SConstruct * Remove from release * Accomodate rednose build changes * Update rednose ref * rednose/helpers in rpath * Add rednose_filters to files_common * Change rednose_root * Copy rednose site_scons to docker images * Remove rednose from rpath * Bump rednose * Bump rednose * Bump rednose --- Dockerfile.openpilot | 1 + SConstruct | 38 +- rednose_repo | 2 +- release/files_common | 3 +- selfdrive/locationd/SConscript | 41 +- selfdrive/locationd/models/gnss_helpers.py | 35 -- selfdrive/locationd/models/gnss_kf.py | 190 ------- selfdrive/locationd/models/lane_kf.py | 105 ---- selfdrive/locationd/models/loc_kf.py | 565 --------------------- tools/sim/Dockerfile.sim | 1 + 10 files changed, 44 insertions(+), 937 deletions(-) delete mode 100644 selfdrive/locationd/models/gnss_helpers.py delete mode 100755 selfdrive/locationd/models/gnss_kf.py delete mode 100755 selfdrive/locationd/models/lane_kf.py delete mode 100755 selfdrive/locationd/models/loc_kf.py diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index 27b1e246bd..1e15722bb9 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -14,6 +14,7 @@ COPY ./openpilot ${OPENPILOT_PATH}/openpilot COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons COPY ./rednose ${OPENPILOT_PATH}/rednose +COPY ./rednose_repo/site_scons ${OPENPILOT_PATH}/rednose_repo/site_scons COPY ./tools ${OPENPILOT_PATH}/tools COPY ./release ${OPENPILOT_PATH}/release COPY ./common ${OPENPILOT_PATH}/common diff --git a/SConstruct b/SConstruct index eb9cd737e2..8790221315 100644 --- a/SConstruct +++ b/SConstruct @@ -116,10 +116,7 @@ else: cflags = [] cxxflags = [] cpppath = [] - rpath += [ - Dir("#cereal").abspath, - Dir("#common").abspath - ] + rpath += [] # MacOS if arch == "Darwin": @@ -144,8 +141,6 @@ else: f"#third_party/acados/{arch}/lib", f"#third_party/libyuv/{arch}/lib", f"#third_party/mapbox-gl-native-qt/{arch}", - "#cereal", - "#common", "/usr/lib", "/usr/local/lib", ] @@ -229,10 +224,13 @@ env = Environment( "#opendbc/can", "#selfdrive/boardd", "#common", + "#rednose/helpers", ], CYTHONCFILESUFFIX=".cpp", COMPILATIONDB_USE_ABSPATH=True, - tools=["default", "cython", "compilation_db"], + REDNOSE_ROOT="#", + tools=["default", "cython", "compilation_db", "rednose_filter"], + toolpath=["#rednose_repo/site_scons/site_tools"], ) if arch == "Darwin": @@ -367,31 +365,7 @@ SConscript([ 'panda/SConscript', ]) -# Build rednose library and ekf models -rednose_deps = [ - "#selfdrive/locationd/models/constants.py", - "#selfdrive/locationd/models/gnss_helpers.py", -] - -rednose_config = { - 'generated_folder': '#selfdrive/locationd/models/generated', - 'to_build': { - 'gnss': ('#selfdrive/locationd/models/gnss_kf.py', True, [], rednose_deps), - 'live': ('#selfdrive/locationd/models/live_kf.py', True, ['live_kf_constants.h'], rednose_deps), - 'car': ('#selfdrive/locationd/models/car_kf.py', True, [], rednose_deps), - }, -} - -if arch != "larch64": - rednose_config['to_build'].update({ - 'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, [], rednose_deps), - 'lane': ('#selfdrive/locationd/models/lane_kf.py', True, [], rednose_deps), - 'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, [], []), - 'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, [], []), - 'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, [], []), - }) - -Export('rednose_config') +# Build rednose library SConscript(['rednose/SConscript']) # Build system services diff --git a/rednose_repo b/rednose_repo index 8658bed296..44e8a891a2 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 8658bed29686b2ddae191fd18302986c85542431 +Subproject commit 44e8a891a2810f274a1fa980775155d9463e87b9 diff --git a/release/files_common b/release/files_common index 46c2c26a5b..cfc3830e0d 100644 --- a/release/files_common +++ b/release/files_common @@ -233,12 +233,10 @@ selfdrive/locationd/paramsd.py selfdrive/locationd/models/__init__.py selfdrive/locationd/models/.gitignore selfdrive/locationd/models/car_kf.py -selfdrive/locationd/models/gnss_kf.py selfdrive/locationd/models/live_kf.py selfdrive/locationd/models/live_kf.h selfdrive/locationd/models/live_kf.cc selfdrive/locationd/models/constants.py -selfdrive/locationd/models/gnss_helpers.py selfdrive/locationd/torqued.py selfdrive/locationd/calibrationd.py @@ -446,6 +444,7 @@ third_party/qt5/larch64/bin/** scripts/update_now.sh scripts/stop_updater.sh +rednose_repo/site_scons/site_tools/rednose_filter.py rednose/.gitignore rednose/** diff --git a/selfdrive/locationd/SConscript b/selfdrive/locationd/SConscript index a6febe0170..07555a6087 100644 --- a/selfdrive/locationd/SConscript +++ b/selfdrive/locationd/SConscript @@ -1,10 +1,37 @@ -Import('env', 'common', 'cereal', 'messaging', 'libkf', 'transformations') +Import('env', 'arch', 'common', 'cereal', 'messaging', 'rednose', 'transformations') -loc_libs = [cereal, messaging, 'zmq', common, 'capnp', 'kj', 'pthread'] +loc_libs = [cereal, messaging, 'zmq', common, 'capnp', 'kj', 'pthread', 'dl'] + +# build ekf models +rednose_gen_dir = 'models/generated' +rednose_gen_deps = [ + "models/constants.py", +] +live_ekf = env.RednoseCompileFilter( + target='live', + filter_gen_script='models/live_kf.py', + output_dir=rednose_gen_dir, + extra_gen_artifacts=['live_kf_constants.h'], + gen_script_deps=rednose_gen_deps, +) +car_ekf = env.RednoseCompileFilter( + target='car', + filter_gen_script='models/car_kf.py', + output_dir=rednose_gen_dir, + extra_gen_artifacts=[], + gen_script_deps=rednose_gen_deps, +) + +# locationd build +locationd_sources = ["locationd.cc", "models/live_kf.cc"] -ekf_sym_cc = env.SharedObject("#rednose/helpers/ekf_sym.cc") -locationd_sources = ["locationd.cc", "models/live_kf.cc", ekf_sym_cc] lenv = env.Clone() -lenv["_LIBFLAGS"] += f' {libkf[0].get_labspath()}' -locationd = lenv.Program("locationd", locationd_sources, LIBS=loc_libs + transformations) -lenv.Depends(locationd, libkf) \ No newline at end of file +# ekf filter libraries need to be linked, even if no symbols are used +if arch != "Darwin": + lenv["LINKFLAGS"] += ["-Wl,--no-as-needed"] + +lenv["LIBPATH"].append(Dir(rednose_gen_dir).abspath) +lenv["RPATH"].append(Dir(rednose_gen_dir).abspath) +locationd = lenv.Program("locationd", locationd_sources, LIBS=["live", "ekf_sym"] + loc_libs + transformations) +lenv.Depends(locationd, rednose) +lenv.Depends(locationd, live_ekf) diff --git a/selfdrive/locationd/models/gnss_helpers.py b/selfdrive/locationd/models/gnss_helpers.py deleted file mode 100644 index f412fafda9..0000000000 --- a/selfdrive/locationd/models/gnss_helpers.py +++ /dev/null @@ -1,35 +0,0 @@ -import numpy as np - - -# source: GNSSMeasurement (https://github.com/commaai/laika/blob/master/laika/raw_gnss.py) -class RawGNSSMeasurementIndices: - PRN = 0 - RECV_TIME_WEEK = 1 - RECV_TIME_SEC = 2 - GLONASS_FREQ = 3 - - PR = 4 - PR_STD = 5 - PRR = 6 - PRR_STD = 7 - - SAT_POS = slice(8, 11) - SAT_VEL = slice(11, 14) - - -def parse_prr(m): - sat_pos_vel_i = np.concatenate((m[RawGNSSMeasurementIndices.SAT_POS], - m[RawGNSSMeasurementIndices.SAT_VEL])) - R_i = np.atleast_2d(m[RawGNSSMeasurementIndices.PRR_STD]**2) - z_i = m[RawGNSSMeasurementIndices.PRR] - return z_i, R_i, sat_pos_vel_i - - -def parse_pr(m): - pseudorange = m[RawGNSSMeasurementIndices.PR] - pseudorange_stdev = m[RawGNSSMeasurementIndices.PR_STD] - sat_pos_freq_i = np.concatenate((m[RawGNSSMeasurementIndices.SAT_POS], - np.array([m[RawGNSSMeasurementIndices.GLONASS_FREQ]]))) - z_i = np.atleast_1d(pseudorange) - R_i = np.atleast_2d(pseudorange_stdev**2) - return z_i, R_i, sat_pos_freq_i diff --git a/selfdrive/locationd/models/gnss_kf.py b/selfdrive/locationd/models/gnss_kf.py deleted file mode 100755 index c4f3b2e210..0000000000 --- a/selfdrive/locationd/models/gnss_kf.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 -import sys -from typing import List - -import numpy as np - -from openpilot.selfdrive.locationd.models.constants import ObservationKind -from openpilot.selfdrive.locationd.models.gnss_helpers import parse_pr, parse_prr - -if __name__ == '__main__': # Generating sympy - import sympy as sp - from rednose.helpers.ekf_sym import gen_code -else: - from rednose.helpers.ekf_sym_pyx import EKF_sym_pyx - from rednose.helpers.ekf_sym import EKF_sym - - -class States(): - ECEF_POS = slice(0, 3) # x, y and z in ECEF in meters - ECEF_VELOCITY = slice(3, 6) - CLOCK_BIAS = slice(6, 7) # clock bias in light-meters, - CLOCK_DRIFT = slice(7, 8) # clock drift in light-meters/s, - CLOCK_ACCELERATION = slice(8, 9) # clock acceleration in light-meters/s**2 - GLONASS_BIAS = slice(9, 10) # clock drift in light-meters/s, - GLONASS_FREQ_SLOPE = slice(10, 11) # GLONASS bias in m expressed as bias + freq_num*freq_slope - - -class GNSSKalman(): - name = 'gnss' - - x_initial = np.array([-2712700.6008, -4281600.6679, 3859300.1830, - 0, 0, 0, - 0, 0, 0, - 0, 0]) - - # state covariance - P_initial = np.diag([1e16, 1e16, 1e16, - 10**2, 10**2, 10**2, - 1e14, (100)**2, (0.2)**2, - (10)**2, (1)**2]) - - maha_test_kinds: List[int] = [] # ObservationKind.PSEUDORANGE_RATE, ObservationKind.PSEUDORANGE, ObservationKind.PSEUDORANGE_GLONASS] - - @staticmethod - def generate_code(generated_dir): - dim_state = GNSSKalman.x_initial.shape[0] - name = GNSSKalman.name - maha_test_kinds = GNSSKalman.maha_test_kinds - - # make functions and jacobians with sympy - # state variables - state_sym = sp.MatrixSymbol('state', dim_state, 1) - state = sp.Matrix(state_sym) - x, y, z = state[0:3, :] - v = state[3:6, :] - vx, vy, vz = v - cb, cd, ca = state[6:9, :] - glonass_bias, glonass_freq_slope = state[9:11, :] - - dt = sp.Symbol('dt') - - state_dot = sp.Matrix(np.zeros((dim_state, 1))) - state_dot[:3, :] = v - state_dot[6, 0] = cd - state_dot[7, 0] = ca - - # Basic descretization, 1st order integrator - # Can be pretty bad if dt is big - f_sym = state + dt * state_dot - - # - # Observation functions - # - - # extra args - sat_pos_freq_sym = sp.MatrixSymbol('sat_pos', 4, 1) - sat_pos_vel_sym = sp.MatrixSymbol('sat_pos_vel', 6, 1) - # sat_los_sym = sp.MatrixSymbol('sat_los', 3, 1) - # orb_epos_sym = sp.MatrixSymbol('orb_epos_sym', 3, 1) - - # expand extra args - sat_x, sat_y, sat_z, glonass_freq = sat_pos_freq_sym - sat_vx, sat_vy, sat_vz = sat_pos_vel_sym[3:] - # los_x, los_y, los_z = sat_los_sym - # orb_x, orb_y, orb_z = orb_epos_sym - - h_pseudorange_sym = sp.Matrix([ - sp.sqrt( - (x - sat_x)**2 + - (y - sat_y)**2 + - (z - sat_z)**2 - ) + cb - ]) - - h_pseudorange_glonass_sym = sp.Matrix([ - sp.sqrt( - (x - sat_x)**2 + - (y - sat_y)**2 + - (z - sat_z)**2 - ) + cb + glonass_bias + glonass_freq_slope * glonass_freq - ]) - - los_vector = (sp.Matrix(sat_pos_vel_sym[0:3]) - sp.Matrix([x, y, z])) - los_vector = los_vector / sp.sqrt(los_vector[0]**2 + los_vector[1]**2 + los_vector[2]**2) - h_pseudorange_rate_sym = sp.Matrix([los_vector[0] * (sat_vx - vx) + - los_vector[1] * (sat_vy - vy) + - los_vector[2] * (sat_vz - vz) + - cd]) - - obs_eqs = [[h_pseudorange_sym, ObservationKind.PSEUDORANGE_GPS, sat_pos_freq_sym], - [h_pseudorange_glonass_sym, ObservationKind.PSEUDORANGE_GLONASS, sat_pos_freq_sym], - [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GPS, sat_pos_vel_sym], - [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GLONASS, sat_pos_vel_sym]] - - gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, maha_test_kinds=maha_test_kinds) - - def __init__(self, generated_dir, cython=False, erratic_clock=False): - # process noise - clock_error_drift = 100.0 if erratic_clock else 0.1 - self.Q = np.diag([0.03**2, 0.03**2, 0.03**2, - 3**2, 3**2, 3**2, - (clock_error_drift)**2, (0)**2, (0.005)**2, - .1**2, (.01)**2]) - - self.dim_state = self.x_initial.shape[0] - - # init filter - filter_cls = EKF_sym_pyx if cython else EKF_sym - self.filter = filter_cls(generated_dir, self.name, self.Q, self.x_initial, self.P_initial, self.dim_state, - self.dim_state, maha_test_kinds=self.maha_test_kinds) - self.init_state(GNSSKalman.x_initial, covs=GNSSKalman.P_initial) - - @property - def x(self): - return self.filter.state() - - @property - def P(self): - return self.filter.covs() - - def predict(self, t): - return self.filter.predict(t) - - def rts_smooth(self, estimates): - return self.filter.rts_smooth(estimates, norm_quats=False) - - def init_state(self, state, covs_diag=None, covs=None, filter_time=None): - if covs_diag is not None: - P = np.diag(covs_diag) - elif covs is not None: - P = covs - else: - P = self.filter.covs() - self.filter.init_state(state, P, filter_time) - - def predict_and_observe(self, t, kind, data): - if len(data) > 0: - data = np.atleast_2d(data) - if kind == ObservationKind.PSEUDORANGE_GPS or kind == ObservationKind.PSEUDORANGE_GLONASS: - r = self.predict_and_update_pseudorange(data, t, kind) - elif kind == ObservationKind.PSEUDORANGE_RATE_GPS or kind == ObservationKind.PSEUDORANGE_RATE_GLONASS: - r = self.predict_and_update_pseudorange_rate(data, t, kind) - return r - - def predict_and_update_pseudorange(self, meas, t, kind): - R = np.zeros((len(meas), 1, 1)) - sat_pos_freq = np.zeros((len(meas), 4)) - z = np.zeros((len(meas), 1)) - for i, m in enumerate(meas): - z_i, R_i, sat_pos_freq_i = parse_pr(m) - sat_pos_freq[i, :] = sat_pos_freq_i - z[i, :] = z_i - R[i, :, :] = R_i - return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_freq) - - def predict_and_update_pseudorange_rate(self, meas, t, kind): - R = np.zeros((len(meas), 1, 1)) - z = np.zeros((len(meas), 1)) - sat_pos_vel = np.zeros((len(meas), 6)) - for i, m in enumerate(meas): - z_i, R_i, sat_pos_vel_i = parse_prr(m) - sat_pos_vel[i] = sat_pos_vel_i - R[i, :, :] = R_i - z[i, :] = z_i - return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_vel) - - -if __name__ == "__main__": - generated_dir = sys.argv[2] - GNSSKalman.generate_code(generated_dir) diff --git a/selfdrive/locationd/models/lane_kf.py b/selfdrive/locationd/models/lane_kf.py deleted file mode 100755 index e8fa999956..0000000000 --- a/selfdrive/locationd/models/lane_kf.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -import sys -import numpy as np -import sympy as sp - -from openpilot.selfdrive.locationd.models.constants import ObservationKind -from rednose.helpers.ekf_sym import gen_code, EKF_sym - - -class LaneKalman(): - name = 'lane' - - @staticmethod - def generate_code(generated_dir): - # make functions and jacobians with sympy - # state variables - dim = 6 - state = sp.MatrixSymbol('state', dim, 1) - - dd = sp.Symbol('dd') # WARNING: NOT TIME - - # Time derivative of the state as a function of state - state_dot = sp.Matrix(np.zeros((dim, 1))) - state_dot[:3,0] = sp.Matrix(state[3:6,0]) - - # Basic descretization, 1st order intergrator - # Can be pretty bad if dt is big - f_sym = sp.Matrix(state) + dd*state_dot - - # - # Observation functions - # - h_lane_sym = sp.Matrix(state[:3,0]) - obs_eqs = [[h_lane_sym, ObservationKind.LANE_PT, None]] - gen_code(generated_dir, LaneKalman.name, f_sym, dd, state, obs_eqs, dim, dim) - - def __init__(self, generated_dir, pt_std=5): - # state - # left and right lane centers in ecef - # WARNING: this is not a temporal model - # the 'time' in this kalman filter is - # the distance traveled by the vehicle, - # which should approximately be the - # distance along the lane path - # a more logical parametrization - # states 0-2 are ecef coordinates distance d - # states 3-5 is the 3d "velocity" of the - # lane in ecef (m/m). - x_initial = np.array([0,0,0, - 0,0,0]) - - # state covariance - P_initial = np.diag([1e16, 1e16, 1e16, - 1**2, 1**2, 1**2]) - - # process noise - Q = np.diag([0.1**2, 0.1**2, 0.1**2, - 0.1**2, 0.1**2, 0.1*2]) - - self.dim_state = len(x_initial) - - # init filter - self.filter = EKF_sym(generated_dir, self.name, Q, x_initial, P_initial, x_initial.shape[0], P_initial.shape[0]) - self.obs_noise = {ObservationKind.LANE_PT: np.diag([pt_std**2]*3)} - - @property - def x(self): - return self.filter.state() - - @property - def P(self): - return self.filter.covs() - - def predict(self, t): - return self.filter.predict(t) - - def rts_smooth(self, estimates): - return self.filter.rts_smooth(estimates, norm_quats=False) - - - def init_state(self, state, covs_diag=None, covs=None, filter_time=None): - if covs_diag is not None: - P = np.diag(covs_diag) - elif covs is not None: - P = covs - else: - P = self.filter.covs() - self.filter.init_state(state, P, filter_time) - - def predict_and_observe(self, t, kind, data): - data = np.atleast_2d(data) - return self.filter.predict_and_update_batch(t, kind, data, self.get_R(kind, len(data))) - - def get_R(self, kind, n): - obs_noise = self.obs_noise[kind] - dim = obs_noise.shape[0] - R = np.zeros((n, dim, dim)) - for i in range(n): - R[i,:,:] = obs_noise - return R - - -if __name__ == "__main__": - generated_dir = sys.argv[2] - LaneKalman.generate_code(generated_dir) diff --git a/selfdrive/locationd/models/loc_kf.py b/selfdrive/locationd/models/loc_kf.py deleted file mode 100755 index 8bd317bd58..0000000000 --- a/selfdrive/locationd/models/loc_kf.py +++ /dev/null @@ -1,565 +0,0 @@ -#!/usr/bin/env python3 - -import sys - -import numpy as np -import sympy as sp - -from rednose.helpers.ekf_sym import EKF_sym, gen_code -from rednose.helpers.lst_sq_computer import LstSqComputer -from rednose.helpers.sympy_helpers import euler_rotate, quat_matrix_r, quat_rotate - -from openpilot.selfdrive.locationd.models.constants import ObservationKind -from openpilot.selfdrive.locationd.models.gnss_helpers import parse_pr, parse_prr - -EARTH_GM = 3.986005e14 # m^3/s^2 (gravitational constant * mass of earth) - -class States(): - ECEF_POS = slice(0, 3) # x, y and z in ECEF in meters - ECEF_ORIENTATION = slice(3, 7) # quat for orientation of phone in ecef - ECEF_VELOCITY = slice(7, 10) # ecef velocity in m/s - ANGULAR_VELOCITY = slice(10, 13) # roll, pitch and yaw rates in device frame in radians/s - CLOCK_BIAS = slice(13, 14) # clock bias in light-meters, - CLOCK_DRIFT = slice(14, 15) # clock drift in light-meters/s, - GYRO_BIAS = slice(15, 18) # roll, pitch and yaw biases - ODO_SCALE_UNUSED = slice(18, 19) # odometer scale - ACCELERATION = slice(19, 22) # Acceleration in device frame in m/s**2 - FOCAL_SCALE_UNUSED = slice(22, 23) # focal length scale - IMU_FROM_DEVICE_EULER = slice(23, 26) # imu offset angles in radians - GLONASS_BIAS = slice(26, 27) # GLONASS bias in m expressed as bias + freq_num*freq_slope - GLONASS_FREQ_SLOPE = slice(27, 28) # GLONASS bias in m expressed as bias + freq_num*freq_slope - CLOCK_ACCELERATION = slice(28, 29) # clock acceleration in light-meters/s**2, - ACCELEROMETER_SCALE_UNUSED = slice(29, 30) # scale of mems accelerometer - ACCELEROMETER_BIAS = slice(30, 33) # bias of mems accelerometer - # TODO the offset is likely a translation of the sensor, not a rotation of the camera - WIDE_FROM_DEVICE_EULER = slice(33, 36) # wide camera offset angles in radians (tici only) - # We currently do not use ACCELEROMETER_SCALE to avoid instability due to too many free variables - # (ACCELEROMETER_SCALE, ACCELEROMETER_BIAS, IMU_FROM_DEVICE_EULER). - # From experiments we see that ACCELEROMETER_BIAS is more correct than ACCELEROMETER_SCALE - - # Error-state has different slices because it is an ESKF - ECEF_POS_ERR = slice(0, 3) - ECEF_ORIENTATION_ERR = slice(3, 6) # euler angles for orientation error - ECEF_VELOCITY_ERR = slice(6, 9) - ANGULAR_VELOCITY_ERR = slice(9, 12) - CLOCK_BIAS_ERR = slice(12, 13) - CLOCK_DRIFT_ERR = slice(13, 14) - GYRO_BIAS_ERR = slice(14, 17) - ODO_SCALE_ERR_UNUSED = slice(17, 18) - ACCELERATION_ERR = slice(18, 21) - FOCAL_SCALE_ERR_UNUSED = slice(21, 22) - IMU_FROM_DEVICE_EULER_ERR = slice(22, 25) - GLONASS_BIAS_ERR = slice(25, 26) - GLONASS_FREQ_SLOPE_ERR = slice(26, 27) - CLOCK_ACCELERATION_ERR = slice(27, 28) - ACCELEROMETER_SCALE_ERR_UNUSED = slice(28, 29) - ACCELEROMETER_BIAS_ERR = slice(29, 32) - WIDE_FROM_DEVICE_EULER_ERR = slice(32, 35) - - -class LocKalman(): - name = "loc" - x_initial = np.array([0, 0, 0, - 1, 0, 0, 0, - 0, 0, 0, - 0, 0, 0, - 0, 0, - 0, 0, 0, - 1, - 0, 0, 0, - 1, - 0, 0, 0, - 0, 0, - 0, - 1, - 0, 0, 0, - 0, 0, 0], dtype=np.float64) - - # state covariance - P_initial = np.diag([1e16, 1e16, 1e16, - 10**2, 10**2, 10**2, - 10**2, 10**2, 10**2, - 1**2, 1**2, 1**2, - 1e14, (100)**2, - 0.05**2, 0.05**2, 0.05**2, - 0.02**2, - 2**2, 2**2, 2**2, - 0.01**2, - 0.01**2, 0.01**2, 0.01**2, - 10**2, 1**2, - 0.2**2, - 0.05**2, - 0.05**2, 0.05**2, 0.05**2, - 0.01**2, 0.01**2, 0.01**2]) - - - # measurements that need to pass mahalanobis distance outlier rejector - maha_test_kinds = [ObservationKind.ORB_FEATURES, ObservationKind.ORB_FEATURES_WIDE] # , ObservationKind.PSEUDORANGE, ObservationKind.PSEUDORANGE_RATE] - dim_augment = 7 - dim_augment_err = 6 - - @staticmethod - def generate_code(generated_dir, N=4): - dim_augment = LocKalman.dim_augment - dim_augment_err = LocKalman.dim_augment_err - - dim_main = LocKalman.x_initial.shape[0] - dim_main_err = LocKalman.P_initial.shape[0] - dim_state = dim_main + dim_augment * N - dim_state_err = dim_main_err + dim_augment_err * N - maha_test_kinds = LocKalman.maha_test_kinds - - name = f"{LocKalman.name}_{N}" - - # make functions and jacobians with sympy - # state variables - state_sym = sp.MatrixSymbol('state', dim_state, 1) - state = sp.Matrix(state_sym) - x, y, z = state[States.ECEF_POS, :] - q = state[States.ECEF_ORIENTATION, :] - v = state[States.ECEF_VELOCITY, :] - vx, vy, vz = v - omega = state[States.ANGULAR_VELOCITY, :] - vroll, vpitch, vyaw = omega - cb = state[States.CLOCK_BIAS, :] - cd = state[States.CLOCK_DRIFT, :] - roll_bias, pitch_bias, yaw_bias = state[States.GYRO_BIAS, :] - acceleration = state[States.ACCELERATION, :] - imu_from_device_euler = state[States.IMU_FROM_DEVICE_EULER, :] - imu_from_device_euler[0, 0] = 0 # not observable enough - imu_from_device_euler[2, 0] = 0 # not observable enough - glonass_bias = state[States.GLONASS_BIAS, :] - glonass_freq_slope = state[States.GLONASS_FREQ_SLOPE, :] - ca = state[States.CLOCK_ACCELERATION, :] - accel_bias = state[States.ACCELEROMETER_BIAS, :] - wide_from_device_euler = state[States.WIDE_FROM_DEVICE_EULER, :] - wide_from_device_euler[0, 0] = 0 # not observable enough - - dt = sp.Symbol('dt') - - # calibration and attitude rotation matrices - quat_rot = quat_rotate(*q) - - # Got the quat predict equations from here - # A New Quaternion-Based Kalman Filter for - # Real-Time Attitude Estimation Using the Two-Step - # Geometrically-Intuitive Correction Algorithm - A = 0.5 * sp.Matrix([[0, -vroll, -vpitch, -vyaw], - [vroll, 0, vyaw, -vpitch], - [vpitch, -vyaw, 0, vroll], - [vyaw, vpitch, -vroll, 0]]) - q_dot = A * q - - # Time derivative of the state as a function of state - state_dot = sp.Matrix(np.zeros((dim_state, 1))) - state_dot[States.ECEF_POS, :] = v - state_dot[States.ECEF_ORIENTATION, :] = q_dot - state_dot[States.ECEF_VELOCITY, 0] = quat_rot * acceleration - state_dot[States.CLOCK_BIAS, :] = cd - state_dot[States.CLOCK_DRIFT, :] = ca - - # Basic descretization, 1st order intergrator - # Can be pretty bad if dt is big - f_sym = state + dt * state_dot - - state_err_sym = sp.MatrixSymbol('state_err', dim_state_err, 1) - state_err = sp.Matrix(state_err_sym) - quat_err = state_err[States.ECEF_ORIENTATION_ERR, :] - v_err = state_err[States.ECEF_VELOCITY_ERR, :] - omega_err = state_err[States.ANGULAR_VELOCITY_ERR, :] - cd_err = state_err[States.CLOCK_DRIFT_ERR, :] - acceleration_err = state_err[States.ACCELERATION_ERR, :] - ca_err = state_err[States.CLOCK_ACCELERATION_ERR, :] - - # Time derivative of the state error as a function of state error and state - quat_err_matrix = euler_rotate(quat_err[0], quat_err[1], quat_err[2]) - q_err_dot = quat_err_matrix * quat_rot * (omega + omega_err) - state_err_dot = sp.Matrix(np.zeros((dim_state_err, 1))) - state_err_dot[States.ECEF_POS_ERR, :] = v_err - state_err_dot[States.ECEF_ORIENTATION_ERR, :] = q_err_dot - state_err_dot[States.ECEF_VELOCITY_ERR, :] = quat_err_matrix * quat_rot * (acceleration + acceleration_err) - state_err_dot[States.CLOCK_BIAS_ERR, :] = cd_err - state_err_dot[States.CLOCK_DRIFT_ERR, :] = ca_err - f_err_sym = state_err + dt * state_err_dot - - # convenient indexing - # q idxs are for quats and p idxs are for other - q_idxs = [[3, dim_augment]] + [[dim_main + n * dim_augment + 3, dim_main + (n + 1) * dim_augment] for n in range(N)] - q_err_idxs = [[3, dim_augment_err]] + [[dim_main_err + n * dim_augment_err + 3, dim_main_err + (n + 1) * dim_augment_err] for n in range(N)] - p_idxs = [[0, 3]] + [[dim_augment, dim_main]] + [[dim_main + n * dim_augment, dim_main + n * dim_augment + 3] for n in range(N)] - p_err_idxs = [[0, 3]] + [[dim_augment_err, dim_main_err]] + [[dim_main_err + n * dim_augment_err, dim_main_err + n * dim_augment_err + 3] for n in range(N)] - - # Observation matrix modifier - H_mod_sym = sp.Matrix(np.zeros((dim_state, dim_state_err))) - for p_idx, p_err_idx in zip(p_idxs, p_err_idxs, strict=True): - H_mod_sym[p_idx[0]:p_idx[1], p_err_idx[0]:p_err_idx[1]] = np.eye(p_idx[1] - p_idx[0]) - for q_idx, q_err_idx in zip(q_idxs, q_err_idxs, strict=True): - H_mod_sym[q_idx[0]:q_idx[1], q_err_idx[0]:q_err_idx[1]] = 0.5 * quat_matrix_r(state[q_idx[0]:q_idx[1]])[:, 1:] - - # these error functions are defined so that say there - # is a nominal x and true x: - # true x = err_function(nominal x, delta x) - # delta x = inv_err_function(nominal x, true x) - nom_x = sp.MatrixSymbol('nom_x', dim_state, 1) - true_x = sp.MatrixSymbol('true_x', dim_state, 1) - delta_x = sp.MatrixSymbol('delta_x', dim_state_err, 1) - - err_function_sym = sp.Matrix(np.zeros((dim_state, 1))) - for q_idx, q_err_idx in zip(q_idxs, q_err_idxs, strict=True): - delta_quat = sp.Matrix(np.ones(4)) - delta_quat[1:, :] = sp.Matrix(0.5 * delta_x[q_err_idx[0]: q_err_idx[1], :]) - err_function_sym[q_idx[0]:q_idx[1], 0] = quat_matrix_r(nom_x[q_idx[0]:q_idx[1], 0]) * delta_quat - for p_idx, p_err_idx in zip(p_idxs, p_err_idxs, strict=True): - err_function_sym[p_idx[0]:p_idx[1], :] = sp.Matrix(nom_x[p_idx[0]:p_idx[1], :] + delta_x[p_err_idx[0]:p_err_idx[1], :]) - - inv_err_function_sym = sp.Matrix(np.zeros((dim_state_err, 1))) - for p_idx, p_err_idx in zip(p_idxs, p_err_idxs, strict=True): - inv_err_function_sym[p_err_idx[0]:p_err_idx[1], 0] = sp.Matrix(-nom_x[p_idx[0]:p_idx[1], 0] + true_x[p_idx[0]:p_idx[1], 0]) - for q_idx, q_err_idx in zip(q_idxs, q_err_idxs, strict=True): - delta_quat = quat_matrix_r(nom_x[q_idx[0]:q_idx[1], 0]).T * true_x[q_idx[0]:q_idx[1], 0] - inv_err_function_sym[q_err_idx[0]:q_err_idx[1], 0] = sp.Matrix(2 * delta_quat[1:]) - - eskf_params = [[err_function_sym, nom_x, delta_x], - [inv_err_function_sym, nom_x, true_x], - H_mod_sym, f_err_sym, state_err_sym] - # - # Observation functions - # - - # extra args - sat_pos_freq_sym = sp.MatrixSymbol('sat_pos', 4, 1) - sat_pos_vel_sym = sp.MatrixSymbol('sat_pos_vel', 6, 1) - # sat_los_sym = sp.MatrixSymbol('sat_los', 3, 1) - - # expand extra args - sat_x, sat_y, sat_z, glonass_freq = sat_pos_freq_sym - sat_vx, sat_vy, sat_vz = sat_pos_vel_sym[3:] - - h_pseudorange_sym = sp.Matrix([ - sp.sqrt( - (x - sat_x)**2 + - (y - sat_y)**2 + - (z - sat_z)**2 - ) + cb[0] - ]) - - h_pseudorange_glonass_sym = sp.Matrix([ - sp.sqrt( - (x - sat_x)**2 + - (y - sat_y)**2 + - (z - sat_z)**2 - ) + cb[0] + glonass_bias[0] + glonass_freq_slope[0] * glonass_freq - ]) - - los_vector = (sp.Matrix(sat_pos_vel_sym[0:3]) - sp.Matrix([x, y, z])) - los_vector = los_vector / sp.sqrt(los_vector[0]**2 + los_vector[1]**2 + los_vector[2]**2) - h_pseudorange_rate_sym = sp.Matrix([los_vector[0] * (sat_vx - vx) + - los_vector[1] * (sat_vy - vy) + - los_vector[2] * (sat_vz - vz) + - cd[0]]) - - imu_from_device = euler_rotate(*imu_from_device_euler) - h_gyro_sym = imu_from_device * sp.Matrix([vroll + roll_bias, - vpitch + pitch_bias, - vyaw + yaw_bias]) - - pos = sp.Matrix([x, y, z]) - # add 1 for stability, prevent division by 0 - gravity = quat_rot.T * ((EARTH_GM / ((x**2 + y**2 + z**2 + 1)**(3.0 / 2.0))) * pos) - h_acc_sym = imu_from_device * (gravity + acceleration + accel_bias) - h_acc_stationary_sym = acceleration - h_phone_rot_sym = sp.Matrix([vroll, vpitch, vyaw]) - h_relative_motion = sp.Matrix(quat_rot.T * v) - - obs_eqs = [[h_gyro_sym, ObservationKind.PHONE_GYRO, None], - [h_phone_rot_sym, ObservationKind.NO_ROT, None], - [h_acc_sym, ObservationKind.PHONE_ACCEL, None], - [h_pseudorange_sym, ObservationKind.PSEUDORANGE_GPS, sat_pos_freq_sym], - [h_pseudorange_glonass_sym, ObservationKind.PSEUDORANGE_GLONASS, sat_pos_freq_sym], - [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GPS, sat_pos_vel_sym], - [h_pseudorange_rate_sym, ObservationKind.PSEUDORANGE_RATE_GLONASS, sat_pos_vel_sym], - [h_relative_motion, ObservationKind.CAMERA_ODO_TRANSLATION, None], - [h_phone_rot_sym, ObservationKind.CAMERA_ODO_ROTATION, None], - [h_acc_stationary_sym, ObservationKind.NO_ACCEL, None]] - - wide_from_device = euler_rotate(*wide_from_device_euler) - # MSCKF configuration - if N > 0: - # experimentally found this is correct value for imx298 with 910 focal length - # this is a variable so it can change with focus, but we disregard that for now - # TODO: this isn't correct for tici - focal_scale = 1.01 - # Add observation functions for orb feature tracks - track_epos_sym = sp.MatrixSymbol('track_epos_sym', 3, 1) - track_x, track_y, track_z = track_epos_sym - h_track_sym = sp.Matrix(np.zeros(((1 + N) * 2, 1))) - h_track_wide_cam_sym = sp.Matrix(np.zeros(((1 + N) * 2, 1))) - - track_pos_sym = sp.Matrix([track_x - x, track_y - y, track_z - z]) - track_pos_rot_sym = quat_rot.T * track_pos_sym - track_pos_rot_wide_cam_sym = wide_from_device * track_pos_rot_sym - h_track_sym[-2:, :] = sp.Matrix([focal_scale * (track_pos_rot_sym[1] / track_pos_rot_sym[0]), - focal_scale * (track_pos_rot_sym[2] / track_pos_rot_sym[0])]) - h_track_wide_cam_sym[-2:, :] = sp.Matrix([focal_scale * (track_pos_rot_wide_cam_sym[1] / track_pos_rot_wide_cam_sym[0]), - focal_scale * (track_pos_rot_wide_cam_sym[2] / track_pos_rot_wide_cam_sym[0])]) - - h_msckf_test_sym = sp.Matrix(np.zeros(((1 + N) * 3, 1))) - h_msckf_test_sym[-3:, :] = track_pos_sym - - for n in range(N): - idx = dim_main + n * dim_augment - # err_idx = dim_main_err + n * dim_augment_err # FIXME: Why is this not used? - x, y, z = state[idx:idx + 3] - q = state[idx + 3:idx + 7] - quat_rot = quat_rotate(*q) - track_pos_sym = sp.Matrix([track_x - x, track_y - y, track_z - z]) - track_pos_rot_sym = quat_rot.T * track_pos_sym - track_pos_rot_wide_cam_sym = wide_from_device * track_pos_rot_sym - h_track_sym[n * 2:n * 2 + 2, :] = sp.Matrix([focal_scale * (track_pos_rot_sym[1] / track_pos_rot_sym[0]), - focal_scale * (track_pos_rot_sym[2] / track_pos_rot_sym[0])]) - h_track_wide_cam_sym[n * 2: n * 2 + 2, :] = sp.Matrix([focal_scale * (track_pos_rot_wide_cam_sym[1] / track_pos_rot_wide_cam_sym[0]), - focal_scale * (track_pos_rot_wide_cam_sym[2] / track_pos_rot_wide_cam_sym[0])]) - h_msckf_test_sym[n * 3:n * 3 + 3, :] = track_pos_sym - - obs_eqs.append([h_msckf_test_sym, ObservationKind.MSCKF_TEST, track_epos_sym]) - obs_eqs.append([h_track_sym, ObservationKind.ORB_FEATURES, track_epos_sym]) - obs_eqs.append([h_track_wide_cam_sym, ObservationKind.ORB_FEATURES_WIDE, track_epos_sym]) - obs_eqs.append([h_track_sym, ObservationKind.FEATURE_TRACK_TEST, track_epos_sym]) - msckf_params = [dim_main, dim_augment, dim_main_err, dim_augment_err, N, - [ObservationKind.MSCKF_TEST, ObservationKind.ORB_FEATURES, ObservationKind.ORB_FEATURES_WIDE]] - else: - msckf_params = None - gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state_err, eskf_params, msckf_params, maha_test_kinds) - - def __init__(self, generated_dir, N=4, erratic_clock=False): - name = f"{self.name}_{N}" - - - # process noise - q_clock_error = 100.0 if erratic_clock else 0.1 - q_clock_error_rate = 10 if erratic_clock else 0.0 - self.Q = np.diag([0.03**2, 0.03**2, 0.03**2, - 0.0**2, 0.0**2, 0.0**2, - 0.0**2, 0.0**2, 0.0**2, - 0.1**2, 0.1**2, 0.1**2, - (q_clock_error)**2, (q_clock_error_rate)**2, - (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, - (0.02 / 100)**2, - 3**2, 3**2, 3**2, - 0.001**2, - (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2, - (.1)**2, (.01)**2, - 0.005**2, - (0.02 / 100)**2, - (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, - (0.05 / 60)**2, (0.05 / 60)**2, (0.05 / 60)**2]) - - - self.obs_noise = {ObservationKind.ODOMETRIC_SPEED: np.atleast_2d(0.2**2), - ObservationKind.PHONE_GYRO: np.diag([0.025**2, 0.025**2, 0.025**2]), - ObservationKind.PHONE_ACCEL: np.diag([.5**2, .5**2, .5**2]), - ObservationKind.CAMERA_ODO_ROTATION: np.diag([0.05**2, 0.05**2, 0.05**2]), - ObservationKind.IMU_FRAME: np.diag([0.05**2, 0.05**2, 0.05**2]), - ObservationKind.NO_ROT: np.diag([0.0025**2, 0.0025**2, 0.0025**2]), - ObservationKind.ECEF_POS: np.diag([5**2, 5**2, 5**2]), - ObservationKind.NO_ACCEL: np.diag([0.0025**2, 0.0025**2, 0.0025**2])} - - # MSCKF stuff - self.N = N - self.dim_main = LocKalman.x_initial.shape[0] - self.dim_main_err = LocKalman.P_initial.shape[0] - self.dim_state = self.dim_main + self.dim_augment * self.N - self.dim_state_err = self.dim_main_err + self.dim_augment_err * self.N - - if self.N > 0: - x_initial, P_initial, Q = self.pad_augmented(self.x_initial, self.P_initial, self.Q) # lgtm[py/mismatched-multiple-assignment] - self.computer = LstSqComputer(generated_dir, N) - - self.quaternion_idxs = [3, ] + [(self.dim_main + i * self.dim_augment + 3)for i in range(self.N)] - - # init filter - self.filter = EKF_sym(generated_dir, name, Q, x_initial, P_initial, self.dim_main, self.dim_main_err, - N, self.dim_augment, self.dim_augment_err, self.maha_test_kinds, self.quaternion_idxs) - - @property - def x(self): - return self.filter.state() - - @property - def t(self): - return self.filter.get_filter_time() - - @property - def P(self): - return self.filter.covs() - - def predict(self, t): - return self.filter.predict(t) - - def rts_smooth(self, estimates): - return self.filter.rts_smooth(estimates, norm_quats=True) - - def pad_augmented(self, x, P, Q=None): - if x.shape[0] == self.dim_main and self.N > 0: - x = np.pad(x, (0, self.N * self.dim_augment), mode='constant') - x[self.dim_main + 3::7] = 1 - if P.shape[0] == self.dim_main_err and self.N > 0: - P = np.pad(P, [(0, self.N * self.dim_augment_err), (0, self.N * self.dim_augment_err)], mode='constant') - P[self.dim_main_err:, self.dim_main_err:] = 10e20 * np.eye(self.dim_augment_err * self.N) - if Q is None: - return x, P - else: - Q = np.pad(Q, [(0, self.N * self.dim_augment_err), (0, self.N * self.dim_augment_err)], mode='constant') - return x, P, Q - - def init_state(self, state, covs_diag=None, covs=None, filter_time=None): - if covs_diag is not None: - P = np.diag(covs_diag) - elif covs is not None: - P = covs - else: - P = self.filter.covs() - state, P = self.pad_augmented(state, P) - self.filter.init_state(state, P, filter_time) - - def predict_and_observe(self, t, kind, data): - if len(data) > 0: - data = np.atleast_2d(data) - if kind == ObservationKind.CAMERA_ODO_TRANSLATION: - r = self.predict_and_update_odo_trans(data, t, kind) - elif kind == ObservationKind.CAMERA_ODO_ROTATION: - r = self.predict_and_update_odo_rot(data, t, kind) - elif kind == ObservationKind.PSEUDORANGE_GPS or kind == ObservationKind.PSEUDORANGE_GLONASS: - r = self.predict_and_update_pseudorange(data, t, kind) - elif kind == ObservationKind.PSEUDORANGE_RATE_GPS or kind == ObservationKind.PSEUDORANGE_RATE_GLONASS: - r = self.predict_and_update_pseudorange_rate(data, t, kind) - elif kind == ObservationKind.ORB_FEATURES or kind == ObservationKind.ORB_FEATURES_WIDE: - r = self.predict_and_update_orb_features(data, t, kind) - elif kind == ObservationKind.MSCKF_TEST: - r = self.predict_and_update_msckf_test(data, t, kind) - else: - r = self.filter.predict_and_update_batch(t, kind, data, self.get_R(kind, len(data))) - # Normalize quats - quat_norm = np.linalg.norm(self.filter.state()[3:7]) - # Should not continue if the quats behave this weirdly - if not 0.1 < quat_norm < 10: - raise RuntimeError("Sir! The filter's gone all wobbly!") - return r - - def get_R(self, kind, n): - obs_noise = self.obs_noise[kind] - dim = obs_noise.shape[0] - R = np.zeros((n, dim, dim)) - for i in range(n): - R[i, :, :] = obs_noise - return R - - def predict_and_update_pseudorange(self, meas, t, kind): - R = np.zeros((len(meas), 1, 1)) - sat_pos_freq = np.zeros((len(meas), 4)) - z = np.zeros((len(meas), 1)) - for i, m in enumerate(meas): - z_i, R_i, sat_pos_freq_i = parse_pr(m) - sat_pos_freq[i, :] = sat_pos_freq_i - z[i, :] = z_i - R[i, :, :] = R_i - return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_freq) - - def predict_and_update_pseudorange_rate(self, meas, t, kind): - R = np.zeros((len(meas), 1, 1)) - z = np.zeros((len(meas), 1)) - sat_pos_vel = np.zeros((len(meas), 6)) - for i, m in enumerate(meas): - z_i, R_i, sat_pos_vel_i = parse_prr(m) - sat_pos_vel[i] = sat_pos_vel_i - R[i, :, :] = R_i - z[i, :] = z_i - return self.filter.predict_and_update_batch(t, kind, z, R, sat_pos_vel) - - def predict_and_update_odo_trans(self, trans, t, kind): - z = trans[:, :3] - R = np.zeros((len(trans), 3, 3)) - for i, _ in enumerate(z): - R[i, :, :] = np.diag(trans[i, 3:]**2) - return self.filter.predict_and_update_batch(t, kind, z, R) - - def predict_and_update_odo_rot(self, rot, t, kind): - z = rot[:, :3] - R = np.zeros((len(rot), 3, 3)) - for i, _ in enumerate(z): - R[i, :, :] = np.diag(rot[i, 3:]**2) - return self.filter.predict_and_update_batch(t, kind, z, R) - - def predict_and_update_orb_features(self, tracks, t, kind): - k = 2 * (self.N + 1) - R = np.zeros((len(tracks), k, k)) - z = np.zeros((len(tracks), k)) - ecef_pos = np.zeros((len(tracks), 3)) - ecef_pos[:] = np.nan - poses = self.x[self.dim_main:].reshape((-1, 7)) - times = tracks.reshape((len(tracks), self.N + 1, 4))[:, :, 0] - if kind==ObservationKind.ORB_FEATURES: - pt_std = 0.005 - else: - pt_std = 0.02 - if times.any(): - assert np.allclose(times[0, :-1], self.filter.get_augment_times(), atol=1e-7, rtol=0.0) - for i, track in enumerate(tracks): - img_positions = track.reshape((self.N + 1, 4))[:, 2:] - - # TODO not perfect as last pose not used - # img_positions = unroll_shutter(img_positions, poses, self.filter.state()[7:10], self.filter.state()[10:13], ecef_pos[i]) - - ecef_pos[i] = self.computer.compute_pos(poses, img_positions[:-1]) - z[i] = img_positions.flatten() - R[i, :, :] = np.diag([pt_std**2] * (k)) - - good_idxs = np.all(np.isfinite(ecef_pos), axis=1) - - # This code relies on wide and narrow orb features being captured at the same time, - # and wide features to be processed first. - ret = self.filter.predict_and_update_batch(t, kind, z[good_idxs], R[good_idxs], ecef_pos[good_idxs], - augment=kind==ObservationKind.ORB_FEATURES) - if ret is None: - return - - # have to do some weird stuff here to keep - # to have the observations input from mesh3d - # consistent with the outputs of the filter - # Probably should be replaced, not sure how. - y_full = np.zeros((z.shape[0], z.shape[1] - 3)) - if sum(good_idxs) > 0: - y_full[good_idxs] = np.array(ret[6]) - ret = ret[:6] + (y_full, z, ecef_pos) - return ret - - def predict_and_update_msckf_test(self, test_data, t, kind): - assert self.N > 0 - z = test_data - R = np.zeros((len(test_data), len(z[0]), len(z[0]))) - ecef_pos = [self.x[:3]] - for i, _ in enumerate(z): - R[i, :, :] = np.diag([0.1**2] * len(z[0])) - ret = self.filter.predict_and_update_batch(t, kind, z, R, ecef_pos) - self.filter.augment() - return ret - - def maha_test_pseudorange(self, x, P, meas, kind, maha_thresh=.3): - bools = [] - for m in meas: - z, R, sat_pos_freq = parse_pr(m) - bools.append(self.filter.maha_test(x, P, kind, z, R, extra_args=sat_pos_freq, maha_thresh=maha_thresh)) - return np.array(bools) - - def maha_test_pseudorange_rate(self, x, P, meas, kind, maha_thresh=.999): - bools = [] - for m in meas: - z, R, sat_pos_vel = parse_prr(m) - bools.append(self.filter.maha_test(x, P, kind, z, R, extra_args=sat_pos_vel, maha_thresh=maha_thresh)) - return np.array(bools) - - -if __name__ == "__main__": - N = int(sys.argv[1].split("_")[-1]) - generated_dir = sys.argv[2] - LocKalman.generate_code(generated_dir, N=N) diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index c2873c187d..a183002589 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -22,6 +22,7 @@ COPY ./body ${OPENPILOT_PATH}/body COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons COPY ./rednose ${OPENPILOT_PATH}/rednose +COPY ./rednose_repo/site_scons ${OPENPILOT_PATH}/rednose_repo/site_scons COPY ./common ${OPENPILOT_PATH}/common COPY ./opendbc ${OPENPILOT_PATH}/opendbc COPY ./cereal ${OPENPILOT_PATH}/cereal From 8d3bf1f41a52825e43ce9542ee86dea4b32f9b6c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 22 Nov 2023 13:45:49 -0800 Subject: [PATCH 069/724] Pytest: add cpp harness (#30471) cpp harness --- pyproject.toml | 1 + selfdrive/test/cpp_harness.py | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100755 selfdrive/test/cpp_harness.py diff --git a/pyproject.toml b/pyproject.toml index 38c3337c6f..1426905d06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,7 @@ minversion = "6.0" addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" cpp_files = "test_*" +cpp_harness = "selfdrive/test/cpp_harness.py" python_files = "test_*.py" #timeout = "30" # you get this long by default markers = [ diff --git a/selfdrive/test/cpp_harness.py b/selfdrive/test/cpp_harness.py new file mode 100755 index 0000000000..a16b5707f5 --- /dev/null +++ b/selfdrive/test/cpp_harness.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +import subprocess +import sys + +from openpilot.common.prefix import OpenpilotPrefix + + +with OpenpilotPrefix(): + ret = subprocess.call(sys.argv[1:]) + +exit(ret) \ No newline at end of file From f845c69b43ff56ad91236d0389f4721b6e9c6ecc Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 23 Nov 2023 11:10:44 -0800 Subject: [PATCH 070/724] Subaru: ignore eyesight faults when openpilot is controlling long (#30390) ignore eyesight faults --- selfdrive/car/subaru/carcontroller.py | 7 ++++--- selfdrive/car/subaru/subarucan.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index c277f012ab..cc8ce4f722 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -95,8 +95,8 @@ class CarController: else: if self.frame % 10 == 0: - can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled, self.CP.openpilotLongitudinalControl, - CC.longActive, hud_control.leadVisible)) + can_sends.append(subarucan.create_es_dashstatus(self.packer, self.frame // 10, CS.es_dashstatus_msg, CC.enabled, + self.CP.openpilotLongitudinalControl, CC.longActive, hud_control.leadVisible)) can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.enabled, hud_control.visualAlert, hud_control.leftLaneVisible, hud_control.rightLaneVisible, @@ -110,7 +110,8 @@ class CarController: can_sends.append(subarucan.create_es_status(self.packer, self.frame // 5, CS.es_status_msg, self.CP.openpilotLongitudinalControl, CC.longActive, cruise_rpm)) - can_sends.append(subarucan.create_es_brake(self.packer, self.frame // 5, CS.es_brake_msg, CC.enabled, cruise_brake)) + can_sends.append(subarucan.create_es_brake(self.packer, self.frame // 5, CS.es_brake_msg, + self.CP.openpilotLongitudinalControl, CC.longActive, cruise_brake)) can_sends.append(subarucan.create_es_distance(self.packer, self.frame // 5, CS.es_distance_msg, 0, pcm_cancel_cmd, self.CP.openpilotLongitudinalControl, cruise_brake > 0, cruise_throttle)) diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index 7d1939a497..b851bcc088 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -46,6 +46,7 @@ def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long # Do not disable openpilot on Eyesight Soft Disable, if openpilot is controlling long values["Cruise_Soft_Disable"] = 0 + values["Cruise_Fault"] = 0 if brake_cmd: values["Cruise_Brake_Active"] = 1 @@ -160,6 +161,7 @@ def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, l if long_enabled: values["PCB_Off"] = 1 # AEB is not presevered, so show the PCB_Off on dash + values["Cruise_Fault"] = 0 # Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts if values["LKAS_State_Msg"] in (2, 3): @@ -167,7 +169,7 @@ def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, l return packer.make_can_msg("ES_DashStatus", CanBus.main, values) -def create_es_brake(packer, frame, es_brake_msg, enabled, brake_value): +def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brake_value): values = {s: es_brake_msg[s] for s in [ "CHECKSUM", "Signal1", @@ -182,8 +184,11 @@ def create_es_brake(packer, frame, es_brake_msg, enabled, brake_value): values["COUNTER"] = frame % 0x10 - if enabled: - values["Cruise_Activated"] = 1 + if long_enabled: + values["Cruise_Brake_Fault"] = 0 + + if long_active: + values["Cruise_Activated"] = 1 values["Brake_Pressure"] = brake_value @@ -210,6 +215,7 @@ def create_es_status(packer, frame, es_status_msg, long_enabled, long_active, cr if long_enabled: values["Cruise_RPM"] = cruise_rpm + values["Cruise_Fault"] = 0 if long_active: values["Cruise_Activated"] = 1 From 4b0d0b0f1eba784d1216dae33da5123f087d976c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 24 Nov 2023 20:17:51 -0800 Subject: [PATCH 071/724] cleanup cameraview shader (#30520) cleanup shader Co-authored-by: Comma Device --- selfdrive/ui/qt/widgets/cameraview.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 6a5064e8ca..edf7b5990b 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -31,8 +31,8 @@ const char frame_vertex_shader[] = " vTexCoord = aTexCoord;\n" "}\n"; -#ifdef QCOM2 const char frame_fragment_shader[] = +#ifdef QCOM2 "#version 300 es\n" "#extension GL_OES_EGL_image_external_essl3 : enable\n" "precision mediump float;\n" @@ -43,7 +43,6 @@ const char frame_fragment_shader[] = " colorOut = texture(uTexture, vTexCoord);\n" "}\n"; #else -const char frame_fragment_shader[] = #ifdef __APPLE__ "#version 330 core\n" #else From e25f78831802d0556937f001e41b0ccca460ff25 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 25 Nov 2023 11:47:04 -0800 Subject: [PATCH 072/724] bump panda (#30519) --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index c345bc3cae..c67c973b99 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit c345bc3cae34f096fdece3c36ae7e2634023ce82 +Subproject commit c67c973b99cf9cd48e46914e9dec38678b2b8da4 From 6f073aed5f412a3571135faebf123b952aedb95c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sun, 26 Nov 2023 08:27:25 -0800 Subject: [PATCH 073/724] CI: automatically create PR to bump submodules (#30523) --- .github/workflows/repo-maintenance.yaml | 31 ++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 66e252bf83..1e6f6366ad 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -2,12 +2,36 @@ name: repo maintenance on: schedule: - - cron: "0 15 * * 2" + - cron: "0 15 * * *" workflow_dispatch: jobs: - updates: - name: updates + bump_submodules: + name: bump_submodules + runs-on: ubuntu-20.04 + container: + image: ghcr.io/commaai/openpilot-base:latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: bump submodules + run: | + git config --global --add safe.directory '*' + git -c submodule."tinygrad".update=none submodule update --remote + git add . + - name: Create Pull Request + uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 + with: + token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} + commit-message: bump submodules + title: 'Bump submodules' + branch: auto-bump-submodules + base: master + delete-branch: true + body: 'Automatic PR from repo-maintenance -> bump_submodules' + package_updates: + name: package_updates runs-on: ubuntu-20.04 container: image: ghcr.io/commaai/openpilot-base:latest @@ -30,3 +54,4 @@ jobs: branch: auto-package-updates base: master delete-branch: true + body: 'Automatic PR from repo-maintenance -> package_updates' \ No newline at end of file From 1d0538bdef949c0a179bfac8598369c0e84706ce Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Nov 2023 09:31:08 -0800 Subject: [PATCH 074/724] Bump submodules (#30528) --- body | 2 +- cereal | 2 +- opendbc | 2 +- panda | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/body b/body index 6ff44357a3..cad7ef43ad 160000 --- a/body +++ b/body @@ -1 +1 @@ -Subproject commit 6ff44357a3e416d29044b1d085a3e9223db9691a +Subproject commit cad7ef43ad5daf40b44d4792d47671c397cca376 diff --git a/cereal b/cereal index 416c3d531c..2cb2bfb015 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 416c3d531c90ce16498d782bf383625a857ee74c +Subproject commit 2cb2bfb0154874775e56715ecf81f0e6b00538e9 diff --git a/opendbc b/opendbc index 4ab347baef..2b96bcc456 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 4ab347baefb7473771ada0723c969c50d0c28d01 +Subproject commit 2b96bcc45669cdd14f9c652b07ef32d6403630f6 diff --git a/panda b/panda index c67c973b99..6eb89cefdf 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit c67c973b99cf9cd48e46914e9dec38678b2b8da4 +Subproject commit 6eb89cefdfdbcb80efef02f121beb6d02867a106 From eb3833037f8389fb7b86efa0a4205e89ca4a3194 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Nov 2023 09:31:20 -0800 Subject: [PATCH 075/724] Update Python packages and pre-commit hooks (#30529) --- .pre-commit-config.yaml | 2 +- poetry.lock | 372 ++++++++++++++++++++-------------------- 2 files changed, 190 insertions(+), 184 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6992141556..b6a46ad507 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -86,6 +86,6 @@ repos: args: - --lock - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.1 + rev: 0.27.2 hooks: - id: check-github-workflows diff --git a/poetry.lock b/poetry.lock index 78b88ebb61..72edd944c7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -934,69 +934,69 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "cython" -version = "3.0.5" +version = "3.0.6" description = "The Cython compiler for writing C extensions in the Python language." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Cython-3.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4faf17ea6e8fc3065a862d4b24be84048fd58ed7abe59aa2f9141446a7a72335"}, - {file = "Cython-3.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1cab30c11880f38a27911b569ea38b0bd67fcf32f8a8a8519b613c70562dae2"}, - {file = "Cython-3.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4d4d92182002b2878adb3329de1ccb7f3f7571d3586f92602e790bfeab45d0"}, - {file = "Cython-3.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94f58e05e69e1a43da551c8f532e9fad057df1641f0f8ae8f103d4ede5a80fe"}, - {file = "Cython-3.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a90f9c7b6635967eacafebe439d518b7dc720aaaf19cb9553f5aad03c13296f4"}, - {file = "Cython-3.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c95bd21d87b08c88fe5296381a5f39cd979a775bf1a1d7379a6ff87c703e510b"}, - {file = "Cython-3.0.5-cp310-cp310-win32.whl", hash = "sha256:ebc901131057c115a8868e14c1df6e56b9190df774b72664c03ebd858296bb81"}, - {file = "Cython-3.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:0759868b4a4d103578375e031e89abd578c26774d957ee4f591764ef8003b363"}, - {file = "Cython-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3679a6693456f5f7ccca9ab2a91408e99ee257e145024fe380da7c78a07e98b6"}, - {file = "Cython-3.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ad4eb2608661d63fb57c674dafb9955f5141d748d4579c7722c1a3c6b86a0c2"}, - {file = "Cython-3.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37f4b0d983316242b4b9241ecbbe55220aa92af93ff04626441fe0ea90a54f9"}, - {file = "Cython-3.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34059c3ea6e342ba388cd9774a61761bb4200ca18bd475de65c9cc70ef4e0204"}, - {file = "Cython-3.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4db9eea298e982aee7ba12b3432c66eb2e91bb2f5d026bbd57c35698ea0f557f"}, - {file = "Cython-3.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:452679284c15a7d5a88bff675e1dd660349360f0665aea50be2d98b7650925f8"}, - {file = "Cython-3.0.5-cp311-cp311-win32.whl", hash = "sha256:2d6bb318ddce8b978c81cf78caf8b3836db84f6235d721952685e87871f506e4"}, - {file = "Cython-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:fcfd2255458a5779dbab813550695331d541b24f0ef831ace83f04f9516ddf26"}, - {file = "Cython-3.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0d9fcfc09d67218fce114fe9fd97bba4f9d56add0f775c588d8c626ed47f1aef"}, - {file = "Cython-3.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ac1cf1f2ed01656b33618352f7e42bf75d027425b83cc96cfe13ce4b6cba5de"}, - {file = "Cython-3.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9d17a6ceb301c5dbd3820e62c1b10a4ad3a6eea3e07e7afaf736b5f490c2e32"}, - {file = "Cython-3.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd9cab3b862bec2b110886aedb11765e9deda363c4c7ab5ea205f3d8f143c411"}, - {file = "Cython-3.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:45277bb54c35b11bcdd6cf0f56eb950eb280b67146db0cb57853fb6334c6d11d"}, - {file = "Cython-3.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:77f4d001fb7a03a68b53be20571acd17452d1dda7907d9c325dff0cc704b1ef9"}, - {file = "Cython-3.0.5-cp312-cp312-win32.whl", hash = "sha256:57b44f789794d74c1feddae054dd045b9f601bdffd7288e069b4ca7ed607ec61"}, - {file = "Cython-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:05c4efd36879ff8020af00407e4c14246b894558ea41dc6486f60dd71601fc67"}, - {file = "Cython-3.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:048fe89c2c753c24e1a7a05496907173dab17e238c8dc3c7cad90b3679b0d846"}, - {file = "Cython-3.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c016b3e859b41cf4ce1b8107f364ad5a83c086f75ea4d8d3990b24691f626a1"}, - {file = "Cython-3.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f31d02b831d0fa9bf099b1b714b5a8252eabd8db34b7d33c48e7e808a2dabf9"}, - {file = "Cython-3.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:485f8a3087392e2287e2869adc0385b439f69b9cfbd262fdf39b00417690c988"}, - {file = "Cython-3.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:063220a6dc0909b576ef068c7e2acf5c608d64423a6d231aacb72d06352cd95b"}, - {file = "Cython-3.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:abb2362783521bd9a22fe69b2141abab4db97237665a36a034b161ddee5b3e72"}, - {file = "Cython-3.0.5-cp36-cp36m-win32.whl", hash = "sha256:a993002fe28c840dc29805fde7341c775b7878b311b85f21560fdebf220c247b"}, - {file = "Cython-3.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:13491f1bfcf020fd02751c4a55294aa8957e21b7ecd2542b0521a7aa50c58bb2"}, - {file = "Cython-3.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:45aaceb082ad89365f2f783a40db42359591ad55914fb298841196edf88afdc5"}, - {file = "Cython-3.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e011fa2ae9e953fe1ab8394329a21bdb54357c7fe509bcfb02b88bc15bffbb"}, - {file = "Cython-3.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f18c13d5ed6fde5efd3b1c039f6a34296d1a0409bb00fbf45bec6f9bcf63ddf5"}, - {file = "Cython-3.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:039877e57dc10abf0d30d2de2c7476f0881d8ecef1f29bdeed1a6a06a8d89141"}, - {file = "Cython-3.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fbc8f62b8d50f9a2eef99927a9dcb8d0a42e5a801ab14c2e4aeab622c88f54b"}, - {file = "Cython-3.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3cffbba1888f795de2103e6fb1482c8ea8d457e638fa813e090fe747f9e549bb"}, - {file = "Cython-3.0.5-cp37-cp37m-win32.whl", hash = "sha256:c18e125537a96e76c8c34201e5a9aad8625e3d872dd26a63155573462e54e185"}, - {file = "Cython-3.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:93502f45948ae8d7f874ba4c113b50cb6fb4ee664caa82e1ddc398500ee0ffb3"}, - {file = "Cython-3.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0a9206b0720f0cad3e70c018722f6d10e81b32e65477e14ffedd3fbfadfaddca"}, - {file = "Cython-3.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:530a474a79fa6c2127bb7e3ba00857b1f26a563615863f17b7434331aa3fe404"}, - {file = "Cython-3.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:115e76fbe9288119526b66963f614042222d1587f1ba5ddb90088215a3d2a25a"}, - {file = "Cython-3.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:035cb6930a8534f865a3f4616543f78bd27e4de9c3e117b2826e395085ffc4c0"}, - {file = "Cython-3.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:077d9a61e6042174dabf68b8e92c0a80f5aff529439ed314aa6e6a233af80b95"}, - {file = "Cython-3.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ba3f7b433c1721a43674c0889d5fad746bf608416c8f849343859e6d4d3a7113"}, - {file = "Cython-3.0.5-cp38-cp38-win32.whl", hash = "sha256:a95ed0e6f481462a3ff2be4c2e4ffffc5d00fc3884d4ccd1fe5b702d4938ec09"}, - {file = "Cython-3.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:f687539ead9fbc17f499e33ee20c1dc41598f70ad95edb4990c576447cec9d23"}, - {file = "Cython-3.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6fcfef825edb44cf3c6ba2c091ad76a83da62ac9c79553e80e0c2a1f75eda2e"}, - {file = "Cython-3.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0d9431101f600d5a557d55989658cbfd02b7c0dcd1e4675dac8ad7e0da8ea5b"}, - {file = "Cython-3.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db21997270e943aee9cb7694112d24a4702fbe1977fbe53b3cb4db3d02be73d9"}, - {file = "Cython-3.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808f56d4cd0511723b787c341f3cc995fd72893e608102268298c188f4a4f2e7"}, - {file = "Cython-3.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:dee39967168d6326ea2df56ad215a4d5049aa52f44cd5aad45bfb63d5b4fb9e5"}, - {file = "Cython-3.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b77f2b45535bcf3741592fa03183558bd42198b872c1584b896fa0ba5f2ac68d"}, - {file = "Cython-3.0.5-cp39-cp39-win32.whl", hash = "sha256:5742ef31e1e2c9a4824ef6b05af0f4949047a6f73af1d4b238ce12935174aede"}, - {file = "Cython-3.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:ada4852db0e33dbdd1425322db081d22b9725cb9f5eba42885467b4e2c4f2ac0"}, - {file = "Cython-3.0.5-py2.py3-none-any.whl", hash = "sha256:75206369504fc442c10a86ecf57b91592dca744e4592af22a47e9a774d53dd10"}, - {file = "Cython-3.0.5.tar.gz", hash = "sha256:39318348db488a2f24e7c84e08bdc82f2624853c0fea8b475ea0b70b27176492"}, + {file = "Cython-3.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcdfbf6fc7d0bd683d55e617c3d5a5f25b28ce8b405bc1e89054fc7c52a97e5"}, + {file = "Cython-3.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccbee314f8d15ee8ddbe270859dda427e1187123f2c7c41526d1f260eee6c8f7"}, + {file = "Cython-3.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b992f36ffa1294921fca5f6488ea192fadd75770dc64fa25975379382551e9"}, + {file = "Cython-3.0.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ca2e90a75d405070f3c41e701bb8005892f14d42322f1d8fd00a61d660bbae7"}, + {file = "Cython-3.0.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4121c1160bc1bd8828546e8ce45906bd9ff27799d14747ce3fbbc9d67efbb1b8"}, + {file = "Cython-3.0.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:519814b8f80869ee5f9ee2cb2363e5c310067c0298cbea291c556b22da1ef6ae"}, + {file = "Cython-3.0.6-cp310-cp310-win32.whl", hash = "sha256:b029d8c754ef867ab4d67fc2477dde9782bf0409cb8e4024a7d29cf5aff37530"}, + {file = "Cython-3.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:2262390f453eedf600e084b074144286576ed2a56bb7fbfe15ad8d9499eceb52"}, + {file = "Cython-3.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfe8c7ac60363769ed8d91fca26398aaa9640368ab999a79b0ccb5e788d3bcf8"}, + {file = "Cython-3.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e31a9b18ec6ce57eb3479df920e6093596fe4ba8010dcc372720040386b4bdb"}, + {file = "Cython-3.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca2542f1f34f0141475b13777df040c31f2073a055097734a0a793ac3a4fb72"}, + {file = "Cython-3.0.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b24c1c38dad4bd85e142ccbe2f88122807f8d5a75352321e1e4baf2b293df7c6"}, + {file = "Cython-3.0.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dc4b4e76c1414584bb55465dfb6f41dd6bd27fd53fb41ddfcaca9edf00c1f80e"}, + {file = "Cython-3.0.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:805a2c532feee09aeed064eaeb7b6ee35cbab650569d0a3756975f3cc4f246cf"}, + {file = "Cython-3.0.6-cp311-cp311-win32.whl", hash = "sha256:dcdb9a177c7c385fe0c0709a9a6790b6508847d67dcac76bb65a2c7ea447efe5"}, + {file = "Cython-3.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:b8640b7f6503292c358cef925df5a69adf230045719893ffe20ad98024fdf7ae"}, + {file = "Cython-3.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:16b3b02cc7b3bc42ee1a0118b1465ca46b0f3fb32d003e6f1a3a352a819bb9a3"}, + {file = "Cython-3.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11e1d9b153573c425846b627bef52b3b99cb73d4fbfbb136e500a878d4b5e803"}, + {file = "Cython-3.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a7a406f78c2f297bf82136ff5deac3150288446005ed1e56552a9e3ac1469f"}, + {file = "Cython-3.0.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88be4fbc760de8f313df89ca8256098c0963c9ec72f3aa88538384b80ef1a6ef"}, + {file = "Cython-3.0.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea2e5a7c503b41618bfb10e4bc610f780ab1c729280531b5cabb24e05aa21cf2"}, + {file = "Cython-3.0.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d296b48e1410cab50220a28a834167f2d7ac6c0e7de12834d66e42248a1b0f6"}, + {file = "Cython-3.0.6-cp312-cp312-win32.whl", hash = "sha256:7f19e99c6e334e9e30dfa844c3ca4ac09931b94dbba406c646bde54687aed758"}, + {file = "Cython-3.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:9cae02e26967ffb6503c6e91b77010acbadfb7189a5a11d6158d634fb0f73679"}, + {file = "Cython-3.0.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cb6a54543869a5b0ad009d86eb0ebc0879fab838392bfd253ad6d4f5e0f17d84"}, + {file = "Cython-3.0.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d2d9e53bf021cc7a5c7b6b537b5b5a7ba466ba7348d498aa17499d0ad12637e"}, + {file = "Cython-3.0.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05d15854b2b363b35c755d22015c1c2fc590b8128202f8c9eb85578461101d9c"}, + {file = "Cython-3.0.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5548316497a3b8b2d9da575ea143476472db90dee73c67def061621940f78ae"}, + {file = "Cython-3.0.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9b853e0855e4b3d164c05b24718e5e2df369e5af54f47cb8d923c4f497dfc92c"}, + {file = "Cython-3.0.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2c77f97f462a40a319dda7e28c1669370cb26f9175f3e8f9bab99d2f8f3f2f09"}, + {file = "Cython-3.0.6-cp36-cp36m-win32.whl", hash = "sha256:3ac8b6734f2cad5640f2da21cd33cf88323547d07e445fb7453ab38ec5033b1f"}, + {file = "Cython-3.0.6-cp36-cp36m-win_amd64.whl", hash = "sha256:8dd5f5f3587909ff71f0562f50e00d4b836c948e56e8f74897b12f38a29e41b9"}, + {file = "Cython-3.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9c0472c6394750469062deb2c166125b10411636f63a0418b5c36a60d0c9a96a"}, + {file = "Cython-3.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97081932c8810bb99cb26b4b0402202a1764b58ee287c8b306071d2848148c24"}, + {file = "Cython-3.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e781b3880dfd0d4d37983c9d414bfd5f26c2141f6d763d20ef1964a0a4cb2405"}, + {file = "Cython-3.0.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef88c46e91e21772a5d3b6b1e70a6da5fe098154ad4768888129b1c05e93bba7"}, + {file = "Cython-3.0.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a38b9e7a252ec27dbc21ee8f00f09a896e88285eebb6ed99207b2ff1ea6af28e"}, + {file = "Cython-3.0.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4975cdaf720d29288ec225b76b4f4471ff03f4f8b51841ba85d6587699ab2ad5"}, + {file = "Cython-3.0.6-cp37-cp37m-win32.whl", hash = "sha256:9b89463ea330318461ca47d3e49b5f606e7e82446b6f37e5c19b60392439674c"}, + {file = "Cython-3.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:0ca8f379b47417bfad98faeb14bf8a3966fc92cf69f8aaf7635cf6885e50d001"}, + {file = "Cython-3.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b3dda1e80eb577b9563cee6cf31923a7b88836b9f9be0043ec545b138b95d8e8"}, + {file = "Cython-3.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e34e9a96f98c379100ef4192994a311678fb5c9af34c83ba5230223577581"}, + {file = "Cython-3.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345d9112fde4ae0347d656f58591fd52017c61a19779c95423bb38735fe4a401"}, + {file = "Cython-3.0.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25da0e51331ac12ff16cd858d1d836e092c984e1dc45d338166081d3802297c0"}, + {file = "Cython-3.0.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:eebbf09089b4988b9f398ed46f168892e32fcfeec346b15954fdd818aa103456"}, + {file = "Cython-3.0.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e3ed0c125556324fa49b9e92bea13be7b158fcae6f72599d63c8733688257788"}, + {file = "Cython-3.0.6-cp38-cp38-win32.whl", hash = "sha256:86e1e5a5c9157a547d0a769de59c98a1fc5e46cfad976f32f60423cc6de11052"}, + {file = "Cython-3.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:0d45a84a315bd84d1515cd3571415a0ee0709eb4e2cd4b13668ede928af344a7"}, + {file = "Cython-3.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a8e788e64b659bb8fe980bc37da3118e1f7285dec40c5fb293adabc74d4205f2"}, + {file = "Cython-3.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a77a174c7fb13d80754c8bf9912efd3f3696d13285b2f568eca17324263b3f7"}, + {file = "Cython-3.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1074e84752cd0daf3226823ddbc37cca8bc45f61c94a1db2a34e641f2b9b0797"}, + {file = "Cython-3.0.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49d5cae02d56e151e1481e614a1af9a0fe659358f2aa5eca7a18f05aa641db61"}, + {file = "Cython-3.0.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b94610fa49e36db068446cfd149a42e3246f38a4256bbe818512ac181446b4b"}, + {file = "Cython-3.0.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fabb2d14dd71add618a7892c40ffec584d1dae1e477caa193778e52e06821d83"}, + {file = "Cython-3.0.6-cp39-cp39-win32.whl", hash = "sha256:ce442c0be72ab014c305399d955b78c3d1e69d5a5ce24398122b605691b69078"}, + {file = "Cython-3.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:8a05f79a0761fc76c42e945e5a9cb5d7986aa9e8e526fdf52bd9ca61a12d4567"}, + {file = "Cython-3.0.6-py2.py3-none-any.whl", hash = "sha256:5921a175ea20779d4443ef99276cfa9a1a47de0e32d593be7679be741c9ed93b"}, + {file = "Cython-3.0.6.tar.gz", hash = "sha256:399d185672c667b26eabbdca420c98564583798af3bc47670a8a09e9f19dd660"}, ] [[package]] @@ -1159,53 +1159,53 @@ files = [ [[package]] name = "fonttools" -version = "4.45.0" +version = "4.45.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.45.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18138744540413eb2ebeff6ce8b9d617926f1ed08da5d1676f99f1966988264e"}, - {file = "fonttools-4.45.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3d240933045b9dbbe6e8c1e28ffe89be72c9be927b6e572e55be5e2b2604f7"}, - {file = "fonttools-4.45.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5853263505f407b69c0d1cbf3ed1c30f985b9505523989b20aa18a5231d4a08a"}, - {file = "fonttools-4.45.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c21f306f1e71146cf7587916d6de5e9c4bf26057aad602a6c7fad4b6e05bf1f"}, - {file = "fonttools-4.45.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1bb4f01018b9f4e2d7b07c2bf79e2ef498acb6f99321b72b5c44b1333481f569"}, - {file = "fonttools-4.45.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d0e6603c3b00604574d84fabbcf9dee81efa7c89d38ed4dcbf4e6c654e1ebb99"}, - {file = "fonttools-4.45.0-cp310-cp310-win32.whl", hash = "sha256:c3e676e50a0713c9a1e46185b806967d3c012643d1936ca814eb9ab62027c090"}, - {file = "fonttools-4.45.0-cp310-cp310-win_amd64.whl", hash = "sha256:e819f14d315024750b1ad2842da605051444b944cd983ea987359ace5213bcb9"}, - {file = "fonttools-4.45.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a6d68b97b967a3361e0ddf14425e4fe030c9f19462b445ce0b190c4a6834eb46"}, - {file = "fonttools-4.45.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:619227c7b9088add4d4e5959bf0fa3c29a71c191baa8b989bf532645876b2168"}, - {file = "fonttools-4.45.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cafe001811ad1ac2a5d357fc99c490357d758569f69511d14da0311c02814e15"}, - {file = "fonttools-4.45.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:756c6f4324fd4bb4d7517462d7237ff65e45da7a002f9e6e08a48c25a11bf073"}, - {file = "fonttools-4.45.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ecc97628be1bf7fa93463e1e791915da66de51df8e655a5a6c846fd9b8ceaa98"}, - {file = "fonttools-4.45.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:636177ffc4479bc824a356c00a3c9a74a2ce591fa6057109321e0a0ffd126e40"}, - {file = "fonttools-4.45.0-cp311-cp311-win32.whl", hash = "sha256:cac462dbd9058778c89bc608ac29ba93ab3fbc37f305d260aa2d771cfb0fa694"}, - {file = "fonttools-4.45.0-cp311-cp311-win_amd64.whl", hash = "sha256:2bd3f33a5d5630cc20cf3f8631074cac6eafdb2aa3ac8745966c3b4bf93656b4"}, - {file = "fonttools-4.45.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5b3da7791a58c97763d1704c2b76a9d654b8f2ef233e64248960bd2c6e669fe4"}, - {file = "fonttools-4.45.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4fbc3d8acb578ba0932fcabc01a962f23b0dd254ab103dd0606128fff0175095"}, - {file = "fonttools-4.45.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f69e07ebfd89d96485349dc10bd81a027fc0e927f988fa31bd9cd06359e06ed"}, - {file = "fonttools-4.45.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b122fe802839bfc8f9603233e5fcbdc98b5e27876f7945b426adfea983918a7b"}, - {file = "fonttools-4.45.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8f8736e885700ae22970519b8f5c7f4c2f29c6e9459d05c649c4e99012c20b23"}, - {file = "fonttools-4.45.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:70f047ba37c6aac3d331b71bb784373e251bd86678da72526edc8585e79418e1"}, - {file = "fonttools-4.45.0-cp312-cp312-win32.whl", hash = "sha256:fdb43f68bce545f494fed1bfb60d2c32b53f410758919112923c2d600cb9a24c"}, - {file = "fonttools-4.45.0-cp312-cp312-win_amd64.whl", hash = "sha256:102a7ca8700a078265c002c29d2ae498edeb14b636375ceec2425b561ce08037"}, - {file = "fonttools-4.45.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4b154cbf93515e4eb477f5cf99de79b46c17229781f321907940bdbabbd64708"}, - {file = "fonttools-4.45.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f546a1b676622638a12c721d89cfb513ad7189548eadac885cdd555e35021557"}, - {file = "fonttools-4.45.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c5a0612049e0d06b467c3a0837d9efe37934acab64ba922f00e1d07c1555a7"}, - {file = "fonttools-4.45.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27166d00e0cd3ea49461b053f55e75676f1109e5483170a14d70c397d082a4c"}, - {file = "fonttools-4.45.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:980ba4e673439db22a00501fac087957ce0731351b042816f6c02df81cadc612"}, - {file = "fonttools-4.45.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:83b70b99f3f55b046cb51ca20fc15702567710233b2cd757a79e1916c25a25f8"}, - {file = "fonttools-4.45.0-cp38-cp38-win32.whl", hash = "sha256:fe8ad943f62bf16273154ebcdf855c44a3b46eac36abea338c209209439b4eb6"}, - {file = "fonttools-4.45.0-cp38-cp38-win_amd64.whl", hash = "sha256:6fb1fdcee2b36e012283805ef0380e4508dbb504950b1c94d0343f8dbbad7d7e"}, - {file = "fonttools-4.45.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5bbcb75ffcea64543ab8203e132e2019b226f59a4a6958637e78c21f9ca560ff"}, - {file = "fonttools-4.45.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ade07953b35ebf66c445a5e02f28ecd038ea588dc7673c555afe319b6e3c5168"}, - {file = "fonttools-4.45.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54ac8be3f693062fc08550213edd40db8f4fe1dd095a1246ed18e887fc254d76"}, - {file = "fonttools-4.45.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc991712aaea9d545b13ec480aaf2ebd12ccdea180fce864dd9863f5134f5a06"}, - {file = "fonttools-4.45.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:420139743e21d832de230757fb7b0c285d8024d602af8064d9506fa055bb62ae"}, - {file = "fonttools-4.45.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:58da830a52c05f18a7cc8a279a8bdddf2e45cdc685b360699653fb3405557459"}, - {file = "fonttools-4.45.0-cp39-cp39-win32.whl", hash = "sha256:715e18f67f0587a16476c7f62b8ff9a165ddceb8c2a262fb08df9f71c7790f0e"}, - {file = "fonttools-4.45.0-cp39-cp39-win_amd64.whl", hash = "sha256:dd26fda8c879558216458a801c1dba52f35dca0e96640fd9c75e86b6574cf1c3"}, - {file = "fonttools-4.45.0-py3-none-any.whl", hash = "sha256:835cf5d0e1b37bbed1d64c286611cc4da9ff19df952400f191ba9142b3cb97f6"}, - {file = "fonttools-4.45.0.tar.gz", hash = "sha256:c1c79d7d4093396892575115c214b24f09e68997cb5c0ab2d99bfdaff74c64b6"}, + {file = "fonttools-4.45.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:45fa321c458ea29224067700954ec44493ae869b47e7c5485a350a149a19fb53"}, + {file = "fonttools-4.45.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0dc7617d96b1e668eea9250e1c1fe62d0c78c3f69573ce7e3332cc40e6d84356"}, + {file = "fonttools-4.45.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ed3bda541e86725f6b4e1b94213f13ed1ae51a5a1f167028534cedea38c010"}, + {file = "fonttools-4.45.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f4a5870e3b56788fb196da8cf30d0dfd51a76dc3b907861d018165f76ae4c2"}, + {file = "fonttools-4.45.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a3c11d9687479f01eddef729aa737abcdea0a44fdaffb62a930a18892f186c9b"}, + {file = "fonttools-4.45.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:316cec50581e844c3ab69d7c82455b54c7cf18236b2f09e722faf665fbfcac58"}, + {file = "fonttools-4.45.1-cp310-cp310-win32.whl", hash = "sha256:e2277cba9f0b525e30de2a9ad3cb4219aa4bc697230c1645666b0deee9f914f0"}, + {file = "fonttools-4.45.1-cp310-cp310-win_amd64.whl", hash = "sha256:1b9e9ad2bcded9a1431afaa57c8d3c39143ac1f050862d66bddd863c515464a2"}, + {file = "fonttools-4.45.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff6a698bdd435d24c379f6e8a54908cd9bb7dda23719084d56bf8c87709bf3bd"}, + {file = "fonttools-4.45.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c980d60cd6ec1376206fe55013d166e5627ad0b149b5c81e74eaa913ab6134f"}, + {file = "fonttools-4.45.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a12dee6523c02ca78aeedd0a5e12bfa9b7b29896350edd5241542897b072ae23"}, + {file = "fonttools-4.45.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37cd1ced6efb3dd6fe82e9f9bf92fd74ac58a5aefc284045f59ecd517a5fb9ab"}, + {file = "fonttools-4.45.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3d24248221bd7151dfff0d88b1b5da02dccd7134bd576ce8888199827bbaa19"}, + {file = "fonttools-4.45.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba6c23591427844dfb0a13658f1718489de75de6a46b64234584c0d17573162d"}, + {file = "fonttools-4.45.1-cp311-cp311-win32.whl", hash = "sha256:cebcddbe9351b67166292b4f71ffdbfcce01ba4b07d4267824eb46b277aeb19a"}, + {file = "fonttools-4.45.1-cp311-cp311-win_amd64.whl", hash = "sha256:f22eb69996a0bd49f76bdefb30be54ce8dbb89a0d1246874d610f05c2aa2e69e"}, + {file = "fonttools-4.45.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:794de93e83297db7b4943f2431e206d8b1ea69cb3ae14638a49cc50332bf0db8"}, + {file = "fonttools-4.45.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ba17822a6681d06849078daaf6e03eccc9f467efe7c4c60280e28a78e8e5df9"}, + {file = "fonttools-4.45.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e50f794d09df0675da8d9dbd7c66bfcab2f74a708343aabcad41936d26556891"}, + {file = "fonttools-4.45.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b07b857d4f9de3199a8c3d1b1bf2078c0f37447891ca1a8d9234106b9a27aff"}, + {file = "fonttools-4.45.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:777ba42b94a27bb7fb2b4082522fccfd345667c32a56011e1c3e105979af5b79"}, + {file = "fonttools-4.45.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:21e96b99878348c74aa58059b8578d7586f9519cbcdadacf56486737038aa043"}, + {file = "fonttools-4.45.1-cp312-cp312-win32.whl", hash = "sha256:5cbf02cda8465b69769d07385f5d11e7bba19954e7787792f46fe679ec755ebb"}, + {file = "fonttools-4.45.1-cp312-cp312-win_amd64.whl", hash = "sha256:800e354e0c3afaeb8d9552769773d02f228e98c37b8cb03041157c3d0687cffc"}, + {file = "fonttools-4.45.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6eb2c54f7a07c92108daabcf02caf31df97825738db02a28270633946bcda4d0"}, + {file = "fonttools-4.45.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43a3d267334109ff849c37cf3629476b5feb392ef1d2e464a167b83de8cd599c"}, + {file = "fonttools-4.45.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e1aefc2bf3c43e0f33f995f828a7bbeff4adc9393a7760b11456dbcf14388f6"}, + {file = "fonttools-4.45.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f53a19dcdd5737440839b8394eeebb35da9ec8109f7926cb6456639b5b58e47"}, + {file = "fonttools-4.45.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a17706b9cc24b27721613fe5773d93331ab7f0ecaca9955aead89c6b843d3a7"}, + {file = "fonttools-4.45.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fb36e5f40191274a95938b40c0a1fa7f895e36935aea8709e1d6deff0b2d0d4f"}, + {file = "fonttools-4.45.1-cp38-cp38-win32.whl", hash = "sha256:46eabddec12066829b8a1efe45ae552ba2f1796981ecf538d5f68284c354c589"}, + {file = "fonttools-4.45.1-cp38-cp38-win_amd64.whl", hash = "sha256:b6de2f0fcd3302fb82f94801002cb473959e998c14c24ec28234adb674aed345"}, + {file = "fonttools-4.45.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:392d0e3cc23daee910193625f7cf1b387aff9dd5b6f1a5f4a925680acb6dcbc2"}, + {file = "fonttools-4.45.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b9544b1346d99848ac0e9b05b5d45ee703d7562fc4c9c48cf4b781de9632e57"}, + {file = "fonttools-4.45.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8717db3e4895e4820ade64ea379187738827ee60748223cb0438ef044ee208c6"}, + {file = "fonttools-4.45.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e29d5f298d616a93a4c5963682dc6cc8cc09f6d89cad2c29019fc5fb3b4d9472"}, + {file = "fonttools-4.45.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cb472905da3049960e80fc1cf808231880d79727a8410e156bf3e5063a1c574f"}, + {file = "fonttools-4.45.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ba299f1fbaa2a1e33210aaaf6fa816d4059e4d3cfe2ae9871368d4ab548c1c6a"}, + {file = "fonttools-4.45.1-cp39-cp39-win32.whl", hash = "sha256:105099968b58a5b4cef6f3eb409db8ea8578b302a9d05e23fecba1b8b0177b5f"}, + {file = "fonttools-4.45.1-cp39-cp39-win_amd64.whl", hash = "sha256:847f3f49dd3423e5a678c098e2ba92c7f4955d4aab3044f6a507b0bb0ecb07e0"}, + {file = "fonttools-4.45.1-py3-none-any.whl", hash = "sha256:3bdd7dfca8f6c9f4779384064027e8477ad6a037d6a327b09381f43e0247c6f3"}, + {file = "fonttools-4.45.1.tar.gz", hash = "sha256:6e441286d55fe7ec7c4fb36812bf914924813776ff514b744b510680fc2733f2"}, ] [package.extras] @@ -1294,40 +1294,46 @@ files = [ [[package]] name = "ft4222" -version = "1.9.0" +version = "1.10.0" description = "Python wrapper around libFT4222." optional = false python-versions = "*" files = [ - {file = "ft4222-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:f07513c6d0f1f55c198051c8a9f5a58585bfe57d5ea5f9470d9ac149d981e2b4"}, - {file = "ft4222-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d019dd0c170de8772bf444a0a06468a5401411aac303400949612a6636cd9533"}, - {file = "ft4222-1.9.0-cp310-cp310-win32.whl", hash = "sha256:ed7fcc12957bc6041e6064b3cf782181703a8ded5335f85f3392d2a9cd47abaa"}, - {file = "ft4222-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:b23c825b1ec494c79d5276c6a34dc978695dcdd0c198dfa6b763f40d9c87a00a"}, - {file = "ft4222-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:385b39d7b1727800161e86340067d31b58fbe3252e68c0f2aa707d85d2da1d70"}, - {file = "ft4222-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e49945cde97ea5f0b4084c5a5564c5774c3c1ed3e6c31b5324740c21237494d"}, - {file = "ft4222-1.9.0-cp311-cp311-win32.whl", hash = "sha256:7ff367ff0c769f05113a89c35df14461663f40ad6a58b5290a2fddac38d0b2af"}, - {file = "ft4222-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbed0608ae6a2cf0f9c9dafc05ca7b657bfdab991472c9b35707762058ad30db"}, - {file = "ft4222-1.9.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:06222c41efa7a3ebe615ee2fece4a8a8e363335ac1f0afa5d318084d223dda7c"}, - {file = "ft4222-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5ec049a5ce706438edfadb03e21f4892464a9ec47269b62eabbfc782c0923884"}, - {file = "ft4222-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d47d1a06899cc5832e509246816c3b20eba549e3d02789e138dd51f7e1a28060"}, - {file = "ft4222-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:34d9eca521648135c48058b0c6bfd3de34a0dd05783f387da79201117d8a26f8"}, - {file = "ft4222-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:0bfabe4e46aaa2e5aa88ef77bb75b755ca2f3633eb9cd34654a8a6b30a151559"}, - {file = "ft4222-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7da01c5e872fd8b82cbb159697b83f31ae5b2516f90615fc3a66026dd3aed5c0"}, - {file = "ft4222-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7bb3be2ac0d21e7063aad1c10615bc29369132bb9d5f800fc385ea805ef16583"}, - {file = "ft4222-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4c2b0c4ba7c621d7a65708fbfa981f7ed34c966e089e7965d6040f318a43221d"}, - {file = "ft4222-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0858024c615c495a015fddb81df449f3f7763a6defb44e426afb23ae80faf4d1"}, - {file = "ft4222-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5375836b967b905146238b2e067936a017a5ec51a8932d950476c1c06b9da87b"}, - {file = "ft4222-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2381aab272200046ee61ec97d2a657d3f03eed2e8270141d5c5617c9ad7cb659"}, - {file = "ft4222-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b25b30f6b81afab04b763a93b604593d6f1e97b7737c40d41949fa8e61ffda17"}, - {file = "ft4222-1.9.0-cp39-cp39-win32.whl", hash = "sha256:77fe7d5eb9b53f1d05b9a0d6f6ea1b62a55b95330b48aa24c9f6c8157220fc2f"}, - {file = "ft4222-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:cd291d6542a2bcb29ab35eba67291680a666ee88ece66d38ab5922dea7f41259"}, - {file = "ft4222-1.9.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:80b7a7b2f7937e8442a6d73aa231676bd6b497cd82a1f02b8a5a29dd28deff8b"}, - {file = "ft4222-1.9.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d06d3ca1f5208abafaa7106854c12d142bbde6f1ca5212590b8859bf9908301a"}, - {file = "ft4222-1.9.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ea756d097863a9afde576ba8a496f702abc1584743c56b17a002d13ac2f0fe99"}, - {file = "ft4222-1.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:2d795f77c2481a6565ca4726038db0abd6503f5db3ef2d0f52da74f7e1a12edd"}, - {file = "ft4222-1.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c41137e64fde5f43263d738c113238196ef16a42e2aa7960d1451e4bed3281f9"}, - {file = "ft4222-1.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a16ca00027dec92080994031d2a342966e618ea0f4d860c550d6b4b89d534aba"}, - {file = "ft4222-1.9.0.tar.gz", hash = "sha256:0d9fe9bc2537b7dd62b06b45e91f362be9f550a7101f71482f954678fe2d1b0f"}, + {file = "ft4222-1.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd003a91ecaf20bc4d4ce8f0aea4a5ecfbc17a3840d41b0ce27410e027530cd0"}, + {file = "ft4222-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1cedabcd6c554e78f3479350153c5a347355ccc977ceeb24726edc37d3e7edb7"}, + {file = "ft4222-1.10.0-cp310-cp310-win32.whl", hash = "sha256:a039d4230926fa9b600baa12903843c53d6b238eb93e99ae68eeeb450d6f4bdc"}, + {file = "ft4222-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:b05e499fd3f1baef225671f3afd25bf93e1f287fa17326c90c1f8db70abb3e89"}, + {file = "ft4222-1.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df9da4d5575d51511ba58e73ee434061dc709ce69491656ec109d995ce304167"}, + {file = "ft4222-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20f2cb89e63ea755d69a4d41c2dcba94d2f720b6e736ea51bdf681f1e809763c"}, + {file = "ft4222-1.10.0-cp311-cp311-win32.whl", hash = "sha256:806d4117d0814c390c7f280345aa99168ec989ca49b20259ce593ea79bcd246f"}, + {file = "ft4222-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:ad626507e9b1520f2f3b6f98ddca0d257c93b844824a1ab7cbb58759b0fbca84"}, + {file = "ft4222-1.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ecaae4e5ec3bedcdbe04a5792bf08119040669cf58c02df947395e6a96bd18c"}, + {file = "ft4222-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8c2655fe7192eb80ef25ab750123139f591e0f079fe2a5e6f31b7cd6f015a9f"}, + {file = "ft4222-1.10.0-cp312-cp312-win32.whl", hash = "sha256:fe668caa43bcacfbf3287cff8b4b11bb586a71d4722307e574f60180bb4a351e"}, + {file = "ft4222-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:4d7a49febc2b96ebed4b843262ab1f00843d158d6f4877c8ee19219f1c41f0da"}, + {file = "ft4222-1.10.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a6f99e31c767b6ccb7e2f0e7f6cc51d15c9b685e2b81fd12ab08cac0924289"}, + {file = "ft4222-1.10.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83efcb50649a7ab4c7e045467357029f16ef0be064d46ffe7ea1e06eb9ad85f8"}, + {file = "ft4222-1.10.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e051c89cb45f964640e355506efa74b41c424e9ea9046367534180075be0a95c"}, + {file = "ft4222-1.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cae991a917602635238c144395bb59a516b7684b6599da251a88cfe37a5ed8f"}, + {file = "ft4222-1.10.0-cp37-cp37m-win32.whl", hash = "sha256:713ed85fbb8f11755ae7ed932302192a34d2aa8834ffa45a7c345d3a5ba8f67b"}, + {file = "ft4222-1.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:6f2458e6c62d251b6219cfa0fbcfb9ccfa22854c5d7463b862f11b268019ec66"}, + {file = "ft4222-1.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4b489cd434b92748e45695c1313716db4e00bdda4f70b6bcf99cdc042a34d8"}, + {file = "ft4222-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0bd0632063b0e4bb2852ccde3fb95252ea7f0a0bfd3e4c3ee677f4ec63c2520"}, + {file = "ft4222-1.10.0-cp38-cp38-win32.whl", hash = "sha256:057c88eaa2db14a05163b90a7ba2938c0aa6e3870fec31c70051fb791cfbbc22"}, + {file = "ft4222-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:648a739bb5c0ca53b5cb0698e0ebb54ddaea090878a02cbe9039b3d80cf818ba"}, + {file = "ft4222-1.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88170a0aaa321ae8c974a664b2316fab00f86dd8249ff5927ea4d58a2ed7fa23"}, + {file = "ft4222-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69fa55d8b5bc7da5a20254d480d4764dbcaa1ca74cd2d10bbc0ce39d39891f2a"}, + {file = "ft4222-1.10.0-cp39-cp39-win32.whl", hash = "sha256:9eb40e86fd43ab8d12b33d092369419e275292a6d57f1ef7de30e63cd8dc9bd5"}, + {file = "ft4222-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:875478f2c8c0fa128037c389969375ede546dde8cf7a5ae796421832aa6dd634"}, + {file = "ft4222-1.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:834b9e549d66f3cc0bbe75983b32e756258523b70363bb67970a7be21c9e3d8f"}, + {file = "ft4222-1.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7af52e4d61e2bcaa10b802e6f1af29823da12a27f520c799823490e6fd9ebb19"}, + {file = "ft4222-1.10.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca8c5aff74e213d8b658e7cdd8df3012e55023a2d379b5074628f6bb8947359a"}, + {file = "ft4222-1.10.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:755a70ab2c5d3fd94030e51fd82c332bc2463c6c455adf093d12e2f3803ea207"}, + {file = "ft4222-1.10.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:035ea7fc9ad627b44be57c31e87bc8d445ebd7cf6ac991d750fedc999683d64e"}, + {file = "ft4222-1.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2df65d174200c1451f5646a109e683aada9d7a076bf5f7ce682145d10725445a"}, + {file = "ft4222-1.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99ef3c412017a85ef9f90d8fc884ba66dae71e85433c8890da6aec38eb0288ab"}, + {file = "ft4222-1.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d00b36bdac3960c377f3a625fc352eca20e36825ddff178feaae97c2d9a8846c"}, + {file = "ft4222-1.10.0.tar.gz", hash = "sha256:74eab32dfc5c012d11952c8645b72157f8828685972669ef3c22025491d96001"}, ] [[package]] @@ -1593,13 +1599,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] @@ -2421,38 +2427,38 @@ files = [ [[package]] name = "mypy" -version = "1.7.0" +version = "1.7.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"}, - {file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"}, - {file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"}, - {file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"}, - {file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"}, - {file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"}, - {file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"}, - {file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"}, - {file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"}, - {file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"}, - {file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"}, - {file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"}, - {file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"}, - {file = "mypy-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cddee95dea7990e2215576fae95f6b78a8c12f4c089d7e4367564704e99118d3"}, - {file = "mypy-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d01921dbd691c4061a3e2ecdbfbfad029410c5c2b1ee88946bf45c62c6c91210"}, - {file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"}, - {file = "mypy-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7b1e399c47b18feb6f8ad4a3eef3813e28c1e871ea7d4ea5d444b2ac03c418"}, - {file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"}, - {file = "mypy-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d0fa29919d2e720c8dbaf07d5578f93d7b313c3e9954c8ec05b6d83da592e5d9"}, - {file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"}, - {file = "mypy-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1b06b4b109e342f7dccc9efda965fc3970a604db70f8560ddfdee7ef19afb05"}, - {file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"}, - {file = "mypy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551d4a0cdcbd1d2cccdcc7cb516bb4ae888794929f5b040bb51aae1846062901"}, - {file = "mypy-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55d28d7963bef00c330cb6461db80b0b72afe2f3c4e2963c99517cf06454e665"}, - {file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"}, - {file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"}, - {file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"}, + {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, + {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, + {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, + {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, + {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, + {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, + {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, + {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, + {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, + {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, + {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, + {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, + {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, + {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, + {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, + {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, + {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, + {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, + {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, + {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, + {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, + {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, + {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, ] [package.dependencies] @@ -3008,13 +3014,13 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.11.0" +version = "4.0.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, - {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, + {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, + {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, ] [package.extras] @@ -3294,20 +3300,20 @@ files = [ [[package]] name = "pyee" -version = "11.0.1" +version = "11.1.0" description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" optional = false python-versions = ">=3.8" files = [ - {file = "pyee-11.0.1-py3-none-any.whl", hash = "sha256:9bcc9647822234f42c228d88de63d0f9ffa881e87a87f9d36ddf5211f6ac977d"}, - {file = "pyee-11.0.1.tar.gz", hash = "sha256:a642c51e3885a33ead087286e35212783a4e9b8d6514a10a5db4e57ac57b2b29"}, + {file = "pyee-11.1.0-py3-none-any.whl", hash = "sha256:5d346a7d0f861a4b2e6c47960295bd895f816725b27d656181947346be98d7c1"}, + {file = "pyee-11.1.0.tar.gz", hash = "sha256:b53af98f6990c810edd9b56b87791021a8f54fd13db4edd1142438d44ba2263f"}, ] [package.dependencies] typing-extensions = "*" [package.extras] -dev = ["black", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"] +dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "sphinx", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"] [[package]] name = "pygame" @@ -3377,13 +3383,13 @@ files = [ [[package]] name = "pygments" -version = "2.17.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "pygments-2.17.1-py3-none-any.whl", hash = "sha256:1b37f1b1e1bff2af52ecaf28cc601e2ef7077000b227a0675da25aef85784bc4"}, - {file = "pygments-2.17.1.tar.gz", hash = "sha256:e45a0e74bf9c530f564ca81b8952343be986a29f6afe7f5ad95c5f06b7bdf5e8"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] @@ -3797,13 +3803,13 @@ pytest = ">=3.1" [[package]] name = "pytest-xdist" -version = "3.4.0" +version = "3.5.0" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-xdist-3.4.0.tar.gz", hash = "sha256:3a94a931dd9e268e0b871a877d09fe2efb6175c2c23d60d56a6001359002b832"}, - {file = "pytest_xdist-3.4.0-py3-none-any.whl", hash = "sha256:e513118bf787677a427e025606f55e95937565e06dfaac8d87f55301e57ae607"}, + {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"}, + {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"}, ] [package.dependencies] @@ -4333,13 +4339,13 @@ test = ["pytest"] [[package]] name = "setuptools" -version = "69.0.1" +version = "69.0.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.1-py3-none-any.whl", hash = "sha256:6875bbd06382d857b1b90cd07cee6a2df701a164f241095706b5192bc56c5c62"}, - {file = "setuptools-69.0.1.tar.gz", hash = "sha256:f25195d54deb649832182d6455bffba7ac3d8fe71d35185e738d2198a4310044"}, + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, ] [package.extras] @@ -4861,19 +4867,19 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.6" +version = "20.24.7" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, - {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, + {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, + {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<4" +platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] From 6f2af97381d030fca96ad1f66c13311f6a870bee Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 26 Nov 2023 23:18:27 -0800 Subject: [PATCH 076/724] Bump submodules (#30533) bump submodules Co-authored-by: sshane --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 6eb89cefdf..5884962372 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 6eb89cefdfdbcb80efef02f121beb6d02867a106 +Subproject commit 58849623721d32f18cd9139cab2c71a1d8f10e4d From e0ab068273a4726e8634c7a3a4ab5cfecd29e730 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 27 Nov 2023 09:40:37 -0800 Subject: [PATCH 077/724] Revert "CI: automatically create PR to bump submodules (#30523)" This reverts commit 6f073aed5f412a3571135faebf123b952aedb95c. --- .github/workflows/repo-maintenance.yaml | 31 +++---------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 1e6f6366ad..66e252bf83 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -2,36 +2,12 @@ name: repo maintenance on: schedule: - - cron: "0 15 * * *" + - cron: "0 15 * * 2" workflow_dispatch: jobs: - bump_submodules: - name: bump_submodules - runs-on: ubuntu-20.04 - container: - image: ghcr.io/commaai/openpilot-base:latest - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: bump submodules - run: | - git config --global --add safe.directory '*' - git -c submodule."tinygrad".update=none submodule update --remote - git add . - - name: Create Pull Request - uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 - with: - token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} - commit-message: bump submodules - title: 'Bump submodules' - branch: auto-bump-submodules - base: master - delete-branch: true - body: 'Automatic PR from repo-maintenance -> bump_submodules' - package_updates: - name: package_updates + updates: + name: updates runs-on: ubuntu-20.04 container: image: ghcr.io/commaai/openpilot-base:latest @@ -54,4 +30,3 @@ jobs: branch: auto-package-updates base: master delete-branch: true - body: 'Automatic PR from repo-maintenance -> package_updates' \ No newline at end of file From ae4e848285d538c03056089ddd87a08da8152928 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 27 Nov 2023 09:57:37 -0800 Subject: [PATCH 078/724] Update Python packages and pre-commit hooks (#30534) Co-authored-by: jnewb1 --- poetry.lock | 154 ++++++++++++++++++++++++++-------------------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/poetry.lock b/poetry.lock index 72edd944c7..b05dcae11e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,87 +2,87 @@ [[package]] name = "aiohttp" -version = "3.9.0" +version = "3.9.1" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6896b8416be9ada4d22cd359d7cb98955576ce863eadad5596b7cdfbf3e17c6c"}, - {file = "aiohttp-3.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1736d87dad8ef46a8ec9cddd349fa9f7bd3a064c47dd6469c0d6763d3d49a4fc"}, - {file = "aiohttp-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c9e5f4d7208cda1a2bb600e29069eecf857e6980d0ccc922ccf9d1372c16f4b"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8488519aa05e636c5997719fe543c8daf19f538f4fa044f3ce94bee608817cff"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ab16c254e2312efeb799bc3c06897f65a133b38b69682bf75d1f1ee1a9c43a9"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a94bde005a8f926d0fa38b88092a03dea4b4875a61fbcd9ac6f4351df1b57cd"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b777c9286b6c6a94f50ddb3a6e730deec327e9e2256cb08b5530db0f7d40fd8"}, - {file = "aiohttp-3.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:571760ad7736b34d05597a1fd38cbc7d47f7b65deb722cb8e86fd827404d1f6b"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:deac0a32aec29608eb25d730f4bc5a261a65b6c48ded1ed861d2a1852577c932"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4ee1b4152bc3190cc40ddd6a14715e3004944263ea208229ab4c297712aa3075"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:3607375053df58ed6f23903aa10cf3112b1240e8c799d243bbad0f7be0666986"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:65b0a70a25456d329a5e1426702dde67be0fb7a4ead718005ba2ca582d023a94"}, - {file = "aiohttp-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a2eb5311a37fe105aa35f62f75a078537e1a9e4e1d78c86ec9893a3c97d7a30"}, - {file = "aiohttp-3.9.0-cp310-cp310-win32.whl", hash = "sha256:2cbc14a13fb6b42d344e4f27746a4b03a2cb0c1c3c5b932b0d6ad8881aa390e3"}, - {file = "aiohttp-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac9669990e2016d644ba8ae4758688534aabde8dbbc81f9af129c3f5f01ca9cd"}, - {file = "aiohttp-3.9.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f8e05f5163528962ce1d1806fce763ab893b1c5b7ace0a3538cd81a90622f844"}, - {file = "aiohttp-3.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4afa8f71dba3a5a2e1e1282a51cba7341ae76585345c43d8f0e624882b622218"}, - {file = "aiohttp-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f929f4c9b9a00f3e6cc0587abb95ab9c05681f8b14e0fe1daecfa83ea90f8318"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28185e36a78d247c55e9fbea2332d16aefa14c5276a582ce7a896231c6b1c208"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a486ddf57ab98b6d19ad36458b9f09e6022de0381674fe00228ca7b741aacb2f"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70e851f596c00f40a2f00a46126c95c2e04e146015af05a9da3e4867cfc55911"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5b7bf8fe4d39886adc34311a233a2e01bc10eb4e842220235ed1de57541a896"}, - {file = "aiohttp-3.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c67a51ea415192c2e53e4e048c78bab82d21955b4281d297f517707dc836bf3d"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:694df243f394629bcae2d8ed94c589a181e8ba8604159e6e45e7b22e58291113"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3dd8119752dd30dd7bca7d4bc2a92a59be6a003e4e5c2cf7e248b89751b8f4b7"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:eb6dfd52063186ac97b4caa25764cdbcdb4b10d97f5c5f66b0fa95052e744eb7"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d97c3e286d0ac9af6223bc132dc4bad6540b37c8d6c0a15fe1e70fb34f9ec411"}, - {file = "aiohttp-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:816f4db40555026e4cdda604a1088577c1fb957d02f3f1292e0221353403f192"}, - {file = "aiohttp-3.9.0-cp311-cp311-win32.whl", hash = "sha256:3abf0551874fecf95f93b58f25ef4fc9a250669a2257753f38f8f592db85ddea"}, - {file = "aiohttp-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:e18d92c3e9e22553a73e33784fcb0ed484c9874e9a3e96c16a8d6a1e74a0217b"}, - {file = "aiohttp-3.9.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:99ae01fb13a618b9942376df77a1f50c20a281390dad3c56a6ec2942e266220d"}, - {file = "aiohttp-3.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:05857848da443c8c12110d99285d499b4e84d59918a21132e45c3f0804876994"}, - {file = "aiohttp-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:317719d7f824eba55857fe0729363af58e27c066c731bc62cd97bc9c3d9c7ea4"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1e3b3c107ccb0e537f309f719994a55621acd2c8fdf6d5ce5152aed788fb940"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45820ddbb276113ead8d4907a7802adb77548087ff5465d5c554f9aa3928ae7d"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a183f1978802588711aed0dea31e697d760ce9055292db9dc1604daa9a8ded"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a4cd44788ea0b5e6bb8fa704597af3a30be75503a7ed1098bc5b8ffdf6c982"}, - {file = "aiohttp-3.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673343fbc0c1ac44d0d2640addc56e97a052504beacd7ade0dc5e76d3a4c16e8"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e8a3b79b6d186a9c99761fd4a5e8dd575a48d96021f220ac5b5fa856e5dd029"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6777a390e41e78e7c45dab43a4a0196c55c3b8c30eebe017b152939372a83253"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7ae5f99a32c53731c93ac3075abd3e1e5cfbe72fc3eaac4c27c9dd64ba3b19fe"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f1e4f254e9c35d8965d377e065c4a8a55d396fe87c8e7e8429bcfdeeb229bfb3"}, - {file = "aiohttp-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11ca808f9a6b63485059f5f6e164ef7ec826483c1212a44f268b3653c91237d8"}, - {file = "aiohttp-3.9.0-cp312-cp312-win32.whl", hash = "sha256:de3cc86f4ea8b4c34a6e43a7306c40c1275e52bfa9748d869c6b7d54aa6dad80"}, - {file = "aiohttp-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca4fddf84ac7d8a7d0866664936f93318ff01ee33e32381a115b19fb5a4d1202"}, - {file = "aiohttp-3.9.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f09960b5bb1017d16c0f9e9f7fc42160a5a49fa1e87a175fd4a2b1a1833ea0af"}, - {file = "aiohttp-3.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8303531e2c17b1a494ffaeba48f2da655fe932c4e9a2626c8718403c83e5dd2b"}, - {file = "aiohttp-3.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4790e44f46a4aa07b64504089def5744d3b6780468c4ec3a1a36eb7f2cae9814"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1d7edf74a36de0e5ca50787e83a77cf352f5504eb0ffa3f07000a911ba353fb"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94697c7293199c2a2551e3e3e18438b4cba293e79c6bc2319f5fd652fccb7456"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1b66dbb8a7d5f50e9e2ea3804b01e766308331d0cac76eb30c563ac89c95985"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9623cfd9e85b76b83ef88519d98326d4731f8d71869867e47a0b979ffec61c73"}, - {file = "aiohttp-3.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f32c86dc967ab8c719fd229ce71917caad13cc1e8356ee997bf02c5b368799bf"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f50b4663c3e0262c3a361faf440761fbef60ccdde5fe8545689a4b3a3c149fb4"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dcf71c55ec853826cd70eadb2b6ac62ec577416442ca1e0a97ad875a1b3a0305"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:42fe4fd9f0dfcc7be4248c162d8056f1d51a04c60e53366b0098d1267c4c9da8"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76a86a9989ebf82ee61e06e2bab408aec4ea367dc6da35145c3352b60a112d11"}, - {file = "aiohttp-3.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f9e09a1c83521d770d170b3801eea19b89f41ccaa61d53026ed111cb6f088887"}, - {file = "aiohttp-3.9.0-cp38-cp38-win32.whl", hash = "sha256:a00ce44c21612d185c5275c5cba4bab8d7c1590f248638b667ed8a782fa8cd6f"}, - {file = "aiohttp-3.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:d5b9345ab92ebe6003ae11d8092ce822a0242146e6fa270889b9ba965457ca40"}, - {file = "aiohttp-3.9.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98d21092bf2637c5fa724a428a69e8f5955f2182bff61f8036827cf6ce1157bf"}, - {file = "aiohttp-3.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:35a68cd63ca6aaef5707888f17a70c36efe62b099a4e853d33dc2e9872125be8"}, - {file = "aiohttp-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7f6235c7475658acfc1769d968e07ab585c79f6ca438ddfecaa9a08006aee2"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db04d1de548f7a62d1dd7e7cdf7c22893ee168e22701895067a28a8ed51b3735"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:536b01513d67d10baf6f71c72decdf492fb7433c5f2f133e9a9087379d4b6f31"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c8b0a6487e8109427ccf638580865b54e2e3db4a6e0e11c02639231b41fc0f"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7276fe0017664414fdc3618fca411630405f1aaf0cc3be69def650eb50441787"}, - {file = "aiohttp-3.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23170247ef89ffa842a02bbfdc425028574d9e010611659abeb24d890bc53bb8"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b1a2ea8252cacc7fd51df5a56d7a2bb1986ed39be9397b51a08015727dfb69bd"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d71abc15ff7047412ef26bf812dfc8d0d1020d664617f4913df2df469f26b76"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:2d820162c8c2bdbe97d328cd4f417c955ca370027dce593345e437b2e9ffdc4d"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:2779f5e7c70f7b421915fd47db332c81de365678180a9f3ab404088f87ba5ff9"}, - {file = "aiohttp-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:366bc870d7ac61726f32a489fbe3d1d8876e87506870be66b01aeb84389e967e"}, - {file = "aiohttp-3.9.0-cp39-cp39-win32.whl", hash = "sha256:1df43596b826022b14998f0460926ce261544fedefe0d2f653e1b20f49e96454"}, - {file = "aiohttp-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:9c196b30f1b1aa3363a69dd69079ae9bec96c2965c4707eaa6914ba099fb7d4f"}, - {file = "aiohttp-3.9.0.tar.gz", hash = "sha256:09f23292d29135025e19e8ff4f0a68df078fe4ee013bca0105b2e803989de92d"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, + {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, + {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, + {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, + {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, + {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, + {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, + {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, + {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, + {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, + {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, + {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, + {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, + {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, + {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, + {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, + {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, + {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, + {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, + {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, + {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, + {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, + {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, + {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, + {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, + {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, + {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, ] [package.dependencies] From d2d4802ad034a19dd34d5c0cf0c432912bc306b9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 27 Nov 2023 14:57:40 -0800 Subject: [PATCH 079/724] remove extra sleep on startup --- launch_chffrplus.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 9fe9b1bd15..a91bd677aa 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -9,11 +9,6 @@ source "$BASEDIR/launch_env.sh" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" function agnos_init { - # wait longer for weston to come up - if [ -f "$BASEDIR/prebuilt" ]; then - sleep 3 - fi - # TODO: move this to agnos sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta From 749d192f5427cb1798d4edf0c63c7aadf69ec617 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 27 Nov 2023 16:36:26 -0800 Subject: [PATCH 080/724] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 5884962372..e6fadef4dd 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 58849623721d32f18cd9139cab2c71a1d8f10e4d +Subproject commit e6fadef4ddccb9e665fbdf2d377f12879e5af07a From 7a387962c66173fd195152007e279f73e020a6a1 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 27 Nov 2023 17:09:32 -0800 Subject: [PATCH 081/724] CI: automatically create PR for submodule bump (#30538) * add job * add job (#52) * add body * Bump submodules (#53) * add job * add body * add . * rm that * need submodules too * better message * monday at 4am * i thought i was still est lol --- .github/workflows/repo-maintenance.yaml | 31 ++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 66e252bf83..1f14895e21 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -2,12 +2,36 @@ name: repo maintenance on: schedule: - - cron: "0 15 * * 2" + - cron: "0 12 * * 1" # every Monday at 12am UTC (4am PST) workflow_dispatch: jobs: - updates: - name: updates + bump_submodules: + name: bump_submodules + runs-on: ubuntu-20.04 + container: + image: ghcr.io/commaai/openpilot-base:latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: bump submodules + run: | + git config --global --add safe.directory '*' + git -c submodule."tinygrad".update=none submodule update --remote + git add . + - name: Create Pull Request + uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5 + with: + token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} + commit-message: bump submodules + title: 'Bump submodules' + branch: auto-bump-submodules + base: master + delete-branch: true + body: 'Automatic PR from repo-maintenance -> bump_submodules' + package_updates: + name: package_updates runs-on: ubuntu-20.04 container: image: ghcr.io/commaai/openpilot-base:latest @@ -30,3 +54,4 @@ jobs: branch: auto-package-updates base: master delete-branch: true + body: 'Automatic PR from repo-maintenance -> package_updates' From 2f5c0bd01661972046af1d6dc1bd0228c9d056f1 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:19:03 -0500 Subject: [PATCH 082/724] JEEP_CHEROKEE -> JEEP_GRAND_CHEROKEE (#30539) --- selfdrive/car/chrysler/interface.py | 4 ++-- selfdrive/car/chrysler/values.py | 18 +++++++++--------- selfdrive/car/tests/routes.py | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index fd030e3ec6..10cdd0fd14 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -28,7 +28,7 @@ class CarInterface(CarInterfaceBase): CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) if candidate not in RAM_CARS: # Newer FW versions standard on the following platforms, or flashed by a dealer onto older platforms have a higher minimum steering speed. - new_eps_platform = candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019) + new_eps_platform = candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_GRAND_CHEROKEE_2019) new_eps_firmware = any(fw.ecu == 'eps' and fw.fwVersion[:4] >= b"6841" for fw in car_fw) if new_eps_platform or new_eps_firmware: ret.flags |= ChryslerFlags.HIGHER_MIN_STEERING_SPEED.value @@ -45,7 +45,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.00006 # Jeep - elif candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019): + elif candidate in (CAR.JEEP_GRAND_CHEROKEE, CAR.JEEP_GRAND_CHEROKEE_2019): ret.mass = 1778 ret.wheelbase = 2.71 ret.steerRatio = 16.7 diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index af12c60a75..9082829995 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -25,8 +25,8 @@ class CAR(StrEnum): PACIFICA_2020 = "CHRYSLER PACIFICA 2020" # Jeep - JEEP_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk - JEEP_CHEROKEE_2019 = "JEEP GRAND CHEROKEE 2019" # includes 2020 Trailhawk + JEEP_GRAND_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk + JEEP_GRAND_CHEROKEE_2019 = "JEEP GRAND CHEROKEE 2019" # includes 2020 Trailhawk # Ram RAM_1500 = "RAM 1500 5TH GEN" @@ -73,8 +73,8 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { ChryslerCarInfo("Chrysler Pacifica 2019-20"), ChryslerCarInfo("Chrysler Pacifica 2021", package="All"), ], - CAR.JEEP_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), - CAR.JEEP_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), + CAR.JEEP_GRAND_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), + CAR.JEEP_GRAND_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-23", car_parts=CarParts.common([CarHarness.ram])), CAR.RAM_HD: [ ChryslerCarInfo("Ram 2500 2020-22", car_parts=CarParts.common([CarHarness.ram])), @@ -131,14 +131,14 @@ FINGERPRINTS = { { 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 450: 8, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 2018: 8, 2020: 8, 2026: 8, 2028: 8 }], - CAR.JEEP_CHEROKEE: [{ + CAR.JEEP_GRAND_CHEROKEE: [{ 55: 8, 168: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 975: 8, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1543: 8, 1562: 8, 1576: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }, # Based on c88f65eeaee4003a|2022-08-04--15-37-16 { 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 658: 6, 660: 8, 671: 8, 672: 8, 678: 8, 680: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 783: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 844: 5, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1235: 8, 1242: 8, 1252: 8, 1792: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1892: 8, 1927: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], - CAR.JEEP_CHEROKEE_2019: [{ + CAR.JEEP_GRAND_CHEROKEE_2019: [{ # Jeep Grand Cherokee 2019, including most 2020 models 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1538: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], @@ -185,7 +185,7 @@ FW_QUERY_CONFIG = FwQueryConfig( ) FW_VERSIONS = { - CAR.JEEP_CHEROKEE_2019: { + CAR.JEEP_GRAND_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ b'68402971AD', ], @@ -383,8 +383,8 @@ DBC = { CAR.PACIFICA_2020: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), CAR.PACIFICA_2018_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), CAR.PACIFICA_2019_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), - CAR.JEEP_CHEROKEE: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), - CAR.JEEP_CHEROKEE_2019: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.JEEP_GRAND_CHEROKEE: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.JEEP_GRAND_CHEROKEE_2019: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), CAR.RAM_1500: dbc_dict('chrysler_ram_dt_generated', None), CAR.RAM_HD: dbc_dict('chrysler_ram_hd_generated', None), } diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index dc9fa32b97..4d16a0a1bc 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -38,8 +38,8 @@ class CarTestRoute(NamedTuple): routes = [ CarTestRoute("efdf9af95e71cd84|2022-05-13--19-03-31", COMMA.BODY), - CarTestRoute("0c94aa1e1296d7c6|2021-05-05--19-48-37", CHRYSLER.JEEP_CHEROKEE), - CarTestRoute("91dfedae61d7bd75|2021-05-22--20-07-52", CHRYSLER.JEEP_CHEROKEE_2019), + 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.PACIFICA_2017_HYBRID), CarTestRoute("43a685a66291579b|2021-05-27--19-47-29", CHRYSLER.PACIFICA_2018), CarTestRoute("378472f830ee7395|2021-05-28--07-38-43", CHRYSLER.PACIFICA_2018_HYBRID), From 07e75c7cb8337c7ca6aad0234cfdbf4c5327ba70 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 28 Nov 2023 11:43:01 -0800 Subject: [PATCH 083/724] athena: remove primeActivated endpoint (#30542) --- selfdrive/athena/athenad.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index c93b434677..eb61c6dd58 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -455,11 +455,6 @@ def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]] return {"success": 1} -@dispatcher.add_method -def primeActivated(activated: bool) -> Dict[str, int]: - return {"success": 1} - - @dispatcher.add_method def setBandwithLimit(upload_speed_kbps: int, download_speed_kbps: int) -> Dict[str, Union[int, str]]: if not AGNOS: From a7d3eb87c7794210cac7533004f9ec4824741081 Mon Sep 17 00:00:00 2001 From: Hoya Date: Wed, 29 Nov 2023 07:01:00 +0900 Subject: [PATCH 084/724] Kia: Add missing fwdCamera for KIA_CARNIVAL_4TH_GEN (#30540) * Update values.py Dongle ID : f9ec55916091ba61 RouteName : f9ec55916091ba61_2023-11-28--13-53-02 KIA_CARNIVAL_4TH_GEN finger need to be added Thanks * Update values.py * Update values.py * Update values.py * Update values.py * Update values.py * Update selfdrive/car/hyundai/values.py --------- Co-authored-by: Shane Smiskol --- selfdrive/car/hyundai/values.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 2d64d7a100..0a0f66eabd 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -2067,6 +2067,7 @@ FW_VERSIONS = { b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221', b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', + b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', From 0b41d1c6e8724e5d81e836462090a87978a43015 Mon Sep 17 00:00:00 2001 From: Vincent Wright Date: Tue, 28 Nov 2023 17:17:20 -0500 Subject: [PATCH 085/724] Chrysler: add Ram HD 2500 2024 FW versions (#30505) * New Ram HD firmwares. Dongle ID is 0f88f104416b674a. * docs --------- Co-authored-by: Shane Smiskol --- selfdrive/car/chrysler/values.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 9082829995..cb1aa4a880 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -77,7 +77,7 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { CAR.JEEP_GRAND_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-23", car_parts=CarParts.common([CarHarness.ram])), CAR.RAM_HD: [ - ChryslerCarInfo("Ram 2500 2020-22", car_parts=CarParts.common([CarHarness.ram])), + ChryslerCarInfo("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])), ChryslerCarInfo("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])), ], } @@ -331,6 +331,7 @@ FW_VERSIONS = { b'68525487AB', b'68525498AB', b'68528791AF', + b'68628474AB', ], (Ecu.srs, 0x744, None): [ b'68399794AC', @@ -347,6 +348,7 @@ FW_VERSIONS = { b'68530686AB', b'68530686AC', b'68544596AC', + b'68641704AA', ], (Ecu.fwdRadar, 0x753, None): [ b'04672895AB', @@ -356,6 +358,7 @@ FW_VERSIONS = { b'68462657AE', b'68484694AD', b'68484694AE', + b'68615489AB', ], (Ecu.eps, 0x761, None): [ b'68421036AC', @@ -366,6 +369,7 @@ FW_VERSIONS = { b'52370131AF', b'52370231AF', b'52370231AG', + b'52370491AA', b'52370931CT', b'52401032AE', b'52421132AF', From 5030824572c90862dbe0acca2b095b3899311e39 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 28 Nov 2023 14:34:00 -0800 Subject: [PATCH 086/724] jenkins auto cpu count (#30544) * auto cpu * build too * need bash --- Jenkinsfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5859920a56..3dd9c45f14 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -225,15 +225,17 @@ node { pcStage("PC tests") { // tests that our build system's dependencies are configured properly, // needs a machine with lots of cores - sh label: "test multi-threaded build", script: "scons --no-cache --random -j42" + sh label: "test multi-threaded build", + script: '''#!/bin/bash + scons --no-cache --random -j$(nproc)''' } }, 'car tests': { pcStage("car tests") { sh label: "build", script: "selfdrive/manager/build.py" sh label: "test_models.py", script: "INTERNAL_SEG_CNT=250 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \ - pytest -n42 --dist=loadscope selfdrive/car/tests/test_models.py" - sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n42 --dist=load selfdrive/car/tests/test_car_interfaces.py" + pytest -n auto --dist=loadscope selfdrive/car/tests/test_models.py" + sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n auto --dist=load selfdrive/car/tests/test_car_interfaces.py" } }, From c56c8a719c4e7e19d365d6465801651f4c0e56c8 Mon Sep 17 00:00:00 2001 From: Keen-coffee <147879855+Keen-coffee@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:40:04 -0700 Subject: [PATCH 087/724] Add 2022 Maverick engine and abs fingerprint (#30467) --- selfdrive/car/ford/values.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index d11ba4ccf0..133038809f 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -254,6 +254,7 @@ FW_VERSIONS = { (Ecu.abs, 0x760, None): [ b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PZ6C-2D053-ED\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ6C-2D053-EE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -265,6 +266,7 @@ FW_VERSIONS = { b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NZ6A-14C204-ZC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PZ6A-14C204-BE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PZ6A-14C204-JC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PZ6A-14C204-JE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', From 2e45a7c2fd41ddf9099fb0cd013de15b3f2022df Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 28 Nov 2023 16:55:03 -0600 Subject: [PATCH 088/724] Toyota: add test route for 2023 RAV4 (#30465) * test rav4 2023 (stripped) * don't use initData, remove relay malfunction tolerance! * all passes! * some clean up * more * uncomment * rm line * remove ignore_addr_checks_valid * right * some changes for stripping data * clean up * check exist * shouldn't be possible * should be success if here * no delete * "" * rm * rm * cleanup * one list comp * more clear * not used * add back legit source * clean up * copy files we don't need to strip (need sas to copy between accounts) * default to strip * revert --- selfdrive/car/tests/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 4d16a0a1bc..9802524886 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -24,7 +24,6 @@ non_tested_cars = [ HYUNDAI.GENESIS_G90, HONDA.ODYSSEY_CHN, VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter - TOYOTA.RAV4_TSS2_2023, SUBARU.FORESTER_HYBRID, ] @@ -185,6 +184,7 @@ routes = [ CarTestRoute("32a7df20486b0f70|2020-02-06--16-06-50", TOYOTA.RAV4H), CarTestRoute("cdf2f7de565d40ae|2019-04-25--03-53-41", TOYOTA.RAV4_TSS2), CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022), + CarTestRoute("ad5a3fa719bc2f83|2023-10-17--19-48-42", TOYOTA.RAV4_TSS2_2023), CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4_TSS2), # hybrid CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4_TSS2_2022), # hybrid CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES), From 573db49deb255636d6d02756eba9d520cff3c0ae Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Tue, 28 Nov 2023 15:10:30 -0800 Subject: [PATCH 089/724] torqued: Update HYUNDAI TUCSON 4TH GEN offline values (#30513) * update tucson offline values * modify params, compute max accel --- selfdrive/car/torque_data/params.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 2d2dbc3f0b..7bd83025f1 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -37,7 +37,7 @@ HYUNDAI SANTA FE PlUG-IN HYBRID 2022: [1.6953050513611045, 1.5837614296206861, 0 HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] -HYUNDAI TUCSON HYBRID 4TH GEN: [2.035545, 2.035545, 0.110272] +HYUNDAI TUCSON HYBRID 4TH GEN: [2.960174, 2.860284, 0.108745] JEEP GRAND CHEROKEE 2019: [2.30972, 1.289689569171081, 0.117048] JEEP GRAND CHEROKEE V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003] KIA EV6 2022: [3.2, 2.093457, 0.05] From 9634e7b8afeebddd5d32d8b659d733dc85ff34c6 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 29 Nov 2023 12:55:52 -0800 Subject: [PATCH 090/724] Subaru: log eyesight fault as a cruise fault (#30546) log cruise fault --- selfdrive/car/subaru/carstate.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index c8a6dfe1e6..b51fcc6846 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -19,6 +19,8 @@ class CarState(CarStateBase): def update(self, cp, cp_cam, cp_body): ret = car.CarState.new_message() + cp_es_distance = cp_body if self.car_fingerprint in (GLOBAL_GEN2 | HYBRID_CARS) else cp_cam + throttle_msg = cp.vl["Throttle"] if self.car_fingerprint not in HYBRID_CARS else cp_body.vl["Throttle_Hybrid"] ret.gas = throttle_msg["Throttle_Pedal"] / 255. @@ -29,6 +31,14 @@ class CarState(CarStateBase): cp_brakes = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1 + if self.car_fingerprint not in HYBRID_CARS: + eyesight_fault = bool(cp_es_distance.vl["ES_Distance"]["Cruise_Fault"]) + + if self.CP.openpilotLongitudinalControl: + ret.carFaultedNonCritical = eyesight_fault + else: + ret.accFaulted = eyesight_fault # if openpilot isn't controlling long, an eyesight fault is an acc fault + cp_wheels = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp ret.wheelSpeeds = self.get_wheel_speeds( cp_wheels.vl["Wheel_Speeds"]["FL"], @@ -84,7 +94,6 @@ class CarState(CarStateBase): cp.vl["BodyInfo"]["DOOR_OPEN_FL"]]) ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1 - cp_es_distance = cp_body if self.car_fingerprint in (GLOBAL_GEN2 | HYBRID_CARS) else cp_cam if self.car_fingerprint in PREGLOBAL_CARS: self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"] self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"] From dcbff66f0c38496c76f7505cf283b9d66def9aab Mon Sep 17 00:00:00 2001 From: DriftedPrism Date: Wed, 29 Nov 2023 17:16:17 -0500 Subject: [PATCH 091/724] Kia: add missing fwdCamea FW for Sorento PHEV 4th gen (#30526) * Update values.py Adding Ecus for KIA Sorento PHEV 4th gen * Update values.py added fwdCamera version * fix --------- Co-authored-by: Shane Smiskol --- selfdrive/car/hyundai/values.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 0a0f66eabd..41ccd576fc 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1912,7 +1912,8 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', - ] + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406', + ], }, CAR.KIA_EV6: { (Ecu.fwdRadar, 0x7d0, None): [ From e61ea6641559ec1d0cbae3b816d4dcf4957382ea Mon Sep 17 00:00:00 2001 From: ebo2k <33906082+ebo2k@users.noreply.github.com> Date: Wed, 29 Nov 2023 14:26:20 -0800 Subject: [PATCH 092/724] Ford: add missing engine FW for Bronco Sport 2021 (#30483) Update values.py Updated to have correct engine firmware --- selfdrive/car/ford/values.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 133038809f..ef8708f01d 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -142,6 +142,7 @@ FW_VERSIONS = { b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'M1PA-14C204-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.ESCAPE_MK4: { From 4f8a2323828dd1eb3a608b81c48e7bced27abe6f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 29 Nov 2023 16:27:37 -0600 Subject: [PATCH 093/724] Car docs: add '19-'20 Santa Fe video (#30553) * Add '19-'20 Santa Fe video Add link to YouTube video for 2019-2020 Hyundai Santa Fe * update docs --------- Co-authored-by: Steve J --- docs/CARS.md | 2 +- selfdrive/car/hyundai/values.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 044db1f0ec..3a67a0151a 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -99,7 +99,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 41ccd576fc..222213322f 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -191,7 +191,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { # TODO: this is the 2024 US MY, not yet released CAR.KONA_EV_2ND_GEN: HyundaiCarInfo("Hyundai Kona Electric (with HDA II, Korea only) 2023", video_link="https://www.youtube.com/watch?v=U2fOCmcQ8hw", car_parts=CarParts.common([CarHarness.hyundai_r])), - CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", car_parts=CarParts.common([CarHarness.hyundai_d])), + CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", video_link="https://youtu.be/bjDR0YjM__s", + car_parts=CarParts.common([CarHarness.hyundai_d])), CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-23", "All", video_link="https://youtu.be/VnHzSTygTS4", car_parts=CarParts.common([CarHarness.hyundai_l])), CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), From 44ef143ae7cc855d6f4ce4b8328b90ab4f125fec Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 29 Nov 2023 14:53:43 -0800 Subject: [PATCH 094/724] Subaru: add 2023 forester (#30554) 23 --- selfdrive/car/subaru/values.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 882d46b7c3..057e0ffc0c 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -126,7 +126,7 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"), CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"), CAR.OUTBACK_PREGLOBAL_2018: SubaruCarInfo("Subaru Outback 2018-19"), - CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022", "All", car_parts=CarParts.common([CarHarness.subaru_c])), + CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022-23", "All", car_parts=CarParts.common([CarHarness.subaru_c])), CAR.OUTBACK_2023: SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), CAR.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), } @@ -671,7 +671,8 @@ FW_VERSIONS = { b'=\xc04\x02', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x04!\x01\x1eD\x07!\x00\x04,' + b'\x04!\x01\x1eD\x07!\x00\x04,', + b'\x04!\x08\x01.\x07!\x08\x022', ], (Ecu.engine, 0x7e0, None): [ b'\xd5"a0\x07', From a2bb41e0ec5fd96763e9a01a7116efda059de6e6 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 29 Nov 2023 17:47:09 -0800 Subject: [PATCH 095/724] Subaru: non-obd FW queries logging (#30552) * add subaru logging fw * whitelist + comma * whitelist is empty for the other requests * all whitelisted --- selfdrive/car/subaru/values.py | 18 ++++++++++++++++++ selfdrive/car/tests/test_fw_fingerprint.py | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 057e0ffc0c..915785d329 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -141,12 +141,30 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], + whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission], ), # Some Eyesight modules fail on TESTER_PRESENT_REQUEST # TODO: check if this resolves the fingerprinting issue for the 2023 Ascent and other new Subaru cars Request( [SUBARU_VERSION_REQUEST], [SUBARU_VERSION_RESPONSE], + whitelist_ecus=[Ecu.fwdCamera], + ), + # Non-OBD requests + Request( + [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], + whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission], + bus=0, + logging=True, + ), + Request( + [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], + whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission], + bus=1, + logging=True, + obd_multiplexing=False, ), ], ) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 6777aeb738..237266dbed 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -227,7 +227,7 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.07 + total_ref_time = 6.27 brand_ref_times = { 1: { 'body': 0.11, @@ -237,7 +237,7 @@ class TestFwFingerprintTiming(unittest.TestCase): 'hyundai': 0.72, 'mazda': 0.2, 'nissan': 0.4, - 'subaru': 0.2, + 'subaru': 0.4, 'tesla': 0.2, 'toyota': 1.6, 'volkswagen': 0.2, From b1e2e0f565010191f0f624487c4b74c63008f03a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 30 Nov 2023 07:57:29 -0800 Subject: [PATCH 096/724] Revert "Subaru: log eyesight fault as a cruise fault" (#30560) --- selfdrive/car/subaru/carstate.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index b51fcc6846..c8a6dfe1e6 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -19,8 +19,6 @@ class CarState(CarStateBase): def update(self, cp, cp_cam, cp_body): ret = car.CarState.new_message() - cp_es_distance = cp_body if self.car_fingerprint in (GLOBAL_GEN2 | HYBRID_CARS) else cp_cam - throttle_msg = cp.vl["Throttle"] if self.car_fingerprint not in HYBRID_CARS else cp_body.vl["Throttle_Hybrid"] ret.gas = throttle_msg["Throttle_Pedal"] / 255. @@ -31,14 +29,6 @@ class CarState(CarStateBase): cp_brakes = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp ret.brakePressed = cp_brakes.vl["Brake_Status"]["Brake"] == 1 - if self.car_fingerprint not in HYBRID_CARS: - eyesight_fault = bool(cp_es_distance.vl["ES_Distance"]["Cruise_Fault"]) - - if self.CP.openpilotLongitudinalControl: - ret.carFaultedNonCritical = eyesight_fault - else: - ret.accFaulted = eyesight_fault # if openpilot isn't controlling long, an eyesight fault is an acc fault - cp_wheels = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp ret.wheelSpeeds = self.get_wheel_speeds( cp_wheels.vl["Wheel_Speeds"]["FL"], @@ -94,6 +84,7 @@ class CarState(CarStateBase): cp.vl["BodyInfo"]["DOOR_OPEN_FL"]]) ret.steerFaultPermanent = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1 + cp_es_distance = cp_body if self.car_fingerprint in (GLOBAL_GEN2 | HYBRID_CARS) else cp_cam if self.car_fingerprint in PREGLOBAL_CARS: self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"] self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"] From 4086795c29bacd245a7cc372a16730e1a6ff974f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 30 Nov 2023 09:55:48 -0800 Subject: [PATCH 097/724] Pytest: more post-test path cleanup (#30556) * test cleanup * missed this one * remove that * static class --- common/api/__init__.py | 4 ++-- common/basedir.py | 9 +------- selfdrive/athena/athenad.py | 7 +++---- selfdrive/athena/registration.py | 6 +++--- selfdrive/athena/tests/test_athenad_ping.py | 5 +++-- selfdrive/athena/tests/test_registration.py | 16 +++++--------- selfdrive/statsd.py | 5 ++++- .../test_longitudinal.py | 1 + system/hardware/hw.py | 21 +++++++++++++++++++ system/loggerd/config.py | 6 ------ tools/lib/auth_config.py | 15 +++++-------- 11 files changed, 48 insertions(+), 47 deletions(-) diff --git a/common/api/__init__.py b/common/api/__init__.py index 0eb8aa7627..79875023a2 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -2,7 +2,7 @@ import jwt import os import requests from datetime import datetime, timedelta -from openpilot.common.basedir import PERSIST +from openpilot.system.hardware.hw import Paths from openpilot.system.version import get_version API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') @@ -10,7 +10,7 @@ API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') class Api(): def __init__(self, dongle_id): self.dongle_id = dongle_id - with open(PERSIST+'/comma/id_rsa') as f: + with open(Paths.persist_root()+'/comma/id_rsa') as f: self.private_key = f.read() def get(self, *args, **kwargs): diff --git a/common/basedir.py b/common/basedir.py index b4486f9f08..c840b86f7f 100644 --- a/common/basedir.py +++ b/common/basedir.py @@ -1,11 +1,4 @@ import os -from pathlib import Path -from openpilot.system.hardware import PC -BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) - -if PC: - PERSIST = os.path.join(str(Path.home()), ".comma", "persist") -else: - PERSIST = "/persist" +BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) \ No newline at end of file diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index eb61c6dd58..423479517c 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -31,13 +31,11 @@ import cereal.messaging as messaging from cereal import log from cereal.services import SERVICE_LIST from openpilot.common.api import Api -from openpilot.common.basedir import PERSIST from openpilot.common.file_helpers import CallbackReader from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.system.hardware import HARDWARE, PC, AGNOS from openpilot.system.loggerd.xattr_cache import getxattr, setxattr -from openpilot.selfdrive.statsd import STATS_DIR from openpilot.system.swaglog import cloudlog from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version from openpilot.system.hardware.hw import Paths @@ -502,10 +500,10 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local @dispatcher.add_method def getPublicKey() -> Optional[str]: - if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'): + if not os.path.isfile(Paths.persist_root() + '/comma/id_rsa.pub'): return None - with open(PERSIST + '/comma/id_rsa.pub') as f: + with open(Paths.persist_root() + '/comma/id_rsa.pub') as f: return f.read() @@ -641,6 +639,7 @@ def log_handler(end_event: threading.Event) -> None: def stat_handler(end_event: threading.Event) -> None: + STATS_DIR = Paths.stats_root() while not end_event.is_set(): last_scan = 0. curr_scan = time.monotonic() diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index 0ab69371c2..7467e7fa86 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -9,9 +9,9 @@ from datetime import datetime, timedelta from openpilot.common.api import api_get from openpilot.common.params import Params from openpilot.common.spinner import Spinner -from openpilot.common.basedir import PERSIST from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.hardware.hw import Paths from openpilot.system.swaglog import cloudlog @@ -32,7 +32,7 @@ def register(show_spinner=False) -> Optional[str]: dongle_id: Optional[str] = params.get("DongleId", encoding='utf8') needs_registration = None in (IMEI, HardwareSerial, dongle_id) - pubkey = Path(PERSIST+"/comma/id_rsa.pub") + pubkey = Path(Paths.persist_root()+"/comma/id_rsa.pub") if not pubkey.is_file(): dongle_id = UNREGISTERED_DONGLE_ID cloudlog.warning(f"missing public key: {pubkey}") @@ -42,7 +42,7 @@ def register(show_spinner=False) -> Optional[str]: spinner.update("registering device") # Create registration token, in the future, this key will make JWTs directly - with open(PERSIST+"/comma/id_rsa.pub") as f1, open(PERSIST+"/comma/id_rsa") as f2: + with open(Paths.persist_root()+"/comma/id_rsa.pub") as f1, open(Paths.persist_root()+"/comma/id_rsa") as f2: public_key = f1.read() private_key = f2.read() diff --git a/selfdrive/athena/tests/test_athenad_ping.py b/selfdrive/athena/tests/test_athenad_ping.py index 3ec7cb115c..2958ec2262 100755 --- a/selfdrive/athena/tests/test_athenad_ping.py +++ b/selfdrive/athena/tests/test_athenad_ping.py @@ -40,8 +40,6 @@ class TestAthenadPing(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - cls.params = Params() - cls.dongle_id = cls.params.get("DongleId", encoding="utf-8") cls._create_connection = athenad.create_connection athenad.create_connection = MagicMock(wraps=cls._create_connection) @@ -51,6 +49,9 @@ class TestAthenadPing(unittest.TestCase): athenad.create_connection = cls._create_connection def setUp(self) -> None: + self.params = Params() + self.dongle_id = self.params.get("DongleId", encoding="utf-8") + wifi_radio(True) self._clear_ping_time() diff --git a/selfdrive/athena/tests/test_registration.py b/selfdrive/athena/tests/test_registration.py index 195fca2df9..e7ad63a370 100755 --- a/selfdrive/athena/tests/test_registration.py +++ b/selfdrive/athena/tests/test_registration.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 import json -import os -import tempfile import unittest from Crypto.PublicKey import RSA from pathlib import Path @@ -10,6 +8,7 @@ from unittest import mock from openpilot.common.params import Params from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.selfdrive.athena.tests.helpers import MockResponse +from openpilot.system.hardware.hw import Paths class TestRegistration(unittest.TestCase): @@ -19,16 +18,11 @@ class TestRegistration(unittest.TestCase): self.params = Params() self.params.clear_all() - self.persist = tempfile.TemporaryDirectory() - os.mkdir(os.path.join(self.persist.name, "comma")) - self.priv_key = Path(os.path.join(self.persist.name, "comma/id_rsa")) - self.pub_key = Path(os.path.join(self.persist.name, "comma/id_rsa.pub")) - self.persist_patcher = mock.patch("openpilot.selfdrive.athena.registration.PERSIST", self.persist.name) - self.persist_patcher.start() + persist_dir = Path(Paths.persist_root()) / "comma" + persist_dir.mkdir(parents=True, exist_ok=True) - def tearDown(self): - self.persist_patcher.stop() - self.persist.cleanup() + self.priv_key = persist_dir / "id_rsa" + self.pub_key = persist_dir / "id_rsa.pub" def _generate_keys(self): self.pub_key.touch() diff --git a/selfdrive/statsd.py b/selfdrive/statsd.py index 8acf406515..e41f41a621 100755 --- a/selfdrive/statsd.py +++ b/selfdrive/statsd.py @@ -9,11 +9,12 @@ from typing import NoReturn, Union, List, Dict from openpilot.common.params import Params from cereal.messaging import SubMaster +from openpilot.system.hardware.hw import Paths from openpilot.system.swaglog import cloudlog from openpilot.system.hardware import HARDWARE from openpilot.common.file_helpers import atomic_write_in_dir from openpilot.system.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty -from openpilot.system.loggerd.config import STATS_DIR, STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S +from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S class METRIC_TYPE: @@ -80,6 +81,8 @@ def main() -> NoReturn: sock = ctx.socket(zmq.PULL) sock.bind(STATS_SOCKET) + STATS_DIR = Paths.stats_root() + # initialize stats directory Path(STATS_DIR).mkdir(parents=True, exist_ok=True) diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index a3b307ccba..f24f788a19 100755 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -156,6 +156,7 @@ class LongitudinalControl(unittest.TestCase): os.environ['SKIP_FW_QUERY'] = "1" os.environ['NO_CAN_TIMEOUT'] = "1" + def setUp(self): params = Params() params.clear_all() params.put_bool("Passive", bool(os.getenv("PASSIVE"))) diff --git a/system/hardware/hw.py b/system/hardware/hw.py index 119ca48c86..6c3d90345c 100644 --- a/system/hardware/hw.py +++ b/system/hardware/hw.py @@ -33,3 +33,24 @@ class Paths: if os.environ.get('COMMA_CACHE', False): return os.environ['COMMA_CACHE'] return "/tmp/comma_download_cache" + os.environ.get("OPENPILOT_PREFIX", "") + "/" + + @staticmethod + def persist_root() -> str: + if PC: + return os.path.join(Paths.comma_home(), "persist") + else: + return "/persist/" + + @staticmethod + def stats_root() -> str: + if PC: + return str(Path(Paths.comma_home()) / "stats") + else: + return "/data/stats/" + + @staticmethod + def config_root() -> str: + if PC: + return Paths.comma_home() + else: + return "/tmp/.comma" \ No newline at end of file diff --git a/system/loggerd/config.py b/system/loggerd/config.py index 664e78b378..5c2c89b73f 100644 --- a/system/loggerd/config.py +++ b/system/loggerd/config.py @@ -1,6 +1,4 @@ import os -from pathlib import Path -from openpilot.system.hardware import PC from openpilot.system.hardware.hw import Paths @@ -9,10 +7,6 @@ SEGMENT_LENGTH = 60 STATS_DIR_FILE_LIMIT = 10000 STATS_SOCKET = "ipc:///tmp/stats" -if PC: - STATS_DIR = str(Path.home() / ".comma" / "stats") -else: - STATS_DIR = "/data/stats/" STATS_FLUSH_TIME_S = 60 def get_available_percent(default=None): diff --git a/tools/lib/auth_config.py b/tools/lib/auth_config.py index bd76761043..e0989f02ea 100644 --- a/tools/lib/auth_config.py +++ b/tools/lib/auth_config.py @@ -1,21 +1,16 @@ import json import os from openpilot.common.file_helpers import mkdirs_exists_ok -from openpilot.system.hardware import PC +from openpilot.system.hardware.hw import Paths class MissingAuthConfigError(Exception): pass -if PC: - CONFIG_DIR = os.path.expanduser('~/.comma') -else: - CONFIG_DIR = "/tmp/.comma" - def get_token(): try: - with open(os.path.join(CONFIG_DIR, 'auth.json')) as f: + with open(os.path.join(Paths.config_root(), 'auth.json')) as f: auth = json.load(f) return auth['access_token'] except Exception: @@ -23,13 +18,13 @@ def get_token(): def set_token(token): - mkdirs_exists_ok(CONFIG_DIR) - with open(os.path.join(CONFIG_DIR, 'auth.json'), 'w') as f: + mkdirs_exists_ok(Paths.config_root()) + with open(os.path.join(Paths.config_root(), 'auth.json'), 'w') as f: json.dump({'access_token': token}, f) def clear_token(): try: - os.unlink(os.path.join(CONFIG_DIR, 'auth.json')) + os.unlink(os.path.join(Paths.config_root(), 'auth.json')) except FileNotFoundError: pass From 7e0f0165f4a036760cb3a0c179facffbe39e15ad Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Thu, 30 Nov 2023 10:42:18 -0800 Subject: [PATCH 098/724] replay: get api url from env var (#30562) --- tools/replay/route.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 9168d25b35..d0ddf7f3c8 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -44,7 +44,7 @@ bool Route::loadFromServer() { loop.exit(success ? loadFromJson(json) : 0); }); - http.sendRequest("https://api.commadotai.com/v1/route/" + route_.str + "/files"); + http.sendRequest(CommaApi::BASE_URL + "/v1/route/" + route_.str + "/files"); return loop.exec(); } From 613ccf4b74ce70ac4bff75d1dbdd5ee7f8f4acdc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 30 Nov 2023 14:53:33 -0800 Subject: [PATCH 099/724] falsify rx checks (#30564) --- selfdrive/car/tests/test_models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index abc9bbc13a..f26fd363ca 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -221,7 +221,7 @@ class TestCarModelBase(unittest.TestCase): error_cnt += car.RadarData.Error.canError in rr.errors self.assertEqual(error_cnt, 0) - def test_panda_safety_rx_valid(self): + def test_panda_safety_rx_checks(self): if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") @@ -256,6 +256,11 @@ class TestCarModelBase(unittest.TestCase): self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}") + # ensure RX checks go invalid after small time with no traffic + self.safety.set_timer(int(t + (2*1e6))) + self.safety.safety_tick_current_safety_config() + self.assertFalse(self.safety.safety_config_valid()) + def test_panda_safety_tx_cases(self, data=None): """Asserts we can tx common messages""" if self.CP.notCar: From 6ac75e492ad49cb16ffc752e64216a234f878442 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 30 Nov 2023 15:28:46 -0800 Subject: [PATCH 100/724] bump panda (#30563) --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index e6fadef4dd..b50455cc76 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e6fadef4ddccb9e665fbdf2d377f12879e5af07a +Subproject commit b50455cc76a0f66aa6faf953dd3ea023783a4b3f From e687be939e7cf67c4a75b87320ab058941d4316f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 30 Nov 2023 18:36:20 -0600 Subject: [PATCH 101/724] FW fingerprinting timing: fix refs (#30566) --- selfdrive/car/tests/test_fw_fingerprint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 237266dbed..5721896787 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -227,7 +227,7 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.27 + total_ref_time = 6.41 brand_ref_times = { 1: { 'body': 0.11, @@ -237,7 +237,7 @@ class TestFwFingerprintTiming(unittest.TestCase): 'hyundai': 0.72, 'mazda': 0.2, 'nissan': 0.4, - 'subaru': 0.4, + 'subaru': 0.52, 'tesla': 0.2, 'toyota': 1.6, 'volkswagen': 0.2, From d87191c1c1982e5c044e5654d7a69c5cc222986c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 1 Dec 2023 12:46:31 +0800 Subject: [PATCH 102/724] cabana: fix missing opendbc files (#30555) * fix missing dbc files * remove suffix --- tools/cabana/mainwin.cc | 7 ++++--- tools/cabana/mainwin.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 71e5ade9bb..f7f2fba45f 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -77,9 +77,10 @@ void MainWindow::loadFingerprints() { QFile json_file(QApplication::applicationDirPath() + "/dbc/car_fingerprint_to_dbc.json"); if (json_file.open(QIODevice::ReadOnly)) { fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); - auto dbc_names = fingerprint_to_dbc.object().toVariantMap().values(); - std::transform(dbc_names.begin(), dbc_names.end(), std::inserter(opendbc_names, opendbc_names.begin()), - [](const auto &name) { return name.toString(); }); + } + // get opendbc names + for (auto fn : QDir(OPENDBC_FILE_PATH).entryList({"*.dbc"}, QDir::Files, QDir::Name)) { + opendbc_names << QFileInfo(fn).baseName(); } } diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 2b1c991271..1a889b88c2 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -86,7 +86,7 @@ protected: QProgressBar *progress_bar; QLabel *status_label; QJsonDocument fingerprint_to_dbc; - std::set opendbc_names; + QStringList opendbc_names; QSplitter *video_splitter = nullptr; enum { MAX_RECENT_FILES = 15 }; QAction *recent_files_acts[MAX_RECENT_FILES] = {}; From afd5877be8260afd840512d477b9164595343eed Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 30 Nov 2023 21:48:11 -0800 Subject: [PATCH 103/724] bump model unit test timeout --- .github/workflows/selfdrive_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 4e622f68a5..94e6304539 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -268,7 +268,7 @@ jobs: ${{ env.RUN }} "scons -j$(nproc)" # PYTHONWARNINGS triggers a SyntaxError in onnxruntime - name: Run model replay with ONNX - timeout-minutes: 3 + timeout-minutes: 4 run: | ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ From 169132c6cc52f598b9b16d9996690f51282082e5 Mon Sep 17 00:00:00 2001 From: Hoya Date: Sat, 2 Dec 2023 07:59:49 +0900 Subject: [PATCH 104/724] Hyundai: add FW for 2019 AZERA_HEV_6TH_GEN (#30548) * added AZERA_HEV_5TH_GEN (2019) * Update values.py * Update interface.py * Update values.py * Update hyundaican.py * Update hyundaican.py * Update interface.py * Update values.py * one line * didnt see harness change * Update selfdrive/car/hyundai/values.py * docs --------- Co-authored-by: Justin Newberry Co-authored-by: Shane Smiskol --- docs/CARS.md | 3 ++- selfdrive/car/hyundai/values.py | 10 +++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 3a67a0151a..f0004c5aa0 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 268 Supported Cars +# 269 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -73,6 +73,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 222213322f..7c6fa7cb4f 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -159,7 +159,10 @@ class HyundaiCarInfo(CarInfo): CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.AZERA_6TH_GEN: HyundaiCarInfo("Hyundai Azera 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.AZERA_HEV_6TH_GEN: HyundaiCarInfo("Hyundai Azera Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), + CAR.AZERA_HEV_6TH_GEN: [ + HyundaiCarInfo("Hyundai Azera Hybrid 2019", "All", car_parts=CarParts.common([CarHarness.hyundai_c])), + HyundaiCarInfo("Hyundai Azera Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), + ], CAR.ELANTRA: [ # TODO: 2017-18 could be Hyundai G HyundaiCarInfo("Hyundai Elantra 2017-18", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])), @@ -556,18 +559,23 @@ FW_VERSIONS = { CAR.AZERA_HEV_6TH_GEN: { (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029', + b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.00 99211-G8000 180903', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00IG MDPS C 1.00 1.00 56310M9600\x00 4IHSC100', + b'\xf1\x00IG MDPS C 1.00 1.01 56310M9350\x00 4IH8C101', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00IGhe SCC FHCUP 1.00 1.00 99110-M9100 ', + b'\xf1\x00IGhe SCC FHCUP 1.00 1.01 99110-M9000 ', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x006T7N0_C2\x00\x006T7VA051\x00\x00TIGSH24KA1\xc7\x85\xe2`', + b'\xf1\x006T7N0_C2\x00\x006T7Q2051\x00\x00TIG2H24KA2\x12@\x11\xb7', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H590051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H570051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.HYUNDAI_GENESIS: { From 5dba9187e52d243a570bf10386ff9fe889e93abf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 1 Dec 2023 19:55:27 -0800 Subject: [PATCH 105/724] CI: speedup docs build (#30574) --- .github/workflows/docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 8b5b165536..c1d4eae52f 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -32,7 +32,7 @@ jobs: ${{ env.RUN }} "scons -j$(nproc)" - name: Build docs run: | - ${{ env.RUN }} "apt update && apt install -y doxygen && cd docs && make html" + ${{ env.RUN }} "apt update && apt install -y doxygen && cd docs && make -j$(nproc) html" - uses: actions/checkout@v4 if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' From e34ee43eeae000d080cb923b722afaf4c06fbc59 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 1 Dec 2023 20:10:07 -0800 Subject: [PATCH 106/724] camerad cleanup (#30573) * misc cleanup * rm those * rm utils * fix build * rm pool * little more * goodbye imgproc --- docs/c_docs.rst | 2 - release/files_common | 5 -- system/camerad/SConscript | 9 +- system/camerad/cameras/camera_common.cc | 6 -- system/camerad/cameras/camera_common.h | 16 +--- system/camerad/cameras/camera_qcom2.cc | 82 +++++++++--------- system/camerad/cameras/camera_qcom2.h | 3 + system/camerad/cameras/camera_util.cc | 14 +-- system/camerad/cameras/camera_util.h | 2 +- system/camerad/imgproc/conv.cl | 110 ------------------------ system/camerad/imgproc/pool.cl | 34 -------- system/camerad/imgproc/utils.cc | 106 ----------------------- system/camerad/imgproc/utils.h | 37 -------- system/camerad/main.cc | 2 +- system/camerad/snapshot/snapshot.py | 1 - 15 files changed, 59 insertions(+), 370 deletions(-) delete mode 100644 system/camerad/imgproc/conv.cl delete mode 100644 system/camerad/imgproc/pool.cl delete mode 100644 system/camerad/imgproc/utils.cc delete mode 100644 system/camerad/imgproc/utils.h diff --git a/docs/c_docs.rst b/docs/c_docs.rst index 5619cc8a51..0b9d23972e 100644 --- a/docs/c_docs.rst +++ b/docs/c_docs.rst @@ -29,8 +29,6 @@ camerad ^^^^^^^ .. autodoxygenindex:: :project: system_camerad_cameras -.. autodoxygenindex:: - :project: system_camerad_imgproc locationd ^^^^^^^^^ diff --git a/release/files_common b/release/files_common index cfc3830e0d..af5d156d41 100644 --- a/release/files_common +++ b/release/files_common @@ -335,11 +335,6 @@ system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.cc system/camerad/cameras/sensor2_i2c.h -system/camerad/imgproc/conv.cl -system/camerad/imgproc/pool.cl -system/camerad/imgproc/utils.cc -system/camerad/imgproc/utils.h - selfdrive/manager/__init__.py selfdrive/manager/build.py selfdrive/manager/helpers.py diff --git a/system/camerad/SConscript b/system/camerad/SConscript index ffd7278bbb..83f93344d8 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -3,12 +3,7 @@ Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon, 'atomic'] camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc']) -env.Program('camerad', [ - 'main.cc', - camera_obj, - ], LIBS=libs) +env.Program('camerad', ['main.cc', camera_obj], LIBS=libs) if GetOption("extras") and arch == "x86_64": - env.Program('test/ae_gray_test', - ['test/ae_gray_test.cc', camera_obj], - LIBS=libs) + env.Program('test/ae_gray_test', ['test/ae_gray_test.cc', camera_obj], LIBS=libs) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index d2c7a35f6e..987ccf23da 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -11,7 +11,6 @@ #include "third_party/libyuv/include/libyuv.h" #include -#include "system/camerad/imgproc/utils.h" #include "common/clutil.h" #include "common/swaglog.h" #include "common/util.h" @@ -93,12 +92,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset); -#ifdef __APPLE__ - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); -#else const cl_queue_properties props[] = {0}; //CL_QUEUE_PRIORITY_KHR, CL_QUEUE_PRIORITY_HIGH_KHR, 0}; q = CL_CHECK_ERR(clCreateCommandQueueWithProperties(context, device_id, props, &err)); -#endif } CameraBuf::~CameraBuf() { @@ -281,7 +276,6 @@ float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip } } - // Find mean lumimance value unsigned int lum_cur = 0; for (lum_med = 255; lum_med >= 0; lum_med--) { diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 0f69aa3774..23bfc8b7d5 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -14,17 +14,9 @@ #include "common/swaglog.h" #include "system/hardware/hw.h" -#define CAMERA_ID_IMX298 0 -#define CAMERA_ID_IMX179 1 -#define CAMERA_ID_S5K3P8SP 2 -#define CAMERA_ID_OV8865 3 -#define CAMERA_ID_IMX298_FLIPPED 4 -#define CAMERA_ID_OV10640 5 -#define CAMERA_ID_LGC920 6 -#define CAMERA_ID_LGC615 7 -#define CAMERA_ID_AR0231 8 -#define CAMERA_ID_OX03C10 9 -#define CAMERA_ID_MAX 10 +#define CAMERA_ID_AR0231 0 +#define CAMERA_ID_OX03C10 1 +#define CAMERA_ID_MAX 2 const int YUV_BUFFER_COUNT = 20; @@ -55,7 +47,7 @@ typedef struct FrameMetadata { uint32_t frame_id; // Timestamps - uint64_t timestamp_sof; // only set on tici + uint64_t timestamp_sof; uint64_t timestamp_eof; // Exposure diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 3a9ecb467b..8d20ed27c8 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -665,51 +665,51 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num if (!enabled) return; struct cam_isp_in_port_info in_port_info = { - .res_type = (uint32_t[]){CAM_ISP_IFE_IN_RES_PHY_0, CAM_ISP_IFE_IN_RES_PHY_1, CAM_ISP_IFE_IN_RES_PHY_2}[camera_num], + .res_type = (uint32_t[]){CAM_ISP_IFE_IN_RES_PHY_0, CAM_ISP_IFE_IN_RES_PHY_1, CAM_ISP_IFE_IN_RES_PHY_2}[camera_num], - .lane_type = CAM_ISP_LANE_TYPE_DPHY, - .lane_num = 4, - .lane_cfg = 0x3210, + .lane_type = CAM_ISP_LANE_TYPE_DPHY, + .lane_num = 4, + .lane_cfg = 0x3210, - .vc = 0x0, - .dt = dt, + .vc = 0x0, + .dt = dt, + .format = CAM_FORMAT_MIPI_RAW_12, + + .test_pattern = 0x2, // 0x3? + .usage_type = 0x0, + + .left_start = 0, + .left_stop = ci.frame_width - 1, + .left_width = ci.frame_width, + + .right_start = 0, + .right_stop = ci.frame_width - 1, + .right_width = ci.frame_width, + + .line_start = 0, + .line_stop = ci.frame_height + ci.extra_height - 1, + .height = ci.frame_height + ci.extra_height, + + .pixel_clk = 0x0, + .batch_size = 0x0, + .dsp_mode = CAM_ISP_DSP_MODE_NONE, + .hbi_cnt = 0x0, + .custom_csid = 0x0, + + .num_out_res = 0x1, + .data[0] = (struct cam_isp_out_port_info){ + .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, .format = CAM_FORMAT_MIPI_RAW_12, - - .test_pattern = 0x2, // 0x3? - .usage_type = 0x0, - - .left_start = 0, - .left_stop = ci.frame_width - 1, - .left_width = ci.frame_width, - - .right_start = 0, - .right_stop = ci.frame_width - 1, - .right_width = ci.frame_width, - - .line_start = 0, - .line_stop = ci.frame_height + ci.extra_height - 1, + .width = ci.frame_width, .height = ci.frame_height + ci.extra_height, - - .pixel_clk = 0x0, - .batch_size = 0x0, - .dsp_mode = CAM_ISP_DSP_MODE_NONE, - .hbi_cnt = 0x0, - .custom_csid = 0x0, - - .num_out_res = 0x1, - .data[0] = (struct cam_isp_out_port_info){ - .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, - .format = CAM_FORMAT_MIPI_RAW_12, - .width = ci.frame_width, - .height = ci.frame_height + ci.extra_height, - .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, - }, + .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, + }, }; struct cam_isp_resource isp_resource = { - .resource_id = CAM_ISP_RES_ID_PORT, - .handle_type = CAM_HANDLE_USER_POINTER, - .res_hdl = (uint64_t)&in_port_info, - .length = sizeof(in_port_info), + .resource_id = CAM_ISP_RES_ID_PORT, + .handle_type = CAM_HANDLE_USER_POINTER, + .res_hdl = (uint64_t)&in_port_info, + .length = sizeof(in_port_info), }; auto isp_dev_handle_ = device_acquire(multi_cam_state->isp_fd, session_handle, &isp_resource); @@ -1098,8 +1098,8 @@ void CameraState::set_camera_exposure(float grey_frac) { std::string gain_bytes, time_bytes; if (env_ctrl_exp_from_params) { - gain_bytes = Params().get("CameraDebugExpGain"); - time_bytes = Params().get("CameraDebugExpTime"); + gain_bytes = params.get("CameraDebugExpGain"); + time_bytes = params.get("CameraDebugExpTime"); } if (gain_bytes.size() > 0 && time_bytes.size() > 0) { diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 9e0109ab20..1e7cbfc104 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -107,6 +107,9 @@ private: // Register parsing std::map> ar0231_register_lut; std::map> ar0231_build_register_lut(uint8_t *data); + + // for debugging + Params params; }; typedef struct MultiCameraState { diff --git a/system/camerad/cameras/camera_util.cc b/system/camerad/cameras/camera_util.cc index 74c81a878a..b2cf4115dc 100644 --- a/system/camerad/cameras/camera_util.cc +++ b/system/camerad/cameras/camera_util.cc @@ -30,10 +30,10 @@ int do_cam_control(int fd, int op_code, void *handle, int size) { std::optional device_acquire(int fd, int32_t session_handle, void *data, uint32_t num_resources) { struct cam_acquire_dev_cmd cmd = { - .session_handle = session_handle, - .handle_type = CAM_HANDLE_USER_POINTER, - .num_resources = (uint32_t)(data ? num_resources : 0), - .resource_hdl = (uint64_t)data, + .session_handle = session_handle, + .handle_type = CAM_HANDLE_USER_POINTER, + .num_resources = (uint32_t)(data ? num_resources : 0), + .resource_hdl = (uint64_t)data, }; int err = do_cam_control(fd, CAM_ACQUIRE_DEV, &cmd, sizeof(cmd)); return err == 0 ? std::make_optional(cmd.dev_handle) : std::nullopt; @@ -41,9 +41,9 @@ std::optional device_acquire(int fd, int32_t session_handle, void *data int device_config(int fd, int32_t session_handle, int32_t dev_handle, uint64_t packet_handle) { struct cam_config_dev_cmd cmd = { - .session_handle = session_handle, - .dev_handle = dev_handle, - .packet_handle = packet_handle, + .session_handle = session_handle, + .dev_handle = dev_handle, + .packet_handle = packet_handle, }; return do_cam_control(fd, CAM_CONFIG_DEV, &cmd, sizeof(cmd)); } diff --git a/system/camerad/cameras/camera_util.h b/system/camerad/cameras/camera_util.h index b36f404c0f..891ce3e793 100644 --- a/system/camerad/cameras/camera_util.h +++ b/system/camerad/cameras/camera_util.h @@ -1,6 +1,6 @@ #pragma once - #include +#include #include #include #include diff --git a/system/camerad/imgproc/conv.cl b/system/camerad/imgproc/conv.cl deleted file mode 100644 index a7115ae76c..0000000000 --- a/system/camerad/imgproc/conv.cl +++ /dev/null @@ -1,110 +0,0 @@ -// const __constant float3 rgb_weights = (0.299, 0.587, 0.114); // opencv rgb2gray weights -// const __constant float3 bgr_weights = (0.114, 0.587, 0.299); // bgr2gray weights - -// convert input rgb image to single channel then conv -__kernel void rgb2gray_conv2d( - const __global uchar * input, - __global short * output, - __constant short * filter, - __local uchar3 * cached -) -{ - const int rowOffset = get_global_id(1) * IMAGE_W; - const int my = get_global_id(0) + rowOffset; - - const int localRowLen = TWICE_HALF_FILTER_SIZE + get_local_size(0); - const int localRowOffset = ( get_local_id(1) + HALF_FILTER_SIZE ) * localRowLen; - const int myLocal = localRowOffset + get_local_id(0) + HALF_FILTER_SIZE; - - // cache local pixels - cached[ myLocal ].x = input[ my * 3 ]; // r - cached[ myLocal ].y = input[ my * 3 + 1]; // g - cached[ myLocal ].z = input[ my * 3 + 2]; // b - - // pad - if ( - get_global_id(0) < HALF_FILTER_SIZE || - get_global_id(0) > IMAGE_W - HALF_FILTER_SIZE - 1 || - get_global_id(1) < HALF_FILTER_SIZE || - get_global_id(1) > IMAGE_H - HALF_FILTER_SIZE - 1 - ) - { - barrier(CLK_LOCAL_MEM_FENCE); - return; - } - else - { - int localColOffset = -1; - int globalColOffset = -1; - - // cache extra - if ( get_local_id(0) < HALF_FILTER_SIZE ) - { - localColOffset = get_local_id(0); - globalColOffset = -HALF_FILTER_SIZE; - - cached[ localRowOffset + get_local_id(0) ].x = input[ my * 3 - HALF_FILTER_SIZE * 3 ]; - cached[ localRowOffset + get_local_id(0) ].y = input[ my * 3 - HALF_FILTER_SIZE * 3 + 1]; - cached[ localRowOffset + get_local_id(0) ].z = input[ my * 3 - HALF_FILTER_SIZE * 3 + 2]; - } - else if ( get_local_id(0) >= get_local_size(0) - HALF_FILTER_SIZE ) - { - localColOffset = get_local_id(0) + TWICE_HALF_FILTER_SIZE; - globalColOffset = HALF_FILTER_SIZE; - - cached[ myLocal + HALF_FILTER_SIZE ].x = input[ my * 3 + HALF_FILTER_SIZE * 3 ]; - cached[ myLocal + HALF_FILTER_SIZE ].y = input[ my * 3 + HALF_FILTER_SIZE * 3 + 1]; - cached[ myLocal + HALF_FILTER_SIZE ].z = input[ my * 3 + HALF_FILTER_SIZE * 3 + 2]; - } - - - if ( get_local_id(1) < HALF_FILTER_SIZE ) - { - cached[ get_local_id(1) * localRowLen + get_local_id(0) + HALF_FILTER_SIZE ].x = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 ]; - cached[ get_local_id(1) * localRowLen + get_local_id(0) + HALF_FILTER_SIZE ].y = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + 1]; - cached[ get_local_id(1) * localRowLen + get_local_id(0) + HALF_FILTER_SIZE ].z = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + 2]; - if (localColOffset > 0) - { - cached[ get_local_id(1) * localRowLen + localColOffset ].x = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3]; - cached[ get_local_id(1) * localRowLen + localColOffset ].y = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3 + 1]; - cached[ get_local_id(1) * localRowLen + localColOffset ].z = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3 + 2]; - } - } - else if ( get_local_id(1) >= get_local_size(1) -HALF_FILTER_SIZE ) - { - int offset = ( get_local_id(1) + TWICE_HALF_FILTER_SIZE ) * localRowLen; - cached[ offset + get_local_id(0) + HALF_FILTER_SIZE ].x = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 ]; - cached[ offset + get_local_id(0) + HALF_FILTER_SIZE ].y = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + 1]; - cached[ offset + get_local_id(0) + HALF_FILTER_SIZE ].z = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + 2]; - if (localColOffset > 0) - { - cached[ offset + localColOffset ].x = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3]; - cached[ offset + localColOffset ].y = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3 + 1]; - cached[ offset + localColOffset ].z = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3 + 2]; - } - } - - // sync - barrier(CLK_LOCAL_MEM_FENCE); - - // perform convolution - int fIndex = 0; - short sum = 0; - - for (int r = -HALF_FILTER_SIZE; r <= HALF_FILTER_SIZE; r++) - { - int curRow = r * localRowLen; - for (int c = -HALF_FILTER_SIZE; c <= HALF_FILTER_SIZE; c++, fIndex++) - { - if (!FLIP_RB){ - // sum += dot(rgb_weights, cached[ myLocal + curRow + c ]) * filter[ fIndex ]; - sum += (cached[ myLocal + curRow + c ].x / 3 + cached[ myLocal + curRow + c ].y / 2 + cached[ myLocal + curRow + c ].z / 9) * filter[ fIndex ]; - } else { - // sum += dot(bgr_weights, cached[ myLocal + curRow + c ]) * filter[ fIndex ]; - sum += (cached[ myLocal + curRow + c ].x / 9 + cached[ myLocal + curRow + c ].y / 2 + cached[ myLocal + curRow + c ].z / 3) * filter[ fIndex ]; - } - } - } - output[my] = sum; - } -} \ No newline at end of file diff --git a/system/camerad/imgproc/pool.cl b/system/camerad/imgproc/pool.cl deleted file mode 100644 index d674b5f363..0000000000 --- a/system/camerad/imgproc/pool.cl +++ /dev/null @@ -1,34 +0,0 @@ -// calculate variance in each subregion -__kernel void var_pool( - const __global char * input, - __global ushort * output // should not be larger than 128*128 so uint16 -) -{ - const int xidx = get_global_id(0) + ROI_X_MIN; - const int yidx = get_global_id(1) + ROI_Y_MIN; - - const int size = X_PITCH * Y_PITCH; - - float fsum = 0; - char mean, max; - - for (int i = 0; i < size; i++) { - int x_offset = i % X_PITCH; - int y_offset = i / X_PITCH; - fsum += input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X]; - max = input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X]>max ? input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X]:max; - } - - mean = convert_char_rte(fsum / size); - - float fvar = 0; - for (int i = 0; i < size; i++) { - int x_offset = i % X_PITCH; - int y_offset = i / X_PITCH; - fvar += (input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X] - mean) * (input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X] - mean); - } - - fvar = fvar / size; - - output[(xidx-ROI_X_MIN)+(yidx-ROI_Y_MIN)*(ROI_X_MAX-ROI_X_MIN+1)] = convert_ushort_rte(5 * fvar + convert_float_rte(max)); -} \ No newline at end of file diff --git a/system/camerad/imgproc/utils.cc b/system/camerad/imgproc/utils.cc deleted file mode 100644 index a7bbeb9e86..0000000000 --- a/system/camerad/imgproc/utils.cc +++ /dev/null @@ -1,106 +0,0 @@ -#include "system/camerad/imgproc/utils.h" - -#include -#include -#include -#include -#include - -const int16_t lapl_conv_krnl[9] = {0, 1, 0, - 1, -4, 1, - 0, 1, 0}; - -// calculate score based on laplacians in one area -uint16_t get_lapmap_one(const int16_t *lap, int x_pitch, int y_pitch) { - const int size = x_pitch * y_pitch; - // avg and max of roi - int16_t max = 0; - int sum = 0; - for (int i = 0; i < size; ++i) { - const int16_t v = lap[i]; - sum += v; - if (v > max) max = v; - } - - const int16_t mean = sum / size; - - // var of roi - int var = 0; - for (int i = 0; i < size; ++i) { - var += std::pow(lap[i] - mean, 2); - } - - const float fvar = (float)var / size; - return std::min(5 * fvar + max, (float)65535); -} - -bool is_blur(const uint16_t *lapmap, const size_t size) { - float bad_sum = 0; - for (int i = 0; i < size; i++) { - if (lapmap[i] < LM_THRESH) { - bad_sum += 1 / (float)size; - } - } - return (bad_sum > LM_PREC_THRESH); -} - -static cl_program build_conv_program(cl_device_id device_id, cl_context context, int image_w, int image_h, int filter_size) { - char args[4096]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DIMAGE_W=%d -DIMAGE_H=%d -DFLIP_RB=%d " - "-DFILTER_SIZE=%d -DHALF_FILTER_SIZE=%d -DTWICE_HALF_FILTER_SIZE=%d -DHALF_FILTER_SIZE_IMAGE_W=%d", - image_w, image_h, 1, - filter_size, filter_size/2, (filter_size/2)*2, (filter_size/2)*image_w); - return cl_program_from_file(context, device_id, "imgproc/conv.cl", args); -} - -LapConv::LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int rgb_stride, int filter_size) - : width(rgb_width / NUM_SEGMENTS_X), height(rgb_height / NUM_SEGMENTS_Y), rgb_stride(rgb_stride), - roi_buf(width * height * 3), result_buf(width * height) { - - prg = build_conv_program(device_id, ctx, width, height, filter_size); - krnl = CL_CHECK_ERR(clCreateKernel(prg, "rgb2gray_conv2d", &err)); - // TODO: Removed CL_MEM_SVM_FINE_GRAIN_BUFFER, confirm it doesn't matter - roi_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, roi_buf.size() * sizeof(roi_buf[0]), NULL, &err)); - result_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, result_buf.size() * sizeof(result_buf[0]), NULL, &err)); - filter_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, - 9 * sizeof(int16_t), (void *)&lapl_conv_krnl, &err)); -} - -LapConv::~LapConv() { - CL_CHECK(clReleaseMemObject(roi_cl)); - CL_CHECK(clReleaseMemObject(result_cl)); - CL_CHECK(clReleaseMemObject(filter_cl)); - CL_CHECK(clReleaseKernel(krnl)); - CL_CHECK(clReleaseProgram(prg)); -} - -uint16_t LapConv::Update(cl_command_queue q, const uint8_t *rgb_buf, const int roi_id) { - // sharpness scores - const int x_offset = ROI_X_MIN + roi_id % (ROI_X_MAX - ROI_X_MIN + 1); - const int y_offset = ROI_Y_MIN + roi_id / (ROI_X_MAX - ROI_X_MIN + 1); - - const uint8_t *rgb_offset = rgb_buf + y_offset * height * rgb_stride + x_offset * width * 3; - for (int i = 0; i < height; ++i) { - memcpy(&roi_buf[i * width * 3], &rgb_offset[i * rgb_stride], width * 3); - } - - constexpr int local_mem_size = (CONV_LOCAL_WORKSIZE + 2 * (3 / 2)) * (CONV_LOCAL_WORKSIZE + 2 * (3 / 2)) * (3 * sizeof(uint8_t)); - const size_t global_work_size[] = {(size_t)width, (size_t)height}; - const size_t local_work_size[] = {CONV_LOCAL_WORKSIZE, CONV_LOCAL_WORKSIZE}; - - CL_CHECK(clEnqueueWriteBuffer(q, roi_cl, CL_TRUE, 0, roi_buf.size() * sizeof(roi_buf[0]), roi_buf.data(), 0, 0, 0)); - CL_CHECK(clSetKernelArg(krnl, 0, sizeof(cl_mem), (void *)&roi_cl)); - CL_CHECK(clSetKernelArg(krnl, 1, sizeof(cl_mem), (void *)&result_cl)); - CL_CHECK(clSetKernelArg(krnl, 2, sizeof(cl_mem), (void *)&filter_cl)); - CL_CHECK(clSetKernelArg(krnl, 3, local_mem_size, 0)); - cl_event conv_event; - CL_CHECK(clEnqueueNDRangeKernel(q, krnl, 2, NULL, global_work_size, local_work_size, 0, 0, &conv_event)); - CL_CHECK(clWaitForEvents(1, &conv_event)); - CL_CHECK(clReleaseEvent(conv_event)); - CL_CHECK(clEnqueueReadBuffer(q, result_cl, CL_TRUE, 0, - result_buf.size() * sizeof(result_buf[0]), result_buf.data(), 0, 0, 0)); - - return get_lapmap_one(result_buf.data(), width, height); -} diff --git a/system/camerad/imgproc/utils.h b/system/camerad/imgproc/utils.h deleted file mode 100644 index 94323b15c5..0000000000 --- a/system/camerad/imgproc/utils.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "common/clutil.h" - -#define NUM_SEGMENTS_X 8 -#define NUM_SEGMENTS_Y 6 - -#define ROI_X_MIN 1 -#define ROI_X_MAX 6 -#define ROI_Y_MIN 2 -#define ROI_Y_MAX 3 - -#define LM_THRESH 120 -#define LM_PREC_THRESH 0.9 // 90 perc is blur -#define CONV_LOCAL_WORKSIZE 16 - -class LapConv { -public: - LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int rgb_stride, int filter_size); - ~LapConv(); - uint16_t Update(cl_command_queue q, const uint8_t *rgb_buf, const int roi_id); - -private: - cl_mem roi_cl, result_cl, filter_cl; - cl_program prg; - cl_kernel krnl; - const int width, height; - const int rgb_stride; - std::vector roi_buf; - std::vector result_buf; -}; - -bool is_blur(const uint16_t *lapmap, const size_t size); diff --git a/system/camerad/main.cc b/system/camerad/main.cc index 35a3329f30..19de21c9bb 100644 --- a/system/camerad/main.cc +++ b/system/camerad/main.cc @@ -8,7 +8,7 @@ int main(int argc, char *argv[]) { if (Hardware::PC()) { - printf("camerad is not meant to run on PC\n"); + printf("exiting, camerad is not meant to run on PC\n"); return 0; } diff --git a/system/camerad/snapshot/snapshot.py b/system/camerad/snapshot/snapshot.py index ede97d1a79..8c1b6084c7 100755 --- a/system/camerad/snapshot/snapshot.py +++ b/system/camerad/snapshot/snapshot.py @@ -13,7 +13,6 @@ from openpilot.system.hardware import PC from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.selfdrive.manager.process_config import managed_processes -LM_THRESH = 120 # defined in system/camerad/imgproc/utils.h VISION_STREAMS = { "roadCameraState": VisionStreamType.VISION_STREAM_ROAD, From f058b5d64eed31878533a85f25eb53386ff9e330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 1 Dec 2023 21:13:37 -0800 Subject: [PATCH 107/724] webrtcd: webrtc streaming server (audio/video/cereal) (#30186) * WebRTCClient and WebRTCServer abstractions * webrtc client implementation * Interactive test scripts * Send localDescriptions as offer/asnwer, as they are different * Tracks need to be added after setting remote description for multi-cam streaming to work * Remove WebRTCStreamingMetadata * Wait for tracks * Move stuff to separate files, rename some things * Refactor everything, create WebRTCStreamBuilder for both offer and answers * ta flight done time to grind * wait for incoming tracks and channels * Dummy track and frame reader track. Fix timing. * dt based on camera type * first trial of the new api * Fix audio track * methods for checking for incoming tracks * Web migration part 2 * Fixes for stream api * use rtc description for web.py * experimental cereal proxy * remove old code from bodyav * fix is_started * serialize session description * fix audio * messaging channel wrapper * fix audiotrack * h264 codec preference * Add codec preference to tracks * override sdp codecs * add logging * Move cli stuff to separate file * slight cleanup * Fix audio track * create codec_mime inside force_codec function * fix incoming media estimation * move builders to __init__ * stream updates following builders * Update example script * web.py support for new builder * web speaker fixes * StreamingMediaInfo API * Move things around * should_add_data_channel rename * is_connected_and_ready * fix linter errors * make cli executable * remove dumb comments * logging support * fix parse_info_from_offer * improve type annotations * satisfy linters * Support for waiting for disconnection * Split device tracks into video/audio files. Move audio speaker to audio.py * default dt for dummy video track * Fix cli * new speaker fixes * Remove almost all functionality from web.py * webrtcd * continue refactoring web.py * after handling joystick reset in controlsd with #30409, controls are not necessary anymore * ping endpoint * Update js files to at least support what worked previously * Fixes after some tests on the body * Streaming fixes * Remove the use of WebRTCStreamBuilder. Subclass use is now required * Add todo * delete all streams on shutdown * Replace lastPing with lastChannelMessageTime * Update ping text only if rtc is still on * That should affect the chart too * Fix paths in web * use protocol in SSLContext * remove warnings since aiortc is not used directly anymore * check if task is done in stop * remove channel handler wrapper, since theres only one channel * Move things around * Moved webrtc abstractions to separate repository * Moved webrtcd to tools/webrtc * Update imports * Add bodyrtc as dependency * Add webrtcd to process_config * Remove usage of DummyVideoStreamTrack * Add main to webrtcd * Move webrtcd to system * Fix imports * Move cereal proxy logic outside of runner * Incoming proxy abstractions * Add some tests * Make it executable * Fix process config * Fix imports * Additional tests. Add tests to pyproject.toml * Update poetry lock * New line * Bump aiortc to 1.6.0 * Added teleoprtc_repo as submodule, and linked its source dir * Add init file to webrtc module * Handle aiortc warnings * Ignore deprecation warnings * Ignore resource warning too * Ignore the warnings * find free port for test_webrtcd * Start process inside the test case * random sleep test * test 2 * Test endpoint function instead * Update comment * Add system/webrtc to release * default arguments for body fields * Add teleoprtc to release * Bump teleoprtc * Exclude teleoprtc from static analysis * Use separate event loop for stream session tests --- .gitmodules | 3 + .pre-commit-config.yaml | 6 +- poetry.lock | 81 +++---- pyproject.toml | 4 + release/files_common | 7 + selfdrive/manager/process_config.py | 1 + system/webrtc/__init__.py | 0 system/webrtc/device/audio.py | 110 +++++++++ system/webrtc/device/video.py | 69 ++++++ system/webrtc/tests/test_stream_session.py | 108 +++++++++ system/webrtc/tests/test_webrtcd.py | 60 +++++ system/webrtc/webrtcd.py | 237 +++++++++++++++++++ teleoprtc | 1 + teleoprtc_repo | 1 + tools/bodyteleop/bodyav.py | 159 ------------- tools/bodyteleop/static/js/jsmain.js | 10 +- tools/bodyteleop/static/js/webrtc.js | 144 +++++------- tools/bodyteleop/web.py | 259 +++++++-------------- 18 files changed, 787 insertions(+), 473 deletions(-) create mode 100644 system/webrtc/__init__.py create mode 100644 system/webrtc/device/audio.py create mode 100644 system/webrtc/device/video.py create mode 100755 system/webrtc/tests/test_stream_session.py create mode 100755 system/webrtc/tests/test_webrtcd.py create mode 100755 system/webrtc/webrtcd.py create mode 120000 teleoprtc create mode 160000 teleoprtc_repo delete mode 100644 tools/bodyteleop/bodyav.py diff --git a/.gitmodules b/.gitmodules index 4ba149cb2d..73f832b1d6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,9 @@ [submodule "body"] path = body url = ../../commaai/body.git +[submodule "teleoprtc_repo"] + path = teleoprtc_repo + url = ../../commaai/teleoprtc [submodule "tinygrad"] path = tinygrad_repo url = https://github.com/geohot/tinygrad.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6a46ad507..2109c18b25 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: rev: v2.2.6 hooks: - id: codespell - exclude: '^(third_party/)|(body/)|(cereal/)|(panda/)|(opendbc/)|(rednose/)|(rednose_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' + exclude: '^(third_party/)|(body/)|(cereal/)|(panda/)|(opendbc/)|(rednose/)|(rednose_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' args: # if you've got a short variable name that's getting flagged, add it here - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints @@ -39,12 +39,12 @@ repos: language: system types: [python] args: ['--explicit-package-bases', '--local-partial-types'] - exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' + exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.1.6 hooks: - id: ruff - exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' + exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' - repo: local hooks: - id: cppcheck diff --git a/poetry.lock b/poetry.lock index b05dcae11e..1461d5457c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -112,66 +112,39 @@ ifaddr = ">=0.2.0" [[package]] name = "aiortc" -version = "1.5.0" +version = "1.6.0" description = "An implementation of WebRTC and ORTC" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "aiortc-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1d3f2d6cc22fae5ea57b0371895b7830e878b9e3705fd3742b3453cdfa0fd51"}, - {file = "aiortc-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2eaf758b5e0bb16f22a9aeb8ab88eb947345f47e2e46cfca18b2815d44726c4e"}, - {file = "aiortc-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee76f6b30d7f39442ba7ac25d58114f077ead1460c5632d0c9e18179d01ad419"}, - {file = "aiortc-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a766052d93474e9bf4186465298b7c8fb9af062ef7f83ba33f191baa79dac1e"}, - {file = "aiortc-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fec292636978ed50728f1ce9b7a9f0d7d2e38bd0b920bb53e091e5728b79e231"}, - {file = "aiortc-1.5.0-cp310-cp310-win32.whl", hash = "sha256:27e879b73377d4b94bd86e4c3e8cd8913905fdca1de90a9a4efb0d9d3779dbf4"}, - {file = "aiortc-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:a720d0dd53553f6dfc28a53bee2ffce4f13283b4cbbc7db548000054cc63a4f9"}, - {file = "aiortc-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5e8cbfce84badd9a8355819343570bbec1e4eef725996cad6aebe4cc3d03ae8"}, - {file = "aiortc-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7931512dbb2ff91fb78f5512ad9ca96546452d7bb627f61bd7393bf59ee48ad3"}, - {file = "aiortc-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6abeb857a98014fc97265891ebf4fd989987d2ee091e0844e3c8fc543b6e2f0"}, - {file = "aiortc-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dead42dc3a31570fb6f5b94f9be9c78e28b1dc045f71489858116840f299862e"}, - {file = "aiortc-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a1a8081ba6d7cabc5896d10462cb50f6db7a8ccf34e6aa3e6c4a0d2d5bc5db5"}, - {file = "aiortc-1.5.0-cp311-cp311-win32.whl", hash = "sha256:cbd5d35bd34b22b8f711c708d266889c973c0dcb38da14a2a9f757266987a181"}, - {file = "aiortc-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:6749145e3d527ac98c80837d72fd832b0c403eded3546aeb7cec6f25592b4d5e"}, - {file = "aiortc-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:50e8e8903cf55f6f2cda9b61c115fca8e444d48f299cdd071980a3b5cec594fa"}, - {file = "aiortc-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15218a1b81f4fa1521f3b839eefdce638b34c46306e8eaf069cee7283fe8c838"}, - {file = "aiortc-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bca7c7bbd3619296b5737a810dd0e2fc7f6264e767fca10e65a709a443bf39"}, - {file = "aiortc-1.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f1d88ae0f8b3047a279e4da06f09a35777cfbe0a9177ca8b053865a98a67912"}, - {file = "aiortc-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:f86b68b182537022d4ada49a7723c7a56f39372d6fbc31a29f57315d335cdc29"}, - {file = "aiortc-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4bbc2f2b97651f7aa6f5e82c69a22590901962454fc02617c4a559a1b51c21a"}, - {file = "aiortc-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7243bce7c3b95e47e56ddf961fbf6015702ddbbf3579b0bbf18c6173b6a6357a"}, - {file = "aiortc-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:883db8926deaf01fdcd32fbd74fcf055db63e968324ceff41d5a46ec86dff90c"}, - {file = "aiortc-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd663f67344e6fe240c6372f620988db5285c9b1b8336306e9fec76ffb4e5493"}, - {file = "aiortc-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe2766f5a7a8e10b445cbf83a510b791a88180c7b1f9adef3f730840fa208afc"}, - {file = "aiortc-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba212562025843e8d9faf66e6156b682148f8f9995a19e5c66e8ea802f3fa121"}, - {file = "aiortc-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b7432c9c78e68811ee060ade8b0f867ac42a21677e4d1a9136bb88cd93ab8299"}, - {file = "aiortc-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ac6e285d4035298f3025b5767dc8f8b0a5a81b2b8744aaa19c75a8fe76f3ad8"}, - {file = "aiortc-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa3c9306d892635dd9c38cc83c6ba67fb608c7da289f422d40f9542e104b7a0f"}, - {file = "aiortc-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:55dab49a38a212556adadb85ea06f6041d2a9e537e01092f9160b21b186b5039"}, - {file = "aiortc-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db93641b6f31315b8fd4c81e14881aef28fbb0700f220926f82909baedfa9888"}, - {file = "aiortc-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f63fd1168df72498afe0ee06555cc86b8496115ef128519a01d1ea8e404784b8"}, - {file = "aiortc-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e436f49617887f2009c6ada872c2da201e3c8010b387e7c1057eab229ae438c3"}, - {file = "aiortc-1.5.0-cp39-cp39-win32.whl", hash = "sha256:6f23495d4e11610117d1bad8686d42d529168d463687a1a1e0bec795d1ec33ce"}, - {file = "aiortc-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:76206601082e39fdb56d86221729f04f8bd79d65f9fd6b82121947eabf7efd6d"}, - {file = "aiortc-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3b2a3b4c120a73242ea0b843ecc3efeaea32861682c771e67f7f08f9d18fddc"}, - {file = "aiortc-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3f6511f2442f49dfaf4e69865b47e0d6d95440fee2f66e6a03a8b4fa1b28e3"}, - {file = "aiortc-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ae221c734864c8749c27cc8add22d296ef3e06ae5f6982dbcbe2d0976b10e1"}, - {file = "aiortc-1.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7732c825ee96e9bc7fb779a4008be768e7663f7f9bf0ab3cccdd412dd7f1c820"}, - {file = "aiortc-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:56ffdd67161488c6d934b090a8c2d277bba8806906a3a18493f46b42976569c1"}, - {file = "aiortc-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f73ba04ca3f331b0ddea0b4ff78424ba30bfd7a49d0b8bd926c75a66ad60f447"}, - {file = "aiortc-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69eedeec467bd7bcac7ace6ad398133e27f18eeae195a3ad0ffda74255a8b812"}, - {file = "aiortc-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e095e5fe22f5a2efd4e0657abec1fea7aca864cb32ae3f0816fbcd340a4f2b7"}, - {file = "aiortc-1.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffd6899a5d3db4356d2c17521021032468931ae168545b1ff4815764a5e2873"}, - {file = "aiortc-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:af3eed686d621af93befd7e68bd73d6d8a8aa3e721e8fa3ce7e21b3225e37c38"}, - {file = "aiortc-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:15e222a308dcfc44351bd9acff21723c8065cdcd75d6649d53b2986ada64b6be"}, - {file = "aiortc-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9ecd61c42e6a78c089805a47542a68eeeec6ba98bf7a2e30cafa3d3f4e94a7f"}, - {file = "aiortc-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d839437c6000d77511ff1889133150f23fbc8a7365971260c45ce06ff007b0f"}, - {file = "aiortc-1.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:025847ad6b8c5686f2895394e1de92c043e20e7d90c266de201eef1b1108c8df"}, - {file = "aiortc-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:85583166ab9c9052d2539bee3ba05f27af7f7b93b15c2259c2fc1bd3de5b31d5"}, - {file = "aiortc-1.5.0.tar.gz", hash = "sha256:82b4131d84f862e24e1c3550b73f78412cc9554140a2575577eb3f04675bbad2"}, + {file = "aiortc-1.6.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4675e04d8441797fef6c8a669b3a67d750670d1b897f08886072a084d743e07d"}, + {file = "aiortc-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:836c0686fca67f142c52e5af8043206c2bb702ad0ddcdc94ef19caf1c22f8d54"}, + {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33f846abd753935881158751994a51f14e345762130688b19c26cab42c01266f"}, + {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4bd45397945a5323bd077d43c702a3a991d75023f23649c1d18df5d80c221d"}, + {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f93561b515ff478b068bb9b047d8b1e896c747dcf3ee465463047c51a7bea24d"}, + {file = "aiortc-1.6.0-cp38-abi3-win32.whl", hash = "sha256:325f847397af2892aa051dc2880a75e9bd79f535cc05ec8f4538b5ed098b3c5d"}, + {file = "aiortc-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:98b118d53ae874126b2e9ec6bb1397ea169b85550c4bd5453e279507ff7f0cf9"}, + {file = "aiortc-1.6.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6677018833a5d648754c99c70e6c6f6d4f3942682cda07ed5afa73422f8a009a"}, + {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:090936c719225e50cd4d66f476e6c17293a8062cf7687a1baa5080f3c90ec8b1"}, + {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e60d5bc269d4d12f1f5f47e2c17aa3799f3b5c8b73fd6d8d246ddc11bb29776"}, + {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b9496b4a3c8cd9d32afb6d5bce07f9170831ec44a20ab8af54d53879aafc8"}, + {file = "aiortc-1.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0a41f4c0b31e45548e7c7397ef1aecc4be49ab68afd8fa134c07581fe0b3a9c2"}, + {file = "aiortc-1.6.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:64a9939016edbc8f300de6189983c983753827813ac9acd9b5be8ce61cc32684"}, + {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:813e7985665c94a0e3387b66e39dba6c751e5e588aedbca06d7e52068c6e37fb"}, + {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:672d0b4ad35c4d8f014f44a142aa55529ec82cfe2809226e1275e35a71fd4422"}, + {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1526a1904174bb11958b8f7e93f01f37f80df2190e5089f0501984bdef79595e"}, + {file = "aiortc-1.6.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cecfa5f462e73218cadc9acd8013cb3a0d9007a4515bceba6e7755d77bb80061"}, + {file = "aiortc-1.6.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:febca6773de18a6bb9e5569ae87c8be55ed184695f1f9fc99aa4744a7b0375f8"}, + {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5ba865be9708713397ec584ed1baeb2f15d2fa9c32594ce19a41ffa6e2517cb"}, + {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9759a3c00e46ba1c3499dbf5a8513ae37ba65b940a56b0e7fa5070478e9379f"}, + {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cff7868663d9d1c74e237b86e45126022466240439a5f63c3440e3acdf0305b"}, + {file = "aiortc-1.6.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9f20479d0dd06116ac81d332850fab874d83b561a73fd7252d218f55c6bd5b79"}, + {file = "aiortc-1.6.0.tar.gz", hash = "sha256:08cffbcde3401b33731b1d4169b9ff860b0aaaca200b62e10ce5978238671ad7"}, ] [package.dependencies] aioice = ">=0.9.0,<1.0.0" -av = ">=9.0.0,<11.0.0" +av = ">=9.0.0,<12.0.0" cffi = ">=1.0.0" cryptography = ">=2.2" google-crc32c = ">=1.1" @@ -180,7 +153,7 @@ pylibsrtp = ">=0.5.6" pyopenssl = ">=23.1.0" [package.extras] -dev = ["aiohttp (>=3.7.0)", "coverage (>=5.0)", "numpy (>=1.19.0)"] +dev = ["aiohttp (>=3.7.0)", "coverage[toml] (>=7.2.2)", "numpy (>=1.19.0)"] [[package]] name = "aiosignal" diff --git a/pyproject.toml b/pyproject.toml index 1426905d06..0e55e2ac60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ testpaths = [ "system/proclogd", "system/tests", "system/ubloxd", + "system/webrtc", "tools/lib/tests", "tools/replay", "tools/cabana" @@ -43,6 +44,8 @@ exclude = [ "rednose_repo/", "tinygrad/", "tinygrad_repo/", + "teleoprtc/", + "teleoprtc_repo/", "third_party/", ] @@ -186,6 +189,7 @@ exclude = [ "opendbc", "rednose_repo", "tinygrad_repo", + "teleoprtc", "third_party", ] flake8-implicit-str-concat.allow-multiline=false diff --git a/release/files_common b/release/files_common index af5d156d41..d92be1c3a6 100644 --- a/release/files_common +++ b/release/files_common @@ -281,6 +281,11 @@ system/sensord/sensors/*.cc system/sensord/sensors/*.h system/sensord/pigeond.py +system/webrtc/__init__.py +system/webrtc/webrtcd.py +system/webrtc/device/audio.py +system/webrtc/device/video.py + selfdrive/thermald/thermald.py selfdrive/thermald/power_monitoring.py selfdrive/thermald/fan_controller.py @@ -439,6 +444,8 @@ third_party/qt5/larch64/bin/** scripts/update_now.sh scripts/stop_updater.sh +teleoprtc/** + rednose_repo/site_scons/site_tools/rednose_filter.py rednose/.gitignore rednose/** diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 6f974b0687..4ad2574188 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -84,6 +84,7 @@ procs = [ # debug procs NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), + PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar), PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), ] diff --git a/system/webrtc/__init__.py b/system/webrtc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/system/webrtc/device/audio.py b/system/webrtc/device/audio.py new file mode 100644 index 0000000000..3c78be6752 --- /dev/null +++ b/system/webrtc/device/audio.py @@ -0,0 +1,110 @@ +import asyncio +import io +from typing import Optional, List, Tuple + +import aiortc +import av +import numpy as np +import pyaudio + + +class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): + PYAUDIO_TO_AV_FORMAT_MAP = { + pyaudio.paUInt8: 'u8', + pyaudio.paInt16: 's16', + pyaudio.paInt24: 's24', + pyaudio.paInt32: 's32', + pyaudio.paFloat32: 'flt', + } + + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: Optional[int] = None): + super().__init__() + + self.p = pyaudio.PyAudio() + chunk_size = int(packet_time * rate) + self.stream = self.p.open(format=audio_format, + channels=channels, + rate=rate, + frames_per_buffer=chunk_size, + input=True, + input_device_index=device_index) + self.format = audio_format + self.rate = rate + self.channels = channels + self.packet_time = packet_time + self.chunk_size = chunk_size + self.pts = 0 + + async def recv(self): + mic_data = self.stream.read(self.chunk_size) + mic_array = np.frombuffer(mic_data, dtype=np.int16) + mic_array = np.expand_dims(mic_array, axis=0) + layout = 'stereo' if self.channels > 1 else 'mono' + frame = av.AudioFrame.from_ndarray(mic_array, format=self.PYAUDIO_TO_AV_FORMAT_MAP[self.format], layout=layout) + frame.rate = self.rate + frame.pts = self.pts + self.pts += frame.samples + + return frame + + +class AudioOutputSpeaker: + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: Optional[int] = None): + + chunk_size = int(packet_time * rate) + self.p = pyaudio.PyAudio() + self.buffer = io.BytesIO() + self.channels = channels + self.stream = self.p.open(format=audio_format, + channels=channels, + rate=rate, + frames_per_buffer=chunk_size, + output=True, + output_device_index=device_index, + stream_callback=self.__pyaudio_callback) + self.tracks_and_tasks: List[Tuple[aiortc.MediaStreamTrack, Optional[asyncio.Task]]] = [] + + def __pyaudio_callback(self, in_data, frame_count, time_info, status): + if self.buffer.getbuffer().nbytes < frame_count * self.channels * 2: + buff = b'\x00\x00' * frame_count * self.channels + elif self.buffer.getbuffer().nbytes > 115200: # 3x the usual read size + self.buffer.seek(0) + buff = self.buffer.read(frame_count * self.channels * 4) + buff = buff[:frame_count * self.channels * 2] + self.buffer.seek(2) + else: + self.buffer.seek(0) + buff = self.buffer.read(frame_count * self.channels * 2) + self.buffer.seek(2) + return (buff, pyaudio.paContinue) + + async def __consume(self, track): + while True: + try: + frame = await track.recv() + except aiortc.MediaStreamError: + return + + self.buffer.write(bytes(frame.planes[0])) + + def hasTrack(self, track: aiortc.MediaStreamTrack) -> bool: + return any(t == track for t, _ in self.tracks_and_tasks) + + def addTrack(self, track: aiortc.MediaStreamTrack): + if not self.hasTrack(track): + self.tracks_and_tasks.append((track, None)) + + def start(self): + for index, (track, task) in enumerate(self.tracks_and_tasks): + if task is None: + self.tracks_and_tasks[index] = (track, asyncio.create_task(self.__consume(track))) + + def stop(self): + for _, task in self.tracks_and_tasks: + if task is not None: + task.cancel() + + self.tracks_and_tasks = [] + self.stream.stop_stream() + self.stream.close() + self.p.terminate() diff --git a/system/webrtc/device/video.py b/system/webrtc/device/video.py new file mode 100644 index 0000000000..1ecb6dbd74 --- /dev/null +++ b/system/webrtc/device/video.py @@ -0,0 +1,69 @@ +import asyncio +from typing import Optional + +import av +from teleoprtc.tracks import TiciVideoStreamTrack + +from cereal import messaging +from openpilot.tools.lib.framereader import FrameReader +from openpilot.common.realtime import DT_MDL, DT_DMON + + +class LiveStreamVideoStreamTrack(TiciVideoStreamTrack): + camera_to_sock_mapping = { + "driver": "livestreamDriverEncodeData", + "wideRoad": "livestreamWideRoadEncodeData", + "road": "livestreamRoadEncodeData", + } + + def __init__(self, camera_type: str): + dt = DT_DMON if camera_type == "driver" else DT_MDL + super().__init__(camera_type, dt) + + self._sock = messaging.sub_sock(self.camera_to_sock_mapping[camera_type], conflate=True) + self._pts = 0 + + async def recv(self): + while True: + msg = messaging.recv_one_or_none(self._sock) + if msg is not None: + break + await asyncio.sleep(0.005) + + evta = getattr(msg, msg.which()) + + packet = av.Packet(evta.header + evta.data) + packet.time_base = self._time_base + packet.pts = self._pts + + self.log_debug("track sending frame %s", self._pts) + self._pts += self._dt * self._clock_rate + + return packet + + def codec_preference(self) -> Optional[str]: + return "H264" + + +class FrameReaderVideoStreamTrack(TiciVideoStreamTrack): + def __init__(self, input_file: str, dt: float = DT_MDL, camera_type: str = "driver"): + super().__init__(camera_type, dt) + + frame_reader = FrameReader(input_file) + self._frames = [frame_reader.get(i, pix_fmt="rgb24") for i in range(frame_reader.frame_count)] + self._frame_count = len(self.frames) + self._frame_index = 0 + self._pts = 0 + + async def recv(self): + self.log_debug("track sending frame %s", self._pts) + img = self._frames[self._frame_index] + + new_frame = av.VideoFrame.from_ndarray(img, format="rgb24") + new_frame.pts = self._pts + new_frame.time_base = self._time_base + + self._frame_index = (self._frame_index + 1) % self._frame_count + self._pts = await self.next_pts(self._pts) + + return new_frame diff --git a/system/webrtc/tests/test_stream_session.py b/system/webrtc/tests/test_stream_session.py new file mode 100755 index 0000000000..2173c3806b --- /dev/null +++ b/system/webrtc/tests/test_stream_session.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +import asyncio +import unittest +from unittest.mock import Mock, MagicMock, patch +import json +# for aiortc and its dependencies +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +from aiortc import RTCDataChannel +from aiortc.mediastreams import VIDEO_CLOCK_RATE, VIDEO_TIME_BASE +import capnp +import pyaudio + +from cereal import messaging, log + +from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy +from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack +from openpilot.system.webrtc.device.audio import AudioInputStreamTrack +from openpilot.common.realtime import DT_DMON + + +class TestStreamSession(unittest.TestCase): + def setUp(self): + self.loop = asyncio.new_event_loop() + + def tearDown(self): + self.loop.stop() + self.loop.close() + + def test_outgoing_proxy(self): + test_msg = log.Event.new_message() + test_msg.logMonoTime = 123 + test_msg.valid = True + test_msg.customReservedRawData0 = b"test" + expected_dict = {"type": "customReservedRawData0", "logMonoTime": 123, "valid": True, "data": "test"} + expected_json = json.dumps(expected_dict).encode() + + channel = Mock(spec=RTCDataChannel) + mocked_submaster = messaging.SubMaster(["customReservedRawData0"]) + def mocked_update(t): + mocked_submaster.update_msgs(0, [test_msg]) + + with patch.object(messaging.SubMaster, "update", side_effect=mocked_update): + proxy = CerealOutgoingMessageProxy(mocked_submaster) + proxy.add_channel(channel) + + proxy.update() + + channel.send.assert_called_once_with(expected_json) + + def test_incoming_proxy(self): + tested_msgs = [ + {"type": "customReservedRawData0", "data": "test"}, # primitive + {"type": "can", "data": [{"address": 0, "busTime": 0, "dat": "", "src": 0}]}, # list + {"type": "testJoystick", "data": {"axes": [0, 0], "buttons": [False]}}, # dict + ] + + mocked_pubmaster = MagicMock(spec=messaging.PubMaster) + + proxy = CerealIncomingMessageProxy(mocked_pubmaster) + + for msg in tested_msgs: + proxy.send(json.dumps(msg).encode()) + + mocked_pubmaster.send.assert_called_once() + mt, md = mocked_pubmaster.send.call_args.args + self.assertEqual(mt, msg["type"]) + self.assertIsInstance(md, capnp._DynamicStructBuilder) + self.assertTrue(hasattr(md, msg["type"])) + + mocked_pubmaster.reset_mock() + + def test_livestream_track(self): + fake_msg = messaging.new_message("livestreamDriverEncodeData") + + config = {"receive.return_value": fake_msg.to_bytes()} + with patch("cereal.messaging.SubSocket", spec=True, **config): + track = LiveStreamVideoStreamTrack("driver") + + self.assertTrue(track.id.startswith("driver")) + self.assertEqual(track.codec_preference(), "H264") + + for i in range(5): + packet = self.loop.run_until_complete(track.recv()) + self.assertEqual(packet.time_base, VIDEO_TIME_BASE) + self.assertEqual(packet.pts, int(i * DT_DMON * VIDEO_CLOCK_RATE)) + self.assertEqual(packet.size, 0) + + def test_input_audio_track(self): + packet_time, rate = 0.02, 16000 + sample_count = int(packet_time * rate) + mocked_stream = MagicMock(spec=pyaudio.Stream) + mocked_stream.read.return_value = b"\x00" * 2 * sample_count + + config = {"open.side_effect": lambda *args, **kwargs: mocked_stream} + with patch("pyaudio.PyAudio", spec=True, **config): + track = AudioInputStreamTrack(audio_format=pyaudio.paInt16, packet_time=packet_time, rate=rate) + + for i in range(5): + frame = self.loop.run_until_complete(track.recv()) + self.assertEqual(frame.rate, rate) + self.assertEqual(frame.samples, sample_count) + self.assertEqual(frame.pts, i * sample_count) + + +if __name__ == "__main__": + unittest.main() diff --git a/system/webrtc/tests/test_webrtcd.py b/system/webrtc/tests/test_webrtcd.py new file mode 100755 index 0000000000..b48bf6bc19 --- /dev/null +++ b/system/webrtc/tests/test_webrtcd.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +import asyncio +import json +import unittest +from unittest.mock import MagicMock, AsyncMock +# for aiortc and its dependencies +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +from openpilot.system.webrtc.webrtcd import get_stream + +import aiortc +from teleoprtc import WebRTCOfferBuilder + + +class TestWebrtcdProc(unittest.IsolatedAsyncioTestCase): + async def assertCompletesWithTimeout(self, awaitable, timeout=1): + try: + async with asyncio.timeout(timeout): + await awaitable + except asyncio.TimeoutError: + self.fail("Timeout while waiting for awaitable to complete") + + async def test_webrtcd(self): + mock_request = MagicMock() + async def connect(offer): + body = {'sdp': offer.sdp, 'cameras': offer.video, 'bridge_services_in': [], 'bridge_services_out': []} + mock_request.json.side_effect = AsyncMock(return_value=body) + response = await get_stream(mock_request) + response_json = json.loads(response.text) + return aiortc.RTCSessionDescription(**response_json) + + builder = WebRTCOfferBuilder(connect) + builder.offer_to_receive_video_stream("road") + builder.offer_to_receive_audio_stream() + builder.add_messaging() + + stream = builder.stream() + + await self.assertCompletesWithTimeout(stream.start()) + await self.assertCompletesWithTimeout(stream.wait_for_connection()) + + self.assertTrue(stream.has_incoming_video_track("road")) + self.assertTrue(stream.has_incoming_audio_track()) + self.assertTrue(stream.has_messaging_channel()) + + video_track, audio_track = stream.get_incoming_video_track("road"), stream.get_incoming_audio_track() + await self.assertCompletesWithTimeout(video_track.recv()) + await self.assertCompletesWithTimeout(audio_track.recv()) + + await self.assertCompletesWithTimeout(stream.stop()) + + # cleanup, very implementation specific, test may break if it changes + self.assertTrue(mock_request.app["streams"].__setitem__.called, "Implementation changed, please update this test") + _, session = mock_request.app["streams"].__setitem__.call_args.args + await self.assertCompletesWithTimeout(session.post_run_cleanup()) + + +if __name__ == "__main__": + unittest.main() diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py new file mode 100755 index 0000000000..237cae78a1 --- /dev/null +++ b/system/webrtc/webrtcd.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python3 + +import argparse +import asyncio +import json +import uuid +import logging +from dataclasses import dataclass, field +from typing import Any, List, Optional, Union + +# aiortc and its dependencies have lots of internal warnings :( +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +import aiortc +from aiortc.mediastreams import VideoStreamTrack, AudioStreamTrack +from aiortc.contrib.media import MediaBlackhole +from aiohttp import web +import capnp +from teleoprtc import WebRTCAnswerBuilder +from teleoprtc.info import parse_info_from_offer + +from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack +from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker + +from cereal import messaging + + +class CerealOutgoingMessageProxy: + def __init__(self, sm: messaging.SubMaster): + self.sm = sm + self.channels: List[aiortc.RTCDataChannel] = [] + + def add_channel(self, channel: aiortc.RTCDataChannel): + self.channels.append(channel) + + def to_json(self, msg_content: Any): + if isinstance(msg_content, capnp._DynamicStructReader): + msg_dict = msg_content.to_dict() + elif isinstance(msg_content, capnp._DynamicListReader): + msg_dict = [self.to_json(msg) for msg in msg_content] + elif isinstance(msg_content, bytes): + msg_dict = msg_content.decode() + else: + msg_dict = msg_content + + return msg_dict + + def update(self): + # this is blocking in async context... + self.sm.update(0) + for service, updated in self.sm.updated.items(): + if not updated: + continue + msg_dict = self.to_json(self.sm[service]) + mono_time, valid = self.sm.logMonoTime[service], self.sm.valid[service] + outgoing_msg = {"type": service, "logMonoTime": mono_time, "valid": valid, "data": msg_dict} + encoded_msg = json.dumps(outgoing_msg).encode() + for channel in self.channels: + channel.send(encoded_msg) + + +class CerealIncomingMessageProxy: + def __init__(self, pm: messaging.PubMaster): + self.pm = pm + + def send(self, message: bytes): + msg_json = json.loads(message) + msg_type, msg_data = msg_json["type"], msg_json["data"] + size = None + if not isinstance(msg_data, dict): + size = len(msg_data) + + msg = messaging.new_message(msg_type, size=size) + setattr(msg, msg_type, msg_data) + self.pm.send(msg_type, msg) + + +class CerealProxyRunner: + def __init__(self, proxy: CerealOutgoingMessageProxy): + self.proxy = proxy + self.is_running = False + self.task = None + self.logger = logging.getLogger("webrtcd") + + def start(self): + assert self.task is None + self.task = asyncio.create_task(self.run()) + + def stop(self): + if self.task is None or self.task.done(): + return + self.task.cancel() + self.task = None + + async def run(self): + while True: + try: + self.proxy.update() + except Exception as ex: + self.logger.error("Cereal outgoing proxy failure: %s", ex) + await asyncio.sleep(0.01) + + +class StreamSession: + def __init__(self, sdp: str, cameras: List[str], incoming_services: List[str], outgoing_services: List[str], debug_mode: bool = False): + config = parse_info_from_offer(sdp) + builder = WebRTCAnswerBuilder(sdp) + + assert len(cameras) == config.n_expected_camera_tracks, "Incoming stream has misconfigured number of video tracks" + for cam in cameras: + track = LiveStreamVideoStreamTrack(cam) if not debug_mode else VideoStreamTrack() + builder.add_video_stream(cam, track) + if config.expected_audio_track: + track = AudioInputStreamTrack() if not debug_mode else AudioStreamTrack() + builder.add_audio_stream(track) + if config.incoming_audio_track: + self.audio_output_cls = AudioOutputSpeaker if not debug_mode else MediaBlackhole + builder.offer_to_receive_audio_stream() + + self.stream = builder.stream() + self.identifier = str(uuid.uuid4()) + + self.outgoing_bridge = CerealOutgoingMessageProxy(messaging.SubMaster(outgoing_services)) + self.incoming_bridge = CerealIncomingMessageProxy(messaging.PubMaster(incoming_services)) + self.outgoing_bridge_runner = CerealProxyRunner(self.outgoing_bridge) + + self.audio_output: Optional[Union[AudioOutputSpeaker, MediaBlackhole]] = None + self.run_task: Optional[asyncio.Task] = None + self.logger = logging.getLogger("webrtcd") + self.logger.info("New stream session (%s), cameras %s, audio in %s out %s, incoming services %s, outgoing services %s", + self.identifier, cameras, config.incoming_audio_track, config.expected_audio_track, incoming_services, outgoing_services) + + def start(self): + self.run_task = asyncio.create_task(self.run()) + + def stop(self): + if self.run_task.done(): + return + self.run_task.cancel() + self.run_task = None + asyncio.run(self.post_run_cleanup()) + + async def get_answer(self): + return await self.stream.start() + + async def message_handler(self, message: bytes): + try: + self.incoming_bridge.send(message) + except Exception as ex: + self.logger.error("Cereal incoming proxy failure: %s", ex) + + async def run(self): + try: + await self.stream.wait_for_connection() + if self.stream.has_messaging_channel(): + self.stream.set_message_handler(self.message_handler) + channel = self.stream.get_messaging_channel() + self.outgoing_bridge_runner.proxy.add_channel(channel) + self.outgoing_bridge_runner.start() + if self.stream.has_incoming_audio_track(): + track = self.stream.get_incoming_audio_track(buffered=False) + self.audio_output = self.audio_output_cls() + self.audio_output.addTrack(track) + self.audio_output.start() + self.logger.info("Stream session (%s) connected", self.identifier) + + await self.stream.wait_for_disconnection() + await self.post_run_cleanup() + + self.logger.info("Stream session (%s) ended", self.identifier) + except Exception as ex: + self.logger.error("Stream session failure: %s", ex) + + async def post_run_cleanup(self): + await self.stream.stop() + self.outgoing_bridge_runner.stop() + if self.audio_output: + self.audio_output.stop() + + +@dataclass +class StreamRequestBody: + sdp: str + cameras: List[str] + bridge_services_in: List[str] = field(default_factory=list) + bridge_services_out: List[str] = field(default_factory=list) + + +async def get_stream(request: web.Request): + stream_dict, debug_mode = request.app['streams'], request.app['debug'] + raw_body = await request.json() + body = StreamRequestBody(**raw_body) + + session = StreamSession(body.sdp, body.cameras, body.bridge_services_in, body.bridge_services_out, debug_mode) + answer = await session.get_answer() + session.start() + + stream_dict[session.identifier] = session + + return web.json_response({"sdp": answer.sdp, "type": answer.type}) + + +async def on_shutdown(app: web.Application): + for session in app['streams'].values(): + session.stop() + del app['streams'] + + +def webrtcd_thread(host: str, port: int, debug: bool): + logging.basicConfig(level=logging.CRITICAL, handlers=[logging.StreamHandler()]) + logging_level = logging.DEBUG if debug else logging.INFO + logging.getLogger("WebRTCStream").setLevel(logging_level) + logging.getLogger("webrtcd").setLevel(logging_level) + + app = web.Application() + + app['streams'] = dict() + app['debug'] = debug + app.on_shutdown.append(on_shutdown) + app.router.add_post("/stream", get_stream) + + web.run_app(app, host=host, port=port) + + +def main(): + parser = argparse.ArgumentParser(description="WebRTC daemon") + parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to listen on") + parser.add_argument("--port", type=int, default=5001, help="Port to listen on") + parser.add_argument("--debug", action="store_true", help="Enable debug mode") + args = parser.parse_args() + + webrtcd_thread(args.host, args.port, args.debug) + + +if __name__=="__main__": + main() diff --git a/teleoprtc b/teleoprtc new file mode 120000 index 0000000000..3d3dbc8dea --- /dev/null +++ b/teleoprtc @@ -0,0 +1 @@ +teleoprtc_repo/teleoprtc \ No newline at end of file diff --git a/teleoprtc_repo b/teleoprtc_repo new file mode 160000 index 0000000000..8ec4778685 --- /dev/null +++ b/teleoprtc_repo @@ -0,0 +1 @@ +Subproject commit 8ec477868591eed9a6136a44f16428bc0468b4e9 diff --git a/tools/bodyteleop/bodyav.py b/tools/bodyteleop/bodyav.py deleted file mode 100644 index 3f11f8d4f2..0000000000 --- a/tools/bodyteleop/bodyav.py +++ /dev/null @@ -1,159 +0,0 @@ -import asyncio -import io -import numpy as np -import pyaudio -import wave - -from aiortc.contrib.media import MediaBlackhole -from aiortc.mediastreams import AudioStreamTrack, MediaStreamError, MediaStreamTrack -from aiortc.mediastreams import VIDEO_CLOCK_RATE, VIDEO_TIME_BASE -from aiortc.rtcrtpsender import RTCRtpSender -from av import CodecContext, Packet -from pydub import AudioSegment -import cereal.messaging as messaging - -AUDIO_RATE = 16000 -SOUNDS = { - 'engage': '../../selfdrive/assets/sounds/engage.wav', - 'disengage': '../../selfdrive/assets/sounds/disengage.wav', - 'error': '../../selfdrive/assets/sounds/warning_immediate.wav', -} - - -def force_codec(pc, sender, forced_codec='video/VP9', stream_type="video"): - codecs = RTCRtpSender.getCapabilities(stream_type).codecs - codec = [codec for codec in codecs if codec.mimeType == forced_codec] - transceiver = next(t for t in pc.getTransceivers() if t.sender == sender) - transceiver.setCodecPreferences(codec) - - -class EncodedBodyVideo(MediaStreamTrack): - kind = "video" - - _start: float - _timestamp: int - - def __init__(self): - super().__init__() - sock_name = 'livestreamDriverEncodeData' - messaging.context = messaging.Context() - self.sock = messaging.sub_sock(sock_name, None, conflate=True) - self.pts = 0 - - async def recv(self) -> Packet: - while True: - msg = messaging.recv_one_or_none(self.sock) - if msg is not None: - break - await asyncio.sleep(0.005) - - evta = getattr(msg, msg.which()) - self.last_idx = evta.idx.encodeId - - packet = Packet(evta.header + evta.data) - packet.time_base = VIDEO_TIME_BASE - packet.pts = self.pts - self.pts += 0.05 * VIDEO_CLOCK_RATE - return packet - - -class WebClientSpeaker(MediaBlackhole): - def __init__(self): - super().__init__() - self.p = pyaudio.PyAudio() - self.buffer = io.BytesIO() - self.channels = 2 - self.stream = self.p.open(format=pyaudio.paInt16, channels=self.channels, rate=48000, frames_per_buffer=9600, - output=True, stream_callback=self.pyaudio_callback) - - def pyaudio_callback(self, in_data, frame_count, time_info, status): - if self.buffer.getbuffer().nbytes < frame_count * self.channels * 2: - buff = np.zeros((frame_count, 2), dtype=np.int16).tobytes() - elif self.buffer.getbuffer().nbytes > 115200: # 3x the usual read size - self.buffer.seek(0) - buff = self.buffer.read(frame_count * self.channels * 4) - buff = buff[:frame_count * self.channels * 2] - self.buffer.seek(2) - else: - self.buffer.seek(0) - buff = self.buffer.read(frame_count * self.channels * 2) - self.buffer.seek(2) - return (buff, pyaudio.paContinue) - - async def consume(self, track): - while True: - try: - frame = await track.recv() - except MediaStreamError: - return - bio = bytes(frame.planes[0]) - self.buffer.write(bio) - - async def start(self): - for track, task in self._MediaBlackhole__tracks.items(): - if task is None: - self._MediaBlackhole__tracks[track] = asyncio.ensure_future(self.consume(track)) - - async def stop(self): - for task in self._MediaBlackhole__tracks.values(): - if task is not None: - task.cancel() - self._MediaBlackhole__tracks = {} - self.stream.stop_stream() - self.stream.close() - self.p.terminate() - - -class BodyMic(AudioStreamTrack): - def __init__(self): - super().__init__() - - self.sample_rate = AUDIO_RATE - self.AUDIO_PTIME = 0.020 # 20ms audio packetization - self.samples = int(self.AUDIO_PTIME * self.sample_rate) - self.FORMAT = pyaudio.paInt16 - self.CHANNELS = 2 - self.RATE = self.sample_rate - self.CHUNK = int(AUDIO_RATE * 0.020) - self.p = pyaudio.PyAudio() - self.mic_stream = self.p.open(format=self.FORMAT, channels=1, rate=self.RATE, input=True, frames_per_buffer=self.CHUNK) - - self.codec = CodecContext.create('pcm_s16le', 'r') - self.codec.sample_rate = self.RATE - self.codec.channels = 2 - self.audio_samples = 0 - self.chunk_number = 0 - - async def recv(self): - mic_data = self.mic_stream.read(self.CHUNK) - mic_sound = AudioSegment(mic_data, sample_width=2, channels=1, frame_rate=self.RATE) - mic_sound = AudioSegment.from_mono_audiosegments(mic_sound, mic_sound) - mic_sound += 3 # increase volume by 3db - packet = Packet(mic_sound.raw_data) - frame = self.codec.decode(packet)[0] - frame.pts = self.audio_samples - self.audio_samples += frame.samples - self.chunk_number = self.chunk_number + 1 - return frame - - -async def play_sound(sound): - chunk = 5120 - with wave.open(SOUNDS[sound], 'rb') as wf: - def callback(in_data, frame_count, time_info, status): - data = wf.readframes(frame_count) - return data, pyaudio.paContinue - - p = pyaudio.PyAudio() - stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), - channels=wf.getnchannels(), - rate=wf.getframerate(), - output=True, - frames_per_buffer=chunk, - stream_callback=callback) - stream.start_stream() - while stream.is_active(): - await asyncio.sleep(0) - stream.stop_stream() - stream.close() - p.terminate() diff --git a/tools/bodyteleop/static/js/jsmain.js b/tools/bodyteleop/static/js/jsmain.js index f521905724..83205a876b 100644 --- a/tools/bodyteleop/static/js/jsmain.js +++ b/tools/bodyteleop/static/js/jsmain.js @@ -1,5 +1,5 @@ import { handleKeyX, executePlan } from "./controls.js"; -import { start, stop, last_ping } from "./webrtc.js"; +import { start, stop, lastChannelMessageTime, playSoundRequest } from "./webrtc.js"; export var pc = null; export var dc = null; @@ -9,10 +9,14 @@ document.addEventListener('keyup', (e)=>(handleKeyX(e.key.toLowerCase(), 0))); $(".keys").bind("mousedown touchstart", (e)=>handleKeyX($(e.target).attr('id').replace('key-', ''), 1)); $(".keys").bind("mouseup touchend", (e)=>handleKeyX($(e.target).attr('id').replace('key-', ''), 0)); $("#plan-button").click(executePlan); +$(".sound").click((e)=>{ + const sound = $(e.target).attr('id').replace('sound-', '') + return playSoundRequest(sound); +}); setInterval( () => { const dt = new Date().getTime(); - if ((dt - last_ping) > 1000) { + if ((dt - lastChannelMessageTime) > 1000) { $(".pre-blob").removeClass('blob'); $("#battery").text("-"); $("#ping-time").text('-'); @@ -20,4 +24,4 @@ setInterval( () => { } }, 5000); -start(pc, dc); \ No newline at end of file +start(pc, dc); diff --git a/tools/bodyteleop/static/js/webrtc.js b/tools/bodyteleop/static/js/webrtc.js index e2f6583c17..165a2ce6c4 100644 --- a/tools/bodyteleop/static/js/webrtc.js +++ b/tools/bodyteleop/static/js/webrtc.js @@ -1,9 +1,34 @@ import { getXY } from "./controls.js"; import { pingPoints, batteryPoints, chartPing, chartBattery } from "./plots.js"; -export let dcInterval = null; -export let batteryInterval = null; -export let last_ping = null; +export let controlCommandInterval = null; +export let latencyInterval = null; +export let lastChannelMessageTime = null; + + +export function offerRtcRequest(sdp, type) { + return fetch('/offer', { + body: JSON.stringify({sdp: sdp, type: type}), + headers: {'Content-Type': 'application/json'}, + method: 'POST' + }); +} + + +export function playSoundRequest(sound) { + return fetch('/sound', { + body: JSON.stringify({sound}), + headers: {'Content-Type': 'application/json'}, + method: 'POST' + }); +} + + +export function pingHeadRequest() { + return fetch('/', { + method: 'HEAD' + }); +} export function createPeerConnection(pc) { @@ -45,16 +70,7 @@ export function negotiate(pc) { }); }).then(function() { var offer = pc.localDescription; - return fetch('/offer', { - body: JSON.stringify({ - sdp: offer.sdp, - type: offer.type, - }), - headers: { - 'Content-Type': 'application/json' - }, - method: 'POST' - }); + return offerRtcRequest(offer.sdp, offer.type); }).then(function(response) { console.log(response); return response.json(); @@ -86,25 +102,6 @@ export const constraints = { }; -export function createDummyVideoTrack() { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - - const frameWidth = 5; // Set the width of the frame - const frameHeight = 5; // Set the height of the frame - canvas.width = frameWidth; - canvas.height = frameHeight; - - context.fillStyle = 'black'; - context.fillRect(0, 0, frameWidth, frameHeight); - - const stream = canvas.captureStream(); - const videoTrack = stream.getVideoTracks()[0]; - - return videoTrack; -} - - export function start(pc, dc) { pc = createPeerConnection(pc); @@ -138,71 +135,56 @@ export function start(pc, dc) { alert('Could not acquire media: ' + err); }); - // add a fake video? - // const dummyVideoTrack = createDummyVideoTrack(); - // const dummyMediaStream = new MediaStream(); - // dummyMediaStream.addTrack(dummyVideoTrack); - // pc.addTrack(dummyVideoTrack, dummyMediaStream); - - // setInterval(() => {pc.getStats(null).then((stats) => {stats.forEach((report) => console.log(report))})}, 10000) - // var video = document.querySelector('video'); - // var print = function (e, f){console.log(e, f); video.requestVideoFrameCallback(print);}; - // video.requestVideoFrameCallback(print); - var parameters = {"ordered": true}; dc = pc.createDataChannel('data', parameters); dc.onclose = function() { - console.log("data channel closed"); - clearInterval(dcInterval); - clearInterval(batteryInterval); + clearInterval(controlCommandInterval); + clearInterval(latencyInterval); }; - function controlCommand() { + + function sendJoystickOverDataChannel() { const {x, y} = getXY(); - const dt = new Date().getTime(); - var message = JSON.stringify({type: 'control_command', x, y, dt}); + var message = JSON.stringify({type: "testJoystick", data: {axes: [x, y], buttons: [false]}}) dc.send(message); } - - function batteryLevel() { - var message = JSON.stringify({type: 'battery_level'}); - dc.send(message); + function checkLatency() { + const initialTime = new Date().getTime(); + pingHeadRequest().then(function() { + const currentTime = new Date().getTime(); + if (Math.abs(currentTime - lastChannelMessageTime) < 1000) { + const pingtime = currentTime - initialTime; + pingPoints.push({'x': currentTime, 'y': pingtime}); + if (pingPoints.length > 1000) { + pingPoints.shift(); + } + chartPing.update(); + $("#ping-time").text((pingtime) + "ms"); + } + }) } - dc.onopen = function() { - dcInterval = setInterval(controlCommand, 50); - batteryInterval = setInterval(batteryLevel, 10000); - controlCommand(); - batteryLevel(); - $(".sound").click((e)=>{ - const sound = $(e.target).attr('id').replace('sound-', '') - dc.send(JSON.stringify({type: 'play_sound', sound})); - }); + controlCommandInterval = setInterval(sendJoystickOverDataChannel, 50); + latencyInterval = setInterval(checkLatency, 1000); + sendJoystickOverDataChannel(); }; - let val_print_idx = 0; + const textDecoder = new TextDecoder(); + var carStaterIndex = 0; dc.onmessage = function(evt) { - const data = JSON.parse(evt.data); - if(val_print_idx == 0 && data.type === 'ping_time') { - const dt = new Date().getTime(); - const pingtime = dt - data.incoming_time; - pingPoints.push({'x': dt, 'y': pingtime}); - if (pingPoints.length > 1000) { - pingPoints.shift(); - } - chartPing.update(); - $("#ping-time").text((pingtime) + "ms"); - last_ping = dt; - $(".pre-blob").addClass('blob'); - } - val_print_idx = (val_print_idx + 1 ) % 20; - if(data.type === 'battery_level') { - $("#battery").text(data.value + "%"); - batteryPoints.push({'x': new Date().getTime(), 'y': data.value}); - if (batteryPoints.length > 1000) { + const text = textDecoder.decode(evt.data); + const msg = JSON.parse(text); + if (carStaterIndex % 100 == 0 && msg.type === 'carState') { + const batteryLevel = Math.round(msg.data.fuelGauge * 100); + $("#battery").text(batteryLevel + "%"); + batteryPoints.push({'x': new Date().getTime(), 'y': batteryLevel}); + if (batteryPoints.length > 1000) { batteryPoints.shift(); } chartBattery.update(); } + carStaterIndex += 1; + lastChannelMessageTime = new Date().getTime(); + $(".pre-blob").addClass('blob'); }; } diff --git a/tools/bodyteleop/web.py b/tools/bodyteleop/web.py index 929cfa26fe..2ec6eb037c 100644 --- a/tools/bodyteleop/web.py +++ b/tools/bodyteleop/web.py @@ -1,208 +1,121 @@ import asyncio +import dataclasses import json import logging import os import ssl -import uuid -import time +import subprocess -# aiortc and its dependencies have lots of internal warnings :( -import warnings -warnings.resetwarnings() -warnings.simplefilter("always") +from aiohttp import web, ClientSession +import pyaudio +import wave -from aiohttp import web -from aiortc import RTCPeerConnection, RTCSessionDescription - -import cereal.messaging as messaging from openpilot.common.basedir import BASEDIR -from openpilot.tools.bodyteleop.bodyav import BodyMic, WebClientSpeaker, force_codec, play_sound, MediaBlackhole, EncodedBodyVideo +from openpilot.system.webrtc.webrtcd import StreamRequestBody -from typing import Optional - -logger = logging.getLogger("pc") +logger = logging.getLogger("bodyteleop") logging.basicConfig(level=logging.INFO) -pcs: set[RTCPeerConnection] = set() -pm: Optional[messaging.PubMaster] = None -sm: Optional[messaging.SubMaster] = None TELEOPDIR = f"{BASEDIR}/tools/bodyteleop" +WEBRTCD_HOST, WEBRTCD_PORT = "localhost", 5001 +## UTILS +async def play_sound(sound): + SOUNDS = { + 'engage': 'selfdrive/assets/sounds/engage.wav', + 'disengage': 'selfdrive/assets/sounds/disengage.wav', + 'error': 'selfdrive/assets/sounds/warning_immediate.wav', + } + assert sound in SOUNDS + + chunk = 5120 + with wave.open(os.path.join(BASEDIR, SOUNDS[sound]), 'rb') as wf: + def callback(in_data, frame_count, time_info, status): + data = wf.readframes(frame_count) + return data, pyaudio.paContinue + + p = pyaudio.PyAudio() + stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), + channels=wf.getnchannels(), + rate=wf.getframerate(), + output=True, + frames_per_buffer=chunk, + stream_callback=callback) + stream.start_stream() + while stream.is_active(): + await asyncio.sleep(0) + stream.stop_stream() + stream.close() + p.terminate() + +## SSL +def create_ssl_cert(cert_path, key_path): + try: + proc = subprocess.run(f'openssl req -x509 -newkey rsa:4096 -nodes -out {cert_path} -keyout {key_path} \ + -days 365 -subj "/C=US/ST=California/O=commaai/OU=comma body"', + stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + proc.check_returncode() + except subprocess.CalledProcessError as ex: + raise ValueError(f"Error creating SSL certificate:\n[stdout]\n{proc.stdout.decode()}\n[stderr]\n{proc.stderr.decode()}") from ex + + +def create_ssl_context(): + cert_path = os.path.join(TELEOPDIR, 'cert.pem') + key_path = os.path.join(TELEOPDIR, 'key.pem') + if not os.path.exists(cert_path) or not os.path.exists(key_path): + logger.info("Creating certificate...") + create_ssl_cert(cert_path, key_path) + else: + logger.info("Certificate exists!") + ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER) + ssl_context.load_cert_chain(cert_path, key_path) + + return ssl_context + +## ENDPOINTS async def index(request): - content = open(TELEOPDIR + "/static/index.html", "r").read() - now = time.monotonic() - request.app['mutable_vals']['last_send_time'] = now - request.app['mutable_vals']['last_override_time'] = now - request.app['mutable_vals']['prev_command'] = [] - request.app['mutable_vals']['find_person'] = False - - return web.Response(content_type="text/html", text=content) + with open(os.path.join(TELEOPDIR, "static", "index.html"), "r") as f: + content = f.read() + return web.Response(content_type="text/html", text=content) -async def control_body(data, app): - now = time.monotonic() - if (data['type'] == 'dummy_controls') and (now < (app['mutable_vals']['last_send_time'] + 0.2)): - return - if (data['type'] == 'control_command') and (app['mutable_vals']['prev_command'] == [data['x'], data['y']] and data['x'] == 0 and data['y'] == 0): - return - - logger.info(str(data)) - x = max(-1.0, min(1.0, data['x'])) - y = max(-1.0, min(1.0, data['y'])) - dat = messaging.new_message('testJoystick') - dat.testJoystick.axes = [x, y] - dat.testJoystick.buttons = [False] - pm.send('testJoystick', dat) - app['mutable_vals']['last_send_time'] = now - if (data['type'] == 'control_command'): - app['mutable_vals']['last_override_time'] = now - app['mutable_vals']['prev_command'] = [data['x'], data['y']] +async def ping(request): + return web.Response(text="pong") -async def dummy_controls_msg(app): - while True: - if 'last_send_time' in app['mutable_vals']: - this_time = time.monotonic() - if (app['mutable_vals']['last_send_time'] + 0.2) < this_time: - await control_body({'type': 'dummy_controls', 'x': 0, 'y': 0}, app) - await asyncio.sleep(0.2) +async def sound(request): + params = await request.json() + sound_to_play = params["sound"] - -async def start_background_tasks(app): - app['bgtask_dummy_controls_msg'] = asyncio.create_task(dummy_controls_msg(app)) - - -async def stop_background_tasks(app): - app['bgtask_dummy_controls_msg'].cancel() - await app['bgtask_dummy_controls_msg'] + await play_sound(sound_to_play) + return web.json_response({"status": "ok"}) async def offer(request): - logger.info("\n\n\nnewoffer!\n\n") - params = await request.json() - offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) - speaker = WebClientSpeaker() - blackhole = MediaBlackhole() + body = StreamRequestBody(params["sdp"], ["driver"], ["testJoystick"], ["carState"]) + body_json = json.dumps(dataclasses.asdict(body)) - pc = RTCPeerConnection() - pc_id = "PeerConnection(%s)" % uuid.uuid4() - pcs.add(pc) - - def log_info(msg, *args): - logger.info(pc_id + " " + msg, *args) - - log_info("Created for %s", request.remote) - - @pc.on("datachannel") - def on_datachannel(channel): - request.app['mutable_vals']['remote_channel'] = channel - - @channel.on("message") - async def on_message(message): - data = json.loads(message) - if data['type'] == 'control_command': - await control_body(data, request.app) - times = { - 'type': 'ping_time', - 'incoming_time': data['dt'], - 'outgoing_time': int(time.time() * 1000), - } - channel.send(json.dumps(times)) - if data['type'] == 'battery_level': - sm.update(timeout=0) - if sm.updated['carState']: - channel.send(json.dumps({'type': 'battery_level', 'value': int(sm['carState'].fuelGauge * 100)})) - if data['type'] == 'play_sound': - logger.info(f"Playing sound: {data['sound']}") - await play_sound(data['sound']) - if data['type'] == 'find_person': - request.app['mutable_vals']['find_person'] = data['value'] - - @pc.on("connectionstatechange") - async def on_connectionstatechange(): - log_info("Connection state is %s", pc.connectionState) - if pc.connectionState == "failed": - await pc.close() - pcs.discard(pc) - - @pc.on('track') - def on_track(track): - logger.info(f"Track received: {track.kind}") - if track.kind == "audio": - speaker.addTrack(track) - elif track.kind == "video": - blackhole.addTrack(track) - - @track.on("ended") - async def on_ended(): - log_info("Remote %s track ended", track.kind) - if track.kind == "audio": - await speaker.stop() - elif track.kind == "video": - await blackhole.stop() - - video_sender = pc.addTrack(EncodedBodyVideo()) - force_codec(pc, video_sender, forced_codec='video/H264') - _ = pc.addTrack(BodyMic()) - - await pc.setRemoteDescription(offer) - await speaker.start() - await blackhole.start() - answer = await pc.createAnswer() - await pc.setLocalDescription(answer) - - return web.Response( - content_type="application/json", - text=json.dumps( - {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type} - ), - ) - - -async def on_shutdown(app): - coros = [pc.close() for pc in pcs] - await asyncio.gather(*coros) - pcs.clear() - - -async def run(cmd): - proc = await asyncio.create_subprocess_shell( - cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE - ) - stdout, stderr = await proc.communicate() - logger.info("Created key and cert!") - if stdout: - logger.info(f'[stdout]\n{stdout.decode()}') - if stderr: - logger.info(f'[stderr]\n{stderr.decode()}') + logger.info("Sending offer to webrtcd...") + webrtcd_url = f"http://{WEBRTCD_HOST}:{WEBRTCD_PORT}/stream" + async with ClientSession() as session, session.post(webrtcd_url, data=body_json) as resp: + assert resp.status == 200 + answer = await resp.json() + return web.json_response(answer) def main(): - global pm, sm - pm = messaging.PubMaster(['testJoystick']) - sm = messaging.SubMaster(['carState', 'logMessage']) # App needs to be HTTPS for microphone and audio autoplay to work on the browser - cert_path = TELEOPDIR + '/cert.pem' - key_path = TELEOPDIR + '/key.pem' - if (not os.path.exists(cert_path)) or (not os.path.exists(key_path)): - asyncio.run(run(f'openssl req -x509 -newkey rsa:4096 -nodes -out {cert_path} -keyout {key_path} \ - -days 365 -subj "/C=US/ST=California/O=commaai/OU=comma body"')) - else: - logger.info("Certificate exists!") - ssl_context = ssl.SSLContext() - ssl_context.load_cert_chain(cert_path, key_path) + ssl_context = create_ssl_context() + app = web.Application() app['mutable_vals'] = {} - app.on_shutdown.append(on_shutdown) - app.router.add_post("/offer", offer) app.router.add_get("/", index) - app.router.add_static('/static', TELEOPDIR + '/static') - app.on_startup.append(start_background_tasks) - app.on_cleanup.append(stop_background_tasks) + app.router.add_get("/ping", ping, allow_head=True) + app.router.add_post("/offer", offer) + app.router.add_post("/sound", sound) + app.router.add_static('/static', os.path.join(TELEOPDIR, 'static')) web.run_app(app, access_log=None, host="0.0.0.0", port=5000, ssl_context=ssl_context) From 54517c0638c99b7a40b146376def1ac324d07074 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 2 Dec 2023 00:27:29 -0600 Subject: [PATCH 108/724] Toyota: LTA cleanup (#30579) * have been running this for a few weeks: good * set setme_x3 closer to stock * add comment to values about additional EPS torque rate safety * rename some variables * should use vEgoRaw here to match panda! * switch * more notes * specify * smaller * for * oof --- selfdrive/car/toyota/carcontroller.py | 21 ++++++++++++++------- selfdrive/car/toyota/interface.py | 2 +- selfdrive/car/toyota/toyotacan.py | 12 +++++++++--- selfdrive/car/toyota/values.py | 3 ++- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 2a02a57d86..0fff0fcf6b 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -21,8 +21,8 @@ MAX_USER_TORQUE = 500 # LTA limits # EPS ignores commands above this angle and causes PCS to fault -MAX_STEER_ANGLE = 94.9461 # deg -MAX_DRIVER_TORQUE_ALLOWANCE = 150 # slightly above steering pressed allows some resistance when changing lanes +MAX_LTA_ANGLE = 94.9461 # deg +MAX_LTA_DRIVER_TORQUE_ALLOWANCE = 150 # slightly above steering pressed allows some resistance when changing lanes class CarController: @@ -71,25 +71,32 @@ class CarController: apply_angle = actuators.steeringAngleDeg + CS.out.steeringAngleOffsetDeg # Angular rate limit based on speed - apply_angle = apply_std_steer_angle_limits(apply_angle, self.last_angle, CS.out.vEgo, self.params) + apply_angle = apply_std_steer_angle_limits(apply_angle, self.last_angle, CS.out.vEgoRaw, self.params) if not lat_active: apply_angle = CS.out.steeringAngleDeg + CS.out.steeringAngleOffsetDeg - self.last_angle = clip(apply_angle, -MAX_STEER_ANGLE, MAX_STEER_ANGLE) + self.last_angle = clip(apply_angle, -MAX_LTA_ANGLE, MAX_LTA_ANGLE) self.last_steer = apply_steer - # toyota can trace shows this message at 42Hz, with counter adding alternatively 1 and 2; + # toyota can trace shows STEERING_LKA at 42Hz, with counter adding alternatively 1 and 2; # sending it at 100Hz seem to allow a higher rate limit, as the rate limit seems imposed # on consecutive messages can_sends.append(toyotacan.create_steer_command(self.packer, apply_steer, apply_steer_req)) + + # STEERING_LTA does not seem to allow more rate by sending faster, and may wind up easier if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR: lta_active = lat_active and self.CP.steerControlType == SteerControlType.angle + # cut steering torque with SETME_X64 when either EPS torque or driver torque is above + # the threshold, to limit max lateral acceleration and for driver torque blending respectively. full_torque_condition = (abs(CS.out.steeringTorqueEps) < self.params.STEER_MAX and - abs(CS.out.steeringTorque) < MAX_DRIVER_TORQUE_ALLOWANCE) + abs(CS.out.steeringTorque) < MAX_LTA_DRIVER_TORQUE_ALLOWANCE) + + # SETME_X64 at 0 ramps down torque at roughly the max down rate of 1500 units/sec setme_x64 = 100 if lta_active and full_torque_condition else 0 - can_sends.append(toyotacan.create_lta_steer_command(self.packer, self.last_angle, lta_active, self.frame // 2, setme_x64)) + can_sends.append(toyotacan.create_lta_steer_command(self.packer, self.CP.steerControlType, self.last_angle, + lta_active, self.frame // 2, setme_x64)) # *** gas and brake *** if self.CP.enableGasInterceptor and CC.longActive: diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 9cf86d69ab..ea28d18302 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -33,7 +33,7 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA # LTA control can be more delayed and winds up more often - ret.steerActuatorDelay = 0.25 + ret.steerActuatorDelay = 0.18 ret.steerLimitTimer = 0.8 else: CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index ed0237c1be..9c0b0643d6 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -1,3 +1,8 @@ +from cereal import car + +SteerControlType = car.CarParams.SteerControlType + + def create_steer_command(packer, steer, steer_req): """Creates a CAN message for the Toyota Steer Command.""" @@ -9,13 +14,14 @@ def create_steer_command(packer, steer, steer_req): return packer.make_can_msg("STEERING_LKA", 0, values) -def create_lta_steer_command(packer, steer_angle, steer_req, frame, setme_x64): +def create_lta_steer_command(packer, steer_control_type, steer_angle, steer_req, frame, setme_x64): """Creates a CAN message for the Toyota LTA Steer Command.""" values = { "COUNTER": frame + 128, - "SETME_X1": 1, - "SETME_X3": 3, + "SETME_X1": 1, # suspected LTA feature availability + # 1 for TSS 2.5 cars, 3 for TSS 2.0. Send based on whether we're using LTA for lateral control + "SETME_X3": 1 if steer_control_type == SteerControlType.angle else 3, "PERCENTAGE": 100, "SETME_X64": setme_x64, "ANGLE": 0, diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 4a1011982c..10702269d6 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -27,7 +27,8 @@ class CarControllerParams: # Assuming a steering ratio of 13.7: # Limit to ~2.0 m/s^3 up (7.5 deg/s), ~3.5 m/s^3 down (13 deg/s) at 75 mph # Worst case, the low speed limits will allow ~4.0 m/s^3 up (15 deg/s) and ~4.9 m/s^3 down (18 deg/s) at 75 mph, - # however the EPS has its own internal limits at all speeds which are less than that + # however the EPS has its own internal limits at all speeds which are less than that: + # Observed internal torque rate limit on TSS 2.5 Camry and RAV4 is ~1500 units/sec up and down when using LTA ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[5, 25], angle_v=[0.3, 0.15]) ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[5, 25], angle_v=[0.36, 0.26]) From 8e00ce672d78dcb2445b4bdfa1a9e557bb365233 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 1 Dec 2023 22:42:43 -0800 Subject: [PATCH 109/724] controlsd: remove old process replay sentinel (#30581) --- selfdrive/controls/controlsd.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 19ce407932..96a81dd441 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -179,8 +179,6 @@ class Controls: self.v_cruise_helper = VCruiseHelper(self.CP) self.recalibrating_seen = False - # TODO: no longer necessary, aside from process replay - self.sm['liveParameters'].valid = True self.can_log_mono_time = 0 self.startup_event = get_startup_event(car_recognized, controller_available, len(self.CP.carFw) > 0) From b97e5b0e03a5e22ef0ae6d6f5c2523592ee25540 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 1 Dec 2023 22:48:29 -0800 Subject: [PATCH 110/724] rename carEvents -> onroadEvents (#30577) * rename carEvents -> onroadEvents * bump cereal * bump cereal --- cereal | 2 +- selfdrive/controls/controlsd.py | 12 ++++++------ selfdrive/debug/count_events.py | 4 ++-- selfdrive/test/process_replay/README.md | 2 +- selfdrive/test/process_replay/conftest.py | 2 +- selfdrive/test/process_replay/process_replay.py | 6 +++--- selfdrive/test/test_onroad.py | 6 +++--- selfdrive/test/test_time_to_onroad.py | 4 ++-- tools/sim/tests/test_sim_bridge.py | 4 ++-- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cereal b/cereal index 2cb2bfb015..65b34b4d15 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 2cb2bfb0154874775e56715ecf81f0e6b00538e9 +Subproject commit 65b34b4d152bcea6d49b821bf1da7189f6086b0f diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 96a81dd441..aca074c95a 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -64,7 +64,7 @@ class Controls: # Setup sockets self.pm = messaging.PubMaster(['sendcan', 'controlsState', 'carState', - 'carControl', 'carEvents', 'carParams']) + 'carControl', 'onroadEvents', 'carParams']) self.sensor_packets = ["accelerometer", "gyroscope"] self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"] @@ -212,7 +212,7 @@ class Controls: self.state = State.enabled def update_events(self, CS): - """Compute carEvents from carState""" + """Compute onroadEvents from carState""" self.events.clear() @@ -823,11 +823,11 @@ class Controls: cs_send.carState.events = car_events self.pm.send('carState', cs_send) - # carEvents - logged every second or on change + # onroadEvents - logged every second or on change if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev): - ce_send = messaging.new_message('carEvents', len(self.events)) - ce_send.carEvents = car_events - self.pm.send('carEvents', ce_send) + ce_send = messaging.new_message('onroadEvents', len(self.events)) + ce_send.onroadEvents = car_events + self.pm.send('onroadEvents', ce_send) self.events_prev = self.events.names.copy() # carParams - logged every 50 seconds (> 1 per segment) diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py index a148996d94..a2c3576b6f 100755 --- a/selfdrive/debug/count_events.py +++ b/selfdrive/debug/count_events.py @@ -32,8 +32,8 @@ if __name__ == "__main__": end_time = max(end_time, msg.logMonoTime) start_time = min(start_time, msg.logMonoTime) - if msg.which() == 'carEvents': - for e in msg.carEvents: + if msg.which() == 'onroadEvents': + for e in msg.onroadEvents: cnt_events[e.name] += 1 elif msg.which() == 'controlsState': if len(alerts) == 0 or alerts[-1][1] != msg.controlsState.alertType: diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index bec3eb9016..008a901010 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -31,7 +31,7 @@ optional arguments: --blacklist-procs BLACKLIST_PROCS Blacklist given processes from the test (e.g. controlsd) --blacklist-cars BLACKLIST_CARS Blacklist given cars from the test (e.g. HONDA) --ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. carState.events) - --ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. carEvents) + --ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents) --update-refs Updates reference logs using current commit --upload-only Skips testing processes and uploads logs from previous test run ``` diff --git a/selfdrive/test/process_replay/conftest.py b/selfdrive/test/process_replay/conftest.py index f3794d26ac..3f9744ed61 100644 --- a/selfdrive/test/process_replay/conftest.py +++ b/selfdrive/test/process_replay/conftest.py @@ -16,7 +16,7 @@ def pytest_addoption(parser: pytest.Parser): parser.addoption("--ignore-fields", type=str, nargs="*", default=[], help="Extra fields or msgs to ignore (e.g. carState.events)") parser.addoption("--ignore-msgs", type=str, nargs="*", default=[], - help="Msgs to ignore (e.g. carEvents)") + help="Msgs to ignore (e.g. onroadEvents)") parser.addoption("--update-refs", action="store_true", help="Updates reference logs using current commit") parser.addoption("--upload-only", action="store_true", diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index a26e964550..77c4ab1eab 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -442,8 +442,8 @@ def controlsd_config_callback(params, cfg, lr): controlsState = msg.controlsState if initialized: break - elif msg.which() == "carEvents": - initialized = car.CarEvent.EventName.controlsInitializing not in [e.name for e in msg.carEvents] + elif msg.which() == "onroadEvents": + initialized = car.CarEvent.EventName.controlsInitializing not in [e.name for e in msg.onroadEvents] assert controlsState is not None and initialized, "controlsState never initialized" params.put("ReplayControlsState", controlsState.as_builder().to_bytes()) @@ -465,7 +465,7 @@ CONFIGS = [ "modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope" ], - subs=["controlsState", "carState", "carControl", "sendcan", "carEvents", "carParams"], + subs=["controlsState", "carState", "carControl", "sendcan", "onroadEvents", "carParams"], ignore=["logMonoTime", "valid", "controlsState.startMonoTime", "controlsState.cumLagMs"], config_callback=controlsd_config_callback, init_callback=controlsd_fingerprint_callback, diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 17026dc69f..e89ce5c72b 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -408,7 +408,7 @@ class TestOnroad(unittest.TestCase): def test_startup(self): startup_alert = None for msg in self.lrs[0]: - # can't use carEvents because the first msg can be dropped while loggerd is starting up + # can't use onroadEvents because the first msg can be dropped while loggerd is starting up if msg.which() == "controlsState": startup_alert = msg.controlsState.alertText1 break @@ -417,8 +417,8 @@ class TestOnroad(unittest.TestCase): def test_engagable(self): no_entries = Counter() - for m in self.service_msgs['carEvents']: - for evt in m.carEvents: + for m in self.service_msgs['onroadEvents']: + for evt in m.onroadEvents: if evt.noEntry: no_entries[evt.name] += 1 diff --git a/selfdrive/test/test_time_to_onroad.py b/selfdrive/test/test_time_to_onroad.py index cc2cc6514e..aec49cb13a 100755 --- a/selfdrive/test/test_time_to_onroad.py +++ b/selfdrive/test/test_time_to_onroad.py @@ -18,7 +18,7 @@ def test_time_to_onroad(): proc = subprocess.Popen(["python", manager_path]) start_time = time.monotonic() - sm = messaging.SubMaster(['controlsState', 'deviceState', 'carEvents']) + sm = messaging.SubMaster(['controlsState', 'deviceState', 'onroadEvents']) try: # wait for onroad with Timeout(20, "timed out waiting to go onroad"): @@ -40,7 +40,7 @@ def test_time_to_onroad(): # once we're enageable, must be for the next few seconds for _ in range(500): sm.update(100) - assert sm['controlsState'].engageable, f"events: {sm['carEvents']}" + assert sm['controlsState'].engageable, f"events: {sm['onroadEvents']}" finally: proc.terminate() if proc.wait(60) is None: diff --git a/tools/sim/tests/test_sim_bridge.py b/tools/sim/tests/test_sim_bridge.py index b00527b322..2654526fa8 100644 --- a/tools/sim/tests/test_sim_bridge.py +++ b/tools/sim/tests/test_sim_bridge.py @@ -24,7 +24,7 @@ class TestSimBridgeBase(unittest.TestCase): p_manager = subprocess.Popen("./launch_openpilot.sh", cwd=SIM_DIR) self.processes.append(p_manager) - sm = messaging.SubMaster(['controlsState', 'carEvents', 'managerState']) + sm = messaging.SubMaster(['controlsState', 'onroadEvents', 'managerState']) q = Queue() carla_bridge = self.create_bridge() p_bridge = carla_bridge.run(q, retries=10) @@ -46,7 +46,7 @@ class TestSimBridgeBase(unittest.TestCase): sm.update() not_running = [p.name for p in sm['managerState'].processes if not p.running and p.shouldBeRunning] - car_event_issues = [event.name for event in sm['carEvents'] if any([event.noEntry, event.softDisable, event.immediateDisable])] + car_event_issues = [event.name for event in sm['onroadEvents'] if any([event.noEntry, event.softDisable, event.immediateDisable])] if sm.all_alive() and len(car_event_issues) == 0 and len(not_running) == 0: no_car_events_issues_once = True From 531e62fc033b06b54f11c702dcbde5982bfe41e6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 1 Dec 2023 23:57:54 -0800 Subject: [PATCH 111/724] clear non-release params on startup (#30583) --- common/params.cc | 2 +- common/params.h | 1 + common/params_pyx.pyx | 1 + selfdrive/controls/controlsd.py | 6 +++--- selfdrive/manager/manager.py | 2 ++ 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/common/params.cc b/common/params.cc index 54168165a1..d1d3513469 100644 --- a/common/params.cc +++ b/common/params.cc @@ -114,7 +114,7 @@ std::unordered_map keys = { {"DoReboot", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, - {"ExperimentalLongitudinalEnabled", PERSISTENT}, + {"ExperimentalLongitudinalEnabled", PERSISTENT | DEVELOPMENT_ONLY}, {"ExperimentalMode", PERSISTENT}, {"ExperimentalModeConfirmed", PERSISTENT}, {"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, diff --git a/common/params.h b/common/params.h index fbe0bba6b0..aa586a1581 100644 --- a/common/params.h +++ b/common/params.h @@ -10,6 +10,7 @@ enum ParamKeyType { CLEAR_ON_ONROAD_TRANSITION = 0x08, CLEAR_ON_OFFROAD_TRANSITION = 0x10, DONT_LOG = 0x20, + DEVELOPMENT_ONLY = 0x40, ALL = 0xFFFFFFFF }; diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx index de52c490b3..db8f496d30 100644 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -11,6 +11,7 @@ cdef extern from "common/params.h": CLEAR_ON_MANAGER_START CLEAR_ON_ONROAD_TRANSITION CLEAR_ON_OFFROAD_TRANSITION + DEVELOPMENT_ONLY ALL cdef cppclass c_Params "Params": diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index aca074c95a..421139e449 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -14,7 +14,7 @@ from cereal.visionipc import VisionIpcClient, VisionStreamType from openpilot.common.conversions import Conversions as CV from panda import ALTERNATIVE_EXPERIENCE from openpilot.system.swaglog import cloudlog -from openpilot.system.version import is_release_branch, get_short_branch +from openpilot.system.version import get_short_branch from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from openpilot.selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can from openpilot.selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET @@ -90,7 +90,7 @@ class Controls: get_one_can(self.can_sock) num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates) - experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled") and not is_release_branch() + experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled") self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], experimental_long_allowed, num_pandas) else: self.CI, self.CP = CI, CI.CP @@ -133,7 +133,7 @@ class Controls: put_nonblocking("CarParamsPersistent", cp_bytes) # cleanup old params - if not self.CP.experimentalLongitudinalAvailable or is_release_branch(): + if not self.CP.experimentalLongitudinalAvailable: self.params.remove("ExperimentalLongitudinalEnabled") if not self.CP.openpilotLongitudinalControl: self.params.remove("ExperimentalMode") diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index a739437de7..9be448499e 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -37,6 +37,8 @@ def manager_init() -> None: params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION) params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION) + if is_release_branch(): + params.clear_all(ParamKeyType.DEVELOPMENT_ONLY) default_params: List[Tuple[str, Union[str, bytes]]] = [ ("CompletedTrainingVersion", "0"), From c028688a652a8354ac641e185eee80a04633543b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 2 Dec 2023 01:16:07 -0800 Subject: [PATCH 112/724] bump cereal (#30582) * bump cereal * fix dmonitoringd * update refs * update refs --- cereal | 2 +- selfdrive/monitoring/dmonitoringd.py | 11 +++-------- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/cereal b/cereal index 65b34b4d15..60d1ee0490 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 65b34b4d152bcea6d49b821bf1da7189f6086b0f +Subproject commit 60d1ee0490a33830168b30a1e92f8d4e43cfbb15 diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index ff2ee71e74..6ea35a6c63 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -3,7 +3,6 @@ import gc import cereal.messaging as messaging from cereal import car -from cereal import log from openpilot.common.params import Params, put_bool_nonblocking from openpilot.common.realtime import set_realtime_priority from openpilot.selfdrive.controls.lib.events import Events @@ -19,18 +18,12 @@ def dmonitoringd_thread(): driver_status = DriverStatus(rhd_saved=Params().get_bool("IsRhdDetected")) - sm['liveCalibration'].calStatus = log.LiveCalibrationData.Status.invalid - sm['liveCalibration'].rpyCalib = [0, 0, 0] - sm['carState'].buttonEvents = [] - sm['carState'].standstill = True - v_cruise_last = 0 driver_engaged = False # 10Hz <- dmonitoringmodeld while True: sm.update() - if not sm.updated['driverStateV2']: continue @@ -48,7 +41,9 @@ def dmonitoringd_thread(): # Get data from dmonitoringmodeld events = Events() - driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) + + if sm.all_checks(): + driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) # Block engaging after max number of distrations if driver_status.terminal_alert_cnt >= driver_status.settings._MAX_TERMINAL_ALERTS or \ diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 40ac91ee70..2ab22ca9ef 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -dbea36698ba48429b201b138846165eb4c329b92 \ No newline at end of file +a42195085f2b92df02d13d60a8cee80354a84c7a \ No newline at end of file From 8971e2c177b6bd9ae1006e4dcbfa4246d6d8096a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 2 Dec 2023 10:01:28 -0800 Subject: [PATCH 113/724] longitudinal tests: cleanup old hacks (#30585) --- selfdrive/controls/controlsd.py | 4 +--- .../longitudinal_maneuvers/test_longitudinal.py | 16 +--------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 421139e449..a04349cd67 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -69,10 +69,8 @@ class Controls: self.sensor_packets = ["accelerometer", "gyroscope"] self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"] - can_timeout = None if os.environ.get('NO_CAN_TIMEOUT', False) else 20 - self.can_sock = messaging.sub_sock('can', timeout=can_timeout) - self.log_sock = messaging.sub_sock('androidLog') + self.can_sock = messaging.sub_sock('can', timeout=20) self.params = Params() ignore = self.sensor_packets + ['testJoystick'] diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index f24f788a19..713b7801f8 100755 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import itertools -import os -from parameterized import parameterized_class import unittest +from parameterized import parameterized_class -from openpilot.common.params import Params from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver @@ -150,18 +148,6 @@ class LongitudinalControl(unittest.TestCase): e2e: bool force_decel: bool - @classmethod - def setUpClass(cls): - os.environ['SIMULATION'] = "1" - os.environ['SKIP_FW_QUERY'] = "1" - os.environ['NO_CAN_TIMEOUT'] = "1" - - def setUp(self): - params = Params() - params.clear_all() - params.put_bool("Passive", bool(os.getenv("PASSIVE"))) - params.put_bool("OpenpilotEnabledToggle", True) - def test_maneuver(self): for maneuver in create_maneuvers({"e2e": self.e2e, "force_decel": self.force_decel}): with self.subTest(title=maneuver.title, e2e=maneuver.e2e, force_decel=maneuver.force_decel): From a2b48efa2083c86638b536df180fbb638cab7404 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 2 Dec 2023 10:47:08 -0800 Subject: [PATCH 114/724] process replay: check valid flag (#30588) --- selfdrive/test/process_replay/process_replay.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 77c4ab1eab..c3c3670a74 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -466,7 +466,7 @@ CONFIGS = [ "testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope" ], subs=["controlsState", "carState", "carControl", "sendcan", "onroadEvents", "carParams"], - ignore=["logMonoTime", "valid", "controlsState.startMonoTime", "controlsState.cumLagMs"], + ignore=["logMonoTime", "controlsState.startMonoTime", "controlsState.cumLagMs"], config_callback=controlsd_config_callback, init_callback=controlsd_fingerprint_callback, should_recv_callback=controlsd_rcv_callback, @@ -478,7 +478,7 @@ CONFIGS = [ proc_name="radard", pubs=["can", "carState", "modelV2"], subs=["radarState", "liveTracks"], - ignore=["logMonoTime", "valid", "radarState.cumLagMs"], + ignore=["logMonoTime", "radarState.cumLagMs"], init_callback=get_car_params_callback, should_recv_callback=MessageBasedRcvCallback("can"), main_pub="can", @@ -487,7 +487,7 @@ CONFIGS = [ proc_name="plannerd", pubs=["modelV2", "carControl", "carState", "controlsState", "radarState"], subs=["lateralPlan", "longitudinalPlan", "uiPlan"], - ignore=["logMonoTime", "valid", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime", "lateralPlan.solverExecutionTime"], + ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime", "lateralPlan.solverExecutionTime"], init_callback=get_car_params_callback, should_recv_callback=FrequencyBasedRcvCallback("modelV2"), tolerance=NUMPY_TOLERANCE, @@ -496,14 +496,14 @@ CONFIGS = [ proc_name="calibrationd", pubs=["carState", "cameraOdometry", "carParams"], subs=["liveCalibration"], - ignore=["logMonoTime", "valid"], + ignore=["logMonoTime"], should_recv_callback=calibration_rcv_callback, ), ProcessConfig( proc_name="dmonitoringd", pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "controlsState"], subs=["driverMonitoringState"], - ignore=["logMonoTime", "valid"], + ignore=["logMonoTime"], should_recv_callback=FrequencyBasedRcvCallback("driverStateV2"), tolerance=NUMPY_TOLERANCE, ), @@ -514,7 +514,7 @@ CONFIGS = [ "liveCalibration", "carState", "carParams", "gpsLocation" ], subs=["liveLocationKalman"], - ignore=["logMonoTime", "valid"], + ignore=["logMonoTime"], config_callback=locationd_config_pubsub_callback, tolerance=NUMPY_TOLERANCE, ), @@ -522,7 +522,7 @@ CONFIGS = [ proc_name="paramsd", pubs=["liveLocationKalman", "carState"], subs=["liveParameters"], - ignore=["logMonoTime", "valid"], + ignore=["logMonoTime"], init_callback=get_car_params_callback, should_recv_callback=FrequencyBasedRcvCallback("liveLocationKalman"), tolerance=NUMPY_TOLERANCE, From a1d36961cf9fa0c6a9c41fe92d6504de5d36cbaa Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 3 Dec 2023 10:50:17 -0800 Subject: [PATCH 115/724] new msgs default to invalid (#30587) * new msgs default to invalid * fix lm * set more valid * update rest * update refs * fix logMessage * more valids * cleanup * fix llk test * pigeond is also valid * more valids --- cereal | 2 +- selfdrive/controls/controlsd.py | 4 +++- selfdrive/controls/radard.py | 1 + selfdrive/locationd/calibrationd.py | 26 +++++++++------------- selfdrive/locationd/test/test_locationd.py | 1 + selfdrive/manager/manager.py | 2 +- selfdrive/modeld/dmonitoringmodeld.py | 2 +- selfdrive/monitoring/dmonitoringd.py | 2 +- selfdrive/navd/navd.py | 4 ++-- selfdrive/test/process_replay/migration.py | 2 ++ selfdrive/test/process_replay/ref_commit | 2 +- selfdrive/thermald/thermald.py | 2 +- system/loggerd/uploader.py | 2 +- system/logmessaged.py | 6 ++--- system/micd.py | 2 +- system/qcomgpsd/qcomgpsd.py | 8 +++---- system/sensord/pigeond.py | 2 +- 17 files changed, 34 insertions(+), 36 deletions(-) diff --git a/cereal b/cereal index 60d1ee0490..033dad2b37 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 60d1ee0490a33830168b30a1e92f8d4e43cfbb15 +Subproject commit 033dad2b370dac411479de19381f6e0253e51e01 diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a04349cd67..ed4c912608 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -80,7 +80,7 @@ class Controls: 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', 'testJoystick'] + self.camera_packets + self.sensor_packets, - ignore_alive=ignore, ignore_avg_freq=['radarState', 'testJoystick']) + ignore_alive=ignore, ignore_avg_freq=['radarState', 'testJoystick'], ignore_valid=['testJoystick', ]) if CI is None: # wait for one pandaState and one CAN packet @@ -824,6 +824,7 @@ class Controls: # onroadEvents - logged every second or on change if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev): ce_send = messaging.new_message('onroadEvents', len(self.events)) + ce_send.valid = True ce_send.onroadEvents = car_events self.pm.send('onroadEvents', ce_send) self.events_prev = self.events.names.copy() @@ -831,6 +832,7 @@ class Controls: # carParams - logged every 50 seconds (> 1 per segment) if (self.sm.frame % int(50. / DT_CTRL) == 0): cp_send = messaging.new_message('carParams') + cp_send.valid = True cp_send.carParams = self.CP self.pm.send('carParams', cp_send) diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index ec42ae66f2..a2d58cfed2 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -270,6 +270,7 @@ class RadarD: # publish tracks for UI debugging (keep last) tracks_msg = messaging.new_message('liveTracks', len(self.tracks)) + tracks_msg.valid = self.radar_state_valid for index, tid in enumerate(sorted(self.tracks.keys())): tracks_msg.liveTracks[index] = { "trackId": tid, diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 566373d7b8..654c72528b 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -164,7 +164,7 @@ class Calibrator: write_this_cycle = (self.idx == 0) and (self.block_idx % (INPUTS_WANTED//5) == 5) if self.param_put and write_this_cycle: - put_nonblocking("CalibrationParams", self.get_msg().to_bytes()) + put_nonblocking("CalibrationParams", self.get_msg(True).to_bytes()) def handle_v_ego(self, v_ego: float) -> None: self.v_ego = v_ego @@ -227,12 +227,13 @@ class Calibrator: return new_rpy - def get_msg(self) -> capnp.lib.capnp._DynamicStructBuilder: + def get_msg(self, valid: bool) -> capnp.lib.capnp._DynamicStructBuilder: smooth_rpy = self.get_smooth_rpy() msg = messaging.new_message('liveCalibration') - liveCalibration = msg.liveCalibration + msg.valid = valid + liveCalibration = msg.liveCalibration liveCalibration.validBlocks = self.valid_blocks liveCalibration.calStatus = self.cal_status liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) @@ -250,19 +251,16 @@ class Calibrator: return msg - def send_data(self, pm: messaging.PubMaster) -> None: - pm.send('liveCalibration', self.get_msg()) + def send_data(self, pm: messaging.PubMaster, valid: bool) -> None: + pm.send('liveCalibration', self.get_msg(valid)) -def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None) -> NoReturn: +def main() -> NoReturn: gc.disable() set_realtime_priority(1) - if sm is None: - sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry']) - - if pm is None: - pm = messaging.PubMaster(['liveCalibration']) + pm = messaging.PubMaster(['liveCalibration']) + sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry']) calibrator = Calibrator(param_put=True) @@ -286,11 +284,7 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m # 4Hz driven by cameraOdometry if sm.frame % 5 == 0: - calibrator.send_data(pm) - - -def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None) -> NoReturn: - calibrationd_thread(sm, pm) + calibrator.send_data(pm, sm.all_checks()) if __name__ == "__main__": diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index 707fdd743f..78de9216dc 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -60,6 +60,7 @@ class TestLocationdProc(unittest.TestCase): msg.cameraOdometry.trans = [0.0, 0.0, 0.0] msg.cameraOdometry.transStd = [0.0, 0.0, 0.0] msg.logMonoTime = t + msg.valid = True return msg def test_params_gps(self): diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 9be448499e..7b23445c8b 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -171,7 +171,7 @@ def manager_thread() -> None: cloudlog.debug(running) # send managerState - msg = messaging.new_message('managerState') + msg = messaging.new_message('managerState', valid=True) msg.managerState.processes = [p.get_process_state_msg() for p in managed_processes.values()] pm.send('managerState', msg) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 53c0af0ff3..aafe78abde 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -101,7 +101,7 @@ def fill_driver_state(msg, ds_result: DriverStateResult): def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: int, execution_time: float, dsp_execution_time: float): model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(DMonitoringModelResult)).contents - msg = messaging.new_message('driverStateV2') + msg = messaging.new_message('driverStateV2', valid=True) ds = msg.driverStateV2 ds.frameId = frame_id ds.modelExecutionTime = execution_time diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 6ea35a6c63..7455459967 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -54,7 +54,7 @@ def dmonitoringd_thread(): driver_status.update_events(events, driver_engaged, sm['controlsState'].enabled, sm['carState'].standstill) # build driverMonitoringState packet - dat = messaging.new_message('driverMonitoringState') + dat = messaging.new_message('driverMonitoringState', valid=sm.all_checks()) dat.driverMonitoringState = { "events": events.to_msg(), "faceDetected": driver_status.face_detected, diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index da2b8c06b9..0d71efcc8d 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -196,7 +196,7 @@ class RouteEngine: self.send_route() def send_instruction(self): - msg = messaging.new_message('navInstruction') + msg = messaging.new_message('navInstruction', valid=True) if self.step_idx is None: msg.valid = False @@ -302,7 +302,7 @@ class RouteEngine: for path in self.route_geometry: coords += [c.as_dict() for c in path] - msg = messaging.new_message('navRoute') + msg = messaging.new_message('navRoute', valid=True) msg.navRoute.coordinates = coords self.pm.send('navRoute', msg) diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index ad5192dbba..831bea7c6f 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -85,6 +85,7 @@ def migrate_peripheralState(lr): continue new_msg = messaging.new_message("peripheralState") + new_msg.valid = msg.valid new_msg.logMonoTime = msg.logMonoTime all_msg.append(new_msg.as_reader()) @@ -149,6 +150,7 @@ def migrate_carParams(lr, old_logtime=False): for msg in lr: if msg.which() == 'carParams': CP = messaging.new_message('carParams') + CP.valid = True CP.carParams = msg.carParams.as_builder() for car_fw in CP.carParams.carFw: car_fw.brand = CP.carParams.carName diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 2ab22ca9ef..030cbc4e07 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -a42195085f2b92df02d13d60a8cee80354a84c7a \ No newline at end of file +921222d49db204071f0a7006fc895690e1045b5d \ No newline at end of file diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 3ccb9349e1..690618d0b8 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -81,7 +81,7 @@ def read_tz(x): def read_thermal(thermal_config): - dat = messaging.new_message('deviceState') + dat = messaging.new_message('deviceState', valid=True) dat.deviceState.cpuTempC = [read_tz(z) / thermal_config.cpu[1] for z in thermal_config.cpu[0]] dat.deviceState.gpuTempC = [read_tz(z) / thermal_config.gpu[1] for z in thermal_config.gpu[0]] dat.deviceState.memoryTempC = read_tz(thermal_config.mem[0]) / thermal_config.mem[1] diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 12c6ecdca9..b5804b1624 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -228,7 +228,7 @@ class Uploader: return success def get_msg(self): - msg = messaging.new_message("uploaderState") + msg = messaging.new_message("uploaderState", valid=True) us = msg.uploaderState us.immediateQueueSize = int(self.immediate_size / 1e6) us.immediateQueueCount = self.immediate_count diff --git a/system/logmessaged.py b/system/logmessaged.py index c53e20e483..5bd83f4b05 100755 --- a/system/logmessaged.py +++ b/system/logmessaged.py @@ -35,13 +35,11 @@ def main() -> NoReturn: continue # then we publish them - msg = messaging.new_message() - msg.logMessage = record + msg = messaging.new_message(None, valid=True, logMessage=record) log_message_sock.send(msg.to_bytes()) if level >= 40: # logging.ERROR - msg = messaging.new_message() - msg.errorLogMessage = record + msg = messaging.new_message(None, valid=True, errorLogMessage=record) error_log_message_sock.send(msg.to_bytes()) finally: sock.close() diff --git a/system/micd.py b/system/micd.py index 72f3b8b490..0b7a4dadfb 100755 --- a/system/micd.py +++ b/system/micd.py @@ -53,7 +53,7 @@ class Mic: self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) def update(self): - msg = messaging.new_message('microphone') + msg = messaging.new_message('microphone', valid=True) msg.microphone.soundPressure = float(self.sound_pressure) msg.microphone.soundPressureWeighted = float(self.sound_pressure_weighted) diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index ac862052d3..78539266c0 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -321,7 +321,7 @@ def main() -> NoReturn: print("%.4f: got log: %x len %d" % (time.time(), log_type, len(log_payload))) if log_type == LOG_GNSS_OEMDRE_MEASUREMENT_REPORT: - msg = messaging.new_message('qcomGnss') + msg = messaging.new_message('qcomGnss', valid=True) gnss = msg.qcomGnss gnss.logTs = log_time @@ -370,7 +370,7 @@ def main() -> NoReturn: vNED = [report["q_FltVelEnuMps[1]"], report["q_FltVelEnuMps[0]"], -report["q_FltVelEnuMps[2]"]] vNEDsigma = [report["q_FltVelSigmaMps[1]"], report["q_FltVelSigmaMps[0]"], -report["q_FltVelSigmaMps[2]"]] - msg = messaging.new_message('gpsLocation') + msg = messaging.new_message('gpsLocation', valid=True) gps = msg.gpsLocation gps.latitude = report["t_DblFinalPosLatLon[0]"] * 180/math.pi gps.longitude = report["t_DblFinalPosLatLon[1]"] * 180/math.pi @@ -396,7 +396,7 @@ def main() -> NoReturn: pm.send('gpsLocation', msg) elif log_type == LOG_GNSS_OEMDRE_SVPOLY_REPORT: - msg = messaging.new_message('qcomGnss') + msg = messaging.new_message('qcomGnss', valid=True) dat = unpack_svpoly(log_payload) dat = relist(dat) gnss = msg.qcomGnss @@ -433,7 +433,7 @@ def main() -> NoReturn: pm.send('qcomGnss', msg) elif log_type in [LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT]: - msg = messaging.new_message('qcomGnss') + msg = messaging.new_message('qcomGnss', valid=True) gnss = msg.qcomGnss gnss.logTs = log_time diff --git a/system/sensord/pigeond.py b/system/sensord/pigeond.py index 2e8f151d17..6a02f627b9 100755 --- a/system/sensord/pigeond.py +++ b/system/sensord/pigeond.py @@ -291,7 +291,7 @@ def run_receiving(pigeon: TTYPigeon, pm: messaging.PubMaster, duration: int = 0) continue # send out to socket - msg = messaging.new_message('ubloxRaw', len(dat)) + msg = messaging.new_message('ubloxRaw', len(dat), valid=True) msg.ubloxRaw = dat[:] pm.send('ubloxRaw', msg) else: From 5ccff25d885365e4247665fdf8380c6b67c36b28 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 3 Dec 2023 13:59:59 -0800 Subject: [PATCH 116/724] Simplify passive mode handling (#30593) * simplify passive handling * makes more sense --- launch_chffrplus.sh | 91 +++------------------- launch_env.sh | 4 - launch_openpilot.sh | 87 ++++++++++++++++++++- selfdrive/manager/manager.py | 8 +- selfdrive/manager/test/test_manager.py | 1 - selfdrive/test/helpers.py | 1 - selfdrive/ui/installer/continue_dashcam.sh | 4 +- tools/webcam/README.md | 2 +- 8 files changed, 100 insertions(+), 98 deletions(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index a91bd677aa..80a6089238 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -1,88 +1,15 @@ #!/usr/bin/bash -if [ -z "$BASEDIR" ]; then - BASEDIR="/data/openpilot" -fi +# TODO: this can be removed after 0.9.6 release -source "$BASEDIR/launch_env.sh" +# migrate continue.sh and relaunch +cat << EOF > /data/continue.sh +#!/usr/bin/bash -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +export PASSIVE=1 -function agnos_init { - # TODO: move this to agnos - sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta +cd /data/openpilot +exec ./launch_openpilot.sh +EOF - # set success flag for current boot slot - sudo abctl --set_success - - # Check if AGNOS update is required - if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then - AGNOS_PY="$DIR/system/hardware/tici/agnos.py" - MANIFEST="$DIR/system/hardware/tici/agnos.json" - if $AGNOS_PY --verify $MANIFEST; then - sudo reboot - fi - $DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST - fi -} - -function launch { - # Remove orphaned git lock if it exists on boot - [ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock - - # Pull time from panda - $DIR/selfdrive/boardd/set_time.py - - # Check to see if there's a valid overlay-based update available. Conditions - # are as follows: - # - # 1. The BASEDIR init file has to exist, with a newer modtime than anything in - # the BASEDIR Git repo. This checks for local development work or the user - # switching branches/forks, which should not be overwritten. - # 2. The FINALIZED consistent file has to exist, indicating there's an update - # that completed successfully and synced to disk. - - if [ -f "${BASEDIR}/.overlay_init" ]; then - find ${BASEDIR}/.git -newer ${BASEDIR}/.overlay_init | grep -q '.' 2> /dev/null - if [ $? -eq 0 ]; then - echo "${BASEDIR} has been modified, skipping overlay update installation" - else - if [ -f "${STAGING_ROOT}/finalized/.overlay_consistent" ]; then - if [ ! -d /data/safe_staging/old_openpilot ]; then - echo "Valid overlay update found, installing" - LAUNCHER_LOCATION="${BASH_SOURCE[0]}" - - mv $BASEDIR /data/safe_staging/old_openpilot - mv "${STAGING_ROOT}/finalized" $BASEDIR - cd $BASEDIR - - echo "Restarting launch script ${LAUNCHER_LOCATION}" - unset AGNOS_VERSION - exec "${LAUNCHER_LOCATION}" - else - echo "openpilot backup found, not updating" - # TODO: restore backup? This means the updater didn't start after swapping - fi - fi - fi - fi - - # handle pythonpath - ln -sfn $(pwd) /data/pythonpath - export PYTHONPATH="$PWD" - - # hardware specific init - agnos_init - - # write tmux scrollback to a file - tmux capture-pane -pq -S-1000 > /tmp/launch_log - - # start manager - cd selfdrive/manager - ./build.py && ./manager.py - - # if broken, keep on screen error - while true; do sleep 1; done -} - -launch +/data/continue.sh diff --git a/launch_env.sh b/launch_env.sh index d07fbe33de..e3eba30b67 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -10,8 +10,4 @@ if [ -z "$AGNOS_VERSION" ]; then export AGNOS_VERSION="8.2" fi -if [ -z "$PASSIVE" ]; then - export PASSIVE="1" -fi - export STAGING_ROOT="/data/safe_staging" diff --git a/launch_openpilot.sh b/launch_openpilot.sh index 1525e1715f..a91bd677aa 100755 --- a/launch_openpilot.sh +++ b/launch_openpilot.sh @@ -1,5 +1,88 @@ #!/usr/bin/bash -export PASSIVE="0" -exec ./launch_chffrplus.sh +if [ -z "$BASEDIR" ]; then + BASEDIR="/data/openpilot" +fi +source "$BASEDIR/launch_env.sh" + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +function agnos_init { + # TODO: move this to agnos + sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta + + # set success flag for current boot slot + sudo abctl --set_success + + # Check if AGNOS update is required + if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then + AGNOS_PY="$DIR/system/hardware/tici/agnos.py" + MANIFEST="$DIR/system/hardware/tici/agnos.json" + if $AGNOS_PY --verify $MANIFEST; then + sudo reboot + fi + $DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST + fi +} + +function launch { + # Remove orphaned git lock if it exists on boot + [ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock + + # Pull time from panda + $DIR/selfdrive/boardd/set_time.py + + # Check to see if there's a valid overlay-based update available. Conditions + # are as follows: + # + # 1. The BASEDIR init file has to exist, with a newer modtime than anything in + # the BASEDIR Git repo. This checks for local development work or the user + # switching branches/forks, which should not be overwritten. + # 2. The FINALIZED consistent file has to exist, indicating there's an update + # that completed successfully and synced to disk. + + if [ -f "${BASEDIR}/.overlay_init" ]; then + find ${BASEDIR}/.git -newer ${BASEDIR}/.overlay_init | grep -q '.' 2> /dev/null + if [ $? -eq 0 ]; then + echo "${BASEDIR} has been modified, skipping overlay update installation" + else + if [ -f "${STAGING_ROOT}/finalized/.overlay_consistent" ]; then + if [ ! -d /data/safe_staging/old_openpilot ]; then + echo "Valid overlay update found, installing" + LAUNCHER_LOCATION="${BASH_SOURCE[0]}" + + mv $BASEDIR /data/safe_staging/old_openpilot + mv "${STAGING_ROOT}/finalized" $BASEDIR + cd $BASEDIR + + echo "Restarting launch script ${LAUNCHER_LOCATION}" + unset AGNOS_VERSION + exec "${LAUNCHER_LOCATION}" + else + echo "openpilot backup found, not updating" + # TODO: restore backup? This means the updater didn't start after swapping + fi + fi + fi + fi + + # handle pythonpath + ln -sfn $(pwd) /data/pythonpath + export PYTHONPATH="$PWD" + + # hardware specific init + agnos_init + + # write tmux scrollback to a file + tmux capture-pane -pq -S-1000 > /tmp/launch_log + + # start manager + cd selfdrive/manager + ./build.py && ./manager.py + + # if broken, keep on screen error + while true; do sleep 1; done +} + +launch diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 7b23445c8b..d329e19a0a 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -60,12 +60,8 @@ def manager_init() -> None: if params.get(k) is None: params.put(k, v) - # is this dashcam? - if os.getenv("PASSIVE") is not None: - params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0")))) - - if params.get("Passive") is None: - raise Exception("Passive must be set to continue") + # is this a dashcam build? + params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0")))) # Create folders needed for msgq try: diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 0fa186baff..306f39aae0 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -21,7 +21,6 @@ BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] @pytest.mark.tici class TestManager(unittest.TestCase): def setUp(self): - os.environ['PASSIVE'] = '0' HARDWARE.set_power_save(False) # ensure clean CarParams diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 552070f024..2103194cb8 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -11,7 +11,6 @@ from openpilot.system.version import training_version, terms_version def set_params_enabled(): - os.environ['PASSIVE'] = "0" os.environ['REPLAY'] = "1" os.environ['FINGERPRINT'] = "TOYOTA COROLLA TSS2 2019" os.environ['LOGPRINT'] = "debug" diff --git a/selfdrive/ui/installer/continue_dashcam.sh b/selfdrive/ui/installer/continue_dashcam.sh index 25233fff11..4447c8302f 100755 --- a/selfdrive/ui/installer/continue_dashcam.sh +++ b/selfdrive/ui/installer/continue_dashcam.sh @@ -1,4 +1,6 @@ #!/usr/bin/bash +export PASSIVE=1 + cd /data/openpilot -exec ./launch_chffrplus.sh +exec ./launch_openpilot.sh diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 237e07cdb6..3491f72820 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -38,7 +38,7 @@ USE_WEBCAM=1 scons -j$(nproc) ## GO ``` cd ~/openpilot/selfdrive/manager -PASSIVE=0 NOSENSOR=1 USE_WEBCAM=1 ./manager.py +NOSENSOR=1 USE_WEBCAM=1 ./manager.py ``` - Start the car, then the UI should show the road webcam's view - Adjust and secure the webcams (you can run tools/webcam/front_mount_helper.py to help mount the driver camera) From bd0ab957b196694c57b125838622e9d0021ca9c0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 3 Dec 2023 14:09:37 -0800 Subject: [PATCH 117/724] add carParams.passive (#30594) --- cereal | 2 +- selfdrive/controls/controlsd.py | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cereal b/cereal index 033dad2b37..4fb8352484 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 033dad2b370dac411479de19381f6e0253e51e01 +Subproject commit 4fb8352484c4ef074bee775975c0d1d1e8f57a78 diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index ed4c912608..33e02aacdd 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -113,8 +113,8 @@ class Controls: car_recognized = self.CP.carName != 'mock' controller_available = self.CI.CC is not None and not passive and not self.CP.dashcamOnly - self.read_only = not car_recognized or not controller_available or self.CP.dashcamOnly - if self.read_only: + self.CP.passive = not car_recognized or not controller_available or self.CP.dashcamOnly + if self.CP.passive: safety_config = car.CarParams.SafetyConfig.new_message() safety_config.safetyModel = car.CarParams.SafetyModel.noOutput self.CP.safetyConfigs = [safety_config] @@ -189,7 +189,7 @@ class Controls: set_offroad_alert("Offroad_CarUnrecognized", True) else: set_offroad_alert("Offroad_NoFirmware", True) - elif self.read_only: + elif self.CP.passive: self.events.add(EventName.dashcamMode, static=True) elif self.joystick_mode: self.events.add(EventName.joystickDebug, static=True) @@ -225,7 +225,7 @@ class Controls: return # no more events while in dashcam mode - if self.read_only: + if self.CP.passive: return # Block resume if cruise never previously enabled @@ -440,7 +440,7 @@ class Controls: if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams: self.sm.ignore_alive.append('wideRoadCameraState') - if not self.read_only: + if not self.CP.passive: self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) self.initialized = True @@ -745,7 +745,7 @@ class Controls: if current_alert: hudControl.visualAlert = current_alert.visual_alert - if not self.read_only and self.initialized: + if not self.CP.passive and self.initialized: # send car controls over can now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9) self.last_actuators, can_sends = self.CI.apply(CC, now_nanos) @@ -860,7 +860,7 @@ class Controls: self.update_events(CS) cloudlog.timestamp("Events updated") - if not self.read_only and self.initialized: + if not self.CP.passive and self.initialized: # Update control state self.state_transition(CS) self.prof.checkpoint("State transition") From ce4bac8218ebcc8d0480ed09e0c1c6467f705a65 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 3 Dec 2023 15:54:18 -0800 Subject: [PATCH 118/724] remove unused last_actuators arg from lateral controllers (#30595) --- selfdrive/controls/controlsd.py | 2 +- selfdrive/controls/lib/latcontrol.py | 2 +- selfdrive/controls/lib/latcontrol_angle.py | 2 +- selfdrive/controls/lib/latcontrol_pid.py | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 2 +- selfdrive/controls/lib/tests/test_latcontrol.py | 4 +--- 6 files changed, 6 insertions(+), 8 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 33e02aacdd..a0bfbcd837 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -614,7 +614,7 @@ class Controls: lat_plan.curvatures, lat_plan.curvatureRates) actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, - self.last_actuators, self.steer_limited, self.desired_curvature, + self.steer_limited, self.desired_curvature, self.desired_curvature_rate, self.sm['liveLocationKalman']) actuators.curvature = self.desired_curvature else: diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 30e1918442..723af7f806 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -17,7 +17,7 @@ class LatControl(ABC): self.steer_max = 1.0 @abstractmethod - def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk): pass def reset(self): diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index b13d41e51c..d363295f0c 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -11,7 +11,7 @@ class LatControlAngle(LatControl): super().__init__(CP, CI) self.sat_check_min_speed = 5. - def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk): angle_log = log.ControlsState.LateralAngleState.new_message() if not active: diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index c41130af95..c673159ebb 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -17,7 +17,7 @@ class LatControlPID(LatControl): super().reset() self.pid.reset() - def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk): pid_log = log.ControlsState.LateralPIDState.new_message() pid_log.steeringAngleDeg = float(CS.steeringAngleDeg) pid_log.steeringRateDeg = float(CS.steeringRateDeg) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 2c77630632..f46ab9eb6c 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -36,7 +36,7 @@ class LatControlTorque(LatControl): self.torque_params.latAccelOffset = latAccelOffset self.torque_params.friction = friction - def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk): pid_log = log.ControlsState.LateralTorqueState.new_message() if not active: diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index 9580e604ff..5faad914cf 100755 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -30,13 +30,11 @@ class TestLatControl(unittest.TestCase): CS.vEgo = 30 CS.steeringPressed = False - last_actuators = car.CarControl.Actuators.new_message() - params = log.LiveParametersData.new_message() llk = gen_llk() for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, last_actuators, False, 1, 0, llk) + _, _, lac_log = controller.update(True, CS, VM, params, False, 1, 0, llk) self.assertTrue(lac_log.saturated) From 05e932b088cde39dce7a1a32a55e5b4a3fae1253 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 4 Dec 2023 13:58:43 -0800 Subject: [PATCH 119/724] Fix passive mode (#30600) remove passive --- launch_chffrplus.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 80a6089238..95d0fc639d 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -6,8 +6,6 @@ cat << EOF > /data/continue.sh #!/usr/bin/bash -export PASSIVE=1 - cd /data/openpilot exec ./launch_openpilot.sh EOF From 99d51bf02c5a13f3064b012da55634f8a319091e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 4 Dec 2023 15:05:05 -0800 Subject: [PATCH 120/724] Revert "Simplify passive mode handling (#30593)" --- launch_chffrplus.sh | 91 ++++++++++++++++++++-- launch_env.sh | 4 + launch_openpilot.sh | 87 +-------------------- selfdrive/manager/manager.py | 8 +- selfdrive/manager/test/test_manager.py | 1 + selfdrive/test/helpers.py | 1 + selfdrive/ui/installer/continue_dashcam.sh | 4 +- tools/webcam/README.md | 2 +- 8 files changed, 99 insertions(+), 99 deletions(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 95d0fc639d..a91bd677aa 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -1,13 +1,88 @@ #!/usr/bin/bash -# TODO: this can be removed after 0.9.6 release +if [ -z "$BASEDIR" ]; then + BASEDIR="/data/openpilot" +fi -# migrate continue.sh and relaunch -cat << EOF > /data/continue.sh -#!/usr/bin/bash +source "$BASEDIR/launch_env.sh" -cd /data/openpilot -exec ./launch_openpilot.sh -EOF +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -/data/continue.sh +function agnos_init { + # TODO: move this to agnos + sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta + + # set success flag for current boot slot + sudo abctl --set_success + + # Check if AGNOS update is required + if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then + AGNOS_PY="$DIR/system/hardware/tici/agnos.py" + MANIFEST="$DIR/system/hardware/tici/agnos.json" + if $AGNOS_PY --verify $MANIFEST; then + sudo reboot + fi + $DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST + fi +} + +function launch { + # Remove orphaned git lock if it exists on boot + [ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock + + # Pull time from panda + $DIR/selfdrive/boardd/set_time.py + + # Check to see if there's a valid overlay-based update available. Conditions + # are as follows: + # + # 1. The BASEDIR init file has to exist, with a newer modtime than anything in + # the BASEDIR Git repo. This checks for local development work or the user + # switching branches/forks, which should not be overwritten. + # 2. The FINALIZED consistent file has to exist, indicating there's an update + # that completed successfully and synced to disk. + + if [ -f "${BASEDIR}/.overlay_init" ]; then + find ${BASEDIR}/.git -newer ${BASEDIR}/.overlay_init | grep -q '.' 2> /dev/null + if [ $? -eq 0 ]; then + echo "${BASEDIR} has been modified, skipping overlay update installation" + else + if [ -f "${STAGING_ROOT}/finalized/.overlay_consistent" ]; then + if [ ! -d /data/safe_staging/old_openpilot ]; then + echo "Valid overlay update found, installing" + LAUNCHER_LOCATION="${BASH_SOURCE[0]}" + + mv $BASEDIR /data/safe_staging/old_openpilot + mv "${STAGING_ROOT}/finalized" $BASEDIR + cd $BASEDIR + + echo "Restarting launch script ${LAUNCHER_LOCATION}" + unset AGNOS_VERSION + exec "${LAUNCHER_LOCATION}" + else + echo "openpilot backup found, not updating" + # TODO: restore backup? This means the updater didn't start after swapping + fi + fi + fi + fi + + # handle pythonpath + ln -sfn $(pwd) /data/pythonpath + export PYTHONPATH="$PWD" + + # hardware specific init + agnos_init + + # write tmux scrollback to a file + tmux capture-pane -pq -S-1000 > /tmp/launch_log + + # start manager + cd selfdrive/manager + ./build.py && ./manager.py + + # if broken, keep on screen error + while true; do sleep 1; done +} + +launch diff --git a/launch_env.sh b/launch_env.sh index e3eba30b67..d07fbe33de 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -10,4 +10,8 @@ if [ -z "$AGNOS_VERSION" ]; then export AGNOS_VERSION="8.2" fi +if [ -z "$PASSIVE" ]; then + export PASSIVE="1" +fi + export STAGING_ROOT="/data/safe_staging" diff --git a/launch_openpilot.sh b/launch_openpilot.sh index a91bd677aa..1525e1715f 100755 --- a/launch_openpilot.sh +++ b/launch_openpilot.sh @@ -1,88 +1,5 @@ #!/usr/bin/bash -if [ -z "$BASEDIR" ]; then - BASEDIR="/data/openpilot" -fi +export PASSIVE="0" +exec ./launch_chffrplus.sh -source "$BASEDIR/launch_env.sh" - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" - -function agnos_init { - # TODO: move this to agnos - sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta - - # set success flag for current boot slot - sudo abctl --set_success - - # Check if AGNOS update is required - if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then - AGNOS_PY="$DIR/system/hardware/tici/agnos.py" - MANIFEST="$DIR/system/hardware/tici/agnos.json" - if $AGNOS_PY --verify $MANIFEST; then - sudo reboot - fi - $DIR/system/hardware/tici/updater $AGNOS_PY $MANIFEST - fi -} - -function launch { - # Remove orphaned git lock if it exists on boot - [ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock - - # Pull time from panda - $DIR/selfdrive/boardd/set_time.py - - # Check to see if there's a valid overlay-based update available. Conditions - # are as follows: - # - # 1. The BASEDIR init file has to exist, with a newer modtime than anything in - # the BASEDIR Git repo. This checks for local development work or the user - # switching branches/forks, which should not be overwritten. - # 2. The FINALIZED consistent file has to exist, indicating there's an update - # that completed successfully and synced to disk. - - if [ -f "${BASEDIR}/.overlay_init" ]; then - find ${BASEDIR}/.git -newer ${BASEDIR}/.overlay_init | grep -q '.' 2> /dev/null - if [ $? -eq 0 ]; then - echo "${BASEDIR} has been modified, skipping overlay update installation" - else - if [ -f "${STAGING_ROOT}/finalized/.overlay_consistent" ]; then - if [ ! -d /data/safe_staging/old_openpilot ]; then - echo "Valid overlay update found, installing" - LAUNCHER_LOCATION="${BASH_SOURCE[0]}" - - mv $BASEDIR /data/safe_staging/old_openpilot - mv "${STAGING_ROOT}/finalized" $BASEDIR - cd $BASEDIR - - echo "Restarting launch script ${LAUNCHER_LOCATION}" - unset AGNOS_VERSION - exec "${LAUNCHER_LOCATION}" - else - echo "openpilot backup found, not updating" - # TODO: restore backup? This means the updater didn't start after swapping - fi - fi - fi - fi - - # handle pythonpath - ln -sfn $(pwd) /data/pythonpath - export PYTHONPATH="$PWD" - - # hardware specific init - agnos_init - - # write tmux scrollback to a file - tmux capture-pane -pq -S-1000 > /tmp/launch_log - - # start manager - cd selfdrive/manager - ./build.py && ./manager.py - - # if broken, keep on screen error - while true; do sleep 1; done -} - -launch diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index d329e19a0a..7b23445c8b 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -60,8 +60,12 @@ def manager_init() -> None: if params.get(k) is None: params.put(k, v) - # is this a dashcam build? - params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0")))) + # is this dashcam? + if os.getenv("PASSIVE") is not None: + params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0")))) + + if params.get("Passive") is None: + raise Exception("Passive must be set to continue") # Create folders needed for msgq try: diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 306f39aae0..0fa186baff 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -21,6 +21,7 @@ BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] @pytest.mark.tici class TestManager(unittest.TestCase): def setUp(self): + os.environ['PASSIVE'] = '0' HARDWARE.set_power_save(False) # ensure clean CarParams diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 2103194cb8..552070f024 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -11,6 +11,7 @@ from openpilot.system.version import training_version, terms_version def set_params_enabled(): + os.environ['PASSIVE'] = "0" os.environ['REPLAY'] = "1" os.environ['FINGERPRINT'] = "TOYOTA COROLLA TSS2 2019" os.environ['LOGPRINT'] = "debug" diff --git a/selfdrive/ui/installer/continue_dashcam.sh b/selfdrive/ui/installer/continue_dashcam.sh index 4447c8302f..25233fff11 100755 --- a/selfdrive/ui/installer/continue_dashcam.sh +++ b/selfdrive/ui/installer/continue_dashcam.sh @@ -1,6 +1,4 @@ #!/usr/bin/bash -export PASSIVE=1 - cd /data/openpilot -exec ./launch_openpilot.sh +exec ./launch_chffrplus.sh diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 3491f72820..237e07cdb6 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -38,7 +38,7 @@ USE_WEBCAM=1 scons -j$(nproc) ## GO ``` cd ~/openpilot/selfdrive/manager -NOSENSOR=1 USE_WEBCAM=1 ./manager.py +PASSIVE=0 NOSENSOR=1 USE_WEBCAM=1 ./manager.py ``` - Start the car, then the UI should show the road webcam's view - Adjust and secure the webcams (you can run tools/webcam/front_mount_helper.py to help mount the driver camera) From 36ccbc8bbef23cf464c38f29ef748a906561ba12 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 4 Dec 2023 17:02:48 -0800 Subject: [PATCH 121/724] Bump submodules (#30596) bump submodules Co-authored-by: jnewb1 --- opendbc | 2 +- panda | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc b/opendbc index 2b96bcc456..ed3af3da1b 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 2b96bcc45669cdd14f9c652b07ef32d6403630f6 +Subproject commit ed3af3da1b7d52aed910f22cc5540e31f92ba254 diff --git a/panda b/panda index b50455cc76..892ca5a0f1 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b50455cc76a0f66aa6faf953dd3ea023783a4b3f +Subproject commit 892ca5a0f19c07a4786373e360027ab6ec57f723 From 33ee7530b39bf01051c11cb888695b0962c92f95 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 5 Dec 2023 09:12:22 +0800 Subject: [PATCH 122/724] cabana: add test case for parsing all opendbc files (#30584) * test opendbc files * bump opendbc * bump opendbc --------- Co-authored-by: Shane Smiskol --- tools/cabana/tests/test_cabana.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index f6884a2dc9..c7dc91e7aa 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -1,5 +1,7 @@ #undef INFO +#include + #include "catch2/catch.hpp" #include "tools/replay/logreader.h" #include "tools/cabana/dbc/dbcmanager.h" @@ -85,3 +87,17 @@ CM_ SG_ 160 signal_2 "multiple line comment REQUIRE(msg->sigs[1]->size == 1); REQUIRE(msg->sigs[1]->receiver_name == "XXX"); } + +TEST_CASE("parse_opendbc") { + QDir dir(OPENDBC_FILE_PATH); + QStringList errors; + for (auto fn : dir.entryList({"*.dbc"}, QDir::Files, QDir::Name)) { + try { + auto dbc = DBCFile(dir.filePath(fn)); + } catch (std::exception &e) { + errors.push_back(e.what()); + } + } + INFO(errors.join("\n").toStdString()); + REQUIRE(errors.empty()); +} From 6a354ddab75fee435da85f6dc89df90ef2d3baec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 4 Dec 2023 19:14:11 -0800 Subject: [PATCH 123/724] webrtcd: stop cereal proxy runner when data channel is closed (#30601) Stop the proxy when channel reaches invalid state --- system/webrtc/webrtcd.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index 237cae78a1..3f2ef2ceb4 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -15,6 +15,7 @@ warnings.filterwarnings("ignore", category=DeprecationWarning) import aiortc from aiortc.mediastreams import VideoStreamTrack, AudioStreamTrack from aiortc.contrib.media import MediaBlackhole +from aiortc.exceptions import InvalidStateError from aiohttp import web import capnp from teleoprtc import WebRTCAnswerBuilder @@ -97,6 +98,9 @@ class CerealProxyRunner: while True: try: self.proxy.update() + except InvalidStateError: + self.logger.warning("Cereal outgoing proxy invalid state (connection closed)") + break except Exception as ex: self.logger.error("Cereal outgoing proxy failure: %s", ex) await asyncio.sleep(0.01) From 10eb70daf707760f785490f69b2d17621698ed75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 4 Dec 2023 20:43:19 -0800 Subject: [PATCH 124/724] webrtcd: endpoint for message schema retrieval (#30578) * Capnp json schema conversion * Schema get endpoint * Type annotation for generate_field * Filter empty services --- system/webrtc/schema.py | 43 ++++++++++++++++++++++++++++++++++++++++ system/webrtc/webrtcd.py | 12 ++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 system/webrtc/schema.py diff --git a/system/webrtc/schema.py b/system/webrtc/schema.py new file mode 100644 index 0000000000..f659b34293 --- /dev/null +++ b/system/webrtc/schema.py @@ -0,0 +1,43 @@ +import capnp +from typing import Union, List, Dict, Any + + +def generate_type(type_walker, schema_walker) -> Union[str, List[Any], Dict[str, Any]]: + data_type = next(type_walker) + if data_type.which() == 'struct': + return generate_struct(next(schema_walker)) + elif data_type.which() == 'list': + _ = next(schema_walker) + return [generate_type(type_walker, schema_walker)] + elif data_type.which() == 'enum': + return "text" + else: + return str(data_type.which()) + + +def generate_struct(schema: capnp.lib.capnp._StructSchema) -> Dict[str, Any]: + return {field: generate_field(schema.fields[field]) for field in schema.fields if not field.endswith("DEPRECATED")} + + +def generate_field(field: capnp.lib.capnp._StructSchemaField) -> Union[str, List[Any], Dict[str, Any]]: + def schema_walker(field): + yield field.schema + + s = field.schema + while hasattr(s, 'elementType'): + s = s.elementType + yield s + + def type_walker(field): + yield field.proto.slot.type + + t = field.proto.slot.type + while hasattr(getattr(t, t.which()), 'elementType'): + t = getattr(t, t.which()).elementType + yield t + + if field.proto.which() == "slot": + schema_gen, type_gen = schema_walker(field), type_walker(field) + return generate_type(type_gen, schema_gen) + else: + return generate_struct(field.schema) diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index 3f2ef2ceb4..12f9328532 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -23,8 +23,9 @@ from teleoprtc.info import parse_info_from_offer from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker +from openpilot.system.webrtc.schema import generate_field -from cereal import messaging +from cereal import messaging, log class CerealOutgoingMessageProxy: @@ -205,6 +206,14 @@ async def get_stream(request: web.Request): return web.json_response({"sdp": answer.sdp, "type": answer.type}) +async def get_schema(request: web.Request): + services = request.query["services"].split(",") + services = [s for s in services if s] + assert all(s in log.Event.schema.fields and not s.endswith("DEPRECATED") for s in services), "Invalid service name" + schema_dict = {s: generate_field(log.Event.schema.fields[s]) for s in services} + return web.json_response(schema_dict) + + async def on_shutdown(app: web.Application): for session in app['streams'].values(): session.stop() @@ -223,6 +232,7 @@ def webrtcd_thread(host: str, port: int, debug: bool): app['debug'] = debug app.on_shutdown.append(on_shutdown) app.router.add_post("/stream", get_stream) + app.router.add_get("/schema", get_schema) web.run_app(app, host=host, port=port) From 3fed87dbb701c263f38d64a29f7bbf186ad25e39 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 4 Dec 2023 23:16:12 -0600 Subject: [PATCH 125/724] Toyota: rename LTA torque wind down signal (#30603) * bump * rename * bump --- opendbc | 2 +- panda | 2 +- selfdrive/car/toyota/carcontroller.py | 8 ++++---- selfdrive/car/toyota/toyotacan.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/opendbc b/opendbc index ed3af3da1b..5b0c73977f 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit ed3af3da1b7d52aed910f22cc5540e31f92ba254 +Subproject commit 5b0c73977f1428700d0344d52874a90a4c5168fb diff --git a/panda b/panda index 892ca5a0f1..ea78657bef 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 892ca5a0f19c07a4786373e360027ab6ec57f723 +Subproject commit ea78657bef236c6a74b44549e541caf1d7fee618 diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 0fff0fcf6b..badfd33e0b 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -88,15 +88,15 @@ class CarController: # STEERING_LTA does not seem to allow more rate by sending faster, and may wind up easier if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR: lta_active = lat_active and self.CP.steerControlType == SteerControlType.angle - # cut steering torque with SETME_X64 when either EPS torque or driver torque is above + # cut steering torque with TORQUE_WIND_DOWN when either EPS torque or driver torque is above # the threshold, to limit max lateral acceleration and for driver torque blending respectively. full_torque_condition = (abs(CS.out.steeringTorqueEps) < self.params.STEER_MAX and abs(CS.out.steeringTorque) < MAX_LTA_DRIVER_TORQUE_ALLOWANCE) - # SETME_X64 at 0 ramps down torque at roughly the max down rate of 1500 units/sec - setme_x64 = 100 if lta_active and full_torque_condition else 0 + # TORQUE_WIND_DOWN at 0 ramps down torque at roughly the max down rate of 1500 units/sec + torque_wind_down = 100 if lta_active and full_torque_condition else 0 can_sends.append(toyotacan.create_lta_steer_command(self.packer, self.CP.steerControlType, self.last_angle, - lta_active, self.frame // 2, setme_x64)) + lta_active, self.frame // 2, torque_wind_down)) # *** gas and brake *** if self.CP.enableGasInterceptor and CC.longActive: diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 9c0b0643d6..e14e3e53a0 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -14,7 +14,7 @@ def create_steer_command(packer, steer, steer_req): return packer.make_can_msg("STEERING_LKA", 0, values) -def create_lta_steer_command(packer, steer_control_type, steer_angle, steer_req, frame, setme_x64): +def create_lta_steer_command(packer, steer_control_type, steer_angle, steer_req, frame, torque_wind_down): """Creates a CAN message for the Toyota LTA Steer Command.""" values = { @@ -23,7 +23,7 @@ def create_lta_steer_command(packer, steer_control_type, steer_angle, steer_req, # 1 for TSS 2.5 cars, 3 for TSS 2.0. Send based on whether we're using LTA for lateral control "SETME_X3": 1 if steer_control_type == SteerControlType.angle else 3, "PERCENTAGE": 100, - "SETME_X64": setme_x64, + "TORQUE_WIND_DOWN": torque_wind_down, "ANGLE": 0, "STEER_ANGLE_CMD": steer_angle, "STEER_REQUEST": steer_req, From 09a9ba6de1603fbcda0f883d5298b3f80f8295e1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 4 Dec 2023 23:16:33 -0600 Subject: [PATCH 126/724] joystick: bump max angle (#30602) --- selfdrive/controls/controlsd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a0bfbcd837..6f2ae6aa3a 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -633,7 +633,7 @@ class Controls: if CC.latActive: steer = clip(joystick_axes[1], -1, 1) # max angle is 45 for angle-based cars, max curvature is 0.02 - actuators.steer, actuators.steeringAngleDeg, actuators.curvature = steer, steer * 45., steer * -0.02 + actuators.steer, actuators.steeringAngleDeg, actuators.curvature = steer, steer * 90., steer * -0.02 lac_log.active = self.active lac_log.steeringAngleDeg = CS.steeringAngleDeg From 2ae7d99143751f2e81890ea2556c9fe0175b54d5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 5 Dec 2023 00:59:35 -0600 Subject: [PATCH 127/724] Revert "webrtcd: endpoint for message schema retrieval" (#30606) Revert "webrtcd: endpoint for message schema retrieval (#30578)" This reverts commit 10eb70daf707760f785490f69b2d17621698ed75. --- system/webrtc/schema.py | 43 ---------------------------------------- system/webrtc/webrtcd.py | 12 +---------- 2 files changed, 1 insertion(+), 54 deletions(-) delete mode 100644 system/webrtc/schema.py diff --git a/system/webrtc/schema.py b/system/webrtc/schema.py deleted file mode 100644 index f659b34293..0000000000 --- a/system/webrtc/schema.py +++ /dev/null @@ -1,43 +0,0 @@ -import capnp -from typing import Union, List, Dict, Any - - -def generate_type(type_walker, schema_walker) -> Union[str, List[Any], Dict[str, Any]]: - data_type = next(type_walker) - if data_type.which() == 'struct': - return generate_struct(next(schema_walker)) - elif data_type.which() == 'list': - _ = next(schema_walker) - return [generate_type(type_walker, schema_walker)] - elif data_type.which() == 'enum': - return "text" - else: - return str(data_type.which()) - - -def generate_struct(schema: capnp.lib.capnp._StructSchema) -> Dict[str, Any]: - return {field: generate_field(schema.fields[field]) for field in schema.fields if not field.endswith("DEPRECATED")} - - -def generate_field(field: capnp.lib.capnp._StructSchemaField) -> Union[str, List[Any], Dict[str, Any]]: - def schema_walker(field): - yield field.schema - - s = field.schema - while hasattr(s, 'elementType'): - s = s.elementType - yield s - - def type_walker(field): - yield field.proto.slot.type - - t = field.proto.slot.type - while hasattr(getattr(t, t.which()), 'elementType'): - t = getattr(t, t.which()).elementType - yield t - - if field.proto.which() == "slot": - schema_gen, type_gen = schema_walker(field), type_walker(field) - return generate_type(type_gen, schema_gen) - else: - return generate_struct(field.schema) diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index 12f9328532..3f2ef2ceb4 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -23,9 +23,8 @@ from teleoprtc.info import parse_info_from_offer from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker -from openpilot.system.webrtc.schema import generate_field -from cereal import messaging, log +from cereal import messaging class CerealOutgoingMessageProxy: @@ -206,14 +205,6 @@ async def get_stream(request: web.Request): return web.json_response({"sdp": answer.sdp, "type": answer.type}) -async def get_schema(request: web.Request): - services = request.query["services"].split(",") - services = [s for s in services if s] - assert all(s in log.Event.schema.fields and not s.endswith("DEPRECATED") for s in services), "Invalid service name" - schema_dict = {s: generate_field(log.Event.schema.fields[s]) for s in services} - return web.json_response(schema_dict) - - async def on_shutdown(app: web.Application): for session in app['streams'].values(): session.stop() @@ -232,7 +223,6 @@ def webrtcd_thread(host: str, port: int, debug: bool): app['debug'] = debug app.on_shutdown.append(on_shutdown) app.router.add_post("/stream", get_stream) - app.router.add_get("/schema", get_schema) web.run_app(app, host=host, port=port) From f46f00b373fac85a9b0c673feb4e809b57802216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 4 Dec 2023 23:51:52 -0800 Subject: [PATCH 128/724] webrtcd: endpoint for message schema retrieval vol. 2 (#30607) * webrtcd: endpoint for message schema retrieval (#30578) * Capnp json schema conversion * Schema get endpoint * Type annotation for generate_field * Filter empty services * Add schema.py to release --- release/files_common | 1 + system/webrtc/schema.py | 43 ++++++++++++++++++++++++++++++++++++++++ system/webrtc/webrtcd.py | 12 ++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 system/webrtc/schema.py diff --git a/release/files_common b/release/files_common index d92be1c3a6..3fd5df5693 100644 --- a/release/files_common +++ b/release/files_common @@ -283,6 +283,7 @@ system/sensord/pigeond.py system/webrtc/__init__.py system/webrtc/webrtcd.py +system/webrtc/schema.py system/webrtc/device/audio.py system/webrtc/device/video.py diff --git a/system/webrtc/schema.py b/system/webrtc/schema.py new file mode 100644 index 0000000000..f659b34293 --- /dev/null +++ b/system/webrtc/schema.py @@ -0,0 +1,43 @@ +import capnp +from typing import Union, List, Dict, Any + + +def generate_type(type_walker, schema_walker) -> Union[str, List[Any], Dict[str, Any]]: + data_type = next(type_walker) + if data_type.which() == 'struct': + return generate_struct(next(schema_walker)) + elif data_type.which() == 'list': + _ = next(schema_walker) + return [generate_type(type_walker, schema_walker)] + elif data_type.which() == 'enum': + return "text" + else: + return str(data_type.which()) + + +def generate_struct(schema: capnp.lib.capnp._StructSchema) -> Dict[str, Any]: + return {field: generate_field(schema.fields[field]) for field in schema.fields if not field.endswith("DEPRECATED")} + + +def generate_field(field: capnp.lib.capnp._StructSchemaField) -> Union[str, List[Any], Dict[str, Any]]: + def schema_walker(field): + yield field.schema + + s = field.schema + while hasattr(s, 'elementType'): + s = s.elementType + yield s + + def type_walker(field): + yield field.proto.slot.type + + t = field.proto.slot.type + while hasattr(getattr(t, t.which()), 'elementType'): + t = getattr(t, t.which()).elementType + yield t + + if field.proto.which() == "slot": + schema_gen, type_gen = schema_walker(field), type_walker(field) + return generate_type(type_gen, schema_gen) + else: + return generate_struct(field.schema) diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index 3f2ef2ceb4..12f9328532 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -23,8 +23,9 @@ from teleoprtc.info import parse_info_from_offer from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker +from openpilot.system.webrtc.schema import generate_field -from cereal import messaging +from cereal import messaging, log class CerealOutgoingMessageProxy: @@ -205,6 +206,14 @@ async def get_stream(request: web.Request): return web.json_response({"sdp": answer.sdp, "type": answer.type}) +async def get_schema(request: web.Request): + services = request.query["services"].split(",") + services = [s for s in services if s] + assert all(s in log.Event.schema.fields and not s.endswith("DEPRECATED") for s in services), "Invalid service name" + schema_dict = {s: generate_field(log.Event.schema.fields[s]) for s in services} + return web.json_response(schema_dict) + + async def on_shutdown(app: web.Application): for session in app['streams'].values(): session.stop() @@ -223,6 +232,7 @@ def webrtcd_thread(host: str, port: int, debug: bool): app['debug'] = debug app.on_shutdown.append(on_shutdown) app.router.add_post("/stream", get_stream) + app.router.add_get("/schema", get_schema) web.run_app(app, host=host, port=port) From 56b8a1a5db81d1dd3b4e1a97b90be5b92d1af510 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 5 Dec 2023 12:24:03 -0800 Subject: [PATCH 129/724] pytest: add durations to tici (#30609) add durations to tici --- selfdrive/test/pytest-tici.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/pytest-tici.ini b/selfdrive/test/pytest-tici.ini index a553018309..98e75d0661 100644 --- a/selfdrive/test/pytest-tici.ini +++ b/selfdrive/test/pytest-tici.ini @@ -1,5 +1,5 @@ [pytest] -addopts = -Werror --strict-config --strict-markers +addopts = -Werror --strict-config --strict-markers --durations=10 markers = slow: tests that take awhile to run and can be skipped with -m 'not slow' tici: tests that are only meant to run on the C3/C3X From 6c62a3146607e87c1c4f796eb5a7c988f733d6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Tue, 5 Dec 2023 14:42:44 -0800 Subject: [PATCH 130/724] joystickd: remove WEB (#30612) Remove WEB option from joystickd --- tools/joystick/joystickd.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index 82847e3fa1..c6024bf5e4 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -82,9 +82,6 @@ def send_thread(joystick): dat.testJoystick.buttons = [joystick.cancel] joystick_sock.send(dat.to_bytes()) print('\n' + ', '.join(f'{name}: {round(v, 3)}' for name, v in joystick.axes_values.items())) - if "WEB" in os.environ: - import requests - requests.get("http://"+os.environ["WEB"]+":5000/control/%f/%f" % tuple([joystick.axes_values[a] for a in joystick.axes_order][::-1]), timeout=None) rk.keep_time() def joystick_thread(joystick): @@ -101,7 +98,7 @@ if __name__ == '__main__': parser.add_argument('--gamepad', action='store_true', help='Use gamepad configuration instead of joystick') args = parser.parse_args() - if not Params().get_bool("IsOffroad") and "ZMQ" not in os.environ and "WEB" not in os.environ: + if not Params().get_bool("IsOffroad") and "ZMQ" not in os.environ: print("The car must be off before running joystickd.") exit() From 7948a61b0eaf98b3ace03e26027d8493f749e74f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Tue, 5 Dec 2023 15:00:05 -0800 Subject: [PATCH 131/724] bodyteleop: toggle joystick debug mode (#30611) * Enable joystick debug mode in web * Remove mutable vals * Rename thread back to main --- tools/bodyteleop/web.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tools/bodyteleop/web.py b/tools/bodyteleop/web.py index 2ec6eb037c..53077af67e 100644 --- a/tools/bodyteleop/web.py +++ b/tools/bodyteleop/web.py @@ -12,6 +12,7 @@ import wave from openpilot.common.basedir import BASEDIR from openpilot.system.webrtc.webrtcd import StreamRequestBody +from openpilot.common.params import Params logger = logging.getLogger("bodyteleop") logging.basicConfig(level=logging.INFO) @@ -23,14 +24,14 @@ WEBRTCD_HOST, WEBRTCD_PORT = "localhost", 5001 ## UTILS async def play_sound(sound): SOUNDS = { - 'engage': 'selfdrive/assets/sounds/engage.wav', - 'disengage': 'selfdrive/assets/sounds/disengage.wav', - 'error': 'selfdrive/assets/sounds/warning_immediate.wav', + "engage": "selfdrive/assets/sounds/engage.wav", + "disengage": "selfdrive/assets/sounds/disengage.wav", + "error": "selfdrive/assets/sounds/warning_immediate.wav", } assert sound in SOUNDS chunk = 5120 - with wave.open(os.path.join(BASEDIR, SOUNDS[sound]), 'rb') as wf: + with wave.open(os.path.join(BASEDIR, SOUNDS[sound]), "rb") as wf: def callback(in_data, frame_count, time_info, status): data = wf.readframes(frame_count) return data, pyaudio.paContinue @@ -61,8 +62,8 @@ def create_ssl_cert(cert_path, key_path): def create_ssl_context(): - cert_path = os.path.join(TELEOPDIR, 'cert.pem') - key_path = os.path.join(TELEOPDIR, 'key.pem') + cert_path = os.path.join(TELEOPDIR, "cert.pem") + key_path = os.path.join(TELEOPDIR, "key.pem") if not os.path.exists(cert_path) or not os.path.exists(key_path): logger.info("Creating certificate...") create_ssl_cert(cert_path, key_path) @@ -106,11 +107,13 @@ async def offer(request): def main(): + # Enable joystick debug mode + Params().put_bool("JoystickDebugMode", True) + # App needs to be HTTPS for microphone and audio autoplay to work on the browser ssl_context = create_ssl_context() app = web.Application() - app['mutable_vals'] = {} app.router.add_get("/", index) app.router.add_get("/ping", ping, allow_head=True) app.router.add_post("/offer", offer) From f32e3ae799aa26a132bfd61e1d98cf7273b5c239 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 5 Dec 2023 15:48:26 -0800 Subject: [PATCH 132/724] Docker: pull image before building (#30614) * pin to a specific version * pull latest version * dont need version debug --- selfdrive/test/docker_build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh index e0ba54f058..bd8c34c472 100755 --- a/selfdrive/test/docker_build.sh +++ b/selfdrive/test/docker_build.sh @@ -17,7 +17,7 @@ fi source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" -DOCKER_BUILDKIT=1 docker buildx build --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR +DOCKER_BUILDKIT=1 docker buildx build --pull --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR if [ -n "$PUSH_IMAGE" ]; then docker push $REMOTE_TAG From abe39e50766c418818bf72d2567db78978dbd466 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 5 Dec 2023 18:10:01 -0800 Subject: [PATCH 133/724] Soundd: move to python (#30567) soundd python --- docs/c_docs.rst | 6 - release/files_common | 5 +- selfdrive/assets/sounds/warning_immediate.wav | Bin 62760 -> 68306 bytes selfdrive/manager/process_config.py | 2 +- selfdrive/test/test_onroad.py | 2 +- selfdrive/ui/SConscript | 6 - selfdrive/ui/__init__.py | 0 selfdrive/ui/soundd.py | 160 ++++++++++++++++++ selfdrive/ui/soundd/.gitignore | 1 - selfdrive/ui/soundd/main.cc | 18 -- selfdrive/ui/soundd/sound.cc | 67 -------- selfdrive/ui/soundd/sound.h | 41 ----- selfdrive/ui/soundd/soundd | 4 - selfdrive/ui/tests/test_sound.cc | 75 -------- selfdrive/ui/tests/test_soundd.py | 80 +++------ system/hardware/base.h | 1 - system/hardware/pc/hardware.h | 8 - system/hardware/tici/hardware.h | 8 - 18 files changed, 186 insertions(+), 298 deletions(-) create mode 100644 selfdrive/ui/__init__.py create mode 100644 selfdrive/ui/soundd.py delete mode 100644 selfdrive/ui/soundd/.gitignore delete mode 100644 selfdrive/ui/soundd/main.cc delete mode 100644 selfdrive/ui/soundd/sound.cc delete mode 100644 selfdrive/ui/soundd/sound.h delete mode 100755 selfdrive/ui/soundd/soundd delete mode 100644 selfdrive/ui/tests/test_sound.cc diff --git a/docs/c_docs.rst b/docs/c_docs.rst index 0b9d23972e..6e8808ec20 100644 --- a/docs/c_docs.rst +++ b/docs/c_docs.rst @@ -41,12 +41,6 @@ ui .. autodoxygenindex:: :project: selfdrive_ui -soundd -"""""" -.. autodoxygenindex:: - :project: selfdrive_ui_soundd - - replay """""" .. autodoxygenindex:: diff --git a/release/files_common b/release/files_common index 3fd5df5693..ddc3e31d4d 100644 --- a/release/files_common +++ b/release/files_common @@ -306,10 +306,7 @@ selfdrive/ui/*.h selfdrive/ui/ui selfdrive/ui/text selfdrive/ui/spinner -selfdrive/ui/soundd/*.cc -selfdrive/ui/soundd/*.h -selfdrive/ui/soundd/soundd -selfdrive/ui/soundd/.gitignore +selfdrive/ui/soundd.py selfdrive/ui/translations/*.ts selfdrive/ui/translations/languages.json selfdrive/ui/update_translations.py diff --git a/selfdrive/assets/sounds/warning_immediate.wav b/selfdrive/assets/sounds/warning_immediate.wav index 9f6f672e2829c0924fc9bdb175050240ef9259e0..b1815a95867b9e4f5e2f5ec41ad29f80ffae228f 100644 GIT binary patch literal 68306 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 literal 62760 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 z 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 diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 4ad2574188..36d69b03bb 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -60,7 +60,7 @@ procs = [ PythonProcess("navmodeld", "selfdrive.modeld.navmodeld", only_onroad), NativeProcess("sensord", "system/sensord", ["./sensord"], only_onroad, enabled=not PC), NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), - NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], only_onroad), + PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"], only_onroad), NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], always_run, enabled=False), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad), diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index e89ce5c72b..f8d8db9cdc 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -46,7 +46,7 @@ PROCS = { "selfdrive.thermald.thermald": 3.87, "selfdrive.locationd.calibrationd": 2.0, "selfdrive.locationd.torqued": 5.0, - "./_soundd": (1.0, 65.0), + "selfdrive.ui.soundd": 5.8, "selfdrive.monitoring.dmonitoringd": 4.0, "./proclogd": 1.54, "system.logmessaged": 0.2, diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 916f1017a3..f0db1f63a7 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -70,12 +70,6 @@ qt_env.Command(assets, [assets_src, translations_assets_src], f"rcc $SOURCES -o qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, translations_assets_src, "#selfdrive/assets/assets.o"]) + [lrelease]) asset_obj = qt_env.Object("assets", assets) -# build soundd -qt_env.Program("soundd/_soundd", ["soundd/main.cc", "soundd/sound.cc"], LIBS=qt_libs) -if GetOption('extras'): - qt_env.Program("tests/playsound", "tests/playsound.cc", LIBS=base_libs) - qt_env.Program('tests/test_sound', ['tests/test_runner.cc', 'soundd/sound.cc', 'tests/test_sound.cc'], LIBS=qt_libs) - qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) # spinner and text window diff --git a/selfdrive/ui/__init__.py b/selfdrive/ui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py new file mode 100644 index 0000000000..33fcf0be31 --- /dev/null +++ b/selfdrive/ui/soundd.py @@ -0,0 +1,160 @@ +import math +import time +import numpy as np +import os +import wave + +from typing import Dict, Optional, Tuple + +from cereal import car, messaging +from openpilot.common.basedir import BASEDIR +from openpilot.common.realtime import Ratekeeper +from openpilot.system.hardware import PC +from openpilot.system.swaglog import cloudlog + +SAMPLE_RATE = 48000 +MAX_VOLUME = 1.0 +MIN_VOLUME = 0.1 +CONTROLS_TIMEOUT = 5 # 5 seconds + +AMBIENT_DB = 30 # DB where MIN_VOLUME is applied +DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied + +AudibleAlert = car.CarControl.HUDControl.AudibleAlert + + +sound_list: Dict[int, Tuple[str, Optional[int], float]] = { + # AudibleAlert, file name, play count (none for infinite) + AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME), + AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME), + AudibleAlert.refuse: ("refuse.wav", 1, MAX_VOLUME), + + AudibleAlert.prompt: ("prompt.wav", 1, MAX_VOLUME), + AudibleAlert.promptRepeat: ("prompt.wav", None, MAX_VOLUME), + AudibleAlert.promptDistracted: ("prompt_distracted.wav", None, MAX_VOLUME), + + AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME), + AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME), +} + +def check_controls_timeout_alert(sm): + controls_missing = time.monotonic() - sm.rcv_time['controlsState'] + + if controls_missing > CONTROLS_TIMEOUT: + if sm['controlsState'].enabled and (controls_missing - CONTROLS_TIMEOUT) < 10: + return True + + return False + + +class Soundd: + def __init__(self): + self.load_sounds() + + self.current_alert = AudibleAlert.none + self.current_volume = MIN_VOLUME + self.current_sound_frame = 0 + + self.controls_timeout_alert = False + + if not PC: + os.system("pactl set-sink-volume @DEFAULT_SINK@ 0.9") # set to max volume and control volume within soundd + + def load_sounds(self): + self.loaded_sounds: Dict[int, np.ndarray] = {} + + # Load all sounds + for sound in sound_list: + filename, play_count, volume = sound_list[sound] + + wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r') + + assert wavefile.getnchannels() == 1 + assert wavefile.getsampwidth() == 2 + assert wavefile.getframerate() == SAMPLE_RATE + + length = wavefile.getnframes() + self.loaded_sounds[sound] = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2) + + def get_sound_data(self, frames): # get "frames" worth of data from the current alert sound, looping when required + + ret = np.zeros(frames, dtype=np.float32) + + if self.current_alert != AudibleAlert.none: + num_loops = sound_list[self.current_alert][1] + sound_data = self.loaded_sounds[self.current_alert] + written_frames = 0 + + current_sound_frame = self.current_sound_frame % len(sound_data) + loops = self.current_sound_frame // len(sound_data) + + while written_frames < frames and (num_loops is None or loops < num_loops): + available_frames = sound_data.shape[0] - current_sound_frame + frames_to_write = min(available_frames, frames - written_frames) + ret[written_frames:written_frames+frames_to_write] = sound_data[current_sound_frame:current_sound_frame+frames_to_write] + written_frames += frames_to_write + self.current_sound_frame += frames_to_write + + return ret * self.current_volume + + def callback(self, data_out: np.ndarray, frames: int, time, status) -> None: + if status: + cloudlog.warning(f"soundd stream over/underflow: {status}") + data_out[:frames, 0] = self.get_sound_data(frames) + + def update_alert(self, new_alert): + current_alert_played_once = self.current_alert == AudibleAlert.none or self.current_sound_frame > len(self.loaded_sounds[self.current_alert]) + if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once): + self.current_alert = new_alert + self.current_sound_frame = 0 + + def get_audible_alert(self, sm): + if sm.updated['controlsState']: + new_alert = sm['controlsState'].alertSound.raw + self.update_alert(new_alert) + elif check_controls_timeout_alert(sm): + self.update_alert(AudibleAlert.warningImmediate) + self.controls_timeout_alert = True + elif self.controls_timeout_alert: + self.update_alert(AudibleAlert.none) + self.controls_timeout_alert = False + + def calculate_volume(self, weighted_db): + volume = ((weighted_db - AMBIENT_DB) / DB_SCALE) * (MAX_VOLUME - MIN_VOLUME) + MIN_VOLUME + return math.pow(10, (np.clip(volume, MIN_VOLUME, MAX_VOLUME) - 1)) + + def soundd_thread(self): + # sounddevice must be imported after forking processes + import sounddevice as sd + + rk = Ratekeeper(20) + + sm = messaging.SubMaster(['controlsState', 'microphone']) + + if PC: + device = None + else: + device = "pulse" # "sdm845-tavil-snd-card: - (hw:0,0)" + + with sd.OutputStream(device=device, channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: + cloudlog.info(f"soundd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") + while True: + sm.update(0) + + if sm.updated['microphone']: + self.current_volume = self.calculate_volume(sm["microphone"].filteredSoundPressureWeightedDb) + + self.get_audible_alert(sm) + + rk.keep_time() + + assert stream.active + + +def main(): + s = Soundd() + s.soundd_thread() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/selfdrive/ui/soundd/.gitignore b/selfdrive/ui/soundd/.gitignore deleted file mode 100644 index c47f949d37..0000000000 --- a/selfdrive/ui/soundd/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_soundd diff --git a/selfdrive/ui/soundd/main.cc b/selfdrive/ui/soundd/main.cc deleted file mode 100644 index c6c7434ca4..0000000000 --- a/selfdrive/ui/soundd/main.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include - -#include - -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/soundd/sound.h" - -int main(int argc, char **argv) { - qInstallMessageHandler(swagLogMessageHandler); - setpriority(PRIO_PROCESS, 0, -20); - - QApplication a(argc, argv); - std::signal(SIGINT, sigTermHandler); - std::signal(SIGTERM, sigTermHandler); - - Sound sound; - return a.exec(); -} diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc deleted file mode 100644 index a5884f113f..0000000000 --- a/selfdrive/ui/soundd/sound.cc +++ /dev/null @@ -1,67 +0,0 @@ -#include "selfdrive/ui/soundd/sound.h" - -#include - -#include -#include -#include - -#include "cereal/messaging/messaging.h" -#include "common/util.h" - -// TODO: detect when we can't play sounds -// TODO: detect when we can't display the UI - -Sound::Sound(QObject *parent) : sm({"controlsState", "microphone"}) { - qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName(); - - for (auto &[alert, fn, loops, volume] : sound_list) { - QSoundEffect *s = new QSoundEffect(this); - QObject::connect(s, &QSoundEffect::statusChanged, [=]() { - assert(s->status() != QSoundEffect::Error); - }); - s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn)); - s->setVolume(volume); - sounds[alert] = {s, loops}; - } - - QTimer *timer = new QTimer(this); - QObject::connect(timer, &QTimer::timeout, this, &Sound::update); - timer->start(1000 / UI_FREQ); -} - -void Sound::update() { - sm.update(0); - - // scale volume using ambient noise level - if (sm.updated("microphone")) { - float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureWeightedDb(), 30.f, 60.f, 0.f, 1.f); - volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); - // set volume on changes - if (std::exchange(current_volume, std::nearbyint(volume * 10)) != current_volume) { - Hardware::set_volume(volume); - } - } - - setAlert(Alert::get(sm, 0)); -} - -void Sound::setAlert(const Alert &alert) { - if (!current_alert.equal(alert)) { - current_alert = alert; - // stop sounds - for (auto &[s, loops] : sounds) { - // Only stop repeating sounds - if (s->loopsRemaining() > 1 || s->loopsRemaining() == QSoundEffect::Infinite) { - s->stop(); - } - } - - // play sound - if (alert.sound != AudibleAlert::NONE) { - auto &[s, loops] = sounds[alert.sound]; - s->setLoopCount(loops); - s->play(); - } - } -} diff --git a/selfdrive/ui/soundd/sound.h b/selfdrive/ui/soundd/sound.h deleted file mode 100644 index 4fcb2e1bce..0000000000 --- a/selfdrive/ui/soundd/sound.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include "system/hardware/hw.h" -#include "selfdrive/ui/ui.h" - - -const float MAX_VOLUME = 1.0; - -const std::tuple sound_list[] = { - // AudibleAlert, file name, loop count - {AudibleAlert::ENGAGE, "engage.wav", 0, MAX_VOLUME}, - {AudibleAlert::DISENGAGE, "disengage.wav", 0, MAX_VOLUME}, - {AudibleAlert::REFUSE, "refuse.wav", 0, MAX_VOLUME}, - - {AudibleAlert::PROMPT, "prompt.wav", 0, MAX_VOLUME}, - {AudibleAlert::PROMPT_REPEAT, "prompt.wav", QSoundEffect::Infinite, MAX_VOLUME}, - {AudibleAlert::PROMPT_DISTRACTED, "prompt_distracted.wav", QSoundEffect::Infinite, MAX_VOLUME}, - - {AudibleAlert::WARNING_SOFT, "warning_soft.wav", QSoundEffect::Infinite, MAX_VOLUME}, - {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", QSoundEffect::Infinite, MAX_VOLUME}, -}; - -class Sound : public QObject { -public: - explicit Sound(QObject *parent = 0); - -protected: - void update(); - void setAlert(const Alert &alert); - - SubMaster sm; - Alert current_alert = {}; - QMap> sounds; - int current_volume = -1; -}; diff --git a/selfdrive/ui/soundd/soundd b/selfdrive/ui/soundd/soundd deleted file mode 100755 index 9b7a32fec9..0000000000 --- a/selfdrive/ui/soundd/soundd +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -cd "$(dirname "$0")" -export QT_QPA_PLATFORM="offscreen" -exec ./_soundd diff --git a/selfdrive/ui/tests/test_sound.cc b/selfdrive/ui/tests/test_sound.cc deleted file mode 100644 index d9cb5c0a7f..0000000000 --- a/selfdrive/ui/tests/test_sound.cc +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include - -#include "catch2/catch.hpp" -#include "selfdrive/ui/soundd/sound.h" - -class TestSound : public Sound { -public: - TestSound() : Sound() { - for (auto i = sounds.constBegin(); i != sounds.constEnd(); ++i) { - sound_stats[i.key()] = {0, 0}; - QObject::connect(i.value().first, &QSoundEffect::playingChanged, [=, s = i.value().first, a = i.key()]() { - if (s->isPlaying()) { - sound_stats[a].first++; - } else { - sound_stats[a].second++; - } - }); - } - } - - QMap> sound_stats; -}; - -void controls_thread(int loop_cnt) { - PubMaster pm({"controlsState", "deviceState"}); - MessageBuilder deviceStateMsg; - auto deviceState = deviceStateMsg.initEvent().initDeviceState(); - deviceState.setStarted(true); - - const int DT_CTRL = 10; // ms - for (int i = 0; i < loop_cnt; ++i) { - for (auto &[alert, fn, loops, volume] : sound_list) { - printf("testing %s\n", qPrintable(fn)); - for (int j = 0; j < 1000 / DT_CTRL; ++j) { - MessageBuilder msg; - auto cs = msg.initEvent().initControlsState(); - cs.setAlertSound(alert); - cs.setAlertType(fn.toStdString()); - pm.send("controlsState", msg); - pm.send("deviceState", deviceStateMsg); - QThread::msleep(DT_CTRL); - } - } - } - - // send no alert sound - for (int j = 0; j < 1000 / DT_CTRL; ++j) { - MessageBuilder msg; - msg.initEvent().initControlsState(); - pm.send("controlsState", msg); - QThread::msleep(DT_CTRL); - } - - QThread::currentThread()->quit(); -} - -TEST_CASE("test soundd") { - QEventLoop loop; - TestSound test_sound; - const int test_loop_cnt = 2; - - QThread t; - QObject::connect(&t, &QThread::started, [=]() { controls_thread(test_loop_cnt); }); - QObject::connect(&t, &QThread::finished, [&]() { loop.quit(); }); - t.start(); - loop.exec(); - - for (const AudibleAlert alert : test_sound.sound_stats.keys()) { - auto [play, stop] = test_sound.sound_stats[alert]; - REQUIRE(play == test_loop_cnt); - REQUIRE(stop == test_loop_cnt); - } -} diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py index 80a261e6d9..94ce26eb47 100755 --- a/selfdrive/ui/tests/test_soundd.py +++ b/selfdrive/ui/tests/test_soundd.py @@ -1,75 +1,41 @@ #!/usr/bin/env python3 -import subprocess -import time import unittest -from cereal import log, car -import cereal.messaging as messaging -from openpilot.selfdrive.test.helpers import phone_only, with_processes -# TODO: rewrite for unittest -from openpilot.common.realtime import DT_CTRL -from openpilot.system.hardware import HARDWARE +from cereal import car +from cereal import messaging +from cereal.messaging import SubMaster, PubMaster +from openpilot.selfdrive.ui.soundd import CONTROLS_TIMEOUT, check_controls_timeout_alert + +import time AudibleAlert = car.CarControl.HUDControl.AudibleAlert -SOUNDS = { - # sound: total writes - AudibleAlert.none: 0, - AudibleAlert.engage: 184, - AudibleAlert.disengage: 186, - AudibleAlert.refuse: 194, - AudibleAlert.prompt: 184, - AudibleAlert.promptRepeat: 487, - AudibleAlert.promptDistracted: 508, - AudibleAlert.warningSoft: 471, - AudibleAlert.warningImmediate: 470, -} - -def get_total_writes(): - audio_flinger = subprocess.check_output('dumpsys media.audio_flinger', shell=True, encoding='utf-8').strip() - write_lines = [l for l in audio_flinger.split('\n') if l.strip().startswith('Total writes')] - return sum(int(l.split(':')[1]) for l in write_lines) class TestSoundd(unittest.TestCase): - def test_sound_card_init(self): - assert HARDWARE.get_sound_card_online() + def test_check_controls_timeout_alert(self): + sm = SubMaster(['controlsState']) + pm = PubMaster(['controlsState']) - @phone_only - @with_processes(['soundd']) - def test_alert_sounds(self): - pm = messaging.PubMaster(['deviceState', 'controlsState']) + for _ in range(100): + cs = messaging.new_message('controlsState') + cs.controlsState.enabled = True - # make sure they're all defined - alert_sounds = {v: k for k, v in car.CarControl.HUDControl.AudibleAlert.schema.enumerants.items()} - diff = set(SOUNDS.keys()).symmetric_difference(alert_sounds.keys()) - assert len(diff) == 0, f"not all sounds defined in test: {diff}" + pm.send("controlsState", cs) - # wait for procs to init - time.sleep(1) + time.sleep(0.01) - for sound, expected_writes in SOUNDS.items(): - print(f"testing {alert_sounds[sound]}") - start_writes = get_total_writes() + sm.update(0) - for i in range(int(10 / DT_CTRL)): - msg = messaging.new_message('deviceState') - msg.deviceState.started = True - pm.send('deviceState', msg) + self.assertFalse(check_controls_timeout_alert(sm)) - msg = messaging.new_message('controlsState') - if i < int(6 / DT_CTRL): - msg.controlsState.alertSound = sound - msg.controlsState.alertType = str(sound) - msg.controlsState.alertText1 = "Testing Sounds" - msg.controlsState.alertText2 = f"playing {alert_sounds[sound]}" - msg.controlsState.alertSize = log.ControlsState.AlertSize.mid - pm.send('controlsState', msg) - time.sleep(DT_CTRL) + for _ in range(CONTROLS_TIMEOUT * 110): + sm.update(0) + time.sleep(0.01) + + self.assertTrue(check_controls_timeout_alert(sm)) + + # TODO: add test with micd for checking that soundd actually outputs sounds - tolerance = expected_writes / 8 - actual_writes = get_total_writes() - start_writes - print(f" expected {expected_writes} writes, got {actual_writes}") - assert abs(expected_writes - actual_writes) <= tolerance, f"{alert_sounds[sound]}: expected {expected_writes} writes, got {actual_writes}" if __name__ == "__main__": unittest.main() diff --git a/system/hardware/base.h b/system/hardware/base.h index 890a743ea0..dc2282a93a 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -29,7 +29,6 @@ public: static void poweroff() {} static void set_brightness(int percent) {} static void set_display_power(bool on) {} - static void set_volume(float volume) {} static bool get_ssh_enabled() { return false; } static void set_ssh_enabled(bool enabled) {} diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h index 189adbbbee..5dea184ca6 100644 --- a/system/hardware/pc/hardware.h +++ b/system/hardware/pc/hardware.h @@ -13,14 +13,6 @@ public: static bool TICI() { return util::getenv("TICI", 0) == 1; } static bool AGNOS() { return util::getenv("TICI", 0) == 1; } - static void set_volume(float volume) { - volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); - - char volume_str[6]; - snprintf(volume_str, sizeof(volume_str), "%.3f", volume); - std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); - } - static void config_cpu_rendering(bool offscreen) { if (offscreen) { setenv("QT_QPA_PLATFORM", "offscreen", 1); diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index 0a00aca5be..f6ea86b002 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -67,14 +67,6 @@ public: bl_power_control.close(); } } - static void set_volume(float volume) { - volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); - - char volume_str[6]; - snprintf(volume_str, sizeof(volume_str), "%.3f", volume); - std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); - } - static std::map get_init_logs() { std::map ret = { From 67d6186bbd5b19269162453a6028cabd6a2fabfc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 5 Dec 2023 21:24:28 -0600 Subject: [PATCH 134/724] Toyota: LTA unit test (#30613) LTA unit test --- selfdrive/car/toyota/tests/test_toyota.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py index 2a82ca5ec0..eae59a861f 100755 --- a/selfdrive/car/toyota/tests/test_toyota.py +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -17,6 +17,10 @@ class TestToyotaInterfaces(unittest.TestCase): self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0) self.assertTrue(len(RADAR_ACC_CAR - TSS2_CAR) == 0) + def test_lta_platforms(self): + # At this time, only RAV4 2023 is expected to use LTA/angle control + self.assertEqual(ANGLE_CONTROL_CAR, {CAR.RAV4_TSS2_2023}) + def test_tss2_dbc(self): # We make some assumptions about TSS2 platforms, # like looking up certain signals only in this DBC From 3b89c5fe29b3a63f34e9782ef71ac868d89c99d5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 5 Dec 2023 21:17:49 -0800 Subject: [PATCH 135/724] agnos 9 (#30543) * agnos 9 * new build * update * Soundd: use alsa (#30617) * update release notes * agnos does this now * prod manifest --------- Co-authored-by: Justin Newberry --- RELEASES.md | 1 + launch_env.sh | 2 +- selfdrive/test/setup_device_ci.sh | 1 - selfdrive/ui/soundd.py | 6 +--- system/hardware/tici/agnos.json | 48 +++++++++++++++---------------- 5 files changed, 27 insertions(+), 31 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 4c12971c54..6a531791df 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,6 @@ Version 0.9.6 (2023-XX-XX) ======================== +* AGNOS 9 Version 0.9.5 (2023-11-17) ======================== diff --git a/launch_env.sh b/launch_env.sh index d07fbe33de..dd8f431c63 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="8.2" + export AGNOS_VERSION="9.1" fi if [ -z "$PASSIVE" ]; then diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index c34e097f73..a180fe0de6 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -29,7 +29,6 @@ sudo abctl --set_success # patch sshd config sudo mount -o rw,remount / -echo tici-$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//') | sudo tee /etc/hostname sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config sudo systemctl daemon-reload sudo systemctl restart ssh diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 33fcf0be31..dac4bb74d8 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -1,7 +1,6 @@ import math import time import numpy as np -import os import wave from typing import Dict, Optional, Tuple @@ -57,9 +56,6 @@ class Soundd: self.controls_timeout_alert = False - if not PC: - os.system("pactl set-sink-volume @DEFAULT_SINK@ 0.9") # set to max volume and control volume within soundd - def load_sounds(self): self.loaded_sounds: Dict[int, np.ndarray] = {} @@ -134,7 +130,7 @@ class Soundd: if PC: device = None else: - device = "pulse" # "sdm845-tavil-snd-card: - (hw:0,0)" + device = "sdm845-tavil-snd-card: - (hw:0,0)" with sd.OutputStream(device=device, channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: cloudlog.info(f"soundd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 7e8cd480be..080fa2cf8e 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -1,9 +1,9 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279.img.xz", - "hash": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279", - "hash_raw": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279", + "url": "https://commadist.azureedge.net/agnosupdate/boot-fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275.img.xz", + "hash": "fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275", + "hash_raw": "fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275", "size": 15636480, "sparse": false, "full_check": true, @@ -11,9 +11,9 @@ }, { "name": "abl", - "url": "https://commadist.azureedge.net/agnosupdate/abl-0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d.img.xz", - "hash": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d", - "hash_raw": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d", + "url": "https://commadist.azureedge.net/agnosupdate/abl-bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a.img.xz", + "hash": "bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a", + "hash_raw": "bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a", "size": 274432, "sparse": false, "full_check": true, @@ -21,9 +21,9 @@ }, { "name": "xbl", - "url": "https://commadist.azureedge.net/agnosupdate/xbl-942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa.img.xz", - "hash": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa", - "hash_raw": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-bcef195b00a1ab685da601f4072722569773ab161e91c8753ad99ca4217a28f5.img.xz", + "hash": "bcef195b00a1ab685da601f4072722569773ab161e91c8753ad99ca4217a28f5", + "hash_raw": "bcef195b00a1ab685da601f4072722569773ab161e91c8753ad99ca4217a28f5", "size": 3282672, "sparse": false, "full_check": true, @@ -31,9 +31,9 @@ }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf.img.xz", - "hash": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf", - "hash_raw": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-19791056558c16f8dae787531b5e30b3b3db2ded9d666688df45ce1b91a72bac.img.xz", + "hash": "19791056558c16f8dae787531b5e30b3b3db2ded9d666688df45ce1b91a72bac", + "hash_raw": "19791056558c16f8dae787531b5e30b3b3db2ded9d666688df45ce1b91a72bac", "size": 98124, "sparse": false, "full_check": true, @@ -41,9 +41,9 @@ }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262.img.xz", - "hash": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262", - "hash_raw": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-be44b73dda5be840b09d5347d536459e31098da3fea97596956c0bdad19bdf27.img.xz", + "hash": "be44b73dda5be840b09d5347d536459e31098da3fea97596956c0bdad19bdf27", + "hash_raw": "be44b73dda5be840b09d5347d536459e31098da3fea97596956c0bdad19bdf27", "size": 40336, "sparse": false, "full_check": true, @@ -51,9 +51,9 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222.img.xz", - "hash": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222", - "hash_raw": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222", + "url": "https://commadist.azureedge.net/agnosupdate/aop-5d764611a683d6a738cf06a1dcf8a926d0f47b5117ad40d3054167de6dd8bd0f.img.xz", + "hash": "5d764611a683d6a738cf06a1dcf8a926d0f47b5117ad40d3054167de6dd8bd0f", + "hash_raw": "5d764611a683d6a738cf06a1dcf8a926d0f47b5117ad40d3054167de6dd8bd0f", "size": 184364, "sparse": false, "full_check": true, @@ -61,16 +61,16 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432.img.xz", - "hash": "611011f3e3f147bc24f371105a9dd3760ec11ba424c56d4a442a66b098c784c0", - "hash_raw": "e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432", + "url": "https://commadist.azureedge.net/agnosupdate/system-e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98.img.xz", + "hash": "3b6cdf9bd881a5e90b21dd02c6faa923b415e32ecae9bfdc96753d4208fb82fe", + "hash_raw": "e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98", "size": 10737418240, "sparse": true, "full_check": false, "has_ab": true, "alt": { - "hash": "256442a55fcb9e8f72969f003a4db91598dee1136f8dda85b553a557d36b93d8", - "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432.img.xz" + "hash": "2fb81e58f4bc6c4e5e71c8e7ac7553f85082c430627d7a5cc54a6bbc82862500", + "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98.img.xz" } } -] +] \ No newline at end of file From 3985103974e59efc84a2ffc6e054b0f8daa6adf2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 5 Dec 2023 21:32:55 -0800 Subject: [PATCH 136/724] update mapsd cpu usage --- selfdrive/test/test_onroad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index f8d8db9cdc..2d674e4610 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -34,7 +34,7 @@ PROCS = { "./encoderd": 17.0, "./camerad": 14.5, "./locationd": 11.0, - "./mapsd": 1.5, + "./mapsd": (1.0, 10.0), "selfdrive.controls.plannerd": 16.5, "./_ui": 18.0, "selfdrive.locationd.paramsd": 9.0, From db35dcd0b5353d9c009fcf5817e611125a6b0b8b Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Wed, 6 Dec 2023 09:55:29 -0800 Subject: [PATCH 137/724] replace common.file_helpers.mkdirs_exists_ok with python os.makedirs funtion (#30618) replace common.file_helpers.mkdirs_exists_ok with python os.makedirs function --- common/file_helpers.py | 10 ---------- selfdrive/tombstoned.py | 3 +-- system/camerad/test/get_thumbnails_for_segment.py | 3 +-- tools/lib/auth_config.py | 3 +-- tools/lib/cache.py | 3 +-- tools/lib/url_file.py | 4 ++-- 6 files changed, 6 insertions(+), 20 deletions(-) diff --git a/common/file_helpers.py b/common/file_helpers.py index 227d614d72..a29eafdd9f 100644 --- a/common/file_helpers.py +++ b/common/file_helpers.py @@ -4,16 +4,6 @@ import tempfile from atomicwrites import AtomicWriter -def mkdirs_exists_ok(path): - if path.startswith(('http://', 'https://')): - raise ValueError('URL path') - try: - os.makedirs(path) - except OSError: - if not os.path.isdir(path): - raise - - def rm_not_exists_ok(path): try: os.remove(path) diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index 3f2fb28d23..51e20dac49 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -9,7 +9,6 @@ import time import glob from typing import NoReturn -from openpilot.common.file_helpers import mkdirs_exists_ok import openpilot.selfdrive.sentry as sentry from openpilot.system.hardware.hw import Paths from openpilot.system.swaglog import cloudlog @@ -128,7 +127,7 @@ def report_tombstone_apport(fn): new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] crashlog_dir = os.path.join(Paths.log_root(), "crash") - mkdirs_exists_ok(crashlog_dir) + os.makedirs(crashlog_dir, exist_ok=True) # Files could be on different filesystems, copy, then delete shutil.copy(fn, os.path.join(crashlog_dir, new_fn)) diff --git a/system/camerad/test/get_thumbnails_for_segment.py b/system/camerad/test/get_thumbnails_for_segment.py index 97667ede86..43f543f1bd 100755 --- a/system/camerad/test/get_thumbnails_for_segment.py +++ b/system/camerad/test/get_thumbnails_for_segment.py @@ -4,7 +4,6 @@ import os from tqdm import tqdm -from openpilot.common.file_helpers import mkdirs_exists_ok from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import Route @@ -17,7 +16,7 @@ if __name__ == "__main__": args = parser.parse_args() out_path = os.path.join("jpegs", f"{args.route.replace('|', '_')}_{args.segment}") - mkdirs_exists_ok(out_path) + os.makedirs(out_path, exist_ok=True) r = Route(args.route) path = r.log_paths()[args.segment] or r.qlog_paths()[args.segment] diff --git a/tools/lib/auth_config.py b/tools/lib/auth_config.py index e0989f02ea..c4e7b6261b 100644 --- a/tools/lib/auth_config.py +++ b/tools/lib/auth_config.py @@ -1,6 +1,5 @@ import json import os -from openpilot.common.file_helpers import mkdirs_exists_ok from openpilot.system.hardware.hw import Paths @@ -18,7 +17,7 @@ def get_token(): def set_token(token): - mkdirs_exists_ok(Paths.config_root()) + os.makedirs(Paths.config_root(), exist_ok=True) with open(os.path.join(Paths.config_root(), 'auth.json'), 'w') as f: json.dump({'access_token': token}, f) diff --git a/tools/lib/cache.py b/tools/lib/cache.py index fd214f6bb5..d5fa288baa 100644 --- a/tools/lib/cache.py +++ b/tools/lib/cache.py @@ -1,12 +1,11 @@ import os import urllib.parse -from openpilot.common.file_helpers import mkdirs_exists_ok DEFAULT_CACHE_DIR = os.getenv("CACHE_ROOT", os.path.expanduser("~/.commacache")) def cache_path_for_file_path(fn, cache_dir=DEFAULT_CACHE_DIR): dir_ = os.path.join(cache_dir, "local") - mkdirs_exists_ok(dir_) + os.makedirs(dir_, exist_ok=True) fn_parsed = urllib.parse.urlparse(fn) if fn_parsed.scheme == '': cache_fn = os.path.abspath(fn).replace("/", "_") diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 315ade514b..a54552d241 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -5,7 +5,7 @@ import pycurl from hashlib import sha256 from io import BytesIO from tenacity import retry, wait_random_exponential, stop_after_attempt -from openpilot.common.file_helpers import mkdirs_exists_ok, atomic_write_in_dir +from openpilot.common.file_helpers import atomic_write_in_dir from openpilot.system.hardware.hw import Paths # Cache chunk size K = 1000 @@ -40,7 +40,7 @@ class URLFile: except AttributeError: self._curl = self._tlocal.curl = pycurl.Curl() if not self._force_download: - mkdirs_exists_ok(Paths.download_cache_root()) + os.makedirs(Paths.download_cache_root(), exist_ok=True) def __enter__(self): return self From 31ab43ce41d6d0f88cb8a9b6df8e79f23b6309fe Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 6 Dec 2023 11:13:22 -0800 Subject: [PATCH 138/724] jenkins: remove pytest tici conf (#30621) * remove pytest conf * remove that too --- Jenkinsfile | 3 ++- release/files_common | 1 - selfdrive/test/pytest-tici.ini | 5 ----- 3 files changed, 2 insertions(+), 7 deletions(-) delete mode 100644 selfdrive/test/pytest-tici.ini diff --git a/Jenkinsfile b/Jenkinsfile index 3dd9c45f14..fbe9cb48a4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,7 +14,8 @@ export GIT_BRANCH=${env.GIT_BRANCH} export GIT_COMMIT=${env.GIT_COMMIT} export AZURE_TOKEN='${env.AZURE_TOKEN}' export MAPBOX_TOKEN='${env.MAPBOX_TOKEN}' -export PYTEST_ADDOPTS="-c selfdrive/test/pytest-tici.ini --rootdir ." +# only use 1 thread for tici tests since most require HIL +export PYTEST_ADDOPTS="-n 1" export GIT_SSH_COMMAND="ssh -i /data/gitkey" diff --git a/release/files_common b/release/files_common index ddc3e31d4d..ff2d8bec28 100644 --- a/release/files_common +++ b/release/files_common @@ -297,7 +297,6 @@ selfdrive/test/helpers.py selfdrive/test/setup_device_ci.sh selfdrive/test/test_onroad.py selfdrive/test/test_time_to_onroad.py -selfdrive/test/pytest-tici.ini selfdrive/ui/.gitignore selfdrive/ui/SConscript diff --git a/selfdrive/test/pytest-tici.ini b/selfdrive/test/pytest-tici.ini deleted file mode 100644 index 98e75d0661..0000000000 --- a/selfdrive/test/pytest-tici.ini +++ /dev/null @@ -1,5 +0,0 @@ -[pytest] -addopts = -Werror --strict-config --strict-markers --durations=10 -markers = - slow: tests that take awhile to run and can be skipped with -m 'not slow' - tici: tests that are only meant to run on the C3/C3X From 5600a8288909e0b6ca95becbdb5746a0a9e35af4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 6 Dec 2023 14:59:16 -0800 Subject: [PATCH 139/724] Soundd: only update ambient db when not playing an alert (#30620) * move to soundd + only when quiet * not filtered --- selfdrive/ui/soundd.py | 14 +++++++++++--- system/micd.py | 6 ------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index dac4bb74d8..a3dc6fedfe 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -1,12 +1,16 @@ import math -import time import numpy as np +import time import wave from typing import Dict, Optional, Tuple from cereal import car, messaging from openpilot.common.basedir import BASEDIR +from openpilot.common.filter_simple import FirstOrderFilter + +from openpilot.system import micd + from openpilot.common.realtime import Ratekeeper from openpilot.system.hardware import PC from openpilot.system.swaglog import cloudlog @@ -15,6 +19,7 @@ SAMPLE_RATE = 48000 MAX_VOLUME = 1.0 MIN_VOLUME = 0.1 CONTROLS_TIMEOUT = 5 # 5 seconds +FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES) AMBIENT_DB = 30 # DB where MIN_VOLUME is applied DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied @@ -56,6 +61,8 @@ class Soundd: self.controls_timeout_alert = False + self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) + def load_sounds(self): self.loaded_sounds: Dict[int, np.ndarray] = {} @@ -137,8 +144,9 @@ class Soundd: while True: sm.update(0) - if sm.updated['microphone']: - self.current_volume = self.calculate_volume(sm["microphone"].filteredSoundPressureWeightedDb) + if sm.updated['microphone'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert + self.spl_filter_weighted.update(sm["microphone"].soundPressureWeightedDb) + self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x)) self.get_audible_alert(sm) diff --git a/system/micd.py b/system/micd.py index 0b7a4dadfb..452c04bc03 100755 --- a/system/micd.py +++ b/system/micd.py @@ -2,7 +2,6 @@ import numpy as np from cereal import messaging -from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import Ratekeeper from openpilot.system.swaglog import cloudlog @@ -10,7 +9,6 @@ RATE = 10 FFT_SAMPLES = 4096 REFERENCE_SPL = 2e-5 # newtons/m^2 SAMPLE_RATE = 44100 -FILTER_DT = 1. / (SAMPLE_RATE / FFT_SAMPLES) def calculate_spl(measurements): @@ -50,15 +48,12 @@ class Mic: self.sound_pressure_weighted = 0 self.sound_pressure_level_weighted = 0 - self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) - def update(self): msg = messaging.new_message('microphone', valid=True) msg.microphone.soundPressure = float(self.sound_pressure) msg.microphone.soundPressureWeighted = float(self.sound_pressure_weighted) msg.microphone.soundPressureWeightedDb = float(self.sound_pressure_level_weighted) - msg.microphone.filteredSoundPressureWeightedDb = float(self.spl_filter_weighted.x) self.pm.send('microphone', msg) self.rk.keep_time() @@ -79,7 +74,6 @@ class Mic: self.sound_pressure, _ = calculate_spl(measurements) measurements_weighted = apply_a_weighting(measurements) self.sound_pressure_weighted, self.sound_pressure_level_weighted = calculate_spl(measurements_weighted) - self.spl_filter_weighted.update(self.sound_pressure_level_weighted) self.measurements = self.measurements[FFT_SAMPLES:] From 492ba68cfcf71fa084c765c49b4357b769cbe70e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 6 Dec 2023 15:32:46 -0800 Subject: [PATCH 140/724] Update RELEASES.md --- RELEASES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 6a531791df..28c3da70a2 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,7 @@ -Version 0.9.6 (2023-XX-XX) +Version 0.9.6 (2023-12-14) ======================== * AGNOS 9 +* comma body streaming and controls over WebRTC Version 0.9.5 (2023-11-17) ======================== From 9dc8ecf722733d645e0a1492c7aff1e73908ab54 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 6 Dec 2023 15:33:17 -0800 Subject: [PATCH 141/724] ruff: ignore teleoprtc repo (#30627) ignore it --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 0e55e2ac60..ffbd5d43b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -190,6 +190,7 @@ exclude = [ "rednose_repo", "tinygrad_repo", "teleoprtc", + "teleoprtc_repo", "third_party", ] flake8-implicit-str-concat.allow-multiline=false From 3777bf7fb9b6d63f49118d010ac13cee89d13d06 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 6 Dec 2023 15:51:29 -0800 Subject: [PATCH 142/724] jenkins: disable xdist on tici (#30624) to preserve it --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index fbe9cb48a4..446435f7fe 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,7 +15,7 @@ export GIT_COMMIT=${env.GIT_COMMIT} export AZURE_TOKEN='${env.AZURE_TOKEN}' export MAPBOX_TOKEN='${env.MAPBOX_TOKEN}' # only use 1 thread for tici tests since most require HIL -export PYTEST_ADDOPTS="-n 1" +export PYTEST_ADDOPTS="-n 0" export GIT_SSH_COMMAND="ssh -i /data/gitkey" From f16df8e4b7901f5e38b6f90a8900eef02faab504 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 6 Dec 2023 16:00:59 -0800 Subject: [PATCH 143/724] pytest: group test_models by route instead of car name (#30625) * group by route * dont need those --- Jenkinsfile | 4 ++-- conftest.py | 5 +++-- selfdrive/car/tests/test_models.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 446435f7fe..ec39882201 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -235,8 +235,8 @@ node { pcStage("car tests") { sh label: "build", script: "selfdrive/manager/build.py" sh label: "test_models.py", script: "INTERNAL_SEG_CNT=250 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \ - pytest -n auto --dist=loadscope selfdrive/car/tests/test_models.py" - sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n auto --dist=load selfdrive/car/tests/test_car_interfaces.py" + pytest selfdrive/car/tests/test_models.py" + sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest selfdrive/car/tests/test_car_interfaces.py" } }, diff --git a/conftest.py b/conftest.py index 9aaf04d798..d1787c24ef 100644 --- a/conftest.py +++ b/conftest.py @@ -45,8 +45,9 @@ def pytest_collection_modifyitems(config, items): item.add_marker(skipper) if "xdist_group_class_property" in item.keywords: - class_property = item.get_closest_marker('xdist_group_class_property').args[0] - item.add_marker(pytest.mark.xdist_group(getattr(item.cls, class_property))) + class_property_name = item.get_closest_marker('xdist_group_class_property').args[0] + class_property_value = getattr(item.cls, class_property_name) + item.add_marker(pytest.mark.xdist_group(class_property_value)) @pytest.hookimpl(trylast=True) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index f26fd363ca..0ae104f9f5 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -382,7 +382,7 @@ class TestCarModelBase(unittest.TestCase): @parameterized_class(('car_model', 'test_route'), get_test_cases()) -@pytest.mark.xdist_group_class_property('car_model') +@pytest.mark.xdist_group_class_property('test_route') class TestCarModel(TestCarModelBase): pass From 36bebb1aa064b4b40580aa48d35b21bd9ebe14c2 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 6 Dec 2023 17:22:32 -0800 Subject: [PATCH 144/724] jenkins: retry build_devel (#30628) * will it work * retry build devel * correct p[ath * try a failure * Revert "try a failure" This reverts commit ba4e6a0a2096b94887fcfbf7d011e34aa2a16c41. --- Jenkinsfile | 2 +- release/build_devel.sh | 1 + scripts/retry.sh | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100755 scripts/retry.sh diff --git a/Jenkinsfile b/Jenkinsfile index ec39882201..840dd29503 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -158,7 +158,7 @@ node { // tici tests 'onroad tests': { deviceStage("onroad", "tici-needs-can", ["SKIP_COPY=1"], [ - ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR ./build_devel.sh"], + ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"], diff --git a/release/build_devel.sh b/release/build_devel.sh index ca04c56f1e..8b6816e423 100755 --- a/release/build_devel.sh +++ b/release/build_devel.sh @@ -22,6 +22,7 @@ pre-commit uninstall || true echo "[-] bringing master-ci and devel in sync T=$SECONDS" cd $TARGET_DIR + git fetch --depth 1 origin master-ci git fetch --depth 1 origin devel diff --git a/scripts/retry.sh b/scripts/retry.sh new file mode 100755 index 0000000000..4861748e55 --- /dev/null +++ b/scripts/retry.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +function fail { + echo $1 >&2 + exit 1 +} + +function retry { + local n=1 + local max=3 # 3 retries before failure + local delay=5 # delay between retries, 5 seconds + while true; do + echo "Running command '$@' with retry, attempt $n/$max" + "$@" && break || { + if [[ $n -lt $max ]]; then + ((n++)) + sleep $delay; + else + fail "The command has failed after $n attempts." + fi + } + done +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + retry "$@" +fi From 35f819c8239b8ac3a000087a0f791b91ccea5e5e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 6 Dec 2023 17:27:51 -0800 Subject: [PATCH 145/724] swaglog.py goes in common/ (#30631) * swaglog.py goes in common/ * all of these go in release * we'll even include the pyx --- {system => common}/swaglog.py | 0 release/files_common | 21 ++----------------- selfdrive/athena/athenad.py | 2 +- selfdrive/athena/manage_athenad.py | 2 +- selfdrive/athena/registration.py | 2 +- selfdrive/boardd/pandad.py | 2 +- selfdrive/car/car_helpers.py | 2 +- selfdrive/car/disable_ecu.py | 2 +- selfdrive/car/ecu_addrs.py | 2 +- selfdrive/car/fw_versions.py | 2 +- selfdrive/car/isotp_parallel_query.py | 2 +- selfdrive/car/vin.py | 2 +- selfdrive/controls/controlsd.py | 2 +- .../lib/longitudinal_mpc_lib/long_mpc.py | 2 +- .../controls/lib/longitudinal_planner.py | 2 +- selfdrive/controls/plannerd.py | 2 +- selfdrive/controls/radard.py | 2 +- selfdrive/locationd/calibrationd.py | 2 +- selfdrive/locationd/helpers.py | 2 +- selfdrive/locationd/models/car_kf.py | 2 +- selfdrive/locationd/paramsd.py | 2 +- selfdrive/locationd/torqued.py | 2 +- selfdrive/manager/build.py | 2 +- selfdrive/manager/manager.py | 2 +- selfdrive/manager/process.py | 2 +- selfdrive/modeld/dmonitoringmodeld.py | 2 +- selfdrive/modeld/modeld.py | 2 +- selfdrive/modeld/navmodeld.py | 2 +- selfdrive/navd/navd.py | 2 +- selfdrive/sentry.py | 2 +- selfdrive/statsd.py | 2 +- selfdrive/thermald/fan_controller.py | 2 +- selfdrive/thermald/power_monitoring.py | 2 +- selfdrive/thermald/thermald.py | 2 +- selfdrive/tombstoned.py | 2 +- selfdrive/ui/soundd.py | 2 +- selfdrive/updated.py | 2 +- system/loggerd/deleter.py | 2 +- system/loggerd/tests/test_uploader.py | 2 +- system/loggerd/uploader.py | 2 +- system/logmessaged.py | 2 +- system/micd.py | 2 +- system/qcomgpsd/qcomgpsd.py | 2 +- system/sensord/pigeond.py | 2 +- system/tests/test_logmessaged.py | 2 +- system/timezoned.py | 2 +- system/version.py | 2 +- 47 files changed, 47 insertions(+), 64 deletions(-) rename {system => common}/swaglog.py (100%) diff --git a/system/swaglog.py b/common/swaglog.py similarity index 100% rename from system/swaglog.py rename to common/swaglog.py diff --git a/release/files_common b/release/files_common index ff2d8bec28..7257fb820d 100644 --- a/release/files_common +++ b/release/files_common @@ -21,24 +21,8 @@ openpilot/** common/.gitignore common/__init__.py -common/conversions.py -common/gpio.py -common/realtime.py -common/timeout.py -common/ffi_wrapper.py -common/file_helpers.py -common/logging_extra.py -common/numpy_fast.py -common/params.py -common/params_pyx.pyx -common/profiler.py -common/basedir.py -common/dict_helpers.py -common/filter_simple.py -common/stat_live.py -common/spinner.py -common/text_window.py -common/time.py +common/*.py +common/*.pyx common/kalman/.gitignore common/kalman/* @@ -76,7 +60,6 @@ selfdrive/statsd.py system/logmessaged.py system/micd.py -system/swaglog.py system/version.py selfdrive/athena/__init__.py diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 423479517c..936797a89e 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -36,7 +36,7 @@ from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.system.hardware import HARDWARE, PC, AGNOS from openpilot.system.loggerd.xattr_cache import getxattr, setxattr -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version from openpilot.system.hardware.hw import Paths diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py index 877d8aca03..2a4a12e559 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/selfdrive/athena/manage_athenad.py @@ -5,7 +5,7 @@ from multiprocessing import Process from openpilot.common.params import Params from openpilot.selfdrive.manager.process import launcher -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_version, is_dirty ATHENA_MGR_PID_PARAM = "AthenadPid" diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index 7467e7fa86..c9a4b949ac 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -12,7 +12,7 @@ from openpilot.common.spinner import Spinner from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.system.hardware import HARDWARE, PC from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog UNREGISTERED_DONGLE_ID = "UnregisteredDevice" diff --git a/selfdrive/boardd/pandad.py b/selfdrive/boardd/pandad.py index 14d272965d..672678778b 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/boardd/pandad.py @@ -12,7 +12,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.boardd.set_time import set_time from openpilot.system.hardware import HARDWARE -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog def get_expected_signature(panda: Panda) -> bytes: diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 5c1234258e..fa68313e86 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.car.interfaces import get_interface_attr from openpilot.selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from openpilot.selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN from openpilot.selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog import cereal.messaging as messaging from openpilot.selfdrive.car import gen_empty_fingerprint diff --git a/selfdrive/car/disable_ecu.py b/selfdrive/car/disable_ecu.py index c11075342c..1f731f0344 100755 --- a/selfdrive/car/disable_ecu.py +++ b/selfdrive/car/disable_ecu.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog EXT_DIAG_REQUEST = b'\x10\x03' EXT_DIAG_RESPONSE = b'\x50\x03' diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index cff1df79a5..13f7926def 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -8,7 +8,7 @@ from panda.python.uds import SERVICE_TYPE from openpilot.selfdrive.car import make_can_msg from openpilot.selfdrive.car.fw_query_definitions import EcuAddrBusType from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog def make_tester_present_msg(addr, bus, subaddr=None): diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 45c4967cb8..f50c604316 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusTyp from openpilot.selfdrive.car.interfaces import get_interface_attr from openpilot.selfdrive.car.fingerprints import FW_VERSIONS from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog Ecu = car.CarParams.Ecu ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index adbc598ea6..696935fd35 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -3,7 +3,7 @@ from collections import defaultdict from functools import partial import cereal.messaging as messaging -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_addr_for_tx_addr diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 28edf157ae..e2709cc842 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -5,7 +5,7 @@ import cereal.messaging as messaging from panda.python.uds import get_rx_addr_for_tx_addr, FUNCTIONAL_ADDRS from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery from openpilot.selfdrive.car.fw_query_definitions import StdQueries -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog VIN_UNKNOWN = "0" * 17 VIN_RE = "[A-HJ-NPR-Z0-9]{17}" diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 6f2ae6aa3a..a180b4c410 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -13,7 +13,7 @@ import cereal.messaging as messaging from cereal.visionipc import VisionIpcClient, VisionStreamType from openpilot.common.conversions import Conversions as CV from panda import ALTERNATIVE_EXPERIENCE -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_short_branch from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from openpilot.selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index a23ba1eaf6..39cfda4e8a 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -4,7 +4,7 @@ import time import numpy as np from cereal import log from openpilot.common.numpy_fast import clip -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog # WARNING: imports outside of constants will not trigger a rebuild from openpilot.selfdrive.modeld.constants import index_function from openpilot.selfdrive.car.interfaces import ACCEL_MIN diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index b1eff5302f..13127b4634 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -15,7 +15,7 @@ from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N, get_speed_error -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog LON_MPC_STEP = 0.2 # first step is 0.2s A_CRUISE_MIN = -1.2 diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 15dbd4c5e2..46d10ef2f5 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -4,7 +4,7 @@ import numpy as np from cereal import car from openpilot.common.params import Params from openpilot.common.realtime import Priority, config_realtime_process -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index a2d58cfed2..f75b4b2059 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -9,7 +9,7 @@ from cereal import messaging, log, car from openpilot.common.numpy_fast import interp from openpilot.common.params import Params from openpilot.common.realtime import Ratekeeper, Priority, config_realtime_process -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.common.kalman.simple_kalman import KF1D diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 654c72528b..961efabf8d 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -18,7 +18,7 @@ from openpilot.common.conversions import Conversions as CV from openpilot.common.params import Params, put_nonblocking from openpilot.common.realtime import set_realtime_priority from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog MIN_SPEED_FILTER = 15 * CV.MPH_TO_MS MAX_VEL_ANGLE_STD = np.radians(0.25) diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index bde21f7879..d57f55d588 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Any from cereal import log from openpilot.common.params import Params -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog class NPQueue: diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index b87c83cac2..9230cb48f0 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -7,7 +7,7 @@ import numpy as np from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.selfdrive.locationd.models.constants import ObservationKind -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from rednose.helpers.kalmanfilter import KalmanFilter diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 0e779c9e3e..4a598e8d4b 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -12,7 +12,7 @@ from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.numpy_fast import clip from openpilot.selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index ff1fac2c22..d0a03f9b80 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -10,7 +10,7 @@ from cereal import car, log from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, cache_points_onexit diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 247f3c8fb8..836723e5a8 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -9,7 +9,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.spinner import Spinner from openpilot.common.text_window import TextWindow from openpilot.system.hardware import AGNOS -from openpilot.system.swaglog import cloudlog, add_file_handler +from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.version import is_dirty MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 7b23445c8b..37947a9a03 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -19,7 +19,7 @@ from openpilot.selfdrive.manager.helpers import unblock_stdout, write_onroad_par from openpilot.selfdrive.manager.process import ensure_running from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID -from openpilot.system.swaglog import cloudlog, add_file_handler +from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ get_normalized_origin, terms_version, training_version, \ is_tested_branch, is_release_branch diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index 82cc8d74a3..523e1fd209 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -15,7 +15,7 @@ import cereal.messaging as messaging import openpilot.selfdrive.sentry as sentry from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog WATCHDOG_FN = "/dev/shm/wd_" ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index aafe78abde..1e25964702 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -11,7 +11,7 @@ from typing import Tuple, Dict from cereal import messaging from cereal.messaging import PubMaster, SubMaster from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.realtime import set_realtime_priority from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 2b211b22e6..9cc238530a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -9,7 +9,7 @@ from typing import Dict, Optional from setproctitle import setproctitle from cereal.messaging import PubMaster, SubMaster from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.common.numpy_fast import interp diff --git a/selfdrive/modeld/navmodeld.py b/selfdrive/modeld/navmodeld.py index 90b9762800..ed0b597dfe 100755 --- a/selfdrive/modeld/navmodeld.py +++ b/selfdrive/modeld/navmodeld.py @@ -10,7 +10,7 @@ from typing import Tuple, Dict from cereal import messaging from cereal.messaging import PubMaster, SubMaster from cereal.visionipc import VisionIpcClient, VisionStreamType -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.realtime import set_realtime_priority from openpilot.selfdrive.modeld.constants import ModelConstants diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index 0d71efcc8d..fcaf46aff4 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -15,7 +15,7 @@ from openpilot.selfdrive.navd.helpers import (Coordinate, coordinate_from_param, distance_along_geometry, maxspeed_to_ms, minimum_distance, parse_banner_instructions) -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog REROUTE_DISTANCE = 25 MANEUVER_TRANSITION_THRESHOLD = 10 diff --git a/selfdrive/sentry.py b/selfdrive/sentry.py index b8bc8eff12..f4bb1fbce8 100644 --- a/selfdrive/sentry.py +++ b/selfdrive/sentry.py @@ -6,7 +6,7 @@ from sentry_sdk.integrations.threading import ThreadingIntegration from openpilot.common.params import Params from openpilot.selfdrive.athena.registration import is_registered_device from openpilot.system.hardware import HARDWARE, PC -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_branch, get_commit, get_origin, get_version, \ is_comma_remote, is_dirty, is_tested_branch diff --git a/selfdrive/statsd.py b/selfdrive/statsd.py index e41f41a621..94572b82c7 100755 --- a/selfdrive/statsd.py +++ b/selfdrive/statsd.py @@ -10,7 +10,7 @@ from typing import NoReturn, Union, List, Dict from openpilot.common.params import Params from cereal.messaging import SubMaster from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import HARDWARE from openpilot.common.file_helpers import atomic_write_in_dir from openpilot.system.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty diff --git a/selfdrive/thermald/fan_controller.py b/selfdrive/thermald/fan_controller.py index 64cd4c78ee..19c3292ce2 100755 --- a/selfdrive/thermald/fan_controller.py +++ b/selfdrive/thermald/fan_controller.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from openpilot.common.realtime import DT_TRML from openpilot.common.numpy_fast import interp -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.pid import PIDController class BaseFanController(ABC): diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 0b3c73a1c0..0520385473 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -4,7 +4,7 @@ from typing import Optional from openpilot.common.params import Params, put_nonblocking from openpilot.system.hardware import HARDWARE -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.statsd import statlog CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1)) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 690618d0b8..8b51aedbdb 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -23,7 +23,7 @@ from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.system.hardware import HARDWARE, TICI, AGNOS from openpilot.system.loggerd.config import get_available_percent from openpilot.selfdrive.statsd import statlog -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.thermald.power_monitoring import PowerMonitoring from openpilot.selfdrive.thermald.fan_controller import TiciFanController from openpilot.system.version import terms_version, training_version diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index 51e20dac49..5a81e93b28 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -11,7 +11,7 @@ from typing import NoReturn import openpilot.selfdrive.sentry as sentry from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_commit MAX_SIZE = 1_000_000 * 100 # allow up to 100M diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index a3dc6fedfe..d64fb0010c 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -13,7 +13,7 @@ from openpilot.system import micd from openpilot.common.realtime import Ratekeeper from openpilot.system.hardware import PC -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog SAMPLE_RATE = 48000 MAX_VOLUME = 1.0 diff --git a/selfdrive/updated.py b/selfdrive/updated.py index fabe28920f..db6a06dc9e 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -18,7 +18,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.time import system_time_valid from openpilot.system.hardware import AGNOS, HARDWARE -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.system.version import is_tested_branch diff --git a/system/loggerd/deleter.py b/system/loggerd/deleter.py index b060232b6e..868340150a 100755 --- a/system/loggerd/deleter.py +++ b/system/loggerd/deleter.py @@ -4,7 +4,7 @@ import shutil import threading from typing import List from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.loggerd.config import get_available_bytes, get_available_percent from openpilot.system.loggerd.uploader import listdir_by_creation from openpilot.system.loggerd.xattr_cache import getxattr diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index a7349fc5e4..538d99f66f 100755 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import List, Optional from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.loggerd.uploader import uploader_fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE from openpilot.system.loggerd.tests.loggerd_tests_common import UploaderTestCase diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index b5804b1624..4bc031a632 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -19,7 +19,7 @@ from openpilot.common.realtime import set_core_affinity from openpilot.system.hardware import TICI from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import getxattr, setxattr -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog NetworkType = log.DeviceState.NetworkType UPLOAD_ATTR_NAME = 'user.upload' diff --git a/system/logmessaged.py b/system/logmessaged.py index 5bd83f4b05..46bf79b0b2 100755 --- a/system/logmessaged.py +++ b/system/logmessaged.py @@ -5,7 +5,7 @@ from typing import NoReturn import cereal.messaging as messaging from openpilot.common.logging_extra import SwagLogFileFormatter from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import get_file_handler +from openpilot.common.swaglog import get_file_handler def main() -> NoReturn: diff --git a/system/micd.py b/system/micd.py index 452c04bc03..88fb08d684 100755 --- a/system/micd.py +++ b/system/micd.py @@ -3,7 +3,7 @@ import numpy as np from cereal import messaging from openpilot.common.realtime import Ratekeeper -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog RATE = 10 FFT_SAMPLES = 4096 diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 78539266c0..ae58c04a3b 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -17,7 +17,7 @@ from cereal import log import cereal.messaging as messaging from openpilot.common.gpio import gpio_init, gpio_set from openpilot.system.hardware.tici.pins import GPIO -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.qcomgpsd.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv from openpilot.system.qcomgpsd.structs import (dict_unpacker, position_report, relist, gps_measurement_report, gps_measurement_report_sv, diff --git a/system/sensord/pigeond.py b/system/sensord/pigeond.py index 6a02f627b9..78b3b07498 100755 --- a/system/sensord/pigeond.py +++ b/system/sensord/pigeond.py @@ -11,7 +11,7 @@ from typing import List, Optional, Tuple from cereal import messaging from openpilot.common.params import Params -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import TICI from openpilot.common.gpio import gpio_init, gpio_set from openpilot.system.hardware.tici.pins import GPIO diff --git a/system/tests/test_logmessaged.py b/system/tests/test_logmessaged.py index 5d0e1bfc82..d27dae46ad 100755 --- a/system/tests/test_logmessaged.py +++ b/system/tests/test_logmessaged.py @@ -7,7 +7,7 @@ import unittest import cereal.messaging as messaging from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog, ipchandler +from openpilot.common.swaglog import cloudlog, ipchandler class TestLogmessaged(unittest.TestCase): diff --git a/system/timezoned.py b/system/timezoned.py index 91424d33b5..546405c9a0 100755 --- a/system/timezoned.py +++ b/system/timezoned.py @@ -10,7 +10,7 @@ from timezonefinder import TimezoneFinder from openpilot.common.params import Params from openpilot.system.hardware import AGNOS -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_version REQUEST_HEADERS = {'User-Agent': "openpilot-" + get_version()} diff --git a/system/version.py b/system/version.py index 82e99fd416..f9fd2bc847 100755 --- a/system/version.py +++ b/system/version.py @@ -5,7 +5,7 @@ from typing import List, Optional from functools import lru_cache from openpilot.common.basedir import BASEDIR -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3', 'nightly'] TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] From 51fa7b227afbc1134bf4860eaee819cdc64174d2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 6 Dec 2023 19:55:27 -0800 Subject: [PATCH 146/724] qcomgpsd: move retry logic to a decorator (#30633) * qcomgpsd: move retry logic to a decorator * make that same * fix * fix that --- common/retry.py | 30 +++++++++++++++++++++++++ system/qcomgpsd/qcomgpsd.py | 45 +++++++++---------------------------- 2 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 common/retry.py diff --git a/common/retry.py b/common/retry.py new file mode 100644 index 0000000000..9bd4ac9522 --- /dev/null +++ b/common/retry.py @@ -0,0 +1,30 @@ +import time +import functools + +from openpilot.common.swaglog import cloudlog + + +def retry(attempts=3, delay=1.0, ignore_failure=False): + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + for _ in range(attempts): + try: + return func(*args, **kwargs) + except Exception: + cloudlog.exception(f"{func.__name__} failed, trying again") + time.sleep(delay) + + if ignore_failure: + cloudlog.error(f"{func.__name__} failed after retry") + else: + raise Exception(f"{func.__name__} failed after retry") + return wrapper + return decorator + + +if __name__ == "__main__": + @retry(attempts=10) + def abc(): + raise ValueError("abc failed :(") + abc() diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index ae58c04a3b..007c61552e 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -16,6 +16,7 @@ from struct import unpack_from, calcsize, pack from cereal import log import cereal.messaging as messaging from openpilot.common.gpio import gpio_init, gpio_set +from openpilot.common.retry import retry from openpilot.system.hardware.tici.pins import GPIO from openpilot.common.swaglog import cloudlog from openpilot.system.qcomgpsd.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv @@ -84,27 +85,13 @@ measurementStatusGlonassFields = { "glonassTimeMarkValid": 17 } +@retry(attempts=10, delay=1.0) +def try_setup_logs(diag, logs): + return setup_logs(diag, logs) -def try_setup_logs(diag, log_types): - for _ in range(10): - try: - setup_logs(diag, log_types) - break - except Exception: - cloudlog.exception("setup logs failed, trying again") - time.sleep(1.0) - else: - raise Exception(f"setup logs failed, {log_types=}") - +@retry(attempts=3, delay=1.0) def at_cmd(cmd: str) -> Optional[str]: - for _ in range(3): - try: - return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') - except subprocess.CalledProcessError: - cloudlog.exception("qcomgps.mmcli_command_failed") - time.sleep(1.0) - raise Exception(f"failed to execute mmcli command {cmd=}") - + return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') def gps_enabled() -> bool: try: @@ -154,23 +141,11 @@ def downloader_loop(event): except KeyboardInterrupt: pass +@retry(attempts=5, delay=0.2, ignore_failure=True) def inject_assistance(): - for _ in range(5): - try: - cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" - subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) - cloudlog.info("successfully loaded assistance data") - return - except subprocess.CalledProcessError as e: - cloudlog.event( - "qcomgps.assistance_loading_failed", - error=True, - cmd=e.cmd, - output=e.output, - returncode=e.returncode - ) - time.sleep(0.2) - cloudlog.error("failed to load assistance after retry") + cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" + subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) + cloudlog.info("successfully loaded assistance data") def setup_quectel(diag: ModemDiag) -> bool: ret = False From 2f2b9c782e2cef0cc972b39bcdc484fe0d8c9dfb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 6 Dec 2023 20:56:20 -0800 Subject: [PATCH 147/724] qcomgpsd: retry quectel setup (#30632) --- system/qcomgpsd/qcomgpsd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 007c61552e..0105d4074f 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -147,6 +147,7 @@ def inject_assistance(): subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) cloudlog.info("successfully loaded assistance data") +@retry(attempts=5, delay=1.0) def setup_quectel(diag: ModemDiag) -> bool: ret = False From 8bf78399e220a55855db7bd17872edb29cae7569 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 8 Dec 2023 02:21:14 +0800 Subject: [PATCH 148/724] sensor2_i2c.h: add `#pragma once` (#30636) --- system/camerad/cameras/sensor2_i2c.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/camerad/cameras/sensor2_i2c.h b/system/camerad/cameras/sensor2_i2c.h index 9170c5183a..6ea10a939b 100644 --- a/system/camerad/cameras/sensor2_i2c.h +++ b/system/camerad/cameras/sensor2_i2c.h @@ -1,3 +1,5 @@ +#pragma once + struct i2c_random_wr_payload start_reg_array_ar0231[] = {{0x301A, 0x91C}}; struct i2c_random_wr_payload stop_reg_array_ar0231[] = {{0x301A, 0x918}}; struct i2c_random_wr_payload start_reg_array_ox03c10[] = {{0x100, 1}}; From 7f07f47302229bcc2694f7c53d1e5ed1236494c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Thu, 7 Dec 2023 10:53:33 -0800 Subject: [PATCH 149/724] locationd: disable locationd temporary error alert on the body (#30608) * Disable locationd temporary error alert on the body * Disable locationd/paramsd/planner alerts on body with joystick mode * joystick_enabled param not joystick_mode --- selfdrive/controls/controlsd.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a180b4c410..41866efc3d 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -93,7 +93,8 @@ class Controls: else: self.CI, self.CP = CI, CI.CP - self.joystick_mode = self.params.get_bool("JoystickDebugMode") or self.CP.notCar + self.joystick_enabled = self.params.get_bool("JoystickDebugMode") + self.joystick_mode = self.joystick_enabled or self.CP.notCar # set alternative experiences from parameters self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") @@ -370,16 +371,17 @@ class Controls: else: self.logged_comm_issue = None - if not self.sm['lateralPlan'].mpcSolutionValid: - self.events.add(EventName.plannerError) - if not self.sm['liveLocationKalman'].posenetOK: - self.events.add(EventName.posenetInvalid) - if not self.sm['liveLocationKalman'].deviceStable: - self.events.add(EventName.deviceFalling) - if not self.sm['liveLocationKalman'].inputsOK: - self.events.add(EventName.locationdTemporaryError) - if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): - self.events.add(EventName.paramsdTemporaryError) + if not (self.CP.notCar and self.joystick_enabled): + if not self.sm['lateralPlan'].mpcSolutionValid: + self.events.add(EventName.plannerError) + if not self.sm['liveLocationKalman'].posenetOK: + self.events.add(EventName.posenetInvalid) + if not self.sm['liveLocationKalman'].deviceStable: + self.events.add(EventName.deviceFalling) + if not self.sm['liveLocationKalman'].inputsOK: + self.events.add(EventName.locationdTemporaryError) + if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): + self.events.add(EventName.paramsdTemporaryError) # conservative HW alert. if the data or frequency are off, locationd will throw an error if any((self.sm.frame - self.sm.rcv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets): From 1cd7b04e6ffb97018159c1fef930622bb0b77054 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 7 Dec 2023 13:37:18 -0600 Subject: [PATCH 150/724] Ford: add Explorer 2023 FW (#30637) * 49cead668ca2ac46 * docs --- docs/CARS.md | 2 +- selfdrive/car/ford/values.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index f0004c5aa0..5113eef50e 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -34,7 +34,7 @@ A supported vehicle is one that just works when you install a comma device. All |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|| |Ford|Bronco Sport 2021-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|

Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Explorer 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Explorer 2020-23|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Maverick 2022|LARIAT Luxury|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index ef8708f01d..3f4540da21 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -88,7 +88,7 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { FordCarInfo("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering"), ], CAR.EXPLORER_MK6: [ - FordCarInfo("Ford Explorer 2020-22"), + FordCarInfo("Ford Explorer 2020-23"), FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"), ], CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"), @@ -185,6 +185,7 @@ FW_VERSIONS = { b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'M1MC-14D003-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'P1MC-14D003-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x760, None): [ b'L1MC-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -212,6 +213,7 @@ FW_VERSIONS = { b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PB5A-14C204-DA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.F_150_MK14: { From ecfe201a57431e5dc1e4def58519f6493d489bab Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 8 Dec 2023 03:48:17 +0800 Subject: [PATCH 151/724] camerad: move AR0231 functions to camera_ar0231.cc (#30635) * move AR0231 functions to camera_ar0231.cc * move to sensors/aro231.cc --- release/files_common | 1 + system/camerad/SConscript | 3 +- system/camerad/cameras/camera_common.h | 3 + system/camerad/cameras/camera_qcom2.cc | 90 ----------------------- system/camerad/cameras/camera_qcom2.h | 3 +- system/camerad/sensors/ar0231.cc | 98 ++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 93 deletions(-) create mode 100644 system/camerad/sensors/ar0231.cc diff --git a/release/files_common b/release/files_common index 7257fb820d..f8b424683a 100644 --- a/release/files_common +++ b/release/files_common @@ -319,6 +319,7 @@ system/camerad/snapshot/* system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.cc system/camerad/cameras/sensor2_i2c.h +system/camerad/sensors/ar0231.cc selfdrive/manager/__init__.py selfdrive/manager/build.py diff --git a/system/camerad/SConscript b/system/camerad/SConscript index 83f93344d8..d814ebf1b0 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -2,7 +2,8 @@ Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon, 'atomic'] -camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc']) +camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc', + 'sensors/ar0231.cc']) env.Program('camerad', ['main.cc', camera_obj], LIBS=libs) if GetOption("extras") and arch == "x86_64": diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 23bfc8b7d5..9d9e42a5ed 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -103,3 +103,6 @@ void cameras_close(MultiCameraState *s); void camerad_thread(); int open_v4l_by_name_and_index(const char name[], int index = 0, int flags = O_RDWR | O_NONBLOCK); + +void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed); + diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 8d20ed27c8..13fd60f4d2 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -907,70 +907,6 @@ void cameras_close(MultiCameraState *s) { delete s->pm; } -std::map> CameraState::ar0231_build_register_lut(uint8_t *data) { - // This function builds a lookup table from register address, to a pair of indices in the - // buffer where to read this address. The buffer contains padding bytes, - // as well as markers to indicate the type of the next byte. - // - // 0xAA is used to indicate the MSB of the address, 0xA5 for the LSB of the address. - // Every byte of data (MSB and LSB) is preceded by 0x5A. Specifying an address is optional - // for contiguous ranges. See page 27-29 of the AR0231 Developer guide for more information. - - int max_i[] = {1828 / 2 * 3, 1500 / 2 * 3}; - auto get_next_idx = [](int cur_idx) { - return (cur_idx % 3 == 1) ? cur_idx + 2 : cur_idx + 1; // Every third byte is padding - }; - - std::map> registers; - for (int register_row = 0; register_row < 2; register_row++) { - uint8_t *registers_raw = data + ci.frame_stride * register_row; - assert(registers_raw[0] == 0x0a); // Start of line - - int value_tag_count = 0; - int first_val_idx = 0; - uint16_t cur_addr = 0; - - for (int i = 1; i <= max_i[register_row]; i = get_next_idx(get_next_idx(i))) { - int val_idx = get_next_idx(i); - - uint8_t tag = registers_raw[i]; - uint16_t val = registers_raw[val_idx]; - - if (tag == 0xAA) { // Register MSB tag - cur_addr = val << 8; - } else if (tag == 0xA5) { // Register LSB tag - cur_addr |= val; - cur_addr -= 2; // Next value tag will increment address again - } else if (tag == 0x5A) { // Value tag - - // First tag - if (value_tag_count % 2 == 0) { - cur_addr += 2; - first_val_idx = val_idx; - } else { - registers[cur_addr] = std::make_pair(first_val_idx + ci.frame_stride * register_row, val_idx + ci.frame_stride * register_row); - } - - value_tag_count++; - } - } - } - return registers; -} - -std::map CameraState::ar0231_parse_registers(uint8_t *data, std::initializer_list addrs) { - if (ar0231_register_lut.empty()) { - ar0231_register_lut = ar0231_build_register_lut(data); - } - - std::map registers; - for (uint16_t addr : addrs) { - auto offset = ar0231_register_lut[addr]; - registers[addr] = ((uint16_t)data[offset.first] << 8) | data[offset.second]; - } - return registers; -} - void CameraState::handle_camera_event(void *evdat) { if (!enabled) return; struct cam_req_mgr_message *event_data = (struct cam_req_mgr_message *)evdat; @@ -1180,32 +1116,6 @@ void CameraState::set_camera_exposure(float grey_frac) { } } -static float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_reg) { - // See AR0231 Developer Guide - page 36 - float slope = (125.0 - 55.0) / ((float)calib1 - (float)calib2); - float t0 = 55.0 - slope * (float)calib2; - return t0 + slope * (float)data_reg; -} - -static void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed){ - const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; - uint8_t *data = (uint8_t*)c->buf.cur_camera_buf->addr + c->ci.registers_offset; - - if (memcmp(data, expected_preamble, std::size(expected_preamble)) != 0){ - LOGE("unexpected register data found"); - return; - } - - auto registers = c->ar0231_parse_registers(data, {0x2000, 0x2002, 0x20b0, 0x20b2, 0x30c6, 0x30c8, 0x30ca, 0x30cc}); - - uint32_t frame_id = ((uint32_t)registers[0x2000] << 16) | registers[0x2002]; - framed.setFrameIdSensor(frame_id); - - float temp_0 = ar0231_parse_temp_sensor(registers[0x30c6], registers[0x30c8], registers[0x20b0]); - float temp_1 = ar0231_parse_temp_sensor(registers[0x30ca], registers[0x30cc], registers[0x20b2]); - framed.setTemperaturesC({temp_0, temp_1}); -} - static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { c->set_camera_exposure(set_exposure_target(&c->buf, 96, 1832, 2, 242, 1148, 4)); diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 1e7cbfc104..87cdd091f6 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -94,7 +94,6 @@ public: CameraBuf buf; MemoryManager mm; -private: void config_isp(int io_mem_handle, int fence, int request_id, int buf0_mem_handle, int buf0_offset); void enqueue_req_multi(int start, int n, bool dp); void enqueue_buffer(int i, bool dp); @@ -106,8 +105,8 @@ private: // Register parsing std::map> ar0231_register_lut; - std::map> ar0231_build_register_lut(uint8_t *data); +private: // for debugging Params params; }; diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc new file mode 100644 index 0000000000..7d7a454f7e --- /dev/null +++ b/system/camerad/sensors/ar0231.cc @@ -0,0 +1,98 @@ +#include + +#include "system/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_qcom2.h" + +namespace { + +std::map> ar0231_build_register_lut(CameraState *c, uint8_t *data) { + // This function builds a lookup table from register address, to a pair of indices in the + // buffer where to read this address. The buffer contains padding bytes, + // as well as markers to indicate the type of the next byte. + // + // 0xAA is used to indicate the MSB of the address, 0xA5 for the LSB of the address. + // Every byte of data (MSB and LSB) is preceded by 0x5A. Specifying an address is optional + // for contiguous ranges. See page 27-29 of the AR0231 Developer guide for more information. + + int max_i[] = {1828 / 2 * 3, 1500 / 2 * 3}; + auto get_next_idx = [](int cur_idx) { + return (cur_idx % 3 == 1) ? cur_idx + 2 : cur_idx + 1; // Every third byte is padding + }; + + std::map> registers; + for (int register_row = 0; register_row < 2; register_row++) { + uint8_t *registers_raw = data + c->ci.frame_stride * register_row; + assert(registers_raw[0] == 0x0a); // Start of line + + int value_tag_count = 0; + int first_val_idx = 0; + uint16_t cur_addr = 0; + + for (int i = 1; i <= max_i[register_row]; i = get_next_idx(get_next_idx(i))) { + int val_idx = get_next_idx(i); + + uint8_t tag = registers_raw[i]; + uint16_t val = registers_raw[val_idx]; + + if (tag == 0xAA) { // Register MSB tag + cur_addr = val << 8; + } else if (tag == 0xA5) { // Register LSB tag + cur_addr |= val; + cur_addr -= 2; // Next value tag will increment address again + } else if (tag == 0x5A) { // Value tag + + // First tag + if (value_tag_count % 2 == 0) { + cur_addr += 2; + first_val_idx = val_idx; + } else { + registers[cur_addr] = std::make_pair(first_val_idx + c->ci.frame_stride * register_row, val_idx + c->ci.frame_stride * register_row); + } + + value_tag_count++; + } + } + } + return registers; +} + +std::map ar0231_parse_registers(CameraState *c, uint8_t *data, std::initializer_list addrs) { + if (c->ar0231_register_lut.empty()) { + c->ar0231_register_lut = ar0231_build_register_lut(c, data); + } + + std::map registers; + for (uint16_t addr : addrs) { + auto offset = c->ar0231_register_lut[addr]; + registers[addr] = ((uint16_t)data[offset.first] << 8) | data[offset.second]; + } + return registers; +} + +float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_reg) { + // See AR0231 Developer Guide - page 36 + float slope = (125.0 - 55.0) / ((float)calib1 - (float)calib2); + float t0 = 55.0 - slope * (float)calib2; + return t0 + slope * (float)data_reg; +} + +} // namespace + +void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) { + const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; + uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci.registers_offset; + + if (memcmp(data, expected_preamble, std::size(expected_preamble)) != 0) { + LOGE("unexpected register data found"); + return; + } + + auto registers = ar0231_parse_registers(c, data, {0x2000, 0x2002, 0x20b0, 0x20b2, 0x30c6, 0x30c8, 0x30ca, 0x30cc}); + + uint32_t frame_id = ((uint32_t)registers[0x2000] << 16) | registers[0x2002]; + framed.setFrameIdSensor(frame_id); + + float temp_0 = ar0231_parse_temp_sensor(registers[0x30c6], registers[0x30c8], registers[0x20b0]); + float temp_1 = ar0231_parse_temp_sensor(registers[0x30ca], registers[0x30cc], registers[0x20b2]); + framed.setTemperaturesC({temp_0, temp_1}); +} From bdf868ddc276e2cf8a644376afe1a9634bdf06fe Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 8 Dec 2023 07:45:49 +0800 Subject: [PATCH 152/724] camerad: split sensor_ic2.h (#30641) * split sensor_ic2.h * move start_reg,stop_reg --- release/files_common | 3 +- system/camerad/cameras/camera_qcom2.cc | 3 +- system/camerad/sensors/ar0231_registers.h | 119 ++++++++++++++++++ .../ox03c10_registers.h} | 117 ----------------- 4 files changed, 123 insertions(+), 119 deletions(-) create mode 100644 system/camerad/sensors/ar0231_registers.h rename system/camerad/{cameras/sensor2_i2c.h => sensors/ox03c10_registers.h} (83%) diff --git a/release/files_common b/release/files_common index f8b424683a..d73c71171a 100644 --- a/release/files_common +++ b/release/files_common @@ -318,8 +318,9 @@ system/camerad/main.cc system/camerad/snapshot/* system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.cc -system/camerad/cameras/sensor2_i2c.h system/camerad/sensors/ar0231.cc +system/camerad/sensors/ar0231_registers.h +system/camerad/sensors/ox03c10_registers.h selfdrive/manager/__init__.py selfdrive/manager/build.py diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 13fd60f4d2..e6ca0fff94 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -23,7 +23,8 @@ #include "media/cam_sensor_cmn_header.h" #include "media/cam_sync.h" #include "common/swaglog.h" -#include "system/camerad/cameras/sensor2_i2c.h" +#include "system/camerad/sensors/ar0231_registers.h" +#include "system/camerad/sensors/ox03c10_registers.h" // For debugging: // echo "4294967295" > /sys/module/cam_debug_util/parameters/debug_mdl diff --git a/system/camerad/sensors/ar0231_registers.h b/system/camerad/sensors/ar0231_registers.h new file mode 100644 index 0000000000..611d08ec8e --- /dev/null +++ b/system/camerad/sensors/ar0231_registers.h @@ -0,0 +1,119 @@ +#pragma once + +struct i2c_random_wr_payload start_reg_array_ar0231[] = {{0x301A, 0x91C}}; +struct i2c_random_wr_payload stop_reg_array_ar0231[] = {{0x301A, 0x918}}; + +struct i2c_random_wr_payload init_array_ar0231[] = { + {0x301A, 0x0018}, // RESET_REGISTER + + // CLOCK Settings + // input clock is 19.2 / 2 * 0x37 = 528 MHz + // pixclk is 528 / 6 = 88 MHz + // full roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*FRAME_LENGTH_LINES)) = 39.99 ms + // img roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*Y_OUTPUT_CONTROL)) = 22.85 ms + {0x302A, 0x0006}, // VT_PIX_CLK_DIV + {0x302C, 0x0001}, // VT_SYS_CLK_DIV + {0x302E, 0x0002}, // PRE_PLL_CLK_DIV + {0x3030, 0x0037}, // PLL_MULTIPLIER + {0x3036, 0x000C}, // OP_PIX_CLK_DIV + {0x3038, 0x0001}, // OP_SYS_CLK_DIV + + // FORMAT + {0x3040, 0xC000}, // READ_MODE + {0x3004, 0x0000}, // X_ADDR_START_ + {0x3008, 0x0787}, // X_ADDR_END_ + {0x3002, 0x0000}, // Y_ADDR_START_ + {0x3006, 0x04B7}, // Y_ADDR_END_ + {0x3032, 0x0000}, // SCALING_MODE + {0x30A2, 0x0001}, // X_ODD_INC_ + {0x30A6, 0x0001}, // Y_ODD_INC_ + {0x3402, 0x0788}, // X_OUTPUT_CONTROL + {0x3404, 0x04B8}, // Y_OUTPUT_CONTROL + {0x3064, 0x1982}, // SMIA_TEST + {0x30BA, 0x11F2}, // DIGITAL_CTRL + + // Enable external trigger and disable GPIO outputs + {0x30CE, 0x0120}, // SLAVE_SH_SYNC_MODE | FRAME_START_MODE + {0x340A, 0xE0}, // GPIO3_INPUT_DISABLE | GPIO2_INPUT_DISABLE | GPIO1_INPUT_DISABLE + {0x340C, 0x802}, // GPIO_HIDRV_EN | GPIO0_ISEL=2 + + // Readout timing + {0x300C, 0x0672}, // LINE_LENGTH_PCK (valid for 3-exposure HDR) + {0x300A, 0x0855}, // FRAME_LENGTH_LINES + {0x3042, 0x0000}, // EXTRA_DELAY + + // Readout Settings + {0x31AE, 0x0204}, // SERIAL_FORMAT, 4-lane MIPI + {0x31AC, 0x0C0C}, // DATA_FORMAT_BITS, 12 -> 12 + {0x3342, 0x1212}, // MIPI_F1_PDT_EDT + {0x3346, 0x1212}, // MIPI_F2_PDT_EDT + {0x334A, 0x1212}, // MIPI_F3_PDT_EDT + {0x334E, 0x1212}, // MIPI_F4_PDT_EDT + {0x3344, 0x0011}, // MIPI_F1_VDT_VC + {0x3348, 0x0111}, // MIPI_F2_VDT_VC + {0x334C, 0x0211}, // MIPI_F3_VDT_VC + {0x3350, 0x0311}, // MIPI_F4_VDT_VC + {0x31B0, 0x0053}, // FRAME_PREAMBLE + {0x31B2, 0x003B}, // LINE_PREAMBLE + {0x301A, 0x001C}, // RESET_REGISTER + + // Noise Corrections + {0x3092, 0x0C24}, // ROW_NOISE_CONTROL + {0x337A, 0x0C80}, // DBLC_SCALE0 + {0x3370, 0x03B1}, // DBLC + {0x3044, 0x0400}, // DARK_CONTROL + + // Enable temperature sensor + {0x30B4, 0x0007}, // TEMPSENS0_CTRL_REG + {0x30B8, 0x0007}, // TEMPSENS1_CTRL_REG + + // Enable dead pixel correction using + // the 1D line correction scheme + {0x31E0, 0x0003}, + + // HDR Settings + {0x3082, 0x0004}, // OPERATION_MODE_CTRL + {0x3238, 0x0444}, // EXPOSURE_RATIO + + {0x1008, 0x0361}, // FINE_INTEGRATION_TIME_MIN + {0x100C, 0x0589}, // FINE_INTEGRATION_TIME2_MIN + {0x100E, 0x07B1}, // FINE_INTEGRATION_TIME3_MIN + {0x1010, 0x0139}, // FINE_INTEGRATION_TIME4_MIN + + // TODO: do these have to be lower than LINE_LENGTH_PCK? + {0x3014, 0x08CB}, // FINE_INTEGRATION_TIME_ + {0x321E, 0x0894}, // FINE_INTEGRATION_TIME2 + + {0x31D0, 0x0000}, // COMPANDING, no good in 10 bit? + {0x33DA, 0x0000}, // COMPANDING + {0x318E, 0x0200}, // PRE_HDR_GAIN_EN + + // DLO Settings + {0x3100, 0x4000}, // DLO_CONTROL0 + {0x3280, 0x0CCC}, // T1 G1 + {0x3282, 0x0CCC}, // T1 R + {0x3284, 0x0CCC}, // T1 B + {0x3286, 0x0CCC}, // T1 G2 + {0x3288, 0x0FA0}, // T2 G1 + {0x328A, 0x0FA0}, // T2 R + {0x328C, 0x0FA0}, // T2 B + {0x328E, 0x0FA0}, // T2 G2 + + // Initial Gains + {0x3022, 0x0001}, // GROUPED_PARAMETER_HOLD_ + {0x3366, 0xFF77}, // ANALOG_GAIN (1x) + + {0x3060, 0x3333}, // ANALOG_COLOR_GAIN + + {0x3362, 0x0000}, // DC GAIN + + {0x305A, 0x00F8}, // red gain + {0x3058, 0x0122}, // blue gain + {0x3056, 0x009A}, // g1 gain + {0x305C, 0x009A}, // g2 gain + + {0x3022, 0x0000}, // GROUPED_PARAMETER_HOLD_ + + // Initial Integration Time + {0x3012, 0x0005}, +}; diff --git a/system/camerad/cameras/sensor2_i2c.h b/system/camerad/sensors/ox03c10_registers.h similarity index 83% rename from system/camerad/cameras/sensor2_i2c.h rename to system/camerad/sensors/ox03c10_registers.h index 6ea10a939b..7aed2e8ac0 100644 --- a/system/camerad/cameras/sensor2_i2c.h +++ b/system/camerad/sensors/ox03c10_registers.h @@ -1,7 +1,5 @@ #pragma once -struct i2c_random_wr_payload start_reg_array_ar0231[] = {{0x301A, 0x91C}}; -struct i2c_random_wr_payload stop_reg_array_ar0231[] = {{0x301A, 0x918}}; struct i2c_random_wr_payload start_reg_array_ox03c10[] = {{0x100, 1}}; struct i2c_random_wr_payload stop_reg_array_ox03c10[] = {{0x100, 0}}; @@ -761,118 +759,3 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x3548, 0x0F}, {0x3549, 0x00}, {0x35c1, 0x00}, }; - -struct i2c_random_wr_payload init_array_ar0231[] = { - {0x301A, 0x0018}, // RESET_REGISTER - - // CLOCK Settings - // input clock is 19.2 / 2 * 0x37 = 528 MHz - // pixclk is 528 / 6 = 88 MHz - // full roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*FRAME_LENGTH_LINES)) = 39.99 ms - // img roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*Y_OUTPUT_CONTROL)) = 22.85 ms - {0x302A, 0x0006}, // VT_PIX_CLK_DIV - {0x302C, 0x0001}, // VT_SYS_CLK_DIV - {0x302E, 0x0002}, // PRE_PLL_CLK_DIV - {0x3030, 0x0037}, // PLL_MULTIPLIER - {0x3036, 0x000C}, // OP_PIX_CLK_DIV - {0x3038, 0x0001}, // OP_SYS_CLK_DIV - - // FORMAT - {0x3040, 0xC000}, // READ_MODE - {0x3004, 0x0000}, // X_ADDR_START_ - {0x3008, 0x0787}, // X_ADDR_END_ - {0x3002, 0x0000}, // Y_ADDR_START_ - {0x3006, 0x04B7}, // Y_ADDR_END_ - {0x3032, 0x0000}, // SCALING_MODE - {0x30A2, 0x0001}, // X_ODD_INC_ - {0x30A6, 0x0001}, // Y_ODD_INC_ - {0x3402, 0x0788}, // X_OUTPUT_CONTROL - {0x3404, 0x04B8}, // Y_OUTPUT_CONTROL - {0x3064, 0x1982}, // SMIA_TEST - {0x30BA, 0x11F2}, // DIGITAL_CTRL - - // Enable external trigger and disable GPIO outputs - {0x30CE, 0x0120}, // SLAVE_SH_SYNC_MODE | FRAME_START_MODE - {0x340A, 0xE0}, // GPIO3_INPUT_DISABLE | GPIO2_INPUT_DISABLE | GPIO1_INPUT_DISABLE - {0x340C, 0x802}, // GPIO_HIDRV_EN | GPIO0_ISEL=2 - - // Readout timing - {0x300C, 0x0672}, // LINE_LENGTH_PCK (valid for 3-exposure HDR) - {0x300A, 0x0855}, // FRAME_LENGTH_LINES - {0x3042, 0x0000}, // EXTRA_DELAY - - // Readout Settings - {0x31AE, 0x0204}, // SERIAL_FORMAT, 4-lane MIPI - {0x31AC, 0x0C0C}, // DATA_FORMAT_BITS, 12 -> 12 - {0x3342, 0x1212}, // MIPI_F1_PDT_EDT - {0x3346, 0x1212}, // MIPI_F2_PDT_EDT - {0x334A, 0x1212}, // MIPI_F3_PDT_EDT - {0x334E, 0x1212}, // MIPI_F4_PDT_EDT - {0x3344, 0x0011}, // MIPI_F1_VDT_VC - {0x3348, 0x0111}, // MIPI_F2_VDT_VC - {0x334C, 0x0211}, // MIPI_F3_VDT_VC - {0x3350, 0x0311}, // MIPI_F4_VDT_VC - {0x31B0, 0x0053}, // FRAME_PREAMBLE - {0x31B2, 0x003B}, // LINE_PREAMBLE - {0x301A, 0x001C}, // RESET_REGISTER - - // Noise Corrections - {0x3092, 0x0C24}, // ROW_NOISE_CONTROL - {0x337A, 0x0C80}, // DBLC_SCALE0 - {0x3370, 0x03B1}, // DBLC - {0x3044, 0x0400}, // DARK_CONTROL - - // Enable temperature sensor - {0x30B4, 0x0007}, // TEMPSENS0_CTRL_REG - {0x30B8, 0x0007}, // TEMPSENS1_CTRL_REG - - // Enable dead pixel correction using - // the 1D line correction scheme - {0x31E0, 0x0003}, - - // HDR Settings - {0x3082, 0x0004}, // OPERATION_MODE_CTRL - {0x3238, 0x0444}, // EXPOSURE_RATIO - - {0x1008, 0x0361}, // FINE_INTEGRATION_TIME_MIN - {0x100C, 0x0589}, // FINE_INTEGRATION_TIME2_MIN - {0x100E, 0x07B1}, // FINE_INTEGRATION_TIME3_MIN - {0x1010, 0x0139}, // FINE_INTEGRATION_TIME4_MIN - - // TODO: do these have to be lower than LINE_LENGTH_PCK? - {0x3014, 0x08CB}, // FINE_INTEGRATION_TIME_ - {0x321E, 0x0894}, // FINE_INTEGRATION_TIME2 - - {0x31D0, 0x0000}, // COMPANDING, no good in 10 bit? - {0x33DA, 0x0000}, // COMPANDING - {0x318E, 0x0200}, // PRE_HDR_GAIN_EN - - // DLO Settings - {0x3100, 0x4000}, // DLO_CONTROL0 - {0x3280, 0x0CCC}, // T1 G1 - {0x3282, 0x0CCC}, // T1 R - {0x3284, 0x0CCC}, // T1 B - {0x3286, 0x0CCC}, // T1 G2 - {0x3288, 0x0FA0}, // T2 G1 - {0x328A, 0x0FA0}, // T2 R - {0x328C, 0x0FA0}, // T2 B - {0x328E, 0x0FA0}, // T2 G2 - - // Initial Gains - {0x3022, 0x0001}, // GROUPED_PARAMETER_HOLD_ - {0x3366, 0xFF77}, // ANALOG_GAIN (1x) - - {0x3060, 0x3333}, // ANALOG_COLOR_GAIN - - {0x3362, 0x0000}, // DC GAIN - - {0x305A, 0x00F8}, // red gain - {0x3058, 0x0122}, // blue gain - {0x3056, 0x009A}, // g1 gain - {0x305C, 0x009A}, // g2 gain - - {0x3022, 0x0000}, // GROUPED_PARAMETER_HOLD_ - - // Initial Integration Time - {0x3012, 0x0005}, -}; From a70911d6396a41fb006043b41e89281fa7a5d8b1 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 8 Dec 2023 08:06:55 +0800 Subject: [PATCH 153/724] camerad: refactor sensor parameters to struct (#30639) * refactor camerainfo * include --- system/camerad/cameras/camera_common.cc | 6 +- system/camerad/cameras/camera_common.h | 22 +++- system/camerad/cameras/camera_qcom2.cc | 135 ++++++++++++------------ system/camerad/cameras/camera_qcom2.h | 23 +--- system/camerad/sensors/ar0231.cc | 6 +- 5 files changed, 98 insertions(+), 94 deletions(-) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 987ccf23da..703378b104 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -28,7 +28,7 @@ class Debayer { public: Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { char args[4096]; - const CameraInfo *ci = &s->ci; + const CameraInfo *ci = s->ci.get(); snprintf(args, sizeof(args), "-cl-fast-relaxed-math -cl-denorms-are-zero " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " @@ -66,7 +66,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, this->yuv_type = init_yuv_type; frame_buf_count = frame_cnt; - const CameraInfo *ci = &s->ci; + const CameraInfo *ci = s->ci.get(); // RAW frame const int frame_size = (ci->frame_height + ci->extra_height) * ci->frame_stride; camera_bufs = std::make_unique(frame_buf_count); @@ -152,7 +152,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setProcessingTime(frame_data.processing_time); const float ev = c->cur_ev[frame_data.frame_id % 3]; - const float perc = util::map_val(ev, c->min_ev, c->max_ev, 0.0f, 100.0f); + const float perc = util::map_val(ev, c->ci->min_ev, c->ci->max_ev, 0.0f, 100.0f); framed.setExposureValPercent(perc); if (c->camera_id == CAMERA_ID_AR0231) { diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 9d9e42a5ed..8022fa844e 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -16,8 +16,8 @@ #define CAMERA_ID_AR0231 0 #define CAMERA_ID_OX03C10 1 -#define CAMERA_ID_MAX 2 +#define ANALOG_GAIN_MAX_CNT 55 const int YUV_BUFFER_COUNT = 20; enum CameraType { @@ -41,6 +41,26 @@ typedef struct CameraInfo { uint32_t extra_height = 0; int registers_offset = -1; int stats_offset = -1; + + int exposure_time_min; + int exposure_time_max; + + float dc_gain_factor; + int dc_gain_min_weight; + int dc_gain_max_weight; + float dc_gain_on_grey; + float dc_gain_off_grey; + + float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; + int analog_gain_min_idx; + int analog_gain_max_idx; + int analog_gain_rec_idx; + int analog_gain_cost_delta; + float analog_gain_cost_low; + float analog_gain_cost_high; + float target_grey_factor; + float min_ev; + float max_ev; } CameraInfo; typedef struct FrameMetadata { diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index e6ca0fff94..5aa95bd63d 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -40,27 +40,6 @@ const size_t AR0231_REGISTERS_HEIGHT = 2; const size_t AR0231_STATS_HEIGHT = 2+8; const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py - -CameraInfo cameras_supported[CAMERA_ID_MAX] = { - [CAMERA_ID_AR0231] = { - .frame_width = FRAME_WIDTH, - .frame_height = FRAME_HEIGHT, - .frame_stride = FRAME_STRIDE, - .extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT, - - .registers_offset = 0, - .frame_offset = AR0231_REGISTERS_HEIGHT, - .stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, - }, - [CAMERA_ID_OX03C10] = { - .frame_width = FRAME_WIDTH, - .frame_height = FRAME_HEIGHT, - .frame_stride = FRAME_STRIDE, // (0xa80*12//8) - .extra_height = 16, // top 2 + bot 14 - .frame_offset = 2, - }, -}; - const float DC_GAIN_AR0231 = 2.5; const float DC_GAIN_OX03C10 = 7.32; @@ -424,10 +403,10 @@ void CameraState::config_isp(int io_mem_handle, int fence, int request_id, int b if (io_mem_handle != 0) { io_cfg[0].mem_handle[0] = io_mem_handle; io_cfg[0].planes[0] = (struct cam_plane_cfg){ - .width = ci.frame_width, - .height = ci.frame_height + ci.extra_height, - .plane_stride = ci.frame_stride, - .slice_height = ci.frame_height + ci.extra_height, + .width = ci->frame_width, + .height = ci->frame_height + ci->extra_height, + .plane_stride = ci->frame_stride, + .slice_height = ci->frame_height + ci->extra_height, .meta_stride = 0x0, // YUV has meta(stride=0x400, size=0x5000) .meta_size = 0x0, .meta_offset = 0x0, @@ -517,8 +496,17 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* -void CameraState::camera_set_parameters() { - if (camera_id == CAMERA_ID_AR0231) { +struct CameraAR0231 : public CameraInfo { + CameraAR0231() { + frame_width = FRAME_WIDTH, + frame_height = FRAME_HEIGHT, + frame_stride = FRAME_STRIDE, + extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT, + + registers_offset = 0, + frame_offset = AR0231_REGISTERS_HEIGHT, + stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, + dc_gain_factor = DC_GAIN_AR0231; dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_AR0231; dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231; @@ -532,12 +520,23 @@ void CameraState::camera_set_parameters() { analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; - for (int i=0; i<=analog_gain_max_idx; i++) { + for (int i = 0; i <= analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; } min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_factor = TARGET_GREY_FACTOR_AR0231; - } else if (camera_id == CAMERA_ID_OX03C10) { + } +}; + +struct CameraOx0310 : public CameraInfo { + CameraOx0310() { + frame_width = FRAME_WIDTH, + frame_height = FRAME_HEIGHT, + frame_stride = FRAME_STRIDE, // (0xa80*12//8) + extra_height = 16, // top 2 + bot 14 + frame_offset = 2, + dc_gain_factor = DC_GAIN_OX03C10; dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_OX03C10; dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10; @@ -555,19 +554,27 @@ void CameraState::camera_set_parameters() { sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; } min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_factor = TARGET_GREY_FACTOR_OX03C10; + } +}; + +void CameraState::camera_set_parameters() { + if (camera_id == CAMERA_ID_AR0231) { + ci = std::make_unique(); + } else if (camera_id == CAMERA_ID_OX03C10) { + ci = std::make_unique(); } else { assert(false); } - max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_fraction = 0.3; dc_gain_enabled = false; - dc_gain_weight = dc_gain_min_weight; - gain_idx = analog_gain_rec_idx; + dc_gain_weight = ci->dc_gain_min_weight; + gain_idx = ci->analog_gain_rec_idx; exposure_time = 5; - cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight) * sensor_analog_gains[gain_idx] * exposure_time; + cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight) * ci->sensor_analog_gains[gain_idx] * exposure_time; } void CameraState::camera_map_bufs(MultiCameraState *s) { @@ -590,10 +597,6 @@ void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, int came camera_id = camera_id_; LOGD("camera init %d", camera_num); - assert(camera_id < std::size(cameras_supported)); - ci = cameras_supported[camera_id]; - assert(ci.frame_width != 0); - request_id_last = 0; skipped = true; @@ -680,16 +683,16 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .usage_type = 0x0, .left_start = 0, - .left_stop = ci.frame_width - 1, - .left_width = ci.frame_width, + .left_stop = ci->frame_width - 1, + .left_width = ci->frame_width, .right_start = 0, - .right_stop = ci.frame_width - 1, - .right_width = ci.frame_width, + .right_stop = ci->frame_width - 1, + .right_width = ci->frame_width, .line_start = 0, - .line_stop = ci.frame_height + ci.extra_height - 1, - .height = ci.frame_height + ci.extra_height, + .line_stop = ci->frame_height + ci->extra_height - 1, + .height = ci->frame_height + ci->extra_height, .pixel_clk = 0x0, .batch_size = 0x0, @@ -701,8 +704,8 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .data[0] = (struct cam_isp_out_port_info){ .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, .format = CAM_FORMAT_MIPI_RAW_12, - .width = ci.frame_width, - .height = ci.frame_height + ci.extra_height, + .width = ci->frame_width, + .height = ci->frame_height + ci->extra_height, .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, }, }; @@ -946,7 +949,7 @@ void CameraState::handle_camera_event(void *evdat) { meta_data.frame_id = main_id - idx_offset; meta_data.timestamp_sof = timestamp; exp_lock.lock(); - meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); meta_data.high_conversion_gain = dc_gain_enabled; meta_data.integ_lines = exposure_time; meta_data.measured_grey_fraction = measured_grey_fraction; @@ -972,15 +975,15 @@ void CameraState::update_exposure_score(float desired_ev, int exp_t, int exp_g_i // Cost of ev diff score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; // Cost of absolute gain - float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + float m = exp_g_idx > ci->analog_gain_rec_idx ? ci->analog_gain_cost_high : ci->analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)ci->analog_gain_rec_idx) * m; // Cost of changing gain score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; } else if (camera_id == CAMERA_ID_OX03C10) { score = std::abs(desired_ev - (exp_t * exp_gain)); - float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; - score += ((1 - analog_gain_cost_delta) + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; + float m = exp_g_idx > ci->analog_gain_rec_idx ? ci->analog_gain_cost_high : ci->analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)ci->analog_gain_rec_idx) * m; + score += ((1 - ci->analog_gain_cost_delta) + ci->analog_gain_cost_delta * (exp_g_idx - ci->analog_gain_min_idx) / (ci->analog_gain_max_idx - ci->analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; } if (score < best_ev_score) { @@ -1008,10 +1011,10 @@ void CameraState::set_camera_exposure(float grey_frac) { const float cur_ev_ = cur_ev[buf.cur_frame_data.frame_id % 3]; // Scale target grey between 0.1 and 0.4 depending on lighting conditions - float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4); + float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + ci->target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4); float target_grey = (1.0 - k_grey) * target_grey_fraction + k_grey * new_target_grey; - float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, min_ev, max_ev); + float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, ci->min_ev, ci->max_ev); float k = (1.0 - k_ev) / 3.0; desired_ev = (k * cur_ev[0]) + (k * cur_ev[1]) + (k * cur_ev[2]) + (k_ev * desired_ev); @@ -1022,16 +1025,16 @@ void CameraState::set_camera_exposure(float grey_frac) { // Hysteresis around high conversion gain // We usually want this on since it results in lower noise, but turn off in very bright day scenes bool enable_dc_gain = dc_gain_enabled; - if (!enable_dc_gain && target_grey < dc_gain_on_grey) { + if (!enable_dc_gain && target_grey < ci->dc_gain_on_grey) { enable_dc_gain = true; - dc_gain_weight = dc_gain_min_weight; - } else if (enable_dc_gain && target_grey > dc_gain_off_grey) { + dc_gain_weight = ci->dc_gain_min_weight; + } else if (enable_dc_gain && target_grey > ci->dc_gain_off_grey) { enable_dc_gain = false; - dc_gain_weight = dc_gain_max_weight; + dc_gain_weight = ci->dc_gain_max_weight; } - if (enable_dc_gain && dc_gain_weight < dc_gain_max_weight) {dc_gain_weight += 1;} - if (!enable_dc_gain && dc_gain_weight > dc_gain_min_weight) {dc_gain_weight -= 1;} + if (enable_dc_gain && dc_gain_weight < ci->dc_gain_max_weight) {dc_gain_weight += 1;} + if (!enable_dc_gain && dc_gain_weight > ci->dc_gain_min_weight) {dc_gain_weight -= 1;} std::string gain_bytes, time_bytes; if (env_ctrl_exp_from_params) { @@ -1050,14 +1053,14 @@ void CameraState::set_camera_exposure(float grey_frac) { } else { // Simple brute force optimizer to choose sensor parameters // to reach desired EV - for (int g = std::max((int)analog_gain_min_idx, gain_idx - 1); g <= std::min((int)analog_gain_max_idx, gain_idx + 1); g++) { - float gain = sensor_analog_gains[g] * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + for (int g = std::max((int)ci->analog_gain_min_idx, gain_idx - 1); g <= std::min((int)ci->analog_gain_max_idx, gain_idx + 1); g++) { + float gain = ci->sensor_analog_gains[g] * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); // Compute optimal time for given gain - int t = std::clamp(int(std::round(desired_ev / gain)), exposure_time_min, exposure_time_max); + int t = std::clamp(int(std::round(desired_ev / gain)), ci->exposure_time_min, ci->exposure_time_max); // Only go below recommended gain when absolutely necessary to not overexpose - if (g < analog_gain_rec_idx && t > 20 && g < gain_idx) { + if (g < ci->analog_gain_rec_idx && t > 20 && g < gain_idx) { continue; } @@ -1070,12 +1073,12 @@ void CameraState::set_camera_exposure(float grey_frac) { measured_grey_fraction = grey_frac; target_grey_fraction = target_grey; - analog_gain_frac = sensor_analog_gains[new_exp_g]; + analog_gain_frac = ci->sensor_analog_gains[new_exp_g]; gain_idx = new_exp_g; exposure_time = new_exp_t; dc_gain_enabled = enable_dc_gain; - float gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + float gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); cur_ev[buf.cur_frame_data.frame_id % 3] = exposure_time * gain; exp_lock.unlock(); @@ -1100,7 +1103,7 @@ void CameraState::set_camera_exposure(float grey_frac) { // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD uint32_t hcg_time = exposure_time; uint32_t lcg_time = hcg_time; - uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OX03C10) / 3), exposure_time_max + VS_TIME_MAX_OX03C10); + uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (ci->exposure_time_max + VS_TIME_MAX_OX03C10) / 3), ci->exposure_time_max + VS_TIME_MAX_OX03C10); uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 87cdd091f6..b1765aa582 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -12,12 +13,11 @@ #include "common/util.h" #define FRAME_BUF_COUNT 4 -#define ANALOG_GAIN_MAX_CNT 55 class CameraState { public: MultiCameraState *multi_cam_state; - CameraInfo ci; + std::unique_ptr ci; bool enabled; std::mutex exp_lock; @@ -28,32 +28,13 @@ public: int gain_idx; float analog_gain_frac; - int exposure_time_min; - int exposure_time_max; - - float dc_gain_factor; - int dc_gain_min_weight; - int dc_gain_max_weight; - float dc_gain_on_grey; - float dc_gain_off_grey; - - float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; - int analog_gain_min_idx; - int analog_gain_max_idx; - int analog_gain_rec_idx; - int analog_gain_cost_delta; - float analog_gain_cost_low; - float analog_gain_cost_high; - float cur_ev[3]; - float min_ev, max_ev; float best_ev_score; int new_exp_g; int new_exp_t; float measured_grey_fraction; float target_grey_fraction; - float target_grey_factor; unique_fd sensor_fd; unique_fd csiphy_fd; diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 7d7a454f7e..10034a7c3c 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -21,7 +21,7 @@ std::map> ar0231_build_register_lut(CameraState *c std::map> registers; for (int register_row = 0; register_row < 2; register_row++) { - uint8_t *registers_raw = data + c->ci.frame_stride * register_row; + uint8_t *registers_raw = data + c->ci->frame_stride * register_row; assert(registers_raw[0] == 0x0a); // Start of line int value_tag_count = 0; @@ -46,7 +46,7 @@ std::map> ar0231_build_register_lut(CameraState *c cur_addr += 2; first_val_idx = val_idx; } else { - registers[cur_addr] = std::make_pair(first_val_idx + c->ci.frame_stride * register_row, val_idx + c->ci.frame_stride * register_row); + registers[cur_addr] = std::make_pair(first_val_idx + c->ci->frame_stride * register_row, val_idx + c->ci->frame_stride * register_row); } value_tag_count++; @@ -80,7 +80,7 @@ float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_r void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) { const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; - uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci.registers_offset; + uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci->registers_offset; if (memcmp(data, expected_preamble, std::size(expected_preamble)) != 0) { LOGE("unexpected register data found"); From 0586f86ad01323bc213c125e8584df277555456d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 7 Dec 2023 16:21:16 -0800 Subject: [PATCH 154/724] Revert "camerad: refactor sensor parameters to struct (#30639)" This reverts commit a70911d6396a41fb006043b41e89281fa7a5d8b1. --- system/camerad/cameras/camera_common.cc | 6 +- system/camerad/cameras/camera_common.h | 22 +--- system/camerad/cameras/camera_qcom2.cc | 135 ++++++++++++------------ system/camerad/cameras/camera_qcom2.h | 23 +++- system/camerad/sensors/ar0231.cc | 6 +- 5 files changed, 94 insertions(+), 98 deletions(-) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 703378b104..987ccf23da 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -28,7 +28,7 @@ class Debayer { public: Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { char args[4096]; - const CameraInfo *ci = s->ci.get(); + const CameraInfo *ci = &s->ci; snprintf(args, sizeof(args), "-cl-fast-relaxed-math -cl-denorms-are-zero " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " @@ -66,7 +66,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, this->yuv_type = init_yuv_type; frame_buf_count = frame_cnt; - const CameraInfo *ci = s->ci.get(); + const CameraInfo *ci = &s->ci; // RAW frame const int frame_size = (ci->frame_height + ci->extra_height) * ci->frame_stride; camera_bufs = std::make_unique(frame_buf_count); @@ -152,7 +152,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setProcessingTime(frame_data.processing_time); const float ev = c->cur_ev[frame_data.frame_id % 3]; - const float perc = util::map_val(ev, c->ci->min_ev, c->ci->max_ev, 0.0f, 100.0f); + const float perc = util::map_val(ev, c->min_ev, c->max_ev, 0.0f, 100.0f); framed.setExposureValPercent(perc); if (c->camera_id == CAMERA_ID_AR0231) { diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 8022fa844e..9d9e42a5ed 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -16,8 +16,8 @@ #define CAMERA_ID_AR0231 0 #define CAMERA_ID_OX03C10 1 +#define CAMERA_ID_MAX 2 -#define ANALOG_GAIN_MAX_CNT 55 const int YUV_BUFFER_COUNT = 20; enum CameraType { @@ -41,26 +41,6 @@ typedef struct CameraInfo { uint32_t extra_height = 0; int registers_offset = -1; int stats_offset = -1; - - int exposure_time_min; - int exposure_time_max; - - float dc_gain_factor; - int dc_gain_min_weight; - int dc_gain_max_weight; - float dc_gain_on_grey; - float dc_gain_off_grey; - - float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; - int analog_gain_min_idx; - int analog_gain_max_idx; - int analog_gain_rec_idx; - int analog_gain_cost_delta; - float analog_gain_cost_low; - float analog_gain_cost_high; - float target_grey_factor; - float min_ev; - float max_ev; } CameraInfo; typedef struct FrameMetadata { diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 5aa95bd63d..e6ca0fff94 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -40,6 +40,27 @@ const size_t AR0231_REGISTERS_HEIGHT = 2; const size_t AR0231_STATS_HEIGHT = 2+8; const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py + +CameraInfo cameras_supported[CAMERA_ID_MAX] = { + [CAMERA_ID_AR0231] = { + .frame_width = FRAME_WIDTH, + .frame_height = FRAME_HEIGHT, + .frame_stride = FRAME_STRIDE, + .extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT, + + .registers_offset = 0, + .frame_offset = AR0231_REGISTERS_HEIGHT, + .stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, + }, + [CAMERA_ID_OX03C10] = { + .frame_width = FRAME_WIDTH, + .frame_height = FRAME_HEIGHT, + .frame_stride = FRAME_STRIDE, // (0xa80*12//8) + .extra_height = 16, // top 2 + bot 14 + .frame_offset = 2, + }, +}; + const float DC_GAIN_AR0231 = 2.5; const float DC_GAIN_OX03C10 = 7.32; @@ -403,10 +424,10 @@ void CameraState::config_isp(int io_mem_handle, int fence, int request_id, int b if (io_mem_handle != 0) { io_cfg[0].mem_handle[0] = io_mem_handle; io_cfg[0].planes[0] = (struct cam_plane_cfg){ - .width = ci->frame_width, - .height = ci->frame_height + ci->extra_height, - .plane_stride = ci->frame_stride, - .slice_height = ci->frame_height + ci->extra_height, + .width = ci.frame_width, + .height = ci.frame_height + ci.extra_height, + .plane_stride = ci.frame_stride, + .slice_height = ci.frame_height + ci.extra_height, .meta_stride = 0x0, // YUV has meta(stride=0x400, size=0x5000) .meta_size = 0x0, .meta_offset = 0x0, @@ -496,17 +517,8 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* -struct CameraAR0231 : public CameraInfo { - CameraAR0231() { - frame_width = FRAME_WIDTH, - frame_height = FRAME_HEIGHT, - frame_stride = FRAME_STRIDE, - extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT, - - registers_offset = 0, - frame_offset = AR0231_REGISTERS_HEIGHT, - stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, - +void CameraState::camera_set_parameters() { + if (camera_id == CAMERA_ID_AR0231) { dc_gain_factor = DC_GAIN_AR0231; dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_AR0231; dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231; @@ -520,23 +532,12 @@ struct CameraAR0231 : public CameraInfo { analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; - for (int i = 0; i <= analog_gain_max_idx; i++) { + for (int i=0; i<=analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; } min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; - max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_factor = TARGET_GREY_FACTOR_AR0231; - } -}; - -struct CameraOx0310 : public CameraInfo { - CameraOx0310() { - frame_width = FRAME_WIDTH, - frame_height = FRAME_HEIGHT, - frame_stride = FRAME_STRIDE, // (0xa80*12//8) - extra_height = 16, // top 2 + bot 14 - frame_offset = 2, - + } else if (camera_id == CAMERA_ID_OX03C10) { dc_gain_factor = DC_GAIN_OX03C10; dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_OX03C10; dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10; @@ -554,27 +555,19 @@ struct CameraOx0310 : public CameraInfo { sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; } min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; - max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_factor = TARGET_GREY_FACTOR_OX03C10; - } -}; - -void CameraState::camera_set_parameters() { - if (camera_id == CAMERA_ID_AR0231) { - ci = std::make_unique(); - } else if (camera_id == CAMERA_ID_OX03C10) { - ci = std::make_unique(); } else { assert(false); } + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_fraction = 0.3; dc_gain_enabled = false; - dc_gain_weight = ci->dc_gain_min_weight; - gain_idx = ci->analog_gain_rec_idx; + dc_gain_weight = dc_gain_min_weight; + gain_idx = analog_gain_rec_idx; exposure_time = 5; - cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight) * ci->sensor_analog_gains[gain_idx] * exposure_time; + cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight) * sensor_analog_gains[gain_idx] * exposure_time; } void CameraState::camera_map_bufs(MultiCameraState *s) { @@ -597,6 +590,10 @@ void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, int came camera_id = camera_id_; LOGD("camera init %d", camera_num); + assert(camera_id < std::size(cameras_supported)); + ci = cameras_supported[camera_id]; + assert(ci.frame_width != 0); + request_id_last = 0; skipped = true; @@ -683,16 +680,16 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .usage_type = 0x0, .left_start = 0, - .left_stop = ci->frame_width - 1, - .left_width = ci->frame_width, + .left_stop = ci.frame_width - 1, + .left_width = ci.frame_width, .right_start = 0, - .right_stop = ci->frame_width - 1, - .right_width = ci->frame_width, + .right_stop = ci.frame_width - 1, + .right_width = ci.frame_width, .line_start = 0, - .line_stop = ci->frame_height + ci->extra_height - 1, - .height = ci->frame_height + ci->extra_height, + .line_stop = ci.frame_height + ci.extra_height - 1, + .height = ci.frame_height + ci.extra_height, .pixel_clk = 0x0, .batch_size = 0x0, @@ -704,8 +701,8 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .data[0] = (struct cam_isp_out_port_info){ .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, .format = CAM_FORMAT_MIPI_RAW_12, - .width = ci->frame_width, - .height = ci->frame_height + ci->extra_height, + .width = ci.frame_width, + .height = ci.frame_height + ci.extra_height, .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, }, }; @@ -949,7 +946,7 @@ void CameraState::handle_camera_event(void *evdat) { meta_data.frame_id = main_id - idx_offset; meta_data.timestamp_sof = timestamp; exp_lock.lock(); - meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); + meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); meta_data.high_conversion_gain = dc_gain_enabled; meta_data.integ_lines = exposure_time; meta_data.measured_grey_fraction = measured_grey_fraction; @@ -975,15 +972,15 @@ void CameraState::update_exposure_score(float desired_ev, int exp_t, int exp_g_i // Cost of ev diff score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; // Cost of absolute gain - float m = exp_g_idx > ci->analog_gain_rec_idx ? ci->analog_gain_cost_high : ci->analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)ci->analog_gain_rec_idx) * m; + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; // Cost of changing gain score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; } else if (camera_id == CAMERA_ID_OX03C10) { score = std::abs(desired_ev - (exp_t * exp_gain)); - float m = exp_g_idx > ci->analog_gain_rec_idx ? ci->analog_gain_cost_high : ci->analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)ci->analog_gain_rec_idx) * m; - score += ((1 - ci->analog_gain_cost_delta) + ci->analog_gain_cost_delta * (exp_g_idx - ci->analog_gain_min_idx) / (ci->analog_gain_max_idx - ci->analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + score += ((1 - analog_gain_cost_delta) + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; } if (score < best_ev_score) { @@ -1011,10 +1008,10 @@ void CameraState::set_camera_exposure(float grey_frac) { const float cur_ev_ = cur_ev[buf.cur_frame_data.frame_id % 3]; // Scale target grey between 0.1 and 0.4 depending on lighting conditions - float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + ci->target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4); + float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4); float target_grey = (1.0 - k_grey) * target_grey_fraction + k_grey * new_target_grey; - float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, ci->min_ev, ci->max_ev); + float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, min_ev, max_ev); float k = (1.0 - k_ev) / 3.0; desired_ev = (k * cur_ev[0]) + (k * cur_ev[1]) + (k * cur_ev[2]) + (k_ev * desired_ev); @@ -1025,16 +1022,16 @@ void CameraState::set_camera_exposure(float grey_frac) { // Hysteresis around high conversion gain // We usually want this on since it results in lower noise, but turn off in very bright day scenes bool enable_dc_gain = dc_gain_enabled; - if (!enable_dc_gain && target_grey < ci->dc_gain_on_grey) { + if (!enable_dc_gain && target_grey < dc_gain_on_grey) { enable_dc_gain = true; - dc_gain_weight = ci->dc_gain_min_weight; - } else if (enable_dc_gain && target_grey > ci->dc_gain_off_grey) { + dc_gain_weight = dc_gain_min_weight; + } else if (enable_dc_gain && target_grey > dc_gain_off_grey) { enable_dc_gain = false; - dc_gain_weight = ci->dc_gain_max_weight; + dc_gain_weight = dc_gain_max_weight; } - if (enable_dc_gain && dc_gain_weight < ci->dc_gain_max_weight) {dc_gain_weight += 1;} - if (!enable_dc_gain && dc_gain_weight > ci->dc_gain_min_weight) {dc_gain_weight -= 1;} + if (enable_dc_gain && dc_gain_weight < dc_gain_max_weight) {dc_gain_weight += 1;} + if (!enable_dc_gain && dc_gain_weight > dc_gain_min_weight) {dc_gain_weight -= 1;} std::string gain_bytes, time_bytes; if (env_ctrl_exp_from_params) { @@ -1053,14 +1050,14 @@ void CameraState::set_camera_exposure(float grey_frac) { } else { // Simple brute force optimizer to choose sensor parameters // to reach desired EV - for (int g = std::max((int)ci->analog_gain_min_idx, gain_idx - 1); g <= std::min((int)ci->analog_gain_max_idx, gain_idx + 1); g++) { - float gain = ci->sensor_analog_gains[g] * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); + for (int g = std::max((int)analog_gain_min_idx, gain_idx - 1); g <= std::min((int)analog_gain_max_idx, gain_idx + 1); g++) { + float gain = sensor_analog_gains[g] * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); // Compute optimal time for given gain - int t = std::clamp(int(std::round(desired_ev / gain)), ci->exposure_time_min, ci->exposure_time_max); + int t = std::clamp(int(std::round(desired_ev / gain)), exposure_time_min, exposure_time_max); // Only go below recommended gain when absolutely necessary to not overexpose - if (g < ci->analog_gain_rec_idx && t > 20 && g < gain_idx) { + if (g < analog_gain_rec_idx && t > 20 && g < gain_idx) { continue; } @@ -1073,12 +1070,12 @@ void CameraState::set_camera_exposure(float grey_frac) { measured_grey_fraction = grey_frac; target_grey_fraction = target_grey; - analog_gain_frac = ci->sensor_analog_gains[new_exp_g]; + analog_gain_frac = sensor_analog_gains[new_exp_g]; gain_idx = new_exp_g; exposure_time = new_exp_t; dc_gain_enabled = enable_dc_gain; - float gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); + float gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); cur_ev[buf.cur_frame_data.frame_id % 3] = exposure_time * gain; exp_lock.unlock(); @@ -1103,7 +1100,7 @@ void CameraState::set_camera_exposure(float grey_frac) { // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD uint32_t hcg_time = exposure_time; uint32_t lcg_time = hcg_time; - uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (ci->exposure_time_max + VS_TIME_MAX_OX03C10) / 3), ci->exposure_time_max + VS_TIME_MAX_OX03C10); + uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OX03C10) / 3), exposure_time_max + VS_TIME_MAX_OX03C10); uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index b1765aa582..87cdd091f6 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -13,11 +12,12 @@ #include "common/util.h" #define FRAME_BUF_COUNT 4 +#define ANALOG_GAIN_MAX_CNT 55 class CameraState { public: MultiCameraState *multi_cam_state; - std::unique_ptr ci; + CameraInfo ci; bool enabled; std::mutex exp_lock; @@ -28,13 +28,32 @@ public: int gain_idx; float analog_gain_frac; + int exposure_time_min; + int exposure_time_max; + + float dc_gain_factor; + int dc_gain_min_weight; + int dc_gain_max_weight; + float dc_gain_on_grey; + float dc_gain_off_grey; + + float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; + int analog_gain_min_idx; + int analog_gain_max_idx; + int analog_gain_rec_idx; + int analog_gain_cost_delta; + float analog_gain_cost_low; + float analog_gain_cost_high; + float cur_ev[3]; + float min_ev, max_ev; float best_ev_score; int new_exp_g; int new_exp_t; float measured_grey_fraction; float target_grey_fraction; + float target_grey_factor; unique_fd sensor_fd; unique_fd csiphy_fd; diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 10034a7c3c..7d7a454f7e 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -21,7 +21,7 @@ std::map> ar0231_build_register_lut(CameraState *c std::map> registers; for (int register_row = 0; register_row < 2; register_row++) { - uint8_t *registers_raw = data + c->ci->frame_stride * register_row; + uint8_t *registers_raw = data + c->ci.frame_stride * register_row; assert(registers_raw[0] == 0x0a); // Start of line int value_tag_count = 0; @@ -46,7 +46,7 @@ std::map> ar0231_build_register_lut(CameraState *c cur_addr += 2; first_val_idx = val_idx; } else { - registers[cur_addr] = std::make_pair(first_val_idx + c->ci->frame_stride * register_row, val_idx + c->ci->frame_stride * register_row); + registers[cur_addr] = std::make_pair(first_val_idx + c->ci.frame_stride * register_row, val_idx + c->ci.frame_stride * register_row); } value_tag_count++; @@ -80,7 +80,7 @@ float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_r void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) { const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; - uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci->registers_offset; + uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci.registers_offset; if (memcmp(data, expected_preamble, std::size(expected_preamble)) != 0) { LOGE("unexpected register data found"); From 49317e390351f702bfe95a0400fef479d389007f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 7 Dec 2023 18:37:59 -0600 Subject: [PATCH 155/724] Ford: add Explorer 2020 FW (#30638) * 49cead668ca2ac46 * docs * b18d8435f1460528 --- selfdrive/car/ford/values.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 3f4540da21..301847b7ea 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -193,6 +193,7 @@ FW_VERSIONS = { b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -214,6 +215,7 @@ FW_VERSIONS = { b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PB5A-14C204-DA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5A-14C204-ATS\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.F_150_MK14: { From 9bff8ccd0fa8f29d3fa92e4505d9cdd7a72d0f12 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 8 Dec 2023 12:11:44 +0800 Subject: [PATCH 156/724] camerad: fix use of uninitialized `CameraInfo` in `camera_open()` (#30642) --- system/camerad/cameras/camera_qcom2.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index e6ca0fff94..d5fd643bf1 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -597,8 +597,6 @@ void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, int came request_id_last = 0; skipped = true; - camera_set_parameters(); - buf.init(device_id, ctx, this, v, FRAME_BUF_COUNT, yuv_type); camera_map_bufs(s); } @@ -634,6 +632,8 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num return; } + camera_set_parameters(); + // create session struct cam_req_mgr_session_info session_info = {}; ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_CREATE_SESSION, &session_info, sizeof(session_info)); From 51328609d21d3d2c6ca211c2eeafcdc9511feb6f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 8 Dec 2023 16:01:16 +0800 Subject: [PATCH 157/724] camerad: refactor sensor parameters to struct (#30644) * refactor camerainfo * include --- system/camerad/cameras/camera_common.cc | 6 +- system/camerad/cameras/camera_common.h | 22 +++- system/camerad/cameras/camera_qcom2.cc | 135 ++++++++++++------------ system/camerad/cameras/camera_qcom2.h | 23 +--- system/camerad/sensors/ar0231.cc | 6 +- 5 files changed, 98 insertions(+), 94 deletions(-) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 987ccf23da..703378b104 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -28,7 +28,7 @@ class Debayer { public: Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { char args[4096]; - const CameraInfo *ci = &s->ci; + const CameraInfo *ci = s->ci.get(); snprintf(args, sizeof(args), "-cl-fast-relaxed-math -cl-denorms-are-zero " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " @@ -66,7 +66,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, this->yuv_type = init_yuv_type; frame_buf_count = frame_cnt; - const CameraInfo *ci = &s->ci; + const CameraInfo *ci = s->ci.get(); // RAW frame const int frame_size = (ci->frame_height + ci->extra_height) * ci->frame_stride; camera_bufs = std::make_unique(frame_buf_count); @@ -152,7 +152,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setProcessingTime(frame_data.processing_time); const float ev = c->cur_ev[frame_data.frame_id % 3]; - const float perc = util::map_val(ev, c->min_ev, c->max_ev, 0.0f, 100.0f); + const float perc = util::map_val(ev, c->ci->min_ev, c->ci->max_ev, 0.0f, 100.0f); framed.setExposureValPercent(perc); if (c->camera_id == CAMERA_ID_AR0231) { diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 9d9e42a5ed..8022fa844e 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -16,8 +16,8 @@ #define CAMERA_ID_AR0231 0 #define CAMERA_ID_OX03C10 1 -#define CAMERA_ID_MAX 2 +#define ANALOG_GAIN_MAX_CNT 55 const int YUV_BUFFER_COUNT = 20; enum CameraType { @@ -41,6 +41,26 @@ typedef struct CameraInfo { uint32_t extra_height = 0; int registers_offset = -1; int stats_offset = -1; + + int exposure_time_min; + int exposure_time_max; + + float dc_gain_factor; + int dc_gain_min_weight; + int dc_gain_max_weight; + float dc_gain_on_grey; + float dc_gain_off_grey; + + float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; + int analog_gain_min_idx; + int analog_gain_max_idx; + int analog_gain_rec_idx; + int analog_gain_cost_delta; + float analog_gain_cost_low; + float analog_gain_cost_high; + float target_grey_factor; + float min_ev; + float max_ev; } CameraInfo; typedef struct FrameMetadata { diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index d5fd643bf1..2e84a8605b 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -40,27 +40,6 @@ const size_t AR0231_REGISTERS_HEIGHT = 2; const size_t AR0231_STATS_HEIGHT = 2+8; const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py - -CameraInfo cameras_supported[CAMERA_ID_MAX] = { - [CAMERA_ID_AR0231] = { - .frame_width = FRAME_WIDTH, - .frame_height = FRAME_HEIGHT, - .frame_stride = FRAME_STRIDE, - .extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT, - - .registers_offset = 0, - .frame_offset = AR0231_REGISTERS_HEIGHT, - .stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, - }, - [CAMERA_ID_OX03C10] = { - .frame_width = FRAME_WIDTH, - .frame_height = FRAME_HEIGHT, - .frame_stride = FRAME_STRIDE, // (0xa80*12//8) - .extra_height = 16, // top 2 + bot 14 - .frame_offset = 2, - }, -}; - const float DC_GAIN_AR0231 = 2.5; const float DC_GAIN_OX03C10 = 7.32; @@ -424,10 +403,10 @@ void CameraState::config_isp(int io_mem_handle, int fence, int request_id, int b if (io_mem_handle != 0) { io_cfg[0].mem_handle[0] = io_mem_handle; io_cfg[0].planes[0] = (struct cam_plane_cfg){ - .width = ci.frame_width, - .height = ci.frame_height + ci.extra_height, - .plane_stride = ci.frame_stride, - .slice_height = ci.frame_height + ci.extra_height, + .width = ci->frame_width, + .height = ci->frame_height + ci->extra_height, + .plane_stride = ci->frame_stride, + .slice_height = ci->frame_height + ci->extra_height, .meta_stride = 0x0, // YUV has meta(stride=0x400, size=0x5000) .meta_size = 0x0, .meta_offset = 0x0, @@ -517,8 +496,17 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* -void CameraState::camera_set_parameters() { - if (camera_id == CAMERA_ID_AR0231) { +struct CameraAR0231 : public CameraInfo { + CameraAR0231() { + frame_width = FRAME_WIDTH, + frame_height = FRAME_HEIGHT, + frame_stride = FRAME_STRIDE, + extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT, + + registers_offset = 0, + frame_offset = AR0231_REGISTERS_HEIGHT, + stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, + dc_gain_factor = DC_GAIN_AR0231; dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_AR0231; dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231; @@ -532,12 +520,23 @@ void CameraState::camera_set_parameters() { analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; - for (int i=0; i<=analog_gain_max_idx; i++) { + for (int i = 0; i <= analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; } min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_factor = TARGET_GREY_FACTOR_AR0231; - } else if (camera_id == CAMERA_ID_OX03C10) { + } +}; + +struct CameraOx0310 : public CameraInfo { + CameraOx0310() { + frame_width = FRAME_WIDTH, + frame_height = FRAME_HEIGHT, + frame_stride = FRAME_STRIDE, // (0xa80*12//8) + extra_height = 16, // top 2 + bot 14 + frame_offset = 2, + dc_gain_factor = DC_GAIN_OX03C10; dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_OX03C10; dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10; @@ -555,19 +554,27 @@ void CameraState::camera_set_parameters() { sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; } min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_factor = TARGET_GREY_FACTOR_OX03C10; + } +}; + +void CameraState::camera_set_parameters() { + if (camera_id == CAMERA_ID_AR0231) { + ci = std::make_unique(); + } else if (camera_id == CAMERA_ID_OX03C10) { + ci = std::make_unique(); } else { assert(false); } - max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; target_grey_fraction = 0.3; dc_gain_enabled = false; - dc_gain_weight = dc_gain_min_weight; - gain_idx = analog_gain_rec_idx; + dc_gain_weight = ci->dc_gain_min_weight; + gain_idx = ci->analog_gain_rec_idx; exposure_time = 5; - cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight) * sensor_analog_gains[gain_idx] * exposure_time; + cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight) * ci->sensor_analog_gains[gain_idx] * exposure_time; } void CameraState::camera_map_bufs(MultiCameraState *s) { @@ -590,10 +597,6 @@ void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, int came camera_id = camera_id_; LOGD("camera init %d", camera_num); - assert(camera_id < std::size(cameras_supported)); - ci = cameras_supported[camera_id]; - assert(ci.frame_width != 0); - request_id_last = 0; skipped = true; @@ -680,16 +683,16 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .usage_type = 0x0, .left_start = 0, - .left_stop = ci.frame_width - 1, - .left_width = ci.frame_width, + .left_stop = ci->frame_width - 1, + .left_width = ci->frame_width, .right_start = 0, - .right_stop = ci.frame_width - 1, - .right_width = ci.frame_width, + .right_stop = ci->frame_width - 1, + .right_width = ci->frame_width, .line_start = 0, - .line_stop = ci.frame_height + ci.extra_height - 1, - .height = ci.frame_height + ci.extra_height, + .line_stop = ci->frame_height + ci->extra_height - 1, + .height = ci->frame_height + ci->extra_height, .pixel_clk = 0x0, .batch_size = 0x0, @@ -701,8 +704,8 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .data[0] = (struct cam_isp_out_port_info){ .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, .format = CAM_FORMAT_MIPI_RAW_12, - .width = ci.frame_width, - .height = ci.frame_height + ci.extra_height, + .width = ci->frame_width, + .height = ci->frame_height + ci->extra_height, .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, }, }; @@ -946,7 +949,7 @@ void CameraState::handle_camera_event(void *evdat) { meta_data.frame_id = main_id - idx_offset; meta_data.timestamp_sof = timestamp; exp_lock.lock(); - meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); meta_data.high_conversion_gain = dc_gain_enabled; meta_data.integ_lines = exposure_time; meta_data.measured_grey_fraction = measured_grey_fraction; @@ -972,15 +975,15 @@ void CameraState::update_exposure_score(float desired_ev, int exp_t, int exp_g_i // Cost of ev diff score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; // Cost of absolute gain - float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + float m = exp_g_idx > ci->analog_gain_rec_idx ? ci->analog_gain_cost_high : ci->analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)ci->analog_gain_rec_idx) * m; // Cost of changing gain score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; } else if (camera_id == CAMERA_ID_OX03C10) { score = std::abs(desired_ev - (exp_t * exp_gain)); - float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; - score += ((1 - analog_gain_cost_delta) + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; + float m = exp_g_idx > ci->analog_gain_rec_idx ? ci->analog_gain_cost_high : ci->analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)ci->analog_gain_rec_idx) * m; + score += ((1 - ci->analog_gain_cost_delta) + ci->analog_gain_cost_delta * (exp_g_idx - ci->analog_gain_min_idx) / (ci->analog_gain_max_idx - ci->analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; } if (score < best_ev_score) { @@ -1008,10 +1011,10 @@ void CameraState::set_camera_exposure(float grey_frac) { const float cur_ev_ = cur_ev[buf.cur_frame_data.frame_id % 3]; // Scale target grey between 0.1 and 0.4 depending on lighting conditions - float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4); + float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + ci->target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4); float target_grey = (1.0 - k_grey) * target_grey_fraction + k_grey * new_target_grey; - float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, min_ev, max_ev); + float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, ci->min_ev, ci->max_ev); float k = (1.0 - k_ev) / 3.0; desired_ev = (k * cur_ev[0]) + (k * cur_ev[1]) + (k * cur_ev[2]) + (k_ev * desired_ev); @@ -1022,16 +1025,16 @@ void CameraState::set_camera_exposure(float grey_frac) { // Hysteresis around high conversion gain // We usually want this on since it results in lower noise, but turn off in very bright day scenes bool enable_dc_gain = dc_gain_enabled; - if (!enable_dc_gain && target_grey < dc_gain_on_grey) { + if (!enable_dc_gain && target_grey < ci->dc_gain_on_grey) { enable_dc_gain = true; - dc_gain_weight = dc_gain_min_weight; - } else if (enable_dc_gain && target_grey > dc_gain_off_grey) { + dc_gain_weight = ci->dc_gain_min_weight; + } else if (enable_dc_gain && target_grey > ci->dc_gain_off_grey) { enable_dc_gain = false; - dc_gain_weight = dc_gain_max_weight; + dc_gain_weight = ci->dc_gain_max_weight; } - if (enable_dc_gain && dc_gain_weight < dc_gain_max_weight) {dc_gain_weight += 1;} - if (!enable_dc_gain && dc_gain_weight > dc_gain_min_weight) {dc_gain_weight -= 1;} + if (enable_dc_gain && dc_gain_weight < ci->dc_gain_max_weight) {dc_gain_weight += 1;} + if (!enable_dc_gain && dc_gain_weight > ci->dc_gain_min_weight) {dc_gain_weight -= 1;} std::string gain_bytes, time_bytes; if (env_ctrl_exp_from_params) { @@ -1050,14 +1053,14 @@ void CameraState::set_camera_exposure(float grey_frac) { } else { // Simple brute force optimizer to choose sensor parameters // to reach desired EV - for (int g = std::max((int)analog_gain_min_idx, gain_idx - 1); g <= std::min((int)analog_gain_max_idx, gain_idx + 1); g++) { - float gain = sensor_analog_gains[g] * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + for (int g = std::max((int)ci->analog_gain_min_idx, gain_idx - 1); g <= std::min((int)ci->analog_gain_max_idx, gain_idx + 1); g++) { + float gain = ci->sensor_analog_gains[g] * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); // Compute optimal time for given gain - int t = std::clamp(int(std::round(desired_ev / gain)), exposure_time_min, exposure_time_max); + int t = std::clamp(int(std::round(desired_ev / gain)), ci->exposure_time_min, ci->exposure_time_max); // Only go below recommended gain when absolutely necessary to not overexpose - if (g < analog_gain_rec_idx && t > 20 && g < gain_idx) { + if (g < ci->analog_gain_rec_idx && t > 20 && g < gain_idx) { continue; } @@ -1070,12 +1073,12 @@ void CameraState::set_camera_exposure(float grey_frac) { measured_grey_fraction = grey_frac; target_grey_fraction = target_grey; - analog_gain_frac = sensor_analog_gains[new_exp_g]; + analog_gain_frac = ci->sensor_analog_gains[new_exp_g]; gain_idx = new_exp_g; exposure_time = new_exp_t; dc_gain_enabled = enable_dc_gain; - float gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + float gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); cur_ev[buf.cur_frame_data.frame_id % 3] = exposure_time * gain; exp_lock.unlock(); @@ -1100,7 +1103,7 @@ void CameraState::set_camera_exposure(float grey_frac) { // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD uint32_t hcg_time = exposure_time; uint32_t lcg_time = hcg_time; - uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OX03C10) / 3), exposure_time_max + VS_TIME_MAX_OX03C10); + uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (ci->exposure_time_max + VS_TIME_MAX_OX03C10) / 3), ci->exposure_time_max + VS_TIME_MAX_OX03C10); uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 87cdd091f6..b1765aa582 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -12,12 +13,11 @@ #include "common/util.h" #define FRAME_BUF_COUNT 4 -#define ANALOG_GAIN_MAX_CNT 55 class CameraState { public: MultiCameraState *multi_cam_state; - CameraInfo ci; + std::unique_ptr ci; bool enabled; std::mutex exp_lock; @@ -28,32 +28,13 @@ public: int gain_idx; float analog_gain_frac; - int exposure_time_min; - int exposure_time_max; - - float dc_gain_factor; - int dc_gain_min_weight; - int dc_gain_max_weight; - float dc_gain_on_grey; - float dc_gain_off_grey; - - float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; - int analog_gain_min_idx; - int analog_gain_max_idx; - int analog_gain_rec_idx; - int analog_gain_cost_delta; - float analog_gain_cost_low; - float analog_gain_cost_high; - float cur_ev[3]; - float min_ev, max_ev; float best_ev_score; int new_exp_g; int new_exp_t; float measured_grey_fraction; float target_grey_fraction; - float target_grey_factor; unique_fd sensor_fd; unique_fd csiphy_fd; diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 7d7a454f7e..10034a7c3c 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -21,7 +21,7 @@ std::map> ar0231_build_register_lut(CameraState *c std::map> registers; for (int register_row = 0; register_row < 2; register_row++) { - uint8_t *registers_raw = data + c->ci.frame_stride * register_row; + uint8_t *registers_raw = data + c->ci->frame_stride * register_row; assert(registers_raw[0] == 0x0a); // Start of line int value_tag_count = 0; @@ -46,7 +46,7 @@ std::map> ar0231_build_register_lut(CameraState *c cur_addr += 2; first_val_idx = val_idx; } else { - registers[cur_addr] = std::make_pair(first_val_idx + c->ci.frame_stride * register_row, val_idx + c->ci.frame_stride * register_row); + registers[cur_addr] = std::make_pair(first_val_idx + c->ci->frame_stride * register_row, val_idx + c->ci->frame_stride * register_row); } value_tag_count++; @@ -80,7 +80,7 @@ float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_r void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) { const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; - uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci.registers_offset; + uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci->registers_offset; if (memcmp(data, expected_preamble, std::size(expected_preamble)) != 0) { LOGE("unexpected register data found"); From ef262ff9eb5e52a58838479510947552a88ae3bd Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 9 Dec 2023 03:19:10 +0800 Subject: [PATCH 158/724] camerad: move sensor related code to `/sensors` (#30647) --- release/files_common | 2 + system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.h | 33 ---- system/camerad/cameras/camera_qcom2.cc | 167 +-------------------- system/camerad/cameras/camera_qcom2.h | 3 +- system/camerad/sensors/ar0231.cc | 72 +++++++++ system/camerad/sensors/ar0231_registers.h | 6 +- system/camerad/sensors/ox03c10.cc | 88 +++++++++++ system/camerad/sensors/ox03c10_registers.h | 6 +- system/camerad/sensors/sensor.h | 59 ++++++++ 10 files changed, 238 insertions(+), 200 deletions(-) create mode 100644 system/camerad/sensors/ox03c10.cc create mode 100644 system/camerad/sensors/sensor.h diff --git a/release/files_common b/release/files_common index d73c71171a..637f8bd7d7 100644 --- a/release/files_common +++ b/release/files_common @@ -320,7 +320,9 @@ system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.cc system/camerad/sensors/ar0231.cc system/camerad/sensors/ar0231_registers.h +system/camerad/sensors/ox03c10.cc system/camerad/sensors/ox03c10_registers.h +system/camerad/sensors/sensor.h selfdrive/manager/__init__.py selfdrive/manager/build.py diff --git a/system/camerad/SConscript b/system/camerad/SConscript index d814ebf1b0..a0056c1f9f 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -3,7 +3,7 @@ Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon, 'atomic'] camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc', - 'sensors/ar0231.cc']) + 'sensors/ar0231.cc', 'sensors/ox03c10.cc']) env.Program('camerad', ['main.cc', camera_obj], LIBS=libs) if GetOption("extras") and arch == "x86_64": diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 8022fa844e..eedeaf1b7a 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -17,7 +17,6 @@ #define CAMERA_ID_AR0231 0 #define CAMERA_ID_OX03C10 1 -#define ANALOG_GAIN_MAX_CNT 55 const int YUV_BUFFER_COUNT = 20; enum CameraType { @@ -34,35 +33,6 @@ const bool env_debug_frames = getenv("DEBUG_FRAMES") != NULL; const bool env_log_raw_frames = getenv("LOG_RAW_FRAMES") != NULL; const bool env_ctrl_exp_from_params = getenv("CTRL_EXP_FROM_PARAMS") != NULL; -typedef struct CameraInfo { - uint32_t frame_width, frame_height; - uint32_t frame_stride; - uint32_t frame_offset = 0; - uint32_t extra_height = 0; - int registers_offset = -1; - int stats_offset = -1; - - int exposure_time_min; - int exposure_time_max; - - float dc_gain_factor; - int dc_gain_min_weight; - int dc_gain_max_weight; - float dc_gain_on_grey; - float dc_gain_off_grey; - - float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; - int analog_gain_min_idx; - int analog_gain_max_idx; - int analog_gain_rec_idx; - int analog_gain_cost_delta; - float analog_gain_cost_low; - float analog_gain_cost_high; - float target_grey_factor; - float min_ev; - float max_ev; -} CameraInfo; - typedef struct FrameMetadata { uint32_t frame_id; @@ -123,6 +93,3 @@ void cameras_close(MultiCameraState *s); void camerad_thread(); int open_v4l_by_name_and_index(const char name[], int index = 0, int flags = O_RDWR | O_NONBLOCK); - -void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed); - diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 2e84a8605b..75367a6478 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -19,85 +19,17 @@ #include "media/cam_defs.h" #include "media/cam_isp.h" #include "media/cam_isp_ife.h" -#include "media/cam_sensor.h" #include "media/cam_sensor_cmn_header.h" #include "media/cam_sync.h" #include "common/swaglog.h" -#include "system/camerad/sensors/ar0231_registers.h" -#include "system/camerad/sensors/ox03c10_registers.h" + +const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py // For debugging: // echo "4294967295" > /sys/module/cam_debug_util/parameters/debug_mdl extern ExitHandler do_exit; -const size_t FRAME_WIDTH = 1928; -const size_t FRAME_HEIGHT = 1208; -const size_t FRAME_STRIDE = 2896; // for 12 bit output. 1928 * 12 / 8 + 4 (alignment) - -const size_t AR0231_REGISTERS_HEIGHT = 2; -// TODO: this extra height is universal and doesn't apply per camera -const size_t AR0231_STATS_HEIGHT = 2+8; - -const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py -const float DC_GAIN_AR0231 = 2.5; -const float DC_GAIN_OX03C10 = 7.32; - -const float DC_GAIN_ON_GREY_AR0231 = 0.2; -const float DC_GAIN_OFF_GREY_AR0231 = 0.3; -const float DC_GAIN_ON_GREY_OX03C10 = 0.9; -const float DC_GAIN_OFF_GREY_OX03C10 = 1.0; - -const int DC_GAIN_MIN_WEIGHT_AR0231 = 0; -const int DC_GAIN_MAX_WEIGHT_AR0231 = 1; -const int DC_GAIN_MIN_WEIGHT_OX03C10 = 1; // always on is fine -const int DC_GAIN_MAX_WEIGHT_OX03C10 = 1; - -const float TARGET_GREY_FACTOR_AR0231 = 1.0; -const float TARGET_GREY_FACTOR_OX03C10 = 0.01; - -const float sensor_analog_gains_AR0231[] = { - 1.0/8.0, 2.0/8.0, 2.0/7.0, 3.0/7.0, // 0, 1, 2, 3 - 3.0/6.0, 4.0/6.0, 4.0/5.0, 5.0/5.0, // 4, 5, 6, 7 - 5.0/4.0, 6.0/4.0, 6.0/3.0, 7.0/3.0, // 8, 9, 10, 11 - 7.0/2.0, 8.0/2.0, 8.0/1.0}; // 12, 13, 14, 15 = bypass - -const float sensor_analog_gains_OX03C10[] = { - 1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875, - 1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0, - 3.125, 3.375, 3.625, 3.875, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5, - 5.75, 6.0, 6.25, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, - 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5}; - -const uint32_t ox03c10_analog_gains_reg[] = { - 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0, - 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300, - 0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, - 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, - 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; - -const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x -const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x -const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x -const int ANALOG_GAIN_COST_DELTA_AR0231 = 0; -const float ANALOG_GAIN_COST_LOW_AR0231 = 0.1; -const float ANALOG_GAIN_COST_HIGH_AR0231 = 5.0; - -const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; -const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x0; // 1x -const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; -const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1; -const float ANALOG_GAIN_COST_LOW_OX03C10 = 0.4; -const float ANALOG_GAIN_COST_HIGH_OX03C10 = 6.4; - -const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss -const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms - -const int EXPOSURE_TIME_MIN_OX03C10 = 2; // 1x -const int EXPOSURE_TIME_MAX_OX03C10 = 2016; -const uint32_t VS_TIME_MIN_OX03C10 = 1; -const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 - int CameraState::clear_req_queue() { struct cam_req_mgr_flush_info req_mgr_flush_request = {0}; req_mgr_flush_request.session_hdl = session_handle; @@ -141,7 +73,7 @@ void CameraState::sensors_poke(int request_id) { } } -void CameraState::sensors_i2c(struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word) { +void CameraState::sensors_i2c(const struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word) { // LOGD("sensors_i2c: %d", len); uint32_t cam_packet_handle = 0; int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*1; @@ -496,74 +428,11 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* -struct CameraAR0231 : public CameraInfo { - CameraAR0231() { - frame_width = FRAME_WIDTH, - frame_height = FRAME_HEIGHT, - frame_stride = FRAME_STRIDE, - extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT, - - registers_offset = 0, - frame_offset = AR0231_REGISTERS_HEIGHT, - stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, - - dc_gain_factor = DC_GAIN_AR0231; - dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_AR0231; - dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231; - dc_gain_on_grey = DC_GAIN_ON_GREY_AR0231; - dc_gain_off_grey = DC_GAIN_OFF_GREY_AR0231; - exposure_time_min = EXPOSURE_TIME_MIN_AR0231; - exposure_time_max = EXPOSURE_TIME_MAX_AR0231; - analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_AR0231; - analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231; - analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231; - analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; - analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; - analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; - for (int i = 0; i <= analog_gain_max_idx; i++) { - sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; - } - min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; - max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; - target_grey_factor = TARGET_GREY_FACTOR_AR0231; - } -}; - -struct CameraOx0310 : public CameraInfo { - CameraOx0310() { - frame_width = FRAME_WIDTH, - frame_height = FRAME_HEIGHT, - frame_stride = FRAME_STRIDE, // (0xa80*12//8) - extra_height = 16, // top 2 + bot 14 - frame_offset = 2, - - dc_gain_factor = DC_GAIN_OX03C10; - dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_OX03C10; - dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10; - dc_gain_on_grey = DC_GAIN_ON_GREY_OX03C10; - dc_gain_off_grey = DC_GAIN_OFF_GREY_OX03C10; - exposure_time_min = EXPOSURE_TIME_MIN_OX03C10; - exposure_time_max = EXPOSURE_TIME_MAX_OX03C10; - analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_OX03C10; - analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10; - analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10; - analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10; - analog_gain_cost_low = ANALOG_GAIN_COST_LOW_OX03C10; - analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_OX03C10; - for (int i=0; i<=analog_gain_max_idx; i++) { - sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; - } - min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; - max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; - target_grey_factor = TARGET_GREY_FACTOR_OX03C10; - } -}; - void CameraState::camera_set_parameters() { if (camera_id == CAMERA_ID_AR0231) { ci = std::make_unique(); } else if (camera_id == CAMERA_ID_OX03C10) { - ci = std::make_unique(); + ci = std::make_unique(); } else { assert(false); } @@ -1092,31 +961,11 @@ void CameraState::set_camera_exposure(float grey_frac) { // LOGE("ae - camera %d, cur_t %.5f, sof %.5f, dt %.5f", camera_num, 1e-9 * nanos_since_boot(), 1e-9 * buf.cur_frame_data.timestamp_sof, 1e-9 * (nanos_since_boot() - buf.cur_frame_data.timestamp_sof)); if (camera_id == CAMERA_ID_AR0231) { - uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g; - struct i2c_random_wr_payload exp_reg_array[] = { - {0x3366, analog_gain_reg}, - {0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)}, - {0x3012, (uint16_t)exposure_time}, - }; - sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); + auto exp_reg_array = ar0231_get_exp_registers(ci.get(), exposure_time, new_exp_g, dc_gain_enabled); + sensors_i2c(exp_reg_array.data(), exp_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); } else if (camera_id == CAMERA_ID_OX03C10) { - // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD - uint32_t hcg_time = exposure_time; - uint32_t lcg_time = hcg_time; - uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (ci->exposure_time_max + VS_TIME_MAX_OX03C10) / 3), ci->exposure_time_max + VS_TIME_MAX_OX03C10); - uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); - - uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; - - struct i2c_random_wr_payload exp_reg_array[] = { - {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, - {0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, - {0x3541, spd_time>>8}, {0x3542, spd_time&0xFF}, - {0x35c2, vs_time&0xFF}, - - {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, - }; - sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); + auto exp_reg_array = ox03c10_get_exp_registers(ci.get(), exposure_time, new_exp_g, dc_gain_enabled); + sensors_i2c(exp_reg_array.data(), exp_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); } } diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index b1765aa582..dc924c882a 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -9,6 +9,7 @@ #include "system/camerad/cameras/camera_common.h" #include "system/camerad/cameras/camera_util.h" +#include "system/camerad/sensors/sensor.h" #include "common/params.h" #include "common/util.h" @@ -82,7 +83,7 @@ public: int sensors_init(); void sensors_poke(int request_id); - void sensors_i2c(struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word); + void sensors_i2c(const struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word); // Register parsing std::map> ar0231_register_lut; diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 10034a7c3c..0ddb5b63d1 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -2,9 +2,40 @@ #include "system/camerad/cameras/camera_common.h" #include "system/camerad/cameras/camera_qcom2.h" +#include "system/camerad/sensors/sensor.h" namespace { +const size_t AR0231_REGISTERS_HEIGHT = 2; +// TODO: this extra height is universal and doesn't apply per camera +const size_t AR0231_STATS_HEIGHT = 2 + 8; + +const float DC_GAIN_AR0231 = 2.5; + +const float DC_GAIN_ON_GREY_AR0231 = 0.2; +const float DC_GAIN_OFF_GREY_AR0231 = 0.3; + +const int DC_GAIN_MIN_WEIGHT_AR0231 = 0; +const int DC_GAIN_MAX_WEIGHT_AR0231 = 1; + +const float TARGET_GREY_FACTOR_AR0231 = 1.0; + +const float sensor_analog_gains_AR0231[] = { + 1.0 / 8.0, 2.0 / 8.0, 2.0 / 7.0, 3.0 / 7.0, // 0, 1, 2, 3 + 3.0 / 6.0, 4.0 / 6.0, 4.0 / 5.0, 5.0 / 5.0, // 4, 5, 6, 7 + 5.0 / 4.0, 6.0 / 4.0, 6.0 / 3.0, 7.0 / 3.0, // 8, 9, 10, 11 + 7.0 / 2.0, 8.0 / 2.0, 8.0 / 1.0}; // 12, 13, 14, 15 = bypass + +const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x +const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x +const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x +const int ANALOG_GAIN_COST_DELTA_AR0231 = 0; +const float ANALOG_GAIN_COST_LOW_AR0231 = 0.1; +const float ANALOG_GAIN_COST_HIGH_AR0231 = 5.0; + +const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss +const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms + std::map> ar0231_build_register_lut(CameraState *c, uint8_t *data) { // This function builds a lookup table from register address, to a pair of indices in the // buffer where to read this address. The buffer contains padding bytes, @@ -78,6 +109,37 @@ float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_r } // namespace +CameraAR0231::CameraAR0231() { + frame_width = FRAME_WIDTH; + frame_height = FRAME_HEIGHT; + frame_stride = FRAME_STRIDE; + extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT; + + registers_offset = 0; + frame_offset = AR0231_REGISTERS_HEIGHT; + stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT; + + dc_gain_factor = DC_GAIN_AR0231; + dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_AR0231; + dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231; + dc_gain_on_grey = DC_GAIN_ON_GREY_AR0231; + dc_gain_off_grey = DC_GAIN_OFF_GREY_AR0231; + exposure_time_min = EXPOSURE_TIME_MIN_AR0231; + exposure_time_max = EXPOSURE_TIME_MAX_AR0231; + analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_AR0231; + analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231; + analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231; + analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; + analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; + analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; + for (int i = 0; i <= analog_gain_max_idx; i++) { + sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; + } + min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; + target_grey_factor = TARGET_GREY_FACTOR_AR0231; +} + void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) { const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci->registers_offset; @@ -96,3 +158,13 @@ void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::Frame float temp_1 = ar0231_parse_temp_sensor(registers[0x30ca], registers[0x30cc], registers[0x20b2]); framed.setTemperaturesC({temp_0, temp_1}); } + + +std::vector ar0231_get_exp_registers(const CameraInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { + uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g; + return { + {0x3366, analog_gain_reg}, + {0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)}, + {0x3012, (uint16_t)exposure_time}, + }; +} diff --git a/system/camerad/sensors/ar0231_registers.h b/system/camerad/sensors/ar0231_registers.h index 611d08ec8e..6c4c251e8e 100644 --- a/system/camerad/sensors/ar0231_registers.h +++ b/system/camerad/sensors/ar0231_registers.h @@ -1,9 +1,9 @@ #pragma once -struct i2c_random_wr_payload start_reg_array_ar0231[] = {{0x301A, 0x91C}}; -struct i2c_random_wr_payload stop_reg_array_ar0231[] = {{0x301A, 0x918}}; +const struct i2c_random_wr_payload start_reg_array_ar0231[] = {{0x301A, 0x91C}}; +const struct i2c_random_wr_payload stop_reg_array_ar0231[] = {{0x301A, 0x918}}; -struct i2c_random_wr_payload init_array_ar0231[] = { +const struct i2c_random_wr_payload init_array_ar0231[] = { {0x301A, 0x0018}, // RESET_REGISTER // CLOCK Settings diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc new file mode 100644 index 0000000000..c1e1cdf6ec --- /dev/null +++ b/system/camerad/sensors/ox03c10.cc @@ -0,0 +1,88 @@ +#include "system/camerad/sensors/sensor.h" + +namespace { + +const float DC_GAIN_OX03C10 = 7.32; + +const float DC_GAIN_ON_GREY_OX03C10 = 0.9; +const float DC_GAIN_OFF_GREY_OX03C10 = 1.0; + +const int DC_GAIN_MIN_WEIGHT_OX03C10 = 1; // always on is fine +const int DC_GAIN_MAX_WEIGHT_OX03C10 = 1; + +const float TARGET_GREY_FACTOR_OX03C10 = 0.01; + +const float sensor_analog_gains_OX03C10[] = { + 1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875, + 1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0, + 3.125, 3.375, 3.625, 3.875, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5, + 5.75, 6.0, 6.25, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, + 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5}; + +const uint32_t ox03c10_analog_gains_reg[] = { + 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0, + 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300, + 0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, + 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, + 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; + +const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; +const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x0; // 1x +const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; +const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1; +const float ANALOG_GAIN_COST_LOW_OX03C10 = 0.4; +const float ANALOG_GAIN_COST_HIGH_OX03C10 = 6.4; + +const int EXPOSURE_TIME_MIN_OX03C10 = 2; // 1x +const int EXPOSURE_TIME_MAX_OX03C10 = 2016; +const uint32_t VS_TIME_MIN_OX03C10 = 1; +const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 + +} // namespace + +CameraOx03c10::CameraOx03c10() { + frame_width = FRAME_WIDTH; + frame_height = FRAME_HEIGHT; + frame_stride = FRAME_STRIDE; // (0xa80*12//8) + extra_height = 16; // top 2 + bot 14 + frame_offset = 2; + + dc_gain_factor = DC_GAIN_OX03C10; + dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_OX03C10; + dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10; + dc_gain_on_grey = DC_GAIN_ON_GREY_OX03C10; + dc_gain_off_grey = DC_GAIN_OFF_GREY_OX03C10; + exposure_time_min = EXPOSURE_TIME_MIN_OX03C10; + exposure_time_max = EXPOSURE_TIME_MAX_OX03C10; + analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_OX03C10; + analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10; + analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10; + analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10; + analog_gain_cost_low = ANALOG_GAIN_COST_LOW_OX03C10; + analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_OX03C10; + for (int i = 0; i <= analog_gain_max_idx; i++) { + sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; + } + min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; + target_grey_factor = TARGET_GREY_FACTOR_OX03C10; +} + +std::vector ox03c10_get_exp_registers(const CameraInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { + // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD + uint32_t hcg_time = exposure_time; + uint32_t lcg_time = hcg_time; + uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (ci->exposure_time_max + VS_TIME_MAX_OX03C10) / 3), ci->exposure_time_max + VS_TIME_MAX_OX03C10); + uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); + + uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; + + return { + {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, + {0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, + {0x3541, spd_time>>8}, {0x3542, spd_time&0xFF}, + {0x35c2, vs_time&0xFF}, + + {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, + }; +} diff --git a/system/camerad/sensors/ox03c10_registers.h b/system/camerad/sensors/ox03c10_registers.h index 7aed2e8ac0..575a2cb934 100644 --- a/system/camerad/sensors/ox03c10_registers.h +++ b/system/camerad/sensors/ox03c10_registers.h @@ -1,9 +1,9 @@ #pragma once -struct i2c_random_wr_payload start_reg_array_ox03c10[] = {{0x100, 1}}; -struct i2c_random_wr_payload stop_reg_array_ox03c10[] = {{0x100, 0}}; +const struct i2c_random_wr_payload start_reg_array_ox03c10[] = {{0x100, 1}}; +const struct i2c_random_wr_payload stop_reg_array_ox03c10[] = {{0x100, 0}}; -struct i2c_random_wr_payload init_array_ox03c10[] = { +const struct i2c_random_wr_payload init_array_ox03c10[] = { {0x103, 1}, {0x107, 1}, diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h new file mode 100644 index 0000000000..60fbdea81b --- /dev/null +++ b/system/camerad/sensors/sensor.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include "media/cam_sensor.h" +#include "system/camerad/cameras/camera_common.h" +#include "system/camerad/sensors/ar0231_registers.h" +#include "system/camerad/sensors/ox03c10_registers.h" + +#define ANALOG_GAIN_MAX_CNT 55 +const size_t FRAME_WIDTH = 1928; +const size_t FRAME_HEIGHT = 1208; +const size_t FRAME_STRIDE = 2896; // for 12 bit output. 1928 * 12 / 8 + 4 (alignment) + +class CameraInfo { +public: + CameraInfo() = default; + + uint32_t frame_width, frame_height; + uint32_t frame_stride; + uint32_t frame_offset = 0; + uint32_t extra_height = 0; + int registers_offset = -1; + int stats_offset = -1; + + int exposure_time_min; + int exposure_time_max; + + float dc_gain_factor; + int dc_gain_min_weight; + int dc_gain_max_weight; + float dc_gain_on_grey; + float dc_gain_off_grey; + + float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; + int analog_gain_min_idx; + int analog_gain_max_idx; + int analog_gain_rec_idx; + int analog_gain_cost_delta; + float analog_gain_cost_low; + float analog_gain_cost_high; + float target_grey_factor; + float min_ev; + float max_ev; +}; + +class CameraAR0231 : public CameraInfo { +public: + CameraAR0231(); +}; + +class CameraOx03c10 : public CameraInfo { +public: + CameraOx03c10(); +}; + +void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed); +std::vector ox03c10_get_exp_registers(const CameraInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled); +std::vector ar0231_get_exp_registers(const CameraInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled); From 2590cf8615ca3dc86cb7bacae83bfb8f95bf8d80 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 8 Dec 2023 11:39:30 -0800 Subject: [PATCH 159/724] update plannerd cpu usage --- selfdrive/test/test_onroad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 2d674e4610..b4121529d1 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -35,7 +35,7 @@ PROCS = { "./camerad": 14.5, "./locationd": 11.0, "./mapsd": (1.0, 10.0), - "selfdrive.controls.plannerd": 16.5, + "selfdrive.controls.plannerd": 11.0, "./_ui": 18.0, "selfdrive.locationd.paramsd": 9.0, "./sensord": 7.0, From e757d9bae7bcd068d26224344e0227c12a5171cc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 8 Dec 2023 11:39:40 -0800 Subject: [PATCH 160/724] camerad: renames (#30649) * sensorinfo * drop the camera --- system/camerad/cameras/camera_common.cc | 4 ++-- system/camerad/cameras/camera_qcom2.cc | 4 ++-- system/camerad/cameras/camera_qcom2.h | 2 +- system/camerad/sensors/ar0231.cc | 4 ++-- system/camerad/sensors/ox03c10.cc | 4 ++-- system/camerad/sensors/sensor.h | 16 ++++++++-------- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 703378b104..a53b0d412a 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -28,7 +28,7 @@ class Debayer { public: Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { char args[4096]; - const CameraInfo *ci = s->ci.get(); + const SensorInfo *ci = s->ci.get(); snprintf(args, sizeof(args), "-cl-fast-relaxed-math -cl-denorms-are-zero " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " @@ -66,7 +66,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, this->yuv_type = init_yuv_type; frame_buf_count = frame_cnt; - const CameraInfo *ci = s->ci.get(); + const SensorInfo *ci = s->ci.get(); // RAW frame const int frame_size = (ci->frame_height + ci->extra_height) * ci->frame_stride; camera_bufs = std::make_unique(frame_buf_count); diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 75367a6478..f2ce058f4f 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -430,9 +430,9 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { void CameraState::camera_set_parameters() { if (camera_id == CAMERA_ID_AR0231) { - ci = std::make_unique(); + ci = std::make_unique(); } else if (camera_id == CAMERA_ID_OX03C10) { - ci = std::make_unique(); + ci = std::make_unique(); } else { assert(false); } diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index dc924c882a..65c7abfb36 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -18,7 +18,7 @@ class CameraState { public: MultiCameraState *multi_cam_state; - std::unique_ptr ci; + std::unique_ptr ci; bool enabled; std::mutex exp_lock; diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 0ddb5b63d1..b542f27c82 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -109,7 +109,7 @@ float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_r } // namespace -CameraAR0231::CameraAR0231() { +AR0231::AR0231() { frame_width = FRAME_WIDTH; frame_height = FRAME_HEIGHT; frame_stride = FRAME_STRIDE; @@ -160,7 +160,7 @@ void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::Frame } -std::vector ar0231_get_exp_registers(const CameraInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { +std::vector ar0231_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g; return { {0x3366, analog_gain_reg}, diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc index c1e1cdf6ec..b60917b5e8 100644 --- a/system/camerad/sensors/ox03c10.cc +++ b/system/camerad/sensors/ox03c10.cc @@ -40,7 +40,7 @@ const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 } // namespace -CameraOx03c10::CameraOx03c10() { +OX03C10::OX03C10() { frame_width = FRAME_WIDTH; frame_height = FRAME_HEIGHT; frame_stride = FRAME_STRIDE; // (0xa80*12//8) @@ -68,7 +68,7 @@ CameraOx03c10::CameraOx03c10() { target_grey_factor = TARGET_GREY_FACTOR_OX03C10; } -std::vector ox03c10_get_exp_registers(const CameraInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { +std::vector ox03c10_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD uint32_t hcg_time = exposure_time; uint32_t lcg_time = hcg_time; diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h index 60fbdea81b..d038d44545 100644 --- a/system/camerad/sensors/sensor.h +++ b/system/camerad/sensors/sensor.h @@ -12,9 +12,9 @@ const size_t FRAME_WIDTH = 1928; const size_t FRAME_HEIGHT = 1208; const size_t FRAME_STRIDE = 2896; // for 12 bit output. 1928 * 12 / 8 + 4 (alignment) -class CameraInfo { +class SensorInfo { public: - CameraInfo() = default; + SensorInfo() = default; uint32_t frame_width, frame_height; uint32_t frame_stride; @@ -44,16 +44,16 @@ public: float max_ev; }; -class CameraAR0231 : public CameraInfo { +class AR0231 : public SensorInfo { public: - CameraAR0231(); + AR0231(); }; -class CameraOx03c10 : public CameraInfo { +class OX03C10 : public SensorInfo { public: - CameraOx03c10(); + OX03C10(); }; void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed); -std::vector ox03c10_get_exp_registers(const CameraInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled); -std::vector ar0231_get_exp_registers(const CameraInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled); +std::vector ox03c10_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled); +std::vector ar0231_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled); From 8fdcddec8a3798f2f100aec48a7e8047040c2c1d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 9 Dec 2023 05:01:41 +0800 Subject: [PATCH 161/724] camerad: define the constants directly in ctor (#30651) --- system/camerad/sensors/ar0231.cc | 48 +++++++++---------------------- system/camerad/sensors/ox03c10.cc | 47 +++++++++--------------------- 2 files changed, 28 insertions(+), 67 deletions(-) diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index b542f27c82..01a95252f4 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -10,32 +10,12 @@ const size_t AR0231_REGISTERS_HEIGHT = 2; // TODO: this extra height is universal and doesn't apply per camera const size_t AR0231_STATS_HEIGHT = 2 + 8; -const float DC_GAIN_AR0231 = 2.5; - -const float DC_GAIN_ON_GREY_AR0231 = 0.2; -const float DC_GAIN_OFF_GREY_AR0231 = 0.3; - -const int DC_GAIN_MIN_WEIGHT_AR0231 = 0; -const int DC_GAIN_MAX_WEIGHT_AR0231 = 1; - -const float TARGET_GREY_FACTOR_AR0231 = 1.0; - const float sensor_analog_gains_AR0231[] = { 1.0 / 8.0, 2.0 / 8.0, 2.0 / 7.0, 3.0 / 7.0, // 0, 1, 2, 3 3.0 / 6.0, 4.0 / 6.0, 4.0 / 5.0, 5.0 / 5.0, // 4, 5, 6, 7 5.0 / 4.0, 6.0 / 4.0, 6.0 / 3.0, 7.0 / 3.0, // 8, 9, 10, 11 7.0 / 2.0, 8.0 / 2.0, 8.0 / 1.0}; // 12, 13, 14, 15 = bypass -const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x -const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x -const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x -const int ANALOG_GAIN_COST_DELTA_AR0231 = 0; -const float ANALOG_GAIN_COST_LOW_AR0231 = 0.1; -const float ANALOG_GAIN_COST_HIGH_AR0231 = 5.0; - -const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss -const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms - std::map> ar0231_build_register_lut(CameraState *c, uint8_t *data) { // This function builds a lookup table from register address, to a pair of indices in the // buffer where to read this address. The buffer contains padding bytes, @@ -119,25 +99,25 @@ AR0231::AR0231() { frame_offset = AR0231_REGISTERS_HEIGHT; stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT; - dc_gain_factor = DC_GAIN_AR0231; - dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_AR0231; - dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231; - dc_gain_on_grey = DC_GAIN_ON_GREY_AR0231; - dc_gain_off_grey = DC_GAIN_OFF_GREY_AR0231; - exposure_time_min = EXPOSURE_TIME_MIN_AR0231; - exposure_time_max = EXPOSURE_TIME_MAX_AR0231; - analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_AR0231; - analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231; - analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231; - analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; - analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; - analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; + dc_gain_factor = 2.5; + dc_gain_min_weight = 0; + dc_gain_max_weight = 1; + dc_gain_on_grey = 0.2; + dc_gain_off_grey = 0.3; + exposure_time_min = 2; // with HDR, fastest ss + exposure_time_max = 0x0855; // with HDR, slowest ss, 40ms + analog_gain_min_idx = 0x1; // 0.25x + analog_gain_rec_idx = 0x6; // 0.8x + analog_gain_max_idx = 0xD; // 4.0x + analog_gain_cost_delta = 0; + analog_gain_cost_low = 0.1; + analog_gain_cost_high = 5.0; for (int i = 0; i <= analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; } min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; - target_grey_factor = TARGET_GREY_FACTOR_AR0231; + target_grey_factor = 1.0; } void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) { diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc index b60917b5e8..d55c73a436 100644 --- a/system/camerad/sensors/ox03c10.cc +++ b/system/camerad/sensors/ox03c10.cc @@ -2,16 +2,6 @@ namespace { -const float DC_GAIN_OX03C10 = 7.32; - -const float DC_GAIN_ON_GREY_OX03C10 = 0.9; -const float DC_GAIN_OFF_GREY_OX03C10 = 1.0; - -const int DC_GAIN_MIN_WEIGHT_OX03C10 = 1; // always on is fine -const int DC_GAIN_MAX_WEIGHT_OX03C10 = 1; - -const float TARGET_GREY_FACTOR_OX03C10 = 0.01; - const float sensor_analog_gains_OX03C10[] = { 1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875, 1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0, @@ -26,15 +16,6 @@ const uint32_t ox03c10_analog_gains_reg[] = { 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; -const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; -const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x0; // 1x -const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; -const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1; -const float ANALOG_GAIN_COST_LOW_OX03C10 = 0.4; -const float ANALOG_GAIN_COST_HIGH_OX03C10 = 6.4; - -const int EXPOSURE_TIME_MIN_OX03C10 = 2; // 1x -const int EXPOSURE_TIME_MAX_OX03C10 = 2016; const uint32_t VS_TIME_MIN_OX03C10 = 1; const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 @@ -47,25 +28,25 @@ OX03C10::OX03C10() { extra_height = 16; // top 2 + bot 14 frame_offset = 2; - dc_gain_factor = DC_GAIN_OX03C10; - dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_OX03C10; - dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10; - dc_gain_on_grey = DC_GAIN_ON_GREY_OX03C10; - dc_gain_off_grey = DC_GAIN_OFF_GREY_OX03C10; - exposure_time_min = EXPOSURE_TIME_MIN_OX03C10; - exposure_time_max = EXPOSURE_TIME_MAX_OX03C10; - analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_OX03C10; - analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10; - analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10; - analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10; - analog_gain_cost_low = ANALOG_GAIN_COST_LOW_OX03C10; - analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_OX03C10; + dc_gain_factor = 7.32; + dc_gain_min_weight = 1; // always on is fine + dc_gain_max_weight = 1; + dc_gain_on_grey = 0.9; + dc_gain_off_grey = 1.0; + exposure_time_min = 2; // 1x + exposure_time_max = 2016; + analog_gain_min_idx = 0x0; + analog_gain_rec_idx = 0x0; // 1x + analog_gain_max_idx = 0x36; + analog_gain_cost_delta = -1; + analog_gain_cost_low = 0.4; + analog_gain_cost_high = 6.4; for (int i = 0; i <= analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; } min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; - target_grey_factor = TARGET_GREY_FACTOR_OX03C10; + target_grey_factor = 0.01; } std::vector ox03c10_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { From 011b1a6e6a1db1047b2c72bc7e9e273a358ab18d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 9 Dec 2023 05:48:31 +0800 Subject: [PATCH 162/724] ui/ParamControl: do not create ConfirmationDialog on every click (#30496) --- selfdrive/ui/qt/widgets/controls.cc | 25 +++++++++++++++++++++++++ selfdrive/ui/qt/widgets/controls.h | 20 ++------------------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 18a79b5983..40dda971f5 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -114,3 +114,28 @@ void ElidedLabel::paintEvent(QPaintEvent *event) { opt.initFrom(this); style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); } + +// ParamControl + +ParamControl::ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent) + : ToggleControl(title, desc, icon, false, parent) { + key = param.toStdString(); + QObject::connect(this, &ParamControl::toggleFlipped, this, &ParamControl::toggleClicked); +} + +void ParamControl::toggleClicked(bool state) { + auto do_confirm = [this]() { + QString content("

" + title_label->text() + "


" + "

" + getDescription() + "

"); + return ConfirmationDialog(content, tr("Enable"), tr("Cancel"), true, this).exec(); + }; + + bool confirmed = store_confirm && params.getBool(key + "Confirmed"); + if (!confirm || confirmed || !state || do_confirm()) { + if (store_confirm && state) params.putBool(key + "Confirmed", true); + params.putBool(key, state); + setIcon(state); + } else { + toggle.togglePosition(); + } +} diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 811595726d..e4630c6590 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -144,24 +144,7 @@ class ParamControl : public ToggleControl { Q_OBJECT public: - ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { - key = param.toStdString(); - QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { - QString content("

" + title + "


" - "

" + getDescription() + "

"); - ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this); - - bool confirmed = store_confirm && params.getBool(key + "Confirmed"); - if (!confirm || confirmed || !state || dialog.exec()) { - if (store_confirm && state) params.putBool(key + "Confirmed", true); - params.putBool(key, state); - setIcon(state); - } else { - toggle.togglePosition(); - } - }); - } - + ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr); void setConfirmation(bool _confirm, bool _store_confirm) { confirm = _confirm; store_confirm = _store_confirm; @@ -184,6 +167,7 @@ public: } private: + void toggleClicked(bool state); void setIcon(bool state) { if (state && !active_icon_pixmap.isNull()) { icon_label->setPixmap(active_icon_pixmap); From fb2f2d9cb21d8109d14a9d9790fda7de07202910 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 9 Dec 2023 06:27:28 +0800 Subject: [PATCH 163/724] camerad: c++ sensorInfo (#30650) * move remaining sensor parameters to CameraInfo * same order * member functions * fix segfault --- system/camerad/cameras/camera_qcom2.cc | 94 +++++--------------------- system/camerad/cameras/camera_qcom2.h | 2 +- system/camerad/sensors/ar0231.cc | 28 +++++++- system/camerad/sensors/ox03c10.cc | 27 +++++++- system/camerad/sensors/sensor.h | 24 +++++-- 5 files changed, 88 insertions(+), 87 deletions(-) diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index f2ce058f4f..2592721886 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -46,13 +46,7 @@ int CameraState::clear_req_queue() { void CameraState::sensors_start() { if (!enabled) return; LOGD("starting sensor %d", camera_num); - if (camera_id == CAMERA_ID_AR0231) { - sensors_i2c(start_reg_array_ar0231, std::size(start_reg_array_ar0231), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); - } else if (camera_id == CAMERA_ID_OX03C10) { - sensors_i2c(start_reg_array_ox03c10, std::size(start_reg_array_ox03c10), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); - } else { - assert(false); - } + sensors_i2c(ci->start_reg_array.data(), ci->start_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, ci->data_word); } void CameraState::sensors_poke(int request_id) { @@ -112,6 +106,8 @@ static cam_cmd_power *power_set_wait(cam_cmd_power *power, int16_t delay_ms) { } int CameraState::sensors_init() { + create_sensor(); + uint32_t cam_packet_handle = 0; int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*2; auto pkt = mm.alloc(size, &cam_packet_handle); @@ -127,21 +123,7 @@ int CameraState::sensors_init() { auto probe = (struct cam_cmd_probe *)(i2c_info.get() + 1); probe->camera_id = camera_num; - switch (camera_num) { - case 0: - // port 0 - i2c_info->slave_addr = (camera_id == CAMERA_ID_AR0231) ? 0x20 : 0x6C; // 6C = 0x36*2 - break; - case 1: - // port 1 - i2c_info->slave_addr = (camera_id == CAMERA_ID_AR0231) ? 0x30 : 0x20; - break; - case 2: - // port 2 - i2c_info->slave_addr = (camera_id == CAMERA_ID_AR0231) ? 0x20 : 0x6C; - break; - } - + i2c_info->slave_addr = ci->getSlaveAddress(camera_num); // 0(I2C_STANDARD_MODE) = 100khz, 1(I2C_FAST_MODE) = 400khz //i2c_info->i2c_freq_mode = I2C_STANDARD_MODE; i2c_info->i2c_freq_mode = I2C_FAST_MODE; @@ -151,15 +133,8 @@ int CameraState::sensors_init() { probe->addr_type = CAMERA_SENSOR_I2C_TYPE_WORD; probe->op_code = 3; // don't care? probe->cmd_type = CAMERA_SENSOR_CMD_TYPE_PROBE; - if (camera_id == CAMERA_ID_AR0231) { - probe->reg_addr = 0x3000; - probe->expected_data = 0x354; - } else if (camera_id == CAMERA_ID_OX03C10) { - probe->reg_addr = 0x300a; - probe->expected_data = 0x5803; - } else { - assert(false); - } + probe->reg_addr = ci->probe_reg_addr; + probe->expected_data = ci->probe_expected_data; probe->data_mask = 0; //buf_desc[1].size = buf_desc[1].length = 148; @@ -182,7 +157,7 @@ int CameraState::sensors_init() { power->count = 1; power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP; power->power_settings[0].power_seq_type = 0; - power->power_settings[0].config_val_low = (camera_id == CAMERA_ID_AR0231) ? 19200000 : 24000000; //Hz + power->power_settings[0].config_val_low = ci->power_config_val_low; power = power_set_wait(power, 1); // reset high @@ -428,7 +403,7 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* -void CameraState::camera_set_parameters() { +void CameraState::create_sensor() { if (camera_id == CAMERA_ID_AR0231) { ci = std::make_unique(); } else if (camera_id == CAMERA_ID_OX03C10) { @@ -504,8 +479,6 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num return; } - camera_set_parameters(); - // create session struct cam_req_mgr_session_info session_info = {}; ret = do_cam_control(multi_cam_state->video0_fd, CAM_REQ_MGR_CREATE_SESSION, &session_info, sizeof(session_info)); @@ -520,18 +493,8 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num LOGD("acquire sensor dev"); LOG("-- Configuring sensor"); - uint32_t dt; - if (camera_id == CAMERA_ID_AR0231) { - sensors_i2c(init_array_ar0231, std::size(init_array_ar0231), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); - dt = 0x12; // Changing stats to 0x2C doesn't work, so change pixels to 0x12 instead - } else if (camera_id == CAMERA_ID_OX03C10) { - sensors_i2c(init_array_ox03c10, std::size(init_array_ox03c10), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); - // one is 0x2a, two are 0x2b - dt = 0x2c; - } else { - assert(false); - } - printf("dt is %x\n", dt); + sensors_i2c(ci->init_reg_array.data(), ci->init_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, ci->data_word); + printf("dt is %x\n", ci->in_port_info_dt); // NOTE: to be able to disable road and wide road, we still have to configure the sensor over i2c // If you don't do this, the strobe GPIO is an output (even in reset it seems!) @@ -545,7 +508,7 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .lane_cfg = 0x3210, .vc = 0x0, - .dt = dt, + .dt = ci->in_port_info_dt, .format = CAM_FORMAT_MIPI_RAW_12, .test_pattern = 0x2, // 0x3? @@ -839,22 +802,7 @@ void CameraState::handle_camera_event(void *evdat) { } void CameraState::update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain) { - float score = 1e6; - if (camera_id == CAMERA_ID_AR0231) { - // Cost of ev diff - score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; - // Cost of absolute gain - float m = exp_g_idx > ci->analog_gain_rec_idx ? ci->analog_gain_cost_high : ci->analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)ci->analog_gain_rec_idx) * m; - // Cost of changing gain - score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; - } else if (camera_id == CAMERA_ID_OX03C10) { - score = std::abs(desired_ev - (exp_t * exp_gain)); - float m = exp_g_idx > ci->analog_gain_rec_idx ? ci->analog_gain_cost_high : ci->analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)ci->analog_gain_rec_idx) * m; - score += ((1 - ci->analog_gain_cost_delta) + ci->analog_gain_cost_delta * (exp_g_idx - ci->analog_gain_min_idx) / (ci->analog_gain_max_idx - ci->analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; - } - + float score = ci->getExposureScore(desired_ev, exp_t, exp_g_idx, exp_gain, gain_idx); if (score < best_ev_score) { new_exp_t = exp_t; new_exp_g = exp_g_idx; @@ -960,13 +908,8 @@ void CameraState::set_camera_exposure(float grey_frac) { } // LOGE("ae - camera %d, cur_t %.5f, sof %.5f, dt %.5f", camera_num, 1e-9 * nanos_since_boot(), 1e-9 * buf.cur_frame_data.timestamp_sof, 1e-9 * (nanos_since_boot() - buf.cur_frame_data.timestamp_sof)); - if (camera_id == CAMERA_ID_AR0231) { - auto exp_reg_array = ar0231_get_exp_registers(ci.get(), exposure_time, new_exp_g, dc_gain_enabled); - sensors_i2c(exp_reg_array.data(), exp_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); - } else if (camera_id == CAMERA_ID_OX03C10) { - auto exp_reg_array = ox03c10_get_exp_registers(ci.get(), exposure_time, new_exp_g, dc_gain_enabled); - sensors_i2c(exp_reg_array.data(), exp_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); - } + auto exp_reg_array = ci->getExposureRegisters(exposure_time, new_exp_g, dc_gain_enabled); + sensors_i2c(exp_reg_array.data(), exp_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, ci->data_word); } static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { @@ -977,9 +920,7 @@ static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) framed.setFrameType(cereal::FrameData::FrameType::FRONT); fill_frame_data(framed, c->buf.cur_frame_data, c); - if (c->camera_id == CAMERA_ID_AR0231) { - ar0231_process_registers(s, c, framed); - } + c->ci->processRegisters(s, c, framed); s->pm->send("driverCameraState", msg); } @@ -994,10 +935,7 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { } LOGT(c->buf.cur_frame_data.frame_id, "%s: Image set", c == &s->road_cam ? "RoadCamera" : "WideRoadCamera"); - if (c->camera_id == CAMERA_ID_AR0231) { - ar0231_process_registers(s, c, framed); - } - + c->ci->processRegisters(s, c, framed); s->pm->send(c == &s->road_cam ? "roadCameraState" : "wideRoadCameraState", msg); const auto [x, y, w, h] = (c == &s->wide_road_cam) ? std::tuple(96, 250, 1734, 524) : std::tuple(96, 160, 1734, 986); diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 65c7abfb36..03c766a2ee 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -49,7 +49,7 @@ public: void sensors_start(); void camera_open(MultiCameraState *multi_cam_state, int camera_num, bool enabled); - void camera_set_parameters(); + void create_sensor(); void camera_map_bufs(MultiCameraState *s); void camera_init(MultiCameraState *s, VisionIpcServer *v, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); void camera_close(); diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 01a95252f4..c750533165 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -90,6 +90,7 @@ float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_r } // namespace AR0231::AR0231() { + data_word = true; frame_width = FRAME_WIDTH; frame_height = FRAME_HEIGHT; frame_stride = FRAME_STRIDE; @@ -99,6 +100,13 @@ AR0231::AR0231() { frame_offset = AR0231_REGISTERS_HEIGHT; stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT; + start_reg_array.assign(std::begin(start_reg_array_ar0231), std::end(start_reg_array_ar0231)); + init_reg_array.assign(std::begin(init_array_ar0231), std::end(init_array_ar0231)); + probe_reg_addr = 0x3000; + probe_expected_data = 0x354; + in_port_info_dt = 0x12; // Changing stats to 0x2C doesn't work, so change pixels to 0x12 instead + power_config_val_low = 19200000; //Hz + dc_gain_factor = 2.5; dc_gain_min_weight = 0; dc_gain_max_weight = 1; @@ -120,7 +128,7 @@ AR0231::AR0231() { target_grey_factor = 1.0; } -void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) { +void AR0231::processRegisters(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) const { const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci->registers_offset; @@ -140,7 +148,7 @@ void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::Frame } -std::vector ar0231_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { +std::vector AR0231::getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g; return { {0x3366, analog_gain_reg}, @@ -148,3 +156,19 @@ std::vector ar0231_get_exp_registers(const SensorI {0x3012, (uint16_t)exposure_time}, }; } + +int AR0231::getSlaveAddress(int port) const { + assert(port >= 0 && port <= 2); + return (int[]){0x20, 0x30, 0x20}[port]; +} + +float AR0231::getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const { + // Cost of ev diff + float score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; + // Cost of absolute gain + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + // Cost of changing gain + score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; + return score; +} diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc index d55c73a436..c30838a7fe 100644 --- a/system/camerad/sensors/ox03c10.cc +++ b/system/camerad/sensors/ox03c10.cc @@ -22,12 +22,20 @@ const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 } // namespace OX03C10::OX03C10() { + data_word = false; frame_width = FRAME_WIDTH; frame_height = FRAME_HEIGHT; frame_stride = FRAME_STRIDE; // (0xa80*12//8) extra_height = 16; // top 2 + bot 14 frame_offset = 2; + start_reg_array.assign(std::begin(start_reg_array_ox03c10), std::end(start_reg_array_ox03c10)); + init_reg_array.assign(std::begin(init_array_ox03c10), std::end(init_array_ox03c10)); + probe_reg_addr = 0x300a; + probe_expected_data = 0x5803; + in_port_info_dt = 0x2c; // one is 0x2a, two are 0x2b + power_config_val_low = 24000000; //Hz + dc_gain_factor = 7.32; dc_gain_min_weight = 1; // always on is fine dc_gain_max_weight = 1; @@ -49,11 +57,11 @@ OX03C10::OX03C10() { target_grey_factor = 0.01; } -std::vector ox03c10_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled) { +std::vector OX03C10::getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD uint32_t hcg_time = exposure_time; uint32_t lcg_time = hcg_time; - uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (ci->exposure_time_max + VS_TIME_MAX_OX03C10) / 3), ci->exposure_time_max + VS_TIME_MAX_OX03C10); + uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OX03C10) / 3), exposure_time_max + VS_TIME_MAX_OX03C10); uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; @@ -67,3 +75,18 @@ std::vector ox03c10_get_exp_registers(const Sensor {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, }; } + +int OX03C10::getSlaveAddress(int port) const { + assert(port >= 0 && port <= 2); + return (int[]){0x6C, 0x20, 0x6C}[port]; +} + +float OX03C10::getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const { + float score = std::abs(desired_ev - (exp_t * exp_gain)); + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + score += ((1 - analog_gain_cost_delta) + + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * + std::abs(exp_g_idx - gain_idx) * 5.0; + return score; +} diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h index d038d44545..62cb31c52c 100644 --- a/system/camerad/sensors/sensor.h +++ b/system/camerad/sensors/sensor.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include "media/cam_sensor.h" @@ -15,6 +16,10 @@ const size_t FRAME_STRIDE = 2896; // for 12 bit output. 1928 * 12 / 8 + 4 (alig class SensorInfo { public: SensorInfo() = default; + virtual std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { return {}; } + virtual float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const {return 0; } + virtual int getSlaveAddress(int port) const { assert(0); } + virtual void processRegisters(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) const {} uint32_t frame_width, frame_height; uint32_t frame_stride; @@ -42,18 +47,29 @@ public: float target_grey_factor; float min_ev; float max_ev; + + bool data_word; + uint32_t probe_reg_addr; + uint32_t probe_expected_data; + std::vector start_reg_array; + std::vector init_reg_array; + uint32_t in_port_info_dt; + uint32_t power_config_val_low; }; class AR0231 : public SensorInfo { public: AR0231(); + std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override; + float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override; + int getSlaveAddress(int port) const override; + void processRegisters(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) const override; }; class OX03C10 : public SensorInfo { public: OX03C10(); + std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override; + float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override; + int getSlaveAddress(int port) const override; }; - -void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed); -std::vector ox03c10_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled); -std::vector ar0231_get_exp_registers(const SensorInfo *ci, int exposure_time, int new_exp_g, bool dc_gain_enabled); From 80bc5833e7a7df4f9f258b19b38f67209b1d413e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 8 Dec 2023 14:44:39 -0800 Subject: [PATCH 164/724] remove common/xattr.py --- common/xattr.py | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 common/xattr.py diff --git a/common/xattr.py b/common/xattr.py deleted file mode 100644 index 26616fd638..0000000000 --- a/common/xattr.py +++ /dev/null @@ -1,46 +0,0 @@ -import os -from cffi import FFI -from typing import Any, List - -# Workaround for the EON/termux build of Python having os.*xattr removed. -ffi = FFI() -ffi.cdef(""" -int setxattr(const char *path, const char *name, const void *value, size_t size, int flags); -ssize_t getxattr(const char *path, const char *name, void *value, size_t size); -ssize_t listxattr(const char *path, char *list, size_t size); -int removexattr(const char *path, const char *name); -""") -libc = ffi.dlopen(None) - -def setxattr(path, name, value, flags=0) -> None: - path = path.encode() - name = name.encode() - if libc.setxattr(path, name, value, len(value), flags) == -1: - raise OSError(ffi.errno, f"{os.strerror(ffi.errno)}: setxattr({path}, {name}, {value}, {flags})") - -def getxattr(path, name, size=128): - path = path.encode() - name = name.encode() - value = ffi.new(f"char[{size}]") - l = libc.getxattr(path, name, value, size) - if l == -1: - # errno 61 means attribute hasn't been set - if ffi.errno == 61: - return None - raise OSError(ffi.errno, f"{os.strerror(ffi.errno)}: getxattr({path}, {name}, {size})") - return ffi.buffer(value)[:l] - -def listxattr(path, size=128) -> List[Any]: - path = path.encode() - attrs = ffi.new(f"char[{size}]") - l = libc.listxattr(path, attrs, size) - if l == -1: - raise OSError(ffi.errno, f"{os.strerror(ffi.errno)}: listxattr({path}, {size})") - # attrs is b'\0' delimited values (so chop off trailing empty item) - return [a.decode() for a in ffi.buffer(attrs)[:l].split(b"\0")[0:-1]] - -def removexattr(path, name) -> None: - path = path.encode() - name = name.encode() - if libc.removexattr(path, name) == -1: - raise OSError(ffi.errno, f"{os.strerror(ffi.errno)}: removexattr({path}, {name})") From 21d5d7d07a278635a1166aef5d5b209e32986382 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 9 Dec 2023 06:57:29 +0800 Subject: [PATCH 165/724] camerad: move ar0231_register_lut to AR0231 (#30652) --- system/camerad/cameras/camera_qcom2.cc | 4 ++-- system/camerad/cameras/camera_qcom2.h | 6 ------ system/camerad/sensors/ar0231.cc | 24 +++++++++--------------- system/camerad/sensors/sensor.h | 9 +++++++-- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 2592721886..2462e969c4 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -920,7 +920,7 @@ static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) framed.setFrameType(cereal::FrameData::FrameType::FRONT); fill_frame_data(framed, c->buf.cur_frame_data, c); - c->ci->processRegisters(s, c, framed); + c->ci->processRegisters(c, framed); s->pm->send("driverCameraState", msg); } @@ -935,7 +935,7 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { } LOGT(c->buf.cur_frame_data.frame_id, "%s: Image set", c == &s->road_cam ? "RoadCamera" : "WideRoadCamera"); - c->ci->processRegisters(s, c, framed); + c->ci->processRegisters(c, framed); s->pm->send(c == &s->road_cam ? "roadCameraState" : "wideRoadCameraState", msg); const auto [x, y, w, h] = (c == &s->wide_road_cam) ? std::tuple(96, 250, 1734, 524) : std::tuple(96, 160, 1734, 986); diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 03c766a2ee..50a68b0b19 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -54,8 +53,6 @@ public: void camera_init(MultiCameraState *s, VisionIpcServer *v, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); void camera_close(); - std::map ar0231_parse_registers(uint8_t *data, std::initializer_list addrs); - int32_t session_handle; int32_t sensor_dev_handle; int32_t isp_dev_handle; @@ -85,9 +82,6 @@ public: void sensors_poke(int request_id); void sensors_i2c(const struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word); - // Register parsing - std::map> ar0231_register_lut; - private: // for debugging Params params; diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index c750533165..9a70befdcc 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -67,19 +67,6 @@ std::map> ar0231_build_register_lut(CameraState *c return registers; } -std::map ar0231_parse_registers(CameraState *c, uint8_t *data, std::initializer_list addrs) { - if (c->ar0231_register_lut.empty()) { - c->ar0231_register_lut = ar0231_build_register_lut(c, data); - } - - std::map registers; - for (uint16_t addr : addrs) { - auto offset = c->ar0231_register_lut[addr]; - registers[addr] = ((uint16_t)data[offset.first] << 8) | data[offset.second]; - } - return registers; -} - float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_reg) { // See AR0231 Developer Guide - page 36 float slope = (125.0 - 55.0) / ((float)calib1 - (float)calib2); @@ -128,7 +115,7 @@ AR0231::AR0231() { target_grey_factor = 1.0; } -void AR0231::processRegisters(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) const { +void AR0231::processRegisters(CameraState *c, cereal::FrameData::Builder &framed) const { const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci->registers_offset; @@ -137,7 +124,14 @@ void AR0231::processRegisters(MultiCameraState *s, CameraState *c, cereal::Frame return; } - auto registers = ar0231_parse_registers(c, data, {0x2000, 0x2002, 0x20b0, 0x20b2, 0x30c6, 0x30c8, 0x30ca, 0x30cc}); + if (ar0231_register_lut.empty()) { + ar0231_register_lut = ar0231_build_register_lut(c, data); + } + std::map registers; + for (uint16_t addr : {0x2000, 0x2002, 0x20b0, 0x20b2, 0x30c6, 0x30c8, 0x30ca, 0x30cc}) { + auto offset = ar0231_register_lut[addr]; + registers[addr] = ((uint16_t)data[offset.first] << 8) | data[offset.second]; + } uint32_t frame_id = ((uint32_t)registers[0x2000] << 16) | registers[0x2002]; framed.setFrameIdSensor(frame_id); diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h index 62cb31c52c..1e45778f20 100644 --- a/system/camerad/sensors/sensor.h +++ b/system/camerad/sensors/sensor.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include "media/cam_sensor.h" #include "system/camerad/cameras/camera_common.h" @@ -19,7 +21,7 @@ public: virtual std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { return {}; } virtual float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const {return 0; } virtual int getSlaveAddress(int port) const { assert(0); } - virtual void processRegisters(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) const {} + virtual void processRegisters(CameraState *c, cereal::FrameData::Builder &framed) const {} uint32_t frame_width, frame_height; uint32_t frame_stride; @@ -63,7 +65,10 @@ public: std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override; float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override; int getSlaveAddress(int port) const override; - void processRegisters(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed) const override; + void processRegisters(CameraState *c, cereal::FrameData::Builder &framed) const override; + +private: + mutable std::map> ar0231_register_lut; }; class OX03C10 : public SensorInfo { From bf4026ed7e55866e1a27f5aee277fbdbeb4853cd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 8 Dec 2023 17:31:10 -0600 Subject: [PATCH 166/724] Toyota: enable LTA (#30109) * bump panda * enable lta * try upper/lower delay similar to longcontrol lag comp * add comment about eps torque rate limits * more clear * more clear * Revert "try upper/lower delay similar to longcontrol lag comp" This reverts commit 8e85333ee6e2456fcda076af4c31a9e9babbc897. * bump * no driver torque limiting to test * fix the setme_x3 * bump * bump * enable inactive safety * use vEgoRaw * rename * fix * some comments/organization * bump * docs * no corolla * shorter name * bump * bump * add to releases * bump panda * remove camry * bump * bump * rm * bump * bump * bump again * bump --- RELEASES.md | 2 ++ docs/CARS.md | 4 +++- panda | 2 +- selfdrive/car/toyota/interface.py | 1 - 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 28c3da70a2..07f70cdaff 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,8 @@ Version 0.9.6 (2023-12-14) ======================== * AGNOS 9 * comma body streaming and controls over WebRTC +* Toyota RAV4 2023 support +* Toyota RAV4 Hybrid 2023 support Version 0.9.5 (2023-11-17) ======================== diff --git a/docs/CARS.md b/docs/CARS.md index 5113eef50e..647b1b15c5 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 269 Supported Cars +# 271 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -237,10 +237,12 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 Hybrid 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/panda b/panda index ea78657bef..80782bcbe4 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit ea78657bef236c6a74b44549e541caf1d7fee618 +Subproject commit 80782bcbe49705810d0859debeff18d86b2cc56e diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index ea28d18302..ae4cb1f2c5 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -28,7 +28,6 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE if candidate in ANGLE_CONTROL_CAR: - ret.dashcamOnly = True ret.steerControlType = SteerControlType.angle ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA From ea0b8920f578e2b6f348d2e18b5783166731c880 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 9 Dec 2023 08:15:58 +0800 Subject: [PATCH 167/724] camerad: remove camera_id (#30654) * remove camera_id * use variable --- system/camerad/cameras/camera_common.cc | 9 ++--- system/camerad/cameras/camera_common.h | 3 -- system/camerad/cameras/camera_qcom2.cc | 46 ++++++++++--------------- system/camerad/cameras/camera_qcom2.h | 5 ++- system/camerad/sensors/ar0231.cc | 1 + system/camerad/sensors/ox03c10.cc | 1 + system/camerad/sensors/sensor.h | 1 + 7 files changed, 26 insertions(+), 40 deletions(-) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index a53b0d412a..e36766ca2d 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -36,7 +36,7 @@ public: "-DIS_OX=%d -DCAM_NUM=%d%s", ci->frame_width, ci->frame_height, ci->frame_stride, ci->frame_offset, b->rgb_width, b->rgb_height, b->rgb_stride, buf_width, uv_offset, - s->camera_id==CAMERA_ID_OX03C10 ? 1 : 0, s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); + ci->image_sensor == cereal::FrameData::ImageSensor::OX03C10, s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); const char *cl_file = "cameras/real_debayer.cl"; cl_program prg_debayer = cl_program_from_file(context, device_id, cl_file, args); krnl_ = CL_CHECK_ERR(clCreateKernel(prg_debayer, "debayer10", &err)); @@ -154,12 +154,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr const float ev = c->cur_ev[frame_data.frame_id % 3]; const float perc = util::map_val(ev, c->ci->min_ev, c->ci->max_ev, 0.0f, 100.0f); framed.setExposureValPercent(perc); - - if (c->camera_id == CAMERA_ID_AR0231) { - framed.setSensor(cereal::FrameData::ImageSensor::AR0231); - } else if (c->camera_id == CAMERA_ID_OX03C10) { - framed.setSensor(cereal::FrameData::ImageSensor::OX03C10); - } + framed.setSensor(c->ci->image_sensor); } kj::Array get_raw_frame_image(const CameraBuf *b) { diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index eedeaf1b7a..e93d357b8d 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -14,9 +14,6 @@ #include "common/swaglog.h" #include "system/hardware/hw.h" -#define CAMERA_ID_AR0231 0 -#define CAMERA_ID_OX03C10 1 - const int YUV_BUFFER_COUNT = 20; enum CameraType { diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 2462e969c4..194f11fe6d 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -106,8 +106,6 @@ static cam_cmd_power *power_set_wait(cam_cmd_power *power, int16_t delay_ms) { } int CameraState::sensors_init() { - create_sensor(); - uint32_t cam_packet_handle = 0; int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*2; auto pkt = mm.alloc(size, &cam_packet_handle); @@ -403,15 +401,7 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* -void CameraState::create_sensor() { - if (camera_id == CAMERA_ID_AR0231) { - ci = std::make_unique(); - } else if (camera_id == CAMERA_ID_OX03C10) { - ci = std::make_unique(); - } else { - assert(false); - } - +void CameraState::sensor_set_parameters() { target_grey_fraction = 0.3; dc_gain_enabled = false; @@ -436,9 +426,8 @@ void CameraState::camera_map_bufs(MultiCameraState *s) { enqueue_req_multi(1, FRAME_BUF_COUNT, 0); } -void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, int camera_id_, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type) { +void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type) { if (!enabled) return; - camera_id = camera_id_; LOGD("camera init %d", camera_num); request_id_last = 0; @@ -462,22 +451,25 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num // init memorymanager for this camera mm.init(multi_cam_state->video0_fd); - // probe the sensor LOGD("-- Probing sensor %d", camera_num); - camera_id = CAMERA_ID_AR0231; - ret = sensors_init(); - if (ret != 0) { - // TODO: use build flag instead? - LOGD("AR0231 init failed, trying OX03C10"); - camera_id = CAMERA_ID_OX03C10; - ret = sensors_init(); - } - LOGD("-- Probing sensor %d done with %d", camera_num, ret); - if (ret != 0) { + + auto init_sensor_lambda = [this](SensorInfo *sensor) { + ci.reset(sensor); + int ret = sensors_init(); + if (ret == 0) { + sensor_set_parameters(); + } + return ret == 0; + }; + + // Try different sensors one by one until it success. + if (!init_sensor_lambda(new AR0231) && + !init_sensor_lambda(new OX03C10)) { LOGE("** sensor %d FAILED bringup, disabling", camera_num); enabled = false; return; } + LOGD("-- Probing sensor %d success", camera_num); // create session struct cam_req_mgr_session_info session_info = {}; @@ -626,9 +618,9 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num } void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { - s->driver_cam.camera_init(s, v, s->driver_cam.camera_id, 20, device_id, ctx, VISION_STREAM_DRIVER); - s->road_cam.camera_init(s, v, s->road_cam.camera_id, 20, device_id, ctx, VISION_STREAM_ROAD); - s->wide_road_cam.camera_init(s, v, s->wide_road_cam.camera_id, 20, device_id, ctx, VISION_STREAM_WIDE_ROAD); + s->driver_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_DRIVER); + s->road_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_ROAD); + s->wide_road_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_WIDE_ROAD); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"}); } diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 50a68b0b19..3da5ec207d 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -48,9 +48,9 @@ public: void sensors_start(); void camera_open(MultiCameraState *multi_cam_state, int camera_num, bool enabled); - void create_sensor(); + void sensor_set_parameters(); void camera_map_bufs(MultiCameraState *s); - void camera_init(MultiCameraState *s, VisionIpcServer *v, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); + void camera_init(MultiCameraState *s, VisionIpcServer *v, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); void camera_close(); int32_t session_handle; @@ -68,7 +68,6 @@ public: int frame_id_last; int idx_offset; bool skipped; - int camera_id; CameraBuf buf; MemoryManager mm; diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 9a70befdcc..9a657982a8 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -77,6 +77,7 @@ float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_r } // namespace AR0231::AR0231() { + image_sensor = cereal::FrameData::ImageSensor::AR0231; data_word = true; frame_width = FRAME_WIDTH; frame_height = FRAME_HEIGHT; diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc index c30838a7fe..2bad50cff9 100644 --- a/system/camerad/sensors/ox03c10.cc +++ b/system/camerad/sensors/ox03c10.cc @@ -22,6 +22,7 @@ const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 } // namespace OX03C10::OX03C10() { + image_sensor = cereal::FrameData::ImageSensor::OX03C10; data_word = false; frame_width = FRAME_WIDTH; frame_height = FRAME_HEIGHT; diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h index 1e45778f20..06d3a43a21 100644 --- a/system/camerad/sensors/sensor.h +++ b/system/camerad/sensors/sensor.h @@ -23,6 +23,7 @@ public: virtual int getSlaveAddress(int port) const { assert(0); } virtual void processRegisters(CameraState *c, cereal::FrameData::Builder &framed) const {} + cereal::FrameData::ImageSensor image_sensor = cereal::FrameData::ImageSensor::UNKNOWN; uint32_t frame_width, frame_height; uint32_t frame_stride; uint32_t frame_offset = 0; From bb9dda976405d62ceb9e366011dbf57c665bfc42 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 8 Dec 2023 16:31:30 -0800 Subject: [PATCH 168/724] simulator: set valid flags (#30656) sim set valid --- tools/sim/lib/camerad.py | 2 +- tools/sim/lib/simulated_sensors.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/sim/lib/camerad.py b/tools/sim/lib/camerad.py index 4621347787..cc6f0cd664 100644 --- a/tools/sim/lib/camerad.py +++ b/tools/sim/lib/camerad.py @@ -59,7 +59,7 @@ class Camerad: eof = int(frame_id * 0.05 * 1e9) self.vipc_server.send(yuv_type, yuv, frame_id, eof, eof) - dat = messaging.new_message(pub_type) + dat = messaging.new_message(pub_type, valid=True) msg = { "frameId": frame_id, "transform": [1.0, 0.0, 0.0, diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index 4520ed35ae..764f99f4d5 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -23,7 +23,7 @@ class SimulatedSensors: def send_imu_message(self, simulator_state: 'SimulatorState'): for _ in range(5): - dat = messaging.new_message('accelerometer') + dat = messaging.new_message('accelerometer', valid=True) dat.accelerometer.sensor = 4 dat.accelerometer.type = 0x10 dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp @@ -32,7 +32,7 @@ class SimulatedSensors: self.pm.send('accelerometer', dat) # copied these numbers from locationd - dat = messaging.new_message('gyroscope') + dat = messaging.new_message('gyroscope', valid=True) dat.gyroscope.sensor = 5 dat.gyroscope.type = 0x10 dat.gyroscope.timestamp = dat.logMonoTime # TODO: use the IMU timestamp @@ -53,7 +53,7 @@ class SimulatedSensors: ] for _ in range(10): - dat = messaging.new_message('gpsLocationExternal') + dat = messaging.new_message('gpsLocationExternal', valid=True) dat.gpsLocationExternal = { "unixTimestampMillis": int(time.time() * 1000), "flags": 1, # valid fix @@ -94,7 +94,7 @@ class SimulatedSensors: self.pm.send('driverStateV2', dat) # dmonitoringd output - dat = messaging.new_message('driverMonitoringState') + dat = messaging.new_message('driverMonitoringState', valid=True) dat.driverMonitoringState = { "faceDetected": True, "isDistracted": False, From 371c1366d435140a1507b6f2bfdd57820515a373 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 8 Dec 2023 16:34:23 -0800 Subject: [PATCH 169/724] bump cereal --- cereal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cereal b/cereal index 4fb8352484..a3a6e4969e 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 4fb8352484c4ef074bee775975c0d1d1e8f57a78 +Subproject commit a3a6e4969e58875f7cdfc9e6cc6b1af3ee2392b5 From e909f634f5b30ab2db5626e88a8637a0eda807ba Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 8 Dec 2023 16:44:07 -0800 Subject: [PATCH 170/724] Bump submodules (#30655) bump submodules Co-authored-by: sshane --- teleoprtc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teleoprtc_repo b/teleoprtc_repo index 8ec4778685..4df237c512 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit 8ec477868591eed9a6136a44f16428bc0468b4e9 +Subproject commit 4df237c512622d3914a73f4fc15a3fc89b7f5a97 From bc70c94f75f64c31998b9796f20ec6e310cae347 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 8 Dec 2023 17:59:31 -0800 Subject: [PATCH 171/724] soundd/micd: more robust setup procedure (#30640) * just use the default device :/ * Ruff * this seems like the most reliable way * Ruff * move comments too * wait for devices * fix that * and that * ruff * add logging for avaliable devices --------- Co-authored-by: Comma Device --- selfdrive/ui/soundd.py | 15 ++++++--------- system/micd.py | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index d64fb0010c..9d6d24a594 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -10,9 +10,9 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.filter_simple import FirstOrderFilter from openpilot.system import micd +from openpilot.system.hardware import TICI from openpilot.common.realtime import Ratekeeper -from openpilot.system.hardware import PC from openpilot.common.swaglog import cloudlog SAMPLE_RATE = 48000 @@ -130,16 +130,13 @@ class Soundd: # sounddevice must be imported after forking processes import sounddevice as sd - rk = Ratekeeper(20) + if TICI: + micd.wait_for_devices(sd) # wait for alsa to be initialized on device - sm = messaging.SubMaster(['controlsState', 'microphone']) + with sd.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: + rk = Ratekeeper(20) + sm = messaging.SubMaster(['controlsState', 'microphone']) - if PC: - device = None - else: - device = "sdm845-tavil-snd-card: - (hw:0,0)" - - with sd.OutputStream(device=device, channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: cloudlog.info(f"soundd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") while True: sm.update(0) diff --git a/system/micd.py b/system/micd.py index 88fb08d684..f4085becd4 100755 --- a/system/micd.py +++ b/system/micd.py @@ -3,7 +3,9 @@ import numpy as np from cereal import messaging from openpilot.common.realtime import Ratekeeper +from openpilot.common.retry import retry from openpilot.common.swaglog import cloudlog +from openpilot.system.hardware import TICI RATE = 10 FFT_SAMPLES = 4096 @@ -11,6 +13,18 @@ REFERENCE_SPL = 2e-5 # newtons/m^2 SAMPLE_RATE = 44100 +@retry(attempts=7, delay=3) +def wait_for_devices(sd): + # reload sounddevice to reinitialize portaudio + sd._terminate() + sd._initialize() + + devices = sd.query_devices() + cloudlog.info(f"sounddevice available devices: {list(devices)}") + + assert len(devices) > 0 + + def calculate_spl(measurements): # https://www.engineeringtoolbox.com/sound-pressure-d_711.html sound_pressure = np.sqrt(np.mean(measurements ** 2)) # RMS of amplitudes @@ -81,7 +95,10 @@ class Mic: # sounddevice must be imported after forking processes import sounddevice as sd - with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: + if TICI: + wait_for_devices(sd) # wait for alsa to be initialized on device + + with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") while True: self.update() From 17d71d2829c8f61b68a6b05b4a5796662185ab7f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 8 Dec 2023 23:40:15 -0600 Subject: [PATCH 172/724] test_athena: remove no-op line (#30660) * test * Update selfdrive/athena/tests/test_athenad.py --- selfdrive/athena/tests/test_athenad.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 8829ebfbf1..beffa0d232 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -46,8 +46,6 @@ class TestAthenadMethods(unittest.TestCase): else: os.unlink(p) - dispatcher["listUploadQueue"]() # ensure queue is empty at start - # *** test helpers *** @staticmethod From e78b80c8fe4f1cf6e6350c9f613ba8c9a43e9d57 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 9 Dec 2023 00:15:27 -0600 Subject: [PATCH 173/724] jenkins: run all car tests with one command (#30661) * run all jenkins car tests in one script, less down time? * fix * speed up * u --- Jenkinsfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 840dd29503..bd894f53e0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -234,9 +234,8 @@ node { 'car tests': { pcStage("car tests") { sh label: "build", script: "selfdrive/manager/build.py" - sh label: "test_models.py", script: "INTERNAL_SEG_CNT=250 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \ - pytest selfdrive/car/tests/test_models.py" - sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest selfdrive/car/tests/test_car_interfaces.py" + sh label: "run car tests", script: "cd selfdrive/car/tests && MAX_EXAMPLES=100 INTERNAL_SEG_CNT=250 FILEREADER_CACHE=1 \ + INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt pytest test_models.py test_car_interfaces.py" } }, From 90c2aee6c36d6f37bc580804d98613e9918a9e33 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 9 Dec 2023 06:53:15 -0600 Subject: [PATCH 174/724] athenad: use `socket` constant (#30663) Update athenad.py --- selfdrive/athena/athenad.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 936797a89e..bdca04c457 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -41,9 +41,6 @@ from openpilot.system.version import get_commit, get_origin, get_short_branch, g from openpilot.system.hardware.hw import Paths -# TODO: use socket constant when mypy recognizes this as a valid attribute -TCP_USER_TIMEOUT = 18 - ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) LOCAL_PORT_WHITELIST = {8022} @@ -760,7 +757,7 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None: if onroad != onroad_prev: onroad_prev = onroad - sock.setsockopt(socket.IPPROTO_TCP, TCP_USER_TIMEOUT, 16000 if onroad else 0) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, 16000 if onroad else 0) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7 if onroad else 30) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3) From 2afff9a0cba605d5118c6cb33c157017a3999957 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 9 Dec 2023 07:06:30 -0600 Subject: [PATCH 175/724] athena tests: patch instead of juggling function (#30664) patch instead of juggling function --- selfdrive/athena/tests/test_athenad_ping.py | 25 +++++++-------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/selfdrive/athena/tests/test_athenad_ping.py b/selfdrive/athena/tests/test_athenad_ping.py index 2958ec2262..5231b0475f 100755 --- a/selfdrive/athena/tests/test_athenad_ping.py +++ b/selfdrive/athena/tests/test_athenad_ping.py @@ -3,8 +3,8 @@ import subprocess import threading import time import unittest -from typing import Callable, cast, Optional -from unittest.mock import MagicMock +from typing import cast, Optional +from unittest import mock from openpilot.common.params import Params from openpilot.common.timeout import Timeout @@ -27,8 +27,6 @@ class TestAthenadPing(unittest.TestCase): athenad: threading.Thread exit_event: threading.Event - _create_connection: Callable - def _get_ping_time(self) -> Optional[str]: return cast(Optional[str], self.params.get("LastAthenaPingTime", encoding="utf-8")) @@ -38,15 +36,9 @@ class TestAthenadPing(unittest.TestCase): def _received_ping(self) -> bool: return self._get_ping_time() is not None - @classmethod - def setUpClass(cls) -> None: - cls._create_connection = athenad.create_connection - athenad.create_connection = MagicMock(wraps=cls._create_connection) - @classmethod def tearDownClass(cls) -> None: wifi_radio(True) - athenad.create_connection = cls._create_connection def setUp(self) -> None: self.params = Params() @@ -58,19 +50,18 @@ class TestAthenadPing(unittest.TestCase): self.exit_event = threading.Event() self.athenad = threading.Thread(target=athenad.main, args=(self.exit_event,)) - athenad.create_connection.reset_mock() - def tearDown(self) -> None: if self.athenad.is_alive(): self.exit_event.set() self.athenad.join() - def assertTimeout(self, reconnect_time: float) -> None: + @mock.patch('openpilot.selfdrive.athena.athenad.create_connection', autospec=True) + def assertTimeout(self, reconnect_time: float, mock_create_connection: mock.MagicMock) -> None: self.athenad.start() time.sleep(1) - athenad.create_connection.assert_called_once() - athenad.create_connection.reset_mock() + mock_create_connection.assert_called_once() + mock_create_connection.reset_mock() # check normal behaviour with self.subTest("Wi-Fi: receives ping"), Timeout(70, "no ping received"): @@ -78,7 +69,7 @@ class TestAthenadPing(unittest.TestCase): time.sleep(0.1) print("ping received") - athenad.create_connection.assert_not_called() + mock_create_connection.assert_not_called() # websocket should attempt reconnect after short time with self.subTest("LTE: attempt reconnect"): @@ -86,7 +77,7 @@ class TestAthenadPing(unittest.TestCase): print("waiting for reconnect attempt") start_time = time.monotonic() with Timeout(reconnect_time, "no reconnect attempt"): - while not athenad.create_connection.called: + while not mock_create_connection.called: time.sleep(0.1) print(f"reconnect attempt after {time.monotonic() - start_time:.2f}s") From 8a530621b482d57aaf6797d9169abb055302f50d Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 10 Dec 2023 00:10:53 +0800 Subject: [PATCH 176/724] camerad: Remove deprecated 'fps' parameter (#30662) remove parameter fps --- system/camerad/cameras/camera_qcom2.cc | 8 ++++---- system/camerad/cameras/camera_qcom2.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 194f11fe6d..7f2118e7e4 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -426,7 +426,7 @@ void CameraState::camera_map_bufs(MultiCameraState *s) { enqueue_req_multi(1, FRAME_BUF_COUNT, 0); } -void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type) { +void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type) { if (!enabled) return; LOGD("camera init %d", camera_num); @@ -618,9 +618,9 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num } void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { - s->driver_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_DRIVER); - s->road_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_ROAD); - s->wide_road_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_WIDE_ROAD); + s->driver_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_DRIVER); + s->road_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_ROAD); + s->wide_road_cam.camera_init(s, v, device_id, ctx, VISION_STREAM_WIDE_ROAD); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"}); } diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 3da5ec207d..d61c087f7c 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -50,7 +50,7 @@ public: void camera_open(MultiCameraState *multi_cam_state, int camera_num, bool enabled); void sensor_set_parameters(); void camera_map_bufs(MultiCameraState *s); - void camera_init(MultiCameraState *s, VisionIpcServer *v, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); + void camera_init(MultiCameraState *s, VisionIpcServer *v, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); void camera_close(); int32_t session_handle; From aa744e84373ecacbbb45382e1a0e4cdd3d5c05fb Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 10 Dec 2023 00:12:11 +0800 Subject: [PATCH 177/724] cabana: cleanp code (#30666) --- tools/cabana/dbc/dbc.h | 4 +--- tools/cabana/dbc/dbcfile.cc | 9 ++------- tools/cabana/dbc/dbcfile.h | 6 ++---- tools/cabana/dbc/dbcmanager.cc | 25 ------------------------- tools/cabana/dbc/dbcmanager.h | 7 +------ tools/cabana/mainwin.cc | 26 +++++++++----------------- tools/cabana/mainwin.h | 1 - tools/cabana/tests/test_cabana.cc | 4 +--- tools/cabana/util.cc | 1 - 9 files changed, 16 insertions(+), 67 deletions(-) diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index e07903e680..0262a546f6 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -1,12 +1,10 @@ #pragma once #include -#include #include #include #include -#include #include #include @@ -47,7 +45,7 @@ struct std::hash { std::size_t operator()(const MessageId &k) const noexcept { return qHash(k); } }; -typedef QList> ValueDescription; +typedef std::vector> ValueDescription; namespace cabana { diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index a22d979212..fbbb54fd58 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -52,8 +52,7 @@ void DBCFile::cleanupAutoSaveFile() { bool DBCFile::writeContents(const QString &fn) { QFile file(fn); if (file.open(QIODevice::WriteOnly)) { - file.write(generateDBC().toUtf8()); - return true; + return file.write(generateDBC().toUtf8()) >= 0; } return false; } @@ -77,10 +76,6 @@ cabana::Msg *DBCFile::msg(const QString &name) { return it != msgs.end() ? &(it->second) : nullptr; } -int DBCFile::signalCount() { - return std::accumulate(msgs.cbegin(), msgs.cend(), 0, [](int &n, const auto &m) { return n + m.second.sigs.size(); }); -} - void DBCFile::parse(const QString &content) { static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))"); static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); @@ -226,7 +221,7 @@ QString DBCFile::generateDBC() { if (!sig->comment.isEmpty()) { signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment); } - if (!sig->val_desc.isEmpty()) { + if (!sig->val_desc.empty()) { QStringList text; for (auto &[val, desc] : sig->val_desc) { text << QString("%1 \"%2\"").arg(val).arg(desc); diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h index ade6b249e2..551bac4946 100644 --- a/tools/cabana/dbc/dbcfile.h +++ b/tools/cabana/dbc/dbcfile.h @@ -27,10 +27,8 @@ public: cabana::Msg *msg(const QString &name); inline cabana::Msg *msg(const MessageId &id) { return msg(id.address); } - int signalCount(); - inline int msgCount() { return msgs.size(); } - inline QString name() { return name_.isEmpty() ? "untitled" : name_; } - inline bool isEmpty() { return (signalCount() == 0) && name_.isEmpty(); } + inline QString name() const { return name_.isEmpty() ? "untitled" : name_; } + inline bool isEmpty() const { return msgs.empty() && name_.isEmpty(); } QString filename; diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 87a7d962c5..250ec225b5 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -106,12 +106,6 @@ QString DBCManager::newSignalName(const MessageId &id) { return m ? m->newSignalName() : ""; } -const std::vector &DBCManager::mask(const MessageId &id) { - static std::vector empty_mask; - auto m = msg(id); - return m ? m->mask : empty_mask; -} - const std::map &DBCManager::getMessages(uint8_t source) { static std::map empty_msgs; auto dbc_file = findDBCFile(source); @@ -143,25 +137,6 @@ QStringList DBCManager::signalNames() { return ret; } -int DBCManager::signalCount(const MessageId &id) { - auto m = msg(id); - return m ? m->sigs.size() : 0; -} - -int DBCManager::signalCount() { - auto files = allDBCFiles(); - return std::accumulate(files.cbegin(), files.cend(), 0, [](int &n, auto &f) { return n + f->signalCount(); }); -} - -int DBCManager::msgCount() { - auto files = allDBCFiles(); - return std::accumulate(files.cbegin(), files.cend(), 0, [](int &n, auto &f) { return n + f->msgCount(); }); -} - -int DBCManager::dbcCount() { - return allDBCFiles().size(); -} - int DBCManager::nonEmptyDBCCount() { auto files = allDBCFiles(); return std::count_if(files.cbegin(), files.cend(), [](auto &f) { return !f->isEmpty(); }); diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 53a77a2c13..5f183752d2 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -4,7 +4,6 @@ #include #include #include -#include #include "tools/cabana/dbc/dbcfile.h" @@ -34,17 +33,13 @@ public: QString newMsgName(const MessageId &id); QString newSignalName(const MessageId &id); - const std::vector& mask(const MessageId &id); const std::map &getMessages(uint8_t source); cabana::Msg *msg(const MessageId &id); cabana::Msg* msg(uint8_t source, const QString &name); QStringList signalNames(); - int signalCount(const MessageId &id); - int signalCount(); - int msgCount(); - int dbcCount(); + inline int dbcCount() { return allDBCFiles().size(); } int nonEmptyDBCCount(); const SourceSet sources(const DBCFile *dbc_file) const; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index f7f2fba45f..410f3a65f4 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -46,7 +46,6 @@ MainWindow::MainWindow() : QMainWindow() { static auto static_main_win = this; qRegisterMetaType("uint64_t"); qRegisterMetaType("SourceSet"); - qRegisterMetaType("ReplyMsgType"); installDownloadProgressHandler([](uint64_t cur, uint64_t total, bool success) { emit static_main_win->updateProgressBar(cur, total, success); }); @@ -54,9 +53,7 @@ MainWindow::MainWindow() : QMainWindow() { if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl; emit static_main_win->showMessage(msg, 2000); }); - installMessageHandler([](ReplyMsgType type, const std::string msg) { - qInfo() << QString::fromStdString(msg); - }); + installMessageHandler([](ReplyMsgType type, const std::string msg) { qInfo() << msg.c_str(); }); setStyleSheet(QString(R"(QMainWindow::separator { width: %1px; /* when vertical */ @@ -329,7 +326,7 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) { QString dbc_str = QGuiApplication::clipboard()->text(); QString error; bool ret = dbc()->open(s, "", dbc_str, &error); - if (ret && dbc()->msgCount() > 0) { + if (ret && dbc()->nonEmptyDBCCount() > 0) { QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!")); } else { QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC from clipboard"), tr("Make sure that you paste the text with correct format.")); @@ -356,7 +353,7 @@ void MainWindow::streamStarted() { video_splitter->setSizes({1, 1}); } // Don't overwrite already loaded DBC - if (!dbc()->msgCount()) { + if (!dbc()->nonEmptyDBCCount()) { newFile(); } @@ -371,7 +368,7 @@ void MainWindow::eventsMerged() { .arg(can->routeName()) .arg(car_fingerprint.isEmpty() ? tr("Unknown Car") : car_fingerprint)); // Don't overwrite already loaded DBC - if (!dbc()->msgCount() && !car_fingerprint.isEmpty()) { + if (!dbc()->nonEmptyDBCCount() && !car_fingerprint.isEmpty()) { auto dbc_name = fingerprint_to_dbc[car_fingerprint]; if (dbc_name != QJsonValue::Undefined) { // Prevent dialog that load autosaved file from blocking replay->start(). @@ -529,7 +526,6 @@ void MainWindow::updateRecentFiles(const QString &fn) { void MainWindow::updateRecentFileActions() { int num_recent_files = std::min(settings.recent_files.size(), MAX_RECENT_FILES); - for (int i = 0; i < num_recent_files; ++i) { QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(settings.recent_files[i]).fileName()); recent_files_acts[i]->setText(text); @@ -543,15 +539,11 @@ void MainWindow::updateRecentFileActions() { } void MainWindow::remindSaveChanges() { - bool discard_changes = false; - while (!UndoStack::instance()->isClean() && !discard_changes) { + while (!UndoStack::instance()->isClean()) { QString text = tr("You have unsaved changes. Press ok to save them, cancel to discard."); - int ret = (QMessageBox::question(this, tr("Unsaved Changes"), text, QMessageBox::Ok | QMessageBox::Cancel)); - if (ret == QMessageBox::Ok) { - save(); - } else { - discard_changes = true; - } + int ret = QMessageBox::question(this, tr("Unsaved Changes"), text, QMessageBox::Ok | QMessageBox::Cancel); + if (ret != QMessageBox::Ok) break; + save(); } UndoStack::instance()->clear(); } @@ -660,7 +652,7 @@ HelpOverlay::HelpOverlay(MainWindow *parent) : QWidget(parent) { void HelpOverlay::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.fillRect(rect(), QColor(0, 0, 0, 50)); - MainWindow *parent = (MainWindow *)parentWidget(); + auto parent = parentWidget(); drawHelpForWidget(painter, parent->findChild()); drawHelpForWidget(painter, parent->findChild()); drawHelpForWidget(painter, parent->findChild()); diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 1a889b88c2..ea0bad5ea4 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -101,7 +101,6 @@ protected: int prev_undostack_index = 0; int prev_undostack_count = 0; QByteArray default_state; - friend class OnlineHelp; }; class HelpOverlay : public QWidget { diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index c7dc91e7aa..bf7550be0b 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -3,9 +3,7 @@ #include #include "catch2/catch.hpp" -#include "tools/replay/logreader.h" #include "tools/cabana/dbc/dbcmanager.h" -#include "tools/cabana/streams/abstractstream.h" const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; @@ -14,7 +12,7 @@ TEST_CASE("DBCFile::generateDBC") { DBCFile dbc_origin(fn); DBCFile dbc_from_generated("", dbc_origin.generateDBC()); - REQUIRE(dbc_origin.msgCount() == dbc_from_generated.msgCount()); + REQUIRE(dbc_origin.getMessages().size() == dbc_from_generated.getMessages().size()); auto &msgs = dbc_origin.getMessages(); auto &new_msgs = dbc_from_generated.getMessages(); for (auto &[id, m] : msgs) { diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc index f984230c47..a17961243f 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/util.cc @@ -12,7 +12,6 @@ #include #include #include -#include #include #include "selfdrive/ui/qt/util.h" From 97da129e11e58b07d5d136093827cd1c62311fea Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 9 Dec 2023 13:24:18 -0800 Subject: [PATCH 178/724] ruff: check newline (#30668) * check newline * nothing catches this --- common/basedir.py | 2 +- common/kalman/simple_kalman.py | 2 +- common/time.py | 2 +- conftest.py | 8 ++++---- pyproject.toml | 4 ++-- selfdrive/boardd/tests/test_pandad.py | 2 +- selfdrive/debug/auto_fingerprint.py | 2 +- selfdrive/test/cpp_harness.py | 2 +- selfdrive/test/helpers.py | 2 +- selfdrive/test/process_replay/conftest.py | 2 +- selfdrive/test/process_replay/helpers.py | 2 +- selfdrive/test/process_replay/test_processes.py | 2 +- selfdrive/ui/soundd.py | 2 +- system/hardware/hw.py | 2 +- tools/sim/bridge/carla/carla_bridge.py | 2 +- tools/sim/bridge/metadrive/metadrive_process.py | 2 +- tools/sim/lib/camerad.py | 2 +- tools/sim/lib/common.py | 2 +- tools/sim/lib/simulated_car.py | 2 +- tools/sim/lib/simulated_sensors.py | 2 +- 20 files changed, 24 insertions(+), 24 deletions(-) diff --git a/common/basedir.py b/common/basedir.py index c840b86f7f..6b4811e53c 100644 --- a/common/basedir.py +++ b/common/basedir.py @@ -1,4 +1,4 @@ import os -BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) \ No newline at end of file +BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) diff --git a/common/kalman/simple_kalman.py b/common/kalman/simple_kalman.py index 5e1b6ce1fb..cd3b5a1df9 100644 --- a/common/kalman/simple_kalman.py +++ b/common/kalman/simple_kalman.py @@ -9,4 +9,4 @@ def get_kalman_gain(dt, A, C, Q, R, iterations=100): S = C.dot(P).dot(C.T) + R K = P.dot(C.T).dot(np.linalg.inv(S)) P = (np.eye(len(P)) - K.dot(C)).dot(P) - return K \ No newline at end of file + return K diff --git a/common/time.py b/common/time.py index b9da106fd0..c8ef9cd383 100644 --- a/common/time.py +++ b/common/time.py @@ -3,4 +3,4 @@ import datetime MIN_DATE = datetime.datetime(year=2023, month=6, day=1) def system_time_valid(): - return datetime.datetime.now() > MIN_DATE \ No newline at end of file + return datetime.datetime.now() > MIN_DATE diff --git a/conftest.py b/conftest.py index d1787c24ef..285d8b0238 100644 --- a/conftest.py +++ b/conftest.py @@ -52,7 +52,7 @@ def pytest_collection_modifyitems(config, items): @pytest.hookimpl(trylast=True) def pytest_configure(config): - config_line = ( - "xdist_group_class_property: group tests by a property of the class that contains them" - ) - config.addinivalue_line("markers", config_line) \ No newline at end of file + config_line = ( + "xdist_group_class_property: group tests by a property of the class that contains them" + ) + config.addinivalue_line("markers", config_line) diff --git a/pyproject.toml b/pyproject.toml index ffbd5d43b7..0116cb4c5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -181,7 +181,7 @@ build-backend = "poetry.core.masonry.api" # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"] -ignore = ["W292", "E741", "E402", "C408", "ISC003", "B027", "B024"] +ignore = ["E741", "E402", "C408", "ISC003", "B027", "B024"] line-length = 160 target-version="py311" exclude = [ @@ -202,4 +202,4 @@ flake8-implicit-str-concat.allow-multiline=false "tools".msg = "Use openpilot.tools" [tool.coverage.run] -concurrency = ["multiprocessing", "thread"] \ No newline at end of file +concurrency = ["multiprocessing", "thread"] diff --git a/selfdrive/boardd/tests/test_pandad.py b/selfdrive/boardd/tests/test_pandad.py index 4036766faa..21cc0b5f37 100755 --- a/selfdrive/boardd/tests/test_pandad.py +++ b/selfdrive/boardd/tests/test_pandad.py @@ -116,4 +116,4 @@ class TestPandad(unittest.TestCase): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/selfdrive/debug/auto_fingerprint.py b/selfdrive/debug/auto_fingerprint.py index 78b109b0bd..10e438e546 100755 --- a/selfdrive/debug/auto_fingerprint.py +++ b/selfdrive/debug/auto_fingerprint.py @@ -102,4 +102,4 @@ if __name__ == "__main__": if not new_fw_versions: print("No new fw versions found...") - add_fw_versions(brand, platform, new_fw_versions) \ No newline at end of file + add_fw_versions(brand, platform, new_fw_versions) diff --git a/selfdrive/test/cpp_harness.py b/selfdrive/test/cpp_harness.py index a16b5707f5..f9d3e681a5 100755 --- a/selfdrive/test/cpp_harness.py +++ b/selfdrive/test/cpp_harness.py @@ -8,4 +8,4 @@ from openpilot.common.prefix import OpenpilotPrefix with OpenpilotPrefix(): ret = subprocess.call(sys.argv[1:]) -exit(ret) \ No newline at end of file +exit(ret) diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 552070f024..78b01c983f 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -71,4 +71,4 @@ def with_processes(processes, init_time=0, ignore_stopped=None): def noop(*args, **kwargs): - pass \ No newline at end of file + pass diff --git a/selfdrive/test/process_replay/conftest.py b/selfdrive/test/process_replay/conftest.py index 3f9744ed61..a78f37f392 100644 --- a/selfdrive/test/process_replay/conftest.py +++ b/selfdrive/test/process_replay/conftest.py @@ -34,4 +34,4 @@ def process_replay_test_arguments(request): request.cls.ignore_msgs = request.config.getoption("--ignore-msgs") request.cls.upload_only = request.config.getoption("--upload-only") request.cls.update_refs = request.config.getoption("--update-refs") - request.cls.long_diff = request.config.getoption("--long-diff") \ No newline at end of file + request.cls.long_diff = request.config.getoption("--long-diff") diff --git a/selfdrive/test/process_replay/helpers.py b/selfdrive/test/process_replay/helpers.py index 0952a01870..8e77454dae 100755 --- a/selfdrive/test/process_replay/helpers.py +++ b/selfdrive/test/process_replay/helpers.py @@ -147,4 +147,4 @@ class TestProcessReplayDiffBase(TestProcessReplayBase): diff_short, diff_long = format_process_diff(diff) - self.assertEqual(len(diff), 0, "\n" + diff_long if self.long_diff else diff_short) \ No newline at end of file + self.assertEqual(len(diff), 0, "\n" + diff_long if self.long_diff else diff_short) diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index eb01e50e33..1517d9bab0 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -96,4 +96,4 @@ class TestCarProcessReplay(TestProcessReplayDiffBase): if __name__ == '__main__': - pytest.main([*sys.argv[1:], __file__]) \ No newline at end of file + pytest.main([*sys.argv[1:], __file__]) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 9d6d24a594..dc1b7061e1 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -158,4 +158,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/system/hardware/hw.py b/system/hardware/hw.py index 6c3d90345c..a8967520e3 100644 --- a/system/hardware/hw.py +++ b/system/hardware/hw.py @@ -53,4 +53,4 @@ class Paths: if PC: return Paths.comma_home() else: - return "/tmp/.comma" \ No newline at end of file + return "/tmp/.comma" diff --git a/tools/sim/bridge/carla/carla_bridge.py b/tools/sim/bridge/carla/carla_bridge.py index dc2377df30..fe7b34802f 100644 --- a/tools/sim/bridge/carla/carla_bridge.py +++ b/tools/sim/bridge/carla/carla_bridge.py @@ -19,4 +19,4 @@ class CarlaBridge(SimulatorBridge): client.set_timeout(5) return CarlaWorld(client, high_quality=self.high_quality, dual_camera=self.dual_camera, - num_selected_spawn_point=self.num_selected_spawn_point, town=self.town) \ No newline at end of file + num_selected_spawn_point=self.num_selected_spawn_point, town=self.town) diff --git a/tools/sim/bridge/metadrive/metadrive_process.py b/tools/sim/bridge/metadrive/metadrive_process.py index 3d21c0e508..3ce89d7d41 100644 --- a/tools/sim/bridge/metadrive/metadrive_process.py +++ b/tools/sim/bridge/metadrive/metadrive_process.py @@ -96,4 +96,4 @@ def metadrive_process(dual_camera: bool, config: dict, camera_array, controls_re # wide_road_image = get_cam_as_rgb("rgb_wide") road_image[...] = get_cam_as_rgb("rgb_road") - rk.keep_time() \ No newline at end of file + rk.keep_time() diff --git a/tools/sim/lib/camerad.py b/tools/sim/lib/camerad.py index cc6f0cd664..95769dd44e 100644 --- a/tools/sim/lib/camerad.py +++ b/tools/sim/lib/camerad.py @@ -67,4 +67,4 @@ class Camerad: 0.0, 0.0, 1.0] } setattr(dat, pub_type, msg) - self.pm.send(pub_type, dat) \ No newline at end of file + self.pm.send(pub_type, dat) diff --git a/tools/sim/lib/common.py b/tools/sim/lib/common.py index b7aa66f5c5..29c5858d33 100644 --- a/tools/sim/lib/common.py +++ b/tools/sim/lib/common.py @@ -91,4 +91,4 @@ class World(ABC): @abstractmethod def reset(self): - pass \ No newline at end of file + pass diff --git a/tools/sim/lib/simulated_car.py b/tools/sim/lib/simulated_car.py index ab60fad8d3..0740f9e3aa 100644 --- a/tools/sim/lib/simulated_car.py +++ b/tools/sim/lib/simulated_car.py @@ -116,4 +116,4 @@ class SimulatedCar: if self.idx % 50 == 0: # only send panda states at 2hz self.send_panda_state(simulator_state) - self.idx += 1 \ No newline at end of file + self.idx += 1 diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index 764f99f4d5..faa8c0691b 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -122,4 +122,4 @@ class SimulatedSensors: if (now - self.last_perp_update) > 0.25: self.send_peripheral_state() - self.last_perp_update = now \ No newline at end of file + self.last_perp_update = now From ac83318ac4f54c46deef02c69d46c549bd741cb3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 9 Dec 2023 14:28:32 -0800 Subject: [PATCH 179/724] test_models: randomize internal segment list (#30653) * randomize internal seg list segments * random * pytest-randomly sets random.seed to a consistent value for all the workers/processes * noeol * update * Revert "update" This reverts commit aff9a69c4e5e3934deebaa33986b42f44b55b002. * lock * don't randomize by default * remove random-order * strict * random * one fix * test * does nothing * rm tests * Revert "rm tests" This reverts commit b548e3fcd48e60538695506888d863c01b459d27. * (can't repro locally) just athena should be fine * bs1 * bs2 * bs3 * bs4 * bs5 * wrong way * no controls * no car * no board * controls? * crazy -common * Revert "crazy -common" This reverts commit 02365d712b3d09cab1893cce2261a4b418bb3851. * test athena * test athena 2 * test athena 3 * test athena 4 * test athena 5 * test athena 6 * test athena 7 * test athena 8 * test athena 9 * ?? * in one commit * common? * car and board * -controls -board * random-order * no board * revert * car/tests * least likely * try * try 2 * draft * draft * so much better * cmt * use randomly * not needed here * directly modify option works * bb * test time * Revert "test time" This reverts commit 2c5caabe2b470b47b7322e37800680b92773fccc. * tmut * i concur * revert --- conftest.py | 6 ++++++ poetry.lock | 18 ++++++++---------- pyproject.toml | 2 +- selfdrive/car/tests/test_models.py | 11 +++++------ 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/conftest.py b/conftest.py index 285d8b0238..3c566e3672 100644 --- a/conftest.py +++ b/conftest.py @@ -6,6 +6,12 @@ from openpilot.common.prefix import OpenpilotPrefix from openpilot.system.hardware import TICI +def pytest_sessionstart(session): + # TODO: fix tests and enable test order randomization + if session.config.pluginmanager.hasplugin('randomly'): + session.config.option.randomly_reorganize = False + + @pytest.fixture(scope="function", autouse=True) def openpilot_function_fixture(): starting_env = dict(os.environ) diff --git a/poetry.lock b/poetry.lock index 1461d5457c..1ec3847b18 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3309,8 +3309,6 @@ files = [ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, - {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"}, - {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, @@ -3719,18 +3717,18 @@ colorama = "*" pytest = ">=7.0" [[package]] -name = "pytest-random-order" -version = "1.1.0" -description = "Randomise the order in which pytest tests are run with some control over the randomness" +name = "pytest-randomly" +version = "3.15.0" +description = "Pytest plugin to randomly order tests and control random.seed." optional = false -python-versions = ">=3.5.0" +python-versions = ">=3.8" files = [ - {file = "pytest-random-order-1.1.0.tar.gz", hash = "sha256:dbe6debb9353a7af984cc9eddbeb3577dd4dbbcc1529a79e3d21f68ed9b45605"}, - {file = "pytest_random_order-1.1.0-py3-none-any.whl", hash = "sha256:6cb1e59ab0f798bb0c3488c11ae0c70d7d3340306a466d28b28ccd8ef8c20b7e"}, + {file = "pytest_randomly-3.15.0-py3-none-any.whl", hash = "sha256:0516f4344b29f4e9cdae8bce31c4aeebf59d0b9ef05927c33354ff3859eeeca6"}, + {file = "pytest_randomly-3.15.0.tar.gz", hash = "sha256:b908529648667ba5e54723088edd6f82252f540cc340d748d1fa985539687047"}, ] [package.dependencies] -pytest = ">=3.0.0" +pytest = "*" [[package]] name = "pytest-subtests" @@ -5011,4 +5009,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "f0abc0f53443c3b98eb06fc4dd0ff736e39197bc68c79db9aeffb4736657b676" +content-hash = "7f7244a89280a41937066ebf80aa64fcb375852484c9d034ea7f2de3eae4d90a" diff --git a/pyproject.toml b/pyproject.toml index 0116cb4c5e..c6a13c4b86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -151,7 +151,7 @@ pytest-subtests = "*" pytest-xdist = "*" pytest-timeout = "*" pytest-timeouts = "*" -pytest-random-order = "*" +pytest-randomly = "*" ruff = "*" scipy = "*" sphinx = "*" diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 0ae104f9f5..aaaa6b9d79 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -3,6 +3,7 @@ import capnp import os import importlib import pytest +import random import unittest from collections import defaultdict, Counter from typing import List, Optional, Tuple @@ -49,12 +50,10 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: with open(os.path.join(BASEDIR, INTERNAL_SEG_LIST), "r") as f: seg_list = f.read().splitlines() - cnt = INTERNAL_SEG_CNT or len(seg_list) - seg_list_iter = iter(seg_list[:cnt]) - - for platform in seg_list_iter: - platform = platform[2:] # get rid of comment - segment_name = SegmentName(next(seg_list_iter)) + seg_list_grouped = [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] + seg_list_grouped = random.sample(seg_list_grouped, INTERNAL_SEG_CNT or len(seg_list_grouped)) + for platform, segment in seg_list_grouped: + segment_name = SegmentName(segment) test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform, segment=segment_name.segment_num))) return test_cases From 6d9629b0e11e6d36b71530e7dbd1475acb30e280 Mon Sep 17 00:00:00 2001 From: TerryWloch <153369573+TerryWloch@users.noreply.github.com> Date: Sat, 9 Dec 2023 23:35:15 +0100 Subject: [PATCH 180/724] VW: add missing FW for Skoda Kodiaq 2017 (#30665) * Update values.py Added FingerPrint for my Skoda Kodiaq 2017 14. tsi DSG 4x4 * Apply suggestions from code review --------- Co-authored-by: Shane Smiskol --- selfdrive/car/volkswagen/values.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index c8e6daaef8..38a6efeca8 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1265,6 +1265,7 @@ FW_VERSIONS = { b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890005', b'\xf1\x875NA906259E \xf1\x890003', + b'\xf1\x8704E906027LD\xf1\x893433', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870D9300043 \xf1\x895202', @@ -1275,6 +1276,7 @@ FW_VERSIONS = { b'\xf1\x870GC300014N \xf1\x892801', b'\xf1\x870GC300019H \xf1\x892806', b'\xf1\x870GC300046Q \xf1\x892802', + b'\xf1\x870D9300014S \xf1\x895201', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211', @@ -1284,6 +1286,7 @@ FW_VERSIONS = { b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121240749', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', @@ -1299,6 +1302,7 @@ FW_VERSIONS = { b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572AB\xf1\x890397', + b'\xf1\x872Q0907572M \xf1\x890233', ], }, CAR.SKODA_OCTAVIA_MK3: { From 5051ff0b84845cbbacd31927461eac82fba90142 Mon Sep 17 00:00:00 2001 From: hanyiabc Date: Sat, 9 Dec 2023 17:46:40 -0500 Subject: [PATCH 181/724] Added fingerprint for 22 Kia Stinger KIA_STINGER_2022 (#30589) * added fingerprint for my 22 kia stinger * Apply suggestions from code review --------- Co-authored-by: Shane Smiskol --- selfdrive/car/hyundai/values.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 7c6fa7cb4f..9a62371601 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1140,6 +1140,7 @@ FW_VERSIONS = { b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ', b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5500 ', b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5600 ', + b'\xf1\x00CK__ SCC FHCUP 1.00 1.01 99110-J5100 ', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1149,17 +1150,20 @@ FW_VERSIONS = { (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5300 4C2CL503', + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5320 4C2VL503', b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622', b'\xf1\x00CK MFC AT KOR LHD 1.00 1.00 99211-J5500 210622', b'\xf1\x00CK MFC AT USA LHD 1.00 1.00 99211-J5500 210622', + b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 99211-J5000 201209', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1', b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T25KH2B\xfbI\xe2', + b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00TCK0T33NH0%g~\xd3', ], }, CAR.PALISADE: { From dd034bbfb7db198d07c05d927b60b0ceba453ea4 Mon Sep 17 00:00:00 2001 From: hodgiers Date: Sat, 9 Dec 2023 18:04:27 -0500 Subject: [PATCH 182/724] Volkswagen: Tiguan R-Line 2024 Fingerprint (#30605) * Update values.py 2024 Tiguan R Line * Update values.py Removed extraneous text * already here * Update values.py Updating model year for Volkswagen Tiguan to include 2024. * Update values.py Correcting Tiguan fingerprint values * Apply suggestions from code review * Update selfdrive/car/volkswagen/values.py --------- Co-authored-by: Hodge Co-authored-by: Shane Smiskol --- selfdrive/car/volkswagen/values.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 38a6efeca8..08ee932fe6 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -843,6 +843,7 @@ FW_VERSIONS = { b'\xf1\x8783A907115K \xf1\x890002', b'\xf1\x8704E906024AP\xf1\x891461', b'\xf1\x8783A907115 \xf1\x890007', + b'\xf1\x8783A907115Q \xf1\x890001', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158DT\xf1\x893698', @@ -851,6 +852,7 @@ FW_VERSIONS = { b'\xf1\x8709G927158GD\xf1\x893820', b'\xf1\x8709G927158GM\xf1\x893936', b'\xf1\x8709G927158GN\xf1\x893938', + b'\xf1\x8709G927158HB\xf1\x894069', b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870DL300011N \xf1\x892001', b'\xf1\x870DL300011N \xf1\x892012', From b3908e3c82f53ab998bdbef34a005d0a699c7448 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 9 Dec 2023 15:05:06 -0800 Subject: [PATCH 183/724] Volkswagen: update MY for Tiguan --- docs/CARS.md | 2 +- selfdrive/car/volkswagen/values.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 647b1b15c5..65c93f9deb 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -276,7 +276,7 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 08ee932fe6..6a3baafa35 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -239,7 +239,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022-23"), CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]), CAR.TIGUAN_MK2: [ - VWCarInfo("Volkswagen Tiguan 2018-23"), + VWCarInfo("Volkswagen Tiguan 2018-24"), VWCarInfo("Volkswagen Tiguan eHybrid 2021-23"), ], CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2016-23"), From e03f1282a864278ae6e2671e1996c7121ee5c92a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 9 Dec 2023 17:13:36 -0800 Subject: [PATCH 184/724] bump panda (#30670) --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 80782bcbe4..ff49666893 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 80782bcbe49705810d0859debeff18d86b2cc56e +Subproject commit ff496668930af5f1a649759109732624cb313446 From 63bf098017c89a09f79059647deb90d1960f720d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 9 Dec 2023 20:36:34 -0500 Subject: [PATCH 185/724] Hyundai CAN-FD Longitudinal: Enable for ICE camera-SCC cars (non-HDA2) (#30034) * Hyundai CAN-FD Longitudinal: Enable for all camera-based SCC platforms (non-HDA2) * allow ice camera scc longitudinal * allow ice camera scc longitudinal * Update docs * Merge commaai#30021 * Update CARS.md * revert unrelated changes * add test route --------- Co-authored-by: Shane Smiskol --- docs/CARS.md | 8 ++++---- panda | 2 +- selfdrive/car/hyundai/interface.py | 3 +-- selfdrive/car/tests/routes.py | 1 + 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 65c93f9deb..515dcad4ae 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -99,7 +99,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -108,8 +108,8 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -144,7 +144,7 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/panda b/panda index ff49666893..0b8c477302 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit ff496668930af5f1a649759109732624cb313446 +Subproject commit 0b8c477302af4a10da92f5ba1d7af2a84e2794a7 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 26b3491358..2127fc320f 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -260,8 +260,7 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] - ret.experimentalLongitudinalAvailable = (candidate in (HYBRID_CAR | EV_CAR) and candidate not in - (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR)) + ret.experimentalLongitudinalAvailable = candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR) else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 9802524886..33a7e9407a 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -157,6 +157,7 @@ routes = [ CarTestRoute("db04d2c63990e3ba|2023-02-08--16-52-39", HYUNDAI.KIA_NIRO_HEV_2ND_GEN), CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE), CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN), + CarTestRoute("09559f1fcaed4704|2023-11-16--02-24-57", HYUNDAI.KIA_SPORTAGE_5TH_GEN), # openpilot longitudinal CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA), CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA_GT_I30), CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021), From 86ab1dca18c868e2689931b193041aa1f4ccf3e5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 9 Dec 2023 17:37:48 -0800 Subject: [PATCH 186/724] bump --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 0b8c477302..a6b76dfda0 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 0b8c477302af4a10da92f5ba1d7af2a84e2794a7 +Subproject commit a6b76dfda0f2bcba51d68d56f013a318f9021fb9 From 13e5e708c28b40cdfcb2771e8d6c2d376cd09f8f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 11 Dec 2023 01:18:44 +0800 Subject: [PATCH 187/724] boardd: cleanup includes (#30674) cleanup includes Co-authored-by: Comma Device --- selfdrive/boardd/boardd.cc | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 0ec33c1a27..61e2424744 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -1,11 +1,5 @@ #include "selfdrive/boardd/boardd.h" -#include -#include -#include -#include -#include - #include #include #include @@ -13,10 +7,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include From f09ee083a24e9928f7b43c271bfb9bee1f07e7e4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 10 Dec 2023 11:33:40 -0800 Subject: [PATCH 188/724] helper script for fixing up repo size (#30028) * small * mv --- scripts/build_small.sh | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 scripts/build_small.sh diff --git a/scripts/build_small.sh b/scripts/build_small.sh new file mode 100755 index 0000000000..d53c7a6c78 --- /dev/null +++ b/scripts/build_small.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -ex + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +# git clone --mirror +SRC=/tmp/openpilot.git/ +OUT=/tmp/smallpilot/ + +echo "starting size $(du -hs .git/)" + +rm -rf $OUT + +cd $SRC +git remote update + +# copy contents +#rsync -a --exclude='.git/' $DIR $OUT + +cp -r $SRC $OUT + +cd $OUT + +# remove all tags +git tag -l | xargs git tag -d + +# remove non-master branches +BRANCHES="release2 dashcam dashcam3 release3 devel master-ci nightly" +for branch in $BRANCHES; do + git branch -D $branch + git branch -D ${branch}-staging || true +done + +#git gc +git reflog expire --expire=now --all +git gc --prune=now +git gc --aggressive --prune=now +echo "new one is $(du -hs .)" From 3831bf19e47ed1ee09b2f4eb0341f8e879194880 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 10 Dec 2023 11:45:35 -0800 Subject: [PATCH 189/724] remove Qt wrapper scripts (#30454) * remove Qt wrapper scripts * revert those --- .gitignore | 1 - release/files_common | 1 - selfdrive/test/test_onroad.py | 4 ++-- selfdrive/ui/.gitignore | 1 + selfdrive/ui/SConscript | 2 +- selfdrive/ui/qt/util.cc | 2 ++ selfdrive/ui/ui | 4 ---- 7 files changed, 6 insertions(+), 9 deletions(-) delete mode 100755 selfdrive/ui/ui diff --git a/.gitignore b/.gitignore index e414e4301a..3e91531d08 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,6 @@ selfdrive/boardd/boardd selfdrive/logcatd/logcatd selfdrive/mapd/default_speeds_by_region.json system/proclogd/proclogd -selfdrive/ui/_ui selfdrive/ui/translations/alerts_generated.h selfdrive/ui/translations/tmp selfdrive/test/longitudinal_maneuvers/out diff --git a/release/files_common b/release/files_common index 637f8bd7d7..10bfe4e1f1 100644 --- a/release/files_common +++ b/release/files_common @@ -285,7 +285,6 @@ selfdrive/ui/.gitignore selfdrive/ui/SConscript selfdrive/ui/*.cc selfdrive/ui/*.h -selfdrive/ui/ui selfdrive/ui/text selfdrive/ui/spinner selfdrive/ui/soundd.py diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index b4121529d1..3c8e848fda 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -36,7 +36,7 @@ PROCS = { "./locationd": 11.0, "./mapsd": (1.0, 10.0), "selfdrive.controls.plannerd": 11.0, - "./_ui": 18.0, + "./ui": 18.0, "selfdrive.locationd.paramsd": 9.0, "./sensord": 7.0, "selfdrive.controls.radard": 4.5, @@ -303,7 +303,7 @@ class TestOnroad(unittest.TestCase): self.assertLessEqual(max(mems) - min(mems), 3.0) def test_gpu_usage(self): - self.assertEqual(self.gpu_procs, {"weston", "_ui", "camerad", "selfdrive.modeld.modeld"}) + self.assertEqual(self.gpu_procs, {"weston", "ui", "camerad", "selfdrive.modeld.modeld"}) def test_camera_processing_time(self): result = "\n" diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 8ad5d39493..99f9097905 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -3,6 +3,7 @@ moc_* translations/main_test_en.* +ui watch3 installer/installers/* qt/text diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index f0db1f63a7..b0f5a4f4a6 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -77,7 +77,7 @@ qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs) qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) # build main UI -qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) +qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs) if GetOption('extras'): qt_src.remove("main.cc") # replaced by test_runner qt_env.Program('tests/test_translations', [asset_obj, 'tests/test_runner.cc', 'tests/test_translations.cc'] + qt_src, LIBS=qt_libs) diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 78da183503..c5222b865f 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -112,6 +112,8 @@ void initApp(int argc, char *argv[], bool disable_hidpi) { #endif } + qputenv("QT_DBL_CLICK_DIST", QByteArray::number(150)); + setQtSurfaceFormat(); } diff --git a/selfdrive/ui/ui b/selfdrive/ui/ui deleted file mode 100755 index acb2a705a8..0000000000 --- a/selfdrive/ui/ui +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -cd "$(dirname "$0")" -export QT_DBL_CLICK_DIST=150 -exec ./_ui From 702c4392a448eff18eb64537fdf46e0741558b8b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 10 Dec 2023 12:10:40 -0800 Subject: [PATCH 190/724] three way merge --- scripts/apply-pr.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/apply-pr.sh b/scripts/apply-pr.sh index 65805b8485..74f765391a 100755 --- a/scripts/apply-pr.sh +++ b/scripts/apply-pr.sh @@ -8,4 +8,4 @@ fi BASE="https://github.com/commaai/openpilot/pull/" PR_NUM="$(echo $1 | grep -o -E '[0-9]+')" -curl -L $BASE/$PR_NUM.patch | git apply +curl -L $BASE/$PR_NUM.patch | git apply -3 From 48cf7eb6d9700d8cdab52c1a28c7e05dc4527574 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 11 Dec 2023 04:12:27 +0800 Subject: [PATCH 191/724] ui/driverview: inherit from CameraWidget (#29769) * refactor DriverViewWidow to inherit from CameraWidget * emit done on visible * merge master * cleanup * lock_guard --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/ui/qt/offroad/driverview.cc | 61 +++++++----------------- selfdrive/ui/qt/offroad/driverview.h | 35 +++----------- selfdrive/ui/translations/main_ar.ts | 2 +- selfdrive/ui/translations/main_de.ts | 2 +- selfdrive/ui/translations/main_fr.ts | 2 +- selfdrive/ui/translations/main_ja.ts | 2 +- selfdrive/ui/translations/main_ko.ts | 2 +- selfdrive/ui/translations/main_pt-BR.ts | 2 +- selfdrive/ui/translations/main_th.ts | 2 +- selfdrive/ui/translations/main_tr.ts | 2 +- selfdrive/ui/translations/main_zh-CHS.ts | 2 +- selfdrive/ui/translations/main_zh-CHT.ts | 2 +- 12 files changed, 34 insertions(+), 82 deletions(-) diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 693a0253b4..df9bb24651 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -3,62 +3,39 @@ #include #include -#include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/util.h" const int FACE_IMG_SIZE = 130; -DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) { - setAttribute(Qt::WA_OpaquePaintEvent); - layout = new QStackedLayout(this); - layout->setStackingMode(QStackedLayout::StackAll); - - cameraView = new CameraWidget("camerad", VISION_STREAM_DRIVER, true, this); - layout->addWidget(cameraView); - - scene = new DriverViewScene(this); - connect(cameraView, &CameraWidget::vipcThreadFrameReceived, scene, &DriverViewScene::frameUpdated); - layout->addWidget(scene); - layout->setCurrentWidget(scene); - - QObject::connect(device(), &Device::interactiveTimeout, this, &DriverViewWindow::closeView); -} - -void DriverViewWindow::closeView() { - if (isVisible()) { - cameraView->stopVipcThread(); - emit done(); - } -} - -void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { - closeView(); -} - -DriverViewScene::DriverViewScene(QWidget* parent) : QWidget(parent) { +DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraWidget("camerad", VISION_STREAM_DRIVER, true, parent) { face_img = loadPixmap("../assets/img_driver_face_static.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); + QObject::connect(this, &CameraWidget::clicked, this, &DriverViewWindow::done); + QObject::connect(device(), &Device::interactiveTimeout, this, [this]() { + if (isVisible()) { + emit done(); + } + }); } -void DriverViewScene::showEvent(QShowEvent* event) { - frame_updated = false; +void DriverViewWindow::showEvent(QShowEvent* event) { params.putBool("IsDriverViewEnabled", true); device()->resetInteractiveTimeout(60); + CameraWidget::showEvent(event); } -void DriverViewScene::hideEvent(QHideEvent* event) { +void DriverViewWindow::hideEvent(QHideEvent* event) { params.putBool("IsDriverViewEnabled", false); + stopVipcThread(); + CameraWidget::hideEvent(event); } -void DriverViewScene::frameUpdated() { - frame_updated = true; - update(); -} +void DriverViewWindow::paintGL() { + CameraWidget::paintGL(); -void DriverViewScene::paintEvent(QPaintEvent* event) { + std::lock_guard lk(frame_lock); QPainter p(this); - // startup msg - if (!frame_updated) { + if (frames.empty()) { p.setPen(Qt::white); p.setRenderHint(QPainter::TextAntialiasing); p.setFont(InterFont(100, QFont::Bold)); @@ -68,10 +45,8 @@ void DriverViewScene::paintEvent(QPaintEvent* event) { const auto &sm = *(uiState()->sm); cereal::DriverStateV2::Reader driver_state = sm["driverStateV2"].getDriverStateV2(); - cereal::DriverStateV2::DriverData::Reader driver_data; - - is_rhd = driver_state.getWheelOnRightProb() > 0.5; - driver_data = is_rhd ? driver_state.getRightDriverData() : driver_state.getLeftDriverData(); + bool is_rhd = driver_state.getWheelOnRightProb() > 0.5; + auto driver_data = is_rhd ? driver_state.getRightDriverData() : driver_state.getLeftDriverData(); bool face_detected = driver_data.getFaceProb() > 0.7; if (face_detected) { diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h index 8bfc7a4b7b..155e4ede32 100644 --- a/selfdrive/ui/qt/offroad/driverview.h +++ b/selfdrive/ui/qt/offroad/driverview.h @@ -1,31 +1,8 @@ #pragma once -#include - #include "selfdrive/ui/qt/widgets/cameraview.h" -class DriverViewScene : public QWidget { - Q_OBJECT - -public: - explicit DriverViewScene(QWidget *parent); - -public slots: - void frameUpdated(); - -protected: - void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; - void paintEvent(QPaintEvent *event) override; - -private: - Params params; - QPixmap face_img; - bool is_rhd = false; - bool frame_updated = false; -}; - -class DriverViewWindow : public QWidget { +class DriverViewWindow : public CameraWidget { Q_OBJECT public: @@ -35,10 +12,10 @@ signals: void done(); protected: - void mouseReleaseEvent(QMouseEvent* e) override; - void closeView(); + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; + void paintGL() override; - CameraWidget *cameraView; - DriverViewScene *scene; - QStackedLayout *layout; + Params params; + QPixmap face_img; }; diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index 4159f28c31..dc771b8c6e 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting بدء تشغيل الكاميرا diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index e2e5771905..498d4c4438 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting Kamera startet diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 456efe6366..5ae3acdc1e 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting démarrage de la caméra diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 63de8f0471..d01657f8e4 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting カメラを起動しています diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index bc0c5f2b0e..d2d6a4ddc1 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting 카메라 시작 중 diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 4cf37cc585..04458974cb 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting câmera iniciando diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index a1748a6951..5f77f6c9de 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting กำลังเปิดกล้อง diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 51fdddd5ce..f8a76669a2 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting kamera başlatılıyor diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 7b682535ce..371106d571 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting 正在启动相机 diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 1794cc78ac..6aa6295ce2 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -275,7 +275,7 @@ - DriverViewScene + DriverViewWindow camera starting 開啟相機中 From 84a3b10da94f6483caf94a52d76c674e62307e77 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 10 Dec 2023 19:46:41 -0500 Subject: [PATCH 192/724] cabana: fix typo (#30678) --- tools/cabana/messageswidget.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index aba655d66f..0cec551017 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -151,7 +151,7 @@ void MessagesWidget::menuAboutToShow() { action->setEnabled(logical_index > 0); } menu->addSeparator(); - auto action = menu->addAction(tr("Mutlti-Line bytes"), this, &MessagesWidget::setMultiLineBytes); + auto action = menu->addAction(tr("Multi-Line bytes"), this, &MessagesWidget::setMultiLineBytes); action->setCheckable(true); action->setChecked(settings.multiple_lines_hex); } From 5a518243dda04b5df8efdfb2b48351297fb181b0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 11 Dec 2023 09:28:47 -0800 Subject: [PATCH 193/724] Bump submodules (#30682) bump submodules Co-authored-by: jnewb1 --- cereal | 2 +- opendbc | 2 +- panda | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cereal b/cereal index a3a6e4969e..d11688a90a 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit a3a6e4969e58875f7cdfc9e6cc6b1af3ee2392b5 +Subproject commit d11688a90a1cebacb3fe00dcfbba5f27551b2d89 diff --git a/opendbc b/opendbc index 5b0c73977f..93b983d49a 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 5b0c73977f1428700d0344d52874a90a4c5168fb +Subproject commit 93b983d49a2d6d5c67e15ce7650f55e4f121485d diff --git a/panda b/panda index a6b76dfda0..a88fe8c883 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit a6b76dfda0f2bcba51d68d56f013a318f9021fb9 +Subproject commit a88fe8c883758951ba2854eb2f6726d5365df099 From 7b5d4d031adb0e2fecf36772bbbb95e2989b9cc7 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 11 Dec 2023 14:46:56 -0800 Subject: [PATCH 194/724] Revert "Process Replay: move to pytest (#30260)" (#30687) This reverts commit 90c873ab1def0756f6f91fcb7f2d6fad503e3f49. --- .github/workflows/selfdrive_tests.yaml | 26 ++- common/prefix.py | 1 - selfdrive/test/process_replay/conftest.py | 37 --- selfdrive/test/process_replay/helpers.py | 150 ------------ selfdrive/test/process_replay/regen_all.py | 3 +- .../test/process_replay/test_processes.py | 213 ++++++++++++++---- 6 files changed, 190 insertions(+), 240 deletions(-) delete mode 100644 selfdrive/test/process_replay/conftest.py delete mode 100755 selfdrive/test/process_replay/helpers.py diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 94e6304539..c0a72924e4 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -208,20 +208,30 @@ jobs: run: | ${{ env.RUN }} "scons -j$(nproc)" - name: Run replay - id: run-replay timeout-minutes: 30 run: | - ${{ env.RUN }} "CI=1 $PYTEST -n auto --dist=loadscope selfdrive/test/process_replay/test_processes.py --long-diff && \ - chmod -R 777 /tmp/comma_download_cache" + ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ + chmod -R 777 /tmp/comma_download_cache && \ + coverage combine && \ + coverage xml" + - name: Print diff + id: print-diff + if: always() + run: cat selfdrive/test/process_replay/diff.txt + - uses: actions/upload-artifact@v3 + if: always() + continue-on-error: true + with: + name: process_replay_diff.txt + path: selfdrive/test/process_replay/diff.txt + - name: Upload reference logs + if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} + run: | + ${{ env.RUN }} "unset PYTHONWARNINGS && CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v3 with: name: ${{ github.job }} - - name: Upload reference logs - if: ${{ failure() && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} - run: | - ${{ env.RUN }} "unset PYTHONWARNINGS && CI=1 AZURE_TOKEN='$AZURE_TOKEN' \ - pytest -n auto --dist=loadscope selfdrive/test/process_replay/test_processes.py --upload-only" regen: name: regen diff --git a/common/prefix.py b/common/prefix.py index c5ae4393cd..c1744e8ff7 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -21,7 +21,6 @@ class OpenpilotPrefix: except FileExistsError: pass os.makedirs(Paths.log_root(), exist_ok=True) - os.makedirs(Paths.download_cache_root(), exist_ok=True) return self diff --git a/selfdrive/test/process_replay/conftest.py b/selfdrive/test/process_replay/conftest.py deleted file mode 100644 index a78f37f392..0000000000 --- a/selfdrive/test/process_replay/conftest.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest - -from openpilot.selfdrive.test.process_replay.helpers import ALL_PROCS -from openpilot.selfdrive.test.process_replay.test_processes import ALL_CARS - - -def pytest_addoption(parser: pytest.Parser): - parser.addoption("--whitelist-procs", type=str, nargs="*", default=ALL_PROCS, - help="Whitelist given processes from the test (e.g. controlsd)") - parser.addoption("--whitelist-cars", type=str, nargs="*", default=ALL_CARS, - help="Whitelist given cars from the test (e.g. HONDA)") - parser.addoption("--blacklist-procs", type=str, nargs="*", default=[], - help="Blacklist given processes from the test (e.g. controlsd)") - parser.addoption("--blacklist-cars", type=str, nargs="*", default=[], - help="Blacklist given cars from the test (e.g. HONDA)") - parser.addoption("--ignore-fields", type=str, nargs="*", default=[], - help="Extra fields or msgs to ignore (e.g. carState.events)") - parser.addoption("--ignore-msgs", type=str, nargs="*", default=[], - help="Msgs to ignore (e.g. onroadEvents)") - parser.addoption("--update-refs", action="store_true", - help="Updates reference logs using current commit") - parser.addoption("--upload-only", action="store_true", - help="Skips testing processes and uploads logs from previous test run") - parser.addoption("--long-diff", action="store_true", - help="Outputs diff in long format") - - -@pytest.fixture(scope="class", autouse=True) -def process_replay_test_arguments(request): - if hasattr(request.cls, "segment"): # check if a subclass of TestProcessReplayBase - request.cls.tested_procs = list(set(request.config.getoption("--whitelist-procs")) - set(request.config.getoption("--blacklist-procs"))) - request.cls.tested_cars = list({c.upper() for c in set(request.config.getoption("--whitelist-cars")) - set(request.config.getoption("--blacklist-cars"))}) - request.cls.ignore_fields = request.config.getoption("--ignore-fields") - request.cls.ignore_msgs = request.config.getoption("--ignore-msgs") - request.cls.upload_only = request.config.getoption("--upload-only") - request.cls.update_refs = request.config.getoption("--update-refs") - request.cls.long_diff = request.config.getoption("--long-diff") diff --git a/selfdrive/test/process_replay/helpers.py b/selfdrive/test/process_replay/helpers.py deleted file mode 100755 index 8e77454dae..0000000000 --- a/selfdrive/test/process_replay/helpers.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import unittest - -from parameterized import parameterized -from typing import Optional, Union, List - - -from openpilot.selfdrive.test.openpilotci import get_url, upload_file -from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_process_diff -from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process -from openpilot.system.version import get_commit -from openpilot.tools.lib.filereader import FileReader -from openpilot.tools.lib.helpers import save_log -from openpilot.tools.lib.logreader import LogReader, LogIterable - - -BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" -REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") -EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"} - - -def get_log_data(segment): - r, n = segment.rsplit("--", 1) - with FileReader(get_url(r, n)) as f: - return f.read() - - -ALL_PROCS = sorted({cfg.proc_name for cfg in CONFIGS if cfg.proc_name not in EXCLUDED_PROCS}) -PROC_TO_CFG = {cfg.proc_name: cfg for cfg in CONFIGS} - -cpu_count = os.cpu_count() or 1 - - -class TestProcessReplayBase(unittest.TestCase): - """ - Base class that replays all processes within test_proceses from a segment, - and puts the log messages in self.log_msgs for analysis by other tests. - """ - segment: Optional[Union[str, LogIterable]] = None - tested_procs: List[str] = ALL_PROCS - - @classmethod - def setUpClass(cls, create_logs=True): - if "Base" in cls.__name__: - raise unittest.SkipTest("skipping base class") - - if isinstance(cls.segment, str): - cls.log_reader = LogReader.from_bytes(get_log_data(cls.segment)) - else: - cls.log_reader = cls.segment - - if create_logs: - cls._create_log_msgs() - - @classmethod - def _run_replay(cls, cfg): - try: - return replay_process(cfg, cls.log_reader, disable_progress=True) - except Exception as e: - raise Exception(f"failed on segment: {cls.segment} \n{e}") from e - - @classmethod - def _create_log_msgs(cls): - cls.log_msgs = {} - cls.proc_cfg = {} - - for proc in cls.tested_procs: - cfg = PROC_TO_CFG[proc] - - log_msgs = cls._run_replay(cfg) - - cls.log_msgs[proc] = log_msgs - cls.proc_cfg[proc] = cfg - - -class TestProcessReplayDiffBase(TestProcessReplayBase): - """ - Base class for checking for diff between process outputs. - """ - update_refs = False - upload_only = False - long_diff = False - ignore_msgs: List[str] = [] - ignore_fields: List[str] = [] - - def setUp(self): - super().setUp() - if self.upload_only: - raise unittest.SkipTest("skipping test, uploading only") - - @classmethod - def setUpClass(cls): - super().setUpClass(not cls.upload_only) - - if cls.long_diff: - cls.maxDiff = None - - os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True) - - cls.cur_commit = get_commit() - cls.assertNotEqual(cls.cur_commit, None, "Couldn't get current commit") - - cls.upload = cls.update_refs or cls.upload_only - - try: - with open(REF_COMMIT_FN) as f: - cls.ref_commit = f.read().strip() - except FileNotFoundError: - print("Couldn't find reference commit") - sys.exit(1) - - cls._create_ref_log_msgs() - - @classmethod - def _create_ref_log_msgs(cls): - cls.ref_log_msgs = {} - - for proc in cls.tested_procs: - cur_log_fn = os.path.join(FAKEDATA, f"{cls.segment}_{proc}_{cls.cur_commit}.bz2") - if cls.update_refs: # reference logs will not exist if routes were just regenerated - ref_log_path = get_url(*cls.segment.rsplit("--", 1)) - else: - ref_log_fn = os.path.join(FAKEDATA, f"{cls.segment}_{proc}_{cls.ref_commit}.bz2") - ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) - - if not cls.upload_only: - save_log(cur_log_fn, cls.log_msgs[proc]) - cls.ref_log_msgs[proc] = list(LogReader(ref_log_path)) - - if cls.upload: - assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}" - upload_file(cur_log_fn, os.path.basename(cur_log_fn)) - os.remove(cur_log_fn) - - @parameterized.expand(ALL_PROCS) - def test_process_diff(self, proc): - if proc not in self.tested_procs: - raise unittest.SkipTest(f"{proc} was not requested to be tested") - - cfg = self.proc_cfg[proc] - log_msgs = self.log_msgs[proc] - ref_log_msgs = self.ref_log_msgs[proc] - - diff = compare_logs(ref_log_msgs, log_msgs, self.ignore_fields + cfg.ignore, self.ignore_msgs) - - diff_short, diff_long = format_process_diff(diff) - - self.assertEqual(len(diff), 0, "\n" + diff_long if self.long_diff else diff_short) diff --git a/selfdrive/test/process_replay/regen_all.py b/selfdrive/test/process_replay/regen_all.py index 070cb5f783..656a5b89e1 100755 --- a/selfdrive/test/process_replay/regen_all.py +++ b/selfdrive/test/process_replay/regen_all.py @@ -8,8 +8,7 @@ from tqdm import tqdm from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.test.process_replay.regen import regen_and_save -from openpilot.selfdrive.test.process_replay.process_replay import FAKEDATA -from openpilot.selfdrive.test.process_replay.test_processes import source_segments as segments +from openpilot.selfdrive.test.process_replay.test_processes import FAKEDATA, source_segments as segments from openpilot.tools.lib.route import SegmentName diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 1517d9bab0..5429c9b63e 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -1,15 +1,20 @@ #!/usr/bin/env python3 -import unittest -import pytest +import argparse +import concurrent.futures +import os import sys - -from parameterized import parameterized_class -from typing import List, Optional +from collections import defaultdict +from tqdm import tqdm +from typing import Any, DefaultDict, Dict from openpilot.selfdrive.car.car_helpers import interface_names -from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled -from openpilot.selfdrive.test.process_replay.helpers import TestProcessReplayDiffBase - +from openpilot.selfdrive.test.openpilotci import get_url, upload_file +from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff +from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, check_openpilot_enabled, replay_process +from openpilot.system.version import get_commit +from openpilot.tools.lib.filereader import FileReader +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.helpers import save_log source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY @@ -58,42 +63,166 @@ segments = [ # dashcamOnly makes don't need to be tested until a full port is done excluded_interfaces = ["mock", "tesla"] -ALL_CARS = sorted({car for car, _ in segments}) +BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" +REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") +EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"} -@pytest.mark.slow -@parameterized_class(('case_name', 'segment'), segments) -@pytest.mark.xdist_group_class_property('case_name') -class TestCarProcessReplay(TestProcessReplayDiffBase): - """ - Runs a replay diff on a segment for each car. - """ +def run_test_process(data): + segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data + res = None + if not args.upload_only: + lr = LogReader.from_bytes(lr_dat) + res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) + # save logs so we can upload when updating refs + save_log(cur_log_fn, log_msgs) - case_name: Optional[str] = None - tested_cars: List[str] = ALL_CARS - - @classmethod - def setUpClass(cls): - if cls.case_name not in cls.tested_cars: - raise unittest.SkipTest(f"{cls.case_name} was not requested to be tested") - super().setUpClass() - - def test_all_makes_are_tested(self): - if set(self.tested_cars) != set(ALL_CARS): - raise unittest.SkipTest("skipping check because some cars were skipped via command line") - - # check to make sure all car brands are tested - untested = (set(interface_names) - set(excluded_interfaces)) - {c.lower() for c in self.tested_cars} - self.assertEqual(len(untested), 0, f"Cars missing routes: {str(untested)}") - - def test_controlsd_engaged(self): - if "controlsd" not in self.tested_procs: - raise unittest.SkipTest("controlsd was not requested to be tested") - - # check to make sure openpilot is engaged in the route - log_msgs = self.log_msgs["controlsd"] - self.assertTrue(check_openpilot_enabled(log_msgs), f"Route did not enable at all or for long enough: {self.segment}") + if args.update_refs or args.upload_only: + print(f'Uploading: {os.path.basename(cur_log_fn)}') + assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}" + upload_file(cur_log_fn, os.path.basename(cur_log_fn)) + os.remove(cur_log_fn) + return (segment, cfg.proc_name, res) -if __name__ == '__main__': - pytest.main([*sys.argv[1:], __file__]) +def get_log_data(segment): + r, n = segment.rsplit("--", 1) + with FileReader(get_url(r, n)) as f: + return (segment, f.read()) + + +def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None): + if ignore_fields is None: + ignore_fields = [] + if ignore_msgs is None: + ignore_msgs = [] + + ref_log_msgs = list(LogReader(ref_log_path)) + + try: + log_msgs = replay_process(cfg, lr, disable_progress=True) + except Exception as e: + raise Exception("failed on segment: " + segment) from e + + # check to make sure openpilot is engaged in the route + if cfg.proc_name == "controlsd": + if not check_openpilot_enabled(log_msgs): + return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs + + try: + return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance), log_msgs + except Exception as e: + return str(e), log_msgs + + +if __name__ == "__main__": + all_cars = {car for car, _ in segments} + all_procs = {cfg.proc_name for cfg in CONFIGS if cfg.proc_name not in EXCLUDED_PROCS} + + cpu_count = os.cpu_count() or 1 + + parser = argparse.ArgumentParser(description="Regression test to identify changes in a process's output") + parser.add_argument("--whitelist-procs", type=str, nargs="*", default=all_procs, + help="Whitelist given processes from the test (e.g. controlsd)") + parser.add_argument("--whitelist-cars", type=str, nargs="*", default=all_cars, + help="Whitelist given cars from the test (e.g. HONDA)") + parser.add_argument("--blacklist-procs", type=str, nargs="*", default=[], + help="Blacklist given processes from the test (e.g. controlsd)") + parser.add_argument("--blacklist-cars", type=str, nargs="*", default=[], + help="Blacklist given cars from the test (e.g. HONDA)") + parser.add_argument("--ignore-fields", type=str, nargs="*", default=[], + help="Extra fields or msgs to ignore (e.g. carState.events)") + parser.add_argument("--ignore-msgs", type=str, nargs="*", default=[], + help="Msgs to ignore (e.g. carEvents)") + parser.add_argument("--update-refs", action="store_true", + help="Updates reference logs using current commit") + parser.add_argument("--upload-only", action="store_true", + help="Skips testing processes and uploads logs from previous test run") + parser.add_argument("-j", "--jobs", type=int, default=max(cpu_count - 2, 1), + help="Max amount of parallel jobs") + args = parser.parse_args() + + tested_procs = set(args.whitelist_procs) - set(args.blacklist_procs) + tested_cars = set(args.whitelist_cars) - set(args.blacklist_cars) + tested_cars = {c.upper() for c in tested_cars} + + full_test = (tested_procs == all_procs) and (tested_cars == all_cars) and all(len(x) == 0 for x in (args.ignore_fields, args.ignore_msgs)) + upload = args.update_refs or args.upload_only + os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True) + + if upload: + assert full_test, "Need to run full test when updating refs" + + try: + ref_commit = open(REF_COMMIT_FN).read().strip() + except FileNotFoundError: + print("Couldn't find reference commit") + sys.exit(1) + + cur_commit = get_commit() + if cur_commit is None: + raise Exception("Couldn't get current commit") + + print(f"***** testing against commit {ref_commit} *****") + + # check to make sure all car brands are tested + if full_test: + untested = (set(interface_names) - set(excluded_interfaces)) - {c.lower() for c in tested_cars} + assert len(untested) == 0, f"Cars missing routes: {str(untested)}" + + log_paths: DefaultDict[str, Dict[str, Dict[str, str]]] = defaultdict(lambda: defaultdict(dict)) + with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: + if not args.upload_only: + download_segments = [seg for car, seg in segments if car in tested_cars] + log_data: Dict[str, LogReader] = {} + p1 = pool.map(get_log_data, download_segments) + for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)): + log_data[segment] = lr + + pool_args: Any = [] + for car_brand, segment in segments: + if car_brand not in tested_cars: + continue + + for cfg in CONFIGS: + if cfg.proc_name not in tested_procs: + continue + + cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.bz2") + if args.update_refs: # reference logs will not exist if routes were just regenerated + ref_log_path = get_url(*segment.rsplit("--", 1)) + else: + ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.bz2") + ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) + + dat = None if args.upload_only else log_data[segment] + pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, dat)) + + log_paths[segment][cfg.proc_name]['ref'] = ref_log_path + log_paths[segment][cfg.proc_name]['new'] = cur_log_fn + + results: Any = defaultdict(dict) + p2 = pool.map(run_test_process, pool_args) + for (segment, proc, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)): + if not args.upload_only: + results[segment][proc] = result + + diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit) + if not upload: + with open(os.path.join(PROC_REPLAY_DIR, "diff.txt"), "w") as f: + f.write(diff_long) + print(diff_short) + + if failed: + print("TEST FAILED") + print("\n\nTo push the new reference logs for this commit run:") + print("./test_processes.py --upload-only") + else: + print("TEST SUCCEEDED") + + else: + with open(REF_COMMIT_FN, "w") as f: + f.write(cur_commit) + print(f"\n\nUpdated reference logs for commit: {cur_commit}") + + sys.exit(int(failed)) From 804973155344f39e839e5d11669db42287f866d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 11 Dec 2023 15:38:59 -0800 Subject: [PATCH 195/724] run_process_on_route: qlog and fingerprint options (#30688) Add --qlog and --fingerprint options for run_process_on_route --- selfdrive/debug/run_process_on_route.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index c7b6250d3f..8209b409ca 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -10,6 +10,8 @@ from openpilot.tools.lib.helpers import save_log if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run process on route and create new logs", formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("--qlog", help="Use qlog instead of log", action="store_true") + parser.add_argument("--fingerprint", help="The fingerprint to use") parser.add_argument("route", help="The route name to use") parser.add_argument("process", help="The process to run") args = parser.parse_args() @@ -17,10 +19,10 @@ if __name__ == "__main__": cfg = [c for c in CONFIGS if c.proc_name == args.process][0] route = Route(args.route) - lr = MultiLogIterator(route.log_paths()) + lr = MultiLogIterator(route.qlog_paths() if args.qlog else route.log_paths()) inputs = list(lr) - outputs = replay_process(cfg, inputs) + outputs = replay_process(cfg, inputs, fingerprint=args.fingerprint) # Remove message generated by the process under test and merge in the new messages produces = {o.which() for o in outputs} From 76c53e4742850966906b1ba10e5faf7b457503fc Mon Sep 17 00:00:00 2001 From: Lukejb14 Date: Mon, 11 Dec 2023 18:41:51 -0500 Subject: [PATCH 196/724] Add 2021 Mazda CX-5 engine fingerprint (#30616) --- selfdrive/car/mazda/values.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 1547f69b04..4dc5726f34 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -132,6 +132,7 @@ FW_VERSIONS = { b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2E-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2F-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', From 3a707082286cec9c93280c202ebbf5d5b89aac94 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 11 Dec 2023 16:21:07 -0800 Subject: [PATCH 197/724] Revert "remove extra sleep", weston is fixed but a couple other things relied on this This reverts commit d2d4802ad034a19dd34d5c0cf0c432912bc306b9. --- launch_chffrplus.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index a91bd677aa..7578c0296a 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -9,6 +9,11 @@ source "$BASEDIR/launch_env.sh" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" function agnos_init { + # wait longer for weston to come up + if [ -f "$BASEDIR/prebuilt" ]; then + sleep 5 + fi + # TODO: move this to agnos sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta From b0831fb117dde77334b6355975011611afc5d76f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 11 Dec 2023 17:25:37 -0800 Subject: [PATCH 198/724] soundd/micd: increase stream buffer size (#30685) * increase buffer * update cpu refs * go to 100ms * correct spot --- selfdrive/test/test_onroad.py | 4 ++-- selfdrive/ui/soundd.py | 5 +++-- system/micd.py | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 3c8e848fda..d7c5c58280 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -46,13 +46,13 @@ PROCS = { "selfdrive.thermald.thermald": 3.87, "selfdrive.locationd.calibrationd": 2.0, "selfdrive.locationd.torqued": 5.0, - "selfdrive.ui.soundd": 5.8, + "selfdrive.ui.soundd": 3.5, "selfdrive.monitoring.dmonitoringd": 4.0, "./proclogd": 1.54, "system.logmessaged": 0.2, "selfdrive.tombstoned": 0, "./logcatd": 0, - "system.micd": 10.0, + "system.micd": 6.0, "system.timezoned": 0, "selfdrive.boardd.pandad": 0, "selfdrive.statsd": 0.4, diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index dc1b7061e1..5ac503aadf 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -16,6 +16,7 @@ from openpilot.common.realtime import Ratekeeper from openpilot.common.swaglog import cloudlog SAMPLE_RATE = 48000 +SAMPLE_BUFFER = 4096 # (approx 100ms) MAX_VOLUME = 1.0 MIN_VOLUME = 0.1 CONTROLS_TIMEOUT = 5 # 5 seconds @@ -133,11 +134,11 @@ class Soundd: if TICI: micd.wait_for_devices(sd) # wait for alsa to be initialized on device - with sd.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: + with sd.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER) as stream: rk = Ratekeeper(20) sm = messaging.SubMaster(['controlsState', 'microphone']) - cloudlog.info(f"soundd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") + cloudlog.info(f"soundd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}, {stream.blocksize=}") while True: sm.update(0) diff --git a/system/micd.py b/system/micd.py index f4085becd4..baf75c17b9 100755 --- a/system/micd.py +++ b/system/micd.py @@ -11,6 +11,7 @@ RATE = 10 FFT_SAMPLES = 4096 REFERENCE_SPL = 2e-5 # newtons/m^2 SAMPLE_RATE = 44100 +SAMPLE_BUFFER = 4096 # (approx 100ms) @retry(attempts=7, delay=3) @@ -98,8 +99,8 @@ class Mic: if TICI: wait_for_devices(sd) # wait for alsa to be initialized on device - with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: - cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") + with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER) as stream: + cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}, {stream.blocksize=}") while True: self.update() From 4028cb6121ffd1f92463cfc109593b17abe3aa8c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 11 Dec 2023 19:24:01 -0800 Subject: [PATCH 199/724] FW query timing test: mock get_data function for timeout (#29712) * use a mock function to simulate timeout * Cleanup * Cleanup * clean refs * tolerance can also go down * fix * better name revert refs * use unittest * revert * Revert "use unittest" This reverts commit 7a1d6a6fc72fa129a66590e0e5998c2438ecbe55. * order * local * use a context * revert * stress test it * let's try * it's consistent --------- Co-authored-by: Shane Smiskol --- selfdrive/car/tests/test_fw_fingerprint.py | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 5721896787..d4cd6315f2 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -5,6 +5,7 @@ import time import unittest from collections import defaultdict from parameterized import parameterized +from unittest import mock import threading from cereal import car @@ -176,7 +177,7 @@ class TestFwFingerprint(unittest.TestCase): class TestFwFingerprintTiming(unittest.TestCase): N: int = 5 - TOL: float = 0.12 + TOL: float = 0.1 @staticmethod def _run_thread(thread: threading.Thread) -> float: @@ -191,12 +192,19 @@ class TestFwFingerprintTiming(unittest.TestCase): return time.perf_counter() - t def _benchmark_brand(self, brand, num_pandas): - fake_socket = FakeSocket() - brand_time = 0 - for _ in range(self.N): - thread = threading.Thread(target=get_fw_versions, args=(fake_socket, fake_socket, brand), - kwargs=dict(num_pandas=num_pandas)) - brand_time += self._run_thread(thread) + def fake_get_data(_, timeout): + nonlocal fake_timeout_time + fake_timeout_time += timeout + return {} + + with mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", fake_get_data): + fake_socket = FakeSocket() + brand_time = 0 + for _ in range(self.N): + fake_timeout_time = 0 + thread = threading.Thread(target=get_fw_versions, args=(fake_socket, fake_socket, brand), + kwargs=dict(num_pandas=num_pandas)) + brand_time += self._run_thread(thread) + fake_timeout_time return brand_time / self.N @@ -227,7 +235,7 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.41 + total_ref_time = 6.58 brand_ref_times = { 1: { 'body': 0.11, From d2583d64f0ce997bdc7413f091f794ac3d704d4b Mon Sep 17 00:00:00 2001 From: Sparkguys <147584184+Sparkguys@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:25:55 +0900 Subject: [PATCH 200/724] Update values.py for VW Passat MK8 (#30646) Update values.py Added FW for Passat MK8 --- selfdrive/car/volkswagen/values.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 6a3baafa35..13508aa1fb 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -667,6 +667,7 @@ FW_VERSIONS = { b'\xf1\x8704L906026GA\xf1\x892013', b'\xf1\x8704L906026KD\xf1\x894798', b'\xf1\x873G0906259B \xf1\x890002', + b'\xf1\x873G0906259 \xf1\x890004', b'\xf1\x873G0906264 \xf1\x890004', ], (Ecu.transmission, 0x7e1, None): [ @@ -682,6 +683,7 @@ FW_VERSIONS = { b'\xf1\x870CW300042H \xf1\x891607', b'\xf1\x870GC300042H \xf1\x891404', b'\xf1\x870D9300018C \xf1\x895297', + b'\xf1\x870D9300042H \xf1\x894901', b'\xf1\x870GC300043 \xf1\x892301', ], (Ecu.srs, 0x715, None): [ @@ -696,6 +698,7 @@ FW_VERSIONS = { b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e5915005914001354701311542900', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900', + b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012416612124111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111', ], (Ecu.eps, 0x712, None): [ @@ -709,6 +712,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516B00501A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521B00703A1', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563B0000600', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00603A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600', ], (Ecu.fwdRadar, 0x757, None): [ @@ -718,6 +722,7 @@ FW_VERSIONS = { b'\xf1\x873Q0907572C \xf1\x890195', b'\xf1\x873Q0907572C \xf1\x890196', b'\xf1\x875Q0907572P \xf1\x890682', + b'\xf1\x873Q0907572B \xf1\x890194', b'\xf1\x875Q0907572R \xf1\x890771', ], }, From 9807bd99dbc526ba92bb68c503d5108b9f892dd1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 11 Dec 2023 23:32:36 -0500 Subject: [PATCH 201/724] HKG: Car Port for Kia Niro Plug-in Hybrid 2022 (#30576) * HKG: Car Port for Kia Niro Plug-in Hybrid 2022 * Update CARS.md and add test route * add to releases --------- Co-authored-by: Shane Smiskol --- RELEASES.md | 1 + docs/CARS.md | 3 ++- selfdrive/car/hyundai/interface.py | 2 +- selfdrive/car/hyundai/values.py | 22 +++++++++++++++++++++- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/substitute.yaml | 1 + 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 07f70cdaff..e9864a0d58 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,7 @@ Version 0.9.6 (2023-12-14) ======================== * AGNOS 9 * comma body streaming and controls over WebRTC +* Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! * Toyota RAV4 2023 support * Toyota RAV4 Hybrid 2023 support diff --git a/docs/CARS.md b/docs/CARS.md index 515dcad4ae..2bec3dfb50 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 271 Supported Cars +# 272 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -135,6 +135,7 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2020|All|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro Plug-in Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 2127fc320f..d04743502c 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -156,7 +156,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 1985. ret.wheelbase = 2.78 ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable - elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_NIRO_HEV_2ND_GEN): + elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_PHEV_2022): ret.mass = 3543. * CV.LB_TO_KG # average of all the cars ret.wheelbase = 2.7 ret.steerRatio = 13.6 # average of all the cars diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 9a62371601..c5162786a6 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -112,6 +112,7 @@ class CAR(StrEnum): KIA_NIRO_EV = "KIA NIRO EV 2020" KIA_NIRO_EV_2ND_GEN = "KIA NIRO EV 2ND GEN" KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" + KIA_NIRO_PHEV_2022 = "KIA NIRO PLUG-IN HYBRID 2022" KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021" KIA_NIRO_HEV_2ND_GEN = "KIA NIRO HYBRID 2ND GEN" KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN" @@ -248,6 +249,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_c])), HyundaiCarInfo("Kia Niro Plug-in Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_d])), ], + CAR.KIA_NIRO_PHEV_2022: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_f])), CAR.KIA_NIRO_HEV_2021: [ HyundaiCarInfo("Kia Niro Hybrid 2021-22", car_parts=CarParts.common([CarHarness.hyundai_f])), # TODO: 2021 could be hyundai_d, verify ], @@ -1623,6 +1625,23 @@ FW_VERSIONS = { b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ', ], }, + CAR.KIA_NIRO_PHEV_2022: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL3\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 99211-G5500 210428', + ], + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00DEhe SCC F-CUP 1.00 1.00 99110-G5600 ', + ], + }, CAR.KIA_NIRO_HEV_2021: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -2144,7 +2163,7 @@ HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_N CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.KIA_K8_HEV_1ST_GEN, - CAR.AZERA_HEV_6TH_GEN} + CAR.AZERA_HEV_6TH_GEN, CAR.KIA_NIRO_PHEV_2022} EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KONA_EV_2ND_GEN} @@ -2227,4 +2246,5 @@ DBC = { CAR.KONA_EV_2ND_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_K8_HEV_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.CUSTIN_1ST_GEN: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_NIRO_PHEV_2022: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), } diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 33a7e9407a..66dafc1312 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -153,6 +153,7 @@ routes = [ CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV), CarTestRoute("b153671049a867b3|2023-04-05--10-00-30", HYUNDAI.KIA_NIRO_EV_2ND_GEN), CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV), + CarTestRoute("23349923ba5c4e3b|2023-12-02--08-51-54", HYUNDAI.KIA_NIRO_PHEV_2022), CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021), CarTestRoute("db04d2c63990e3ba|2023-02-08--16-52-39", HYUNDAI.KIA_NIRO_HEV_2ND_GEN), CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE), diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index c7a1566b32..242a38b3d0 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -20,6 +20,7 @@ KIA FORTE E 2018 & GT 2021: HYUNDAI SONATA 2020 KIA CEED INTRO ED 2019: HYUNDAI SONATA 2020 KIA SELTOS 2021: HYUNDAI SONATA 2020 KIA NIRO HYBRID 2019: KIA NIRO EV 2020 +KIA NIRO PLUG-IN HYBRID 2022: KIA NIRO EV 2020 KIA NIRO HYBRID 2021: KIA NIRO EV 2020 HYUNDAI VELOSTER 2019: HYUNDAI SONATA 2019 HYUNDAI KONA 2020: HYUNDAI KONA ELECTRIC 2019 From 11ec5bf25e82e53bbefa88643c81ab5b7581e913 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 11 Dec 2023 20:38:37 -0800 Subject: [PATCH 202/724] Simulator: remove carla (#30690) * no carla * no arg and stylize metadrive --- .devcontainer/devcontainer.json | 1 - README.md | 2 +- poetry.lock | 56 +++------- pyproject.toml | 6 - tools/sim/README.md | 39 +------ tools/sim/bridge/carla/carla_bridge.py | 22 ---- tools/sim/bridge/carla/carla_world.py | 145 ------------------------- tools/sim/lib/simulated_sensors.py | 7 +- tools/sim/run_bridge.py | 18 +-- tools/sim/start_carla.sh | 34 ------ tools/sim/start_openpilot_docker.sh | 2 +- tools/sim/tests/test_carla_bridge.py | 45 -------- tools/sim/tests/test_sim_bridge.py | 8 +- tools/sim/tmux_script.sh | 6 - 14 files changed, 31 insertions(+), 360 deletions(-) delete mode 100644 tools/sim/bridge/carla/carla_bridge.py delete mode 100644 tools/sim/bridge/carla/carla_world.py delete mode 100755 tools/sim/start_carla.sh delete mode 100755 tools/sim/tests/test_carla_bridge.py delete mode 100755 tools/sim/tmux_script.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a0ff582e68..a7a63658ed 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,6 @@ "DISPLAY": "${localEnv:DISPLAY}", "PYTHONPATH": "${containerWorkspaceFolder}", "TERM": "xterm-256color", - "CARLA_HOST": "host.docker.internal", "force_color_prompt": "1" }, "runArgs": [ diff --git a/README.md b/README.md index 7d051ada14..d85d1e5676 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ All openpilot services can run as usual on a PC without requiring special hardwa With openpilot's tools, you can plot logs, replay drives, and watch the full-res camera streams. See [the tools README](tools/README.md) for more information. -You can also run openpilot in simulation [with the CARLA simulator](tools/sim/README.md). This allows openpilot to drive around a virtual car on your Ubuntu machine. The whole setup should only take a few minutes but does require a decent GPU. +You can also run openpilot in simulation [with the MetaDrive simulator](tools/sim/README.md). This allows openpilot to drive around a virtual car on your Ubuntu machine. The whole setup should only take a few minutes but does require a decent GPU. A PC running openpilot can also control your vehicle if it is connected to a [webcam](https://github.com/commaai/openpilot/tree/master/tools/webcam), a [black panda](https://comma.ai/shop/products/panda), and a [harness](https://comma.ai/shop/products/car-harness). diff --git a/poetry.lock b/poetry.lock index 1ec3847b18..44e07568b3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -346,20 +346,6 @@ files = [ docutils = ">=0.12" Sphinx = ">=4.0,<5.0.0 || >5.0.0" -[[package]] -name = "carla" -version = "0.9.14" -description = "Python API for communicating with the CARLA server." -optional = false -python-versions = "*" -files = [ - {file = "carla-0.9.14-cp311-cp311-linux_x86_64.whl", hash = "sha256:f0a8ce0c760d1fef3577e2ef90e9d468e3d85e65bd6d68b44bce51f0d5a0723a"}, -] - -[package.source] -type = "url" -url = "https://github.com/commaai/carla/releases/download/3.11.4/carla-0.9.14-cp311-cp311-linux_x86_64.whl" - [[package]] name = "casadi" version = "3.6.3" @@ -2081,16 +2067,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2671,7 +2647,14 @@ files = [ ] [package.dependencies] -numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] [[package]] name = "opencv-python-headless" @@ -2690,7 +2673,14 @@ files = [ ] [package.dependencies] -numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] [[package]] name = "packaging" @@ -3869,7 +3859,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3877,15 +3866,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3902,7 +3884,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3910,7 +3891,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -5009,4 +4989,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "7f7244a89280a41937066ebf80aa64fcb375852484c9d034ea7f2de3eae4d90a" +content-hash = "08910461234279f3339c1692bd0cc2732f6e17f61ca9e3ffa53d461526e3dce4" diff --git a/pyproject.toml b/pyproject.toml index c6a13c4b86..58c506e378 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -168,12 +168,6 @@ types-tabulate = "*" # this is only pinned since 5.15.11 is broken pyqt5 = { version = "==5.15.2", markers = "platform_machine == 'x86_64'" } # no aarch64 wheels for macOS/linux -[tool.poetry.group.carla] -optional = true - -[tool.poetry.group.carla.dependencies] -carla = { url = "https://github.com/commaai/carla/releases/download/3.11.4/carla-0.9.14-cp311-cp311-linux_x86_64.whl", platform = "linux", markers = "platform_machine == 'x86_64'" } - [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/tools/sim/README.md b/tools/sim/README.md index 0892e77736..72727f765d 100644 --- a/tools/sim/README.md +++ b/tools/sim/README.md @@ -1,7 +1,7 @@ openpilot in simulator ===================== -openpilot implements a [bridge](run_bridge.py) that allows it to run in the [MetaDrive simulator](https://github.com/metadriverse/metadrive) or [CARLA simulator](https://carla.org/). +openpilot implements a [bridge](run_bridge.py) that allows it to run in the [MetaDrive simulator](https://github.com/metadriverse/metadrive). ## Launching openpilot First, start openpilot. @@ -13,8 +13,7 @@ First, start openpilot. ## Bridge usage ``` $ ./run_bridge.py -h -usage: run_bridge.py [-h] [--joystick] [--high_quality] [--dual_camera] [--simulator SIMULATOR] [--town TOWN] [--spawn_point NUM_SELECTED_SPAWN_POINT] [--host HOST] [--port PORT] - +usage: run_bridge.py [-h] [--joystick] [--high_quality] [--dual_camera] [--simulator SIMULATOR] Bridge between the simulator and openpilot. options: @@ -23,10 +22,6 @@ options: --high_quality --dual_camera --simulator SIMULATOR - --town TOWN - --spawn_point NUM_SELECTED_SPAWN_POINT - --host HOST - --port PORT ``` #### Bridge Controls: @@ -53,32 +48,4 @@ options: Start bridge processes located in tools/sim: ``` bash ./run_bridge.py --simulator metadrive -``` - -## Carla - -CARLA is also partially supported, though the performance is not great. It needs to be manually installed with: - -```bash -poetry install --with=carla -``` - -openpilot doesn't have any extreme hardware requirements, however CARLA requires an NVIDIA graphics card and is very resource-intensive and may not run smoothly on your system. -For this case, we have the simulator in low quality by default. - -You can also check out the [CARLA python documentation](https://carla.readthedocs.io/en/latest/python_api/) to find more parameters to tune that might increase performance on your system. - -### Launching Carla -Start Carla simulator and bridge processes located in tools/sim: -``` bash -# Terminal 1 -./start_carla.sh - -# Terminal 2 -./run_bridge.py --simulator carla -``` - -## Further Reading - -The following resources contain more details and troubleshooting tips. -* [CARLA on the openpilot wiki](https://github.com/commaai/openpilot/wiki/CARLA) +``` \ No newline at end of file diff --git a/tools/sim/bridge/carla/carla_bridge.py b/tools/sim/bridge/carla/carla_bridge.py deleted file mode 100644 index fe7b34802f..0000000000 --- a/tools/sim/bridge/carla/carla_bridge.py +++ /dev/null @@ -1,22 +0,0 @@ -from openpilot.tools.sim.bridge.common import SimulatorBridge -from openpilot.tools.sim.bridge.carla.carla_world import CarlaWorld - - -class CarlaBridge(SimulatorBridge): - TICKS_PER_FRAME = 5 - - def __init__(self, arguments): - super().__init__(arguments) - self.host = arguments.host - self.port = arguments.port - self.town = arguments.town - self.num_selected_spawn_point = arguments.num_selected_spawn_point - - def spawn_world(self): - import carla - - client = carla.Client(self.host, self.port) - client.set_timeout(5) - - return CarlaWorld(client, high_quality=self.high_quality, dual_camera=self.dual_camera, - num_selected_spawn_point=self.num_selected_spawn_point, town=self.town) diff --git a/tools/sim/bridge/carla/carla_world.py b/tools/sim/bridge/carla/carla_world.py deleted file mode 100644 index 14b300ed7b..0000000000 --- a/tools/sim/bridge/carla/carla_world.py +++ /dev/null @@ -1,145 +0,0 @@ -import numpy as np - -from openpilot.common.params import Params -from openpilot.tools.sim.lib.common import SimulatorState, vec3 -from openpilot.tools.sim.bridge.common import World -from openpilot.tools.sim.lib.camerad import W, H - - -class CarlaWorld(World): - def __init__(self, client, high_quality, dual_camera, num_selected_spawn_point, town): - super().__init__(dual_camera) - import carla - - low_quality_layers = carla.MapLayer(carla.MapLayer.Ground | carla.MapLayer.Walls | carla.MapLayer.Decals) - - layers = carla.MapLayer.All if high_quality else low_quality_layers - - world = client.load_world(town, map_layers=layers) - - settings = world.get_settings() - settings.fixed_delta_seconds = 0.01 - world.apply_settings(settings) - - world.set_weather(carla.WeatherParameters.ClearSunset) - - self.world = world - world_map = world.get_map() - - blueprint_library = world.get_blueprint_library() - - vehicle_bp = blueprint_library.filter('vehicle.tesla.*')[1] - vehicle_bp.set_attribute('role_name', 'hero') - spawn_points = world_map.get_spawn_points() - assert len(spawn_points) > num_selected_spawn_point, \ - f'''No spawn point {num_selected_spawn_point}, try a value between 0 and {len(spawn_points)} for this town.''' - self.spawn_point = spawn_points[num_selected_spawn_point] - self.vehicle = world.spawn_actor(vehicle_bp, self.spawn_point) - - physics_control = self.vehicle.get_physics_control() - physics_control.mass = 2326 - physics_control.torque_curve = [[20.0, 500.0], [5000.0, 500.0]] - physics_control.gear_switch_time = 0.0 - self.vehicle.apply_physics_control(physics_control) - - self.vc: carla.VehicleControl = carla.VehicleControl(throttle=0, steer=0, brake=0, reverse=False) - self.max_steer_angle: float = self.vehicle.get_physics_control().wheels[0].max_steer_angle - self.params = Params() - - self.steer_ratio = 15 - - self.carla_objects = [] - - transform = carla.Transform(carla.Location(x=0.8, z=1.13)) - - def create_camera(fov, callback): - blueprint = blueprint_library.find('sensor.camera.rgb') - blueprint.set_attribute('image_size_x', str(W)) - blueprint.set_attribute('image_size_y', str(H)) - blueprint.set_attribute('fov', str(fov)) - blueprint.set_attribute('sensor_tick', str(1/20)) - if not high_quality: - blueprint.set_attribute('enable_postprocess_effects', 'False') - camera = world.spawn_actor(blueprint, transform, attach_to=self.vehicle) - camera.listen(callback) - return camera - - self.road_camera = create_camera(fov=40, callback=self.cam_callback_road) - if dual_camera: - self.road_wide_camera = create_camera(fov=120, callback=self.cam_callback_wide_road) # fov bigger than 120 shows unwanted artifacts - else: - self.road_wide_camera = None - - # re-enable IMU - imu_bp = blueprint_library.find('sensor.other.imu') - imu_bp.set_attribute('sensor_tick', '0.01') - self.imu = world.spawn_actor(imu_bp, transform, attach_to=self.vehicle) - - gps_bp = blueprint_library.find('sensor.other.gnss') - self.gps = world.spawn_actor(gps_bp, transform, attach_to=self.vehicle) - self.params.put_bool("UbloxAvailable", True) - - self.carla_objects = [self.imu, self.gps, self.road_camera, self.road_wide_camera, self.vehicle] - - def close(self): - for s in self.carla_objects: - if s is not None: - try: - s.destroy() - except Exception as e: - print("Failed to destroy carla object", e) - - def carla_image_to_rgb(self, image): - rgb = np.frombuffer(image.raw_data, dtype=np.dtype("uint8")) - rgb = np.reshape(rgb, (H, W, 4)) - return np.ascontiguousarray(rgb[:, :, [0, 1, 2]]) - - def cam_callback_road(self, image): - with self.image_lock: - self.road_image = self.carla_image_to_rgb(image) - - def cam_callback_wide_road(self, image): - with self.image_lock: - self.wide_road_image = self.carla_image_to_rgb(image) - - def apply_controls(self, steer_angle, throttle_out, brake_out): - self.vc.throttle = throttle_out - - steer_carla = steer_angle * -1 / (self.max_steer_angle * self.steer_ratio) - steer_carla = np.clip(steer_carla, -1, 1) - - self.vc.steer = steer_carla - self.vc.brake = brake_out - self.vehicle.apply_control(self.vc) - - def read_sensors(self, simulator_state: SimulatorState): - simulator_state.imu.bearing = self.imu.get_transform().rotation.yaw - - simulator_state.imu.accelerometer = vec3( - self.imu.get_acceleration().x, - self.imu.get_acceleration().y, - self.imu.get_acceleration().z - ) - - simulator_state.imu.gyroscope = vec3( - self.imu.get_angular_velocity().x, - self.imu.get_angular_velocity().y, - self.imu.get_angular_velocity().z - ) - - simulator_state.gps.from_xy([self.vehicle.get_location().x, self.vehicle.get_location().y]) - - simulator_state.velocity = self.vehicle.get_velocity() - simulator_state.valid = True - simulator_state.steering_angle = self.vc.steer * self.max_steer_angle - - def read_cameras(self): - pass # cameras are read within a callback for carla - - def tick(self): - self.world.tick() - - def reset(self): - import carla - self.vehicle.set_transform(self.spawn_point) - self.vehicle.set_target_velocity(carla.Vector3D()) diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index faa8c0691b..e85665f903 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -44,11 +44,10 @@ class SimulatedSensors: if not simulator_state.valid: return - # transform vel from carla to NED - # north is -Y in CARLA + # transform from vel to NED velNED = [ - -simulator_state.velocity.y, # north/south component of NED is negative when moving south - simulator_state.velocity.x, # positive when moving east, which is x in carla + -simulator_state.velocity.y, + simulator_state.velocity.x, simulator_state.velocity.z, ] diff --git a/tools/sim/run_bridge.py b/tools/sim/run_bridge.py index 99617b181e..5f0f0d5e99 100755 --- a/tools/sim/run_bridge.py +++ b/tools/sim/run_bridge.py @@ -1,12 +1,9 @@ #!/usr/bin/env python import argparse -import os from typing import Any from multiprocessing import Queue -from openpilot.tools.sim.bridge.common import SimulatorBridge -from openpilot.tools.sim.bridge.carla.carla_bridge import CarlaBridge from openpilot.tools.sim.bridge.metadrive.metadrive_bridge import MetaDriveBridge @@ -15,13 +12,6 @@ def parse_args(add_args=None): parser.add_argument('--joystick', action='store_true') parser.add_argument('--high_quality', action='store_true') parser.add_argument('--dual_camera', action='store_true') - parser.add_argument('--simulator', dest='simulator', type=str, default='metadrive') - - # Carla specific - parser.add_argument('--town', type=str, default='Town04_Opt') - parser.add_argument('--spawn_point', dest='num_selected_spawn_point', type=int, default=16) - parser.add_argument('--host', dest='host', type=str, default=os.environ.get("CARLA_HOST", '127.0.0.1')) - parser.add_argument('--port', dest='port', type=int, default=2000) return parser.parse_args(add_args) @@ -29,13 +19,7 @@ if __name__ == "__main__": q: Any = Queue() args = parse_args() - simulator_bridge: SimulatorBridge - if args.simulator == "carla": - simulator_bridge = CarlaBridge(args) - elif args.simulator == "metadrive": - simulator_bridge = MetaDriveBridge(args) - else: - raise AssertionError("simulator type not supported") + simulator_bridge = MetaDriveBridge(args) p = simulator_bridge.run(q) if args.joystick: diff --git a/tools/sim/start_carla.sh b/tools/sim/start_carla.sh deleted file mode 100755 index f14e0efd16..0000000000 --- a/tools/sim/start_carla.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -# Requires nvidia docker - https://github.com/NVIDIA/nvidia-docker -if ! $(apt list --installed | grep -q nvidia-container-toolkit); then - read -p "Nvidia docker is required. Do you want to install it now? (y/n)"; - if [ "${REPLY}" == "y" ]; then - distribution=$(. /etc/os-release;echo $ID$VERSION_ID) - echo $distribution - curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list - sudo apt-get update && sudo apt-get install -y nvidia-docker2 # Also installs docker-ce and nvidia-container-toolkit - sudo systemctl restart docker - else - exit 0 - fi -fi - -docker pull carlasim/carla:0.9.14 - -EXTRA_ARGS="-it" -if [[ "$DETACH" ]]; then - EXTRA_ARGS="-d" -fi - -docker kill carla_sim || true -docker run \ - --name carla_sim \ - --rm \ - --gpus all \ - --net=host \ - -v /tmp/.X11-unix:/tmp/.X11-unix:rw \ - $EXTRA_ARGS \ - carlasim/carla:0.9.14 \ - /bin/bash ./CarlaUE4.sh -opengl -nosound -RenderOffScreen -benchmark -fps=20 -quality-level=Low diff --git a/tools/sim/start_openpilot_docker.sh b/tools/sim/start_openpilot_docker.sh index 54f4995e45..3dce660579 100755 --- a/tools/sim/start_openpilot_docker.sh +++ b/tools/sim/start_openpilot_docker.sh @@ -10,7 +10,7 @@ if ! [[ -z "$MOUNT_OPENPILOT" ]]; then fi if [[ "$CI" ]]; then - CMD="CI=1 ${OPENPILOT_DIR}/tools/sim/tests/test_carla_integration.py" + CMD="CI=1 ${OPENPILOT_DIR}/tools/sim/tests/test_metadrive_integration.py" else # expose X to the container xhost +local:root diff --git a/tools/sim/tests/test_carla_bridge.py b/tools/sim/tests/test_carla_bridge.py deleted file mode 100755 index 3a654c993a..0000000000 --- a/tools/sim/tests/test_carla_bridge.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -import subprocess -import time -import unittest -from subprocess import Popen - -from openpilot.selfdrive.manager.helpers import unblock_stdout -from openpilot.tools.sim.run_bridge import parse_args -from openpilot.tools.sim.bridge.carla.carla_bridge import CarlaBridge -from openpilot.tools.sim.tests.test_sim_bridge import SIM_DIR, TestSimBridgeBase - -from typing import Optional - -class TestCarlaBridge(TestSimBridgeBase): - """ - Tests need Carla simulator to run - """ - carla_process: Optional[Popen] = None - - def setUp(self): - super().setUp() - - # We want to make sure that carla_sim docker isn't still running. - subprocess.run("docker rm -f carla_sim", shell=True, stderr=subprocess.PIPE, check=False) - self.carla_process = subprocess.Popen("./start_carla.sh", cwd=SIM_DIR) - - # Too many lagging messages in bridge.py can cause a crash. This prevents it. - unblock_stdout() - # Wait 10 seconds to startup carla - time.sleep(10) - - def create_bridge(self): - return CarlaBridge(parse_args([])) - - def tearDown(self): - super().tearDown() - - # Stop carla simulator by removing docker container - subprocess.run("docker rm -f carla_sim", shell=True, stderr=subprocess.PIPE, check=False) - if self.carla_process is not None: - self.carla_process.wait() - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/sim/tests/test_sim_bridge.py b/tools/sim/tests/test_sim_bridge.py index 2654526fa8..2a12ef791a 100644 --- a/tools/sim/tests/test_sim_bridge.py +++ b/tools/sim/tests/test_sim_bridge.py @@ -14,7 +14,7 @@ class TestSimBridgeBase(unittest.TestCase): @classmethod def setUpClass(cls): if cls is TestSimBridgeBase: - raise unittest.SkipTest("Don't run this base class, run test_carla_bridge.py instead") + raise unittest.SkipTest("Don't run this base class, run test_metadrive_bridge.py instead") def setUp(self): self.processes = [] @@ -26,15 +26,15 @@ class TestSimBridgeBase(unittest.TestCase): sm = messaging.SubMaster(['controlsState', 'onroadEvents', 'managerState']) q = Queue() - carla_bridge = self.create_bridge() - p_bridge = carla_bridge.run(q, retries=10) + bridge = self.create_bridge() + p_bridge = bridge.run(q, retries=10) self.processes.append(p_bridge) max_time_per_step = 60 # Wait for bridge to startup start_waiting = time.monotonic() - while not carla_bridge.started and time.monotonic() < start_waiting + max_time_per_step: + while not bridge.started and time.monotonic() < start_waiting + max_time_per_step: time.sleep(0.1) self.assertEqual(p_bridge.exitcode, None, f"Bridge process should be running, but exited with code {p_bridge.exitcode}") diff --git a/tools/sim/tmux_script.sh b/tools/sim/tmux_script.sh deleted file mode 100755 index 2a1e74958a..0000000000 --- a/tools/sim/tmux_script.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -tmux new -d -s carla-sim -tmux send-keys "./launch_openpilot.sh" ENTER -tmux neww -tmux send-keys "./run_bridge.py $*" ENTER -tmux a -t carla-sim From 62670e66bfb93f2d203abcb675f93ccac39d768a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 12 Dec 2023 13:53:27 +0800 Subject: [PATCH 203/724] camerad/CameraBuf: rename yuv_type to stream_type (#30695) --- system/camerad/cameras/camera_common.cc | 8 ++++---- system/camerad/cameras/camera_common.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index e36766ca2d..645a6f305c 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -61,9 +61,9 @@ private: cl_kernel krnl_; }; -void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType init_yuv_type) { +void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; - this->yuv_type = init_yuv_type; + stream_type = type; frame_buf_count = frame_cnt; const SensorInfo *ci = s->ci.get(); @@ -87,7 +87,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, assert(nv12_height/2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, rgb_height)); size_t nv12_size = 2346 * nv12_width; // comes from v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage size_t nv12_uv_offset = nv12_width * nv12_height; - vipc_server->create_buffers_with_sizes(yuv_type, YUV_BUFFER_COUNT, false, rgb_width, rgb_height, nv12_size, nv12_width, nv12_uv_offset); + vipc_server->create_buffers_with_sizes(stream_type, YUV_BUFFER_COUNT, false, rgb_width, rgb_height, nv12_size, nv12_width, nv12_uv_offset); LOGD("created %d YUV vipc buffers with size %dx%d", YUV_BUFFER_COUNT, nv12_width, nv12_height); debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset); @@ -113,7 +113,7 @@ bool CameraBuf::acquire() { } cur_frame_data = camera_bufs_metadata[cur_buf_idx]; - cur_yuv_buf = vipc_server->get_buffer(yuv_type); + cur_yuv_buf = vipc_server->get_buffer(stream_type); cur_camera_buf = &camera_bufs[cur_buf_idx]; double start_time = millis_since_boot(); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index e93d357b8d..9ebe018408 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -55,7 +55,7 @@ class CameraBuf { private: VisionIpcServer *vipc_server; Debayer *debayer = nullptr; - VisionStreamType yuv_type; + VisionStreamType stream_type; int cur_buf_idx; SafeQueue safe_queue; int frame_buf_count; @@ -71,7 +71,7 @@ public: CameraBuf() = default; ~CameraBuf(); - void init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType yuv_type); + void init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType type); bool acquire(); void queue(size_t buf_idx); }; From b1ecfd55ce73062f297a5ea7d99d0c55d57c58ab Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 11 Dec 2023 22:12:21 -0800 Subject: [PATCH 204/724] Revert "FW query timing test: mock get_data function for timeout" (#30696) Revert "FW query timing test: mock get_data function for timeout (#29712)" This reverts commit 4028cb6121ffd1f92463cfc109593b17abe3aa8c. --- selfdrive/car/tests/test_fw_fingerprint.py | 24 ++++++++-------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index d4cd6315f2..5721896787 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -5,7 +5,6 @@ import time import unittest from collections import defaultdict from parameterized import parameterized -from unittest import mock import threading from cereal import car @@ -177,7 +176,7 @@ class TestFwFingerprint(unittest.TestCase): class TestFwFingerprintTiming(unittest.TestCase): N: int = 5 - TOL: float = 0.1 + TOL: float = 0.12 @staticmethod def _run_thread(thread: threading.Thread) -> float: @@ -192,19 +191,12 @@ class TestFwFingerprintTiming(unittest.TestCase): return time.perf_counter() - t def _benchmark_brand(self, brand, num_pandas): - def fake_get_data(_, timeout): - nonlocal fake_timeout_time - fake_timeout_time += timeout - return {} - - with mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", fake_get_data): - fake_socket = FakeSocket() - brand_time = 0 - for _ in range(self.N): - fake_timeout_time = 0 - thread = threading.Thread(target=get_fw_versions, args=(fake_socket, fake_socket, brand), - kwargs=dict(num_pandas=num_pandas)) - brand_time += self._run_thread(thread) + fake_timeout_time + fake_socket = FakeSocket() + brand_time = 0 + for _ in range(self.N): + thread = threading.Thread(target=get_fw_versions, args=(fake_socket, fake_socket, brand), + kwargs=dict(num_pandas=num_pandas)) + brand_time += self._run_thread(thread) return brand_time / self.N @@ -235,7 +227,7 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.58 + total_ref_time = 6.41 brand_ref_times = { 1: { 'body': 0.11, From 1c011e6024527b9e2b311fbe48485e6d424f8bca Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 12 Dec 2023 00:45:13 -0800 Subject: [PATCH 205/724] Ford: add CAN FD harness info (#30698) * add correct harness to f150 and angled mount * can remove this --- selfdrive/car/ford/values.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 301847b7ea..68f276d4b5 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,12 +1,12 @@ from collections import defaultdict -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum, StrEnum from typing import Dict, List, Union from cereal import car from openpilot.selfdrive.car import AngleRateLimit, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ - Device + Device from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -74,11 +74,13 @@ class Footnote(Enum): @dataclass class FordCarInfo(CarInfo): package: str = "Co-Pilot360 Assist+" - car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.ford_q3])) def init_make(self, CP: car.CarParams): - if CP.carFingerprint in (CAR.BRONCO_SPORT_MK1, CAR.MAVERICK_MK1): - self.car_parts = CarParts([Device.threex_angled_mount, CarHarness.ford_q3]) + harness = CarHarness.ford_q4 if CP.carFingerprint in CANFD_CAR else CarHarness.ford_q3 + if CP.carFingerprint in (CAR.BRONCO_SPORT_MK1, CAR.MAVERICK_MK1, CAR.F_150_MK14): + self.car_parts = CarParts([Device.threex_angled_mount, harness]) + else: + self.car_parts = CarParts([Device.threex, harness]) CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { From ad0a882c1ced9f03abd3f85b40dca6fb3262a8dc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 12 Dec 2023 00:50:42 -0800 Subject: [PATCH 206/724] 2022 --- docs/CARS.md | 2 +- selfdrive/car/hyundai/values.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 2bec3dfb50..490fb59e13 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -115,7 +115,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|EV6 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index c5162786a6..840f6d7a42 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -281,7 +281,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia EV6 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])) ], CAR.KIA_CARNIVAL_4TH_GEN: [ - HyundaiCarInfo("Kia Carnival 2023-24", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarInfo("Kia Carnival 2022-24", car_parts=CarParts.common([CarHarness.hyundai_a])), HyundaiCarInfo("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k])) ], @@ -2101,6 +2101,7 @@ FW_VERSIONS = { b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', From fc0daa9693361b51e787500e8e40f0cdffc44737 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 12 Dec 2023 00:51:05 -0800 Subject: [PATCH 207/724] Revert "2022" This reverts commit ad0a882c1ced9f03abd3f85b40dca6fb3262a8dc. --- docs/CARS.md | 2 +- selfdrive/car/hyundai/values.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 490fb59e13..2bec3dfb50 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -115,7 +115,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|EV6 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 840f6d7a42..c5162786a6 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -281,7 +281,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia EV6 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])) ], CAR.KIA_CARNIVAL_4TH_GEN: [ - HyundaiCarInfo("Kia Carnival 2022-24", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarInfo("Kia Carnival 2023-24", car_parts=CarParts.common([CarHarness.hyundai_a])), HyundaiCarInfo("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k])) ], @@ -2101,7 +2101,6 @@ FW_VERSIONS = { b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', - b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', From a03a931737fed35f4ff203236039d1ccc4c7de96 Mon Sep 17 00:00:00 2001 From: Rabin Karki Date: Tue, 12 Dec 2023 03:52:36 -0500 Subject: [PATCH 208/724] HKG: Add FW Versions for Kia Carnival 2022 (#30677) * HKG: Add FW Versions for Kia Carnival 2022 * 2022 * should --------- Co-authored-by: Shane Smiskol --- docs/CARS.md | 2 +- selfdrive/car/hyundai/values.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 2bec3dfb50..490fb59e13 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -115,7 +115,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|EV6 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index c5162786a6..840f6d7a42 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -281,7 +281,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia EV6 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])) ], CAR.KIA_CARNIVAL_4TH_GEN: [ - HyundaiCarInfo("Kia Carnival 2023-24", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarInfo("Kia Carnival 2022-24", car_parts=CarParts.common([CarHarness.hyundai_a])), HyundaiCarInfo("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k])) ], @@ -2101,6 +2101,7 @@ FW_VERSIONS = { b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', From be33d29d7a437201386638570fbda9e085a9910c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 12 Dec 2023 09:45:34 -0800 Subject: [PATCH 209/724] Simulator: --simulator is no longer an argument (#30700) these are also gone --- tools/sim/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/sim/README.md b/tools/sim/README.md index 72727f765d..f6ddde2580 100644 --- a/tools/sim/README.md +++ b/tools/sim/README.md @@ -13,7 +13,7 @@ First, start openpilot. ## Bridge usage ``` $ ./run_bridge.py -h -usage: run_bridge.py [-h] [--joystick] [--high_quality] [--dual_camera] [--simulator SIMULATOR] +usage: run_bridge.py [-h] [--joystick] [--high_quality] [--dual_camera] Bridge between the simulator and openpilot. options: @@ -21,7 +21,6 @@ options: --joystick --high_quality --dual_camera - --simulator SIMULATOR ``` #### Bridge Controls: @@ -47,5 +46,5 @@ options: ### Launching Metadrive Start bridge processes located in tools/sim: ``` bash -./run_bridge.py --simulator metadrive +./run_bridge.py ``` \ No newline at end of file From aaff6aebcaf3bece8ae741d3e12abf764bf635c5 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:57:12 -0600 Subject: [PATCH 210/724] =?UTF-8?q?VW=20MQB:=20Add=20FW=20for=202020=20?= =?UTF-8?q?=C5=A0koda=20Superb=20(#30699)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- selfdrive/car/volkswagen/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 13508aa1fb..c47dd2d161 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1394,6 +1394,7 @@ FW_VERSIONS = { b'\xf1\x8704L906026KB\xf1\x894071', b'\xf1\x8704L906026KD\xf1\x894798', b'\xf1\x8704L906026MT\xf1\x893076', + b'\xf1\x8705L906022BK\xf1\x899971', b'\xf1\x873G0906259 \xf1\x890004', b'\xf1\x873G0906259B \xf1\x890002', b'\xf1\x873G0906259L \xf1\x890003', @@ -1414,6 +1415,7 @@ FW_VERSIONS = { b'\xf1\x870GC300014M \xf1\x892801', b'\xf1\x870GC300019G \xf1\x892803', b'\xf1\x870GC300043 \xf1\x892301', + b'\xf1\x870GC300046D \xf1\x892402', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111', @@ -1423,6 +1425,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655AT\xf1\x890317\xf1\x82\x1331310031313100313131013131319331313100', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100', b'\xf1\x875Q0959655BK\xf1\x890336\xf1\x82\x1331310031313100313131013141319331413100', + b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1333310031313100313152015351539331423100', b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151013141319331423100', b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151823143319331423100', b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1333310031313100313152025350539331463100', From dd54ec9aa8f1991b70d8a012bd0d854462b2c0ff Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 12 Dec 2023 11:24:04 -0800 Subject: [PATCH 211/724] =?UTF-8?q?blue=20diamond=20model=20=F0=9F=92=8E?= =?UTF-8?q?=20(#30504)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * c53b1113-ca16-434f-a3e7-457a36ba98cc/700 * 672ee0a5-0466-420d-be18-0610c5d71b6d/700 * update model replay ref commit --- selfdrive/modeld/models/supercombo.onnx | 4 ++-- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index e2897b08a5..9697c01793 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b7184d46dc2ba3f84eb748252634205105a3742bac29942ae4380b0332fa850 -size 52524758 +oid sha256:cf6133c5bff295a3ee69eeb01297ba77adb6b83dbc1d774442a48117dbaf4626 +size 48457192 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 977f19e7bc..bb111e22e2 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -35a05ff1b68062c9478b2adfe96f1c294bee1b6c +91cd2bf71771c2770c0effc26c0bb23d27208138 From 43e1409fd2fa4a92350a54434602b71b1874fd60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Tue, 12 Dec 2023 14:09:07 -0800 Subject: [PATCH 212/724] metadrive: fix wasd steer signs (#30623) * Swap AD signs * Fix type annotation * fix user torque --------- Co-authored-by: Justin Newberry --- tools/sim/bridge/common.py | 2 +- tools/sim/lib/keyboard_ctrl.py | 4 ++-- tools/sim/lib/manual_ctrl.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/sim/bridge/common.py b/tools/sim/bridge/common.py index a0e6538b2c..35656f39f3 100644 --- a/tools/sim/bridge/common.py +++ b/tools/sim/bridge/common.py @@ -147,7 +147,7 @@ Ignition: {self.simulator_state.ignition} Engaged: {self.simulator_state.is_enga self.simulator_state.user_brake = brake_manual self.simulator_state.user_gas = throttle_manual - self.simulator_state.user_torque = steer_manual * 10000 + self.simulator_state.user_torque = steer_manual * -10000 steer_manual = steer_manual * -40 diff --git a/tools/sim/lib/keyboard_ctrl.py b/tools/sim/lib/keyboard_ctrl.py index c37f6ea479..f03d79c312 100644 --- a/tools/sim/lib/keyboard_ctrl.py +++ b/tools/sim/lib/keyboard_ctrl.py @@ -62,11 +62,11 @@ def keyboard_poll_thread(q: 'Queue[str]'): elif c == 'w': q.put("throttle_%f" % 1.0) elif c == 'a': - q.put("steer_%f" % 0.15) + q.put("steer_%f" % -0.15) elif c == 's': q.put("brake_%f" % 1.0) elif c == 'd': - q.put("steer_%f" % -0.15) + q.put("steer_%f" % 0.15) elif c == 'z': q.put("blinker_left") elif c == 'x': diff --git a/tools/sim/lib/manual_ctrl.py b/tools/sim/lib/manual_ctrl.py index 04e228c18c..5e826e7baa 100755 --- a/tools/sim/lib/manual_ctrl.py +++ b/tools/sim/lib/manual_ctrl.py @@ -183,6 +183,6 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: if __name__ == '__main__': from multiprocessing import Process, Queue - q: Queue[str] = Queue() + q: 'Queue[str]' = Queue() p = Process(target=wheel_poll_thread, args=(q,)) p.start() From e62756596b82e4e40d02a30b34d3f3c9960bffe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Tue, 12 Dec 2023 14:14:51 -0800 Subject: [PATCH 213/724] controlsd: require joystick mode param for the body (#30680) * Update joystick mode in step * Stop putting the body in joystick mode by default * Toggle JoystickDebugMode by default for notCars in process_replay * Remove subsection about web joystick * Update param only in non-cars * Make joystickDebug non-static event, and add it in a loop * Add comment --- selfdrive/controls/controlsd.py | 15 +++++++++------ selfdrive/test/process_replay/process_replay.py | 3 +++ tools/joystick/README.md | 11 ----------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 41866efc3d..bbfde95cd6 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -93,8 +93,7 @@ class Controls: else: self.CI, self.CP = CI, CI.CP - self.joystick_enabled = self.params.get_bool("JoystickDebugMode") - self.joystick_mode = self.joystick_enabled or self.CP.notCar + self.joystick_mode = self.params.get_bool("JoystickDebugMode") # set alternative experiences from parameters self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") @@ -192,9 +191,6 @@ class Controls: set_offroad_alert("Offroad_NoFirmware", True) elif self.CP.passive: self.events.add(EventName.dashcamMode, static=True) - elif self.joystick_mode: - self.events.add(EventName.joystickDebug, static=True) - self.startup_event = None # controlsd is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) @@ -215,6 +211,11 @@ class Controls: self.events.clear() + # Add joystick event, static on cars, dynamic on nonCars + if self.joystick_mode: + self.events.add(EventName.joystickDebug) + self.startup_event = None + # Add startup event if self.startup_event is not None: self.events.add(self.startup_event) @@ -371,7 +372,7 @@ class Controls: else: self.logged_comm_issue = None - if not (self.CP.notCar and self.joystick_enabled): + if not (self.CP.notCar and self.joystick_mode): if not self.sm['lateralPlan'].mpcSolutionValid: self.events.add(EventName.plannerError) if not self.sm['liveLocationKalman'].posenetOK: @@ -853,6 +854,8 @@ class Controls: self.is_metric = self.params.get_bool("IsMetric") self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl + if self.CP.notCar: + self.joystick_mode = self.params.get_bool("JoystickDebugMode") # Sample data from sockets and get a carState CS = self.data_sample() diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index c3c3670a74..19f1c127a6 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -749,6 +749,9 @@ def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=Non if CP.openpilotLongitudinalControl: params_dict["ExperimentalLongitudinalEnabled"] = True + if CP.notCar: + params_dict["JoystickDebugMode"] = True + return params_dict diff --git a/tools/joystick/README.md b/tools/joystick/README.md index 65422afe80..aea93dad53 100644 --- a/tools/joystick/README.md +++ b/tools/joystick/README.md @@ -45,17 +45,6 @@ In order to use a joystick over the network, we need to run joystickd locally fr tools/joystick/joystickd.py ``` -### Web joystick on your mobile device - -A browser-based virtual joystick designed for touch screens. Starts automatically when installed on comma body (non-car robotics platform). -For cars, start the web joystick service manually via SSH before starting the car. - -```shell -tools/joystick/web.py -``` - -After starting the car/body, open the web joystick app at this URL: `http://[comma three IP address]:5000` - --- Now start your car and openpilot should go into joystick mode with an alert on startup! The status of the axes will display on the alert, while button statuses print in the shell. From 7931561416087ac0ceaa64f8ec1e920d89cc358d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 12 Dec 2023 15:19:57 -0800 Subject: [PATCH 214/724] workflow docs (#30703) * start workflow docs * update * update * linter * update * update --- docs/CONTRIBUTING.md | 8 -------- docs/WORKFLOW.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 docs/WORKFLOW.md diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7a074f12de..043ac7a9c7 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -37,12 +37,4 @@ We've released a [Model Port guide](https://blog.comma.ai/openpilot-port-guide-f If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/). -## Testing -### Automated Testing - -All PRs and commits are automatically checked by GitHub Actions. Check out `.github/workflows/` for what GitHub Actions runs. Any new tests should be added to GitHub Actions. - -### Code Style and Linting - -Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`. diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md new file mode 100644 index 0000000000..485adaf1de --- /dev/null +++ b/docs/WORKFLOW.md @@ -0,0 +1,42 @@ +# openpilot development workflow + +Aside from the ML models, most tools used for openpilot development are in this repo. + +Most development happens on normal Ubuntu workstations, and not in cars or directly on comma devices. + +## Quick start + +```bash +# get the latest stuff +git pull +git submodule update --init --recursive + +# update dependencies +tools/ubuntu_setup.sh + +# build everything +scons -j$(nproc) + +# build just the ui with either of these +scons -j8 selfdrive/ui/ +cd selfdrive/ui/ && scons -u -j8 + +# test everything +pytest . + +# test just logging services +cd system/loggerd && pytest . + +# run the linter +pre-commit run --all +``` + +## Testing + +### Automated Testing + +All PRs and commits are automatically checked by GitHub Actions. Check out `.github/workflows/` for what GitHub Actions runs. Any new tests should be added to GitHub Actions. + +### Code Style and Linting + +Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`. From 8184186dd3f187c7f7ae967d75c9df0dc9ebd629 Mon Sep 17 00:00:00 2001 From: Yassine Date: Tue, 12 Dec 2023 15:53:15 -0800 Subject: [PATCH 215/724] update release notes --- RELEASES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index e9864a0d58..41b9b946d8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,8 @@ Version 0.9.6 (2023-12-14) ======================== +* New driving model + * Vision model trained on more data + * Improved driving performance * AGNOS 9 * comma body streaming and controls over WebRTC * Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! From 4f3a186a6faa9f95b0764069a79f86693973bfb2 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 12 Dec 2023 15:58:24 -0800 Subject: [PATCH 216/724] soundd/micd: retry getting stream (#30701) retry soundd/micd --- selfdrive/ui/soundd.py | 20 ++++++++++++-------- system/micd.py | 25 ++++++++----------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 5ac503aadf..01148ec199 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -8,12 +8,11 @@ from typing import Dict, Optional, Tuple from cereal import car, messaging from openpilot.common.basedir import BASEDIR from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.realtime import Ratekeeper +from openpilot.common.retry import retry +from openpilot.common.swaglog import cloudlog from openpilot.system import micd -from openpilot.system.hardware import TICI - -from openpilot.common.realtime import Ratekeeper -from openpilot.common.swaglog import cloudlog SAMPLE_RATE = 48000 SAMPLE_BUFFER = 4096 # (approx 100ms) @@ -127,16 +126,21 @@ class Soundd: volume = ((weighted_db - AMBIENT_DB) / DB_SCALE) * (MAX_VOLUME - MIN_VOLUME) + MIN_VOLUME return math.pow(10, (np.clip(volume, MIN_VOLUME, MAX_VOLUME) - 1)) + @retry(attempts=7, delay=3) + def get_stream(self, sd): + # reload sounddevice to reinitialize portaudio + sd._terminate() + sd._initialize() + return sd.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER) + def soundd_thread(self): # sounddevice must be imported after forking processes import sounddevice as sd - if TICI: - micd.wait_for_devices(sd) # wait for alsa to be initialized on device + sm = messaging.SubMaster(['controlsState', 'microphone']) - with sd.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER) as stream: + with self.get_stream(sd) as stream: rk = Ratekeeper(20) - sm = messaging.SubMaster(['controlsState', 'microphone']) cloudlog.info(f"soundd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}, {stream.blocksize=}") while True: diff --git a/system/micd.py b/system/micd.py index baf75c17b9..8b738ebe93 100755 --- a/system/micd.py +++ b/system/micd.py @@ -5,7 +5,6 @@ from cereal import messaging from openpilot.common.realtime import Ratekeeper from openpilot.common.retry import retry from openpilot.common.swaglog import cloudlog -from openpilot.system.hardware import TICI RATE = 10 FFT_SAMPLES = 4096 @@ -14,18 +13,6 @@ SAMPLE_RATE = 44100 SAMPLE_BUFFER = 4096 # (approx 100ms) -@retry(attempts=7, delay=3) -def wait_for_devices(sd): - # reload sounddevice to reinitialize portaudio - sd._terminate() - sd._initialize() - - devices = sd.query_devices() - cloudlog.info(f"sounddevice available devices: {list(devices)}") - - assert len(devices) > 0 - - def calculate_spl(measurements): # https://www.engineeringtoolbox.com/sound-pressure-d_711.html sound_pressure = np.sqrt(np.mean(measurements ** 2)) # RMS of amplitudes @@ -92,14 +79,18 @@ class Mic: self.measurements = self.measurements[FFT_SAMPLES:] + @retry(attempts=7, delay=3) + def get_stream(self, sd): + # reload sounddevice to reinitialize portaudio + sd._terminate() + sd._initialize() + return sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER) + def micd_thread(self): # sounddevice must be imported after forking processes import sounddevice as sd - if TICI: - wait_for_devices(sd) # wait for alsa to be initialized on device - - with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER) as stream: + with self.get_stream(sd) as stream: cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}, {stream.blocksize=}") while True: self.update() From 595041ffc0a2fd8ab6eb09e366d7cd4ddd262781 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 12 Dec 2023 16:40:21 -0800 Subject: [PATCH 217/724] add car porting docs (#30704) * add car porting docs * add cabana * absolute link * absolute link * absolute link * selfdrive/car --- selfdrive/car/README.MD | 76 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/selfdrive/car/README.MD b/selfdrive/car/README.MD index de4db0ee50..19a33085f0 100644 --- a/selfdrive/car/README.MD +++ b/selfdrive/car/README.MD @@ -1,11 +1,77 @@ -## Port structure -##### interface.py +# selfdrive/car + +### Checkout this blogpost for a high level overview of car ports +https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/ + +## Useful car porting utilities + +Testing car ports in your car is very time consuming! Checkout these utilities to do basic checks on your work before running it in your car. + +### [Cabana](/tools/cabana/README.md) + +View your cars CAN signals through DBC files, which openpilot uses to parse and create messages that talk to the car. + +Example: +```bash +> tools/cabana/cabana '1bbe6bf2d62f58a8|2022-07-14--17-11-43' +``` + +### [selfdrive/debug/auto_fingerprint.py](/selfdrive/debug/auto_fingerprint.py) + +Given a route and platform, automatically inserts fw fingerprints from the platform into the correct place in values.py + +Example: +```bash +> python selfdrive/debug/auto_fingerprint.py '1bbe6bf2d62f58a8|2022-07-14--17-11-43' 'SUBARU OUTBACK 6TH GEN' +Attempting to add fw version for: SUBARU OUTBACK 6TH GEN +``` + +### [selfdrive/car/tests/test_car_interfaces.py](/selfdrive/car/tests/test_car_interfaces.py) + +Finds common bugs for car interfaces, without even requiring a route! + + +#### Example: Typo in signal name +```bash +> pytest selfdrive/car/tests/test_car_interfaces.py -k subaru # (replace with the brand you are working on!) + +===================================================================== +FAILED selfdrive/car/tests/test_car_interfaces.py::TestCarInterfaces::test_car_interfaces_165_SUBARU_LEGACY_7TH_GEN - KeyError: 'CruiseControlOOPS' + +``` + +### [selfdrive/debug/test_car_model.py](/selfdrive/debug/test_car_model.py) + +Given a route, runs most of the car interface to check for common errors like missing signals, blocked panda messages, and mismatches. + +#### Example: Panda safety mismatch for gasPressed +```bash +> python selfdrive/debug/test_car_model.py '4822a427b188122a|2023-08-14--16-22-21' + +===================================================================== +FAIL: test_panda_safety_carstate (__main__.CarModelTestCase.test_panda_safety_carstate) +Assert that panda safety matches openpilot's carState +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/home/batman/xx/openpilot/openpilot/selfdrive/car/tests/test_models.py", line 380, in test_panda_safety_carstate + self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") +AssertionError: 1 is not false : panda safety doesn't agree with openpilot: {'gasPressed': 116} +``` + + +## Car Port structure + +### interface.py Generic interface to send and receive messages from CAN (controlsd uses this to communicate with car) -##### carcontroller.py + +### carcontroller.py Builds CAN messages to send to car + ##### carstate.py Reads CAN from car and builds openpilot CarState message + ##### values.py -Fingerprints and absolute limits +Fingerprints, limits for actuation, car doc information, etc + ##### radar_interface.py -Radar interface +Interface for parsing radar points from the car From 3a97bde8635c47c314b71acc065a2e5eb2f46830 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 12 Dec 2023 17:27:42 -0800 Subject: [PATCH 218/724] Update contributing (#30705) * update * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Update CONTRIBUTING.md * Update CONTRIBUTING.md --- docs/CONTRIBUTING.md | 50 +++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 043ac7a9c7..777788630b 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,26 +1,46 @@ # How to contribute -Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/). - -Most open source development activity is coordinated through our [GitHub Discussions](https://github.com/commaai/openpilot/discussions) and [Discord](https://discord.comma.ai). A lot of documentation is available at https://docs.comma.ai and on our [blog](https://blog.comma.ai/). +Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/). Development activity is coordinated through our GitHub Issues, [GitHub Discussions](https://github.com/commaai/openpilot/discussions), and [Discord](https://discord.comma.ai). ### Getting Started - * Setup your [development environment](../tools/) - * Join our [Discord](https://discord.comma.ai) - * Make sure you have a [GitHub account](https://github.com/signup/free) - * Fork [our repositories](https://github.com/commaai) on GitHub +* Setup your [development environment](../tools/) +* Read about the [development workflow](WORKFLOW.md) +* Join our [Discord](https://discord.comma.ai) +* Docs are at https://docs.comma.ai and https://blog.comma.ai + +### What contributions are we looking for? + +**openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all developoment is towards that goal. + +The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged. +If a PR offers *some* value but will take lots of time to get merged, it will be closed. +Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. + +All of these are examples of good PRs: +* typo fix: https://github.com/commaai/openpilot/pull/30678 +* removing unused code: https://github.com/commaai/openpilot/pull/30573 +* simple car model port: https://github.com/commaai/openpilot/pull/30245 +* car brand port: https://github.com/commaai/openpilot/pull/23331 + +### What doesn't get merged? + +* arbitrary style changes +* PRs without a clear goal +* 500+ line PRs: it should be either cleaned up, broken up into smaller PRs, or both ### First contribution -Try out some of these first pull requests ideas to dive into the codebase: -* Increase our [mypy](http://mypy-lang.org/) coverage -* Write some documentation -* Tackle an open [good first issue](https://github.com/commaai/openpilot/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) +Check out any [good first issue](https://github.com/commaai/openpilot/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) to get started. + +### What do I need to contribute? + +A lot of openpilot work requires only a PC, and some requires a comma device. +Most car-related contributions require access to that car, plus a comma device installed in the car. ## Pull Requests -Pull requests should be against the master branch. Welcomed contributions include bug reports, car ports, and any [open issue](https://github.com/commaai/openpilot/issues). If you're unsure about a contribution, feel free to open a discussion, issue, or draft PR to discuss the problem you're trying to solve. +Pull requests should be against the master branch. If you're unsure about a contribution, feel free to open a discussion, issue, or draft PR to discuss the problem you're trying to solve. A good pull request has all of the following: * a clearly stated purpose @@ -37,4 +57,10 @@ We've released a [Model Port guide](https://blog.comma.ai/openpilot-port-guide-f If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/). +## Contributing without Code +* Report bugs in GitHub issues. +* Report driving issues in the `#driving-feedback` Discord channel. +* Consider opting into driver camera uploads to improve the driver monitoring model. +* Connect your device to Wi-Fi regularly, so that we can pull data for training better driving models. +* Run the `nightly` branch and report issues. This branch is like `master` but it's built just like a release. From c066fdd7a32b2d5b419419ac3f19ab4e8ba34280 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 12 Dec 2023 17:48:03 -0800 Subject: [PATCH 219/724] jenkins: remove old root user permission fixes (#30684) * Batman * add debug stuff * Revert that for now * remove debug stuff --- Jenkinsfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index bd894f53e0..4e8575a0d8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -92,10 +92,6 @@ def pcStage(String stageName, Closure body) { docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .").inside(dockerArgs) { timeout(time: 20, unit: 'MINUTES') { try { - // TODO: remove these after all jenkins jobs are running as batman (merged with master) - sh "sudo chown -R batman:batman /tmp/scons_cache" - sh "sudo chown -R batman:batman /tmp/comma_download_cache" - sh "git config --global --add safe.directory '*'" sh "git submodule update --init --recursive" sh "git lfs pull" From 93cd19e3a553d34d2f8bcd1aea121d09a7b2196b Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 12 Dec 2023 17:54:17 -0800 Subject: [PATCH 220/724] jenkins: retry pc setup (#30659) * retry pc setup * sleep and retry * exception * throw e * remove force exit * make a function for it * remove force exit * move to top * separate exception --- Jenkinsfile | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4e8575a0d8..84f87bbdd9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,3 +1,15 @@ +def retryWithDelay(int maxRetries, int delay, Closure body) { + for (int i = 0; i < maxRetries; i++) { + try { + body() + return + } catch (Exception e) { + sleep(delay) + } + } + throw Exception("Failed after ${maxRetries} retries") +} + def device(String ip, String step_label, String cmd) { withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) { def ssh_cmd = """ @@ -92,18 +104,20 @@ def pcStage(String stageName, Closure body) { docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .").inside(dockerArgs) { timeout(time: 20, unit: 'MINUTES') { try { - sh "git config --global --add safe.directory '*'" - sh "git submodule update --init --recursive" - sh "git lfs pull" + retryWithDelay (3, 15) { + sh "git config --global --add safe.directory '*'" + sh "git submodule update --init --recursive" + sh "git lfs pull" + } body() } finally { - sh "rm -rf ${env.WORKSPACE}/* || true" - sh "rm -rf .* || true" + sh "rm -rf ${env.WORKSPACE}/* || true" + sh "rm -rf .* || true" + } } } } } - } } def setupCredentials() { From 02373fcb05e3c5fcb3fe2596024bca02fa7f8ab8 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 12 Dec 2023 18:12:19 -0800 Subject: [PATCH 221/724] jenkins: retry docker_build (#30629) * retry docker build * retry docker build * retry docker build * does this work nicely? * docker fail! * Revert "docker fail!" This reverts commit ab227a1ea37fad3d6413081e4516211b474b0712. --- Jenkinsfile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 84f87bbdd9..2118c4c4ed 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,8 +1,7 @@ def retryWithDelay(int maxRetries, int delay, Closure body) { for (int i = 0; i < maxRetries; i++) { try { - body() - return + return body() } catch (Exception e) { sleep(delay) } @@ -101,7 +100,12 @@ def pcStage(String stageName, Closure body) { checkout scm def dockerArgs = "--user=batman -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache -e PYTHONPATH=${env.WORKSPACE}"; - docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .").inside(dockerArgs) { + + def openpilot_base = retryWithDelay (3, 15) { + return docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .") + } + + openpilot_base.inside(dockerArgs) { timeout(time: 20, unit: 'MINUTES') { try { retryWithDelay (3, 15) { From 5d80858a16cfc1ab36f16ac3ed464eb6010c68a4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 12 Dec 2023 18:17:10 -0800 Subject: [PATCH 222/724] cleanup car port docs (#30707) readme --- selfdrive/car/README.MD | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/README.MD b/selfdrive/car/README.MD index 19a33085f0..2dcbf56059 100644 --- a/selfdrive/car/README.MD +++ b/selfdrive/car/README.MD @@ -1,6 +1,6 @@ # selfdrive/car -### Checkout this blogpost for a high level overview of car ports +### Check out this blogpost for a high level overview of car ports https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/ ## Useful car porting utilities @@ -9,7 +9,7 @@ Testing car ports in your car is very time consuming! Checkout these utilities t ### [Cabana](/tools/cabana/README.md) -View your cars CAN signals through DBC files, which openpilot uses to parse and create messages that talk to the car. +View your car's CAN signals through DBC files, which openpilot uses to parse and create messages that talk to the car. Example: ```bash @@ -18,7 +18,7 @@ Example: ### [selfdrive/debug/auto_fingerprint.py](/selfdrive/debug/auto_fingerprint.py) -Given a route and platform, automatically inserts fw fingerprints from the platform into the correct place in values.py +Given a route and platform, automatically inserts FW fingerprints from the platform into the correct place in values.py Example: ```bash From a4179a7c230e687bebd15581ccdb3f7d34a690f4 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 13 Dec 2023 11:15:47 +0800 Subject: [PATCH 223/724] encoder: reduce memory allocations and copying (#28704) * reduce memory allocations and copying * #include --- system/loggerd/encoder/encoder.cc | 11 +++++++---- system/loggerd/encoder/encoder.h | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/system/loggerd/encoder/encoder.cc b/system/loggerd/encoder/encoder.cc index 869b4617b3..0aba4b8b49 100644 --- a/system/loggerd/encoder/encoder.cc +++ b/system/loggerd/encoder/encoder.cc @@ -27,8 +27,11 @@ void VideoEncoder::publisher_publish(VideoEncoder *e, int segment_num, uint32_t edat.setData(dat); if (flags & V4L2_BUF_FLAG_KEYFRAME) edat.setHeader(header); - auto words = new kj::Array(capnp::messageToFlatArray(msg)); - auto bytes = words->asBytes(); - e->pm->send(e->encoder_info.publish_name, bytes.begin(), bytes.size()); - delete words; + uint32_t bytes_size = capnp::computeSerializedSizeInWords(msg) * sizeof(capnp::word); + if (e->msg_cache.size() < bytes_size) { + e->msg_cache.resize(bytes_size); + } + kj::ArrayOutputStream output_stream(kj::ArrayPtr(e->msg_cache.data(), bytes_size)); + capnp::writeMessage(output_stream, msg); + e->pm->send(e->encoder_info.publish_name, e->msg_cache.data(), bytes_size); } diff --git a/system/loggerd/encoder/encoder.h b/system/loggerd/encoder/encoder.h index a8bfd5c054..9c23928cc6 100644 --- a/system/loggerd/encoder/encoder.h +++ b/system/loggerd/encoder/encoder.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "cereal/messaging/messaging.h" #include "cereal/visionipc/visionipc.h" @@ -23,7 +24,6 @@ public: static void publisher_publish(VideoEncoder *e, int segment_num, uint32_t idx, VisionIpcBufExtra &extra, unsigned int flags, kj::ArrayPtr header, kj::ArrayPtr dat); - protected: int in_width, in_height; const EncoderInfo encoder_info; @@ -32,4 +32,5 @@ private: // total frames encoded int cnt = 0; std::unique_ptr pm; + std::vector msg_cache; }; From 73db7e8e3a9daec01c7e7b7cfb404a1687544b21 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 12 Dec 2023 19:15:25 -0800 Subject: [PATCH 224/724] Revert "compressed_vipc: fix issues with VisionIpcServer in ZMQ mode (#30115)" This reverts commit 0e5cfa3426a3cc2e270c239169a126cdd233865d. --- tools/camerastream/compressed_vipc.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/camerastream/compressed_vipc.py b/tools/camerastream/compressed_vipc.py index c6e05a0af8..ac7ca2520b 100755 --- a/tools/camerastream/compressed_vipc.py +++ b/tools/camerastream/compressed_vipc.py @@ -19,11 +19,7 @@ ENCODE_SOCKETS = { VisionStreamType.VISION_STREAM_DRIVER: "driverEncodeData", } -def decoder(addr, vst, nvidia, debug=False): - vipc_server = VisionIpcServer("camerad") - vipc_server.create_buffers(vst, 4, False, W, H) - vipc_server.start_listener() - +def decoder(addr, vipc_server, vst, nvidia, debug=False): sock_name = ENCODE_SOCKETS[vst] if debug: print("start decoder for %s" % sock_name) @@ -105,9 +101,14 @@ def decoder(addr, vst, nvidia, debug=False): class CompressedVipc: def __init__(self, addr, vision_streams, nvidia=False, debug=False): + self.vipc_server = VisionIpcServer("camerad") + for vst in vision_streams: + self.vipc_server.create_buffers(vst, 4, False, W, H) + self.vipc_server.start_listener() + self.procs = [] for vst in vision_streams: - p = multiprocessing.Process(target=decoder, args=(addr, vst, nvidia, debug)) + p = multiprocessing.Process(target=decoder, args=(addr, self.vipc_server, vst, nvidia, debug)) p.start() self.procs.append(p) From fb977e45cdbc8852cb65f4aa153cb1e9a59accac Mon Sep 17 00:00:00 2001 From: Cone Guy <153409307+cone-guy@users.noreply.github.com> Date: Tue, 12 Dec 2023 22:24:36 -0500 Subject: [PATCH 225/724] Link to setup guide in WORKFLOW.md (#30708) * Mention dev containers and WSL * Update WORKFLOW.md --------- Co-authored-by: Adeeb Shihadeh --- docs/WORKFLOW.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index 485adaf1de..85ae8d86f4 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -2,7 +2,7 @@ Aside from the ML models, most tools used for openpilot development are in this repo. -Most development happens on normal Ubuntu workstations, and not in cars or directly on comma devices. +Most development happens on normal Ubuntu workstations, and not in cars or directly on comma devices. See the [setup guide](../tools) for getting your PC setup for openpilot development. ## Quick start From 2192cc170c3231186704b0929e1ee81d318e940e Mon Sep 17 00:00:00 2001 From: Isaac <48419673+coffee-cake-isaac@users.noreply.github.com> Date: Tue, 12 Dec 2023 23:07:59 -0500 Subject: [PATCH 226/724] Ford: Add F-150 Lightning (#30692) * Update interface.py * Update values.py * Update routes.py * Update override.yaml * Update values.py * Update interface.py * Update selfdrive/car/ford/interface.py Co-authored-by: Adeeb Shihadeh --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/car/ford/interface.py | 12 ++++++++++-- selfdrive/car/ford/values.py | 19 ++++++++++++++++++- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/override.yaml | 1 + 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 3045c317ff..b6bc834ef0 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -14,7 +14,7 @@ class CarInterface(CarInterfaceBase): @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "ford" - ret.dashcamOnly = candidate in {CAR.F_150_MK14} + ret.dashcamOnly = candidate in CANFD_CAR ret.radarUnavailable = True ret.steerControlType = car.CarParams.SteerControlType.angle @@ -56,6 +56,12 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 17.0 ret.mass = 2000 + elif candidate == CAR.F_150_LIGHTNING_MK1: + # required trim only on SuperCrew + ret.wheelbase = 3.70 + ret.steerRatio = 16.9 + ret.mass = 2948 + elif candidate == CAR.FOCUS_MK4: ret.wheelbase = 2.7 ret.steerRatio = 15.0 @@ -94,7 +100,9 @@ class CarInterface(CarInterfaceBase): events = self.create_common_events(ret, extra_gears=[GearShifter.manumatic]) if not self.CS.vehicle_sensors_valid: events.add(car.CarEvent.EventName.vehicleSensorsInvalid) - if self.CS.hybrid_platform: + + # Ford Q3 Hybrids are disabled due to occasional incorrect checksums some stock messages. This must be root caused before enabling support. + if self.CS.hybrid_platform and self.CP.carFingerprint not in CANFD_CAR: events.add(car.CarEvent.EventName.startupNoControl) ret.events = events.to_msg() diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 68f276d4b5..98f94f26f6 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -47,9 +47,10 @@ class CAR(StrEnum): F_150_MK14 = "FORD F-150 14TH GEN" FOCUS_MK4 = "FORD FOCUS 4TH GEN" MAVERICK_MK1 = "FORD MAVERICK 1ST GEN" + F_150_LIGHTNING_MK1 = "FORD F-150 LIGHTNING 1ST GEN" -CANFD_CAR = {CAR.F_150_MK14} +CANFD_CAR = {CAR.F_150_MK14, CAR.F_150_LIGHTNING_MK1} class RADAR: @@ -61,6 +62,7 @@ DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base # F-150 radar is not yet supported DBC[CAR.F_150_MK14] = dbc_dict("ford_lincoln_base_pt", None) +DBC[CAR.F_150_LIGHTNING_MK1] = dbc_dict("ford_lincoln_base_pt", None) class Footnote(Enum): @@ -94,6 +96,7 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"), ], CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"), + CAR.F_150_LIGHTNING_MK1: FordCarInfo("Ford F-150 Lightning 2021-23", "Co-Pilot360 Active 2.0"), CAR.FOCUS_MK4: FordCarInfo("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS]), CAR.MAVERICK_MK1: [ FordCarInfo("Ford Maverick 2022", "LARIAT Luxury"), @@ -236,6 +239,20 @@ FW_VERSIONS = { (Ecu.engine, 0x7E0, None): [ b'PL3A-14C204-BRB\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], + }, + CAR.F_150_LIGHTNING_MK1: { + (Ecu.abs, 0x760, None): [ + b'PL38-2D053-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'ML3T-14H102-ABT\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'NL3A-14C204-BAR\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], }, CAR.FOCUS_MK4: { (Ecu.eps, 0x730, None): [ diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 66dafc1312..bf16693f3c 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -52,6 +52,7 @@ routes = [ CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.EXPLORER_MK6), CarTestRoute("e886087f430e7fe7|2023-06-16--23-06-36", FORD.FOCUS_MK4), CarTestRoute("bd37e43731e5964b|2023-04-30--10-42-26", FORD.MAVERICK_MK1), + CarTestRoute("112e4d6e0cad05e1|2023-11-14--08-21-43", FORD.F_150_LIGHTNING_MK1), #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION), CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA), diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 12b483cfa7..09a3456629 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -26,6 +26,7 @@ FORD EXPLORER 6TH GEN: [.nan, 1.5, .nan] FORD F-150 14TH GEN: [.nan, 1.5, .nan] FORD FOCUS 4TH GEN: [.nan, 1.5, .nan] FORD MAVERICK 1ST GEN: [.nan, 1.5, .nan] +FORD F-150 LIGHTNING 1ST GEN: [.nan, 1.5, .nan] ### # No steering wheel From 77066a226068ea933b565b9521c26a99c72e7a6a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 12 Dec 2023 20:37:37 -0800 Subject: [PATCH 227/724] Ford: consolidate comments about unsupported hybrid platforms (#30709) * fix typo, move comments to one place * -appear * Update selfdrive/car/ford/carstate.py --- selfdrive/car/ford/carstate.py | 9 +++++---- selfdrive/car/ford/interface.py | 4 +--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 5c787b787a..9230d16ef9 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -18,15 +18,16 @@ class CarState(CarStateBase): self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"] self.vehicle_sensors_valid = False - self.hybrid_platform = False + self.unsupported_platform = False def update(self, cp, cp_cam): ret = car.CarState.new_message() - # Hybrid variants experience a bug where a message from the PCM sends invalid checksums, - # we do not support these cars at this time. + # Ford Q3 hybrid variants experience a bug where a message from the PCM sends invalid checksums, + # this must be root-caused before enabling support. Ford Q4 hybrids do not have this problem. # TrnAin_Tq_Actl and its quality flag are only set on ICE platform variants - self.hybrid_platform = cp.vl["VehicleOperatingModes"]["TrnAinTq_D_Qf"] == 0 + self.unsupported_platform = (cp.vl["VehicleOperatingModes"]["TrnAinTq_D_Qf"] == 0 and + self.CP.carFingerprint not in CANFD_CAR) # Occasionally on startup, the ABS module recalibrates the steering pinion offset, so we need to block engagement # The vehicle usually recovers out of this state within a minute of normal driving diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index b6bc834ef0..993b4a511c 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -100,9 +100,7 @@ class CarInterface(CarInterfaceBase): events = self.create_common_events(ret, extra_gears=[GearShifter.manumatic]) if not self.CS.vehicle_sensors_valid: events.add(car.CarEvent.EventName.vehicleSensorsInvalid) - - # Ford Q3 Hybrids are disabled due to occasional incorrect checksums some stock messages. This must be root caused before enabling support. - if self.CS.hybrid_platform and self.CP.carFingerprint not in CANFD_CAR: + if self.CS.unsupported_platform: events.add(car.CarEvent.EventName.startupNoControl) ret.events = events.to_msg() From c4759c0a5008b33c2b772a2b2ad27c5d05d50d77 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 12 Dec 2023 20:41:43 -0800 Subject: [PATCH 228/724] Car docs: fixes (#30710) * always lower case * more fixes * link * rm () --- selfdrive/car/{README.MD => README.md} | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) rename selfdrive/car/{README.MD => README.md} (79%) diff --git a/selfdrive/car/README.MD b/selfdrive/car/README.md similarity index 79% rename from selfdrive/car/README.MD rename to selfdrive/car/README.md index 2dcbf56059..b64472b2bc 100644 --- a/selfdrive/car/README.MD +++ b/selfdrive/car/README.md @@ -1,11 +1,10 @@ # selfdrive/car -### Check out this blogpost for a high level overview of car ports -https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/ +Check out [this blog post](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/) for a high-level overview of porting a car. ## Useful car porting utilities -Testing car ports in your car is very time consuming! Checkout these utilities to do basic checks on your work before running it in your car. +Testing car ports in your car is very time-consuming. Check out these utilities to do basic checks on your work before running it in your car. ### [Cabana](/tools/cabana/README.md) @@ -28,12 +27,12 @@ Attempting to add fw version for: SUBARU OUTBACK 6TH GEN ### [selfdrive/car/tests/test_car_interfaces.py](/selfdrive/car/tests/test_car_interfaces.py) -Finds common bugs for car interfaces, without even requiring a route! +Finds common bugs for car interfaces, without even requiring a route. #### Example: Typo in signal name ```bash -> pytest selfdrive/car/tests/test_car_interfaces.py -k subaru # (replace with the brand you are working on!) +> pytest selfdrive/car/tests/test_car_interfaces.py -k subaru # replace with the brand you are working on ===================================================================== FAILED selfdrive/car/tests/test_car_interfaces.py::TestCarInterfaces::test_car_interfaces_165_SUBARU_LEGACY_7TH_GEN - KeyError: 'CruiseControlOOPS' @@ -42,9 +41,9 @@ FAILED selfdrive/car/tests/test_car_interfaces.py::TestCarInterfaces::test_car_i ### [selfdrive/debug/test_car_model.py](/selfdrive/debug/test_car_model.py) -Given a route, runs most of the car interface to check for common errors like missing signals, blocked panda messages, and mismatches. +Given a route, runs most of the car interface to check for common errors like missing signals, blocked panda messages, and safety mismatches. -#### Example: Panda safety mismatch for gasPressed +#### Example: panda safety mismatch for gasPressed ```bash > python selfdrive/debug/test_car_model.py '4822a427b188122a|2023-08-14--16-22-21' @@ -59,7 +58,7 @@ AssertionError: 1 is not false : panda safety doesn't agree with openpilot: {'ga ``` -## Car Port structure +## Car port structure ### interface.py Generic interface to send and receive messages from CAN (controlsd uses this to communicate with car) @@ -71,7 +70,7 @@ Builds CAN messages to send to car Reads CAN from car and builds openpilot CarState message ##### values.py -Fingerprints, limits for actuation, car doc information, etc +Fingerprints, limits for actuation, and supported car documentation ##### radar_interface.py Interface for parsing radar points from the car From 7ca440fe51f109fcd2aef1860bc5bab2a72d693f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 12 Dec 2023 23:59:38 -0800 Subject: [PATCH 229/724] ignore tools/ in codecov (#30711) --- codecov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 40d03dcd7a..519e7d1a38 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,4 +9,5 @@ coverage: ignore: - "**/test_*.py" - "selfdrive/test/**" - - "system/version.py" # codecov changes depending on if we are in a branch or not \ No newline at end of file + - "system/version.py" # codecov changes depending on if we are in a branch or not + - "tools" From d6465e39bd74591d82d9a8c673052f656ed62516 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 13 Dec 2023 11:23:10 -0500 Subject: [PATCH 230/724] Hyundai: Car Port for Staria 2023 (#30672) * Hyundai: Car Port for Staria 2023 * camera scc! * add test route * Update RELEASES.md * Apply suggestions from code review * bump down max torque --------- Co-authored-by: Shane Smiskol --- RELEASES.md | 1 + docs/CARS.md | 3 ++- selfdrive/car/hyundai/interface.py | 4 ++++ selfdrive/car/hyundai/values.py | 14 +++++++++++++- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/override.yaml | 1 + 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 41b9b946d8..4e72c3c986 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -5,6 +5,7 @@ Version 0.9.6 (2023-12-14) * Improved driving performance * AGNOS 9 * comma body streaming and controls over WebRTC +* Hyundai Staria 2023 support thanks to sunnyhaibin! * Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! * Toyota RAV4 2023 support * Toyota RAV4 Hybrid 2023 support diff --git a/docs/CARS.md b/docs/CARS.md index 490fb59e13..bb0a29d159 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 272 Supported Cars +# 273 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -107,6 +107,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Staria 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index d04743502c..88d4e70183 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -150,6 +150,10 @@ class CarInterface(CarInterfaceBase): ret.mass = 1690. # from https://www.hyundai-motor.com.tw/clicktobuy/custin#spec_0 ret.wheelbase = 3.055 ret.steerRatio = 17.0 # from learner + elif candidate == CAR.STARIA_4TH_GEN: + ret.mass = 2205. + ret.wheelbase = 3.273 + ret.steerRatio = 11.94 # https://www.hyundai.com/content/dam/hyundai/au/en/models/staria-load/premium-pip-update-2023/spec-sheet/STARIA_Load_Spec-Table_March_2023_v3.1.pdf # Kia elif candidate == CAR.KIA_SORENTO: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 840f6d7a42..a8d7c06b9e 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -93,6 +93,7 @@ class CAR(StrEnum): SANTA_FE_PHEV_2022 = "HYUNDAI SANTA FE PlUG-IN HYBRID 2022" SONATA = "HYUNDAI SONATA 2020" SONATA_LF = "HYUNDAI SONATA 2019" + STARIA_4TH_GEN = "HYUNDAI STARIA 4TH GEN" TUCSON = "HYUNDAI TUCSON 2019" PALISADE = "HYUNDAI PALISADE 2020" VELOSTER = "HYUNDAI VELOSTER 2019" @@ -203,6 +204,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-23", "All", video_link="https://www.youtube.com/watch?v=ix63r9kE3Fw", car_parts=CarParts.common([CarHarness.hyundai_a])), + CAR.STARIA_4TH_GEN: HyundaiCarInfo("Hyundai Staria 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", car_parts=CarParts.common([CarHarness.hyundai_e])), CAR.TUCSON: [ HyundaiCarInfo("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_l])), @@ -2128,6 +2130,14 @@ FW_VERSIONS = { b'\xf1\x00GL3_ RDR ----- 1.00 1.02 99110-L8000 ', ], }, + CAR.STARIA_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00US4 MFC AT KOR LHD 1.00 1.06 99211-CG000 230524', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00US4_ RDR ----- 1.00 1.00 99110-CG000 ', + ], + }, } CHECKSUM = { @@ -2146,7 +2156,8 @@ CAN_GEARS = { CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, - CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.KIA_K8_HEV_1ST_GEN} + CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.KIA_K8_HEV_1ST_GEN, + CAR.STARIA_4TH_GEN} # The radar does SCC on these cars when HDA I, rather than the camera CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, @@ -2248,4 +2259,5 @@ DBC = { CAR.KIA_K8_HEV_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.CUSTIN_1ST_GEN: dbc_dict('hyundai_kia_generic', None), CAR.KIA_NIRO_PHEV_2022: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), + CAR.STARIA_4TH_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index bf16693f3c..96d589d1a8 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -118,6 +118,7 @@ routes = [ CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_HYBRID_5TH_GEN), CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF), + CarTestRoute("c344fd2492c7a9d2|2023-12-11--09-03-23", HYUNDAI.STARIA_4TH_GEN), CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), CarTestRoute("db68bbe12250812c|2022-12-05--00-54-12", HYUNDAI.TUCSON_4TH_GEN), # 2023 CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN), diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 09a3456629..0272b02d2d 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -65,6 +65,7 @@ HYUNDAI AZERA HYBRID 6TH GEN: [1.8, 1.8, 0.1] KIA K8 HYBRID 1ST GEN: [2.5, 2.5, 0.1] HYUNDAI CUSTIN 1ST GEN: [2.5, 2.5, 0.1] LEXUS GS F 2016: [2.5, 2.5, 0.08] +HYUNDAI STARIA 4TH GEN: [1.8, 2.0, 0.15] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] From 26a67641d2e4a4b908239996c7a9b580c3813d4b Mon Sep 17 00:00:00 2001 From: Isaac <48419673+coffee-cake-isaac@users.noreply.github.com> Date: Wed, 13 Dec 2023 12:05:41 -0500 Subject: [PATCH 231/724] Ford: Add Mach-E (#30691) * Update interface.py * Update values.py * Update routes.py * Update override.yaml * Update selfdrive/car/ford/values.py Co-authored-by: Adeeb Shihadeh * Update interface.py * order * only have dongles for these 42f08a29af3699f4 83a4e056c7072678 from https://github.com/commaai/openpilot/pull/30444 --------- Co-authored-by: Adeeb Shihadeh Co-authored-by: Shane Smiskol --- selfdrive/car/ford/interface.py | 5 +++++ selfdrive/car/ford/values.py | 26 +++++++++++++++++++++++-- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/override.yaml | 1 + 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 993b4a511c..8a8ce36bd0 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -62,6 +62,11 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 16.9 ret.mass = 2948 + elif candidate == CAR.MUSTANG_MACH_E_MK1: + ret.wheelbase = 2.984 + ret.steerRatio = 17.0 # guess + ret.mass = 2200 + elif candidate == CAR.FOCUS_MK4: ret.wheelbase = 2.7 ret.steerRatio = 15.0 diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 98f94f26f6..4b05d474f7 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -48,9 +48,10 @@ class CAR(StrEnum): FOCUS_MK4 = "FORD FOCUS 4TH GEN" MAVERICK_MK1 = "FORD MAVERICK 1ST GEN" F_150_LIGHTNING_MK1 = "FORD F-150 LIGHTNING 1ST GEN" + MUSTANG_MACH_E_MK1 = "FORD MUSTANG MACH-E 1ST GEN" -CANFD_CAR = {CAR.F_150_MK14, CAR.F_150_LIGHTNING_MK1} +CANFD_CAR = {CAR.F_150_MK14, CAR.F_150_LIGHTNING_MK1, CAR.MUSTANG_MACH_E_MK1} class RADAR: @@ -63,6 +64,7 @@ DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base # F-150 radar is not yet supported DBC[CAR.F_150_MK14] = dbc_dict("ford_lincoln_base_pt", None) DBC[CAR.F_150_LIGHTNING_MK1] = dbc_dict("ford_lincoln_base_pt", None) +DBC[CAR.MUSTANG_MACH_E_MK1] = dbc_dict("ford_lincoln_base_pt", None) class Footnote(Enum): @@ -97,6 +99,7 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { ], CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"), CAR.F_150_LIGHTNING_MK1: FordCarInfo("Ford F-150 Lightning 2021-23", "Co-Pilot360 Active 2.0"), + CAR.MUSTANG_MACH_E_MK1: FordCarInfo("Ford Mustang Mach-E 2021-23", "Co-Pilot360 Active 2.0"), CAR.FOCUS_MK4: FordCarInfo("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS]), CAR.MAVERICK_MK1: [ FordCarInfo("Ford Maverick 2022", "LARIAT Luxury"), @@ -240,7 +243,7 @@ FW_VERSIONS = { b'PL3A-14C204-BRB\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.F_150_LIGHTNING_MK1: { + CAR.F_150_LIGHTNING_MK1: { (Ecu.abs, 0x760, None): [ b'PL38-2D053-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -254,6 +257,25 @@ FW_VERSIONS = { b'NL3A-14C204-BAR\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, + CAR.MUSTANG_MACH_E_MK1: { + (Ecu.eps, 0x730, None): [ + b'LJ9C-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LJ9C-14D003-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'LK9C-2D053-CK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MJ98-14C204-BBS\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, CAR.FOCUS_MK4: { (Ecu.eps, 0x730, None): [ b'JX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 96d589d1a8..8de688e8ff 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -53,6 +53,7 @@ routes = [ CarTestRoute("e886087f430e7fe7|2023-06-16--23-06-36", FORD.FOCUS_MK4), CarTestRoute("bd37e43731e5964b|2023-04-30--10-42-26", FORD.MAVERICK_MK1), CarTestRoute("112e4d6e0cad05e1|2023-11-14--08-21-43", FORD.F_150_LIGHTNING_MK1), + CarTestRoute("83a4e056c7072678|2023-11-13--16-51-33", FORD.MUSTANG_MACH_E_MK1), #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION), CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA), diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 0272b02d2d..60e54af210 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -27,6 +27,7 @@ FORD F-150 14TH GEN: [.nan, 1.5, .nan] FORD FOCUS 4TH GEN: [.nan, 1.5, .nan] FORD MAVERICK 1ST GEN: [.nan, 1.5, .nan] FORD F-150 LIGHTNING 1ST GEN: [.nan, 1.5, .nan] +FORD MUSTANG MACH-E 1ST GEN: [.nan, 1.5, .nan] ### # No steering wheel From a3c0e2bd2500e04da1b8afd39657e7e12e555a7e Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 13 Dec 2023 11:50:44 -0800 Subject: [PATCH 232/724] CI: add codecov token (#30716) * add codecov token * retrigger with token --- .github/workflows/selfdrive_tests.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index c0a72924e4..c47947f90d 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -185,6 +185,8 @@ jobs: uses: codecov/codecov-action@v3 with: name: ${{ github.job }} + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} process_replay: name: process replay @@ -232,6 +234,8 @@ jobs: uses: codecov/codecov-action@v3 with: name: ${{ github.job }} + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} regen: name: regen @@ -293,6 +297,8 @@ jobs: uses: codecov/codecov-action@v3 with: name: ${{ github.job }} + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} test_cars: name: cars @@ -326,6 +332,8 @@ jobs: uses: codecov/codecov-action@v3 with: name: ${{ github.job }}-${{ matrix.job }} + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} car_docs_diff: name: PR comments From 2a1403a46b95b4e7664c6f12d78f65e697ade752 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 13 Dec 2023 13:57:50 -0800 Subject: [PATCH 233/724] bounties docs (#30718) --- docs/BOUNTIES.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 docs/BOUNTIES.md diff --git a/docs/BOUNTIES.md b/docs/BOUNTIES.md new file mode 100644 index 0000000000..7d6af8540f --- /dev/null +++ b/docs/BOUNTIES.md @@ -0,0 +1,62 @@ +# Bounties + +Get paid to improve openpilot! + +## Rules + +* code must be merged into openpilot master +* bounty eligibility is solely at our discretion +* once you open a PR, the bounty is locked to you until you stop working on it +* open a ticket at [comma.ai/support](https://comma.ai/support/shop-order) with links to your PRs to claim +* get an extra 20% if you redeem your bounty in [comma shop](https://comma.ai/shop) credit + +New bounties can be proposed in the **#contributing** channel in Discord. + +## Issue bounties + +We've tagged bounty eligible issues across openpilot and the rest of our repos; check out all the open ones [here](https://github.com/search?q=org%3Acommaai+is%3Aissue+label%3Abounty++&type=issues&state=open). These bounties roughly work out like this: +* **$100** - a few hours of work for an experienced openpilot developer; a good intro for someone new to openpilot +* **$300** - a day of work for an experienced openpilot developer +* **$500** - a few days of work for an experienced openpilot developer +* **$1k+** - a week or two of work (could be less for the right person) + +## Car bounties + +The car bounties only apply to cars that have a path to ship in openpilot release, which excludes unsupportable cars (e.g. Fords with a steering lockout) or cars that require extra hardware (Honda Accord with serial steering). + +#### Brand or platform port - $2000 +Example PR: [commaai/openpilot#23331](https://github.com/commaai/openpilot/pull/23331) + +This is for adding support for an entirely new brand or a substantially new ADAS platform within a brand (e.g. the Volkswagen PQ platform). + +#### Model port - $250 +Example PR: [commaai/openpilot#30245](https://github.com/commaai/openpilot/pull/30245) + +This is for porting a new car model that runs on a platform openpilot already supports. +In the average case, this is a few hours of work for an experienced software developer. + +This bounty also covers getting openpilot supported on a previously unsupported trim of an already supported car, e.g. the Chevy Bolt without ACC. + +#### Reverse Engineering a new Actuation Message - $300 + +This is for cars that are already supported, and it has three components: +* reverse a new steering, adaptive cruise, or AEB message +* merge the DBC definitions to [opendbc](http://github.com/commaai/opendbc) +* merge the openpilot code to use it and post a demo route + +The control doesn't have to be perfect, but it should generally do what it's supposed to do. + +### Specific Cars + +#### Rivian R1T or R1S - $3000 + +Get a Rivian driving with openpilot. +Requires a merged port with lateral control and at least a POC of longitudinal control. + +#### Toyota SecOc - $5000 + +We're contributing $5k to the [community-organized bounty](https://github.com/commaai/openpilot/discussions/19932). + +#### Chevy Bolt with SuperCruise - $2500 + +The Bolt is already supported on the trim with standard ACC. Get openpilot working on the trim with SuperCruise. It must be a normal install: no extra pandas or other hardware, no ECU reflashes, etc. From 33987d4cc9a6de80d6f9b3a4bae7a159d182620a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 14 Dec 2023 06:51:09 +0800 Subject: [PATCH 234/724] ui/onroad: avoid drawing invalid lanes and leads (#29666) --- selfdrive/ui/qt/onroad.cc | 15 +++++---------- selfdrive/ui/ui.cc | 7 +++++++ selfdrive/ui/ui.h | 4 +--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 971eb673e2..b4c39cafb5 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -605,7 +605,6 @@ void AnnotatedCameraWidget::paintGL() { SubMaster &sm = *(s->sm); const double start_draw_t = millis_since_boot(); const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); - const cereal::RadarState::Reader &radar_state = sm["radarState"].getRadarState(); // draw camera frame { @@ -654,17 +653,13 @@ void AnnotatedCameraWidget::paintGL() { painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); - if (s->worldObjectsVisible()) { - if (sm.rcv_frame("modelV2") > s->scene.started_frame) { - update_model(s, model, sm["uiPlan"].getUiPlan()); - if (sm.rcv_frame("radarState") > s->scene.started_frame) { - update_leads(s, radar_state, model.getPosition()); - } - } - + if (s->scene.world_objects_visible) { + update_model(s, model, sm["uiPlan"].getUiPlan()); drawLaneLines(painter, s); - if (s->scene.longitudinal_control) { + if (s->scene.longitudinal_control && sm.rcv_frame("radarState") > s->scene.started_frame) { + auto radar_state = sm["radarState"].getRadarState(); + update_leads(s, radar_state, model.getPosition()); auto lead_one = radar_state.getLeadOne(); auto lead_two = radar_state.getLeadTwo(); if (lead_one.getStatus()) { diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 7b2d24b53a..906de7d1ee 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -207,6 +207,12 @@ static void update_state(UIState *s) { scene.light_sensor = std::max(100.0f - scale * cam_state.getExposureValPercent(), 0.0f); } scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; + + scene.world_objects_visible = scene.world_objects_visible || + (scene.started && + sm.rcv_frame("liveCalibration") > scene.started_frame && + sm.rcv_frame("modelV2") > scene.started_frame && + sm.rcv_frame("uiPlan") > scene.started_frame); } void ui_update_params(UIState *s) { @@ -233,6 +239,7 @@ void UIState::updateStatus() { scene.started_frame = sm->frame; } started_prev = scene.started; + scene.world_objects_visible = false; emit offroadTransition(!scene.started); } } diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index d9a03344b6..86cd70f028 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -158,6 +158,7 @@ typedef struct UIScene { float light_sensor; bool started, ignition, is_metric, map_on_left, longitudinal_control; + bool world_objects_visible = false; uint64_t started_frame; } UIScene; @@ -167,9 +168,6 @@ class UIState : public QObject { public: UIState(QObject* parent = 0); void updateStatus(); - inline bool worldObjectsVisible() const { - return sm->rcv_frame("liveCalibration") > scene.started_frame; - } inline bool engaged() const { return scene.started && (*sm)["controlsState"].getControlsState().getEnabled(); } From ae85ee093252720b749ac07b61933b51a609511c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 13 Dec 2023 17:00:14 -0800 Subject: [PATCH 235/724] fixup can replay for jungle v2 and tres --- tools/replay/can_replay.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index 032c29824a..0827ca799f 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -22,6 +22,8 @@ def send_thread(s, flock): for i in [0, 1, 2, 3, 0xFFFF]: s.can_clear(i) + s.set_can_speed_kbps(i, 500) + s.set_can_data_speed_kbps(i, 500) s.set_ignition(False) time.sleep(5) s.set_ignition(True) @@ -64,6 +66,11 @@ def connect(): for s in p.list(): if s not in serials: + with p(s) as pp: + if pp.get_type() == Panda.HW_TYPE_TRES: + serials[s] = None + continue + print("starting send thread for", s) serials[s] = threading.Thread(target=send_thread, args=(p(s), flashing_lock)) serials[s].start() @@ -71,9 +78,10 @@ def connect(): # try to join all send threads cur_serials = serials.copy() for s, t in cur_serials.items(): - t.join(0.01) - if not t.is_alive(): - del serials[s] + if t is not None: + t.join(0.01) + if not t.is_alive(): + del serials[s] time.sleep(1) From fe24bdc689f1e3b82e0bec92034a855fd5da78b2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 13 Dec 2023 19:00:22 -0800 Subject: [PATCH 236/724] read params in a thread --- selfdrive/controls/controlsd.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index bbfde95cd6..397d5e7d09 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -2,6 +2,7 @@ import os import math import time +import threading from typing import SupportsFloat from cereal import car, log @@ -852,11 +853,6 @@ class Controls: start_time = time.monotonic() self.prof.checkpoint("Ratekeeper", ignore=True) - self.is_metric = self.params.get_bool("IsMetric") - self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl - if self.CP.notCar: - self.joystick_mode = self.params.get_bool("JoystickDebugMode") - # Sample data from sockets and get a carState CS = self.data_sample() cloudlog.timestamp("Data sampled") @@ -881,11 +877,26 @@ class Controls: self.CS_prev = CS + def params_thread(self, evt): + while not evt.is_set(): + self.is_metric = self.params.get_bool("IsMetric") + self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl + if self.CP.notCar: + self.joystick_mode = self.params.get_bool("JoystickDebugMode") + time.sleep(0.1) + def controlsd_thread(self): - while True: - self.step() - self.rk.monitor_time() - self.prof.display() + e = threading.Event() + t = threading.Thread(target=self.params_thread, args=(e, )) + try: + t.start() + while True: + self.step() + self.rk.monitor_time() + self.prof.display() + except SystemExit: + e.set() + t.join() def main(): From 8c25741e468b7cb11a59c8905c9d00d5612b9143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 14 Dec 2023 03:54:06 +0000 Subject: [PATCH 237/724] Ford: radar + long control fixups (#28948) * enable radar * manual new track * Don't filter points * cleanup * Update radar_interface.py * This is non-zero in stock * add speed * more extreme * A few more things * fixes * cleanup * revert that --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/car/ford/carcontroller.py | 4 ++-- selfdrive/car/ford/fordcan.py | 4 +++- selfdrive/car/ford/radar_interface.py | 18 ++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index fe9ee404a4..88ec6a5072 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -1,5 +1,6 @@ from cereal import car from openpilot.common.numpy_fast import clip +from openpilot.common.conversions import Conversions as CV from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_std_steer_angle_limits from openpilot.selfdrive.car.ford import fordcan @@ -88,9 +89,8 @@ class CarController: gas = accel if not CC.longActive or gas < CarControllerParams.MIN_GAS: gas = CarControllerParams.INACTIVE_GAS - stopping = CC.actuators.longControlState == LongCtrlState.stopping - can_sends.append(fordcan.create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping)) + can_sends.append(fordcan.create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping, v_ego_kph=40 * CV.MPH_TO_KPH)) ### ui ### send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert) diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index e0086ecbb5..78a249e387 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -118,7 +118,7 @@ def create_lat_ctl2_msg(packer, CAN: CanBus, mode: int, path_offset: float, path return packer.make_can_msg("LateralMotionControl2", CAN.main, values) -def create_acc_msg(packer, CAN: CanBus, long_active: bool, gas: float, accel: float, stopping: bool): +def create_acc_msg(packer, CAN: CanBus, long_active: bool, gas: float, accel: float, stopping: bool, v_ego_kph: float): """ Creates a CAN message for the Ford ACC Command. @@ -133,7 +133,9 @@ def create_acc_msg(packer, CAN: CanBus, long_active: bool, gas: float, accel: fl "AccBrkTot_A_Rq": accel, # Brake total accel request: [-20|11.9449] m/s^2 "Cmbb_B_Enbl": 1 if long_active else 0, # Enabled: 0=No, 1=Yes "AccPrpl_A_Rq": gas, # Acceleration request: [-5|5.23] m/s^2 + "AccPrpl_A_Pred": gas, # Acceleration request: [-5|5.23] m/s^2 "AccResumEnbl_B_Rq": 1 if long_active else 0, + "AccVeh_V_Trg": v_ego_kph, # Target speed: [0|255] km/h # TODO: we may be able to improve braking response by utilizing pre-charging better "AccBrkPrchg_B_Rq": 1 if decel else 0, # Pre-charge brake request: 0=No, 1=Yes "AccBrkDecel_B_Rq": 1 if decel else 0, # Deceleration request: 0=Inactive, 1=Active diff --git a/selfdrive/car/ford/radar_interface.py b/selfdrive/car/ford/radar_interface.py index 716c9b6e58..209bbebae3 100644 --- a/selfdrive/car/ford/radar_interface.py +++ b/selfdrive/car/ford/radar_interface.py @@ -119,17 +119,23 @@ class RadarInterface(RadarInterfaceBase): self.track_id += 1 valid = bool(msg[f"CAN_DET_VALID_LEVEL_{ii:02d}"]) - amplitude = msg[f"CAN_DET_AMPLITUDE_{ii:02d}"] # dBsm [-64|63] - if valid and 0 < amplitude <= 15: + if valid: azimuth = msg[f"CAN_DET_AZIMUTH_{ii:02d}"] # rad [-3.1416|3.13964] dist = msg[f"CAN_DET_RANGE_{ii:02d}"] # m [0|255.984] distRate = msg[f"CAN_DET_RANGE_RATE_{ii:02d}"] # m/s [-128|127.984] + dRel = cos(azimuth) * dist # m from front of car + yRel = -sin(azimuth) * dist # in car frame's y axis, left is positive - # *** openpilot radar point *** - self.pts[i].dRel = cos(azimuth) * dist # m from front of car - self.pts[i].yRel = -sin(azimuth) * dist # in car frame's y axis, left is positive - self.pts[i].vRel = distRate # m/s + # delphi doesn't notify of track switches, so do it manually + # TODO: refactor this to radard if more radars behave this way + if abs(self.pts[i].vRel - distRate) > 2 or abs(self.pts[i].dRel - dRel) > 5: + self.track_id += 1 + self.pts[i].trackId = self.track_id + + self.pts[i].dRel = dRel + self.pts[i].yRel = yRel + self.pts[i].vRel = distRate self.pts[i].measured = True From 8009b11516fdedd964716a414dcefc996dea807a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 13 Dec 2023 23:15:25 -0500 Subject: [PATCH 238/724] Hyundai CAN-FD: Panda safety config assignments cleanup (#29733) * Hyundai CAN-FD: Interface small cleanup * some more * Do this too * fix * cleaner * use flag * consolidate * do it there * Fix * hda2 only * cleaner * default to False * in another PR * lint --- selfdrive/car/hyundai/interface.py | 43 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 88d4e70183..cd6657a23c 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -11,6 +11,7 @@ from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.disable_ecu import disable_ecu Ecu = car.CarParams.Ecu +SafetyModel = car.CarParams.SafetyModel ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL) @@ -18,6 +19,17 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu Buttons.GAP_DIST: ButtonType.gapAdjustCruise, Buttons.CANCEL: ButtonType.cancel} +def set_safety_config_hyundai(candidate, CAN, can_fd=False): + platform = SafetyModel.hyundaiCanfd if can_fd else \ + SafetyModel.hyundaiLegacy if candidate in LEGACY_SAFETY_MODE_CAR else \ + SafetyModel.hyundai + cfgs = [get_safety_config(platform), ] + if CAN.ECAN >= 4: + cfgs.insert(0, get_safety_config(SafetyModel.noOutput)) + + return cfgs + + class CarInterface(CarInterfaceBase): @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): @@ -36,7 +48,6 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: # detect HDA2 with ADAS Driving ECU if hda2: - ret.flags |= HyundaiFlags.CANFD_HDA2.value if 0x110 in fingerprint[CAN.CAM]: ret.flags |= HyundaiFlags.CANFD_HDA2_ALT_STEERING.value else: @@ -286,30 +297,20 @@ class CarInterface(CarInterfaceBase): ret.enableBsm = 0x58b in fingerprint[0] # *** panda safety config *** - if candidate in CANFD_CAR: - cfgs = [get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd), ] - if CAN.ECAN >= 4: - cfgs.insert(0, get_safety_config(car.CarParams.SafetyModel.noOutput)) - ret.safetyConfigs = cfgs + ret.safetyConfigs = set_safety_config_hyundai(candidate, CAN, can_fd=(candidate in CANFD_CAR)) - if ret.flags & HyundaiFlags.CANFD_HDA2: - ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 - if ret.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING: - ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING + if hda2: + ret.flags |= HyundaiFlags.CANFD_HDA2.value + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 + + if candidate in CANFD_CAR: + if hda2 and ret.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS - if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC: - ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC - else: - if candidate in LEGACY_SAFETY_MODE_CAR: - # these cars require a special panda safety mode due to missing counters and checksums in the messages - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)] - else: - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)] - - if candidate in CAMERA_SCC_CAR: - ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC + if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC or candidate in CAMERA_SCC_CAR: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC if ret.openpilotLongitudinalControl: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_LONG if candidate in HYBRID_CAR: From 5f24078a480c9436ce99bf19358b5737aebf065f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 13 Dec 2023 20:17:33 -0800 Subject: [PATCH 239/724] Subaru: enable alpha longitudinal for gen1 cars (#30714) * enable alpha long * update docs * added footnote about eyesight * only for these cars * add alpha Co-authored-by: Shane Smiskol --------- Co-authored-by: Shane Smiskol --- docs/CARS.md | 139 +++++++++++++++--------------- selfdrive/car/subaru/interface.py | 2 +- selfdrive/car/subaru/values.py | 6 ++ selfdrive/car/tests/routes.py | 2 +- 4 files changed, 78 insertions(+), 71 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index bb0a29d159..e0d28cc8a9 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -11,12 +11,12 @@ A supported vehicle is one that just works when you install a comma device. All |Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Buick|LaCrosse 2017-19[4](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade 2017[4](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade ESV 2016[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -172,8 +172,8 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|UX Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lincoln|Aviator 2020-21|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-5 2022-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -181,26 +181,26 @@ A supported vehicle is one that just works when you install a comma device. All |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ram|1500 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Ascent 2019-21|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Forester 2019-21|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Forester 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Škoda|Fabia 2022-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Škoda|Kamiq 2021-23[9,11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Škoda|Karoq 2019-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Kodiaq 2017-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Octavia 2015-19[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Octavia RS 2016[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Scala 2020-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Škoda|Superb 2015-22[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Škoda|Fabia 2022-23[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| +|Škoda|Kamiq 2021-23[10,12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| +|Škoda|Karoq 2019-23[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Kodiaq 2017-23[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Octavia 2015-19[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Octavia RS 2016[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Scala 2020-23[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| +|Škoda|Superb 2015-22[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -213,8 +213,8 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2021-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2018-20|All|Stock|0 mph[8](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2021-24|All|openpilot|0 mph[8](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry 2018-20|All|Stock|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry 2021-24|All|openpilot|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -246,41 +246,41 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat 2015-22[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|T-Roc 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat 2015-22[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| +|Volkswagen|T-Roc 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| +|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| ### Footnotes 1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
@@ -290,12 +290,13 @@ A supported vehicle is one that just works when you install a comma device. All 52019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
6Requires a CAN FD panda kit if not using comma 3X for this CAN FD car.
7In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
-8openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-9Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-10Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-11Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
-12Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-13Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+8Enabling longitudinal control will disable all EyeSight functionality, including AEB, LDW, and RAB.
+9openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+10Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+11Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+12Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
+13Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+14Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 79576f6433..8a33ce5e24 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -118,7 +118,7 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"unknown car: {candidate}") - #ret.experimentalLongitudinalAvailable = candidate not in (GLOBAL_GEN2 | PREGLOBAL_CARS | LKAS_ANGLE | HYBRID_CARS) + ret.experimentalLongitudinalAvailable = candidate not in (GLOBAL_GEN2 | PREGLOBAL_CARS | LKAS_ANGLE | HYBRID_CARS) ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable if candidate in GLOBAL_GEN2 and ret.openpilotLongitudinalControl: diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 915785d329..a0753d99cc 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -93,6 +93,9 @@ class Footnote(Enum): GLOBAL = CarFootnote( "In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.", Column.PACKAGE) + EXP_LONG = CarFootnote( + "Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.", + Column.LONGITUDINAL) @dataclass @@ -104,6 +107,9 @@ class SubaruCarInfo(CarInfo): def init_make(self, CP: car.CarParams): self.car_parts.parts.extend([Tool.socket_8mm_deep, Tool.pry_tool]) + if CP.experimentalLongitudinalAvailable: + self.footnotes.append(Footnote.EXP_LONG) + CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-21", "All"), CAR.OUTBACK: SubaruCarInfo("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 8de688e8ff..c711f81929 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -256,7 +256,7 @@ routes = [ CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER), CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA), CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020), - # CarTestRoute("8de015561e1ea4a0|2023-08-29--17-08-31", SUBARU.IMPREZA), # openpilot longitudinal + CarTestRoute("8de015561e1ea4a0|2023-08-29--17-08-31", SUBARU.IMPREZA), # openpilot longitudinal # CarTestRoute("c3d1ccb52f5f9d65|2023-07-22--01-23-20", SUBARU.OUTBACK, segment=9), # gen2 longitudinal, eyesight disabled CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=10), CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3), From dbda641cee9c8b5218518ffd6589599c8f641faf Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 14 Dec 2023 12:18:45 +0800 Subject: [PATCH 240/724] ui: destroy egl_images in main thread (#30253) * call eglDestroyImageKHR in ui thread * assert --- selfdrive/ui/qt/widgets/cameraview.cc | 22 +++++++++++++--------- selfdrive/ui/qt/widgets/cameraview.h | 1 - 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index edf7b5990b..7b1f2f1d24 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -187,12 +187,23 @@ void CameraWidget::showEvent(QShowEvent *event) { } void CameraWidget::stopVipcThread() { + makeCurrent(); if (vipc_thread) { vipc_thread->requestInterruption(); vipc_thread->quit(); vipc_thread->wait(); vipc_thread = nullptr; } + +#ifdef QCOM2 + EGLDisplay egl_display = eglGetCurrentDisplay(); + assert(egl_display != EGL_NO_DISPLAY); + for (auto &pair : egl_images) { + eglDestroyImageKHR(egl_display, pair.second); + assert(eglGetError() == EGL_SUCCESS); + } + egl_images.clear(); +#endif } void CameraWidget::availableStreamsUpdated(std::set streams) { @@ -323,8 +334,8 @@ void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { stream_stride = vipc_client->buffers[0].stride; #ifdef QCOM2 - egl_display = eglGetCurrentDisplay(); - + EGLDisplay egl_display = eglGetCurrentDisplay(); + assert(egl_display != EGL_NO_DISPLAY); for (auto &pair : egl_images) { eglDestroyImageKHR(egl_display, pair.second); } @@ -415,13 +426,6 @@ void CameraWidget::vipcThread() { } } } - -#ifdef QCOM2 - for (auto &pair : egl_images) { - eglDestroyImageKHR(egl_display, pair.second); - } - egl_images.clear(); -#endif } void CameraWidget::clearFrames() { diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index fcd5b1b18f..c97038cf43 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -70,7 +70,6 @@ protected: QColor bg = QColor("#000000"); #ifdef QCOM2 - EGLDisplay egl_display; std::map egl_images; #endif From abe76065a9b6a1855adb3d64f8d59eafe2b83f10 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 13 Dec 2023 20:26:51 -0800 Subject: [PATCH 241/724] update docs --- docs/CARS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CARS.md b/docs/CARS.md index e0d28cc8a9..113e7c877b 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -290,7 +290,7 @@ A supported vehicle is one that just works when you install a comma device. All 52019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
6Requires a CAN FD panda kit if not using comma 3X for this CAN FD car.
7In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
-8Enabling longitudinal control will disable all EyeSight functionality, including AEB, LDW, and RAB.
+8Enabling longitudinal control (alpha) will disable all EyeSight functionality, including AEB, LDW, and RAB.
9openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
10Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
11Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
From ea94a6d89fa00f9852e8ee6b5c8203199c1a06e8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 13 Dec 2023 20:27:35 -0800 Subject: [PATCH 242/724] athena: cleanup unused methods --- selfdrive/athena/athenad.py | 31 +--------------- system/hardware/tici/hardware.py | 61 -------------------------------- 2 files changed, 1 insertion(+), 91 deletions(-) diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index bdca04c457..719d50c1ab 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -11,7 +11,6 @@ import queue import random import select import socket -import subprocess import sys import tempfile import threading @@ -34,7 +33,7 @@ from openpilot.common.api import Api from openpilot.common.file_helpers import CallbackReader from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity -from openpilot.system.hardware import HARDWARE, PC, AGNOS +from openpilot.system.hardware import HARDWARE, PC from openpilot.system.loggerd.xattr_cache import getxattr, setxattr from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version @@ -360,22 +359,6 @@ def listDataDirectory(prefix='') -> List[str]: return scan_dir(Paths.log_root(), prefix) -@dispatcher.add_method -def reboot() -> Dict[str, int]: - sock = messaging.sub_sock("deviceState", timeout=1000) - ret = messaging.recv_one(sock) - if ret is None or ret.deviceState.started: - raise Exception("Reboot unavailable") - - def do_reboot() -> None: - time.sleep(2) - HARDWARE.reboot() - - threading.Thread(target=do_reboot).start() - - return {"success": 1} - - @dispatcher.add_method def uploadFileToUrl(fn: str, url: str, headers: Dict[str, str]) -> UploadFilesToUrlResponse: # this is because mypy doesn't understand that the decorator doesn't change the return type @@ -450,18 +433,6 @@ def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]] return {"success": 1} -@dispatcher.add_method -def setBandwithLimit(upload_speed_kbps: int, download_speed_kbps: int) -> Dict[str, Union[int, str]]: - if not AGNOS: - return {"success": 0, "error": "only supported on AGNOS"} - - try: - HARDWARE.set_bandwidth_limit(upload_speed_kbps, download_speed_kbps) - return {"success": 1} - except subprocess.CalledProcessError as e: - return {"success": 0, "error": "failed to set limit", "stdout": e.stdout, "stderr": e.stderr} - - def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> Dict[str, int]: try: if local_port not in LOCAL_PORT_WHITELIST: diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 5f8267a461..f895bbc508 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -293,67 +293,6 @@ class Tici(HardwareBase): return super().get_network_metered(network_type) - @staticmethod - def set_bandwidth_limit(upload_speed_kbps: int, download_speed_kbps: int) -> None: - upload_speed_kbps = int(upload_speed_kbps) # Ensure integer value - download_speed_kbps = int(download_speed_kbps) # Ensure integer value - - adapter = "wwan0" - ifb = "ifb0" - - sudo = ["sudo"] - tc = sudo + ["tc"] - - # check, cmd - cleanup = [ - # Clean up old rules - (False, tc + ["qdisc", "del", "dev", adapter, "root"]), - (False, tc + ["qdisc", "del", "dev", ifb, "root"]), - (False, tc + ["qdisc", "del", "dev", adapter, "ingress"]), - (False, tc + ["qdisc", "del", "dev", ifb, "ingress"]), - - # Bring ifb0 down - (False, sudo + ["ip", "link", "set", "dev", ifb, "down"]), - ] - - upload = [ - # Create root Hierarchy Token Bucket that sends all traffic to 1:20 - (True, tc + ["qdisc", "add", "dev", adapter, "root", "handle", "1:", "htb", "default", "20"]), - - # Create class 1:20 with specified rate limit - (True, tc + ["class", "add", "dev", adapter, "parent", "1:", "classid", "1:20", "htb", "rate", f"{upload_speed_kbps}kbit"]), - - # Create universal 32 bit filter on adapter that sends all outbound ip traffic through the class - (True, tc + ["filter", "add", "dev", adapter, "parent", "1:", "protocol", "ip", "prio", \ - "10", "u32", "match", "ip", "dst", "0.0.0.0/0", "flowid", "1:20"]), - ] - - download = [ - # Bring ifb0 up - (True, sudo + ["ip", "link", "set", "dev", ifb, "up"]), - - # Redirect ingress (incoming) to egress ifb0 - (True, tc + ["qdisc", "add", "dev", adapter, "handle", "ffff:", "ingress"]), - (True, tc + ["filter", "add", "dev", adapter, "parent", "ffff:", "protocol", "ip", "u32", \ - "match", "u32", "0", "0", "action", "mirred", "egress", "redirect", "dev", ifb]), - - # Add class and rules for virtual interface - (True, tc + ["qdisc", "add", "dev", ifb, "root", "handle", "2:", "htb"]), - (True, tc + ["class", "add", "dev", ifb, "parent", "2:", "classid", "2:1", "htb", "rate", f"{download_speed_kbps}kbit"]), - - # Add filter to rule for IP address - (True, tc + ["filter", "add", "dev", ifb, "protocol", "ip", "parent", "2:", "prio", "1", "u32", "match", "ip", "src", "0.0.0.0/0", "flowid", "2:1"]), - ] - - commands = cleanup - if upload_speed_kbps != -1: - commands += upload - if download_speed_kbps != -1: - commands += download - - for check, cmd in commands: - subprocess.run(cmd, check=check) - def get_modem_version(self): try: modem = self.get_modem() From fcc671297ee0cd44abc6f7535e1adddc11d58192 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 13 Dec 2023 23:30:33 -0500 Subject: [PATCH 243/724] torqued: cache bucket points every minute while onroad (#30515) * torqued: cache bucket points every minute while onroad * use put_nonblocking * remove caching on exit * unused * more unused * remove logging --- selfdrive/locationd/helpers.py | 15 ++++----------- selfdrive/locationd/torqued.py | 12 +++++------- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index d57f55d588..53299c847f 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -1,11 +1,8 @@ import numpy as np -import signal -import sys from typing import List, Optional, Tuple, Any from cereal import log -from openpilot.common.params import Params -from openpilot.common.swaglog import cloudlog +from openpilot.common.params import put_nonblocking class NPQueue: @@ -68,10 +65,6 @@ class ParameterEstimator: raise NotImplementedError -def cache_points_onexit(param_name, estimator, sig, frame): - signal.signal(sig, signal.SIG_DFL) - cloudlog.warning(f"Caching {param_name} param") - params = Params() - msg = estimator.get_msg(valid=True, with_points=True) - params.put(param_name, msg.to_bytes()) - sys.exit(0) +def cache_points(param_name, estimator, valid): + msg = estimator.get_msg(valid=valid, with_points=True) + put_nonblocking(param_name, msg.to_bytes()) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index d0a03f9b80..fa37cbbcc4 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -1,9 +1,6 @@ #!/usr/bin/env python3 -import os -import signal import numpy as np from collections import deque, defaultdict -from functools import partial import cereal.messaging as messaging from cereal import car, log @@ -12,7 +9,7 @@ from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY -from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, cache_points_onexit +from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, cache_points HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 @@ -227,9 +224,6 @@ def main(): with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP: estimator = TorqueEstimator(CP) - if "REPLAY" not in os.environ: - signal.signal(signal.SIGINT, partial(cache_points_onexit, "LiveTorqueParameters", estimator)) - while True: sm.update() if sm.all_checks(): @@ -242,6 +236,10 @@ def main(): if sm.frame % 5 == 0: pm.send('liveTorqueParameters', estimator.get_msg(valid=sm.all_checks())) + # Cache points every 60 seconds while onroad + if sm.frame % 240 == 0: + cache_points("LiveTorqueParameters", estimator, sm.all_checks()) + if __name__ == "__main__": main() From 3c4c4d1f7f22884f84f0f52ad45ab0412fab1eb5 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 14 Dec 2023 12:36:01 +0800 Subject: [PATCH 244/724] common/params: support nonblocking write (#29808) * Safe and efficient asynchronous writing parameters * call putNonBlocking in locationd * remove space * ->AsyncWriter * remove semicolon * use member function * asyc write multiple times * add test case for AsyncWriter * merge master * add missing include * public * cleanup * create once * cleanup * update that * explicit waiting * improve test case * pass prefix to asywriter * move to params * assert(queue.empty()) * add comment * add todo * test_power_monitoring: remove patch * rm laikad.py * fix import --------- Co-authored-by: Adeeb Shihadeh --- common/SConscript | 2 +- common/params.cc | 30 +++++++++++++++++-- common/params.h | 20 +++++++++++-- common/params.py | 5 +--- common/params_pyx.pyx | 22 +++++++++----- common/tests/test_params.cc | 27 +++++++++++++++++ common/tests/test_params.py | 6 ++-- selfdrive/controls/controlsd.py | 8 ++--- selfdrive/locationd/calibrationd.py | 8 ++--- selfdrive/locationd/locationd.cc | 8 ++--- selfdrive/locationd/paramsd.py | 4 +-- selfdrive/monitoring/dmonitoringd.py | 7 +++-- selfdrive/thermald/power_monitoring.py | 4 +-- .../thermald/tests/test_power_monitoring.py | 2 -- 14 files changed, 111 insertions(+), 42 deletions(-) create mode 100644 common/tests/test_params.cc diff --git a/common/SConscript b/common/SConscript index 97322b248d..b8c23b7019 100644 --- a/common/SConscript +++ b/common/SConscript @@ -24,7 +24,7 @@ Export('_common', '_gpucommon') if GetOption('extras'): env.Program('tests/test_common', - ['tests/test_runner.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc', 'tests/test_ratekeeper.cc'], + ['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc', 'tests/test_ratekeeper.cc'], LIBS=[_common, 'json11', 'zmq', 'pthread']) # Cython bindings diff --git a/common/params.cc b/common/params.cc index d1d3513469..620cd42641 100644 --- a/common/params.cc +++ b/common/params.cc @@ -4,9 +4,11 @@ #include #include +#include #include #include +#include "common/queue.h" #include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" @@ -214,8 +216,15 @@ std::unordered_map keys = { Params::Params(const std::string &path) { - prefix = "/" + util::getenv("OPENPILOT_PREFIX", "d"); - params_path = ensure_params_path(prefix, path); + params_prefix = "/" + util::getenv("OPENPILOT_PREFIX", "d"); + params_path = ensure_params_path(params_prefix, path); +} + +Params::~Params() { + if (future.valid()) { + future.wait(); + } + assert(queue.empty()); } std::vector Params::allKeys() const { @@ -328,3 +337,20 @@ void Params::clearAll(ParamKeyType key_type) { fsync_dir(getParamPath()); } + +void Params::putNonBlocking(const std::string &key, const std::string &val) { + queue.push(std::make_pair(key, val)); + // start thread on demand + if (!future.valid() || future.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) { + future = std::async(std::launch::async, &Params::asyncWriteThread, this); + } +} + +void Params::asyncWriteThread() { + // TODO: write the latest one if a key has multiple values in the queue. + std::pair p; + while (queue.try_pop(p, 0)) { + // Params::put is Thread-Safe + put(p.first, p.second); + } +} diff --git a/common/params.h b/common/params.h index aa586a1581..d726a6185f 100644 --- a/common/params.h +++ b/common/params.h @@ -1,9 +1,14 @@ #pragma once +#include #include #include +#include +#include #include +#include "common/queue.h" + enum ParamKeyType { PERSISTENT = 0x02, CLEAR_ON_MANAGER_START = 0x04, @@ -17,6 +22,7 @@ enum ParamKeyType { class Params { public: explicit Params(const std::string &path = {}); + ~Params(); // Not copyable. Params(const Params&) = delete; Params& operator=(const Params&) = delete; @@ -25,7 +31,7 @@ public: bool checkKey(const std::string &key); ParamKeyType getKeyType(const std::string &key); inline std::string getParamPath(const std::string &key = {}) { - return params_path + prefix + (key.empty() ? "" : "/" + key); + return params_path + params_prefix + (key.empty() ? "" : "/" + key); } // Delete a value @@ -47,8 +53,18 @@ public: inline int putBool(const std::string &key, bool val) { return put(key.c_str(), val ? "1" : "0", 1); } + void putNonBlocking(const std::string &key, const std::string &val); + inline void putBoolNonBlocking(const std::string &key, bool val) { + putNonBlocking(key, val ? "1" : "0"); + } private: + void asyncWriteThread(); + std::string params_path; - std::string prefix; + std::string params_prefix; + + // for nonblocking write + std::future future; + SafeQueue> queue; }; diff --git a/common/params.py b/common/params.py index ea8ac7514a..66808083dc 100644 --- a/common/params.py +++ b/common/params.py @@ -1,10 +1,7 @@ -from openpilot.common.params_pyx import Params, ParamKeyType, UnknownKeyName, put_nonblocking, \ - put_bool_nonblocking +from openpilot.common.params_pyx import Params, ParamKeyType, UnknownKeyName assert Params assert ParamKeyType assert UnknownKeyName -assert put_nonblocking -assert put_bool_nonblocking if __name__ == "__main__": import sys diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx index db8f496d30..47d2075df2 100644 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -3,7 +3,6 @@ from libcpp cimport bool from libcpp.string cimport string from libcpp.vector cimport vector -import threading cdef extern from "common/params.h": cpdef enum ParamKeyType: @@ -20,6 +19,8 @@ cdef extern from "common/params.h": bool getBool(string, bool) nogil int remove(string) nogil int put(string, string) nogil + void putNonBlocking(string, string) nogil + void putBoolNonBlocking(string, bool) nogil int putBool(string, bool) nogil bool checkKey(string) nogil string getParamPath(string) nogil @@ -80,7 +81,7 @@ cdef class Params: """ Warning: This function blocks until the param is written to disk! In very rare cases this can take over a second, and your code will hang. - Use the put_nonblocking helper function in time sensitive code, but + Use the put_nonblocking, put_bool_nonblocking in time sensitive code, but in general try to avoid writing params as much as possible. """ cdef string k = self.check_key(key) @@ -93,6 +94,17 @@ cdef class Params: with nogil: self.p.putBool(k, val) + def put_nonblocking(self, key, dat): + cdef string k = self.check_key(key) + cdef string dat_bytes = ensure_bytes(dat) + with nogil: + self.p.putNonBlocking(k, dat_bytes) + + def put_bool_nonblocking(self, key, bool val): + cdef string k = self.check_key(key) + with nogil: + self.p.putBoolNonBlocking(k, val) + def remove(self, key): cdef string k = self.check_key(key) with nogil: @@ -104,9 +116,3 @@ cdef class Params: def all_keys(self): return self.p.allKeys() - -def put_nonblocking(key, val, d=""): - threading.Thread(target=lambda: Params(d).put(key, val)).start() - -def put_bool_nonblocking(key, bool val, d=""): - threading.Thread(target=lambda: Params(d).put_bool(key, val)).start() diff --git a/common/tests/test_params.cc b/common/tests/test_params.cc new file mode 100644 index 0000000000..f8d6c79f55 --- /dev/null +++ b/common/tests/test_params.cc @@ -0,0 +1,27 @@ +#include "catch2/catch.hpp" +#define private public +#include "common/params.h" +#include "common/util.h" + +TEST_CASE("params_nonblocking_put") { + char tmp_path[] = "/tmp/asyncWriter_XXXXXX"; + const std::string param_path = mkdtemp(tmp_path); + auto param_names = {"CarParams", "IsMetric"}; + { + Params params(param_path); + for (const auto &name : param_names) { + params.putNonBlocking(name, "1"); + // param is empty + REQUIRE(params.get(name).empty()); + } + + // check if thread is running + REQUIRE(params.future.valid()); + REQUIRE(params.future.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout); + } + // check results + Params p(param_path); + for (const auto &name : param_names) { + REQUIRE(p.get(name) == "1"); + } +} diff --git a/common/tests/test_params.py b/common/tests/test_params.py index fb6f320ea4..490ee122be 100644 --- a/common/tests/test_params.py +++ b/common/tests/test_params.py @@ -4,7 +4,7 @@ import time import uuid import unittest -from openpilot.common.params import Params, ParamKeyType, UnknownKeyName, put_nonblocking, put_bool_nonblocking +from openpilot.common.params import Params, ParamKeyType, UnknownKeyName class TestParams(unittest.TestCase): def setUp(self): @@ -86,7 +86,7 @@ class TestParams(unittest.TestCase): q = Params() def _delayed_writer(): time.sleep(0.1) - put_nonblocking("CarParams", "test") + Params().put_nonblocking("CarParams", "test") threading.Thread(target=_delayed_writer).start() assert q.get("CarParams") is None assert q.get("CarParams", True) == b"test" @@ -95,7 +95,7 @@ class TestParams(unittest.TestCase): q = Params() def _delayed_writer(): time.sleep(0.1) - put_bool_nonblocking("CarParams", True) + Params().put_bool_nonblocking("CarParams", True) threading.Thread(target=_delayed_writer).start() assert q.get("CarParams") is None assert q.get("CarParams", True) == b"1" diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 397d5e7d09..46a0b7f462 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -9,7 +9,7 @@ from cereal import car, log from openpilot.common.numpy_fast import clip from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL from openpilot.common.profiler import Profiler -from openpilot.common.params import Params, put_nonblocking, put_bool_nonblocking +from openpilot.common.params import Params import cereal.messaging as messaging from cereal.visionipc import VisionIpcClient, VisionStreamType from openpilot.common.conversions import Conversions as CV @@ -128,8 +128,8 @@ class Controls: # Write CarParams for radard cp_bytes = self.CP.to_bytes() self.params.put("CarParams", cp_bytes) - put_nonblocking("CarParamsCache", cp_bytes) - put_nonblocking("CarParamsPersistent", cp_bytes) + self.params.put_nonblocking("CarParamsCache", cp_bytes) + self.params.put_nonblocking("CarParamsPersistent", cp_bytes) # cleanup old params if not self.CP.experimentalLongitudinalAvailable: @@ -449,7 +449,7 @@ class Controls: self.initialized = True self.set_initial_state() - put_bool_nonblocking("ControlsReady", True) + self.params.put_bool_nonblocking("ControlsReady", True) # Check for CAN timeout if not can_strs: diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 961efabf8d..06be9f031a 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -15,7 +15,7 @@ from typing import List, NoReturn, Optional from cereal import log import cereal.messaging as messaging from openpilot.common.conversions import Conversions as CV -from openpilot.common.params import Params, put_nonblocking +from openpilot.common.params import Params from openpilot.common.realtime import set_realtime_priority from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot from openpilot.common.swaglog import cloudlog @@ -64,8 +64,8 @@ class Calibrator: self.not_car = False # Read saved calibration - params = Params() - calibration_params = params.get("CalibrationParams") + self.params = Params() + calibration_params = self.params.get("CalibrationParams") rpy_init = RPY_INIT wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT height = HEIGHT_INIT @@ -164,7 +164,7 @@ class Calibrator: write_this_cycle = (self.idx == 0) and (self.block_idx % (INPUTS_WANTED//5) == 5) if self.param_put and write_this_cycle: - put_nonblocking("CalibrationParams", self.get_msg(True).to_bytes()) + self.params.put_nonblocking("CalibrationParams", self.get_msg(True).to_bytes()) def handle_v_ego(self, v_ego: float) -> None: self.v_ego = v_ego diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 80882fc951..e32ed78a3e 100644 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -678,9 +678,10 @@ void Localizer::configure_gnss_source(const LocalizerGnssSource &source) { } int Localizer::locationd_thread() { + Params params; LocalizerGnssSource source; const char* gps_location_socket; - if (Params().getBool("UbloxAvailable")) { + if (params.getBool("UbloxAvailable")) { source = LocalizerGnssSource::UBLOX; gps_location_socket = "gpsLocationExternal"; } else { @@ -737,10 +738,7 @@ int Localizer::locationd_thread() { VectorXd posGeo = this->get_position_geodetic(); std::string lastGPSPosJSON = util::string_format( "{\"latitude\": %.15f, \"longitude\": %.15f, \"altitude\": %.15f}", posGeo(0), posGeo(1), posGeo(2)); - - std::thread([] (const std::string gpsjson) { - Params().put("LastGPSPosition", gpsjson); - }, lastGPSPosJSON).detach(); + params.putNonBlocking("LastGPSPosition", lastGPSPosJSON); } cnt++; } diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 4a598e8d4b..183a8666e8 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -7,7 +7,7 @@ import numpy as np import cereal.messaging as messaging from cereal import car from cereal import log -from openpilot.common.params import Params, put_nonblocking +from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.numpy_fast import clip from openpilot.selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States @@ -251,7 +251,7 @@ def main(): 'stiffnessFactor': liveParameters.stiffnessFactor, 'angleOffsetAverageDeg': liveParameters.angleOffsetAverageDeg, } - put_nonblocking("LiveParameters", json.dumps(params)) + params_reader.put_nonblocking("LiveParameters", json.dumps(params)) pm.send('liveParameters', msg) diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 7455459967..7a24c0107e 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -3,7 +3,7 @@ import gc import cereal.messaging as messaging from cereal import car -from openpilot.common.params import Params, put_bool_nonblocking +from openpilot.common.params import Params from openpilot.common.realtime import set_realtime_priority from openpilot.selfdrive.controls.lib.events import Events from openpilot.selfdrive.monitoring.driver_monitor import DriverStatus @@ -13,10 +13,11 @@ def dmonitoringd_thread(): gc.disable() set_realtime_priority(2) + params = Params() pm = messaging.PubMaster(['driverMonitoringState']) sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'controlsState', 'modelV2'], poll=['driverStateV2']) - driver_status = DriverStatus(rhd_saved=Params().get_bool("IsRhdDetected")) + driver_status = DriverStatus(rhd_saved=params.get_bool("IsRhdDetected")) v_cruise_last = 0 driver_engaged = False @@ -79,7 +80,7 @@ def dmonitoringd_thread(): if (sm['driverStateV2'].frameId % 6000 == 0 and driver_status.wheelpos_learner.filtered_stat.n > driver_status.settings._WHEELPOS_FILTER_MIN_COUNT and driver_status.wheel_on_right == (driver_status.wheelpos_learner.filtered_stat.M > driver_status.settings._WHEELPOS_THRESHOLD)): - put_bool_nonblocking("IsRhdDetected", driver_status.wheel_on_right) + params.put_bool_nonblocking("IsRhdDetected", driver_status.wheel_on_right) def main(): dmonitoringd_thread() diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 0520385473..8802a82af4 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -2,7 +2,7 @@ import time import threading from typing import Optional -from openpilot.common.params import Params, put_nonblocking +from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.statsd import statlog @@ -60,7 +60,7 @@ class PowerMonitoring: self.car_battery_capacity_uWh = max(self.car_battery_capacity_uWh, 0) self.car_battery_capacity_uWh = min(self.car_battery_capacity_uWh, CAR_BATTERY_CAPACITY_uWh) if now - self.last_save_time >= 10: - put_nonblocking("CarBatteryCapacity", str(int(self.car_battery_capacity_uWh))) + self.params.put_nonblocking("CarBatteryCapacity", str(int(self.car_battery_capacity_uWh))) self.last_save_time = now # First measurement, set integration time diff --git a/selfdrive/thermald/tests/test_power_monitoring.py b/selfdrive/thermald/tests/test_power_monitoring.py index f8d43737cd..c3a890f068 100755 --- a/selfdrive/thermald/tests/test_power_monitoring.py +++ b/selfdrive/thermald/tests/test_power_monitoring.py @@ -3,7 +3,6 @@ import unittest from unittest.mock import patch from openpilot.common.params import Params -from openpilot.selfdrive.test.helpers import noop from openpilot.selfdrive.thermald.power_monitoring import PowerMonitoring, CAR_BATTERY_CAPACITY_uWh, \ CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING, DELAY_SHUTDOWN_TIME_S @@ -26,7 +25,6 @@ def pm_patch(name, value, constant=False): @patch("time.monotonic", new=mock_time_monotonic) -@patch("openpilot.selfdrive.thermald.power_monitoring.put_nonblocking", new=noop) # TODO: Remove this once nonblocking params are safer class TestPowerMonitoring(unittest.TestCase): def setUp(self): self.params = Params() From b53b505d2ea52b61e9b42582bd97f20bff0a1fa3 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 14 Dec 2023 12:48:51 +0800 Subject: [PATCH 245/724] boardd: improve the performance of `PandaUsbHandle::list()` (#30673) * improve list performance * revert test_onroad --------- Co-authored-by: Comma Device --- selfdrive/boardd/panda_comms.cc | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/selfdrive/boardd/panda_comms.cc b/selfdrive/boardd/panda_comms.cc index bc4e5f5867..e2c78abea2 100644 --- a/selfdrive/boardd/panda_comms.cc +++ b/selfdrive/boardd/panda_comms.cc @@ -2,33 +2,33 @@ #include #include +#include #include "common/swaglog.h" -static int init_usb_ctx(libusb_context **context) { - assert(context != nullptr); - - int err = libusb_init(context); +static libusb_context *init_usb_ctx() { + libusb_context *context = nullptr; + int err = libusb_init(&context); if (err != 0) { LOGE("libusb initialization error"); - return err; + return nullptr; } #if LIBUSB_API_VERSION >= 0x01000106 - libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); + libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); #else - libusb_set_debug(*context, 3); + libusb_set_debug(context, 3); #endif - - return err; + return context; } PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) { // init libusb ssize_t num_devices; libusb_device **dev_list = NULL; - int err = init_usb_ctx(&ctx); - if (err != 0) { goto fail; } + int err = 0; + ctx = init_usb_ctx(); + if (!ctx) { goto fail; } // connect by serial num_devices = libusb_get_device_list(ctx, &dev_list); @@ -94,16 +94,14 @@ void PandaUsbHandle::cleanup() { } std::vector PandaUsbHandle::list() { + static std::unique_ptr context(init_usb_ctx(), libusb_exit); // init libusb ssize_t num_devices; - libusb_context *context = NULL; libusb_device **dev_list = NULL; std::vector serials; + if (!context) { return serials; } - int err = init_usb_ctx(&context); - if (err != 0) { return serials; } - - num_devices = libusb_get_device_list(context, &dev_list); + num_devices = libusb_get_device_list(context.get(), &dev_list); if (num_devices < 0) { LOGE("libusb can't get device list"); goto finish; @@ -130,9 +128,6 @@ finish: if (dev_list != NULL) { libusb_free_device_list(dev_list, 1); } - if (context) { - libusb_exit(context); - } return serials; } From bd8a99ba285bebc498c41136dfa900bfdfa86ea5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 13 Dec 2023 20:52:43 -0800 Subject: [PATCH 246/724] fix put_nonblocking reference --- selfdrive/locationd/helpers.py | 6 ------ selfdrive/locationd/torqued.py | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index 53299c847f..93e2929139 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -2,7 +2,6 @@ import numpy as np from typing import List, Optional, Tuple, Any from cereal import log -from openpilot.common.params import put_nonblocking class NPQueue: @@ -63,8 +62,3 @@ class ParameterEstimator: def get_msg(self, valid: bool, with_points: bool) -> log.Event: raise NotImplementedError - - -def cache_points(param_name, estimator, valid): - msg = estimator.get_msg(valid=valid, with_points=True) - put_nonblocking(param_name, msg.to_bytes()) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index fa37cbbcc4..21a5ebca5f 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -9,7 +9,7 @@ from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY -from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, cache_points +from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 @@ -238,8 +238,8 @@ def main(): # Cache points every 60 seconds while onroad if sm.frame % 240 == 0: - cache_points("LiveTorqueParameters", estimator, sm.all_checks()) - + msg = estimator.get_msg(valid=sm.all_checks(), with_points=True) + params.put_nonblocking("LiveTorqueParameters", msg.to_bytes()) if __name__ == "__main__": main() From 2b12a499f7b923758d8b85705b680b04156f27c0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 13 Dec 2023 21:18:20 -0800 Subject: [PATCH 247/724] bump up installer size (#30722) --- selfdrive/ui/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index b0f5a4f4a6..7b570b93f8 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -125,7 +125,7 @@ if GetOption('extras') and arch != "Darwin": obj = senv.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) f = senv.Program(f"installer/installers/installer_{name}", [obj, cont[brand]], LIBS=qt_libs) # keep installers small - assert f[0].get_size() < 300*1e3 + assert f[0].get_size() < 350*1e3 # build watch3 if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'): From 0c24126a6b795b8f8e2c8fff0411ab671f568ace Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 10:42:43 -0800 Subject: [PATCH 248/724] add comma10k to contributing --- docs/CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 777788630b..e98a1b6dbc 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -64,3 +64,4 @@ If you port openpilot to a substantially new car brand, see this more generic [B * Consider opting into driver camera uploads to improve the driver monitoring model. * Connect your device to Wi-Fi regularly, so that we can pull data for training better driving models. * Run the `nightly` branch and report issues. This branch is like `master` but it's built just like a release. +* Annotate images in the [comma10k dateset](https://github.com/commaai/comma10k). From b69c71eda91a85feef71d3afdb3ab3d29ccaa3fd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 11:25:09 -0800 Subject: [PATCH 249/724] Update CONTRIBUTING.md --- docs/CONTRIBUTING.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index e98a1b6dbc..a3eebca630 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -9,10 +9,12 @@ Our software is open source so you can solve your own problems without needing h * Join our [Discord](https://discord.comma.ai) * Docs are at https://docs.comma.ai and https://blog.comma.ai -### What contributions are we looking for? +## What contributions are we looking for? **openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all developoment is towards that goal. +### What gets merged? + The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged. If a PR offers *some* value but will take lots of time to get merged, it will be closed. Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. @@ -51,12 +53,6 @@ A good pull request has all of the following: * if you've improved your car's tuning, post before and after plots * passes the CI tests -### Car Ports - -We've released a [Model Port guide](https://blog.comma.ai/openpilot-port-guide-for-toyota-models/) for porting to Toyota/Lexus models. - -If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/). - ## Contributing without Code * Report bugs in GitHub issues. From 075e6ff3e62107073a489863769767552295be62 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 14 Dec 2023 12:46:24 -0800 Subject: [PATCH 250/724] Docker: add xvfb (#30727) add xvfb --- tools/install_ubuntu_dependencies.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 78a01f2b75..e1bd30aa39 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -77,6 +77,7 @@ function install_ubuntu_common_requirements() { libqt5x11extras5-dev \ libreadline-dev \ libdw1 \ + xvfb \ valgrind } From 57b9e75d6dcac5a401e832aa8d83e665b2815080 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 13:24:48 -0800 Subject: [PATCH 251/724] Docs update (#30726) * update readme * three videos looks way better * shorter picture * even smaller * Update README.md * Update README.md --- README.md | 111 +++++++++++++----------------------------------------- 1 file changed, 26 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index d85d1e5676..d7f8effb81 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,4 @@ -![openpilot on the comma 3X](https://github.com/commaai/openpilot/assets/4038174/f1081737-8718-4241-a22a-3ceba526361a) - -Table of Contents -======================= - -* [What is openpilot?](#what-is-openpilot) -* [Running in a car](#running-on-a-dedicated-device-in-a-car) -* [Running on PC](#running-on-pc) -* [Community and Contributing](#community-and-contributing) -* [User Data and comma Account](#user-data-and-comma-account) -* [Safety and Testing](#safety-and-testing) -* [Directory Structure](#directory-structure) -* [Licensing](#licensing) - ---- +![openpilot on the comma 3X](https://github.com/commaai/openpilot/assets/8762862/f09e6d29-db2d-4179-80c2-51e8d92bdb5c) What is openpilot? ------ @@ -23,33 +9,21 @@ What is openpilot? - - - - - - - - + + drive to taco bell - Running on a dedicated device in a car ------ -To use openpilot in a car, you need four things -1. **Supported Device:** A comma 3/3X. You can purchase these devices from (https://comma.ai/shop/comma-3x) - -2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software. - To install the release version of openpilot, use the URL `openpilot.comma.ai`. - To install openpilot master (for more advanced users), use the URL `installer.comma.ai/commaai/master`. You can replace "commaai" with another GitHub username to install a fork. - -3. **Supported Car:** Ensure that you have one of [the 250+ supported cars](docs/CARS.md). openpilot supports a wide range of car makes including Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and many more. - If your car is not officially listed as supported but has adaptive cruise control and lane-keeping assist, it's likely capable of running openpilot. - +To use openpilot in a car, you need four things: +1. **Supported Device:** a comma 3/3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). +2. **Software:** The setup procedure for the comma 3/3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. +3. **Supported Car:** Ensure that you have one of [the 250+ supported cars](docs/CARS.md). 4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car. - We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). + +We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Running on PC ------ @@ -58,22 +32,32 @@ All openpilot services can run as usual on a PC without requiring special hardwa With openpilot's tools, you can plot logs, replay drives, and watch the full-res camera streams. See [the tools README](tools/README.md) for more information. -You can also run openpilot in simulation [with the MetaDrive simulator](tools/sim/README.md). This allows openpilot to drive around a virtual car on your Ubuntu machine. The whole setup should only take a few minutes but does require a decent GPU. +You can also run openpilot in simulation [with the CARLA simulator](tools/sim/README.md). This allows openpilot to drive around a virtual car on your Ubuntu machine. The whole setup should only take a few minutes but does require a decent GPU. A PC running openpilot can also control your vehicle if it is connected to a [webcam](https://github.com/commaai/openpilot/tree/master/tools/webcam), a [black panda](https://comma.ai/shop/products/panda), and a [harness](https://comma.ai/shop/products/car-harness). Community and Contributing ------ -openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot). Bug fixes and new car ports are encouraged. Check out [the contributing docs](docs/CONTRIBUTING.md). +openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot). -Documentation related to openpilot development can be found on [docs.comma.ai](https://docs.comma.ai). Information about running openpilot (e.g. FAQ, fingerprinting, troubleshooting, custom forks, community hardware) should go on the [wiki](https://github.com/commaai/openpilot/wiki). +* Join the [community Discord](https://discord.comma.ai) +* Check out [the contributing docs](docs/CONTRIBUTING.md) +* Code documentation lives at https://docs.comma.ai +* Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki) -You can add support for your car by following guides we have written for [Brand](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/) and [Model](https://blog.comma.ai/openpilot-port-guide-for-toyota-models/) ports. Generally, a car with adaptive cruise control and lane keep assist is a good candidate. [Join our Discord](https://discord.comma.ai) to discuss car ports: most car makes have a dedicated channel. +Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions) and offers lots of [bounties](docs/BOUNTIES.md). -Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions). +Safety and Testing +---- -And [follow us on Twitter](https://twitter.com/comma_ai). +* openpilot observes [ISO26262](https://en.wikipedia.org/wiki/ISO_26262) guidelines, see [SAFETY.md](docs/SAFETY.md) for more details. +* openpilot has software-in-the-loop [tests](.github/workflows/selfdrive_tests.yaml) that run on every commit. +* The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details. +* panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety). +* Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes. +* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile). +* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes. User Data and comma Account ------ @@ -87,49 +71,6 @@ The driver-facing camera is only logged if you explicitly opt-in in settings. Th By using openpilot, you agree to [our Privacy Policy](https://comma.ai/privacy). You understand that use of this software or its related services will generate certain types of user data, which may be logged and stored at the sole discretion of comma. By accepting this agreement, you grant an irrevocable, perpetual, worldwide right to comma for the use of this data. -Safety and Testing ----- - -* openpilot observes ISO26262 guidelines, see [SAFETY.md](docs/SAFETY.md) for more details. -* openpilot has software-in-the-loop [tests](.github/workflows/selfdrive_tests.yaml) that run on every commit. -* The code enforcing the safety model lives in panda and is written in C, see [code rigor](https://github.com/commaai/panda#code-rigor) for more details. -* panda has software-in-the-loop [safety tests](https://github.com/commaai/panda/tree/master/tests/safety). -* Internally, we have a hardware-in-the-loop Jenkins test suite that builds and unit tests the various processes. -* panda has additional hardware-in-the-loop [tests](https://github.com/commaai/panda/blob/master/Jenkinsfile). -* We run the latest openpilot in a testing closet containing 10 comma devices continuously replaying routes. - -Directory Structure ------- - . - ├── cereal # The messaging spec and libs used for all logs - ├── common # Library like functionality we've developed here - ├── docs # Documentation - ├── opendbc # Files showing how to interpret data from cars - ├── panda # Code used to communicate on CAN - ├── third_party # External libraries - └── system # Generic services - ├── camerad # Driver to capture images from the camera sensors - ├── hardware # Hardware abstraction classes - ├── logcatd # systemd journal as a service - ├── loggerd # Logger and uploader of car data - ├── proclogd # Logs information from /proc - ├── sensord # IMU interface code - └── ubloxd # u-blox GNSS module interface code - └── selfdrive # Code needed to drive the car - ├── assets # Fonts, images, and sounds for UI - ├── athena # Allows communication with the app - ├── boardd # Daemon to talk to the board - ├── car # Car specific code to read states and control actuators - ├── controls # Planning and controls - ├── debug # Tools to help you debug and do car ports - ├── locationd # Precise localization and vehicle parameter estimation - ├── manager # Daemon that starts/stops all other daemons as needed - ├── modeld # Driving and monitoring model runners - ├── monitoring # Daemon to determine driver attention - ├── navd # Turn-by-turn navigation - ├── test # Unit tests, system tests, and a car simulator - └── ui # The UI - Licensing ------ @@ -145,5 +86,5 @@ NO WARRANTY EXPRESSED OR IMPLIED.** -[![openpilot tests](https://github.com/commaai/openpilot/workflows/openpilot%20tests/badge.svg?event=push)](https://github.com/commaai/openpilot/actions) +![openpilot tests](https://github.com/commaai/openpilot/actions/workflows/selfdrive_tests.yaml/badge.svg) [![codecov](https://codecov.io/gh/commaai/openpilot/branch/master/graph/badge.svg)](https://codecov.io/gh/commaai/openpilot) From f36866c89841df89f118e4fbfad2f86d15a1c71c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 13:50:30 -0800 Subject: [PATCH 252/724] better video thumbnails --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d7f8effb81..8f9d3e2ce3 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,9 @@ What is openpilot? - - - + + +
- drive to taco bell
From f78e7ed175a4dea030d180d2703bbf11c733a08d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 13:59:22 -0800 Subject: [PATCH 253/724] jk --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 4e72c3c986..226a272824 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,4 @@ -Version 0.9.6 (2023-12-14) +Version 0.9.6 (20XX-XX-XX) ======================== * New driving model * Vision model trained on more data From bd9d5f363dd9381f04c1fb3f68872e58a0fbeac0 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 14 Dec 2023 14:13:11 -0800 Subject: [PATCH 254/724] make the main photo link to the comma shop (#30729) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f9d3e2ce3..321520b8f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![openpilot on the comma 3X](https://github.com/commaai/openpilot/assets/8762862/f09e6d29-db2d-4179-80c2-51e8d92bdb5c) +[![openpilot on the comma 3X](https://github.com/commaai/openpilot/assets/8762862/f09e6d29-db2d-4179-80c2-51e8d92bdb5c)](https://comma.ai/shop/comma-3x) What is openpilot? ------ From e82d5419a84b721d2f6bb965b80619b880148010 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 14 Dec 2023 14:43:42 -0800 Subject: [PATCH 255/724] remove carla from README (#30731) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 321520b8f9..31f88158a1 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ All openpilot services can run as usual on a PC without requiring special hardwa With openpilot's tools, you can plot logs, replay drives, and watch the full-res camera streams. See [the tools README](tools/README.md) for more information. -You can also run openpilot in simulation [with the CARLA simulator](tools/sim/README.md). This allows openpilot to drive around a virtual car on your Ubuntu machine. The whole setup should only take a few minutes but does require a decent GPU. +You can also run openpilot in simulation [with the MetaDrive simulator](tools/sim/README.md). This allows openpilot to drive around a virtual car on your Ubuntu machine. A PC running openpilot can also control your vehicle if it is connected to a [webcam](https://github.com/commaai/openpilot/tree/master/tools/webcam), a [black panda](https://comma.ai/shop/products/panda), and a [harness](https://comma.ai/shop/products/car-harness). From 5b909c7d5277f62614a2108f294732b0a5e6ae40 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 14:51:56 -0800 Subject: [PATCH 256/724] Update CONTRIBUTING.md --- docs/CONTRIBUTING.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index a3eebca630..fa80e760de 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -11,7 +11,7 @@ Our software is open source so you can solve your own problems without needing h ## What contributions are we looking for? -**openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all developoment is towards that goal. +**openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and **all** developoment is towards that goal. ### What gets merged? @@ -27,9 +27,11 @@ All of these are examples of good PRs: ### What doesn't get merged? -* arbitrary style changes -* PRs without a clear goal -* 500+ line PRs: it should be either cleaned up, broken up into smaller PRs, or both +* **arbitrary style changes**: code is art, and it's up to the author to make it beautiful +* **500+ line PRs**: clean it up, break it up into smaller PRs, or both +* **PRs without a clear goal**: every PR must have a singular and clear goal +* **UI design changes**: we do not have a good review process for this yet +* **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't. ### First contribution From 582743fdb5a20fbbe841aa154cf36304771d52a6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 14 Dec 2023 16:53:22 -0800 Subject: [PATCH 257/724] Use a safety param for gas interceptor (#30719) * bump * bump * space * remove from boardd * set flag for honda to pass tests * migrate * Update ref_commit * final bump * bump cereal * bump to master --- cereal | 2 +- panda | 2 +- selfdrive/boardd/boardd.cc | 1 - selfdrive/car/honda/interface.py | 3 +++ selfdrive/car/toyota/interface.py | 3 +++ selfdrive/test/process_replay/migration.py | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 7 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cereal b/cereal index d11688a90a..8ae5c51a65 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit d11688a90a1cebacb3fe00dcfbba5f27551b2d89 +Subproject commit 8ae5c51a6515c171f3a82c9511a18d79a9e55c07 diff --git a/panda b/panda index a88fe8c883..bb75afc84e 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit a88fe8c883758951ba2854eb2f6726d5365df099 +Subproject commit bb75afc84e4d33af1554ada81851547db648e33e diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 61e2424744..9e2a960055 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -351,7 +351,6 @@ std::optional send_panda_states(PubMaster *pm, const std::vector ps.setIgnitionLine(health.ignition_line_pkt); ps.setIgnitionCan(health.ignition_can_pkt); ps.setControlsAllowed(health.controls_allowed_pkt); - ps.setGasInterceptorDetected(health.gas_interceptor_detected_pkt); ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt); ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt); ps.setGmlanSendErrs(health.gmlan_send_errs_pkt); diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index d1a287a76a..9f228cd8fb 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -285,6 +285,9 @@ class CarInterface(CarInterfaceBase): if ret.openpilotLongitudinalControl and candidate in HONDA_BOSCH: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_BOSCH_LONG + if ret.enableGasInterceptor and candidate not in HONDA_BOSCH: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_GAS_INTERCEPTOR + if candidate in HONDA_BOSCH_RADARLESS: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_RADARLESS diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index ae4cb1f2c5..b5151f4cda 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -222,6 +222,9 @@ class CarInterface(CarInterfaceBase): and not (ret.flags & ToyotaFlags.SMART_DSU) ret.enableGasInterceptor = 0x201 in fingerprint[0] + if ret.enableGasInterceptor: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_GAS_INTERCEPTOR + # if the smartDSU is detected, openpilot can send ACC_CONTROL and the smartDSU will block it from the DSU or radar. # since we don't yet parse radar on TSS2/TSS-P radar-based ACC cars, gate longitudinal behind experimental toggle use_sdsu = bool(ret.flags & ToyotaFlags.SMART_DSU) diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 831bea7c6f..ef74314172 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -40,7 +40,7 @@ def migrate_pandaStates(lr): # TODO: safety param migration should be handled automatically safety_param_migration = { "TOYOTA PRIUS 2017": EPS_SCALE["TOYOTA PRIUS 2017"] | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL, - "TOYOTA RAV4 2017": EPS_SCALE["TOYOTA RAV4 2017"] | Panda.FLAG_TOYOTA_ALT_BRAKE, + "TOYOTA RAV4 2017": EPS_SCALE["TOYOTA RAV4 2017"] | Panda.FLAG_TOYOTA_ALT_BRAKE | Panda.FLAG_TOYOTA_GAS_INTERCEPTOR, "KIA EV6 2022": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CANFD_HDA2, } diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 030cbc4e07..559749be14 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -921222d49db204071f0a7006fc895690e1045b5d \ No newline at end of file +ea96f935a7a16c53623c3b03e70c0fbfa6b249e7 From 5d668701cb69c1e3d170109a8e788a2acbacebd3 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 14 Dec 2023 16:59:28 -0800 Subject: [PATCH 258/724] CI: move test_maprenderer to GH actions (#30610) maprenderer to ci --- .github/workflows/badges.yaml | 2 +- .github/workflows/docs.yaml | 2 +- .github/workflows/selfdrive_tests.yaml | 8 +++--- .github/workflows/tools_tests.yaml | 4 +-- Jenkinsfile | 1 - pyproject.toml | 1 + selfdrive/navd/tests/test_map_renderer.py | 31 +++++++++++++---------- selfdrive/test/setup_xvfb.sh | 15 +++++++++++ 8 files changed, 43 insertions(+), 21 deletions(-) create mode 100755 selfdrive/test/setup_xvfb.sh diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index c19be4c563..923e249849 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -7,7 +7,7 @@ on: env: BASE_IMAGE: openpilot-base DOCKER_REGISTRY: ghcr.io/commaai - RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/sh -c + RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/bash -c jobs: badges: diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index c1d4eae52f..6ad273a14d 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -15,7 +15,7 @@ env: BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c + RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c jobs: docs: diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index c47947f90d..708f16368f 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -20,11 +20,11 @@ env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c + RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c BUILD_CL: selfdrive/test/docker_build.sh cl - RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c + RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/bash -c PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 @@ -175,7 +175,9 @@ jobs: - name: Run unit tests timeout-minutes: 15 run: | - ${{ env.RUN }} "$PYTEST --timeout 30 -m 'not slow' && \ + ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ + export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ + $PYTEST --timeout 30 -m 'not slow' && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py && \ diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 75a34b3c55..d564606c3e 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -17,11 +17,11 @@ env: BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c + RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c BUILD_CL: selfdrive/test/docker_build.sh cl - RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c + RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/bash -c jobs: diff --git a/Jenkinsfile b/Jenkinsfile index 2118c4c4ed..926a9bbfc8 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -187,7 +187,6 @@ node { ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"], ["test pigeond", "pytest system/sensord/tests/test_pigeond.py"], ["test manager", "pytest selfdrive/manager/test/test_manager.py"], - ["test nav", "pytest selfdrive/navd/tests/"], ]) }, 'loopback': { diff --git a/pyproject.toml b/pyproject.toml index 58c506e378..07cf9a2233 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ testpaths = [ "selfdrive/controls", "selfdrive/locationd", "selfdrive/monitoring", + "selfdrive/navd/tests", "selfdrive/thermald", "selfdrive/test/longitudinal_maneuvers", "system/hardware/tici", diff --git a/selfdrive/navd/tests/test_map_renderer.py b/selfdrive/navd/tests/test_map_renderer.py index dbd6add995..d336ffb651 100755 --- a/selfdrive/navd/tests/test_map_renderer.py +++ b/selfdrive/navd/tests/test_map_renderer.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import time import numpy as np import os import pytest @@ -10,7 +11,7 @@ import cereal.messaging as messaging from typing import Any from cereal.visionipc import VisionIpcClient, VisionStreamType -from openpilot.selfdrive.manager.process_config import managed_processes +from openpilot.selfdrive.test.helpers import with_processes LLK_DECIMATION = 10 CACHE_PATH = "/data/mbgl-cache-navd.db" @@ -18,7 +19,8 @@ CACHE_PATH = "/data/mbgl-cache-navd.db" LOCATION1 = (32.7174, -117.16277) LOCATION2 = (32.7558, -117.2037) -DEFAULT_ITERATIONS = 30 * LLK_DECIMATION +RENDER_FRAMES = 15 +DEFAULT_ITERATIONS = RENDER_FRAMES * LLK_DECIMATION LOCATION1_REPEATED = [LOCATION1] * DEFAULT_ITERATIONS LOCATION2_REPEATED = [LOCATION2] * DEFAULT_ITERATIONS @@ -60,7 +62,8 @@ class MapBoxInternetDisabledRequestHandler(http.server.BaseHTTPRequestHandler): class MapBoxInternetDisabledServer(threading.Thread): def run(self): - self.server = http.server.HTTPServer(("127.0.0.1", 5000), MapBoxInternetDisabledRequestHandler) + self.server = http.server.HTTPServer(("127.0.0.1", 0), MapBoxInternetDisabledRequestHandler) + self.port = self.server.server_port self.server.serve_forever() def stop(self): @@ -74,13 +77,15 @@ class MapBoxInternetDisabledServer(threading.Thread): class TestMapRenderer(unittest.TestCase): - server = MapBoxInternetDisabledServer() + server: MapBoxInternetDisabledServer @classmethod def setUpClass(cls): assert "MAPBOX_TOKEN" in os.environ cls.original_token = os.environ["MAPBOX_TOKEN"] + cls.server = MapBoxInternetDisabledServer() cls.server.start() + time.sleep(0.5) # wait for server to startup @classmethod def tearDownClass(cls) -> None: @@ -88,7 +93,7 @@ class TestMapRenderer(unittest.TestCase): def setUp(self): self.server.enable_internet() - os.environ['MAPS_HOST'] = 'http://localhost:5000' + os.environ['MAPS_HOST'] = f'http://localhost:{self.server.port}' self.sm = messaging.SubMaster(['mapRenderState']) self.pm = messaging.PubMaster(['liveLocationKalman']) @@ -97,15 +102,11 @@ class TestMapRenderer(unittest.TestCase): if os.path.exists(CACHE_PATH): os.remove(CACHE_PATH) - def tearDown(self): - managed_processes['mapsd'].stop() - def _setup_test(self): - # start + sync up - managed_processes['mapsd'].start() - assert self.pm.wait_for_readers_to_update("liveLocationKalman", 10) + time.sleep(0.5) + assert VisionIpcClient.available_streams("navd", False) == {VisionStreamType.VISION_STREAM_MAP, } assert self.vipc.connect(False) self.vipc.recv() @@ -164,17 +165,22 @@ class TestMapRenderer(unittest.TestCase): assert self.vipc.timestamp_sof == llk.logMonoTime assert self.vipc.frame_id == self.sm['mapRenderState'].frameId + assert frames_since_test_start >= RENDER_FRAMES + return render_times + @with_processes(["mapsd"]) def test_with_internet(self): self._setup_test() self._run_test(True) + @with_processes(["mapsd"]) def test_with_no_internet(self): self.server.disable_internet() self._setup_test() self._run_test(False) + @with_processes(["mapsd"]) def test_recover_from_no_internet(self): self._setup_test() self._run_test(True) @@ -187,8 +193,7 @@ class TestMapRenderer(unittest.TestCase): self.server.enable_internet() self._run_test(True, LOCATION2_REPEATED) - self._run_test(True, LOCATION2_REPEATED) - + @with_processes(["mapsd"]) @pytest.mark.tici def test_render_time_distribution(self): self._setup_test() diff --git a/selfdrive/test/setup_xvfb.sh b/selfdrive/test/setup_xvfb.sh new file mode 100755 index 0000000000..2c79adec4a --- /dev/null +++ b/selfdrive/test/setup_xvfb.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Sets up a virtual display for running map renderer and simulator without an X11 display + +DISP_ID=99 +export DISPLAY=:$DISP_ID + +sudo Xvfb $DISPLAY -screen 0 2160x1080x24 & + +# check for x11 socket for the specified display ID +while [ ! -S /tmp/.X11-unix/X$DISP_ID ] +do + echo "Waiting for Xvfb..." + sleep 1 +done \ No newline at end of file From 23c20bce8f90aabec31fece84f4f8f6a3770d61b Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 14 Dec 2023 17:24:47 -0800 Subject: [PATCH 259/724] test_athena: remove slow memory leak test (#30734) way too long --- selfdrive/athena/tests/test_athenad.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index beffa0d232..a562ae8582 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -13,7 +13,6 @@ from datetime import datetime, timedelta from parameterized import parameterized from typing import Optional -from pympler.tracker import SummaryTracker from unittest import mock from websocket import ABNF from websocket._exceptions import WebSocketConnectionClosedException @@ -144,11 +143,6 @@ class TestAthenadMethods(unittest.TestCase): # random bytes to ensure rather large object post-compression fn = self._create_file('qlog', data=os.urandom(10000 * 1024)) - # warm up object tracker - tracker = SummaryTracker() - for _ in range(5): - tracker.diff() - upload_fn = fn + ('.bz2' if compress else '') item = athenad.UploadItem(path=upload_fn, url="http://localhost:1238", headers={}, created_at=int(time.time()*1000), id='') with self.assertRaises(requests.exceptions.ConnectionError): @@ -158,11 +152,6 @@ class TestAthenadMethods(unittest.TestCase): resp = athenad._do_upload(item) self.assertEqual(resp.status_code, 201) - # assert memory cleaned up - for _type, num_objects, total_size in tracker.diff(): - with self.subTest(_type=_type): - self.assertLess(total_size / 1024, 10, f'Object {_type} ({num_objects=}) grew larger than 10 kB while uploading file') - @with_http_server def test_uploadFileToUrl(self, host): fn = self._create_file('qlog.bz2') From 977dd033c4b2003c06978eed391e744ed06ea8f9 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 14 Dec 2023 19:34:06 -0800 Subject: [PATCH 260/724] Simulator: fix safety param (#30738) fix param --- tools/sim/lib/simulated_car.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/sim/lib/simulated_car.py b/tools/sim/lib/simulated_car.py index 0740f9e3aa..85b4ac2387 100644 --- a/tools/sim/lib/simulated_car.py +++ b/tools/sim/lib/simulated_car.py @@ -5,6 +5,7 @@ from opendbc.can.parser import CANParser from openpilot.selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp from openpilot.selfdrive.car import crc8_pedal from openpilot.tools.sim.lib.common import SimulatorState +from panda.python import Panda class SimulatedCar: @@ -106,7 +107,8 @@ class SimulatedCar: 'pandaType': "blackPanda", 'controlsAllowed': True, 'safetyModel': 'hondaNidec', - 'alternativeExperience': self.sm["carParams"].alternativeExperience + 'alternativeExperience': self.sm["carParams"].alternativeExperience, + 'safetyParam': Panda.FLAG_HONDA_GAS_INTERCEPTOR } self.pm.send('pandaStates', dat) From 303f1cf9cf2f0c933fca103f104b382523db6cf2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 19:59:16 -0800 Subject: [PATCH 261/724] jenkins: run test_onroad in the normal env (#30740) --- Jenkinsfile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 926a9bbfc8..4c0976eb1b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -104,7 +104,7 @@ def pcStage(String stageName, Closure body) { def openpilot_base = retryWithDelay (3, 15) { return docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .") } - + openpilot_base.inside(dockerArgs) { timeout(time: 20, unit: 'MINUTES') { try { @@ -171,8 +171,9 @@ node { parallel ( // tici tests 'onroad tests': { - deviceStage("onroad", "tici-needs-can", ["SKIP_COPY=1"], [ - ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"], + deviceStage("onroad", "tici-needs-can", [], [ + // TODO: ideally, this test runs in master-ci, but it takes 5+m to build it + //["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"], From 05d5056e8c9dff538b5a5be66022bf32c8995802 Mon Sep 17 00:00:00 2001 From: royjr Date: Thu, 14 Dec 2023 23:36:33 -0500 Subject: [PATCH 262/724] car docs: remove outdated video link (#30737) remove outdated link --- docs/CARS.md | 2 +- selfdrive/car/hyundai/values.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 113e7c877b..e48f4765bb 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -97,7 +97,7 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index a8d7c06b9e..08611de790 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -191,8 +191,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.KONA: HyundaiCarInfo("Hyundai Kona 2020", car_parts=CarParts.common([CarHarness.hyundai_b])), CAR.KONA_EV: HyundaiCarInfo("Hyundai Kona Electric 2018-21", car_parts=CarParts.common([CarHarness.hyundai_g])), CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022-23", car_parts=CarParts.common([CarHarness.hyundai_o])), - CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", video_link="https://youtu.be/0dwpAHiZgFo", - car_parts=CarParts.common([CarHarness.hyundai_i])), # TODO: check packages + CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", car_parts=CarParts.common([CarHarness.hyundai_i])), # TODO: check packages # TODO: this is the 2024 US MY, not yet released CAR.KONA_EV_2ND_GEN: HyundaiCarInfo("Hyundai Kona Electric (with HDA II, Korea only) 2023", video_link="https://www.youtube.com/watch?v=U2fOCmcQ8hw", car_parts=CarParts.common([CarHarness.hyundai_r])), From 2cad6dfa58f58af09ff70c70ad557fdf761b9293 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 20:41:11 -0800 Subject: [PATCH 263/724] jenkins: only run pandad tests when paths change (#30741) * jenkins: only run pandad when paths changed * fix * ugh shadow --- Jenkinsfile | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4c0976eb1b..25649c4949 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -67,13 +67,14 @@ END""" } } -def deviceStage(String stageName, String deviceType, List env, def steps) { +def deviceStage(String stageName, String deviceType, List extra_env, def steps) { stage(stageName) { if (currentBuild.result != null) { return } - def extra = env.collect { "export ${it}" }.join('\n'); + def extra = extra_env.collect { "export ${it}" }.join('\n'); + def branch = env.BRANCH_NAME ?: 'master'; docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') { lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1) { @@ -82,6 +83,10 @@ def deviceStage(String stageName, String deviceType, List env, def steps) { device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh")) } steps.each { item -> + if (branch != "master" && item.size() == 3 && !hasDirectoryChanged(item[2])) { + println "Skipped '${item[0]}', no relevant changes were detected." + return; + } device(device_ip, item[0], item[1]) } } @@ -134,6 +139,21 @@ def setupCredentials() { } } +def hasDirectoryChanged(List paths) { + for (change in currentBuild.changeSets) { + for (item in change.items) { + for (affectedPath in item.affectedPaths) { + for (path in paths) { + if (affectedPath.startsWith(path)) { + return true + } + } + } + } + } + return false +} + node { env.CI = "1" env.PYTHONWARNINGS = "error" @@ -171,7 +191,7 @@ node { parallel ( // tici tests 'onroad tests': { - deviceStage("onroad", "tici-needs-can", [], [ + deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [ // TODO: ideally, this test runs in master-ci, but it takes 5+m to build it //["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], @@ -183,7 +203,7 @@ node { 'HW + Unit Tests': { deviceStage("tici", "tici-common", ["UNSAFE=1"], [ ["build", "cd selfdrive/manager && ./build.py"], - ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], + ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py", ["panda/", "selfdrive/boardd/"]], ["test power draw", "./system/hardware/tici/tests/test_power_draw.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"], ["test pigeond", "pytest system/sensord/tests/test_pigeond.py"], @@ -228,7 +248,7 @@ node { deviceStage("tizi", "tizi", ["UNSAFE=1"], [ ["build openpilot", "cd selfdrive/manager && ./build.py"], ["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.py"], - ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], + ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py", ["panda/", "selfdrive/boardd/"]], ["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"], ["test hw", "pytest system/hardware/tici/tests/test_hardware.py"], ["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"], From 0830f62b36fd4e802ea6f1a74062ae9e8bd7ecd1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 20:44:14 -0800 Subject: [PATCH 264/724] also run qcomgpsd on path update --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 25649c4949..2522dcc0cd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -251,7 +251,7 @@ node { ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py", ["panda/", "selfdrive/boardd/"]], ["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"], ["test hw", "pytest system/hardware/tici/tests/test_hardware.py"], - ["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"], + ["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", ["system/qcomgpsd/"]], ]) }, From 5fe9f1459c73d338bf6277dafc8654f92ee8b00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Thu, 14 Dec 2023 20:51:06 -0800 Subject: [PATCH 265/724] metadrive: fix dual_camera mode (#30743) * Use wide cam in metadrive * 120 fov --- tools/sim/bridge/metadrive/metadrive_bridge.py | 4 ++-- tools/sim/bridge/metadrive/metadrive_process.py | 9 ++++++--- tools/sim/bridge/metadrive/metadrive_world.py | 6 +++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_bridge.py b/tools/sim/bridge/metadrive/metadrive_bridge.py index fa946e11e3..142a9e0462 100644 --- a/tools/sim/bridge/metadrive/metadrive_bridge.py +++ b/tools/sim/bridge/metadrive/metadrive_bridge.py @@ -36,7 +36,7 @@ class RGBCameraWide(CopyRamRGBCamera): cam = self.get_cam() cam.setPos(C3_POSITION) lens = self.get_lens() - lens.setFov(160) + lens.setFov(120) class RGBCameraRoad(CopyRamRGBCamera): def __init__(self, *args, **kwargs): @@ -116,4 +116,4 @@ class MetaDriveBridge(SimulatorBridge): preload_models=False ) - return MetaDriveWorld(config) + return MetaDriveWorld(config, self.dual_camera) diff --git a/tools/sim/bridge/metadrive/metadrive_process.py b/tools/sim/bridge/metadrive/metadrive_process.py index 3ce89d7d41..d55ecd7ae5 100644 --- a/tools/sim/bridge/metadrive/metadrive_process.py +++ b/tools/sim/bridge/metadrive/metadrive_process.py @@ -39,10 +39,13 @@ def apply_metadrive_patches(): MetaDriveEnv._is_arrive_destination = arrive_destination_patch -def metadrive_process(dual_camera: bool, config: dict, camera_array, controls_recv: Connection, state_send: Connection, exit_event): +def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera_array, controls_recv: Connection, state_send: Connection, exit_event): apply_metadrive_patches() road_image = np.frombuffer(camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) + if dual_camera: + assert wide_camera_array is not None + wide_road_image = np.frombuffer(wide_camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) env = MetaDriveEnv(config) @@ -92,8 +95,8 @@ def metadrive_process(dual_camera: bool, config: dict, camera_array, controls_re if terminated: reset() - #if dual_camera: - # wide_road_image = get_cam_as_rgb("rgb_wide") + if dual_camera: + wide_road_image[...] = get_cam_as_rgb("rgb_wide") road_image[...] = get_cam_as_rgb("rgb_road") rk.keep_time() diff --git a/tools/sim/bridge/metadrive/metadrive_world.py b/tools/sim/bridge/metadrive/metadrive_world.py index 3705f19a97..570c9cb4f5 100644 --- a/tools/sim/bridge/metadrive/metadrive_world.py +++ b/tools/sim/bridge/metadrive/metadrive_world.py @@ -15,6 +15,10 @@ class MetaDriveWorld(World): super().__init__(dual_camera) self.camera_array = Array(ctypes.c_uint8, W*H*3) self.road_image = np.frombuffer(self.camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) + self.wide_camera_array = None + if dual_camera: + self.wide_camera_array = Array(ctypes.c_uint8, W*H*3) + self.wide_road_image = np.frombuffer(self.wide_camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) self.controls_send, self.controls_recv = Pipe() self.state_send, self.state_recv = Pipe() @@ -23,7 +27,7 @@ class MetaDriveWorld(World): self.metadrive_process = multiprocessing.Process(name="metadrive process", target= functools.partial(metadrive_process, dual_camera, config, - self.camera_array, self.controls_recv, self.state_send, self.exit_event)) + self.camera_array, self.wide_camera_array, self.controls_recv, self.state_send, self.exit_event)) self.metadrive_process.start() print("----------------------------------------------------------") From da12a34ff66dbe70f532056ba43a6fdb228d41d3 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 14 Dec 2023 21:03:02 -0800 Subject: [PATCH 266/724] Metadrive: set near clip (#30745) set near --- tools/sim/bridge/metadrive/metadrive_bridge.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/sim/bridge/metadrive/metadrive_bridge.py b/tools/sim/bridge/metadrive/metadrive_bridge.py index 142a9e0462..c8d59ff5e6 100644 --- a/tools/sim/bridge/metadrive/metadrive_bridge.py +++ b/tools/sim/bridge/metadrive/metadrive_bridge.py @@ -37,6 +37,7 @@ class RGBCameraWide(CopyRamRGBCamera): cam.setPos(C3_POSITION) lens = self.get_lens() lens.setFov(120) + lens.setNear(0.1) class RGBCameraRoad(CopyRamRGBCamera): def __init__(self, *args, **kwargs): @@ -45,6 +46,7 @@ class RGBCameraRoad(CopyRamRGBCamera): cam.setPos(C3_POSITION) lens = self.get_lens() lens.setFov(40) + lens.setNear(0.1) def straight_block(length): From 10ea45183184001b1b3ac513b96f233ed7053cb9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 22:24:09 -0800 Subject: [PATCH 267/724] test_onroad: fix test_log_sizes when running inside a prefix --- selfdrive/test/test_onroad.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index d7c5c58280..e3807af143 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -168,6 +168,15 @@ class TestOnroad(unittest.TestCase): cls.lr = list(LogReader(os.path.join(str(cls.segments[1]), "rlog"))) cls.log_path = cls.segments[1] + cls.log_sizes = {} + for f in cls.log_path.iterdir(): + assert f.is_file() + cls.log_sizes[f] = f.stat().st_size / 1e6 + if f.name in ("qlog", "rlog"): + with open(f, 'rb') as ff: + cls.log_sizes[f] = len(bz2.compress(ff.read())) / 1e6 + + @cached_property def service_msgs(self): msgs = defaultdict(list) @@ -198,14 +207,7 @@ class TestOnroad(unittest.TestCase): self.assertEqual(len(big_logs), 0, f"Log spam: {big_logs}") def test_log_sizes(self): - for f in self.log_path.iterdir(): - assert f.is_file() - - sz = f.stat().st_size / 1e6 - if f.name in ("qlog", "rlog"): - with open(f, 'rb') as ff: - sz = len(bz2.compress(ff.read())) / 1e6 - + for f, sz in self.log_sizes.items(): if f.name == "qcamera.ts": assert 2.15 < sz < 2.35 elif f.name == "qlog": From 37351cbdc1e40c5d990035d867008aec44b64c76 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 22:30:46 -0800 Subject: [PATCH 268/724] uploader: reduce max qlog/qcam size (#30746) --- system/loggerd/uploader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 4bc031a632..914c9e8079 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -25,7 +25,7 @@ NetworkType = log.DeviceState.NetworkType UPLOAD_ATTR_NAME = 'user.upload' UPLOAD_ATTR_VALUE = b'1' -UPLOAD_QLOG_QCAM_MAX_SIZE = 100 * 1e6 # MB +UPLOAD_QLOG_QCAM_MAX_SIZE = 5 * 1e6 # MB allow_sleep = bool(os.getenv("UPLOADER_SLEEP", "1")) force_wifi = os.getenv("FORCEWIFI") is not None From d6eb1265a0cf14f2d53a2eb8250e0d6990503c7f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 14 Dec 2023 22:31:29 -0800 Subject: [PATCH 269/724] Docker: cleanup .git and test folders (#30747) docker cleanup --- Dockerfile.openpilot_base | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 98d07ea39e..028d877a93 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -40,7 +40,9 @@ COPY --chown=$USER tools/install_python_dependencies.sh /tmp/tools/ RUN cd /tmp && \ tools/install_python_dependencies.sh && \ rm -rf /tmp/* && \ - rm -rf /home/$USER/.cache + rm -rf /home/$USER/.cache && \ + find /home/$USER/pyenv -type d -name ".git" | xargs rm -rf && \ + rm -rf /home/$USER/pyenv/versions/3.11.4/lib/python3.11/test USER root RUN sudo git config --global --add safe.directory /tmp/openpilot \ No newline at end of file From 6f143f6c45b04702de24371db2d836210be9b860 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 23:00:07 -0800 Subject: [PATCH 270/724] full clean for onroad device --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2522dcc0cd..3f573e6ae4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -191,7 +191,7 @@ node { parallel ( // tici tests 'onroad tests': { - deviceStage("onroad", "tici-needs-can", ["UNSAFE=1"], [ + deviceStage("onroad", "tici-needs-can", [], [ // TODO: ideally, this test runs in master-ci, but it takes 5+m to build it //["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], From 1733b04741323155ee241fa5f300e779f3c931bb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 14 Dec 2023 23:40:46 -0800 Subject: [PATCH 271/724] jenkins: always do the copy in safe_checkout --- selfdrive/test/setup_device_ci.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index a180fe0de6..5c85312f1a 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -79,9 +79,7 @@ safe_checkout() { echo "git checkout done, t=$SECONDS" du -hs $SOURCE_DIR $SOURCE_DIR/.git - if [ -z "SKIP_COPY" ]; then - rsync -a --delete $SOURCE_DIR $TEST_DIR - fi + rsync -a --delete $SOURCE_DIR $TEST_DIR } unsafe_checkout() { From 1ca1cf39223557955cee58e18a418914faf472c3 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 15 Dec 2023 14:50:37 -0800 Subject: [PATCH 272/724] test_models: check consitency between panda and openpilot for velocity (#30750) * sanity check on velocity * import from there * use raw * just check last * ignore body * reduce tolerance and bump panda * its not average anymore * slightly higher tolerance --- panda | 2 +- selfdrive/car/tests/test_models.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/panda b/panda index bb75afc84e..7e2b312fa8 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit bb75afc84e4d33af1554ada81851547db648e33e +Subproject commit 7e2b312fa86e6cdfeba496f261407cb47cb005d1 diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index aaaa6b9d79..3a76eff3ab 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -23,6 +23,7 @@ from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import Route, SegmentName, RouteName from panda.tests.libpanda import libpanda_py +from panda.tests.safety.common import VEHICLE_SPEED_FACTOR EventName = car.CarEvent.EventName PandaType = log.PandaState.PandaType @@ -345,6 +346,11 @@ class TestCarModelBase(unittest.TestCase): checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev() checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev() + # Verify that panda has the correct velocity for cars that use it (angle based cars) + if self.CP.steerControlType in [car.CarParams.SteerControlType.angle] and not self.CP.notCar: + panda_velocity = self.safety.get_vehicle_speed_last() / VEHICLE_SPEED_FACTOR + checks['vEgo'] += abs(panda_velocity - CS.vEgoRaw) > 0.2 + if self.CP.pcmCruise: # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state. # On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but From 123ef46af3694afc30b4ec28ce8f995d9fa92cd1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 15 Dec 2023 17:16:01 -0800 Subject: [PATCH 273/724] Revert "test_models: check consitency between panda and openpilot for velocity (#30750)" (#30753) This reverts commit 1ca1cf39223557955cee58e18a418914faf472c3. --- panda | 2 +- selfdrive/car/tests/test_models.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/panda b/panda index 7e2b312fa8..bb75afc84e 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 7e2b312fa86e6cdfeba496f261407cb47cb005d1 +Subproject commit bb75afc84e4d33af1554ada81851547db648e33e diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 3a76eff3ab..aaaa6b9d79 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -23,7 +23,6 @@ from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import Route, SegmentName, RouteName from panda.tests.libpanda import libpanda_py -from panda.tests.safety.common import VEHICLE_SPEED_FACTOR EventName = car.CarEvent.EventName PandaType = log.PandaState.PandaType @@ -346,11 +345,6 @@ class TestCarModelBase(unittest.TestCase): checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev() checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev() - # Verify that panda has the correct velocity for cars that use it (angle based cars) - if self.CP.steerControlType in [car.CarParams.SteerControlType.angle] and not self.CP.notCar: - panda_velocity = self.safety.get_vehicle_speed_last() / VEHICLE_SPEED_FACTOR - checks['vEgo'] += abs(panda_velocity - CS.vEgoRaw) > 0.2 - if self.CP.pcmCruise: # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state. # On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but From d9a8846d58af2a3a9a598fcf9fb1acf89e4b54f6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 15 Dec 2023 17:16:19 -0800 Subject: [PATCH 274/724] test_models: use gen_empty_fingerprint (#30752) use gen_empty_fingerprint --- selfdrive/car/tests/test_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index aaaa6b9d79..2103b6ccce 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -13,6 +13,7 @@ from cereal import log, car from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.car import gen_empty_fingerprint from openpilot.selfdrive.car.fingerprints import all_known_cars from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH @@ -104,7 +105,7 @@ class TestCarModelBase(unittest.TestCase): can_msgs = [] cls.elm_frame = None cls.car_safety_mode_frame = None - fingerprint = defaultdict(dict) + fingerprint = gen_empty_fingerprint() experimental_long = False for msg in lr: if msg.which() == "can": From cb53e6579fb6763cf7feeb8e72da1aa3d9ddaa31 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 15 Dec 2023 17:44:54 -0800 Subject: [PATCH 275/724] Bump submodules (#30755) bump submodules Co-authored-by: jnewb1 --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index bb75afc84e..7e2b312fa8 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit bb75afc84e4d33af1554ada81851547db648e33e +Subproject commit 7e2b312fa86e6cdfeba496f261407cb47cb005d1 From f9ee7bff006c792738f34e2923ace009e7f34709 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 16 Dec 2023 11:32:25 +0800 Subject: [PATCH 276/724] ui: remove unused `roadCameraState` from submaster (#30757) --- selfdrive/ui/ui.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 906de7d1ee..9afd22f13a 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -246,7 +246,7 @@ void UIState::updateStatus() { UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ - "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", + "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "uiPlan", }); From ea7e701052f8da1fb562746c62bdb5a64410ea76 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 16 Dec 2023 11:48:26 +0800 Subject: [PATCH 277/724] camerad/CameraBuf: remove deprecated rgb_stride (#30758) --- system/camerad/cameras/camera_common.cc | 4 ++-- system/camerad/cameras/camera_common.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 645a6f305c..8943fb4cba 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -32,10 +32,10 @@ public: snprintf(args, sizeof(args), "-cl-fast-relaxed-math -cl-denorms-are-zero " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " - "-DRGB_WIDTH=%d -DRGB_HEIGHT=%d -DRGB_STRIDE=%d -DYUV_STRIDE=%d -DUV_OFFSET=%d " + "-DRGB_WIDTH=%d -DRGB_HEIGHT=%d -DYUV_STRIDE=%d -DUV_OFFSET=%d " "-DIS_OX=%d -DCAM_NUM=%d%s", ci->frame_width, ci->frame_height, ci->frame_stride, ci->frame_offset, - b->rgb_width, b->rgb_height, b->rgb_stride, buf_width, uv_offset, + b->rgb_width, b->rgb_height, buf_width, uv_offset, ci->image_sensor == cereal::FrameData::ImageSensor::OX03C10, s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); const char *cl_file = "cameras/real_debayer.cl"; cl_program prg_debayer = cl_program_from_file(context, device_id, cl_file, args); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 9ebe018408..d56f20894a 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -67,7 +67,7 @@ public: VisionBuf *cur_camera_buf; std::unique_ptr camera_bufs; std::unique_ptr camera_bufs_metadata; - int rgb_width, rgb_height, rgb_stride; + int rgb_width, rgb_height; CameraBuf() = default; ~CameraBuf(); From dfa2e9146f1997f3879e4a2bb4be00c4e6068228 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 18:52:15 -0800 Subject: [PATCH 278/724] cleanup python dependencies (#30764) * less * try to remove that * little more * update * ugh still need this --- poetry.lock | 66 ++++++++++++++++++++------------------------------ pyproject.toml | 23 ++++++++---------- 2 files changed, 36 insertions(+), 53 deletions(-) diff --git a/poetry.lock b/poetry.lock index 44e07568b3..1e5740c78d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2067,6 +2067,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2647,14 +2657,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "opencv-python-headless" @@ -2673,14 +2676,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "packaging" @@ -3250,17 +3246,6 @@ files = [ {file = "pycurl-7.45.2.tar.gz", hash = "sha256:5730590be0271364a5bddd9e245c9cc0fb710c4cbacbdd95264a3122d23224ca"}, ] -[[package]] -name = "pydub" -version = "0.25.1" -description = "Manipulate audio with an simple and easy high level interface" -optional = false -python-versions = "*" -files = [ - {file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"}, - {file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"}, -] - [[package]] name = "pyee" version = "11.1.0" @@ -3299,6 +3284,8 @@ files = [ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, @@ -3439,17 +3426,6 @@ files = [ [package.dependencies] cffi = ">=1.0.0" -[[package]] -name = "pympler" -version = "1.0.1" -description = "A development tool to measure, monitor and analyze the memory behavior of Python objects." -optional = false -python-versions = ">=3.6" -files = [ - {file = "Pympler-1.0.1-py3-none-any.whl", hash = "sha256:d260dda9ae781e1eab6ea15bacb84015849833ba5555f141d2d9b7b7473b307d"}, - {file = "Pympler-1.0.1.tar.gz", hash = "sha256:993f1a3599ca3f4fcd7160c7545ad06310c9e12f70174ae7ae8d4e25f6c5d3fa"}, -] - [[package]] name = "pyopencl" version = "2023.1.4" @@ -3859,6 +3835,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3866,8 +3843,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3884,6 +3868,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3891,6 +3876,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -4989,4 +4975,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "08910461234279f3339c1692bd0cc2732f6e17f61ca9e3ffa53d461526e3dce4" +content-hash = "765614c2f5d309dc09e84b87c77b92df4e0cb1258b7317cc64c6a3f1559a82bc" diff --git a/pyproject.toml b/pyproject.toml index 07cf9a2233..fc336da676 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,18 +75,14 @@ documentation = "https://docs.comma.ai" [tool.poetry.dependencies] python = "~3.11" -atomicwrites = "*" aiohttp = "*" aiortc = "*" casadi = "==3.6.3" cffi = "*" -control = "*" crcmod = "*" -cryptography = "*" Cython = "*" future-fstrings = "*" # for acados hexdump = "*" -Jinja2 = "*" json-rpc = "*" libusb1 = "*" numpy = "*" @@ -97,8 +93,6 @@ psutil = "*" pyaudio = "*" pycapnp = "*" pycryptodome = "*" -pycurl = "*" -pydub = "*" PyJWT = "*" pyopencl = "*" pyserial = "*" @@ -107,30 +101,35 @@ pyzmq = "*" requests = "*" scons = "*" sentry-sdk = "==1.28.1" # needs to be updated with AGNOS -setproctitle = "*" smbus2 = "*" sounddevice = "*" spidev = { version = "*", platform = "linux" } sympy = "*" -timezonefinder = "*" -tqdm = "*" websocket_client = "*" polyline = "*" sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"} +# these should be removed +atomicwrites = "*" +markdown-it-py = "*" +timezonefinder = "*" +pycurl = "*" +setproctitle = "*" + [tool.poetry.group.dev.dependencies] av = "*" azure-identity = "*" azure-storage-blob = "*" breathe = "*" +control = "*" coverage = "*" dictdiffer = "*" ft4222 = "*" hypothesis = "~6.47" inputs = "*" +Jinja2 = "*" lru-dict = "*" -markdown-it-py = "*" matplotlib = "*" metadrive-simulator = { git = "https://github.com/metadriverse/metadrive.git", rev ="72e842cd1d025bf676e4af8797a01e4aa282109f", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies mpld3 = "*" @@ -138,12 +137,10 @@ mypy = "*" myst-parser = "*" natsort = "*" opencv-python-headless = "*" -pandas = "*" parameterized = "^0.8" pprofile = "*" pre-commit = "*" pygame = "*" -pympler = "*" pyprof2calltree = "*" pytest = "*" pytest-cov = "*" @@ -154,7 +151,6 @@ pytest-timeout = "*" pytest-timeouts = "*" pytest-randomly = "*" ruff = "*" -scipy = "*" sphinx = "*" sphinx-rtd-theme = "*" sphinx-sitemap = "*" @@ -165,6 +161,7 @@ types-pycurl = "*" types-PyYAML = "*" types-requests = "*" types-tabulate = "*" +tqdm = "*" # this is only pinned since 5.15.11 is broken pyqt5 = { version = "==5.15.2", markers = "platform_machine == 'x86_64'" } # no aarch64 wheels for macOS/linux From a7828eba3ce73b523dc1bbd2c897c760329e7f7a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 19:16:34 -0800 Subject: [PATCH 279/724] remove sconscontrib package (#30767) * remove sconscontrib package * lock --- poetry.lock | 43 +------------------------------------------ pyproject.toml | 1 - 2 files changed, 1 insertion(+), 43 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1e5740c78d..abdc6ea832 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2870,26 +2870,6 @@ sql-other = ["SQLAlchemy (>=1.4.36)"] test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.8.0)"] -[[package]] -name = "panflute" -version = "2.3.0" -description = "Pythonic Pandoc filters" -optional = false -python-versions = ">=3.6" -files = [ - {file = "panflute-2.3.0-py3-none-any.whl", hash = "sha256:02673bcbdb521a805f08a2ca0ce864de86ad409ad406a01b3700fcf2aca81635"}, - {file = "panflute-2.3.0.tar.gz", hash = "sha256:cefd9dfc48ccd9732a53db57610701d22806da397a8a97e5cc8dc070b55865ca"}, -] - -[package.dependencies] -click = ">=6,<9" -pyyaml = ">=3,<7" - -[package.extras] -dev = ["configparser", "coverage", "flake8", "pandocfilters", "pytest", "pytest-cov", "requests"] -extras = ["yamlloader (>=1,<2)"] -pypi = ["Pygments", "docutils", "twine", "wheel"] - [[package]] name = "parameterized" version = "0.8.1" @@ -4090,27 +4070,6 @@ files = [ [package.dependencies] setuptools = "*" -[[package]] -name = "sconscontrib" -version = "1.0" -description = "Contributed builders and other useful logic for the SCons build system.," -optional = false -python-versions = "<4,>=3.6" -files = [] -develop = false - -[package.dependencies] -docutils = "*" -panflute = "*" -SCons = ">=4" -sphinx = "*" - -[package.source] -type = "git" -url = "https://github.com/SCons/scons-contrib.git" -reference = "HEAD" -resolved_reference = "f3b0100d3a628e4d18f496815903660a99489bae" - [[package]] name = "seaborn" version = "0.13.0" @@ -4975,4 +4934,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "765614c2f5d309dc09e84b87c77b92df4e0cb1258b7317cc64c6a3f1559a82bc" +content-hash = "cee6c08a80e55f31e99f6cadaf3635fc63d2f2f2df1175b19f186a2c7f576386" diff --git a/pyproject.toml b/pyproject.toml index fc336da676..64b5069b5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,7 +107,6 @@ spidev = { version = "*", platform = "linux" } sympy = "*" websocket_client = "*" polyline = "*" -sconscontrib = {git = "https://github.com/SCons/scons-contrib.git"} # these should be removed atomicwrites = "*" From 6239cf67803690e541e32898443e7363c711b6ac Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Sat, 16 Dec 2023 19:29:45 -0800 Subject: [PATCH 280/724] CI: Close PRs not against master (#30768) * check branch * Update labeler.yaml * Update labeler.yaml * Update labeler.yaml * rename --------- Co-authored-by: Adeeb Shihadeh --- .github/workflows/auto_pr_review.yaml | 34 +++++++++++++++++++++++++++ .github/workflows/labeler.yaml | 18 -------------- 2 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/auto_pr_review.yaml delete mode 100644 .github/workflows/labeler.yaml diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml new file mode 100644 index 0000000000..fd95592e91 --- /dev/null +++ b/.github/workflows/auto_pr_review.yaml @@ -0,0 +1,34 @@ +name: "PR review" +on: + pull_request_target: + +jobs: + labeler: + name: apply labels + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: false + - uses: actions/labeler@v5.0.0-alpha.1 + with: + dot: true + configuration-path: .github/labeler.yaml + + pr_branch_check: + name: check branch + runs-on: ubuntu-latest + if: github.repository == 'commaai/openpilot' + steps: + - uses: Vankka/pr-target-branch-action@69ab6dd5c221de3548b3b6c4d102c1f4913d3baa + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + target: /^(?!master$).*/ + exclude: /commaai:.*/ + change-to: ${{ github.base_ref }} + already-exists-action: close_this + already-exists-comment: "Your PR should be made against the `master` branch" diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml deleted file mode 100644 index 95d1af8bc6..0000000000 --- a/.github/workflows/labeler.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: "Pull Request Labeler" -on: - pull_request_target: - -jobs: - labeler: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: false - - uses: actions/labeler@v5.0.0-alpha.1 - with: - dot: true - configuration-path: .github/labeler.yaml \ No newline at end of file From bc54ed794b2cd9dcb9522cdeb241ad78ac30d998 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 20:48:57 -0800 Subject: [PATCH 281/724] remove PyYAML package (#30770) * remove PyYAML package * remove those * add comments back * fix lock * fix those --- poetry.lock | 13 +--- pyproject.toml | 2 - release/files_common | 4 +- selfdrive/car/interfaces.py | 20 +++--- selfdrive/car/torque_data/override.toml | 76 ++++++++++++++++++++ selfdrive/car/torque_data/override.yaml | 76 -------------------- selfdrive/car/torque_data/params.toml | 88 +++++++++++++++++++++++ selfdrive/car/torque_data/params.yaml | 88 ----------------------- selfdrive/car/torque_data/substitute.toml | 84 ++++++++++++++++++++++ selfdrive/car/torque_data/substitute.yaml | 83 --------------------- 10 files changed, 260 insertions(+), 274 deletions(-) create mode 100644 selfdrive/car/torque_data/override.toml delete mode 100644 selfdrive/car/torque_data/override.yaml create mode 100644 selfdrive/car/torque_data/params.toml delete mode 100644 selfdrive/car/torque_data/params.yaml create mode 100644 selfdrive/car/torque_data/substitute.toml delete mode 100644 selfdrive/car/torque_data/substitute.yaml diff --git a/poetry.lock b/poetry.lock index abdc6ea832..c1f9bd890f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4687,17 +4687,6 @@ files = [ {file = "types_pycurl-7.45.2.5-py3-none-any.whl", hash = "sha256:e433f13a266245be770018eabc6a50139ea26150f75df5b0ee698d291575582b"}, ] -[[package]] -name = "types-pyyaml" -version = "6.0.12.12" -description = "Typing stubs for PyYAML" -optional = false -python-versions = "*" -files = [ - {file = "types-PyYAML-6.0.12.12.tar.gz", hash = "sha256:334373d392fde0fdf95af5c3f1661885fa10c52167b14593eb856289e1855062"}, - {file = "types_PyYAML-6.0.12.12-py3-none-any.whl", hash = "sha256:c05bc6c158facb0676674b7f11fe3960db4f389718e19e62bd2b84d6205cfd24"}, -] - [[package]] name = "types-requests" version = "2.31.0.10" @@ -4934,4 +4923,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "cee6c08a80e55f31e99f6cadaf3635fc63d2f2f2df1175b19f186a2c7f576386" +content-hash = "68be4dd5356e38c1e5a004a6e40b4fef963aacd1cad902b5fba70c51c442d981" diff --git a/pyproject.toml b/pyproject.toml index 64b5069b5b..336c440f6b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,6 @@ pycryptodome = "*" PyJWT = "*" pyopencl = "*" pyserial = "*" -PyYAML = "*" pyzmq = "*" requests = "*" scons = "*" @@ -157,7 +156,6 @@ tabulate = "*" tenacity = "*" types-atomicwrites = "*" types-pycurl = "*" -types-PyYAML = "*" types-requests = "*" types-tabulate = "*" tqdm = "*" diff --git a/release/files_common b/release/files_common index 10bfe4e1f1..2ef68651c8 100644 --- a/release/files_common +++ b/release/files_common @@ -98,9 +98,7 @@ selfdrive/car/ecu_addrs.py selfdrive/car/isotp_parallel_query.py selfdrive/car/tests/__init__.py selfdrive/car/tests/test_car_interfaces.py -selfdrive/car/torque_data/params.yaml -selfdrive/car/torque_data/substitute.yaml -selfdrive/car/torque_data/override.yaml +selfdrive/car/torque_data/*.toml selfdrive/car/body/*.py selfdrive/car/chrysler/*.py diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 1b68b1dbcf..94b3bdfc84 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -1,7 +1,7 @@ -import yaml import os import time import numpy as np +import tomllib from abc import abstractmethod, ABC from typing import Any, Dict, Optional, Tuple, List, Callable @@ -26,21 +26,21 @@ ACCEL_MAX = 2.0 ACCEL_MIN = -3.5 FRICTION_THRESHOLD = 0.3 -TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml') -TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml') -TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.yaml') +TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.toml') +TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.toml') +TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.toml') def get_torque_params(candidate): - with open(TORQUE_SUBSTITUTE_PATH) as f: - sub = yaml.load(f, Loader=yaml.CSafeLoader) + with open(TORQUE_SUBSTITUTE_PATH, 'rb') as f: + sub = tomllib.load(f) if candidate in sub: candidate = sub[candidate] - with open(TORQUE_PARAMS_PATH) as f: - params = yaml.load(f, Loader=yaml.CSafeLoader) - with open(TORQUE_OVERRIDE_PATH) as f: - override = yaml.load(f, Loader=yaml.CSafeLoader) + with open(TORQUE_PARAMS_PATH, 'rb') as f: + params = tomllib.load(f) + with open(TORQUE_OVERRIDE_PATH, 'rb') as f: + override = tomllib.load(f) # Ensure no overlap if sum([candidate in x for x in [sub, params, override]]) > 1: diff --git a/selfdrive/car/torque_data/override.toml b/selfdrive/car/torque_data/override.toml new file mode 100644 index 0000000000..5cdebc1709 --- /dev/null +++ b/selfdrive/car/torque_data/override.toml @@ -0,0 +1,76 @@ +legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] +### angle control +# Nissan appears to have torque +"NISSAN X-TRAIL 2017" = [nan, 1.5, nan] +"NISSAN ALTIMA 2020" = [nan, 1.5, nan] +"NISSAN LEAF 2018 Instrument Cluster" = [nan, 1.5, nan] +"NISSAN LEAF 2018" = [nan, 1.5, nan] +"NISSAN ROGUE 2019" = [nan, 1.5, nan] + +# New subarus angle based controllers +"SUBARU FORESTER 2022" = [nan, 3.0, nan] +"SUBARU OUTBACK 7TH GEN" = [nan, 3.0, nan] +"SUBARU ASCENT 2023" = [nan, 3.0, nan] + +# Toyota LTA also has torque +"TOYOTA RAV4 2023" = [nan, 3.0, nan] + +# Tesla has high torque +"TESLA AP1 MODEL S" = [nan, 2.5, nan] +"TESLA AP2 MODEL S" = [nan, 2.5, nan] + +# Guess +"FORD BRONCO SPORT 1ST GEN" = [nan, 1.5, nan] +"FORD ESCAPE 4TH GEN" = [nan, 1.5, nan] +"FORD EXPLORER 6TH GEN" = [nan, 1.5, nan] +"FORD F-150 14TH GEN" = [nan, 1.5, nan] +"FORD FOCUS 4TH GEN" = [nan, 1.5, nan] +"FORD MAVERICK 1ST GEN" = [nan, 1.5, nan] +"FORD F-150 LIGHTNING 1ST GEN" = [nan, 1.5, nan] +"FORD MUSTANG MACH-E 1ST GEN" = [nan, 1.5, nan] +### + +# No steering wheel +"COMMA BODY" = [nan, 1000, nan] + +# Totally new cars +"RAM 1500 5TH GEN" = [2.0, 2.0, 0.05] +"RAM HD 5TH GEN" = [1.4, 1.4, 0.05] +"SUBARU OUTBACK 6TH GEN" = [2.0, 2.0, 0.2] +"CADILLAC ESCALADE 2017" = [1.899999976158142, 1.842270016670227, 0.1120000034570694] +"CADILLAC ESCALADE ESV 2019" = [1.15, 1.3, 0.2] +"CHEVROLET BOLT EUV 2022" = [2.0, 2.0, 0.05] +"CHEVROLET SILVERADO 1500 2020" = [1.9, 1.9, 0.112] +"CHEVROLET TRAILBLAZER 2021" = [1.33, 1.9, 0.16] +"CHEVROLET EQUINOX 2019" = [2.0, 2.0, 0.05] +"VOLKSWAGEN PASSAT NMS" = [2.5, 2.5, 0.1] +"VOLKSWAGEN SHARAN 2ND GEN" = [2.5, 2.5, 0.1] +"HYUNDAI SANTA CRUZ 1ST GEN" = [2.7, 2.7, 0.1] +"KIA SPORTAGE 5TH GEN" = [2.7, 2.7, 0.1] +"KIA SPORTAGE HYBRID 5TH GEN" = [2.5, 2.5, 0.1] +"GENESIS GV70 1ST GEN" = [2.42, 2.42, 0.1] +"KIA SORENTO PLUG-IN HYBRID 4TH GEN" = [2.5, 2.5, 0.1] +"GENESIS GV60 ELECTRIC 1ST GEN" = [2.5, 2.5, 0.1] +"KIA SORENTO 4TH GEN" = [2.5, 2.5, 0.1] +"KIA NIRO HYBRID 2ND GEN" = [2.42, 2.5, 0.12] +"KIA NIRO EV 2ND GEN" = [2.05, 2.5, 0.14] +"GENESIS GV80 2023" = [2.5, 2.5, 0.1] +"KIA CARNIVAL 4TH GEN" = [1.75, 1.75, 0.15] +"GMC ACADIA DENALI 2018" = [1.6, 1.6, 0.2] +"LEXUS IS 2023" = [2.0, 2.0, 0.1] +"KIA SORENTO HYBRID 4TH GEN" = [2.5, 2.5, 0.1] +"HYUNDAI KONA ELECTRIC 2ND GEN" = [2.5, 2.5, 0.1] +"HYUNDAI IONIQ 6 2023" = [2.5, 2.5, 0.1] +"HYUNDAI AZERA 6TH GEN" = [1.8, 1.8, 0.1] +"HYUNDAI AZERA HYBRID 6TH GEN" = [1.8, 1.8, 0.1] +"KIA K8 HYBRID 1ST GEN" = [2.5, 2.5, 0.1] +"HYUNDAI CUSTIN 1ST GEN" = [2.5, 2.5, 0.1] +"LEXUS GS F 2016" = [2.5, 2.5, 0.08] +"HYUNDAI STARIA 4TH GEN" = [1.8, 2.0, 0.15] + +# Dashcam or fallback configured as ideal car +"mock" = [10.0, 10, 0.0] + +# Manually checked +"HONDA CIVIC 2022" = [2.5, 1.2, 0.15] +"HONDA HR-V 2023" = [2.5, 1.2, 0.2] diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml deleted file mode 100644 index 60e54af210..0000000000 --- a/selfdrive/car/torque_data/override.yaml +++ /dev/null @@ -1,76 +0,0 @@ -legend: [LAT_ACCEL_FACTOR, MAX_LAT_ACCEL_MEASURED, FRICTION] -### angle control -# Nissan appears to have torque -NISSAN X-TRAIL 2017: [.nan, 1.5, .nan] -NISSAN ALTIMA 2020: [.nan, 1.5, .nan] -NISSAN LEAF 2018 Instrument Cluster: [.nan, 1.5, .nan] -NISSAN LEAF 2018: [.nan, 1.5, .nan] -NISSAN ROGUE 2019: [.nan, 1.5, .nan] - -# New subarus angle based controllers -SUBARU FORESTER 2022: [.nan, 3.0, .nan] -SUBARU OUTBACK 7TH GEN: [.nan, 3.0, .nan] -SUBARU ASCENT 2023: [.nan, 3.0, .nan] - -# Toyota LTA also has torque -TOYOTA RAV4 2023: [.nan, 3.0, .nan] - -# Tesla has high torque -TESLA AP1 MODEL S: [.nan, 2.5, .nan] -TESLA AP2 MODEL S: [.nan, 2.5, .nan] - -# Guess -FORD BRONCO SPORT 1ST GEN: [.nan, 1.5, .nan] -FORD ESCAPE 4TH GEN: [.nan, 1.5, .nan] -FORD EXPLORER 6TH GEN: [.nan, 1.5, .nan] -FORD F-150 14TH GEN: [.nan, 1.5, .nan] -FORD FOCUS 4TH GEN: [.nan, 1.5, .nan] -FORD MAVERICK 1ST GEN: [.nan, 1.5, .nan] -FORD F-150 LIGHTNING 1ST GEN: [.nan, 1.5, .nan] -FORD MUSTANG MACH-E 1ST GEN: [.nan, 1.5, .nan] -### - -# No steering wheel -COMMA BODY: [.nan, 1000, .nan] - -# Totally new cars -RAM 1500 5TH GEN: [2.0, 2.0, 0.05] -RAM HD 5TH GEN: [1.4, 1.4, 0.05] -SUBARU OUTBACK 6TH GEN: [2.0, 2.0, 0.2] -CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694] -CADILLAC ESCALADE ESV 2019: [1.15, 1.3, 0.2] -CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] -CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] -CHEVROLET TRAILBLAZER 2021: [1.33, 1.9, 0.16] -CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] -VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] -VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] -HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.1] -KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.1] -KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1] -GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1] -KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1] -GENESIS GV60 ELECTRIC 1ST GEN: [2.5, 2.5, 0.1] -KIA SORENTO 4TH GEN: [2.5, 2.5, 0.1] -KIA NIRO HYBRID 2ND GEN: [2.42, 2.5, 0.12] -KIA NIRO EV 2ND GEN: [2.05, 2.5, 0.14] -GENESIS GV80 2023: [2.5, 2.5, 0.1] -KIA CARNIVAL 4TH GEN: [1.75, 1.75, 0.15] -GMC ACADIA DENALI 2018: [1.6, 1.6, 0.2] -LEXUS IS 2023: [2.0, 2.0, 0.1] -KIA SORENTO HYBRID 4TH GEN: [2.5, 2.5, 0.1] -HYUNDAI KONA ELECTRIC 2ND GEN: [2.5, 2.5, 0.1] -HYUNDAI IONIQ 6 2023: [2.5, 2.5, 0.1] -HYUNDAI AZERA 6TH GEN: [1.8, 1.8, 0.1] -HYUNDAI AZERA HYBRID 6TH GEN: [1.8, 1.8, 0.1] -KIA K8 HYBRID 1ST GEN: [2.5, 2.5, 0.1] -HYUNDAI CUSTIN 1ST GEN: [2.5, 2.5, 0.1] -LEXUS GS F 2016: [2.5, 2.5, 0.08] -HYUNDAI STARIA 4TH GEN: [1.8, 2.0, 0.15] - -# Dashcam or fallback configured as ideal car -mock: [10.0, 10, 0.0] - -# Manually checked -HONDA CIVIC 2022: [2.5, 1.2, 0.15] -HONDA HR-V 2023: [2.5, 1.2, 0.2] diff --git a/selfdrive/car/torque_data/params.toml b/selfdrive/car/torque_data/params.toml new file mode 100644 index 0000000000..2e04e1c6a0 --- /dev/null +++ b/selfdrive/car/torque_data/params.toml @@ -0,0 +1,88 @@ +legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] +"ACURA ILX 2016" = [1.524988973896102, 0.519011053086259, 0.34236219253028] +"ACURA RDX 2018" = [0.9987728568686902, 0.5323765166196301, 0.303218805715844] +"ACURA RDX 2020" = [1.4314459806646749, 0.33874701282109954, 0.18048847083897598] +"AUDI A3 3RD GEN" = [1.5122414863077502, 1.7443517531719404, 0.15194151892450905] +"AUDI Q3 2ND GEN" = [1.4439223359448605, 1.2254955789112076, 0.1413798895978097] +"CHEVROLET VOLT PREMIER 2017" = [1.5961527626411784, 1.8422651988094612, 0.1572393918005158] +"CHRYSLER PACIFICA 2018" = [2.07140, 1.3366521181047952, 0.13776367250652022] +"CHRYSLER PACIFICA 2020" = [1.86206, 1.509076559398423, 0.14328246159386085] +"CHRYSLER PACIFICA HYBRID 2017" = [1.79422, 1.06831764583744, 0.116237] +"CHRYSLER PACIFICA HYBRID 2018" = [2.08887, 1.2943025830995154, 0.114818] +"CHRYSLER PACIFICA HYBRID 2019" = [1.90120, 1.1958788168371808, 0.131520] +"GENESIS G70 2018" = [3.8520195946707947, 2.354697063349854, 0.06830285485626221] +"HONDA ACCORD 2018" = [1.7135052593468778, 0.3461280068322071, 0.21579936052863807] +"HONDA ACCORD HYBRID 2018" = [1.6651615004829625, 0.30322180951193245, 0.2083000440586149] +"HONDA CIVIC (BOSCH) 2019" = [1.691708637466905, 0.40132900729454185, 0.25460295304024094] +"HONDA CIVIC 2016" = [1.6528895627785531, 0.4018518740819229, 0.25458812851328544] +"HONDA CR-V 2016" = [0.7667141440182675, 0.5927571534745969, 0.40909087636157127] +"HONDA CR-V 2017" = [2.01323205142022, 0.2700612209345081, 0.2238412881331528] +"HONDA CR-V HYBRID 2019" = [2.072034634644233, 0.7152085160516978, 0.20237105008376083] +"HONDA FIT 2018" = [1.5719981427109775, 0.5712761407108976, 0.110773383324281] +"HONDA HRV 2019" = [2.0661212805710205, 0.7521343418694775, 0.17760375789242094] +"HONDA INSIGHT 2019" = [1.5201671214069354, 0.5660229120683284, 0.25808042580281876] +"HONDA ODYSSEY 2018" = [1.8774809275211801, 0.8394431662987996, 0.2096978613792822] +"HONDA PILOT 2017" = [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] +"HONDA RIDGELINE 2017" = [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] +"HYUNDAI ELANTRA 2021" = [3.169, 2.1259108157250735, 0.0819] +"HYUNDAI GENESIS 2015-2016" = [2.7807965280270794, 2.325, 0.0984484465421171] +"HYUNDAI IONIQ 5 2022" = [3.172929, 2.713050, 0.096019] +"HYUNDAI IONIQ ELECTRIC LIMITED 2019" = [1.7662975472852054, 1.613755614526594, 0.17087579756306276] +"HYUNDAI IONIQ PHEV 2020" = [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] +"HYUNDAI IONIQ PLUG-IN HYBRID 2019" = [2.970807902012267, 1.6312321830002083, 0.1088964990357482] +"HYUNDAI KONA ELECTRIC 2019" = [3.078814714619148, 2.307336938253934, 0.12359762054065548] +"HYUNDAI PALISADE 2020" = [2.544642494803999, 1.8721703683337008, 0.1301424599248651] +"HYUNDAI SANTA FE 2019" = [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] +"HYUNDAI SANTA FE HYBRID 2022" = [3.501877602644835, 2.729064118456137, 0.10384068104538963] +"HYUNDAI SANTA FE PlUG-IN HYBRID 2022" = [1.6953050513611045, 1.5837614296206861, 0.12672855941458458] +"HYUNDAI SONATA 2019" = [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] +"HYUNDAI SONATA 2020" = [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] +"HYUNDAI SONATA HYBRID 2021" = [2.8990264092395734, 2.061410192222139, 0.0899805488717382] +"HYUNDAI TUCSON HYBRID 4TH GEN" = [2.960174, 2.860284, 0.108745] +"JEEP GRAND CHEROKEE 2019" = [2.30972, 1.289689569171081, 0.117048] +"JEEP GRAND CHEROKEE V6 2018" = [2.27116, 1.4057367824262523, 0.11725947414922003] +"KIA EV6 2022" = [3.2, 2.093457, 0.05] +"KIA K5 2021" = [2.405339728085138, 1.460032270828705, 0.11650989850813716] +"KIA NIRO EV 2020" = [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] +"KIA SORENTO GT LINE 2018" = [2.464854685101844, 1.5335274218367956, 0.12056170567599558] +"KIA STINGER GT2 2018" = [2.7499043387418967, 1.849652021986449, 0.12048334239559202] +"LEXUS ES 2019" = [2.0357564999999997, 1.999082295195227, 0.101533] +"LEXUS NX 2018" = [2.3525924753753613, 1.9731412277641067, 0.15168101064205927] +"LEXUS NX 2020" = [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] +"LEXUS RX 2016" = [1.5876816543130423, 1.0427699298523752, 0.21334066732397142] +"LEXUS RX 2020" = [1.5375561442049257, 1.343166476215164, 0.1931062001527557] +"LEXUS RX HYBRID 2017" = [1.6984261557042386, 1.3211501880159107, 0.1820354534928893] +"MAZDA CX-9 2021" = [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] +"SKODA SUPERB 3RD GEN" = [1.166437404652981, 1.1686163012668165, 0.12194533036948708] +"SUBARU FORESTER 2019" = [3.6617001649776793, 2.342197172531713, 0.11075960785398745] +"SUBARU IMPREZA LIMITED 2019" = [1.0670704910352047, 0.8234374840709592, 0.20986563268614938] +"SUBARU IMPREZA SPORT 2020" = [2.6068223389108303, 2.134872342760203, 0.15261513193561627] +"TOYOTA AVALON 2016" = [2.5185770183845646, 1.7153346784214922, 0.10603968787111022] +"TOYOTA AVALON 2019" = [1.7036141952825095, 1.239619084240008, 0.08459830394899492] +"TOYOTA AVALON 2022" = [2.3154403649717357, 2.7777922854327124, 0.11453999639164605] +"TOYOTA C-HR 2018" = [1.5591084333664578, 1.271271459066948, 0.20259087058453193] +"TOYOTA C-HR 2021" = [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] +"TOYOTA CAMRY 2018" = [2.0568162685952505, 1.7576185169559122, 0.108878753] +"TOYOTA CAMRY 2021" = [2.3548324999999997, 2.368900128946771, 0.118436] +"TOYOTA COROLLA 2017" = [3.117154369115421, 1.8438132575043773, 0.12289685869250652] +"TOYOTA COROLLA TSS2 2019" = [1.991132339206426, 1.868866242720403, 0.19570063298031432] +"TOYOTA HIGHLANDER 2017" = [1.8696367437248915, 1.626293990451463, 0.17485372210240796] +"TOYOTA HIGHLANDER 2020" = [1.9617570834136164, 1.8611643317268927, 0.14519673256119725] +"TOYOTA HIGHLANDER HYBRID 2018" = [1.752033, 1.6433903296845025, 0.144600] +"TOYOTA MIRAI 2021" = [2.506899832157829, 1.7417213930750164, 0.20182618449440565] +"TOYOTA PRIUS 2017" = [1.60, 1.5023147650693636, 0.151515] +"TOYOTA PRIUS TSS2 2021" = [1.972600, 1.9104337425537743, 0.170968] +"TOYOTA RAV4 2017" = [2.085695074355425, 2.2142832316984733, 0.13339165270103975] +"TOYOTA RAV4 2019" = [2.279239424615458, 2.087101966779332, 0.13682208413446817] +"TOYOTA RAV4 2019 8965" = [2.3080951748210854, 2.1189367835820603, 0.12942102328134028] +"TOYOTA RAV4 2019 x02" = [2.762293266024922, 2.243615865975329, 0.11113568178327986] +"TOYOTA RAV4 HYBRID 2017" = [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] +"TOYOTA RAV4 2022" = [2.241883248393209, 1.9304407208090029, 0.112174] +"TOYOTA RAV4 2022 x02" = [3.044930631831037, 2.3979189796380918, 0.14023209146703736] +"TOYOTA SIENNA 2018" = [1.689726, 1.3208264576110418, 0.140456] +"VOLKSWAGEN ARTEON 1ST GEN" = [1.45136518053819, 1.3639364049316804, 0.23806361745695032] +"VOLKSWAGEN ATLAS 1ST GEN" = [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] +"VOLKSWAGEN GOLF 7TH GEN" = [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] +"VOLKSWAGEN JETTA 7TH GEN" = [1.2271623034089392, 1.216955117387, 0.19437384688370712] +"VOLKSWAGEN PASSAT 8TH GEN" = [1.3432120736752917, 1.7087275587362314, 0.19444383787326647] +"VOLKSWAGEN TIGUAN 2ND GEN" = [0.9711965500094828, 1.0001565939459098, 0.1465626137072916] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml deleted file mode 100644 index 7bd83025f1..0000000000 --- a/selfdrive/car/torque_data/params.yaml +++ /dev/null @@ -1,88 +0,0 @@ -ACURA ILX 2016: [1.524988973896102, 0.519011053086259, 0.34236219253028] -ACURA RDX 2018: [0.9987728568686902, 0.5323765166196301, 0.303218805715844] -ACURA RDX 2020: [1.4314459806646749, 0.33874701282109954, 0.18048847083897598] -AUDI A3 3RD GEN: [1.5122414863077502, 1.7443517531719404, 0.15194151892450905] -AUDI Q3 2ND GEN: [1.4439223359448605, 1.2254955789112076, 0.1413798895978097] -CHEVROLET VOLT PREMIER 2017: [1.5961527626411784, 1.8422651988094612, 0.1572393918005158] -CHRYSLER PACIFICA 2018: [2.07140, 1.3366521181047952, 0.13776367250652022] -CHRYSLER PACIFICA 2020: [1.86206, 1.509076559398423, 0.14328246159386085] -CHRYSLER PACIFICA HYBRID 2017: [1.79422, 1.06831764583744, 0.116237] -CHRYSLER PACIFICA HYBRID 2018: [2.08887, 1.2943025830995154, 0.114818] -CHRYSLER PACIFICA HYBRID 2019: [1.90120, 1.1958788168371808, 0.131520] -GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221] -HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807] -HONDA ACCORD HYBRID 2018: [1.6651615004829625, 0.30322180951193245, 0.2083000440586149] -HONDA CIVIC (BOSCH) 2019: [1.691708637466905, 0.40132900729454185, 0.25460295304024094] -HONDA CIVIC 2016: [1.6528895627785531, 0.4018518740819229, 0.25458812851328544] -HONDA CR-V 2016: [0.7667141440182675, 0.5927571534745969, 0.40909087636157127] -HONDA CR-V 2017: [2.01323205142022, 0.2700612209345081, 0.2238412881331528] -HONDA CR-V HYBRID 2019: [2.072034634644233, 0.7152085160516978, 0.20237105008376083] -HONDA FIT 2018: [1.5719981427109775, 0.5712761407108976, 0.110773383324281] -HONDA HRV 2019: [2.0661212805710205, 0.7521343418694775, 0.17760375789242094] -HONDA INSIGHT 2019: [1.5201671214069354, 0.5660229120683284, 0.25808042580281876] -HONDA ODYSSEY 2018: [1.8774809275211801, 0.8394431662987996, 0.2096978613792822] -HONDA PILOT 2017: [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] -HONDA RIDGELINE 2017: [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] -HYUNDAI ELANTRA 2021: [3.169, 2.1259108157250735, 0.0819] -HYUNDAI GENESIS 2015-2016: [2.7807965280270794, 2.325, 0.0984484465421171] -HYUNDAI IONIQ 5 2022: [3.172929, 2.713050, 0.096019] -HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276] -HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] -HYUNDAI IONIQ PLUG-IN HYBRID 2019: [2.970807902012267, 1.6312321830002083, 0.1088964990357482] -HYUNDAI KONA ELECTRIC 2019: [3.078814714619148, 2.307336938253934, 0.12359762054065548] -HYUNDAI PALISADE 2020: [2.544642494803999, 1.8721703683337008, 0.1301424599248651] -HYUNDAI SANTA FE 2019: [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] -HYUNDAI SANTA FE HYBRID 2022: [3.501877602644835, 2.729064118456137, 0.10384068104538963] -HYUNDAI SANTA FE PlUG-IN HYBRID 2022: [1.6953050513611045, 1.5837614296206861, 0.12672855941458458] -HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] -HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] -HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] -HYUNDAI TUCSON HYBRID 4TH GEN: [2.960174, 2.860284, 0.108745] -JEEP GRAND CHEROKEE 2019: [2.30972, 1.289689569171081, 0.117048] -JEEP GRAND CHEROKEE V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003] -KIA EV6 2022: [3.2, 2.093457, 0.05] -KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716] -KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] -KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558] -KIA STINGER GT2 2018: [2.7499043387418967, 1.849652021986449, 0.12048334239559202] -LEXUS ES 2019: [2.0357564999999997, 1.999082295195227, 0.101533] -LEXUS NX 2018: [2.3525924753753613, 1.9731412277641067, 0.15168101064205927] -LEXUS NX 2020: [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] -LEXUS RX 2016: [1.5876816543130423, 1.0427699298523752, 0.21334066732397142] -LEXUS RX 2020: [1.5375561442049257, 1.343166476215164, 0.1931062001527557] -LEXUS RX HYBRID 2017: [1.6984261557042386, 1.3211501880159107, 0.1820354534928893] -MAZDA CX-9 2021: [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] -SKODA SUPERB 3RD GEN: [1.166437404652981, 1.1686163012668165, 0.12194533036948708] -SUBARU FORESTER 2019: [3.6617001649776793, 2.342197172531713, 0.11075960785398745] -SUBARU IMPREZA LIMITED 2019: [1.0670704910352047, 0.8234374840709592, 0.20986563268614938] -SUBARU IMPREZA SPORT 2020: [2.6068223389108303, 2.134872342760203, 0.15261513193561627] -TOYOTA AVALON 2016: [2.5185770183845646, 1.7153346784214922, 0.10603968787111022] -TOYOTA AVALON 2019: [1.7036141952825095, 1.239619084240008, 0.08459830394899492] -TOYOTA AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605] -TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193] -TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] -TOYOTA CAMRY 2018: [2.0568162685952505, 1.7576185169559122, 0.108878753] -TOYOTA CAMRY 2021: [2.3548324999999997, 2.368900128946771, 0.118436] -TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652] -TOYOTA COROLLA TSS2 2019: [1.991132339206426, 1.868866242720403, 0.19570063298031432] -TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796] -TOYOTA HIGHLANDER 2020: [1.9617570834136164, 1.8611643317268927, 0.14519673256119725] -TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600] -TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] -TOYOTA PRIUS 2017: [1.60, 1.5023147650693636, 0.151515] -TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968] -TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975] -TOYOTA RAV4 2019: [2.279239424615458, 2.087101966779332, 0.13682208413446817] -TOYOTA RAV4 2019 8965: [2.3080951748210854, 2.1189367835820603, 0.12942102328134028] -TOYOTA RAV4 2019 x02: [2.762293266024922, 2.243615865975329, 0.11113568178327986] -TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] -TOYOTA RAV4 2022: [2.241883248393209, 1.9304407208090029, 0.112174] -TOYOTA RAV4 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736] -TOYOTA SIENNA 2018: [1.689726, 1.3208264576110418, 0.140456] -VOLKSWAGEN ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032] -VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] -VOLKSWAGEN GOLF 7TH GEN: [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] -VOLKSWAGEN JETTA 7TH GEN: [1.2271623034089392, 1.216955117387, 0.19437384688370712] -VOLKSWAGEN PASSAT 8TH GEN: [1.3432120736752917, 1.7087275587362314, 0.19444383787326647] -VOLKSWAGEN TIGUAN 2ND GEN: [0.9711965500094828, 1.0001565939459098, 0.1465626137072916] -legend: [LAT_ACCEL_FACTOR, MAX_LAT_ACCEL_MEASURED, FRICTION] diff --git a/selfdrive/car/torque_data/substitute.toml b/selfdrive/car/torque_data/substitute.toml new file mode 100644 index 0000000000..2d3c4dc55c --- /dev/null +++ b/selfdrive/car/torque_data/substitute.toml @@ -0,0 +1,84 @@ +legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] +"MAZDA 3" = "MAZDA CX-9 2021" +"MAZDA 6" = "MAZDA CX-9 2021" +"MAZDA CX-5" = "MAZDA CX-9 2021" +"MAZDA CX-5 2022" = "MAZDA CX-9 2021" +"MAZDA CX-9" = "MAZDA CX-9 2021" + +"TOYOTA ALPHARD 2020" = "TOYOTA SIENNA 2018" +"TOYOTA PRIUS v 2017" = "TOYOTA PRIUS 2017" +"LEXUS IS 2018" = "LEXUS NX 2018" +"LEXUS CT HYBRID 2018" = "LEXUS NX 2018" +"LEXUS ES 2018" = "TOYOTA CAMRY 2018" +"LEXUS ES HYBRID 2018" = "TOYOTA CAMRY 2018" +"LEXUS RC 2020" = "LEXUS NX 2020" + +"KIA OPTIMA 4TH GEN" = "HYUNDAI SONATA 2020" +"KIA OPTIMA 4TH GEN FACELIFT" = "HYUNDAI SONATA 2020" +"KIA OPTIMA HYBRID 2017 & SPORTS 2019" = "HYUNDAI SONATA 2020" +"KIA OPTIMA HYBRID 4TH GEN FACELIFT" = "HYUNDAI SONATA 2020" +"KIA FORTE E 2018 & GT 2021" = "HYUNDAI SONATA 2020" +"KIA CEED INTRO ED 2019" = "HYUNDAI SONATA 2020" +"KIA SELTOS 2021" = "HYUNDAI SONATA 2020" +"KIA NIRO HYBRID 2019" = "KIA NIRO EV 2020" +"KIA NIRO PLUG-IN HYBRID 2022" = "KIA NIRO EV 2020" +"KIA NIRO HYBRID 2021" = "KIA NIRO EV 2020" +"HYUNDAI VELOSTER 2019" = "HYUNDAI SONATA 2019" +"HYUNDAI KONA 2020" = "HYUNDAI KONA ELECTRIC 2019" +"HYUNDAI KONA HYBRID 2020" = "HYUNDAI KONA ELECTRIC 2019" +"HYUNDAI KONA ELECTRIC 2022" = "HYUNDAI KONA ELECTRIC 2019" +"HYUNDAI IONIQ HYBRID 2017-2019" = "HYUNDAI IONIQ PLUG-IN HYBRID 2019" +"HYUNDAI IONIQ HYBRID 2020-2022" = "HYUNDAI IONIQ PLUG-IN HYBRID 2019" +"HYUNDAI IONIQ ELECTRIC 2020" = "HYUNDAI IONIQ PLUG-IN HYBRID 2019" +"HYUNDAI ELANTRA 2017" = "HYUNDAI SONATA 2019" +"HYUNDAI I30 N LINE 2019 & GT 2018 DCT" = "HYUNDAI SONATA 2019" +"HYUNDAI ELANTRA HYBRID 2021" = "HYUNDAI SONATA 2020" +"HYUNDAI TUCSON 2019" = "HYUNDAI SANTA FE 2019" +"HYUNDAI TUCSON 4TH GEN" = "HYUNDAI TUCSON HYBRID 4TH GEN" +"HYUNDAI SANTA FE 2022" = "HYUNDAI SANTA FE HYBRID 2022" +"KIA K5 HYBRID 2020" = "KIA K5 2021" +"KIA STINGER 2022" = "KIA STINGER GT2 2018" +"GENESIS G90 2017" = "GENESIS G70 2018" +"GENESIS G80 2017" = "GENESIS G70 2018" +"GENESIS G70 2020" = "HYUNDAI SONATA 2020" + +"HONDA FREED 2020" = "HONDA ODYSSEY 2018" +"HONDA CR-V EU 2016" = "HONDA CR-V 2016" +"HONDA CIVIC SEDAN 1.6 DIESEL 2019" = "HONDA CIVIC (BOSCH) 2019" +"HONDA E 2020" = "HONDA CIVIC (BOSCH) 2019" +"HONDA ODYSSEY CHN 2019" = "HONDA ODYSSEY 2018" + +"BUICK LACROSSE 2017" = "CHEVROLET VOLT PREMIER 2017" +"BUICK REGAL ESSENCE 2018" = "CHEVROLET VOLT PREMIER 2017" +"CADILLAC ESCALADE ESV 2016" = "CHEVROLET VOLT PREMIER 2017" +"CADILLAC ATS Premium Performance 2018" = "CHEVROLET VOLT PREMIER 2017" +"CHEVROLET MALIBU PREMIER 2017" = "CHEVROLET VOLT PREMIER 2017" +"HOLDEN ASTRA RS-V BK 2017" = "CHEVROLET VOLT PREMIER 2017" + +"SKODA FABIA 4TH GEN" = "VOLKSWAGEN GOLF 7TH GEN" +"SKODA OCTAVIA 3RD GEN" = "SKODA SUPERB 3RD GEN" +"SKODA SCALA 1ST GEN" = "SKODA SUPERB 3RD GEN" +"SKODA KODIAQ 1ST GEN" = "SKODA SUPERB 3RD GEN" +"SKODA KAROQ 1ST GEN" = "SKODA SUPERB 3RD GEN" +"SKODA KAMIQ 1ST GEN" = "SKODA SUPERB 3RD GEN" +"VOLKSWAGEN CRAFTER 2ND GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" +"VOLKSWAGEN T-ROC 1ST GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" +"VOLKSWAGEN T-CROSS 1ST GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" +"VOLKSWAGEN TOURAN 2ND GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" +"VOLKSWAGEN TRANSPORTER T6.1" = "VOLKSWAGEN TIGUAN 2ND GEN" +"AUDI Q2 1ST GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" +"VOLKSWAGEN TAOS 1ST GEN" = "VOLKSWAGEN TIGUAN 2ND GEN" +"VOLKSWAGEN POLO 6TH GEN" = "VOLKSWAGEN GOLF 7TH GEN" +"SEAT LEON 3RD GEN" = "VOLKSWAGEN GOLF 7TH GEN" +"SEAT ATECA 1ST GEN" = "VOLKSWAGEN GOLF 7TH GEN" + +"SUBARU CROSSTREK HYBRID 2020" = "SUBARU IMPREZA SPORT 2020" +"SUBARU FORESTER HYBRID 2020" = "SUBARU IMPREZA SPORT 2020" +"SUBARU LEGACY 7TH GEN" = "SUBARU OUTBACK 6TH GEN" + +# Old subarus don't have much data guessing it's like low torque impreza" +"SUBARU OUTBACK 2018 - 2019" = "SUBARU IMPREZA LIMITED 2019" +"SUBARU OUTBACK 2015 - 2017" = "SUBARU IMPREZA LIMITED 2019" +"SUBARU FORESTER 2017 - 2018" = "SUBARU IMPREZA LIMITED 2019" +"SUBARU LEGACY 2015 - 2018" = "SUBARU IMPREZA LIMITED 2019" +"SUBARU ASCENT LIMITED 2019" = "SUBARU FORESTER 2019" diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml deleted file mode 100644 index 242a38b3d0..0000000000 --- a/selfdrive/car/torque_data/substitute.yaml +++ /dev/null @@ -1,83 +0,0 @@ -MAZDA 3: MAZDA CX-9 2021 -MAZDA 6: MAZDA CX-9 2021 -MAZDA CX-5: MAZDA CX-9 2021 -MAZDA CX-5 2022: MAZDA CX-9 2021 -MAZDA CX-9: MAZDA CX-9 2021 - -TOYOTA ALPHARD 2020: TOYOTA SIENNA 2018 -TOYOTA PRIUS v 2017 : TOYOTA PRIUS 2017 -LEXUS IS 2018: LEXUS NX 2018 -LEXUS CT HYBRID 2018 : LEXUS NX 2018 -LEXUS ES 2018: TOYOTA CAMRY 2018 -LEXUS ES HYBRID 2018: TOYOTA CAMRY 2018 -LEXUS RC 2020: LEXUS NX 2020 - -KIA OPTIMA 4TH GEN: HYUNDAI SONATA 2020 -KIA OPTIMA 4TH GEN FACELIFT: HYUNDAI SONATA 2020 -KIA OPTIMA HYBRID 2017 & SPORTS 2019: HYUNDAI SONATA 2020 -KIA OPTIMA HYBRID 4TH GEN FACELIFT: HYUNDAI SONATA 2020 -KIA FORTE E 2018 & GT 2021: HYUNDAI SONATA 2020 -KIA CEED INTRO ED 2019: HYUNDAI SONATA 2020 -KIA SELTOS 2021: HYUNDAI SONATA 2020 -KIA NIRO HYBRID 2019: KIA NIRO EV 2020 -KIA NIRO PLUG-IN HYBRID 2022: KIA NIRO EV 2020 -KIA NIRO HYBRID 2021: KIA NIRO EV 2020 -HYUNDAI VELOSTER 2019: HYUNDAI SONATA 2019 -HYUNDAI KONA 2020: HYUNDAI KONA ELECTRIC 2019 -HYUNDAI KONA HYBRID 2020: HYUNDAI KONA ELECTRIC 2019 -HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019 -HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019 -HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019 -HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 -HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 -HYUNDAI I30 N LINE 2019 & GT 2018 DCT: HYUNDAI SONATA 2019 -HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 -HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 -HYUNDAI TUCSON 4TH GEN: HYUNDAI TUCSON HYBRID 4TH GEN -HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022 -KIA K5 HYBRID 2020: KIA K5 2021 -KIA STINGER 2022: KIA STINGER GT2 2018 -GENESIS G90 2017: GENESIS G70 2018 -GENESIS G80 2017: GENESIS G70 2018 -GENESIS G70 2020: HYUNDAI SONATA 2020 - -HONDA FREED 2020: HONDA ODYSSEY 2018 -HONDA CR-V EU 2016: HONDA CR-V 2016 -HONDA CIVIC SEDAN 1.6 DIESEL 2019: HONDA CIVIC (BOSCH) 2019 -HONDA E 2020: HONDA CIVIC (BOSCH) 2019 -HONDA ODYSSEY CHN 2019: HONDA ODYSSEY 2018 - -BUICK LACROSSE 2017: CHEVROLET VOLT PREMIER 2017 -BUICK REGAL ESSENCE 2018: CHEVROLET VOLT PREMIER 2017 -CADILLAC ESCALADE ESV 2016: CHEVROLET VOLT PREMIER 2017 -CADILLAC ATS Premium Performance 2018: CHEVROLET VOLT PREMIER 2017 -CHEVROLET MALIBU PREMIER 2017: CHEVROLET VOLT PREMIER 2017 -HOLDEN ASTRA RS-V BK 2017: CHEVROLET VOLT PREMIER 2017 - -SKODA FABIA 4TH GEN: VOLKSWAGEN GOLF 7TH GEN -SKODA OCTAVIA 3RD GEN: SKODA SUPERB 3RD GEN -SKODA SCALA 1ST GEN: SKODA SUPERB 3RD GEN -SKODA KODIAQ 1ST GEN: SKODA SUPERB 3RD GEN -SKODA KAROQ 1ST GEN: SKODA SUPERB 3RD GEN -SKODA KAMIQ 1ST GEN: SKODA SUPERB 3RD GEN -VOLKSWAGEN CRAFTER 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN -VOLKSWAGEN T-ROC 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN -VOLKSWAGEN T-CROSS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN -VOLKSWAGEN TOURAN 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN -VOLKSWAGEN TRANSPORTER T6.1: VOLKSWAGEN TIGUAN 2ND GEN -AUDI Q2 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN -VOLKSWAGEN TAOS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN -VOLKSWAGEN POLO 6TH GEN: VOLKSWAGEN GOLF 7TH GEN -SEAT LEON 3RD GEN: VOLKSWAGEN GOLF 7TH GEN -SEAT ATECA 1ST GEN: VOLKSWAGEN GOLF 7TH GEN - -SUBARU CROSSTREK HYBRID 2020: SUBARU IMPREZA SPORT 2020 -SUBARU FORESTER HYBRID 2020: SUBARU IMPREZA SPORT 2020 -SUBARU LEGACY 7TH GEN: SUBARU OUTBACK 6TH GEN - -# Old subarus don't have much data guessing it's like low torque impreza -SUBARU OUTBACK 2018 - 2019: SUBARU IMPREZA LIMITED 2019 -SUBARU OUTBACK 2015 - 2017: SUBARU IMPREZA LIMITED 2019 -SUBARU FORESTER 2017 - 2018: SUBARU IMPREZA LIMITED 2019 -SUBARU LEGACY 2015 - 2018: SUBARU IMPREZA LIMITED 2019 -SUBARU ASCENT LIMITED 2019: SUBARU FORESTER 2019 From 7f9e41ce2e9da4c712eba469a0f4407afbe3583f Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 17 Dec 2023 00:58:19 -0500 Subject: [PATCH 282/724] dependency: remove atomicwrites package (#30769) * Update file_helpers.py * remove dependency * Revert "remove dependency" This reverts commit e2bd5f4edab7d14b75f65c296631855d886e38d1. * remove atomicwrites * allow overwrite * fix indents * add type hints * Update poetry.lock * optional * wrap --- common/file_helpers.py | 37 ++++++++++++++++++++++--------------- poetry.lock | 23 +---------------------- pyproject.toml | 2 -- 3 files changed, 23 insertions(+), 39 deletions(-) diff --git a/common/file_helpers.py b/common/file_helpers.py index a29eafdd9f..7524860f96 100644 --- a/common/file_helpers.py +++ b/common/file_helpers.py @@ -1,7 +1,8 @@ import os import shutil import tempfile -from atomicwrites import AtomicWriter +import contextlib +from typing import Optional def rm_not_exists_ok(path): @@ -71,19 +72,25 @@ def _get_fileobject_func(writer, temp_dir): return writer.get_fileobject(dir=temp_dir) return _get_fileobject -def atomic_write_on_fs_tmp(path, **kwargs): - """Creates an atomic writer using a temporary file in a temporary directory - on the same filesystem as path. - """ - # TODO(mgraczyk): This use of AtomicWriter relies on implementation details to set the temp - # directory. - writer = AtomicWriter(path, **kwargs) - return writer._open(_get_fileobject_func(writer, get_tmpdir_on_same_filesystem(path))) +@contextlib.contextmanager +def atomic_write_on_fs_tmp(path: str, mode: str = 'w', buffering: int = -1, encoding: Optional[str] = None, newline: Optional[str] = None): + """Write to a file atomically using a temporary file in a temporary directory on the same filesystem as path.""" + temp_dir = get_tmpdir_on_same_filesystem(path) + with tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, dir=temp_dir, delete=False) as tmp_file: + yield tmp_file + tmp_file_name = tmp_file.name + os.replace(tmp_file_name, path) +@contextlib.contextmanager +def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: Optional[str] = None, newline: Optional[str] = None, + overwrite: bool = False): + """Write to a file atomically using a temporary file in the same directory as the destination file.""" + dir_name = os.path.dirname(path) -def atomic_write_in_dir(path, **kwargs): - """Creates an atomic writer using a temporary file in the same directory - as the destination file. - """ - writer = AtomicWriter(path, **kwargs) - return writer._open(_get_fileobject_func(writer, os.path.dirname(path))) + if not overwrite and os.path.exists(path): + raise FileExistsError(f"File '{path}' already exists. To overwrite it, set 'overwrite' to True.") + + with tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, dir=dir_name, delete=False) as tmp_file: + yield tmp_file + tmp_file_name = tmp_file.name + os.replace(tmp_file_name, path) diff --git a/poetry.lock b/poetry.lock index c1f9bd890f..3ad023cdd7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -180,16 +180,6 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] -[[package]] -name = "atomicwrites" -version = "1.4.1" -description = "Atomic file writes." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] - [[package]] name = "attrs" version = "23.1.0" @@ -4665,17 +4655,6 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] -[[package]] -name = "types-atomicwrites" -version = "1.4.5.1" -description = "Typing stubs for atomicwrites" -optional = false -python-versions = "*" -files = [ - {file = "types-atomicwrites-1.4.5.1.tar.gz", hash = "sha256:9e9f0923ebf93524b28bcece5a23ac8c3820f39b060df29f671936d2e4bc04bc"}, - {file = "types_atomicwrites-1.4.5.1-py3-none-any.whl", hash = "sha256:2f1febbdc78b55453b189fa5b136dce34bab7d1d82319163d470e404aab55c83"}, -] - [[package]] name = "types-pycurl" version = "7.45.2.5" @@ -4923,4 +4902,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "68be4dd5356e38c1e5a004a6e40b4fef963aacd1cad902b5fba70c51c442d981" +content-hash = "eeb5efb6161eaec95fb863ddc9ec08afb540ed3cd5d521646a6a759ddf33ab2e" diff --git a/pyproject.toml b/pyproject.toml index 336c440f6b..98091e0088 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,7 +108,6 @@ websocket_client = "*" polyline = "*" # these should be removed -atomicwrites = "*" markdown-it-py = "*" timezonefinder = "*" pycurl = "*" @@ -154,7 +153,6 @@ sphinx-rtd-theme = "*" sphinx-sitemap = "*" tabulate = "*" tenacity = "*" -types-atomicwrites = "*" types-pycurl = "*" types-requests = "*" types-tabulate = "*" From 1346704426ef247a74de3922da670e53251858d9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 22:19:50 -0800 Subject: [PATCH 283/724] common/file_helpers: remove xx stuff (#30772) * remove xx stuff * cleanup --- common/file_helpers.py | 60 +------------------------------ common/tests/test_file_helpers.py | 5 +-- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/common/file_helpers.py b/common/file_helpers.py index 7524860f96..dea298a529 100644 --- a/common/file_helpers.py +++ b/common/file_helpers.py @@ -1,53 +1,9 @@ import os -import shutil import tempfile import contextlib from typing import Optional -def rm_not_exists_ok(path): - try: - os.remove(path) - except OSError: - if os.path.exists(path): - raise - - -def rm_tree_or_link(path): - if os.path.islink(path): - os.unlink(path) - elif os.path.isdir(path): - shutil.rmtree(path) - - -def get_tmpdir_on_same_filesystem(path): - normpath = os.path.normpath(path) - parts = normpath.split("/") - if len(parts) > 1 and parts[1] == "scratch": - return "/scratch/tmp" - elif len(parts) > 2 and parts[2] == "runner": - return f"/{parts[1]}/runner/tmp" - return "/tmp" - - -class NamedTemporaryDir(): - def __init__(self, temp_dir=None): - self._path = tempfile.mkdtemp(dir=temp_dir) - - @property - def name(self): - return self._path - - def close(self): - shutil.rmtree(self._path) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - self.close() - - class CallbackReader: """Wraps a file, but overrides the read method to also call a callback function with the number of bytes read so far.""" @@ -67,22 +23,8 @@ class CallbackReader: return chunk -def _get_fileobject_func(writer, temp_dir): - def _get_fileobject(): - return writer.get_fileobject(dir=temp_dir) - return _get_fileobject - @contextlib.contextmanager -def atomic_write_on_fs_tmp(path: str, mode: str = 'w', buffering: int = -1, encoding: Optional[str] = None, newline: Optional[str] = None): - """Write to a file atomically using a temporary file in a temporary directory on the same filesystem as path.""" - temp_dir = get_tmpdir_on_same_filesystem(path) - with tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, dir=temp_dir, delete=False) as tmp_file: - yield tmp_file - tmp_file_name = tmp_file.name - os.replace(tmp_file_name, path) - -@contextlib.contextmanager -def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: Optional[str] = None, newline: Optional[str] = None, +def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: Optional[str] = None, newline: Optional[str] = None, overwrite: bool = False): """Write to a file atomically using a temporary file in the same directory as the destination file.""" dir_name = os.path.dirname(path) diff --git a/common/tests/test_file_helpers.py b/common/tests/test_file_helpers.py index e36df57a32..1817f77cd2 100644 --- a/common/tests/test_file_helpers.py +++ b/common/tests/test_file_helpers.py @@ -2,7 +2,6 @@ import os import unittest from uuid import uuid4 -from openpilot.common.file_helpers import atomic_write_on_fs_tmp from openpilot.common.file_helpers import atomic_write_in_dir @@ -11,14 +10,12 @@ class TestFileHelpers(unittest.TestCase): path = f"/tmp/tmp{uuid4()}" with atomic_write_func(path) as f: f.write("test") + assert not os.path.exists(path) with open(path) as f: self.assertEqual(f.read(), "test") os.remove(path) - def test_atomic_write_on_fs_tmp(self): - self.run_atomic_write_func(atomic_write_on_fs_tmp) - def test_atomic_write_in_dir(self): self.run_atomic_write_func(atomic_write_in_dir) From 2d466e70cdd6b4cf8ff5b939f401c2483a15f743 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 22:36:26 -0800 Subject: [PATCH 284/724] ui: show wifi password by default (#30766) * ui: show wifi password by default * update immediately --- selfdrive/ui/qt/widgets/input.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 52d0c5cd6e..4ff0de29f9 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -129,7 +129,8 @@ InputDialog::InputDialog(const QString &title, QWidget *parent, const QString &s line->setEchoMode(QLineEdit::Normal); } }); - eye_btn->setChecked(true); + eye_btn->toggle(); + eye_btn->setChecked(false); textbox_layout->addWidget(eye_btn); } From 4b54c0b3cd18a258df7a2f8752caf24980cf5d09 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 22:37:35 -0800 Subject: [PATCH 285/724] better uploader cpu thresholds --- selfdrive/test/test_onroad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index e3807af143..3f97ef6bc6 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -57,7 +57,7 @@ PROCS = { "selfdrive.boardd.pandad": 0, "selfdrive.statsd": 0.4, "selfdrive.navd.navd": 0.4, - "system.loggerd.uploader": 3.0, + "system.loggerd.uploader": (0.5, 10.0), "system.loggerd.deleter": 0.1, } From a07060b24bf9bc0963dad5fa94dc9881d6e3cc06 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 23:33:55 -0800 Subject: [PATCH 286/724] common: remove statlog (#30774) * common: remove statlog * fix release --- common/SConscript | 1 - common/statlog.cc | 47 -------------------------------------------- common/statlog.h | 10 ---------- release/files_common | 26 ++---------------------- 4 files changed, 2 insertions(+), 82 deletions(-) delete mode 100644 common/statlog.cc delete mode 100644 common/statlog.h diff --git a/common/SConscript b/common/SConscript index b8c23b7019..9b81e1dd29 100644 --- a/common/SConscript +++ b/common/SConscript @@ -2,7 +2,6 @@ Import('env', 'envCython', 'arch') common_libs = [ 'params.cc', - 'statlog.cc', 'swaglog.cc', 'util.cc', 'i2c.cc', diff --git a/common/statlog.cc b/common/statlog.cc deleted file mode 100644 index 587f3e8620..0000000000 --- a/common/statlog.cc +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef _GNU_SOURCE -#define _GNU_SOURCE -#endif - -#include "common/statlog.h" -#include "common/util.h" - -#include -#include -#include -#include - -class StatlogState : public LogState { - public: - StatlogState() : LogState("ipc:///tmp/stats") {} -}; - -static StatlogState s = {}; - -static void log(const char* metric_type, const char* metric, const char* fmt, ...) { - std::lock_guard lk(s.lock); - if (!s.initialized) s.initialize(); - - char* value_buf = nullptr; - va_list args; - va_start(args, fmt); - int ret = vasprintf(&value_buf, fmt, args); - va_end(args); - - if (ret > 0 && value_buf) { - char* line_buf = nullptr; - ret = asprintf(&line_buf, "%s:%s|%s", metric, value_buf, metric_type); - if (ret > 0 && line_buf) { - zmq_send(s.sock, line_buf, ret, ZMQ_NOBLOCK); - free(line_buf); - } - free(value_buf); - } -} - -void statlog_log(const char* metric_type, const char* metric, int value) { - log(metric_type, metric, "%d", value); -} - -void statlog_log(const char* metric_type, const char* metric, float value) { - log(metric_type, metric, "%f", value); -} diff --git a/common/statlog.h b/common/statlog.h deleted file mode 100644 index 5d223bb666..0000000000 --- a/common/statlog.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#define STATLOG_GAUGE "g" -#define STATLOG_SAMPLE "sa" - -void statlog_log(const char* metric_type, const char* metric, int value); -void statlog_log(const char* metric_type, const char* metric, float value); - -#define statlog_gauge(metric, value) statlog_log(STATLOG_GAUGE, metric, value) -#define statlog_sample(metric, value) statlog_log(STATLOG_SAMPLE, metric, value) diff --git a/release/files_common b/release/files_common index 2ef68651c8..3dd116613c 100644 --- a/release/files_common +++ b/release/files_common @@ -127,30 +127,8 @@ selfdrive/debug/vw_mqb_config.py common/SConscript common/version.h -common/prefix.h -common/swaglog.h -common/swaglog.cc -common/statlog.h -common/statlog.cc -common/util.cc -common/util.h -common/queue.h -common/clutil.cc -common/clutil.h -common/params.h -common/params.cc -common/ratekeeper.cc -common/ratekeeper.h -common/watchdog.cc -common/watchdog.h - -common/mat.h -common/timing.h - -common/gpio.cc -common/gpio.h -common/i2c.cc -common/i2c.h +common/*.h +common/*.cc selfdrive/controls/__init__.py selfdrive/controls/controlsd.py From 7aeefaad65b1d31789031a904a0a5e66180537af Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 23:35:43 -0800 Subject: [PATCH 287/724] jenkins: only do replay when relevant files change --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 3f573e6ae4..a7f272cd80 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -241,7 +241,7 @@ node { 'replay': { deviceStage("tici", "tici-replay", ["UNSAFE=1"], [ ["build", "cd selfdrive/manager && ./build.py"], - ["model replay", "selfdrive/test/process_replay/model_replay.py"], + ["model replay", "selfdrive/test/process_replay/model_replay.py", ["tinygrad/", "selfdrive/modeld/"]], ]) }, 'tizi': { From 1421551297396cf16e3d041506f156c1626ded50 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 16 Dec 2023 23:45:28 -0800 Subject: [PATCH 288/724] Cython KF1D to Python (#30773) * Cython KF1D to Python * cleanup * set x * less nesting * fix release * Revert "fix release" This reverts commit 97e5d0f80405660efb861c7b9df7fbfd5dda5dd9. --- common/SConscript | 7 +- common/kalman/.gitignore | 1 - common/kalman/SConscript | 5 -- common/kalman/__init__.py | 0 common/kalman/simple_kalman.py | 12 ---- common/kalman/simple_kalman_impl.pxd | 18 ----- common/kalman/simple_kalman_impl.pyx | 37 ---------- common/kalman/simple_kalman_old.py | 23 ------ common/kalman/tests/__init__.py | 0 common/kalman/tests/test_simple_kalman.py | 87 ----------------------- common/simple_kalman.py | 54 ++++++++++++++ common/tests/test_simple_kalman.py | 35 +++++++++ release/files_common | 3 - selfdrive/car/interfaces.py | 4 +- selfdrive/controls/radard.py | 2 +- 15 files changed, 95 insertions(+), 193 deletions(-) delete mode 100644 common/kalman/.gitignore delete mode 100644 common/kalman/SConscript delete mode 100644 common/kalman/__init__.py delete mode 100644 common/kalman/simple_kalman.py delete mode 100644 common/kalman/simple_kalman_impl.pxd delete mode 100644 common/kalman/simple_kalman_impl.pyx delete mode 100644 common/kalman/simple_kalman_old.py delete mode 100644 common/kalman/tests/__init__.py delete mode 100644 common/kalman/tests/test_simple_kalman.py create mode 100644 common/simple_kalman.py create mode 100644 common/tests/test_simple_kalman.py diff --git a/common/SConscript b/common/SConscript index 9b81e1dd29..52b3adf1d3 100644 --- a/common/SConscript +++ b/common/SConscript @@ -30,11 +30,10 @@ if GetOption('extras'): params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) SConscript([ - 'kalman/SConscript', - 'transformations/SConscript' + 'transformations/SConscript', ]) -Import('simple_kalman_python', 'transformations_python') -common_python = [params_python, simple_kalman_python, transformations_python] +Import('transformations_python') +common_python = [params_python, transformations_python] Export('common_python') diff --git a/common/kalman/.gitignore b/common/kalman/.gitignore deleted file mode 100644 index d86912e7d0..0000000000 --- a/common/kalman/.gitignore +++ /dev/null @@ -1 +0,0 @@ -simple_kalman_impl.c diff --git a/common/kalman/SConscript b/common/kalman/SConscript deleted file mode 100644 index 7cba4a9a61..0000000000 --- a/common/kalman/SConscript +++ /dev/null @@ -1,5 +0,0 @@ -Import('envCython') - -simple_kalman_python = envCython.Program('simple_kalman_impl.so', 'simple_kalman_impl.pyx') - -Export('simple_kalman_python') diff --git a/common/kalman/__init__.py b/common/kalman/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/kalman/simple_kalman.py b/common/kalman/simple_kalman.py deleted file mode 100644 index cd3b5a1df9..0000000000 --- a/common/kalman/simple_kalman.py +++ /dev/null @@ -1,12 +0,0 @@ -from openpilot.common.kalman.simple_kalman_impl import KF1D as KF1D -assert KF1D -import numpy as np - -def get_kalman_gain(dt, A, C, Q, R, iterations=100): - P = np.zeros_like(Q) - for _ in range(iterations): - P = A.dot(P).dot(A.T) + dt * Q - S = C.dot(P).dot(C.T) + R - K = P.dot(C.T).dot(np.linalg.inv(S)) - P = (np.eye(len(P)) - K.dot(C)).dot(P) - return K diff --git a/common/kalman/simple_kalman_impl.pxd b/common/kalman/simple_kalman_impl.pxd deleted file mode 100644 index cb39a45bca..0000000000 --- a/common/kalman/simple_kalman_impl.pxd +++ /dev/null @@ -1,18 +0,0 @@ -# cython: language_level = 3 - -cdef class KF1D: - cdef public: - double x0_0 - double x1_0 - double K0_0 - double K1_0 - double A0_0 - double A0_1 - double A1_0 - double A1_1 - double C0_0 - double C0_1 - double A_K_0 - double A_K_1 - double A_K_2 - double A_K_3 diff --git a/common/kalman/simple_kalman_impl.pyx b/common/kalman/simple_kalman_impl.pyx deleted file mode 100644 index 16aefba2e5..0000000000 --- a/common/kalman/simple_kalman_impl.pyx +++ /dev/null @@ -1,37 +0,0 @@ -# distutils: language = c++ -# cython: language_level=3 - -cdef class KF1D: - def __init__(self, x0, A, C, K): - self.x0_0 = x0[0][0] - self.x1_0 = x0[1][0] - self.A0_0 = A[0][0] - self.A0_1 = A[0][1] - self.A1_0 = A[1][0] - self.A1_1 = A[1][1] - self.C0_0 = C[0] - self.C0_1 = C[1] - self.K0_0 = K[0][0] - self.K1_0 = K[1][0] - - self.A_K_0 = self.A0_0 - self.K0_0 * self.C0_0 - self.A_K_1 = self.A0_1 - self.K0_0 * self.C0_1 - self.A_K_2 = self.A1_0 - self.K1_0 * self.C0_0 - self.A_K_3 = self.A1_1 - self.K1_0 * self.C0_1 - - def update(self, meas): - cdef double x0_0 = self.A_K_0 * self.x0_0 + self.A_K_1 * self.x1_0 + self.K0_0 * meas - cdef double x1_0 = self.A_K_2 * self.x0_0 + self.A_K_3 * self.x1_0 + self.K1_0 * meas - self.x0_0 = x0_0 - self.x1_0 = x1_0 - - return [self.x0_0, self.x1_0] - - @property - def x(self): - return [[self.x0_0], [self.x1_0]] - - @x.setter - def x(self, x): - self.x0_0 = x[0][0] - self.x1_0 = x[1][0] diff --git a/common/kalman/simple_kalman_old.py b/common/kalman/simple_kalman_old.py deleted file mode 100644 index d11770faf6..0000000000 --- a/common/kalman/simple_kalman_old.py +++ /dev/null @@ -1,23 +0,0 @@ -import numpy as np - - -class KF1D: - # this EKF assumes constant covariance matrix, so calculations are much simpler - # the Kalman gain also needs to be precomputed using the control module - - def __init__(self, x0, A, C, K): - self.x = x0 - self.A = A - self.C = np.atleast_2d(C) - self.K = K - - self.A_K = self.A - np.dot(self.K, self.C) - - # K matrix needs to be pre-computed as follow: - # import control - # (x, l, K) = control.dare(np.transpose(self.A), np.transpose(self.C), Q, R) - # self.K = np.transpose(K) - - def update(self, meas): - self.x = np.dot(self.A_K, self.x) + np.dot(self.K, meas) - return self.x diff --git a/common/kalman/tests/__init__.py b/common/kalman/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/kalman/tests/test_simple_kalman.py b/common/kalman/tests/test_simple_kalman.py deleted file mode 100644 index 32cc79fc3d..0000000000 --- a/common/kalman/tests/test_simple_kalman.py +++ /dev/null @@ -1,87 +0,0 @@ -import unittest -import random -import timeit -import numpy as np - -from openpilot.common.kalman.simple_kalman import KF1D -from openpilot.common.kalman.simple_kalman_old import KF1D as KF1D_old - - -class TestSimpleKalman(unittest.TestCase): - def setUp(self): - dt = 0.01 - x0_0 = 0.0 - x1_0 = 0.0 - A0_0 = 1.0 - A0_1 = dt - A1_0 = 0.0 - A1_1 = 1.0 - C0_0 = 1.0 - C0_1 = 0.0 - K0_0 = 0.12287673 - K1_0 = 0.29666309 - - self.kf_old = KF1D_old(x0=np.array([[x0_0], [x1_0]]), - A=np.array([[A0_0, A0_1], [A1_0, A1_1]]), - C=np.array([C0_0, C0_1]), - K=np.array([[K0_0], [K1_0]])) - - self.kf = KF1D(x0=[[x0_0], [x1_0]], - A=[[A0_0, A0_1], [A1_0, A1_1]], - C=[C0_0, C0_1], - K=[[K0_0], [K1_0]]) - - def test_getter_setter(self): - self.kf.x = [[1.0], [1.0]] - self.assertEqual(self.kf.x, [[1.0], [1.0]]) - - def update_returns_state(self): - x = self.kf.update(100) - self.assertEqual(x, self.kf.x) - - def test_old_equal_new(self): - for _ in range(1000): - v_wheel = random.uniform(0, 200) - - x_old = self.kf_old.update(v_wheel) - x = self.kf.update(v_wheel) - - # Compare the output x, verify that the error is less than 1e-4 - np.testing.assert_almost_equal(x_old[0], x[0]) - np.testing.assert_almost_equal(x_old[1], x[1]) - - def test_new_is_faster(self): - setup = """ -import numpy as np - -from openpilot.common.kalman.simple_kalman import KF1D -from openpilot.common.kalman.simple_kalman_old import KF1D as KF1D_old - -dt = 0.01 -x0_0 = 0.0 -x1_0 = 0.0 -A0_0 = 1.0 -A0_1 = dt -A1_0 = 0.0 -A1_1 = 1.0 -C0_0 = 1.0 -C0_1 = 0.0 -K0_0 = 0.12287673 -K1_0 = 0.29666309 - -kf_old = KF1D_old(x0=np.array([[x0_0], [x1_0]]), - A=np.array([[A0_0, A0_1], [A1_0, A1_1]]), - C=np.array([C0_0, C0_1]), - K=np.array([[K0_0], [K1_0]])) - -kf = KF1D(x0=[[x0_0], [x1_0]], - A=[[A0_0, A0_1], [A1_0, A1_1]], - C=[C0_0, C0_1], - K=[[K0_0], [K1_0]]) - """ - kf_speed = timeit.timeit("kf.update(1234)", setup=setup, number=10000) - kf_old_speed = timeit.timeit("kf_old.update(1234)", setup=setup, number=10000) - self.assertTrue(kf_speed < kf_old_speed / 4) - -if __name__ == "__main__": - unittest.main() diff --git a/common/simple_kalman.py b/common/simple_kalman.py new file mode 100644 index 0000000000..194b27204b --- /dev/null +++ b/common/simple_kalman.py @@ -0,0 +1,54 @@ +import numpy as np + + +def get_kalman_gain(dt, A, C, Q, R, iterations=100): + P = np.zeros_like(Q) + for _ in range(iterations): + P = A.dot(P).dot(A.T) + dt * Q + S = C.dot(P).dot(C.T) + R + K = P.dot(C.T).dot(np.linalg.inv(S)) + P = (np.eye(len(P)) - K.dot(C)).dot(P) + return K + + +class KF1D: + # this EKF assumes constant covariance matrix, so calculations are much simpler + # the Kalman gain also needs to be precomputed using the control module + + def __init__(self, x0, A, C, K): + self.x0_0 = x0[0][0] + self.x1_0 = x0[1][0] + self.A0_0 = A[0][0] + self.A0_1 = A[0][1] + self.A1_0 = A[1][0] + self.A1_1 = A[1][1] + self.C0_0 = C[0] + self.C0_1 = C[1] + self.K0_0 = K[0][0] + self.K1_0 = K[1][0] + + self.A_K_0 = self.A0_0 - self.K0_0 * self.C0_0 + self.A_K_1 = self.A0_1 - self.K0_0 * self.C0_1 + self.A_K_2 = self.A1_0 - self.K1_0 * self.C0_0 + self.A_K_3 = self.A1_1 - self.K1_0 * self.C0_1 + + # K matrix needs to be pre-computed as follow: + # import control + # (x, l, K) = control.dare(np.transpose(self.A), np.transpose(self.C), Q, R) + # self.K = np.transpose(K) + + def update(self, meas): + #self.x = np.dot(self.A_K, self.x) + np.dot(self.K, meas) + x0_0 = self.A_K_0 * self.x0_0 + self.A_K_1 * self.x1_0 + self.K0_0 * meas + x1_0 = self.A_K_2 * self.x0_0 + self.A_K_3 * self.x1_0 + self.K1_0 * meas + self.x0_0 = x0_0 + self.x1_0 = x1_0 + return [self.x0_0, self.x1_0] + + @property + def x(self): + return [[self.x0_0], [self.x1_0]] + + def set_x(self, x): + self.x0_0 = x[0][0] + self.x1_0 = x[1][0] diff --git a/common/tests/test_simple_kalman.py b/common/tests/test_simple_kalman.py new file mode 100644 index 0000000000..f641cd19e6 --- /dev/null +++ b/common/tests/test_simple_kalman.py @@ -0,0 +1,35 @@ +import unittest + +from openpilot.common.simple_kalman import KF1D + + +class TestSimpleKalman(unittest.TestCase): + def setUp(self): + dt = 0.01 + x0_0 = 0.0 + x1_0 = 0.0 + A0_0 = 1.0 + A0_1 = dt + A1_0 = 0.0 + A1_1 = 1.0 + C0_0 = 1.0 + C0_1 = 0.0 + K0_0 = 0.12287673 + K1_0 = 0.29666309 + + self.kf = KF1D(x0=[[x0_0], [x1_0]], + A=[[A0_0, A0_1], [A1_0, A1_1]], + C=[C0_0, C0_1], + K=[[K0_0], [K1_0]]) + + def test_getter_setter(self): + self.kf.set_x([[1.0], [1.0]]) + self.assertEqual(self.kf.x, [[1.0], [1.0]]) + + def update_returns_state(self): + x = self.kf.update(100) + self.assertEqual(x, self.kf.x) + + +if __name__ == "__main__": + unittest.main() diff --git a/release/files_common b/release/files_common index 3dd116613c..2039347048 100644 --- a/release/files_common +++ b/release/files_common @@ -24,9 +24,6 @@ common/__init__.py common/*.py common/*.pyx -common/kalman/.gitignore -common/kalman/* - common/transformations/__init__.py common/transformations/camera.py common/transformations/model.py diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 94b3bdfc84..3bbf16c789 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -8,7 +8,7 @@ from typing import Any, Dict, Optional, Tuple, List, Callable from cereal import car from openpilot.common.basedir import BASEDIR from openpilot.common.conversions import Conversions as CV -from openpilot.common.kalman.simple_kalman import KF1D, get_kalman_gain +from openpilot.common.simple_kalman import KF1D, get_kalman_gain from openpilot.common.numpy_fast import clip from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG @@ -346,7 +346,7 @@ class CarStateBase(ABC): def update_speed_kf(self, v_ego_raw): if abs(v_ego_raw - self.v_ego_kf.x[0][0]) > 2.0: # Prevent large accelerations when car starts at non zero speed - self.v_ego_kf.x = [[v_ego_raw], [0.0]] + self.v_ego_kf.set_x([[v_ego_raw], [0.0]]) v_ego_x = self.v_ego_kf.update(v_ego_raw) return float(v_ego_x[0]), float(v_ego_x[1]) diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index f75b4b2059..4acadee7a7 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -11,7 +11,7 @@ from openpilot.common.params import Params from openpilot.common.realtime import Ratekeeper, Priority, config_realtime_process from openpilot.common.swaglog import cloudlog -from openpilot.common.kalman.simple_kalman import KF1D +from openpilot.common.simple_kalman import KF1D # Default lead acceleration decay set to 50% at 1s From 77bafb0708408a70d867828d48ac1f5795844774 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 17 Dec 2023 10:43:14 -0800 Subject: [PATCH 289/724] Bump submodules (#30776) bump submodules Co-authored-by: adeebshihadeh --- panda | 2 +- teleoprtc_repo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/panda b/panda index 7e2b312fa8..6e645d2d33 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 7e2b312fa86e6cdfeba496f261407cb47cb005d1 +Subproject commit 6e645d2d337f0c6b245ebae9bd8a54467634f13b diff --git a/teleoprtc_repo b/teleoprtc_repo index 4df237c512..ca10fb2410 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit 4df237c512622d3914a73f4fc15a3fc89b7f5a97 +Subproject commit ca10fb2410ee14e207f30aadebeec707904dc559 From 2c30421b161de649e7e255b04b86b1eb88698a86 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 17 Dec 2023 10:50:45 -0800 Subject: [PATCH 290/724] move common.window to xx (#30777) --- common/window.py | 61 ----------------------------------- tools/camerastream/receive.py | 44 ------------------------- 2 files changed, 105 deletions(-) delete mode 100644 common/window.py delete mode 100755 tools/camerastream/receive.py diff --git a/common/window.py b/common/window.py deleted file mode 100644 index 95886c0c92..0000000000 --- a/common/window.py +++ /dev/null @@ -1,61 +0,0 @@ -import sys -import pygame -import cv2 - -class Window: - def __init__(self, w, h, caption="window", double=False, halve=False): - self.w = w - self.h = h - pygame.display.init() - pygame.display.set_caption(caption) - self.double = double - self.halve = halve - if self.double: - self.rw, self.rh = w*2, h*2 - elif self.halve: - self.rw, self.rh = w//2, h//2 - else: - self.rw, self.rh = w, h - self.screen = pygame.display.set_mode((self.rw, self.rh)) - pygame.display.flip() - - # hack for xmonad, it shrinks the window by 6 pixels after the display.flip - if self.screen.get_width() != self.rw: - self.screen = pygame.display.set_mode((self.rw+(self.rw-self.screen.get_width()), self.rh+(self.rh-self.screen.get_height()))) - pygame.display.flip() - - def draw(self, out): - pygame.event.pump() - if self.double: - out2 = cv2.resize(out, (self.w*2, self.h*2)) - pygame.surfarray.blit_array(self.screen, out2.swapaxes(0, 1)) - elif self.halve: - out2 = cv2.resize(out, (self.w//2, self.h//2)) - pygame.surfarray.blit_array(self.screen, out2.swapaxes(0, 1)) - else: - pygame.surfarray.blit_array(self.screen, out.swapaxes(0, 1)) - pygame.display.flip() - - def getkey(self): - while 1: - event = pygame.event.wait() - if event.type == pygame.QUIT: - pygame.quit() - sys.exit() - if event.type == pygame.KEYDOWN: - return event.key - - def getclick(self): - for event in pygame.event.get(): - if event.type == pygame.MOUSEBUTTONDOWN: - mx, my = pygame.mouse.get_pos() - return mx, my - -if __name__ == "__main__": - import numpy as np - win = Window(200, 200, double=True) - img: np.ndarray = np.zeros((200, 200, 3), np.uint8) - while 1: - print("draw") - img += 1 - win.draw(img) diff --git a/tools/camerastream/receive.py b/tools/camerastream/receive.py deleted file mode 100755 index bf6929c039..0000000000 --- a/tools/camerastream/receive.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -import os -import sys -import numpy as np -os.environ['ZMQ'] = '1' - -from openpilot.common.window import Window -import cereal.messaging as messaging - -# start camerad with 'SEND_ROAD=1 SEND_DRIVER=1 SEND_WIDE_ROAD=1 XMIN=771 XMAX=1156 YMIN=483 YMAX=724 ./camerad' -# also start bridge -# then run this "./receive.py " - -if "FULL" in os.environ: - SCALE = 2 - XMIN, XMAX = 0, 1927 - YMIN, YMAX = 0, 1207 -else: - SCALE = 1 - XMIN = 771 - XMAX = 1156 - YMIN = 483 - YMAX = 724 -H, W = ((YMAX-YMIN+1)//SCALE, (XMAX-XMIN+1)//SCALE) - -if __name__ == '__main__': - cameras = ['roadCameraState', 'wideRoadCameraState', 'driverCameraState'] - if "CAM" in os.environ: - cam = int(os.environ['CAM']) - cameras = cameras[cam:cam+1] - sm = messaging.SubMaster(cameras, addr=sys.argv[1]) - win = Window(W*len(cameras), H) - bdat = np.zeros((H, W*len(cameras), 3), dtype=np.uint8) - - while 1: - sm.update() - for i,k in enumerate(cameras): - if sm.updated[k]: - #print("update", k) - bgr_raw = sm[k].image - dat = np.frombuffer(bgr_raw, dtype=np.uint8).reshape(H, W, 3)[:, :, [2,1,0]] - bdat[:, W*i:W*(i+1)] = dat - win.draw(bdat) - From b75cdd15422d8716390ff981102fdbd290770b75 Mon Sep 17 00:00:00 2001 From: Andrei Radulescu Date: Sun, 17 Dec 2023 21:07:48 +0200 Subject: [PATCH 291/724] wifiManager: fix for lteConnectionPath not set on boot (#30517) Co-authored-by: Justin Newberry --- selfdrive/ui/qt/network/wifi_manager.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/network/wifi_manager.cc b/selfdrive/ui/qt/network/wifi_manager.cc index 03c6896f7a..ebb5cb8736 100644 --- a/selfdrive/ui/qt/network/wifi_manager.cc +++ b/selfdrive/ui/qt/network/wifi_manager.cc @@ -59,6 +59,8 @@ WifiManager::WifiManager(QObject *parent) : QObject(parent) { } timer.callOnTimeout(this, &WifiManager::requestScan); + + initConnections(); } void WifiManager::setup() { @@ -72,7 +74,6 @@ void WifiManager::setup() { raw_adapter_state = call(adapter, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_DEVICE, "State"); activeAp = call(adapter, NM_DBUS_INTERFACE_PROPERTIES, "Get", NM_DBUS_INTERFACE_DEVICE_WIRELESS, "ActiveAccessPoint").path(); - initConnections(); requestScan(); } From 8c1176ca839bdfd805f87d281a6d06b9af881eb5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 17 Dec 2023 11:40:46 -0800 Subject: [PATCH 292/724] more common/ pruning (#30778) --- common/ffi_wrapper.py | 49 +---------------------------------------- common/lazy_property.py | 12 ---------- common/numpy_helpers.py | 22 ------------------ 3 files changed, 1 insertion(+), 82 deletions(-) delete mode 100644 common/lazy_property.py delete mode 100644 common/numpy_helpers.py diff --git a/common/ffi_wrapper.py b/common/ffi_wrapper.py index a228b40256..01741c6f42 100644 --- a/common/ffi_wrapper.py +++ b/common/ffi_wrapper.py @@ -1,55 +1,8 @@ -import os -import sys -import fcntl -import hashlib import platform -from cffi import FFI + def suffix(): if platform.system() == "Darwin": return ".dylib" else: return ".so" - -def ffi_wrap(name, c_code, c_header, tmpdir="/tmp/ccache", cflags="", libraries=None): - if libraries is None: - libraries = [] - - cache = name + "_" + hashlib.sha1(c_code.encode('utf-8')).hexdigest() - try: - os.mkdir(tmpdir) - except OSError: - pass - - fd = os.open(tmpdir, 0) - fcntl.flock(fd, fcntl.LOCK_EX) - try: - sys.path.append(tmpdir) - try: - mod = __import__(cache) - except Exception: - print(f"cache miss {cache}") - compile_code(cache, c_code, c_header, tmpdir, cflags, libraries) - mod = __import__(cache) - finally: - os.close(fd) - - return mod.ffi, mod.lib - - -def compile_code(name, c_code, c_header, directory, cflags="", libraries=None): - if libraries is None: - libraries = [] - - ffibuilder = FFI() - ffibuilder.set_source(name, c_code, source_extension='.cpp', libraries=libraries) - ffibuilder.cdef(c_header) - os.environ['OPT'] = "-fwrapv -O2 -DNDEBUG -std=c++1z" - os.environ['CFLAGS'] = cflags - ffibuilder.compile(verbose=True, debug=False, tmpdir=directory) - - -def wrap_compiled(name, directory): - sys.path.append(directory) - mod = __import__(name) - return mod.ffi, mod.lib diff --git a/common/lazy_property.py b/common/lazy_property.py deleted file mode 100644 index 919dd9e87e..0000000000 --- a/common/lazy_property.py +++ /dev/null @@ -1,12 +0,0 @@ -class lazy_property(): - """Defines a property whose value will be computed only once and as needed. - - This can only be used on instance methods. - """ - def __init__(self, func): - self._func = func - - def __get__(self, obj_self, cls): - value = self._func(obj_self) - setattr(obj_self, self._func.__name__, value) - return value diff --git a/common/numpy_helpers.py b/common/numpy_helpers.py deleted file mode 100644 index 7b1efe8976..0000000000 --- a/common/numpy_helpers.py +++ /dev/null @@ -1,22 +0,0 @@ -import numpy as np - - -def deep_interp_np(x, xp, fp, axis=None): - if axis is not None: - fp = fp.swapaxes(0,axis) - x = np.atleast_1d(x) - xp = np.array(xp) - if len(xp) < 2: - return np.repeat(fp, len(x), axis=0) - if min(np.diff(xp)) < 0: - raise RuntimeError('Bad x array for interpolation') - j = np.searchsorted(xp, x) - 1 - j = np.clip(j, 0, len(xp)-2) - d = np.divide(x - xp[j], xp[j + 1] - xp[j], out=np.ones_like(x, dtype=np.float64), where=xp[j + 1] - xp[j] != 0) - vals_interp = (fp[j].T*(1 - d)).T + (fp[j + 1].T*d).T - if axis is not None: - vals_interp = vals_interp.swapaxes(0,axis) - if len(vals_interp) == 1: - return vals_interp[0] - else: - return vals_interp From 70624ffc819c29d842cb68fd86f42eac95421b10 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 17 Dec 2023 19:25:25 -0500 Subject: [PATCH 293/724] dependency: remove pycurl package (#30771) * Update qcomgpsd.py * Update url_file.py * remove pycurl * requests -> urllib3 * unused * redundant * fix import * Revert "requests -> urllib3" This reverts commit 7ca39e618917986f50356519d2795a432e3a5280. * headless * fix trail * use requests.exceptions.RequestException * use fp.tell * fix indents * reorder imports * change timeout * fix debug timing * remove exception * add timeout * missing headers * move to constructor * move import * unused import * fix debug * try * no retries --- poetry.lock | 23 +----------- pyproject.toml | 2 -- system/qcomgpsd/qcomgpsd.py | 29 ++++++--------- tools/lib/url_file.py | 70 ++++++++++++------------------------- 4 files changed, 33 insertions(+), 91 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3ad023cdd7..477cbac64c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3206,16 +3206,6 @@ files = [ {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"}, ] -[[package]] -name = "pycurl" -version = "7.45.2" -description = "PycURL -- A Python Interface To The cURL library" -optional = false -python-versions = ">=3.5" -files = [ - {file = "pycurl-7.45.2.tar.gz", hash = "sha256:5730590be0271364a5bddd9e245c9cc0fb710c4cbacbdd95264a3122d23224ca"}, -] - [[package]] name = "pyee" version = "11.1.0" @@ -4655,17 +4645,6 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] -[[package]] -name = "types-pycurl" -version = "7.45.2.5" -description = "Typing stubs for pycurl" -optional = false -python-versions = "*" -files = [ - {file = "types-pycurl-7.45.2.5.tar.gz", hash = "sha256:cc07e4b1e388b98e1a935bc12ef33eec8e7294496d0a81ed50c46a31bf5df5ee"}, - {file = "types_pycurl-7.45.2.5-py3-none-any.whl", hash = "sha256:e433f13a266245be770018eabc6a50139ea26150f75df5b0ee698d291575582b"}, -] - [[package]] name = "types-requests" version = "2.31.0.10" @@ -4902,4 +4881,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "eeb5efb6161eaec95fb863ddc9ec08afb540ed3cd5d521646a6a759ddf33ab2e" +content-hash = "26e963a3e9236b0e29466a0bca243215395a780c88485e9c17f6518969334890" diff --git a/pyproject.toml b/pyproject.toml index 98091e0088..a519670d48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,7 +110,6 @@ polyline = "*" # these should be removed markdown-it-py = "*" timezonefinder = "*" -pycurl = "*" setproctitle = "*" @@ -153,7 +152,6 @@ sphinx-rtd-theme = "*" sphinx-sitemap = "*" tabulate = "*" tenacity = "*" -types-pycurl = "*" types-requests = "*" types-tabulate = "*" tqdm = "*" diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 0105d4074f..3f72350234 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -5,7 +5,7 @@ import signal import itertools import math import time -import pycurl +import requests import shutil import subprocess import datetime @@ -102,27 +102,18 @@ def gps_enabled() -> bool: def download_assistance(): try: - c = pycurl.Curl() - c.setopt(pycurl.URL, ASSISTANCE_URL) - c.setopt(pycurl.NOBODY, 1) - c.setopt(pycurl.CONNECTTIMEOUT, 2) - c.perform() - bytes_n = c.getinfo(pycurl.CONTENT_LENGTH_DOWNLOAD) - c.close() - if bytes_n > 1e5: - cloudlog.error("Qcom assistance data larger than expected") - return + response = requests.get(ASSISTANCE_URL, timeout=5, stream=True) with open(ASSIST_DATA_FILE_DOWNLOAD, 'wb') as fp: - c = pycurl.Curl() - c.setopt(pycurl.URL, ASSISTANCE_URL) - c.setopt(pycurl.CONNECTTIMEOUT, 5) + for chunk in response.iter_content(chunk_size=8192): + fp.write(chunk) + if fp.tell() > 1e5: + cloudlog.error("Qcom assistance data larger than expected") + return - c.setopt(pycurl.WRITEDATA, fp) - c.perform() - c.close() - os.rename(ASSIST_DATA_FILE_DOWNLOAD, ASSIST_DATA_FILE) - except pycurl.error: + os.rename(ASSIST_DATA_FILE_DOWNLOAD, ASSIST_DATA_FILE) + + except requests.exceptions.RequestException: cloudlog.exception("Failed to download assistance file") return diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index a54552d241..3fb2135c08 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -1,10 +1,11 @@ import os import time import threading -import pycurl from hashlib import sha256 -from io import BytesIO +from urllib3 import PoolManager, Retry +from urllib3.util import Timeout from tenacity import retry, wait_random_exponential, stop_after_attempt + from openpilot.common.file_helpers import atomic_write_in_dir from openpilot.system.hardware.hw import Paths # Cache chunk size @@ -35,13 +36,14 @@ class URLFile: if cache is not None: self._force_download = not cache - try: - self._curl = self._tlocal.curl - except AttributeError: - self._curl = self._tlocal.curl = pycurl.Curl() if not self._force_download: os.makedirs(Paths.download_cache_root(), exist_ok=True) + try: + self._http_client = URLFile._tlocal.http_client + except AttributeError: + self._http_client = URLFile._tlocal.http_client = PoolManager() + def __enter__(self): return self @@ -53,17 +55,10 @@ class URLFile: @retry(wait=wait_random_exponential(multiplier=1, max=5), stop=stop_after_attempt(3), reraise=True) def get_length_online(self): - c = self._curl - c.reset() - c.setopt(pycurl.NOSIGNAL, 1) - c.setopt(pycurl.TIMEOUT_MS, 500000) - c.setopt(pycurl.FOLLOWLOCATION, True) - c.setopt(pycurl.URL, self._url) - c.setopt(c.NOBODY, 1) - c.perform() - length = int(c.getinfo(c.CONTENT_LENGTH_DOWNLOAD)) - c.reset() - return length + timeout = Timeout(connect=50.0, read=500.0) + response = self._http_client.request('HEAD', self._url, timeout=timeout, preload_content=False) + length = response.headers.get('content-length', 0) + return int(length) def get_length(self): if self._length is not None: @@ -117,7 +112,7 @@ class URLFile: @retry(wait=wait_random_exponential(multiplier=1, max=5), stop=stop_after_attempt(3), reraise=True) def read_aux(self, ll=None): download_range = False - headers = ["Connection: keep-alive"] + headers = {'Connection': 'keep-alive'} if self._pos != 0 or ll is not None: if ll is None: end = self.get_length() - 1 @@ -125,50 +120,29 @@ class URLFile: end = min(self._pos + ll, self.get_length()) - 1 if self._pos >= end: return b"" - headers.append(f"Range: bytes={self._pos}-{end}") + headers['Range'] = f"bytes={self._pos}-{end}" download_range = True - dats = BytesIO() - c = self._curl - c.setopt(pycurl.URL, self._url) - c.setopt(pycurl.WRITEDATA, dats) - c.setopt(pycurl.NOSIGNAL, 1) - c.setopt(pycurl.TIMEOUT_MS, 500000) - c.setopt(pycurl.HTTPHEADER, headers) - c.setopt(pycurl.FOLLOWLOCATION, True) - if self._debug: - print("downloading", self._url) - - def header(x): - if b'MISS' in x: - print(x.strip()) - - c.setopt(pycurl.HEADERFUNCTION, header) - - def test(debug_type, debug_msg): - print(" debug(%d): %s" % (debug_type, debug_msg.strip())) - - c.setopt(pycurl.VERBOSE, 1) - c.setopt(pycurl.DEBUGFUNCTION, test) t1 = time.time() - c.perform() + timeout = Timeout(connect=50.0, read=500.0) + response = self._http_client.request('GET', self._url, timeout=timeout, preload_content=False, headers=headers) + ret = response.data if self._debug: t2 = time.time() if t2 - t1 > 0.1: - print(f"get {self._url} {headers!r} {t2 - t1:.f} slow") + print(f"get {self._url} {headers!r} {t2 - t1:.3f} slow") - response_code = c.getinfo(pycurl.RESPONSE_CODE) + response_code = response.status if response_code == 416: # Requested Range Not Satisfiable - raise URLFileException(f"Error, range out of bounds {response_code} {headers} ({self._url}): {repr(dats.getvalue())[:500]}") + raise URLFileException(f"Error, range out of bounds {response_code} {headers} ({self._url}): {repr(ret)[:500]}") if download_range and response_code != 206: # Partial Content - raise URLFileException(f"Error, requested range but got unexpected response {response_code} {headers} ({self._url}): {repr(dats.getvalue())[:500]}") + raise URLFileException(f"Error, requested range but got unexpected response {response_code} {headers} ({self._url}): {repr(ret)[:500]}") if (not download_range) and response_code != 200: # OK - raise URLFileException(f"Error {response_code} {headers} ({self._url}): {repr(dats.getvalue())[:500]}") + raise URLFileException(f"Error {response_code} {headers} ({self._url}): {repr(ret)[:500]}") - ret = dats.getvalue() self._pos += len(ret) return ret From 51a93cf9aaa3c1bc07738a412a4e19cb3dc6dcba Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 17 Dec 2023 16:32:56 -0800 Subject: [PATCH 294/724] fix unused --- tools/lib/url_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 3fb2135c08..d055f86577 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -2,7 +2,7 @@ import os import time import threading from hashlib import sha256 -from urllib3 import PoolManager, Retry +from urllib3 import PoolManager from urllib3.util import Timeout from tenacity import retry, wait_random_exponential, stop_after_attempt From 846be323d52ea07c7b303759beb3c4c45f526c45 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 17 Dec 2023 16:37:33 -0800 Subject: [PATCH 295/724] packages cleanup, part two --- poetry.lock | 14 ++------------ pyproject.toml | 5 ++--- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/poetry.lock b/poetry.lock index 477cbac64c..2731ff1c48 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -1480,16 +1480,6 @@ all = ["flake8", "numpy", "pylint", "pytest", "pytest-cov"] numpy = ["numpy"] test = ["flake8", "pylint", "pytest", "pytest-cov"] -[[package]] -name = "hexdump" -version = "3.3" -description = "dump binary data to hex format and restore from there" -optional = false -python-versions = "*" -files = [ - {file = "hexdump-3.3.zip", hash = "sha256:d781a43b0c16ace3f9366aade73e8ad3a7bd5137d58f0b45ab2d3f54876f20db"}, -] - [[package]] name = "humanfriendly" version = "10.0" @@ -4881,4 +4871,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "26e963a3e9236b0e29466a0bca243215395a780c88485e9c17f6518969334890" +content-hash = "3ab4ab63ba35d780759581d6572a6d6359c019a1be419f3c72ff3f0ee6008095" diff --git a/pyproject.toml b/pyproject.toml index a519670d48..93dd9456a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,6 @@ cffi = "*" crcmod = "*" Cython = "*" future-fstrings = "*" # for acados -hexdump = "*" json-rpc = "*" libusb1 = "*" numpy = "*" @@ -94,7 +93,6 @@ pyaudio = "*" pycapnp = "*" pycryptodome = "*" PyJWT = "*" -pyopencl = "*" pyserial = "*" pyzmq = "*" requests = "*" @@ -105,7 +103,6 @@ sounddevice = "*" spidev = { version = "*", platform = "linux" } sympy = "*" websocket_client = "*" -polyline = "*" # these should be removed markdown-it-py = "*" @@ -135,7 +132,9 @@ natsort = "*" opencv-python-headless = "*" parameterized = "^0.8" pprofile = "*" +polyline = "*" pre-commit = "*" +pyopencl = "*" pygame = "*" pyprof2calltree = "*" pytest = "*" From ab0b26d2b8dc8b3957812856917084af2cb3df0a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 18 Dec 2023 14:42:52 +0800 Subject: [PATCH 296/724] camerad: cleanup includes (#30782) * cleanup includes * continue --------- Co-authored-by: Comma Device --- selfdrive/ui/qt/onroad.cc | 1 + system/camerad/cameras/camera_common.cc | 6 ------ system/camerad/cameras/camera_common.h | 8 +------- system/camerad/cameras/camera_qcom2.cc | 6 +----- system/camerad/cameras/camera_qcom2.h | 3 --- system/camerad/sensors/ar0231.cc | 1 + tools/cabana/streams/replaystream.cc | 2 ++ tools/replay/consoleui.cc | 1 + 8 files changed, 7 insertions(+), 21 deletions(-) diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index b4c39cafb5..4df0ed81cb 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -9,6 +9,7 @@ #include #include +#include "common/swaglog.h" #include "common/timing.h" #include "selfdrive/ui/qt/util.h" #ifdef ENABLE_MAPS diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 8943fb4cba..f74fb15bff 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -1,12 +1,7 @@ #include "system/camerad/cameras/camera_common.h" -#include - #include -#include -#include #include -#include #include "third_party/libyuv/include/libyuv.h" #include @@ -14,7 +9,6 @@ #include "common/clutil.h" #include "common/swaglog.h" #include "common/util.h" -#include "system/hardware/hw.h" #include "third_party/linux/include/msm_media_info.h" #include "system/camerad/cameras/camera_qcom2.h" diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index d56f20894a..029d69990c 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -1,18 +1,12 @@ #pragma once -#include -#include +#include #include #include #include "cereal/messaging/messaging.h" -#include "cereal/visionipc/visionbuf.h" -#include "cereal/visionipc/visionipc.h" #include "cereal/visionipc/visionipc_server.h" -#include "common/mat.h" #include "common/queue.h" -#include "common/swaglog.h" -#include "system/hardware/hw.h" const int YUV_BUFFER_COUNT = 20; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 7f2118e7e4..afb7805d3e 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -1,17 +1,12 @@ #include "system/camerad/cameras/camera_qcom2.h" -#include #include #include -#include -#include #include -#include #include #include #include -#include #include #include #include @@ -19,6 +14,7 @@ #include "media/cam_defs.h" #include "media/cam_isp.h" #include "media/cam_isp_ife.h" +#include "media/cam_req_mgr.h" #include "media/cam_sensor_cmn_header.h" #include "media/cam_sync.h" #include "common/swaglog.h" diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index d61c087f7c..47ca578b99 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -1,11 +1,8 @@ #pragma once -#include #include #include -#include - #include "system/camerad/cameras/camera_common.h" #include "system/camerad/cameras/camera_util.h" #include "system/camerad/sensors/sensor.h" diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 9a657982a8..559370ca62 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -1,5 +1,6 @@ #include +#include "common/swaglog.h" #include "system/camerad/cameras/camera_common.h" #include "system/camerad/cameras/camera_qcom2.h" #include "system/camerad/sensors/sensor.h" diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index c61c81d56f..aff0122b47 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -6,6 +6,8 @@ #include #include +#include "common/timing.h" + ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) { unsetenv("ZMQ"); setenv("COMMA_CACHE", "/tmp/comma_download_cache", 1); diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 3332aa9c90..54056c6cd5 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -7,6 +7,7 @@ #include +#include "common/util.h" #include "common/version.h" namespace { From b9080d23627849aafc4969d39df50cc615ffecdf Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 19 Dec 2023 05:54:11 +0800 Subject: [PATCH 297/724] camerad: zero memory before returning from MemoryManager::alloc (#30781) * zero memory * remove redundant memset --------- Co-authored-by: Comma Device --- system/camerad/cameras/camera_qcom2.cc | 1 - system/camerad/cameras/camera_util.cc | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index afb7805d3e..7915ad21fc 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -135,7 +135,6 @@ int CameraState::sensors_init() { buf_desc[1].size = buf_desc[1].length = 196; buf_desc[1].type = CAM_CMD_BUF_I2C; auto power_settings = mm.alloc(buf_desc[1].size, (uint32_t*)&buf_desc[1].mem_handle); - memset(power_settings.get(), 0, buf_desc[1].size); // power on struct cam_cmd_power *power = power_settings.get(); diff --git a/system/camerad/cameras/camera_util.cc b/system/camerad/cameras/camera_util.cc index b2cf4115dc..4fe38997ce 100644 --- a/system/camerad/cameras/camera_util.cc +++ b/system/camerad/cameras/camera_util.cc @@ -1,5 +1,7 @@ #include "system/camerad/cameras/camera_util.h" +#include + #include #include @@ -111,6 +113,7 @@ void *MemoryManager::alloc_buf(int size, uint32_t *handle) { size_lookup[ptr] = size; } lock.unlock(); + memset(ptr, 0, size); return ptr; } From 28eed156e0a62ab993de6d534bb102a6ede222f7 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 18 Dec 2023 14:04:27 -0800 Subject: [PATCH 298/724] Move all fingerprints to fingerprints.py (#30790) * move fingerpints * fix circular imports * fix circular imports * fix auto fingerprint * fix those tests * and controlsd * add labeler rule * no defaultdict --- .github/labeler.yaml | 4 + selfdrive/car/README.md | 5 +- selfdrive/car/body/fingerprints.py | 25 + selfdrive/car/body/values.py | 19 - selfdrive/car/chrysler/fingerprints.py | 266 +++ selfdrive/car/chrysler/values.py | 258 --- selfdrive/car/ford/fingerprints.py | 194 ++ selfdrive/car/ford/tests/test_ford.py | 3 +- selfdrive/car/ford/values.py | 190 -- selfdrive/car/fw_query_definitions.py | 6 +- selfdrive/car/fw_versions.py | 2 +- selfdrive/car/gm/fingerprints.py | 83 + selfdrive/car/gm/tests/test_gm.py | 3 +- selfdrive/car/gm/values.py | 80 - selfdrive/car/honda/fingerprints.py | 1368 ++++++++++++++ selfdrive/car/honda/tests/test_honda.py | 2 +- selfdrive/car/honda/values.py | 1362 ------------- selfdrive/car/hyundai/fingerprints.py | 1679 +++++++++++++++++ .../car/hyundai/tests/print_platform_codes.py | 3 +- selfdrive/car/hyundai/tests/test_hyundai.py | 5 +- selfdrive/car/hyundai/values.py | 1677 +--------------- selfdrive/car/interfaces.py | 7 +- selfdrive/car/mazda/fingerprints.py | 263 +++ selfdrive/car/mazda/values.py | 259 --- selfdrive/car/nissan/fingerprints.py | 116 ++ selfdrive/car/nissan/values.py | 111 -- selfdrive/car/subaru/fingerprints.py | 563 ++++++ selfdrive/car/subaru/values.py | 559 ------ selfdrive/car/tesla/fingerprints.py | 30 + selfdrive/car/tesla/values.py | 24 - selfdrive/car/tests/test_fw_fingerprint.py | 2 +- selfdrive/car/toyota/fingerprints.py | 1641 ++++++++++++++++ .../car/toyota/tests/print_platform_codes.py | 3 +- selfdrive/car/toyota/tests/test_toyota.py | 5 +- selfdrive/car/toyota/values.py | 1640 +--------------- selfdrive/car/volkswagen/fingerprints.py | 1154 +++++++++++ selfdrive/car/volkswagen/values.py | 1149 ----------- selfdrive/debug/auto_fingerprint.py | 2 +- 38 files changed, 7423 insertions(+), 7339 deletions(-) create mode 100644 selfdrive/car/body/fingerprints.py create mode 100644 selfdrive/car/chrysler/fingerprints.py create mode 100644 selfdrive/car/ford/fingerprints.py create mode 100644 selfdrive/car/gm/fingerprints.py create mode 100644 selfdrive/car/honda/fingerprints.py create mode 100644 selfdrive/car/hyundai/fingerprints.py create mode 100644 selfdrive/car/mazda/fingerprints.py create mode 100644 selfdrive/car/nissan/fingerprints.py create mode 100644 selfdrive/car/subaru/fingerprints.py create mode 100644 selfdrive/car/tesla/fingerprints.py create mode 100644 selfdrive/car/toyota/fingerprints.py create mode 100644 selfdrive/car/volkswagen/fingerprints.py diff --git a/.github/labeler.yaml b/.github/labeler.yaml index d4a17ab78c..cf7a70a3f5 100644 --- a/.github/labeler.yaml +++ b/.github/labeler.yaml @@ -43,6 +43,10 @@ volkswagen: - all: - changed-files: ['selfdrive/car/volkswagen/*'] +fingerprint: + - all: + - changed-files: ['selfdrive/car/*/fingerprints.py'] + simulation: - all: - changed-files: ['tools/sim/**'] diff --git a/selfdrive/car/README.md b/selfdrive/car/README.md index b64472b2bc..f77bea205b 100644 --- a/selfdrive/car/README.md +++ b/selfdrive/car/README.md @@ -63,6 +63,9 @@ AssertionError: 1 is not false : panda safety doesn't agree with openpilot: {'ga ### interface.py Generic interface to send and receive messages from CAN (controlsd uses this to communicate with car) +### fingerprints.py +Fingerprints for matching to a specific car + ### carcontroller.py Builds CAN messages to send to car @@ -70,7 +73,7 @@ Builds CAN messages to send to car Reads CAN from car and builds openpilot CarState message ##### values.py -Fingerprints, limits for actuation, and supported car documentation +Limits for actuation, general constants for cars, and supported car documentation ##### radar_interface.py Interface for parsing radar points from the car diff --git a/selfdrive/car/body/fingerprints.py b/selfdrive/car/body/fingerprints.py new file mode 100644 index 0000000000..620967acac --- /dev/null +++ b/selfdrive/car/body/fingerprints.py @@ -0,0 +1,25 @@ +from cereal import car +from openpilot.selfdrive.car.body.values import CAR + +Ecu = car.CarParams.Ecu + +FINGERPRINTS = { + CAR.BODY: [{ + 513: 8, 516: 8, 514: 3, 515: 4, + }], +} + +FW_VERSIONS = { + CAR.BODY: { + (Ecu.engine, 0x720, None): [ + b'0.0.01', + b'02/27/2022', + b'0.3.00a', + ], + # git hash of the firmware used + (Ecu.debug, 0x721, None): [ + b'166bd860', + b'dc780f85', + ], + }, +} diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py index ce811ad221..33119bf0fd 100644 --- a/selfdrive/car/body/values.py +++ b/selfdrive/car/body/values.py @@ -30,11 +30,6 @@ CAR_INFO: Dict[str, CarInfo] = { CAR.BODY: CarInfo("comma body", package="All"), } -FINGERPRINTS = { - CAR.BODY: [{ - 513: 8, 516: 8, 514: 3, 515: 4, - }], -} FW_QUERY_CONFIG = FwQueryConfig( requests=[ @@ -46,20 +41,6 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) -FW_VERSIONS = { - CAR.BODY: { - (Ecu.engine, 0x720, None): [ - b'0.0.01', - b'02/27/2022', - b'0.3.00a', - ], - # git hash of the firmware used - (Ecu.debug, 0x721, None): [ - b'166bd860', - b'dc780f85', - ], - }, -} DBC = { CAR.BODY: dbc_dict('comma_body', None), diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py new file mode 100644 index 0000000000..fb11936f77 --- /dev/null +++ b/selfdrive/car/chrysler/fingerprints.py @@ -0,0 +1,266 @@ +# ruff: noqa: E501 +from cereal import car +from openpilot.selfdrive.car.chrysler.values import CAR + +Ecu = car.CarParams.Ecu + +# Unique CAN messages: +# Only the hybrids have 270: 8 +# Only the gas have 55: 8, 416: 7 +# For 564, All 2017 have length 4, whereas 2018-19 have length 8. +# For 924, Pacifica 2017 has length 3, whereas all 2018-19 have length 8. +# For 560, All 2019 have length 8, whereas all 2017-18 have length 4. + +# Jeep Grand Cherokee unique messages: +# 2017 Trailhawk: 618: 8 +# For 924, Trailhawk 2017 has length 3, whereas 2018 V6 has length 8. + + +FINGERPRINTS = { + CAR.PACIFICA_2017_HYBRID: [{ + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 840: 8, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 3, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1892: 8, 2016: 8, 2024: 8 + }], + CAR.PACIFICA_2018: [{ + 55: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 8, 926: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 + }, + { + 55: 8, 58: 6, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 926: 3, 937: 8, 947: 8, 948: 8, 956: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 + }], + CAR.PACIFICA_2020: [{ + 55: 8, 179: 8, 181: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 536: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 776: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 886: 8, 897: 8, 906: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 7, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1543: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 2015: 8, 2016: 8, 2017:8, 2024: 8, 2025: 8 + }], + CAR.PACIFICA_2018_HYBRID: [{ + 68: 8, 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8 + }, + # based on 9ae7821dc4e92455|2019-07-01--16-42-55 + { + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2016: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 + }], + CAR.PACIFICA_2019_HYBRID: [{ + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1538: 8 + }, + # Based on 0607d2516fc2148f|2019-02-13--23-03-16 + { + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1537: 8 + }, + # Based on 3c7ce223e3571b54|2019-05-11--20-16-14 + { + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1562: 8, 1570: 8 + }, + # Based on "8190c7275a24557b|2020-02-24--09-57-23" + { + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1536: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2015: 8, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 + }, + { + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 450: 8, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 2018: 8, 2020: 8, 2026: 8, 2028: 8 + }], + CAR.JEEP_GRAND_CHEROKEE: [{ + 55: 8, 168: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 975: 8, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1543: 8, 1562: 8, 1576: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }, + # Based on c88f65eeaee4003a|2022-08-04--15-37-16 + { + 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 658: 6, 660: 8, 671: 8, 672: 8, 678: 8, 680: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 783: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 844: 5, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1235: 8, 1242: 8, 1252: 8, 1792: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1892: 8, 1927: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }], + CAR.JEEP_GRAND_CHEROKEE_2019: [{ + # Jeep Grand Cherokee 2019, including most 2020 models + 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1538: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }], +} + + +FW_VERSIONS = { + CAR.JEEP_GRAND_CHEROKEE_2019: { + (Ecu.combinationMeter, 0x742, None): [ + b'68402971AD', + ], + (Ecu.srs, 0x744, None): [ + b'68355363AB', + ], + (Ecu.abs, 0x747, None): [ + b'68408639AD', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'68456722AC', + ], + (Ecu.eps, 0x75A, None): [ + b'68453431AA', + ], + (Ecu.engine, 0x7e0, None): [ + b'05035674AB ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'05035707AA', + ], + }, + + CAR.RAM_1500: { + (Ecu.combinationMeter, 0x742, None): [ + b'68294051AG', + b'68294051AI', + b'68294052AG', + b'68294063AG', + b'68294063AH', + b'68294063AI', + b'68434846AC', + b'68434858AC', + b'68434860AC', + b'68453503AC', + b'68453505AC', + b'68453511AC', + b'68453513AD', + b'68453514AD', + b'68510280AG', + b'68510283AG', + b'68527346AE', + b'68527375AD', + b'68527382AE', + ], + (Ecu.srs, 0x744, None): [ + b'68428609AB', + b'68441329AB', + b'68473844AB', + b'68490898AA', + b'68500728AA', + b'68615033AA', + b'68615034AA', + ], + (Ecu.abs, 0x747, None): [ + b'68292406AH', + b'68432418AB', + b'68432418AD', + b'68436004AD', + b'68436004AE', + b'68438454AC', + b'68438454AD', + b'68438456AE', + b'68438456AF', + b'68535469AB', + b'68535470AC', + b'68548900AB', + b'68586307AB', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'04672892AB', + b'04672932AB', + b'04672932AC', + b'22DTRHD_AA', + b'68320950AH', + b'68320950AI', + b'68320950AJ', + b'68320950AL', + b'68320950AM', + b'68454268AB', + b'68475160AE', + b'68475160AF', + b'68475160AG', + ], + (Ecu.eps, 0x75A, None): [ + b'21590101AA', + b'21590101AB', + b'68273275AF', + b'68273275AG', + b'68273275AH', + b'68312176AE', + b'68312176AG', + b'68440789AC', + b'68466110AB', + b'68469901AA', + b'68522583AB', + b'68522585AB', + b'68552788AA', + b'68552789AA', + b'68552790AA', + b'68585106AB', + b'68585109AB', + b'68585112AB', + ], + (Ecu.engine, 0x7e0, None): [ + b'05036065AE ', + b'05036066AE ', + b'05149592AE ', + b'05149591AD ', + b'05149846AA ', + b'05149848AA ', + b'68378701AI ', + b'68378748AL ', + b'68378758AM ', + b'68448163AJ', + b'68448165AK', + b'68500630AD', + b'68500630AE', + b'68539650AD', + ], + (Ecu.transmission, 0x7e1, None): [ + b'05149536AC', + b'68360078AL', + b'68360080AM', + b'68360081AM', + b'68360085AL', + b'68384328AD', + b'68384332AD', + b'68445533AB', + b'68484467AC', + b'68502994AD', + b'68520867AE', + b'68540431AB', + ], + }, + + CAR.RAM_HD: { + (Ecu.combinationMeter, 0x742, None): [ + b'68361606AH', + b'68437735AC', + b'68492693AD', + b'68525485AB', + b'68525487AB', + b'68525498AB', + b'68528791AF', + b'68628474AB', + ], + (Ecu.srs, 0x744, None): [ + b'68399794AC', + b'68428503AA', + b'68428505AA', + b'68428507AA', + ], + (Ecu.abs, 0x747, None): [ + b'68334977AH', + b'68455481AC', + b'68504022AA', + b'68504022AB', + b'68504022AC', + b'68530686AB', + b'68530686AC', + b'68544596AC', + b'68641704AA', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'04672895AB', + b'04672934AB', + b'56029827AG', + b'56029827AH', + b'68462657AE', + b'68484694AD', + b'68484694AE', + b'68615489AB', + ], + (Ecu.eps, 0x761, None): [ + b'68421036AC', + b'68507906AB', + b'68534023AC', + ], + (Ecu.engine, 0x7e0, None): [ + b'52370131AF', + b'52370231AF', + b'52370231AG', + b'52370491AA', + b'52370931CT', + b'52401032AE', + b'52421132AF', + b'52421332AF', + b'68527616AD ', + b'M2370131MB', + b'M2421132MB', + ], + }, +} diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index cb1aa4a880..055efba829 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -1,4 +1,3 @@ -# ruff: noqa: E501 from enum import IntFlag, StrEnum from dataclasses import dataclass, field from typing import Dict, List, Optional, Union @@ -82,67 +81,6 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { ], } -# Unique CAN messages: -# Only the hybrids have 270: 8 -# Only the gas have 55: 8, 416: 7 -# For 564, All 2017 have length 4, whereas 2018-19 have length 8. -# For 924, Pacifica 2017 has length 3, whereas all 2018-19 have length 8. -# For 560, All 2019 have length 8, whereas all 2017-18 have length 4. - -# Jeep Grand Cherokee unique messages: -# 2017 Trailhawk: 618: 8 -# For 924, Trailhawk 2017 has length 3, whereas 2018 V6 has length 8. - -FINGERPRINTS = { - CAR.PACIFICA_2017_HYBRID: [{ - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 840: 8, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 3, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1892: 8, 2016: 8, 2024: 8 - }], - CAR.PACIFICA_2018: [{ - 55: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 8, 926: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 - }, - { - 55: 8, 58: 6, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 926: 3, 937: 8, 947: 8, 948: 8, 956: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 - }], - CAR.PACIFICA_2020: [{ - 55: 8, 179: 8, 181: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 536: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 776: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 886: 8, 897: 8, 906: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 7, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1543: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 2015: 8, 2016: 8, 2017:8, 2024: 8, 2025: 8 - }], - CAR.PACIFICA_2018_HYBRID: [{ - 68: 8, 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8 - }, - # based on 9ae7821dc4e92455|2019-07-01--16-42-55 - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2016: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 - }], - CAR.PACIFICA_2019_HYBRID: [{ - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1538: 8 - }, - # Based on 0607d2516fc2148f|2019-02-13--23-03-16 - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1537: 8 - }, - # Based on 3c7ce223e3571b54|2019-05-11--20-16-14 - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1562: 8, 1570: 8 - }, - # Based on "8190c7275a24557b|2020-02-24--09-57-23" - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1536: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2015: 8, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 - }, - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 450: 8, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 2018: 8, 2020: 8, 2026: 8, 2028: 8 - }], - CAR.JEEP_GRAND_CHEROKEE: [{ - 55: 8, 168: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 975: 8, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1543: 8, 1562: 8, 1576: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }, - # Based on c88f65eeaee4003a|2022-08-04--15-37-16 - { - 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 658: 6, 660: 8, 671: 8, 672: 8, 678: 8, 680: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 783: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 844: 5, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1235: 8, 1242: 8, 1252: 8, 1792: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1892: 8, 1927: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], - CAR.JEEP_GRAND_CHEROKEE_2019: [{ - # Jeep Grand Cherokee 2019, including most 2020 models - 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1538: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], -} CHRYSLER_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(0xf132) @@ -184,202 +122,6 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) -FW_VERSIONS = { - CAR.JEEP_GRAND_CHEROKEE_2019: { - (Ecu.combinationMeter, 0x742, None): [ - b'68402971AD', - ], - (Ecu.srs, 0x744, None): [ - b'68355363AB', - ], - (Ecu.abs, 0x747, None): [ - b'68408639AD', - ], - (Ecu.fwdRadar, 0x753, None): [ - b'68456722AC', - ], - (Ecu.eps, 0x75A, None): [ - b'68453431AA', - ], - (Ecu.engine, 0x7e0, None): [ - b'05035674AB ', - ], - (Ecu.transmission, 0x7e1, None): [ - b'05035707AA', - ], - }, - - CAR.RAM_1500: { - (Ecu.combinationMeter, 0x742, None): [ - b'68294051AG', - b'68294051AI', - b'68294052AG', - b'68294063AG', - b'68294063AH', - b'68294063AI', - b'68434846AC', - b'68434858AC', - b'68434860AC', - b'68453503AC', - b'68453505AC', - b'68453511AC', - b'68453513AD', - b'68453514AD', - b'68510280AG', - b'68510283AG', - b'68527346AE', - b'68527375AD', - b'68527382AE', - ], - (Ecu.srs, 0x744, None): [ - b'68428609AB', - b'68441329AB', - b'68473844AB', - b'68490898AA', - b'68500728AA', - b'68615033AA', - b'68615034AA', - ], - (Ecu.abs, 0x747, None): [ - b'68292406AH', - b'68432418AB', - b'68432418AD', - b'68436004AD', - b'68436004AE', - b'68438454AC', - b'68438454AD', - b'68438456AE', - b'68438456AF', - b'68535469AB', - b'68535470AC', - b'68548900AB', - b'68586307AB', - ], - (Ecu.fwdRadar, 0x753, None): [ - b'04672892AB', - b'04672932AB', - b'04672932AC', - b'22DTRHD_AA', - b'68320950AH', - b'68320950AI', - b'68320950AJ', - b'68320950AL', - b'68320950AM', - b'68454268AB', - b'68475160AE', - b'68475160AF', - b'68475160AG', - ], - (Ecu.eps, 0x75A, None): [ - b'21590101AA', - b'21590101AB', - b'68273275AF', - b'68273275AG', - b'68273275AH', - b'68312176AE', - b'68312176AG', - b'68440789AC', - b'68466110AB', - b'68469901AA', - b'68522583AB', - b'68522585AB', - b'68552788AA', - b'68552789AA', - b'68552790AA', - b'68585106AB', - b'68585109AB', - b'68585112AB', - ], - (Ecu.engine, 0x7e0, None): [ - b'05036065AE ', - b'05036066AE ', - b'05149592AE ', - b'05149591AD ', - b'05149846AA ', - b'05149848AA ', - b'68378701AI ', - b'68378748AL ', - b'68378758AM ', - b'68448163AJ', - b'68448165AK', - b'68500630AD', - b'68500630AE', - b'68539650AD', - ], - (Ecu.transmission, 0x7e1, None): [ - b'05149536AC', - b'68360078AL', - b'68360080AM', - b'68360081AM', - b'68360085AL', - b'68384328AD', - b'68384332AD', - b'68445533AB', - b'68484467AC', - b'68502994AD', - b'68520867AE', - b'68540431AB', - ], - }, - - CAR.RAM_HD: { - (Ecu.combinationMeter, 0x742, None): [ - b'68361606AH', - b'68437735AC', - b'68492693AD', - b'68525485AB', - b'68525487AB', - b'68525498AB', - b'68528791AF', - b'68628474AB', - ], - (Ecu.srs, 0x744, None): [ - b'68399794AC', - b'68428503AA', - b'68428505AA', - b'68428507AA', - ], - (Ecu.abs, 0x747, None): [ - b'68334977AH', - b'68455481AC', - b'68504022AA', - b'68504022AB', - b'68504022AC', - b'68530686AB', - b'68530686AC', - b'68544596AC', - b'68641704AA', - ], - (Ecu.fwdRadar, 0x753, None): [ - b'04672895AB', - b'04672934AB', - b'56029827AG', - b'56029827AH', - b'68462657AE', - b'68484694AD', - b'68484694AE', - b'68615489AB', - ], - (Ecu.eps, 0x761, None): [ - b'68421036AC', - b'68507906AB', - b'68534023AC', - ], - (Ecu.engine, 0x7e0, None): [ - b'52370131AF', - b'52370231AF', - b'52370231AG', - b'52370491AA', - b'52370931CT', - b'52401032AE', - b'52421132AF', - b'52421332AF', - b'68527616AD ', - b'M2370131MB', - b'M2421132MB', - ], - }, -} DBC = { CAR.PACIFICA_2017_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py new file mode 100644 index 0000000000..d26c3eac0a --- /dev/null +++ b/selfdrive/car/ford/fingerprints.py @@ -0,0 +1,194 @@ +from cereal import car +from openpilot.selfdrive.car.ford.values import CAR + +Ecu = car.CarParams.Ecu + +FW_VERSIONS = { + CAR.BRONCO_SPORT_MK1: { + (Ecu.eps, 0x730, None): [ + b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'LX6C-2D053-RD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'M1PA-14C204-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.ESCAPE_MK4: { + (Ecu.eps, 0x730, None): [ + b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-NT\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-NY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-SA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-SD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LV4T-14F397-GG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6A-14C204-BJX\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6A-14C204-CNG\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6A-14C204-DPK\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MX6A-14C204-BEJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MX6A-14C204-CAB\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NX6A-14C204-BLE\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.EXPLORER_MK6: { + (Ecu.eps, 0x730, None): [ + b'L1MC-14D003-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'M1MC-14D003-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'P1MC-14D003-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'L1MC-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'LB5T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LC5T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'LB5A-14C204-ATJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5A-14C204-AUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5A-14C204-AZL\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5A-14C204-BUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PB5A-14C204-DA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5A-14C204-ATS\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.F_150_MK14: { + (Ecu.eps, 0x730, None): [ + b'ML3V-14D003-BC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'PL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'PL3A-14C204-BRB\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.F_150_LIGHTNING_MK1: { + (Ecu.abs, 0x760, None): [ + b'PL38-2D053-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'ML3T-14H102-ABT\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'NL3A-14C204-BAR\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.MUSTANG_MACH_E_MK1: { + (Ecu.eps, 0x730, None): [ + b'LJ9C-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LJ9C-14D003-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'LK9C-2D053-CK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MJ98-14C204-BBS\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.FOCUS_MK4: { + (Ecu.eps, 0x730, None): [ + b'JX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'JX61-2D053-CJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'JX7T-14D049-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'JX7T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.MAVERICK_MK1: { + (Ecu.eps, 0x730, None): [ + b'NZ6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ6C-2D053-ED\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ6C-2D053-EE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NZ6A-14C204-ZC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ6A-14C204-BE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ6A-14C204-JC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ6A-14C204-JE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, +} diff --git a/selfdrive/car/ford/tests/test_ford.py b/selfdrive/car/ford/tests/test_ford.py index e645b4cbe6..a1f0b81a58 100755 --- a/selfdrive/car/ford/tests/test_ford.py +++ b/selfdrive/car/ford/tests/test_ford.py @@ -6,7 +6,8 @@ from typing import Dict, Iterable, Optional, Tuple import capnp from cereal import car -from openpilot.selfdrive.car.ford.values import FW_QUERY_CONFIG, FW_VERSIONS +from openpilot.selfdrive.car.ford.values import FW_QUERY_CONFIG +from openpilot.selfdrive.car.ford.fingerprints import FW_VERSIONS Ecu = car.CarParams.Ecu diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 4b05d474f7..2c4415be2b 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -129,193 +129,3 @@ FW_QUERY_CONFIG = FwQueryConfig( (Ecu.shiftByWire, 0x732, None), ], ) - -FW_VERSIONS = { - CAR.BRONCO_SPORT_MK1: { - (Ecu.eps, 0x730, None): [ - b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'LX6C-2D053-RD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6C-2D053-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7E0, None): [ - b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'M1PA-14C204-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.ESCAPE_MK4: { - (Ecu.eps, 0x730, None): [ - b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6C-2D053-NT\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6C-2D053-NY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6C-2D053-SA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6C-2D053-SD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LV4T-14F397-GG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7E0, None): [ - b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6A-14C204-BJX\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6A-14C204-CNG\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6A-14C204-DPK\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MX6A-14C204-BEJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MX6A-14C204-CAB\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NX6A-14C204-BLE\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.EXPLORER_MK6: { - (Ecu.eps, 0x730, None): [ - b'L1MC-14D003-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'M1MC-14D003-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'P1MC-14D003-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'L1MC-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MC-2D053-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'LB5T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LC5T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7E0, None): [ - b'LB5A-14C204-ATJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-AUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-AZL\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-BUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PB5A-14C204-DA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-ATS\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.F_150_MK14: { - (Ecu.eps, 0x730, None): [ - b'ML3V-14D003-BC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'PL34-2D053-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7E0, None): [ - b'PL3A-14C204-BRB\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.F_150_LIGHTNING_MK1: { - (Ecu.abs, 0x760, None): [ - b'PL38-2D053-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'ML3T-14H102-ABT\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7E0, None): [ - b'NL3A-14C204-BAR\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.MUSTANG_MACH_E_MK1: { - (Ecu.eps, 0x730, None): [ - b'LJ9C-14D003-AM\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LJ9C-14D003-CC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'LK9C-2D053-CK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7E0, None): [ - b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MJ98-14C204-BBS\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.FOCUS_MK4: { - (Ecu.eps, 0x730, None): [ - b'JX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'JX61-2D053-CJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'JX7T-14D049-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'JX7T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7E0, None): [ - b'JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.MAVERICK_MK1: { - (Ecu.eps, 0x730, None): [ - b'NZ6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ6C-2D053-ED\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ6C-2D053-EE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7E0, None): [ - b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NZ6A-14C204-ZC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ6A-14C204-BE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ6A-14C204-JC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ6A-14C204-JE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, -} diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index 1caedcd321..4ffb087bdc 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -9,6 +9,10 @@ import panda.python.uds as uds AddrType = Tuple[int, Optional[int]] EcuAddrBusType = Tuple[int, Optional[int], int] +EcuAddrSubAddr = Tuple[int, int, Optional[int]] + +LIVE_FW_VERSIONS = Dict[AddrType, Set[bytes]] +OFFLINE_FW_VERSIONS = Dict[str, Dict[EcuAddrSubAddr, List[bytes]]] def p16(val): @@ -79,7 +83,7 @@ class FwQueryConfig: extra_ecus: List[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]]] = field(default_factory=list) # Function a brand can implement to provide better fuzzy matching. Takes in FW versions, # returns set of candidates. Only will match if one candidate is returned - match_fw_to_car_fuzzy: Optional[Callable[[Dict[AddrType, Set[bytes]]], Set[str]]] = None + match_fw_to_car_fuzzy: Optional[Callable[[LIVE_FW_VERSIONS, OFFLINE_FW_VERSIONS], Set[str]]] = None def __post_init__(self): for i in range(len(self.requests)): diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index f50c604316..a4c8db6797 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -159,7 +159,7 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True): # If specified and no matches so far, fall back to brand's fuzzy fingerprinting function config = FW_QUERY_CONFIGS[brand] if not exact_match and not len(matches) and config.match_fw_to_car_fuzzy is not None: - matches |= config.match_fw_to_car_fuzzy(fw_versions_dict) + matches |= config.match_fw_to_car_fuzzy(fw_versions_dict, VERSIONS[brand]) if len(matches): return exact_match, matches diff --git a/selfdrive/car/gm/fingerprints.py b/selfdrive/car/gm/fingerprints.py new file mode 100644 index 0000000000..8fabb71003 --- /dev/null +++ b/selfdrive/car/gm/fingerprints.py @@ -0,0 +1,83 @@ +# ruff: noqa: E501 +from openpilot.selfdrive.car.gm.values import CAR + + +FINGERPRINTS = { + CAR.HOLDEN_ASTRA: [ + # Astra BK MY17, ASCM unplugged + { + 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 401: 8, 413: 8, 417: 8, 419: 8, 422: 1, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 458: 5, 479: 8, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 647: 5, 707: 8, 715: 8, 723: 8, 753: 5, 761: 7, 806: 1, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1009: 8, 1011: 6, 1017: 8, 1019: 3, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 8, 1280: 4, 1300: 8, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1908: 7, 1912: 7, 1919: 7, + }], + CAR.VOLT: [ + # Volt Premier w/ ACC 2017 + { + 170: 8, 171: 8, 189: 7, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 289: 8, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 577: 8, 647: 3, 707: 8, 711: 6, 715: 8, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8 + }, + # Volt Premier w/ ACC 2018 + { + 170: 8, 171: 8, 189: 7, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 577: 8, 578: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 715: 8, 717: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1516: 8, 1601: 8, 1618: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1930: 7, 2016: 8, 2018: 8, 2020: 8, 2024: 8, 2028: 8 + }, + # Volt Premier 2018 w/ flashed firmware, no radar + { + 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 368: 8, 381: 2, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 5, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 4, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 761: 7, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 5, 844: 8, 853: 8, 866: 4, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7 + }], + CAR.BUICK_LACROSSE: [ + # LaCrosse Premium AWD 2017 + { + 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 + }], + CAR.BUICK_REGAL : [ + # Regal TourX Essence w/ ACC 2018 + { + 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 8, 419: 8, 422: 4, 426: 8, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 463: 3, 479: 8, 481: 7, 485: 8, 487: 8, 489: 8, 495: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 578: 8, 579: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 884: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 8, 969: 8, 977: 8, 979: 8, 985: 8, 1001: 8, 1005: 6, 1009: 8, 1011: 8, 1013: 3, 1017: 8, 1020: 8, 1024: 8, 1025: 8, 1026: 8, 1027: 8, 1028: 8, 1029: 8, 1030: 8, 1031: 8, 1032: 2, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 8, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 8, 1263: 8, 1265: 8, 1267: 8, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1603: 7, 1611: 8, 1618: 8, 1906: 8, 1907: 7, 1912: 7, 1914: 7, 1916: 7, 1919: 7, 1930: 7, 2016: 8, 2018: 8, 2019: 8, 2024: 8, 2026: 8 + }], + CAR.CADILLAC_ATS: [ + # Cadillac ATS Coupe Premium Performance 3.6L RWD w/ ACC 2018 + { + 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 368: 3, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 401: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 462: 4, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 491: 2, 493: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 723: 2, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1233: 8, 1241: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1916: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 + }], + CAR.MALIBU: [ + # Malibu Premier w/ ACC 2017 + { + 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7, 1930: 7, 2016: 8, 2024: 8, + }], + CAR.ACADIA: [ + # Acadia Denali w/ACC 2018 + { + 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 1, 290: 1, 298: 8, 304: 8, 309: 8, 313: 8, 320: 8, 322: 7, 328: 1, 352: 7, 368: 8, 381: 8, 384: 8, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 458: 8, 460: 4, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 5, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 512: 3, 530: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 568: 2, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 801: 8, 803: 8, 804: 3, 805: 8, 832: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7 + }, + # Acadia Denali w/ /ACC 2018 + { + 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 338: 6, 340: 6, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 + }], + CAR.ESCALADE: [ + { + 170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 4, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 + }], + CAR.ESCALADE_ESV: [ + { + 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 + }], + CAR.ESCALADE_ESV_2019: [ + { + 715: 8, 840: 5, 717: 5, 869: 4, 880: 6, 289: 8, 454: 8, 842: 5, 460: 5, 463: 3, 801: 8, 170: 8, 190: 6, 241: 6, 201: 8, 417: 7, 211: 2, 419: 1, 398: 8, 426: 7, 487: 8, 442: 8, 451: 8, 452: 8, 453: 6, 479: 3, 311: 8, 500: 6, 647: 6, 193: 8, 707: 8, 197: 8, 209: 7, 199: 4, 455: 7, 313: 8, 481: 7, 485: 8, 489: 8, 249: 8, 393: 7, 407: 7, 413: 8, 422: 4, 431: 8, 501: 8, 499: 3, 810: 8, 508: 8, 381: 8, 462: 4, 532: 6, 562: 8, 386: 8, 761: 7, 573: 1, 554: 3, 719: 5, 560: 8, 1279: 4, 388: 8, 288: 5, 1005: 6, 497: 8, 844: 8, 961: 8, 967: 4, 977: 8, 979: 8, 985: 5, 1001: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 510: 8, 866: 4, 304: 1, 969: 8, 384: 4, 1033: 7, 1009: 8, 1034: 7, 1296: 4, 1930: 7, 1105: 5, 1013: 5, 1225: 7, 1919: 7, 320: 3, 534: 2, 352: 5, 298: 8, 1223: 2, 1233: 8, 608: 8, 1265: 8, 609: 6, 1267: 1, 1417: 8, 610: 6, 1906: 7, 611: 6, 612: 8, 613: 8, 208: 8, 564: 5, 309: 8, 1221: 5, 1280: 4, 1249: 8, 1907: 7, 1257: 6, 1300: 8, 1920: 7, 563: 5, 1322: 6, 1323: 4, 1328: 4, 1917: 7, 328: 1, 1912: 7, 1914: 7, 804: 3, 1918: 7 + }], + CAR.BOLT_EUV: [ + { + 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 + }], + CAR.SILVERADO: [ + { + 190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 + }], + CAR.EQUINOX: [ + { + 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 + }], + # Trailblazer also matches as a Silverado, so comment out to avoid conflicts. + # TODO: split with FW versions + # CAR.TRAILBLAZER: [ + # { + # 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1609: 8, 1611: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8 + # }], +} diff --git a/selfdrive/car/gm/tests/test_gm.py b/selfdrive/car/gm/tests/test_gm.py index fce5fea720..2aea5b231b 100755 --- a/selfdrive/car/gm/tests/test_gm.py +++ b/selfdrive/car/gm/tests/test_gm.py @@ -2,7 +2,8 @@ from parameterized import parameterized import unittest -from openpilot.selfdrive.car.gm.values import CAMERA_ACC_CAR, CAR, FINGERPRINTS, GM_RX_OFFSET +from openpilot.selfdrive.car.gm.fingerprints import FINGERPRINTS +from openpilot.selfdrive.car.gm.values import CAMERA_ACC_CAR, CAR, GM_RX_OFFSET CAMERA_DIAGNOSTIC_ADDRESS = 0x24b diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index e9ec8de9c5..2a8577164f 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -1,4 +1,3 @@ -# ruff: noqa: E501 from collections import defaultdict from dataclasses import dataclass from enum import Enum, StrEnum @@ -144,85 +143,6 @@ class CanBus: LOOPBACK = 128 DROPPED = 192 -FINGERPRINTS = { - CAR.HOLDEN_ASTRA: [ - # Astra BK MY17, ASCM unplugged - { - 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 401: 8, 413: 8, 417: 8, 419: 8, 422: 1, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 458: 5, 479: 8, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 647: 5, 707: 8, 715: 8, 723: 8, 753: 5, 761: 7, 806: 1, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1009: 8, 1011: 6, 1017: 8, 1019: 3, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 8, 1280: 4, 1300: 8, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1908: 7, 1912: 7, 1919: 7, - }], - CAR.VOLT: [ - # Volt Premier w/ ACC 2017 - { - 170: 8, 171: 8, 189: 7, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 289: 8, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 577: 8, 647: 3, 707: 8, 711: 6, 715: 8, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8 - }, - # Volt Premier w/ ACC 2018 - { - 170: 8, 171: 8, 189: 7, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 577: 8, 578: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 715: 8, 717: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1516: 8, 1601: 8, 1618: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1930: 7, 2016: 8, 2018: 8, 2020: 8, 2024: 8, 2028: 8 - }, - # Volt Premier 2018 w/ flashed firmware, no radar - { - 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 368: 8, 381: 2, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 5, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 4, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 761: 7, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 5, 844: 8, 853: 8, 866: 4, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7 - }], - CAR.BUICK_LACROSSE: [ - # LaCrosse Premium AWD 2017 - { - 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 - }], - CAR.BUICK_REGAL : [ - # Regal TourX Essence w/ ACC 2018 - { - 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 8, 419: 8, 422: 4, 426: 8, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 463: 3, 479: 8, 481: 7, 485: 8, 487: 8, 489: 8, 495: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 578: 8, 579: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 884: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 8, 969: 8, 977: 8, 979: 8, 985: 8, 1001: 8, 1005: 6, 1009: 8, 1011: 8, 1013: 3, 1017: 8, 1020: 8, 1024: 8, 1025: 8, 1026: 8, 1027: 8, 1028: 8, 1029: 8, 1030: 8, 1031: 8, 1032: 2, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 8, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 8, 1263: 8, 1265: 8, 1267: 8, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1603: 7, 1611: 8, 1618: 8, 1906: 8, 1907: 7, 1912: 7, 1914: 7, 1916: 7, 1919: 7, 1930: 7, 2016: 8, 2018: 8, 2019: 8, 2024: 8, 2026: 8 - }], - CAR.CADILLAC_ATS: [ - # Cadillac ATS Coupe Premium Performance 3.6L RWD w/ ACC 2018 - { - 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 368: 3, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 401: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 462: 4, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 491: 2, 493: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 723: 2, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1233: 8, 1241: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1916: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 - }], - CAR.MALIBU: [ - # Malibu Premier w/ ACC 2017 - { - 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7, 1930: 7, 2016: 8, 2024: 8, - }], - CAR.ACADIA: [ - # Acadia Denali w/ACC 2018 - { - 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 1, 290: 1, 298: 8, 304: 8, 309: 8, 313: 8, 320: 8, 322: 7, 328: 1, 352: 7, 368: 8, 381: 8, 384: 8, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 458: 8, 460: 4, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 5, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 512: 3, 530: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 568: 2, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 801: 8, 803: 8, 804: 3, 805: 8, 832: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7 - }, - # Acadia Denali w/ /ACC 2018 - { - 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 338: 6, 340: 6, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 - }], - CAR.ESCALADE: [ - { - 170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 4, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 - }], - CAR.ESCALADE_ESV: [ - { - 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 - }], - CAR.ESCALADE_ESV_2019: [ - { - 715: 8, 840: 5, 717: 5, 869: 4, 880: 6, 289: 8, 454: 8, 842: 5, 460: 5, 463: 3, 801: 8, 170: 8, 190: 6, 241: 6, 201: 8, 417: 7, 211: 2, 419: 1, 398: 8, 426: 7, 487: 8, 442: 8, 451: 8, 452: 8, 453: 6, 479: 3, 311: 8, 500: 6, 647: 6, 193: 8, 707: 8, 197: 8, 209: 7, 199: 4, 455: 7, 313: 8, 481: 7, 485: 8, 489: 8, 249: 8, 393: 7, 407: 7, 413: 8, 422: 4, 431: 8, 501: 8, 499: 3, 810: 8, 508: 8, 381: 8, 462: 4, 532: 6, 562: 8, 386: 8, 761: 7, 573: 1, 554: 3, 719: 5, 560: 8, 1279: 4, 388: 8, 288: 5, 1005: 6, 497: 8, 844: 8, 961: 8, 967: 4, 977: 8, 979: 8, 985: 5, 1001: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 510: 8, 866: 4, 304: 1, 969: 8, 384: 4, 1033: 7, 1009: 8, 1034: 7, 1296: 4, 1930: 7, 1105: 5, 1013: 5, 1225: 7, 1919: 7, 320: 3, 534: 2, 352: 5, 298: 8, 1223: 2, 1233: 8, 608: 8, 1265: 8, 609: 6, 1267: 1, 1417: 8, 610: 6, 1906: 7, 611: 6, 612: 8, 613: 8, 208: 8, 564: 5, 309: 8, 1221: 5, 1280: 4, 1249: 8, 1907: 7, 1257: 6, 1300: 8, 1920: 7, 563: 5, 1322: 6, 1323: 4, 1328: 4, 1917: 7, 328: 1, 1912: 7, 1914: 7, 804: 3, 1918: 7 - }], - CAR.BOLT_EUV: [ - { - 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 - }], - CAR.SILVERADO: [ - { - 190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 - }], - CAR.EQUINOX: [ - { - 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 - }], - # Trailblazer also matches as a Silverado, so comment out to avoid conflicts. - # TODO: split with FW versions - # CAR.TRAILBLAZER: [ - # { - # 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1609: 8, 1611: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8 - # }], -} GM_RX_OFFSET = 0x400 diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py new file mode 100644 index 0000000000..467ff30197 --- /dev/null +++ b/selfdrive/car/honda/fingerprints.py @@ -0,0 +1,1368 @@ + +from cereal import car +from openpilot.selfdrive.car.honda.values import CAR + +Ecu = car.CarParams.Ecu + +FW_VERSIONS = { + CAR.ACCORD: { + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-6A0-8720\x00\x00', + b'37805-6A0-9520\x00\x00', + b'37805-6A0-9620\x00\x00', + b'37805-6A0-9720\x00\x00', + b'37805-6A0-A540\x00\x00', + b'37805-6A0-A550\x00\x00', + b'37805-6A0-A640\x00\x00', + b'37805-6A0-A650\x00\x00', + b'37805-6A0-A740\x00\x00', + b'37805-6A0-A750\x00\x00', + b'37805-6A0-A840\x00\x00', + b'37805-6A0-A850\x00\x00', + b'37805-6A0-A930\x00\x00', + b'37805-6A0-AF30\x00\x00', + b'37805-6A0-AG30\x00\x00', + b'37805-6B2-C520\x00\x00', + b'37805-6A0-C540\x00\x00', + b'37805-6A1-H650\x00\x00', + b'37805-6B2-A550\x00\x00', + b'37805-6B2-A560\x00\x00', + b'37805-6B2-A650\x00\x00', + b'37805-6B2-A660\x00\x00', + b'37805-6B2-A720\x00\x00', + b'37805-6B2-A810\x00\x00', + b'37805-6B2-A820\x00\x00', + b'37805-6B2-A920\x00\x00', + b'37805-6B2-M520\x00\x00', + b'37805-6B2-Y810\x00\x00', + b'37805-6M4-B730\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TVC-A910\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-6A7-A220\x00\x00', + b'28101-6A7-A230\x00\x00', + b'28101-6A7-A320\x00\x00', + b'28101-6A7-A330\x00\x00', + b'28101-6A7-A410\x00\x00', + b'28101-6A7-A510\x00\x00', + b'28101-6A7-A610\x00\x00', + b'28101-6A7-A710\x00\x00', + b'28101-6A9-H140\x00\x00', + b'28101-6A9-H420\x00\x00', + b'28102-6B8-A560\x00\x00', + b'28102-6B8-A570\x00\x00', + b'28102-6B8-A700\x00\x00', + b'28102-6B8-A800\x00\x00', + b'28102-6B8-C560\x00\x00', + b'28102-6B8-C570\x00\x00', + b'28102-6B8-M520\x00\x00', + b'28102-6B8-R700\x00\x00', + ], + (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ + b'46114-TVA-A060\x00\x00', + b'46114-TVA-A080\x00\x00', + b'46114-TVA-A120\x00\x00', + b'46114-TVA-A320\x00\x00', + b'46114-TVA-A050\x00\x00', + b'46114-TVE-H550\x00\x00', + b'46114-TVE-H560\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TVA-B040\x00\x00', + b'57114-TVA-B050\x00\x00', + b'57114-TVA-B060\x00\x00', + b'57114-TVA-B530\x00\x00', + b'57114-TVA-C040\x00\x00', + b'57114-TVA-C050\x00\x00', + b'57114-TVA-C060\x00\x00', + b'57114-TVA-C530\x00\x00', + b'57114-TVA-E520\x00\x00', + b'57114-TVE-H250\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TBX-H120\x00\x00', + b'39990-TVA-A140\x00\x00', + b'39990-TVA-A150\x00\x00', + b'39990-TVA-A160\x00\x00', + b'39990-TVA-A340\x00\x00', + b'39990-TVA-X030\x00\x00', + b'39990-TVA-X040\x00\x00', + b'39990-TVA,A150\x00\x00', # modified firmware + b'39990-TVE-H130\x00\x00', + ], + (Ecu.unknown, 0x18da3af1, None): [ + b'39390-TVA-A020\x00\x00', + b'39390-TVA-A120\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TBX-H230\x00\x00', + b'77959-TVA-A460\x00\x00', + b'77959-TVA-F330\x00\x00', + b'77959-TVA-H230\x00\x00', + b'77959-TVA-L420\x00\x00', + b'77959-TVA-X330\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TBX-H310\x00\x00', + b'78109-TVA-A010\x00\x00', + b'78109-TVA-A020\x00\x00', + b'78109-TVA-A030\x00\x00', + b'78109-TVA-A110\x00\x00', + b'78109-TVA-A120\x00\x00', + b'78109-TVA-A130\x00\x00', + b'78109-TVA-A210\x00\x00', + b'78109-TVA-A220\x00\x00', + b'78109-TVA-A230\x00\x00', + b'78109-TVA-A310\x00\x00', + b'78109-TVA-C010\x00\x00', + b'78109-TVA-L010\x00\x00', + b'78109-TVA-L210\x00\x00', + b'78109-TVA-R310\x00\x00', + b'78109-TVC-A010\x00\x00', + b'78109-TVC-A020\x00\x00', + b'78109-TVC-A030\x00\x00', + b'78109-TVC-A110\x00\x00', + b'78109-TVC-A130\x00\x00', + b'78109-TVC-A210\x00\x00', + b'78109-TVC-A220\x00\x00', + b'78109-TVC-A230\x00\x00', + b'78109-TVC-C010\x00\x00', + b'78109-TVC-C110\x00\x00', + b'78109-TVC-L010\x00\x00', + b'78109-TVC-L210\x00\x00', + b'78109-TVC-M510\x00\x00', + b'78109-TVC-YF10\x00\x00', + b'78109-TVE-H610\x00\x00', + b'78109-TWA-A210\x00\x00', + ], + (Ecu.hud, 0x18da61f1, None): [ + b'78209-TVA-A010\x00\x00', + b'78209-TVA-A110\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TBX-H140\x00\x00', + b'36802-TVA-A150\x00\x00', + b'36802-TVA-A160\x00\x00', + b'36802-TVA-A170\x00\x00', + b'36802-TVA-A330\x00\x00', + b'36802-TVC-A330\x00\x00', + b'36802-TVE-H070\x00\x00', + b'36802-TWA-A070\x00\x00', + b'36802-TWA-A080\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TBX-H130\x00\x00', + b'36161-TVA-A060\x00\x00', + b'36161-TVA-A330\x00\x00', + b'36161-TVC-A330\x00\x00', + b'36161-TVE-H050\x00\x00', + b'36161-TWA-A070\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TVA-A010\x00\x00', + b'38897-TVA-A020\x00\x00', + b'38897-TVA-A230\x00\x00', + b'38897-TVA-A240\x00\x00', + ], + }, + CAR.ACCORDH: { + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TWA-A120\x00\x00', + b'38897-TWD-J020\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TWA-A040\x00\x00', + b'57114-TWA-A050\x00\x00', + b'57114-TWA-A530\x00\x00', + b'57114-TWA-B520\x00\x00', + b'57114-TWB-H030\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TWA-A440\x00\x00', + b'77959-TWA-L420\x00\x00', + b'77959-TWB-H220\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TWA-A010\x00\x00', + b'78109-TWA-A020\x00\x00', + b'78109-TWA-A030\x00\x00', + b'78109-TWA-A110\x00\x00', + b'78109-TWA-A120\x00\x00', + b'78109-TWA-A130\x00\x00', + b'78109-TWA-A210\x00\x00', + b'78109-TWA-A220\x00\x00', + b'78109-TWA-A230\x00\x00', + b'78109-TWA-L010\x00\x00', + b'78109-TWA-L210\x00\x00', + b'78109-TWA-H210\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TWA-A910\x00\x00', + ], + (Ecu.hud, 0x18da61f1, None): [ + b'78209-TVA-A010\x00\x00', + b'78209-TVA-A110\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TWA-A070\x00\x00', + b'36161-TWA-A330\x00\x00', + b'36161-TWB-H040\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TWA-A070\x00\x00', + b'36802-TWA-A080\x00\x00', + b'36802-TWA-A330\x00\x00', + b'36802-TWB-H060\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TVA-A160\x00\x00', + b'39990-TVA-A150\x00\x00', + b'39990-TVA-A340\x00\x00', + b'39990-TWB-H120\x00\x00', + ], + }, + CAR.CIVIC: { + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-5AA-A640\x00\x00', + b'37805-5AA-A650\x00\x00', + b'37805-5AA-A670\x00\x00', + b'37805-5AA-A680\x00\x00', + b'37805-5AA-A810\x00\x00', + b'37805-5AA-C640\x00\x00', + b'37805-5AA-C680\x00\x00', + b'37805-5AA-C820\x00\x00', + b'37805-5AA-L650\x00\x00', + b'37805-5AA-L660\x00\x00', + b'37805-5AA-L680\x00\x00', + b'37805-5AA-L690\x00\x00', + b'37805-5AA-L810\000\000', + b'37805-5AG-Q710\x00\x00', + b'37805-5AJ-A610\x00\x00', + b'37805-5AJ-A620\x00\x00', + b'37805-5AJ-L610\x00\x00', + b'37805-5BA-A310\x00\x00', + b'37805-5BA-A510\x00\x00', + b'37805-5BA-A740\x00\x00', + b'37805-5BA-A760\x00\x00', + b'37805-5BA-A930\x00\x00', + b'37805-5BA-A960\x00\x00', + b'37805-5BA-C860\x00\x00', + b'37805-5BA-L410\x00\x00', + b'37805-5BA-L760\x00\x00', + b'37805-5BA-L930\x00\x00', + b'37805-5BA-L940\x00\x00', + b'37805-5BA-L960\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-5CG-A040\x00\x00', + b'28101-5CG-A050\x00\x00', + b'28101-5CG-A070\x00\x00', + b'28101-5CG-A080\x00\x00', + b'28101-5CG-A320\x00\x00', + b'28101-5CG-A810\x00\x00', + b'28101-5CG-A820\x00\x00', + b'28101-5DJ-A040\x00\x00', + b'28101-5DJ-A060\x00\x00', + b'28101-5DJ-A510\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TBA-A540\x00\x00', + b'57114-TBA-A550\x00\x00', + b'57114-TBA-A560\x00\x00', + b'57114-TBA-A570\x00\x00', + b'57114-TEA-Q220\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TBA,A030\x00\x00', # modified firmware + b'39990-TBA-A030\x00\x00', + b'39990-TBG-A030\x00\x00', + b'39990-TEA-T020\x00\x00', + b'39990-TEG-A010\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TBA-A030\x00\x00', + b'77959-TBA-A040\x00\x00', + b'77959-TBG-A030\x00\x00', + b'77959-TEA-Q820\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TBA-A510\x00\x00', + b'78109-TBA-A520\x00\x00', + b'78109-TBA-A530\x00\x00', + b'78109-TBA-C520\x00\x00', + b'78109-TBC-A310\x00\x00', + b'78109-TBC-A320\x00\x00', + b'78109-TBC-A510\x00\x00', + b'78109-TBC-A520\x00\x00', + b'78109-TBC-A530\x00\x00', + b'78109-TBC-C510\x00\x00', + b'78109-TBC-C520\x00\x00', + b'78109-TBC-C530\x00\x00', + b'78109-TBH-A510\x00\x00', + b'78109-TBH-A530\x00\x00', + b'78109-TED-Q510\x00\x00', + b'78109-TEG-A310\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-TBA-A020\x00\x00', + b'36161-TBA-A030\x00\x00', + b'36161-TBA-A040\x00\x00', + b'36161-TBC-A020\x00\x00', + b'36161-TBC-A030\x00\x00', + b'36161-TED-Q320\x00\x00', + b'36161-TEG-A010\x00\x00', + b'36161-TEG-A020\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TBA-A010\x00\x00', + b'38897-TBA-A020\x00\x00', + ], + }, + CAR.CIVIC_BOSCH: { + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-5AA-A940\x00\x00', + b'37805-5AA-A950\x00\x00', + b'37805-5AA-C950\x00\x00', + b'37805-5AA-L940\x00\x00', + b'37805-5AA-L950\x00\x00', + b'37805-5AG-Z910\x00\x00', + b'37805-5AJ-A750\x00\x00', + b'37805-5AJ-L750\x00\x00', + b'37805-5AK-T530\x00\x00', + b'37805-5AN-A750\x00\x00', + b'37805-5AN-A830\x00\x00', + b'37805-5AN-A840\x00\x00', + b'37805-5AN-A930\x00\x00', + b'37805-5AN-A940\x00\x00', + b'37805-5AN-A950\x00\x00', + b'37805-5AN-AG20\x00\x00', + b'37805-5AN-AH20\x00\x00', + b'37805-5AN-AJ30\x00\x00', + b'37805-5AN-AK10\x00\x00', + b'37805-5AN-AK20\x00\x00', + b'37805-5AN-AR10\x00\x00', + b'37805-5AN-AR20\x00\x00', + b'37805-5AN-CH20\x00\x00', + b'37805-5AN-E630\x00\x00', + b'37805-5AN-E720\x00\x00', + b'37805-5AN-E820\x00\x00', + b'37805-5AN-J820\x00\x00', + b'37805-5AN-L840\x00\x00', + b'37805-5AN-L930\x00\x00', + b'37805-5AN-L940\x00\x00', + b'37805-5AN-LF20\x00\x00', + b'37805-5AN-LH20\x00\x00', + b'37805-5AN-LJ20\x00\x00', + b'37805-5AN-LR20\x00\x00', + b'37805-5AN-LS20\x00\x00', + b'37805-5AW-G720\x00\x00', + b'37805-5AZ-E850\x00\x00', + b'37805-5AZ-G540\x00\x00', + b'37805-5AZ-G740\x00\x00', + b'37805-5AZ-G840\x00\x00', + b'37805-5BB-A530\x00\x00', + b'37805-5BB-A540\x00\x00', + b'37805-5BB-A630\x00\x00', + b'37805-5BB-A640\x00\x00', + b'37805-5BB-C540\x00\x00', + b'37805-5BB-C630\x00\x00', + b'37805-5BB-C640\x00\x00', + b'37805-5BB-L540\x00\x00', + b'37805-5BB-L630\x00\x00', + b'37805-5BB-L640\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-5CG-A920\x00\x00', + b'28101-5CG-AB10\x00\x00', + b'28101-5CG-C110\x00\x00', + b'28101-5CG-C220\x00\x00', + b'28101-5CG-C320\x00\x00', + b'28101-5CG-G020\x00\x00', + b'28101-5CG-L020\x00\x00', + b'28101-5CK-A130\x00\x00', + b'28101-5CK-A140\x00\x00', + b'28101-5CK-A150\x00\x00', + b'28101-5CK-C130\x00\x00', + b'28101-5CK-C140\x00\x00', + b'28101-5CK-C150\x00\x00', + b'28101-5CK-G210\x00\x00', + b'28101-5CK-J710\x00\x00', + b'28101-5CK-Q610\x00\x00', + b'28101-5DJ-A610\x00\x00', + b'28101-5DJ-A710\x00\x00', + b'28101-5DV-E330\x00\x00', + b'28101-5DV-E610\x00\x00', + b'28101-5DV-E820\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TBG-A330\x00\x00', + b'57114-TBG-A340\x00\x00', + b'57114-TBG-A350\x00\x00', + b'57114-TGG-A340\x00\x00', + b'57114-TGG-C320\x00\x00', + b'57114-TGG-G320\x00\x00', + b'57114-TGG-L320\x00\x00', + b'57114-TGG-L330\x00\x00', + b'57114-TGK-T320\x00\x00', + b'57114-TGL-G330\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TBA-C020\x00\x00', + b'39990-TBA-C120\x00\x00', + b'39990-TEA-T820\x00\x00', + b'39990-TEZ-T020\x00\x00', + b'39990-TGG-A020\x00\x00', + b'39990-TGG-A120\x00\x00', + b'39990-TGG-J510\x00\x00', + b'39990-TGL-E130\x00\x00', + b'39990-TGN-E120\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TBA-A060\x00\x00', + b'77959-TBG-A050\x00\x00', + b'77959-TEA-G020\x00\x00', + b'77959-TGG-A020\x00\x00', + b'77959-TGG-A030\x00\x00', + b'77959-TGG-E010\x00\x00', + b'77959-TGG-G010\x00\x00', + b'77959-TGG-G110\x00\x00', + b'77959-TGG-J320\x00\x00', + b'77959-TGG-Z820\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TBA-A110\x00\x00', + b'78109-TBA-A910\x00\x00', + b'78109-TBA-C340\x00\x00', + b'78109-TBA-C910\x00\x00', + b'78109-TBC-A740\x00\x00', + b'78109-TBC-C540\x00\x00', + b'78109-TBG-A110\x00\x00', + b'78109-TBH-A710\x00\x00', + b'78109-TEG-A720\x00\x00', + b'78109-TFJ-G020\x00\x00', + b'78109-TGG-9020\x00\x00', + b'78109-TGG-A210\x00\x00', + b'78109-TGG-A220\x00\x00', + b'78109-TGG-A310\x00\x00', + b'78109-TGG-A320\x00\x00', + b'78109-TGG-A330\x00\x00', + b'78109-TGG-A610\x00\x00', + b'78109-TGG-A620\x00\x00', + b'78109-TGG-A810\x00\x00', + b'78109-TGG-A820\x00\x00', + b'78109-TGG-C220\x00\x00', + b'78109-TGG-E110\x00\x00', + b'78109-TGG-G030\x00\x00', + b'78109-TGG-G230\x00\x00', + b'78109-TGG-G410\x00\x00', + b'78109-TGK-Z410\x00\x00', + b'78109-TGL-G120\x00\x00', + b'78109-TGL-G130\x00\x00', + b'78109-TGL-G210\x00\x00', + b'78109-TGL-G230\x00\x00', + b'78109-TGL-GM10\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TBA-A150\x00\x00', + b'36802-TBA-A160\x00\x00', + b'36802-TFJ-G060\x00\x00', + b'36802-TGG-A050\x00\x00', + b'36802-TGG-A060\x00\x00', + b'36802-TGG-A130\x00\x00', + b'36802-TGG-G040\x00\x00', + b'36802-TGG-G130\x00\x00', + b'36802-TGK-Q120\x00\x00', + b'36802-TGL-G040\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TBA-A130\x00\x00', + b'36161-TBA-A140\x00\x00', + b'36161-TFJ-G070\x00\x00', + b'36161-TGG-A060\x00\x00', + b'36161-TGG-A080\x00\x00', + b'36161-TGG-A120\x00\x00', + b'36161-TGG-G050\x00\x00', + b'36161-TGG-G130\x00\x00', + b'36161-TGG-G140\x00\x00', + b'36161-TGK-Q120\x00\x00', + b'36161-TGL-G050\x00\x00', + b'36161-TGL-G070\x00\x00', + b'36161-TGG-G070\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TBA-A110\x00\x00', + b'38897-TBA-A020\x00\x00', + ], + (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ + b'39494-TGL-G030\x00\x00', + ], + }, + CAR.CIVIC_BOSCH_DIESEL: { + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-59N-G630\x00\x00', + b'37805-59N-G830\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-59Y-G220\x00\x00', + b'28101-59Y-G620\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TGN-E320\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TFK-G020\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TFK-G210\x00\x00', + b'77959-TGN-G220\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TFK-G020\x00\x00', + b'78109-TGN-G120\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TFK-G130\x00\x00', + b'36802-TGN-G130\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TGN-E010\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TFK-G130\x00\x00', + b'36161-TGN-G130\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TBA-A020\x00\x00', + ], + }, + CAR.CRV: { + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-T1W-A230\x00\x00', + b'57114-T1W-A240\x00\x00', + b'57114-TFF-A940\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-T0A-A230\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-T1W-A210\x00\x00', + b'78109-T1W-C210\x00\x00', + b'78109-T1X-A210\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T1W-A830\x00\x00', + b'36161-T1W-C830\x00\x00', + b'36161-T1X-A830\x00\x00', + ], + }, + CAR.CRV_5G: { + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-5PA-AH20\x00\x00', + b'37805-5PA-3060\x00\x00', + b'37805-5PA-3080\x00\x00', + b'37805-5PA-3180\x00\x00', + b'37805-5PA-4050\x00\x00', + b'37805-5PA-4150\x00\x00', + b'37805-5PA-6520\x00\x00', + b'37805-5PA-6530\x00\x00', + b'37805-5PA-6630\x00\x00', + b'37805-5PA-6640\x00\x00', + b'37805-5PA-7630\x00\x00', + b'37805-5PA-9630\x00\x00', + b'37805-5PA-9640\x00\x00', + b'37805-5PA-9730\x00\x00', + b'37805-5PA-9830\x00\x00', + b'37805-5PA-9840\x00\x00', + b'37805-5PA-A650\x00\x00', + b'37805-5PA-A670\x00\x00', + b'37805-5PA-A680\x00\x00', + b'37805-5PA-A850\x00\x00', + b'37805-5PA-A870\x00\x00', + b'37805-5PA-A880\x00\x00', + b'37805-5PA-A890\x00\x00', + b'37805-5PA-AB10\x00\x00', + b'37805-5PA-AD10\x00\x00', + b'37805-5PA-AF20\x00\x00', + b'37805-5PA-C680\x00\x00', + b'37805-5PD-Q630\x00\x00', + b'37805-5PF-F730\x00\x00', + b'37805-5PF-M630\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-5RG-A020\x00\x00', + b'28101-5RG-A030\x00\x00', + b'28101-5RG-A040\x00\x00', + b'28101-5RG-A120\x00\x00', + b'28101-5RG-A220\x00\x00', + b'28101-5RH-A020\x00\x00', + b'28101-5RH-A030\x00\x00', + b'28101-5RH-A040\x00\x00', + b'28101-5RH-A120\x00\x00', + b'28101-5RH-A220\x00\x00', + b'28101-5RL-Q010\x00\x00', + b'28101-5RM-F010\x00\x00', + b'28101-5RM-K010\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TLA-A040\x00\x00', + b'57114-TLA-A050\x00\x00', + b'57114-TLA-A060\x00\x00', + b'57114-TLB-A830\x00\x00', + b'57114-TMC-Z040\x00\x00', + b'57114-TMC-Z050\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TLA-A040\x00\x00', + b'39990-TLA-A110\x00\x00', + b'39990-TLA-A220\x00\x00', + b'39990-TLA,A040\x00\x00', # modified firmware + b'39990-TME-T030\x00\x00', + b'39990-TME-T120\x00\x00', + b'39990-TMT-T010\x00\x00', + ], + (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ + b'46114-TLA-A040\x00\x00', + b'46114-TLA-A050\x00\x00', + b'46114-TLA-A930\x00\x00', + b'46114-TMC-U020\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TLA-A110\x00\x00', + b'78109-TLA-A120\x00\x00', + b'78109-TLA-A210\x00\x00', + b'78109-TLA-A220\x00\x00', + b'78109-TLA-C020\x00\x00', + b'78109-TLA-C110\x00\x00', + b'78109-TLA-C210\x00\x00', + b'78109-TLA-C310\x00\x00', + b'78109-TLB-A020\x00\x00', + b'78109-TLB-A110\x00\x00', + b'78109-TLB-A120\x00\x00', + b'78109-TLB-A210\x00\x00', + b'78109-TLB-A220\x00\x00', + b'78109-TMC-Q210\x00\x00', + b'78109-TMM-F210\x00\x00', + b'78109-TMM-M110\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TLA-A010\x00\x00', + b'38897-TLA-A110\x00\x00', + b'38897-TNY-G010\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TLA-A040\x00\x00', + b'36802-TLA-A050\x00\x00', + b'36802-TLA-A060\x00\x00', + b'36802-TMC-Q040\x00\x00', + b'36802-TMC-Q070\x00\x00', + b'36802-TNY-A030\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TLA-A060\x00\x00', + b'36161-TLA-A070\x00\x00', + b'36161-TLA-A080\x00\x00', + b'36161-TMC-Q020\x00\x00', + b'36161-TMC-Q030\x00\x00', + b'36161-TMC-Q040\x00\x00', + b'36161-TNY-A020\x00\x00', + b'36161-TNY-A030\x00\x00', + b'36161-TNY-A040\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TLA-A240\x00\x00', + b'77959-TLA-A250\x00\x00', + b'77959-TLA-A320\x00\x00', + b'77959-TLA-A410\x00\x00', + b'77959-TLA-A420\x00\x00', + b'77959-TLA-Q040\x00\x00', + b'77959-TLA-Z040\x00\x00', + b'77959-TMM-F040\x00\x00', + ], + }, + CAR.CRV_EU: { + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-R5Z-G740\x00\x00', + b'37805-R5Z-G780\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [b'57114-T1V-G920\x00\x00'], + (Ecu.fwdRadar, 0x18dab0f1, None): [b'36161-T1V-G520\x00\x00'], + (Ecu.shiftByWire, 0x18da0bf1, None): [b'54008-T1V-G010\x00\x00'], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-5LH-E120\x00\x00', + b'28103-5LH-E100\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-T1V-G020\x00\x00', + b'78109-T1B-3050\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [b'77959-T1G-G940\x00\x00'], + }, + CAR.CRV_HYBRID: { + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TPA-G020\x00\x00', + b'57114-TPG-A020\x00\x00', + b'57114-TMB-H030\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TPA-G030\x00\x00', + b'39990-TPG-A020\x00\x00', + b'39990-TMA-H020\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TMA-H110\x00\x00', + b'38897-TPG-A110\x00\x00', + b'38897-TPG-A210\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TMB-H510\x00\x00', + b'54008-TMB-H610\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TMB-H040\x00\x00', + b'36161-TPA-E050\x00\x00', + b'36161-TPG-A030\x00\x00', + b'36161-TPG-A040\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TMB-H220\x00\x00', + b'78109-TPA-G520\x00\x00', + b'78109-TPG-A110\x00\x00', + b'78109-TPG-A210\x00\x00', + ], + (Ecu.hud, 0x18da61f1, None): [ + b'78209-TLA-X010\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TPA-E040\x00\x00', + b'36802-TPG-A020\x00\x00', + b'36802-TMB-H040\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TLA-C320\x00\x00', + b'77959-TLA-C410\x00\x00', + b'77959-TLA-C420\x00\x00', + b'77959-TLA-G220\x00\x00', + b'77959-TLA-H240\x00\x00', + ], + }, + CAR.FIT: { + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-T5R-L020\x00\x00', + b'57114-T5R-L220\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-T5R-C020\x00\x00', + b'39990-T5R-C030\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-T5A-J010\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-T5A-A210\x00\x00', + b'78109-T5A-A410\x00\x00', + b'78109-T5A-A420\x00\x00', + b'78109-T5A-A910\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T5R-A040\x00\x00', + b'36161-T5R-A240\x00\x00', + b'36161-T5R-A520\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-T5R-A230\x00\x00', + ], + }, + CAR.FREED: { + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TDK-J010\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TDK-J050\x00\x00', + b'39990-TDK-N020\x00\x00', + ], + # TODO: vsa is "essential" for fpv2 but doesn't appear on some models + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TDK-J120\x00\x00', + b'57114-TDK-J330\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TDK-J310\x00\x00', + b'78109-TDK-J320\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-TDK-J070\x00\x00', + b'36161-TDK-J080\x00\x00', + b'36161-TDK-J530\x00\x00', + ], + }, + CAR.ODYSSEY: { + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-THR-A010\x00\x00', + b'38897-THR-A020\x00\x00', + ], + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-5MR-4080\x00\x00', + b'37805-5MR-A240\x00\x00', + b'37805-5MR-A250\x00\x00', + b'37805-5MR-A310\x00\x00', + b'37805-5MR-A740\x00\x00', + b'37805-5MR-A750\x00\x00', + b'37805-5MR-A840\x00\x00', + b'37805-5MR-C620\x00\x00', + b'37805-5MR-D530\x00\x00', + b'37805-5MR-K730\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-THR-A020\x00\x00', + b'39990-THR-A030\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-THR-A010\x00\x00', + b'77959-THR-A110\x00\x00', + b'77959-THR-X010\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-THR-A020\x00\x00', + b'36161-THR-A030\x00\x00', + b'36161-THR-A110\x00\x00', + b'36161-THR-A720\x00\x00', + b'36161-THR-A730\x00\x00', + b'36161-THR-A810\x00\x00', + b'36161-THR-A910\x00\x00', + b'36161-THR-C010\x00\x00', + b'36161-THR-D110\x00\x00', + b'36161-THR-K020\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-5NZ-A110\x00\x00', + b'28101-5NZ-A310\x00\x00', + b'28101-5NZ-C310\x00\x00', + b'28102-5MX-A001\x00\x00', + b'28102-5MX-A600\x00\x00', + b'28102-5MX-A610\x00\x00', + b'28102-5MX-A710\x00\x00', + b'28102-5MX-A900\x00\x00', + b'28102-5MX-A910\x00\x00', + b'28102-5MX-C001\x00\x00', + b'28102-5MX-D001\x00\x00', + b'28102-5MX-D710\x00\x00', + b'28102-5MX-K610\x00\x00', + b'28103-5NZ-A100\x00\x00', + b'28103-5NZ-A300\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-THR-A040\x00\x00', + b'57114-THR-A110\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-THR-A220\x00\x00', + b'78109-THR-A230\x00\x00', + b'78109-THR-A420\x00\x00', + b'78109-THR-A430\x00\x00', + b'78109-THR-A720\x00\x00', + b'78109-THR-A820\x00\x00', + b'78109-THR-A830\x00\x00', + b'78109-THR-AB20\x00\x00', + b'78109-THR-AB30\x00\x00', + b'78109-THR-AB40\x00\x00', + b'78109-THR-AC20\x00\x00', + b'78109-THR-AC30\x00\x00', + b'78109-THR-AC40\x00\x00', + b'78109-THR-AC50\x00\x00', + b'78109-THR-AD30\x00\x00', + b'78109-THR-AE20\x00\x00', + b'78109-THR-AE30\x00\x00', + b'78109-THR-AE40\x00\x00', + b'78109-THR-AK10\x00\x00', + b'78109-THR-AL10\x00\x00', + b'78109-THR-AN10\x00\x00', + b'78109-THR-C220\x00\x00', + b'78109-THR-C330\x00\x00', + b'78109-THR-CE20\x00\x00', + b'78109-THR-DA20\x00\x00', + b'78109-THR-DA30\x00\x00', + b'78109-THR-DA40\x00\x00', + b'78109-THR-K120\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-THR-A020\x00\x00', + ], + }, + CAR.ODYSSEY_CHN: { + (Ecu.eps, 0x18da30f1, None): [ + b'39990-T6D-H220\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-T6A-J010\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-T6A-F310\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T6A-P040\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-T6A-P110\x00\x00', + ], + }, + CAR.PILOT: { + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TG7-A520\x00\x00', + b'54008-TG7-A530\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-5EY-A050\x00\x00', + b'28101-5EY-A100\x00\x00', + b'28101-5EZ-A050\x00\x00', + b'28101-5EZ-A060\x00\x00', + b'28101-5EZ-A100\x00\x00', + b'28101-5EZ-A210\x00\x00', + b'28101-5EZ-A600\x00\x00', + b'28101-5EZ-A430\x00\x00', + b'28101-5EZ-A700\x00\x00', + ], + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-RLV-4060\x00\x00', + b'37805-RLV-4070\x00\x00', + b'37805-RLV-A830\x00\x00', + b'37805-RLV-A840\x00\x00', + b'37805-RLV-C430\x00\x00', + b'37805-RLV-C510\x00\x00', + b'37805-RLV-C520\x00\x00', + b'37805-RLV-C530\x00\x00', + b'37805-RLV-C910\x00\x00', + b'37805-RLV-B220\x00\x00', + b'37805-RLV-B210\x00\x00', + b'37805-RLV-L160\x00\x00', + b'37805-RLV-B420\x00\x00', + b'37805-RLV-F120\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TG7-A030\x00\x00', + b'38897-TG7-A040\x00\x00', + b'38897-TG7-A110\x00\x00', + b'38897-TG7-A210\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TG7-A030\x00\x00', + b'39990-TG7-A040\x00\x00', + b'39990-TG7-A060\x00\x00', + b'39990-TG7-A070\x00\x00', + b'39990-TGS-A230\x00\x00', + b'39990-TGS-A320\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-TG7-A310\x00\x00', + b'36161-TG7-A520\x00\x00', + b'36161-TG7-A630\x00\x00', + b'36161-TG7-A720\x00\x00', + b'36161-TG7-A820\x00\x00', + b'36161-TG7-A930\x00\x00', + b'36161-TG7-C520\x00\x00', + b'36161-TG7-D520\x00\x00', + b'36161-TG7-D630\x00\x00', + b'36161-TG7-Y630\x00\x00', + b'36161-TG8-A520\x00\x00', + b'36161-TG8-A630\x00\x00', + b'36161-TG8-A720\x00\x00', + b'36161-TG8-A830\x00\x00', + b'36161-TGS-A130\x00\x00', + b'36161-TGT-A030\x00\x00', + b'36161-TGT-A130\x00\x00', + b'36161-TGS-A030\x00\x00', + b'36161-TGS-A220\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TG7-A020\x00\x00', + b'77959-TG7-A110\x00\x00', + b'77959-TG7-A210\x00\x00', + b'77959-TG7-Y210\x00\x00', + b'77959-TGS-A010\x00\x00', + b'77959-TGS-A110\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TG7-A040\x00\x00', + b'78109-TG7-A050\x00\x00', + b'78109-TG7-A420\x00\x00', + b'78109-TG7-A520\x00\x00', + b'78109-TG7-A720\x00\x00', + b'78109-TG7-AJ10\x00\x00', + b'78109-TG7-AJ20\x00\x00', + b'78109-TG7-AK10\x00\x00', + b'78109-TG7-AK20\x00\x00', + b'78109-TG7-AM20\x00\x00', + b'78109-TG7-AP10\x00\x00', + b'78109-TG7-AP20\x00\x00', + b'78109-TG7-AS20\x00\x00', + b'78109-TG7-AT20\x00\x00', + b'78109-TG7-AU20\x00\x00', + b'78109-TG7-AX20\x00\x00', + b'78109-TG7-D020\x00\x00', + b'78109-TG7-DJ10\x00\x00', + b'78109-TG7-YK20\x00\x00', + b'78109-TG8-A420\x00\x00', + b'78109-TG8-A520\x00\x00', + b'78109-TG8-AJ10\x00\x00', + b'78109-TG8-AJ20\x00\x00', + b'78109-TG8-AK20\x00\x00', + b'78109-TGS-AK20\x00\x00', + b'78109-TGS-AP20\x00\x00', + b'78109-TGT-AJ20\x00\x00', + b'78109-TGT-AK30\x00\x00', + b'78109-TGS-AT20\x00\x00', + b'78109-TGS-AX20\x00\x00', + b'78109-TGS-AJ20\x00\x00', + b'78109-TGS-AC10\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TG7-A130\x00\x00', + b'57114-TG7-A140\x00\x00', + b'57114-TG7-A230\x00\x00', + b'57114-TG7-A240\x00\x00', + b'57114-TG7-A630\x00\x00', + b'57114-TG7-A730\x00\x00', + b'57114-TG8-A140\x00\x00', + b'57114-TG8-A240\x00\x00', + b'57114-TG8-A630\x00\x00', + b'57114-TG8-A730\x00\x00', + b'57114-TGS-A530\x00\x00', + b'57114-TGT-A530\x00\x00', + ], + }, + CAR.ACURA_RDX: { + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TX5-A220\x00\x00', + b'57114-TX4-A220\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-TX5-A030\x00\x00', + b'36161-TX4-A030\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TX4-C010\x00\x00', + b'77959-TX4-B010\x00\x00', + b'77959-TX4-C020\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TX5-A310\x00\x00', + b'78109-TX4-A210\x00\x00', + b'78109-TX4-A310\x00\x00', + ], + }, + CAR.ACURA_RDX_3G: { + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-5YF-A130\x00\x00', + b'37805-5YF-A230\x00\x00', + b'37805-5YF-A320\x00\x00', + b'37805-5YF-A330\x00\x00', + b'37805-5YF-A420\x00\x00', + b'37805-5YF-A430\x00\x00', + b'37805-5YF-A750\x00\x00', + b'37805-5YF-A850\x00\x00', + b'37805-5YF-A870\x00\x00', + b'37805-5YF-AD20\x00\x00', + b'37805-5YF-C210\x00\x00', + b'37805-5YF-C220\x00\x00', + b'37805-5YF-C410\000\000', + b'37805-5YF-C420\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TJB-A030\x00\x00', + b'57114-TJB-A040\x00\x00', + b'57114-TJB-A120\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TJB-A040\x00\x00', + b'36802-TJB-A050\x00\x00', + b'36802-TJB-A540\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TJB-A040\x00\x00', + b'36161-TJB-A530\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TJB-A520\x00\x00', + b'54008-TJB-A530\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28102-5YK-A610\x00\x00', + b'28102-5YK-A620\x00\x00', + b'28102-5YK-A630\x00\x00', + b'28102-5YK-A700\x00\x00', + b'28102-5YK-A711\x00\x00', + b'28102-5YK-A800\x00\x00', + b'28102-5YL-A620\x00\x00', + b'28102-5YL-A700\x00\x00', + b'28102-5YL-A711\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TJB-A140\x00\x00', + b'78109-TJB-A240\x00\x00', + b'78109-TJB-A420\x00\x00', + b'78109-TJB-AB10\x00\x00', + b'78109-TJB-AD10\x00\x00', + b'78109-TJB-AF10\x00\x00', + b'78109-TJB-AQ20\x00\x00', + b'78109-TJB-AR10\x00\x00', + b'78109-TJB-AS10\000\000', + b'78109-TJB-AU10\x00\x00', + b'78109-TJB-AW10\x00\x00', + b'78109-TJC-A420\x00\x00', + b'78109-TJC-AA10\x00\x00', + b'78109-TJC-AD10\x00\x00', + b'78109-TJC-AF10\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TJB-A040\x00\x00', + b'77959-TJB-A120\x00\x00', + b'77959-TJB-A210\x00\x00', + ], + (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ + b'46114-TJB-A040\x00\x00', + b'46114-TJB-A050\x00\x00', + b'46114-TJB-A060\x00\x00', + b'46114-TJB-A120\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TJB-A040\x00\x00', + b'38897-TJB-A110\x00\x00', + b'38897-TJB-A120\x00\x00', + b'38897-TJB-A220\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TJB-A030\x00\x00', + b'39990-TJB-A040\x00\x00', + b'39990-TJB-A070\x00\x00', + b'39990-TJB-A130\x00\x00', + ], + }, + CAR.RIDGELINE: { + (Ecu.eps, 0x18da30f1, None): [ + b'39990-T6Z-A020\x00\x00', + b'39990-T6Z-A030\x00\x00', + b'39990-T6Z-A050\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T6Z-A020\x00\x00', + b'36161-T6Z-A310\x00\x00', + b'36161-T6Z-A420\x00\x00', + b'36161-T6Z-A520\x00\x00', + b'36161-T6Z-A620\x00\x00', + b'36161-TJZ-A120\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-T6Z-A010\x00\x00', + b'38897-T6Z-A110\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-T6Z-A420\x00\x00', + b'78109-T6Z-A510\x00\x00', + b'78109-T6Z-A710\x00\x00', + b'78109-T6Z-A810\x00\x00', + b'78109-T6Z-A910\x00\x00', + b'78109-T6Z-AA10\x00\x00', + b'78109-T6Z-C620\x00\x00', + b'78109-T6Z-C910\x00\x00', + b'78109-TJZ-A510\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-T6Z-A020\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-T6Z-A120\x00\x00', + b'57114-T6Z-A130\x00\x00', + b'57114-T6Z-A520\x00\x00', + b'57114-TJZ-A520\x00\x00', + ], + }, + CAR.INSIGHT: { + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TXM-A040\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TXM-A070\x00\x00', + b'36802-TXM-A080\x00\x00', + ], + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TXM-A050\x00\x00', + b'36161-TXM-A060\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TXM-A230\x00\x00', + ], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TXM-A030\x00\x00', + b'57114-TXM-A040\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TWA-A910\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TXM-A020\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TXM-A010\x00\x00', + b'78109-TXM-A020\x00\x00', + b'78109-TXM-A110\x00\x00', + b'78109-TXM-C010\x00\x00', + b'78109-TXM-A030\x00\x00', + ], + }, + CAR.HRV: { + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-T7A-A010\x00\x00', + b'38897-T7A-A110\x00\x00', + ], + (Ecu.eps, 0x18da30f1, None): [ + b'39990-THX-A020\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T7A-A140\x00\x00', + b'36161-T7A-A240\x00\x00', + b'36161-T7A-C440\x00\x00', + b'36161-T7A-A040\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-T7A-A230\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-THX-A110\x00\x00', + b'78109-THX-A120\x00\x00', + b'78109-THX-A210\x00\x00', + b'78109-THX-A220\x00\x00', + b'78109-THX-C220\x00\x00', + b'78109-THW-A110\x00\x00', + ], + }, + CAR.HRV_3G: { + (Ecu.eps, 0x18DA30F1, None): [ + b'39990-3W0-A030\x00\x00', + ], + (Ecu.gateway, 0x18DAEFF1, None): [ + b'38897-3W1-A010\x00\x00', + ], + (Ecu.srs, 0x18DA53F1, None): [ + b'77959-3V0-A820\x00\x00', + ], + (Ecu.combinationMeter, 0x18DA60F1, None): [ + b'78108-3V1-A220\x00\x00', + ], + (Ecu.vsa, 0x18DA28F1, None): [ + b'57114-3W0-A040\x00\x00', + ], + (Ecu.transmission, 0x18DA1EF1, None): [ + b'28101-6EH-A010\x00\x00', + ], + (Ecu.programmedFuelInjection, 0x18DA10F1, None): [ + b'37805-6CT-A710\x00\x00', + ], + (Ecu.electricBrakeBooster, 0x18DA2BF1, None): [ + b'46114-3W0-A020\x00\x00', + ], + }, + CAR.ACURA_ILX: { + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TX6-A010\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-TV9-A140\x00\x00', + b'36161-TX6-A030\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TX6-A230\x00\x00', + b'77959-TX6-C210\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-T3R-A120\x00\x00', + b'78109-T3R-A410\x00\x00', + b'78109-TV9-A510\x00\x00', + ], + }, + CAR.HONDA_E:{ + (Ecu.eps, 0x18DA30F1, None):[ + b'39990-TYF-N030\x00\x00' + ], + (Ecu.gateway, 0x18DAEFF1, None):[ + b'38897-TYF-E140\x00\x00' + ], + (Ecu.shiftByWire, 0x18DA0BF1, None):[ + b'54008-TYF-E010\x00\x00' + ], + (Ecu.srs, 0x18DA53F1, None):[ + b'77959-TYF-G430\x00\x00' + ], + (Ecu.combinationMeter, 0x18DA60F1, None):[ + b'78108-TYF-G610\x00\x00' + ], + (Ecu.fwdRadar, 0x18DAB0F1, None):[ + b'36802-TYF-E030\x00\x00' + ], + (Ecu.fwdCamera, 0x18DAB5F1, None):[ + b'36161-TYF-E020\x00\x00' + ], + (Ecu.vsa, 0x18DA28F1, None):[ + b'57114-TYF-E030\x00\x00' + ], + }, + CAR.CIVIC_2022: { + (Ecu.eps, 0x18DA30F1, None): [ + b'39990-T39-A130\x00\x00', + b'39990-T43-J020\x00\x00', + b'39990-T24-T120\x00\x00', + ], + (Ecu.gateway, 0x18DAEFF1, None): [ + b'38897-T20-A020\x00\x00', + b'38897-T20-A510\x00\x00', + b'38897-T21-A010\x00\x00', + b'38897-T20-A210\x00\x00', + b'38897-T20-A310\x00\x00', + b'38897-T24-Z120\x00\x00', + ], + (Ecu.srs, 0x18DA53F1, None): [ + b'77959-T20-A970\x00\x00', + b'77959-T47-A940\x00\x00', + b'77959-T47-A950\x00\x00', + b'77959-T20-M820\x00\x00', + b'77959-T20-A980\x00\x00', + ], + (Ecu.combinationMeter, 0x18DA60F1, None): [ + b'78108-T21-A220\x00\x00', + b'78108-T21-A620\x00\x00', + b'78108-T23-A110\x00\x00', + b'78108-T21-A230\x00\x00', + b'78108-T22-A020\x00\x00', + b'78108-T21-MB10\x00\x00', + b'78108-T21-A740\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T20-A070\x00\x00', + b'36161-T20-A080\x00\x00', + b'36161-T20-A060\x00\x00', + b'36161-T47-A070\x00\x00', + b'36161-T24-T070\x00\x00', + ], + (Ecu.vsa, 0x18DA28F1, None): [ + b'57114-T20-AB40\x00\x00', + b'57114-T43-JB30\x00\x00', + b'57114-T24-TB30\x00\x00', + ], + (Ecu.transmission, 0x18da1ef1, None): [ + b'28101-65D-A020\x00\x00', + b'28101-65D-A120\x00\x00', + b'28101-65H-A020\x00\x00', + b'28101-65H-A120\x00\x00', + b'28101-65J-N010\x00\x00', + ], + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-64L-A540\x00\x00', + b'37805-64S-A540\x00\x00', + b'37805-64S-A720\x00\x00', + b'37805-64A-A540\x00\x00', + b'37805-64A-A620\x00\x00', + b'37805-64D-P510\x00\x00', + b'37805-64S-AA10\x00\x00', + ], + }, +} diff --git a/selfdrive/car/honda/tests/test_honda.py b/selfdrive/car/honda/tests/test_honda.py index da64b8f70b..4e3f9182ea 100755 --- a/selfdrive/car/honda/tests/test_honda.py +++ b/selfdrive/car/honda/tests/test_honda.py @@ -2,7 +2,7 @@ import re import unittest -from openpilot.selfdrive.car.honda.values import FW_VERSIONS +from openpilot.selfdrive.car.honda.fingerprints import FW_VERSIONS HONDA_FW_VERSION_RE = br"\d{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$" diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 38ba0f22fe..a10080ec0e 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -198,1368 +198,6 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) -FW_VERSIONS = { - CAR.ACCORD: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-6A0-8720\x00\x00', - b'37805-6A0-9520\x00\x00', - b'37805-6A0-9620\x00\x00', - b'37805-6A0-9720\x00\x00', - b'37805-6A0-A540\x00\x00', - b'37805-6A0-A550\x00\x00', - b'37805-6A0-A640\x00\x00', - b'37805-6A0-A650\x00\x00', - b'37805-6A0-A740\x00\x00', - b'37805-6A0-A750\x00\x00', - b'37805-6A0-A840\x00\x00', - b'37805-6A0-A850\x00\x00', - b'37805-6A0-A930\x00\x00', - b'37805-6A0-AF30\x00\x00', - b'37805-6A0-AG30\x00\x00', - b'37805-6B2-C520\x00\x00', - b'37805-6A0-C540\x00\x00', - b'37805-6A1-H650\x00\x00', - b'37805-6B2-A550\x00\x00', - b'37805-6B2-A560\x00\x00', - b'37805-6B2-A650\x00\x00', - b'37805-6B2-A660\x00\x00', - b'37805-6B2-A720\x00\x00', - b'37805-6B2-A810\x00\x00', - b'37805-6B2-A820\x00\x00', - b'37805-6B2-A920\x00\x00', - b'37805-6B2-M520\x00\x00', - b'37805-6B2-Y810\x00\x00', - b'37805-6M4-B730\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TVC-A910\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-6A7-A220\x00\x00', - b'28101-6A7-A230\x00\x00', - b'28101-6A7-A320\x00\x00', - b'28101-6A7-A330\x00\x00', - b'28101-6A7-A410\x00\x00', - b'28101-6A7-A510\x00\x00', - b'28101-6A7-A610\x00\x00', - b'28101-6A7-A710\x00\x00', - b'28101-6A9-H140\x00\x00', - b'28101-6A9-H420\x00\x00', - b'28102-6B8-A560\x00\x00', - b'28102-6B8-A570\x00\x00', - b'28102-6B8-A700\x00\x00', - b'28102-6B8-A800\x00\x00', - b'28102-6B8-C560\x00\x00', - b'28102-6B8-C570\x00\x00', - b'28102-6B8-M520\x00\x00', - b'28102-6B8-R700\x00\x00', - ], - (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ - b'46114-TVA-A060\x00\x00', - b'46114-TVA-A080\x00\x00', - b'46114-TVA-A120\x00\x00', - b'46114-TVA-A320\x00\x00', - b'46114-TVA-A050\x00\x00', - b'46114-TVE-H550\x00\x00', - b'46114-TVE-H560\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TVA-B040\x00\x00', - b'57114-TVA-B050\x00\x00', - b'57114-TVA-B060\x00\x00', - b'57114-TVA-B530\x00\x00', - b'57114-TVA-C040\x00\x00', - b'57114-TVA-C050\x00\x00', - b'57114-TVA-C060\x00\x00', - b'57114-TVA-C530\x00\x00', - b'57114-TVA-E520\x00\x00', - b'57114-TVE-H250\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TBX-H120\x00\x00', - b'39990-TVA-A140\x00\x00', - b'39990-TVA-A150\x00\x00', - b'39990-TVA-A160\x00\x00', - b'39990-TVA-A340\x00\x00', - b'39990-TVA-X030\x00\x00', - b'39990-TVA-X040\x00\x00', - b'39990-TVA,A150\x00\x00', # modified firmware - b'39990-TVE-H130\x00\x00', - ], - (Ecu.unknown, 0x18da3af1, None): [ - b'39390-TVA-A020\x00\x00', - b'39390-TVA-A120\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TBX-H230\x00\x00', - b'77959-TVA-A460\x00\x00', - b'77959-TVA-F330\x00\x00', - b'77959-TVA-H230\x00\x00', - b'77959-TVA-L420\x00\x00', - b'77959-TVA-X330\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TBX-H310\x00\x00', - b'78109-TVA-A010\x00\x00', - b'78109-TVA-A020\x00\x00', - b'78109-TVA-A030\x00\x00', - b'78109-TVA-A110\x00\x00', - b'78109-TVA-A120\x00\x00', - b'78109-TVA-A130\x00\x00', - b'78109-TVA-A210\x00\x00', - b'78109-TVA-A220\x00\x00', - b'78109-TVA-A230\x00\x00', - b'78109-TVA-A310\x00\x00', - b'78109-TVA-C010\x00\x00', - b'78109-TVA-L010\x00\x00', - b'78109-TVA-L210\x00\x00', - b'78109-TVA-R310\x00\x00', - b'78109-TVC-A010\x00\x00', - b'78109-TVC-A020\x00\x00', - b'78109-TVC-A030\x00\x00', - b'78109-TVC-A110\x00\x00', - b'78109-TVC-A130\x00\x00', - b'78109-TVC-A210\x00\x00', - b'78109-TVC-A220\x00\x00', - b'78109-TVC-A230\x00\x00', - b'78109-TVC-C010\x00\x00', - b'78109-TVC-C110\x00\x00', - b'78109-TVC-L010\x00\x00', - b'78109-TVC-L210\x00\x00', - b'78109-TVC-M510\x00\x00', - b'78109-TVC-YF10\x00\x00', - b'78109-TVE-H610\x00\x00', - b'78109-TWA-A210\x00\x00', - ], - (Ecu.hud, 0x18da61f1, None): [ - b'78209-TVA-A010\x00\x00', - b'78209-TVA-A110\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TBX-H140\x00\x00', - b'36802-TVA-A150\x00\x00', - b'36802-TVA-A160\x00\x00', - b'36802-TVA-A170\x00\x00', - b'36802-TVA-A330\x00\x00', - b'36802-TVC-A330\x00\x00', - b'36802-TVE-H070\x00\x00', - b'36802-TWA-A070\x00\x00', - b'36802-TWA-A080\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TBX-H130\x00\x00', - b'36161-TVA-A060\x00\x00', - b'36161-TVA-A330\x00\x00', - b'36161-TVC-A330\x00\x00', - b'36161-TVE-H050\x00\x00', - b'36161-TWA-A070\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TVA-A010\x00\x00', - b'38897-TVA-A020\x00\x00', - b'38897-TVA-A230\x00\x00', - b'38897-TVA-A240\x00\x00', - ], - }, - CAR.ACCORDH: { - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TWA-A120\x00\x00', - b'38897-TWD-J020\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TWA-A040\x00\x00', - b'57114-TWA-A050\x00\x00', - b'57114-TWA-A530\x00\x00', - b'57114-TWA-B520\x00\x00', - b'57114-TWB-H030\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TWA-A440\x00\x00', - b'77959-TWA-L420\x00\x00', - b'77959-TWB-H220\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TWA-A010\x00\x00', - b'78109-TWA-A020\x00\x00', - b'78109-TWA-A030\x00\x00', - b'78109-TWA-A110\x00\x00', - b'78109-TWA-A120\x00\x00', - b'78109-TWA-A130\x00\x00', - b'78109-TWA-A210\x00\x00', - b'78109-TWA-A220\x00\x00', - b'78109-TWA-A230\x00\x00', - b'78109-TWA-L010\x00\x00', - b'78109-TWA-L210\x00\x00', - b'78109-TWA-H210\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TWA-A910\x00\x00', - ], - (Ecu.hud, 0x18da61f1, None): [ - b'78209-TVA-A010\x00\x00', - b'78209-TVA-A110\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TWA-A070\x00\x00', - b'36161-TWA-A330\x00\x00', - b'36161-TWB-H040\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TWA-A070\x00\x00', - b'36802-TWA-A080\x00\x00', - b'36802-TWA-A330\x00\x00', - b'36802-TWB-H060\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TVA-A160\x00\x00', - b'39990-TVA-A150\x00\x00', - b'39990-TVA-A340\x00\x00', - b'39990-TWB-H120\x00\x00', - ], - }, - CAR.CIVIC: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5AA-A640\x00\x00', - b'37805-5AA-A650\x00\x00', - b'37805-5AA-A670\x00\x00', - b'37805-5AA-A680\x00\x00', - b'37805-5AA-A810\x00\x00', - b'37805-5AA-C640\x00\x00', - b'37805-5AA-C680\x00\x00', - b'37805-5AA-C820\x00\x00', - b'37805-5AA-L650\x00\x00', - b'37805-5AA-L660\x00\x00', - b'37805-5AA-L680\x00\x00', - b'37805-5AA-L690\x00\x00', - b'37805-5AA-L810\000\000', - b'37805-5AG-Q710\x00\x00', - b'37805-5AJ-A610\x00\x00', - b'37805-5AJ-A620\x00\x00', - b'37805-5AJ-L610\x00\x00', - b'37805-5BA-A310\x00\x00', - b'37805-5BA-A510\x00\x00', - b'37805-5BA-A740\x00\x00', - b'37805-5BA-A760\x00\x00', - b'37805-5BA-A930\x00\x00', - b'37805-5BA-A960\x00\x00', - b'37805-5BA-C860\x00\x00', - b'37805-5BA-L410\x00\x00', - b'37805-5BA-L760\x00\x00', - b'37805-5BA-L930\x00\x00', - b'37805-5BA-L940\x00\x00', - b'37805-5BA-L960\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-5CG-A040\x00\x00', - b'28101-5CG-A050\x00\x00', - b'28101-5CG-A070\x00\x00', - b'28101-5CG-A080\x00\x00', - b'28101-5CG-A320\x00\x00', - b'28101-5CG-A810\x00\x00', - b'28101-5CG-A820\x00\x00', - b'28101-5DJ-A040\x00\x00', - b'28101-5DJ-A060\x00\x00', - b'28101-5DJ-A510\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TBA-A540\x00\x00', - b'57114-TBA-A550\x00\x00', - b'57114-TBA-A560\x00\x00', - b'57114-TBA-A570\x00\x00', - b'57114-TEA-Q220\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TBA,A030\x00\x00', # modified firmware - b'39990-TBA-A030\x00\x00', - b'39990-TBG-A030\x00\x00', - b'39990-TEA-T020\x00\x00', - b'39990-TEG-A010\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TBA-A030\x00\x00', - b'77959-TBA-A040\x00\x00', - b'77959-TBG-A030\x00\x00', - b'77959-TEA-Q820\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TBA-A510\x00\x00', - b'78109-TBA-A520\x00\x00', - b'78109-TBA-A530\x00\x00', - b'78109-TBA-C520\x00\x00', - b'78109-TBC-A310\x00\x00', - b'78109-TBC-A320\x00\x00', - b'78109-TBC-A510\x00\x00', - b'78109-TBC-A520\x00\x00', - b'78109-TBC-A530\x00\x00', - b'78109-TBC-C510\x00\x00', - b'78109-TBC-C520\x00\x00', - b'78109-TBC-C530\x00\x00', - b'78109-TBH-A510\x00\x00', - b'78109-TBH-A530\x00\x00', - b'78109-TED-Q510\x00\x00', - b'78109-TEG-A310\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-TBA-A020\x00\x00', - b'36161-TBA-A030\x00\x00', - b'36161-TBA-A040\x00\x00', - b'36161-TBC-A020\x00\x00', - b'36161-TBC-A030\x00\x00', - b'36161-TED-Q320\x00\x00', - b'36161-TEG-A010\x00\x00', - b'36161-TEG-A020\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TBA-A010\x00\x00', - b'38897-TBA-A020\x00\x00', - ], - }, - CAR.CIVIC_BOSCH: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5AA-A940\x00\x00', - b'37805-5AA-A950\x00\x00', - b'37805-5AA-C950\x00\x00', - b'37805-5AA-L940\x00\x00', - b'37805-5AA-L950\x00\x00', - b'37805-5AG-Z910\x00\x00', - b'37805-5AJ-A750\x00\x00', - b'37805-5AJ-L750\x00\x00', - b'37805-5AK-T530\x00\x00', - b'37805-5AN-A750\x00\x00', - b'37805-5AN-A830\x00\x00', - b'37805-5AN-A840\x00\x00', - b'37805-5AN-A930\x00\x00', - b'37805-5AN-A940\x00\x00', - b'37805-5AN-A950\x00\x00', - b'37805-5AN-AG20\x00\x00', - b'37805-5AN-AH20\x00\x00', - b'37805-5AN-AJ30\x00\x00', - b'37805-5AN-AK10\x00\x00', - b'37805-5AN-AK20\x00\x00', - b'37805-5AN-AR10\x00\x00', - b'37805-5AN-AR20\x00\x00', - b'37805-5AN-CH20\x00\x00', - b'37805-5AN-E630\x00\x00', - b'37805-5AN-E720\x00\x00', - b'37805-5AN-E820\x00\x00', - b'37805-5AN-J820\x00\x00', - b'37805-5AN-L840\x00\x00', - b'37805-5AN-L930\x00\x00', - b'37805-5AN-L940\x00\x00', - b'37805-5AN-LF20\x00\x00', - b'37805-5AN-LH20\x00\x00', - b'37805-5AN-LJ20\x00\x00', - b'37805-5AN-LR20\x00\x00', - b'37805-5AN-LS20\x00\x00', - b'37805-5AW-G720\x00\x00', - b'37805-5AZ-E850\x00\x00', - b'37805-5AZ-G540\x00\x00', - b'37805-5AZ-G740\x00\x00', - b'37805-5AZ-G840\x00\x00', - b'37805-5BB-A530\x00\x00', - b'37805-5BB-A540\x00\x00', - b'37805-5BB-A630\x00\x00', - b'37805-5BB-A640\x00\x00', - b'37805-5BB-C540\x00\x00', - b'37805-5BB-C630\x00\x00', - b'37805-5BB-C640\x00\x00', - b'37805-5BB-L540\x00\x00', - b'37805-5BB-L630\x00\x00', - b'37805-5BB-L640\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-5CG-A920\x00\x00', - b'28101-5CG-AB10\x00\x00', - b'28101-5CG-C110\x00\x00', - b'28101-5CG-C220\x00\x00', - b'28101-5CG-C320\x00\x00', - b'28101-5CG-G020\x00\x00', - b'28101-5CG-L020\x00\x00', - b'28101-5CK-A130\x00\x00', - b'28101-5CK-A140\x00\x00', - b'28101-5CK-A150\x00\x00', - b'28101-5CK-C130\x00\x00', - b'28101-5CK-C140\x00\x00', - b'28101-5CK-C150\x00\x00', - b'28101-5CK-G210\x00\x00', - b'28101-5CK-J710\x00\x00', - b'28101-5CK-Q610\x00\x00', - b'28101-5DJ-A610\x00\x00', - b'28101-5DJ-A710\x00\x00', - b'28101-5DV-E330\x00\x00', - b'28101-5DV-E610\x00\x00', - b'28101-5DV-E820\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TBG-A330\x00\x00', - b'57114-TBG-A340\x00\x00', - b'57114-TBG-A350\x00\x00', - b'57114-TGG-A340\x00\x00', - b'57114-TGG-C320\x00\x00', - b'57114-TGG-G320\x00\x00', - b'57114-TGG-L320\x00\x00', - b'57114-TGG-L330\x00\x00', - b'57114-TGK-T320\x00\x00', - b'57114-TGL-G330\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TBA-C020\x00\x00', - b'39990-TBA-C120\x00\x00', - b'39990-TEA-T820\x00\x00', - b'39990-TEZ-T020\x00\x00', - b'39990-TGG-A020\x00\x00', - b'39990-TGG-A120\x00\x00', - b'39990-TGG-J510\x00\x00', - b'39990-TGL-E130\x00\x00', - b'39990-TGN-E120\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TBA-A060\x00\x00', - b'77959-TBG-A050\x00\x00', - b'77959-TEA-G020\x00\x00', - b'77959-TGG-A020\x00\x00', - b'77959-TGG-A030\x00\x00', - b'77959-TGG-E010\x00\x00', - b'77959-TGG-G010\x00\x00', - b'77959-TGG-G110\x00\x00', - b'77959-TGG-J320\x00\x00', - b'77959-TGG-Z820\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TBA-A110\x00\x00', - b'78109-TBA-A910\x00\x00', - b'78109-TBA-C340\x00\x00', - b'78109-TBA-C910\x00\x00', - b'78109-TBC-A740\x00\x00', - b'78109-TBC-C540\x00\x00', - b'78109-TBG-A110\x00\x00', - b'78109-TBH-A710\x00\x00', - b'78109-TEG-A720\x00\x00', - b'78109-TFJ-G020\x00\x00', - b'78109-TGG-9020\x00\x00', - b'78109-TGG-A210\x00\x00', - b'78109-TGG-A220\x00\x00', - b'78109-TGG-A310\x00\x00', - b'78109-TGG-A320\x00\x00', - b'78109-TGG-A330\x00\x00', - b'78109-TGG-A610\x00\x00', - b'78109-TGG-A620\x00\x00', - b'78109-TGG-A810\x00\x00', - b'78109-TGG-A820\x00\x00', - b'78109-TGG-C220\x00\x00', - b'78109-TGG-E110\x00\x00', - b'78109-TGG-G030\x00\x00', - b'78109-TGG-G230\x00\x00', - b'78109-TGG-G410\x00\x00', - b'78109-TGK-Z410\x00\x00', - b'78109-TGL-G120\x00\x00', - b'78109-TGL-G130\x00\x00', - b'78109-TGL-G210\x00\x00', - b'78109-TGL-G230\x00\x00', - b'78109-TGL-GM10\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TBA-A150\x00\x00', - b'36802-TBA-A160\x00\x00', - b'36802-TFJ-G060\x00\x00', - b'36802-TGG-A050\x00\x00', - b'36802-TGG-A060\x00\x00', - b'36802-TGG-A130\x00\x00', - b'36802-TGG-G040\x00\x00', - b'36802-TGG-G130\x00\x00', - b'36802-TGK-Q120\x00\x00', - b'36802-TGL-G040\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TBA-A130\x00\x00', - b'36161-TBA-A140\x00\x00', - b'36161-TFJ-G070\x00\x00', - b'36161-TGG-A060\x00\x00', - b'36161-TGG-A080\x00\x00', - b'36161-TGG-A120\x00\x00', - b'36161-TGG-G050\x00\x00', - b'36161-TGG-G130\x00\x00', - b'36161-TGG-G140\x00\x00', - b'36161-TGK-Q120\x00\x00', - b'36161-TGL-G050\x00\x00', - b'36161-TGL-G070\x00\x00', - b'36161-TGG-G070\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TBA-A110\x00\x00', - b'38897-TBA-A020\x00\x00', - ], - (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ - b'39494-TGL-G030\x00\x00', - ], - }, - CAR.CIVIC_BOSCH_DIESEL: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-59N-G630\x00\x00', - b'37805-59N-G830\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-59Y-G220\x00\x00', - b'28101-59Y-G620\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TGN-E320\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TFK-G020\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TFK-G210\x00\x00', - b'77959-TGN-G220\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TFK-G020\x00\x00', - b'78109-TGN-G120\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TFK-G130\x00\x00', - b'36802-TGN-G130\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TGN-E010\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TFK-G130\x00\x00', - b'36161-TGN-G130\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TBA-A020\x00\x00', - ], - }, - CAR.CRV: { - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-T1W-A230\x00\x00', - b'57114-T1W-A240\x00\x00', - b'57114-TFF-A940\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-T0A-A230\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T1W-A210\x00\x00', - b'78109-T1W-C210\x00\x00', - b'78109-T1X-A210\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-T1W-A830\x00\x00', - b'36161-T1W-C830\x00\x00', - b'36161-T1X-A830\x00\x00', - ], - }, - CAR.CRV_5G: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5PA-AH20\x00\x00', - b'37805-5PA-3060\x00\x00', - b'37805-5PA-3080\x00\x00', - b'37805-5PA-3180\x00\x00', - b'37805-5PA-4050\x00\x00', - b'37805-5PA-4150\x00\x00', - b'37805-5PA-6520\x00\x00', - b'37805-5PA-6530\x00\x00', - b'37805-5PA-6630\x00\x00', - b'37805-5PA-6640\x00\x00', - b'37805-5PA-7630\x00\x00', - b'37805-5PA-9630\x00\x00', - b'37805-5PA-9640\x00\x00', - b'37805-5PA-9730\x00\x00', - b'37805-5PA-9830\x00\x00', - b'37805-5PA-9840\x00\x00', - b'37805-5PA-A650\x00\x00', - b'37805-5PA-A670\x00\x00', - b'37805-5PA-A680\x00\x00', - b'37805-5PA-A850\x00\x00', - b'37805-5PA-A870\x00\x00', - b'37805-5PA-A880\x00\x00', - b'37805-5PA-A890\x00\x00', - b'37805-5PA-AB10\x00\x00', - b'37805-5PA-AD10\x00\x00', - b'37805-5PA-AF20\x00\x00', - b'37805-5PA-C680\x00\x00', - b'37805-5PD-Q630\x00\x00', - b'37805-5PF-F730\x00\x00', - b'37805-5PF-M630\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-5RG-A020\x00\x00', - b'28101-5RG-A030\x00\x00', - b'28101-5RG-A040\x00\x00', - b'28101-5RG-A120\x00\x00', - b'28101-5RG-A220\x00\x00', - b'28101-5RH-A020\x00\x00', - b'28101-5RH-A030\x00\x00', - b'28101-5RH-A040\x00\x00', - b'28101-5RH-A120\x00\x00', - b'28101-5RH-A220\x00\x00', - b'28101-5RL-Q010\x00\x00', - b'28101-5RM-F010\x00\x00', - b'28101-5RM-K010\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TLA-A040\x00\x00', - b'57114-TLA-A050\x00\x00', - b'57114-TLA-A060\x00\x00', - b'57114-TLB-A830\x00\x00', - b'57114-TMC-Z040\x00\x00', - b'57114-TMC-Z050\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TLA-A040\x00\x00', - b'39990-TLA-A110\x00\x00', - b'39990-TLA-A220\x00\x00', - b'39990-TLA,A040\x00\x00', # modified firmware - b'39990-TME-T030\x00\x00', - b'39990-TME-T120\x00\x00', - b'39990-TMT-T010\x00\x00', - ], - (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ - b'46114-TLA-A040\x00\x00', - b'46114-TLA-A050\x00\x00', - b'46114-TLA-A930\x00\x00', - b'46114-TMC-U020\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TLA-A110\x00\x00', - b'78109-TLA-A120\x00\x00', - b'78109-TLA-A210\x00\x00', - b'78109-TLA-A220\x00\x00', - b'78109-TLA-C020\x00\x00', - b'78109-TLA-C110\x00\x00', - b'78109-TLA-C210\x00\x00', - b'78109-TLA-C310\x00\x00', - b'78109-TLB-A020\x00\x00', - b'78109-TLB-A110\x00\x00', - b'78109-TLB-A120\x00\x00', - b'78109-TLB-A210\x00\x00', - b'78109-TLB-A220\x00\x00', - b'78109-TMC-Q210\x00\x00', - b'78109-TMM-F210\x00\x00', - b'78109-TMM-M110\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TLA-A010\x00\x00', - b'38897-TLA-A110\x00\x00', - b'38897-TNY-G010\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TLA-A040\x00\x00', - b'36802-TLA-A050\x00\x00', - b'36802-TLA-A060\x00\x00', - b'36802-TMC-Q040\x00\x00', - b'36802-TMC-Q070\x00\x00', - b'36802-TNY-A030\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TLA-A060\x00\x00', - b'36161-TLA-A070\x00\x00', - b'36161-TLA-A080\x00\x00', - b'36161-TMC-Q020\x00\x00', - b'36161-TMC-Q030\x00\x00', - b'36161-TMC-Q040\x00\x00', - b'36161-TNY-A020\x00\x00', - b'36161-TNY-A030\x00\x00', - b'36161-TNY-A040\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TLA-A240\x00\x00', - b'77959-TLA-A250\x00\x00', - b'77959-TLA-A320\x00\x00', - b'77959-TLA-A410\x00\x00', - b'77959-TLA-A420\x00\x00', - b'77959-TLA-Q040\x00\x00', - b'77959-TLA-Z040\x00\x00', - b'77959-TMM-F040\x00\x00', - ], - }, - CAR.CRV_EU: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-R5Z-G740\x00\x00', - b'37805-R5Z-G780\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [b'57114-T1V-G920\x00\x00'], - (Ecu.fwdRadar, 0x18dab0f1, None): [b'36161-T1V-G520\x00\x00'], - (Ecu.shiftByWire, 0x18da0bf1, None): [b'54008-T1V-G010\x00\x00'], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-5LH-E120\x00\x00', - b'28103-5LH-E100\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T1V-G020\x00\x00', - b'78109-T1B-3050\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [b'77959-T1G-G940\x00\x00'], - }, - CAR.CRV_HYBRID: { - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TPA-G020\x00\x00', - b'57114-TPG-A020\x00\x00', - b'57114-TMB-H030\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TPA-G030\x00\x00', - b'39990-TPG-A020\x00\x00', - b'39990-TMA-H020\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TMA-H110\x00\x00', - b'38897-TPG-A110\x00\x00', - b'38897-TPG-A210\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TMB-H510\x00\x00', - b'54008-TMB-H610\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TMB-H040\x00\x00', - b'36161-TPA-E050\x00\x00', - b'36161-TPG-A030\x00\x00', - b'36161-TPG-A040\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TMB-H220\x00\x00', - b'78109-TPA-G520\x00\x00', - b'78109-TPG-A110\x00\x00', - b'78109-TPG-A210\x00\x00', - ], - (Ecu.hud, 0x18da61f1, None): [ - b'78209-TLA-X010\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TPA-E040\x00\x00', - b'36802-TPG-A020\x00\x00', - b'36802-TMB-H040\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TLA-C320\x00\x00', - b'77959-TLA-C410\x00\x00', - b'77959-TLA-C420\x00\x00', - b'77959-TLA-G220\x00\x00', - b'77959-TLA-H240\x00\x00', - ], - }, - CAR.FIT: { - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-T5R-L020\x00\x00', - b'57114-T5R-L220\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-T5R-C020\x00\x00', - b'39990-T5R-C030\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-T5A-J010\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T5A-A210\x00\x00', - b'78109-T5A-A410\x00\x00', - b'78109-T5A-A420\x00\x00', - b'78109-T5A-A910\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-T5R-A040\x00\x00', - b'36161-T5R-A240\x00\x00', - b'36161-T5R-A520\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-T5R-A230\x00\x00', - ], - }, - CAR.FREED: { - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TDK-J010\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TDK-J050\x00\x00', - b'39990-TDK-N020\x00\x00', - ], - # TODO: vsa is "essential" for fpv2 but doesn't appear on some models - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TDK-J120\x00\x00', - b'57114-TDK-J330\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TDK-J310\x00\x00', - b'78109-TDK-J320\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-TDK-J070\x00\x00', - b'36161-TDK-J080\x00\x00', - b'36161-TDK-J530\x00\x00', - ], - }, - CAR.ODYSSEY: { - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-THR-A010\x00\x00', - b'38897-THR-A020\x00\x00', - ], - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5MR-4080\x00\x00', - b'37805-5MR-A240\x00\x00', - b'37805-5MR-A250\x00\x00', - b'37805-5MR-A310\x00\x00', - b'37805-5MR-A740\x00\x00', - b'37805-5MR-A750\x00\x00', - b'37805-5MR-A840\x00\x00', - b'37805-5MR-C620\x00\x00', - b'37805-5MR-D530\x00\x00', - b'37805-5MR-K730\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-THR-A020\x00\x00', - b'39990-THR-A030\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-THR-A010\x00\x00', - b'77959-THR-A110\x00\x00', - b'77959-THR-X010\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-THR-A020\x00\x00', - b'36161-THR-A030\x00\x00', - b'36161-THR-A110\x00\x00', - b'36161-THR-A720\x00\x00', - b'36161-THR-A730\x00\x00', - b'36161-THR-A810\x00\x00', - b'36161-THR-A910\x00\x00', - b'36161-THR-C010\x00\x00', - b'36161-THR-D110\x00\x00', - b'36161-THR-K020\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-5NZ-A110\x00\x00', - b'28101-5NZ-A310\x00\x00', - b'28101-5NZ-C310\x00\x00', - b'28102-5MX-A001\x00\x00', - b'28102-5MX-A600\x00\x00', - b'28102-5MX-A610\x00\x00', - b'28102-5MX-A710\x00\x00', - b'28102-5MX-A900\x00\x00', - b'28102-5MX-A910\x00\x00', - b'28102-5MX-C001\x00\x00', - b'28102-5MX-D001\x00\x00', - b'28102-5MX-D710\x00\x00', - b'28102-5MX-K610\x00\x00', - b'28103-5NZ-A100\x00\x00', - b'28103-5NZ-A300\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-THR-A040\x00\x00', - b'57114-THR-A110\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-THR-A220\x00\x00', - b'78109-THR-A230\x00\x00', - b'78109-THR-A420\x00\x00', - b'78109-THR-A430\x00\x00', - b'78109-THR-A720\x00\x00', - b'78109-THR-A820\x00\x00', - b'78109-THR-A830\x00\x00', - b'78109-THR-AB20\x00\x00', - b'78109-THR-AB30\x00\x00', - b'78109-THR-AB40\x00\x00', - b'78109-THR-AC20\x00\x00', - b'78109-THR-AC30\x00\x00', - b'78109-THR-AC40\x00\x00', - b'78109-THR-AC50\x00\x00', - b'78109-THR-AD30\x00\x00', - b'78109-THR-AE20\x00\x00', - b'78109-THR-AE30\x00\x00', - b'78109-THR-AE40\x00\x00', - b'78109-THR-AK10\x00\x00', - b'78109-THR-AL10\x00\x00', - b'78109-THR-AN10\x00\x00', - b'78109-THR-C220\x00\x00', - b'78109-THR-C330\x00\x00', - b'78109-THR-CE20\x00\x00', - b'78109-THR-DA20\x00\x00', - b'78109-THR-DA30\x00\x00', - b'78109-THR-DA40\x00\x00', - b'78109-THR-K120\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-THR-A020\x00\x00', - ], - }, - CAR.ODYSSEY_CHN: { - (Ecu.eps, 0x18da30f1, None): [ - b'39990-T6D-H220\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-T6A-J010\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T6A-F310\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-T6A-P040\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-T6A-P110\x00\x00', - ], - }, - CAR.PILOT: { - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TG7-A520\x00\x00', - b'54008-TG7-A530\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-5EY-A050\x00\x00', - b'28101-5EY-A100\x00\x00', - b'28101-5EZ-A050\x00\x00', - b'28101-5EZ-A060\x00\x00', - b'28101-5EZ-A100\x00\x00', - b'28101-5EZ-A210\x00\x00', - b'28101-5EZ-A600\x00\x00', - b'28101-5EZ-A430\x00\x00', - b'28101-5EZ-A700\x00\x00', - ], - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-RLV-4060\x00\x00', - b'37805-RLV-4070\x00\x00', - b'37805-RLV-A830\x00\x00', - b'37805-RLV-A840\x00\x00', - b'37805-RLV-C430\x00\x00', - b'37805-RLV-C510\x00\x00', - b'37805-RLV-C520\x00\x00', - b'37805-RLV-C530\x00\x00', - b'37805-RLV-C910\x00\x00', - b'37805-RLV-B220\x00\x00', - b'37805-RLV-B210\x00\x00', - b'37805-RLV-L160\x00\x00', - b'37805-RLV-B420\x00\x00', - b'37805-RLV-F120\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TG7-A030\x00\x00', - b'38897-TG7-A040\x00\x00', - b'38897-TG7-A110\x00\x00', - b'38897-TG7-A210\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TG7-A030\x00\x00', - b'39990-TG7-A040\x00\x00', - b'39990-TG7-A060\x00\x00', - b'39990-TG7-A070\x00\x00', - b'39990-TGS-A230\x00\x00', - b'39990-TGS-A320\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-TG7-A310\x00\x00', - b'36161-TG7-A520\x00\x00', - b'36161-TG7-A630\x00\x00', - b'36161-TG7-A720\x00\x00', - b'36161-TG7-A820\x00\x00', - b'36161-TG7-A930\x00\x00', - b'36161-TG7-C520\x00\x00', - b'36161-TG7-D520\x00\x00', - b'36161-TG7-D630\x00\x00', - b'36161-TG7-Y630\x00\x00', - b'36161-TG8-A520\x00\x00', - b'36161-TG8-A630\x00\x00', - b'36161-TG8-A720\x00\x00', - b'36161-TG8-A830\x00\x00', - b'36161-TGS-A130\x00\x00', - b'36161-TGT-A030\x00\x00', - b'36161-TGT-A130\x00\x00', - b'36161-TGS-A030\x00\x00', - b'36161-TGS-A220\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TG7-A020\x00\x00', - b'77959-TG7-A110\x00\x00', - b'77959-TG7-A210\x00\x00', - b'77959-TG7-Y210\x00\x00', - b'77959-TGS-A010\x00\x00', - b'77959-TGS-A110\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TG7-A040\x00\x00', - b'78109-TG7-A050\x00\x00', - b'78109-TG7-A420\x00\x00', - b'78109-TG7-A520\x00\x00', - b'78109-TG7-A720\x00\x00', - b'78109-TG7-AJ10\x00\x00', - b'78109-TG7-AJ20\x00\x00', - b'78109-TG7-AK10\x00\x00', - b'78109-TG7-AK20\x00\x00', - b'78109-TG7-AM20\x00\x00', - b'78109-TG7-AP10\x00\x00', - b'78109-TG7-AP20\x00\x00', - b'78109-TG7-AS20\x00\x00', - b'78109-TG7-AT20\x00\x00', - b'78109-TG7-AU20\x00\x00', - b'78109-TG7-AX20\x00\x00', - b'78109-TG7-D020\x00\x00', - b'78109-TG7-DJ10\x00\x00', - b'78109-TG7-YK20\x00\x00', - b'78109-TG8-A420\x00\x00', - b'78109-TG8-A520\x00\x00', - b'78109-TG8-AJ10\x00\x00', - b'78109-TG8-AJ20\x00\x00', - b'78109-TG8-AK20\x00\x00', - b'78109-TGS-AK20\x00\x00', - b'78109-TGS-AP20\x00\x00', - b'78109-TGT-AJ20\x00\x00', - b'78109-TGT-AK30\x00\x00', - b'78109-TGS-AT20\x00\x00', - b'78109-TGS-AX20\x00\x00', - b'78109-TGS-AJ20\x00\x00', - b'78109-TGS-AC10\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TG7-A130\x00\x00', - b'57114-TG7-A140\x00\x00', - b'57114-TG7-A230\x00\x00', - b'57114-TG7-A240\x00\x00', - b'57114-TG7-A630\x00\x00', - b'57114-TG7-A730\x00\x00', - b'57114-TG8-A140\x00\x00', - b'57114-TG8-A240\x00\x00', - b'57114-TG8-A630\x00\x00', - b'57114-TG8-A730\x00\x00', - b'57114-TGS-A530\x00\x00', - b'57114-TGT-A530\x00\x00', - ], - }, - CAR.ACURA_RDX: { - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TX5-A220\x00\x00', - b'57114-TX4-A220\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-TX5-A030\x00\x00', - b'36161-TX4-A030\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TX4-C010\x00\x00', - b'77959-TX4-B010\x00\x00', - b'77959-TX4-C020\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TX5-A310\x00\x00', - b'78109-TX4-A210\x00\x00', - b'78109-TX4-A310\x00\x00', - ], - }, - CAR.ACURA_RDX_3G: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5YF-A130\x00\x00', - b'37805-5YF-A230\x00\x00', - b'37805-5YF-A320\x00\x00', - b'37805-5YF-A330\x00\x00', - b'37805-5YF-A420\x00\x00', - b'37805-5YF-A430\x00\x00', - b'37805-5YF-A750\x00\x00', - b'37805-5YF-A850\x00\x00', - b'37805-5YF-A870\x00\x00', - b'37805-5YF-AD20\x00\x00', - b'37805-5YF-C210\x00\x00', - b'37805-5YF-C220\x00\x00', - b'37805-5YF-C410\000\000', - b'37805-5YF-C420\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TJB-A030\x00\x00', - b'57114-TJB-A040\x00\x00', - b'57114-TJB-A120\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TJB-A040\x00\x00', - b'36802-TJB-A050\x00\x00', - b'36802-TJB-A540\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TJB-A040\x00\x00', - b'36161-TJB-A530\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TJB-A520\x00\x00', - b'54008-TJB-A530\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28102-5YK-A610\x00\x00', - b'28102-5YK-A620\x00\x00', - b'28102-5YK-A630\x00\x00', - b'28102-5YK-A700\x00\x00', - b'28102-5YK-A711\x00\x00', - b'28102-5YK-A800\x00\x00', - b'28102-5YL-A620\x00\x00', - b'28102-5YL-A700\x00\x00', - b'28102-5YL-A711\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TJB-A140\x00\x00', - b'78109-TJB-A240\x00\x00', - b'78109-TJB-A420\x00\x00', - b'78109-TJB-AB10\x00\x00', - b'78109-TJB-AD10\x00\x00', - b'78109-TJB-AF10\x00\x00', - b'78109-TJB-AQ20\x00\x00', - b'78109-TJB-AR10\x00\x00', - b'78109-TJB-AS10\000\000', - b'78109-TJB-AU10\x00\x00', - b'78109-TJB-AW10\x00\x00', - b'78109-TJC-A420\x00\x00', - b'78109-TJC-AA10\x00\x00', - b'78109-TJC-AD10\x00\x00', - b'78109-TJC-AF10\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TJB-A040\x00\x00', - b'77959-TJB-A120\x00\x00', - b'77959-TJB-A210\x00\x00', - ], - (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ - b'46114-TJB-A040\x00\x00', - b'46114-TJB-A050\x00\x00', - b'46114-TJB-A060\x00\x00', - b'46114-TJB-A120\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TJB-A040\x00\x00', - b'38897-TJB-A110\x00\x00', - b'38897-TJB-A120\x00\x00', - b'38897-TJB-A220\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TJB-A030\x00\x00', - b'39990-TJB-A040\x00\x00', - b'39990-TJB-A070\x00\x00', - b'39990-TJB-A130\x00\x00', - ], - }, - CAR.RIDGELINE: { - (Ecu.eps, 0x18da30f1, None): [ - b'39990-T6Z-A020\x00\x00', - b'39990-T6Z-A030\x00\x00', - b'39990-T6Z-A050\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-T6Z-A020\x00\x00', - b'36161-T6Z-A310\x00\x00', - b'36161-T6Z-A420\x00\x00', - b'36161-T6Z-A520\x00\x00', - b'36161-T6Z-A620\x00\x00', - b'36161-TJZ-A120\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-T6Z-A010\x00\x00', - b'38897-T6Z-A110\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T6Z-A420\x00\x00', - b'78109-T6Z-A510\x00\x00', - b'78109-T6Z-A710\x00\x00', - b'78109-T6Z-A810\x00\x00', - b'78109-T6Z-A910\x00\x00', - b'78109-T6Z-AA10\x00\x00', - b'78109-T6Z-C620\x00\x00', - b'78109-T6Z-C910\x00\x00', - b'78109-TJZ-A510\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-T6Z-A020\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-T6Z-A120\x00\x00', - b'57114-T6Z-A130\x00\x00', - b'57114-T6Z-A520\x00\x00', - b'57114-TJZ-A520\x00\x00', - ], - }, - CAR.INSIGHT: { - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TXM-A040\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TXM-A070\x00\x00', - b'36802-TXM-A080\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TXM-A050\x00\x00', - b'36161-TXM-A060\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TXM-A230\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TXM-A030\x00\x00', - b'57114-TXM-A040\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TWA-A910\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TXM-A020\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TXM-A010\x00\x00', - b'78109-TXM-A020\x00\x00', - b'78109-TXM-A110\x00\x00', - b'78109-TXM-C010\x00\x00', - b'78109-TXM-A030\x00\x00', - ], - }, - CAR.HRV: { - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-T7A-A010\x00\x00', - b'38897-T7A-A110\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-THX-A020\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-T7A-A140\x00\x00', - b'36161-T7A-A240\x00\x00', - b'36161-T7A-C440\x00\x00', - b'36161-T7A-A040\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-T7A-A230\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-THX-A110\x00\x00', - b'78109-THX-A120\x00\x00', - b'78109-THX-A210\x00\x00', - b'78109-THX-A220\x00\x00', - b'78109-THX-C220\x00\x00', - b'78109-THW-A110\x00\x00', - ], - }, - CAR.HRV_3G: { - (Ecu.eps, 0x18DA30F1, None): [ - b'39990-3W0-A030\x00\x00', - ], - (Ecu.gateway, 0x18DAEFF1, None): [ - b'38897-3W1-A010\x00\x00', - ], - (Ecu.srs, 0x18DA53F1, None): [ - b'77959-3V0-A820\x00\x00', - ], - (Ecu.combinationMeter, 0x18DA60F1, None): [ - b'78108-3V1-A220\x00\x00', - ], - (Ecu.vsa, 0x18DA28F1, None): [ - b'57114-3W0-A040\x00\x00', - ], - (Ecu.transmission, 0x18DA1EF1, None): [ - b'28101-6EH-A010\x00\x00', - ], - (Ecu.programmedFuelInjection, 0x18DA10F1, None): [ - b'37805-6CT-A710\x00\x00', - ], - (Ecu.electricBrakeBooster, 0x18DA2BF1, None): [ - b'46114-3W0-A020\x00\x00', - ], - }, - CAR.ACURA_ILX: { - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TX6-A010\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-TV9-A140\x00\x00', - b'36161-TX6-A030\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TX6-A230\x00\x00', - b'77959-TX6-C210\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T3R-A120\x00\x00', - b'78109-T3R-A410\x00\x00', - b'78109-TV9-A510\x00\x00', - ], - }, - CAR.HONDA_E:{ - (Ecu.eps, 0x18DA30F1, None):[ - b'39990-TYF-N030\x00\x00' - ], - (Ecu.gateway, 0x18DAEFF1, None):[ - b'38897-TYF-E140\x00\x00' - ], - (Ecu.shiftByWire, 0x18DA0BF1, None):[ - b'54008-TYF-E010\x00\x00' - ], - (Ecu.srs, 0x18DA53F1, None):[ - b'77959-TYF-G430\x00\x00' - ], - (Ecu.combinationMeter, 0x18DA60F1, None):[ - b'78108-TYF-G610\x00\x00' - ], - (Ecu.fwdRadar, 0x18DAB0F1, None):[ - b'36802-TYF-E030\x00\x00' - ], - (Ecu.fwdCamera, 0x18DAB5F1, None):[ - b'36161-TYF-E020\x00\x00' - ], - (Ecu.vsa, 0x18DA28F1, None):[ - b'57114-TYF-E030\x00\x00' - ], - }, - CAR.CIVIC_2022: { - (Ecu.eps, 0x18DA30F1, None): [ - b'39990-T39-A130\x00\x00', - b'39990-T43-J020\x00\x00', - b'39990-T24-T120\x00\x00', - ], - (Ecu.gateway, 0x18DAEFF1, None): [ - b'38897-T20-A020\x00\x00', - b'38897-T20-A510\x00\x00', - b'38897-T21-A010\x00\x00', - b'38897-T20-A210\x00\x00', - b'38897-T20-A310\x00\x00', - b'38897-T24-Z120\x00\x00', - ], - (Ecu.srs, 0x18DA53F1, None): [ - b'77959-T20-A970\x00\x00', - b'77959-T47-A940\x00\x00', - b'77959-T47-A950\x00\x00', - b'77959-T20-M820\x00\x00', - b'77959-T20-A980\x00\x00', - ], - (Ecu.combinationMeter, 0x18DA60F1, None): [ - b'78108-T21-A220\x00\x00', - b'78108-T21-A620\x00\x00', - b'78108-T23-A110\x00\x00', - b'78108-T21-A230\x00\x00', - b'78108-T22-A020\x00\x00', - b'78108-T21-MB10\x00\x00', - b'78108-T21-A740\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-T20-A070\x00\x00', - b'36161-T20-A080\x00\x00', - b'36161-T20-A060\x00\x00', - b'36161-T47-A070\x00\x00', - b'36161-T24-T070\x00\x00', - ], - (Ecu.vsa, 0x18DA28F1, None): [ - b'57114-T20-AB40\x00\x00', - b'57114-T43-JB30\x00\x00', - b'57114-T24-TB30\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-65D-A020\x00\x00', - b'28101-65D-A120\x00\x00', - b'28101-65H-A020\x00\x00', - b'28101-65H-A120\x00\x00', - b'28101-65J-N010\x00\x00', - ], - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-64L-A540\x00\x00', - b'37805-64S-A540\x00\x00', - b'37805-64S-A720\x00\x00', - b'37805-64A-A540\x00\x00', - b'37805-64A-A620\x00\x00', - b'37805-64D-P510\x00\x00', - b'37805-64S-AA10\x00\x00', - ], - }, -} DBC = { CAR.ACCORD: dbc_dict('honda_accord_2018_can_generated', None), diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py new file mode 100644 index 0000000000..269c3845b9 --- /dev/null +++ b/selfdrive/car/hyundai/fingerprints.py @@ -0,0 +1,1679 @@ +# ruff: noqa: E501 +from cereal import car +from openpilot.selfdrive.car.hyundai.values import CAR + +Ecu = car.CarParams.Ecu + +FINGERPRINTS = { + CAR.HYUNDAI_GENESIS: [{ + 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 + }, + { + 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1378: 4, 1379: 8, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 + }, + { + 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1268: 8, 1280: 1, 1281: 3, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1437: 8, 1456: 4 + }, + { + 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1378: 4, 1379: 8, 1384: 5, 1407: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4 + }, + { + 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4 + }], + CAR.SANTA_FE: [{ + 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1379: 8, 1384: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1456: 4, 1470: 8 + }, + { + 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 6, 764: 8, 809: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1155: 8, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1180: 8, 1183: 8, 1186: 2, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1384: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1456: 4, 1470: 8, 1988: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8 + }, + { + 67: 8, 68: 8, 80: 4, 160: 8, 161: 8, 272: 8, 288: 4, 339: 8, 356: 8, 357: 8, 399: 8, 544: 8, 608: 8, 672: 8, 688: 5, 704: 1, 790: 8, 809: 8, 848: 8, 880: 8, 898: 8, 900: 8, 901: 8, 904: 8, 1056: 8, 1064: 8, 1065: 8, 1072: 8, 1075: 8, 1087: 8, 1088: 8, 1151: 8, 1200: 8, 1201: 8, 1232: 4, 1264: 8, 1265: 8, 1266: 8, 1296: 8, 1306: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1348: 8, 1349: 8, 1369: 8, 1370: 8, 1371: 8, 1407: 8, 1415: 8, 1419: 8, 1440: 8, 1442: 4, 1461: 8, 1470: 8 + }], + CAR.SONATA: [ + {67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 549: 8, 550: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 8, 865: 8, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 908: 8, 909: 8, 912: 7, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1089: 5, 1096: 8, 1107: 5, 1108: 8, 1114: 8, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1170: 8, 1173: 8, 1180: 8, 1183: 8, 1184: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1268: 8, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1330: 8, 1339: 8, 1342: 6, 1343: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1379: 8, 1384: 8, 1394: 8, 1407: 8, 1419: 8, 1427: 6, 1446: 8, 1456: 4, 1460: 8, 1470: 8, 1485: 8, 1504: 3, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8}, + ], + CAR.SONATA_LF: [ + {66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8}, + ], + CAR.KIA_SORENTO: [{ + 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1384: 8, 1407: 8, 1411: 8, 1419: 8, 1425: 2, 1427: 6, 1444: 8, 1456: 4, 1470: 8, 1489: 1 + }], + CAR.KIA_STINGER: [{ + 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 576: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 4, 1379: 8, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1456: 4, 1470: 8 + }], + CAR.GENESIS_G80: [{ + 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1024: 2, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1191: 2, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1456: 4, 1470: 8 + }, + { + 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 546: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1437: 8, 1456: 4, 1470: 8 + }, + { + 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1162: 8, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1193: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4, 1470: 8 + }], + CAR.GENESIS_G90: [{ + 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1162: 4, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1456: 4, 1470: 8, 1988: 8, 2000: 8, 2003: 8, 2004: 8, 2005: 8, 2008: 8, 2011: 8, 2012: 8, 2013: 8 + }], + CAR.IONIQ_EV_2020: [{ + 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 7, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 + }], + CAR.IONIQ: [{ + 68:8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 8, 576:8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1473: 8, 1476: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 + }], + CAR.KONA_EV: [{ + 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 549: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1307: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1378: 4, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 1157: 4, 1193: 8, 1379: 8, 1988: 8, 1996: 8 + }], + CAR.KONA_EV_2022: [{ + 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1069: 8, 1078: 4, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1173: 8, 1183: 8, 1188: 8, 1191: 2, 1193: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1339: 8, 1342: 8, 1343: 8, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1446: 8, 1456: 4, 1470: 8, 1473: 8, 1485: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8 + }], + CAR.KIA_NIRO_EV: [{ + 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 516: 8, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1193: 8, 1225: 8, 1260: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8 + }], + CAR.KIA_OPTIMA_H: [{ + 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1168: 7, 1173: 8, 1236: 2, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1371: 8, 1407: 8, 1419: 8, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1476: 8, 1535: 8 + }, + { + 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 576: 8, 593: 8, 688: 5, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 909: 8, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1168: 7, 1173: 8, 1180: 8, 1186: 2, 1191: 2, 1265: 4, 1268: 8, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1371: 8, 1407: 8, 1419: 8, 1420: 8, 1425: 2, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1476: 8, 1535: 8 + }], + CAR.PALISADE: [{ + 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 547: 8, 548: 8, 549: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1123: 8, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1180: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1384: 8, 1407: 8, 1419: 8, 1427: 6, 1456: 4, 1470: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8 + }], +} + + +FW_VERSIONS = { + CAR.AZERA_6TH_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00IG__ SCC F-CU- 1.00 1.00 99110-G8100 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00IG MDPS C 1.00 1.02 56310G8510\x00 4IGSC103', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00IG MFC AT MES LHD 1.00 1.04 99211-G8100 200511', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U912\x00\x00\x00\x00\x00\x00SIG0M35MH0\xa4 |.', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81641KA051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.AZERA_HEV_6TH_GEN: { + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029', + b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.00 99211-G8000 180903', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00IG MDPS C 1.00 1.00 56310M9600\x00 4IHSC100', + b'\xf1\x00IG MDPS C 1.00 1.01 56310M9350\x00 4IH8C101', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00IGhe SCC FHCUP 1.00 1.00 99110-M9100 ', + b'\xf1\x00IGhe SCC FHCUP 1.00 1.01 99110-M9000 ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006T7N0_C2\x00\x006T7VA051\x00\x00TIGSH24KA1\xc7\x85\xe2`', + b'\xf1\x006T7N0_C2\x00\x006T7Q2051\x00\x00TIG2H24KA2\x12@\x11\xb7', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H590051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H570051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.HYUNDAI_GENESIS: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DH LKAS 1.1 -150210', + b'\xf1\x00DH LKAS 1.4 -140110', + b'\xf1\x00DH LKAS 1.5 -140425', + ], + }, + CAR.IONIQ: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2301 4AEHC107', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6F2051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U3H1051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H1051\x00\x00HAE0G16US2\x00\x00\x00\x00', + ], + }, + CAR.IONIQ_PHEV_2019: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2100 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2501 4AEHC107', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2400 180222', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\xdbD\r\x81', + b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\x00\x00\x00\x00', + ], + }, + CAR.IONIQ_PHEV: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', + b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2200 ', + b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', + b'\xf1\x00AEhe SCC F-CUP 1.00 1.02 99110-G2100 ', + b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101', + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101', + b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2310 4APHC101', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEP MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027', + b'\xf1\x00AEP MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U3J9051\000\000\xf1\0006U3H1_C2\000\0006U3J9051\000\000PAE0G16NL0\x82zT\xd2', + b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', + b'\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', + ], + }, + CAR.IONIQ_EV_2020: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7000 ', + b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7200 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7310 4APEC101', + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7560 4APEC101', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.03 95740-G2500 190516', + b'\xf1\x00AEE MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2600 190730', + ], + }, + CAR.IONIQ_EV_LTD: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AEev SCC F-CUP 1.00 1.00 96400-G7000 ', + b'\xf1\x00AEev SCC F-CUP 1.00 1.00 96400-G7100 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.02 56310G7300\x00 4AEEC102', + b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7501 4AEEC104', + b'\xf1\x00AE MDPS C 1.00 1.03 56310/G7300 4AEEC103', + b'\xf1\x00AE MDPS C 1.00 1.03 56310G7300\x00 4AEEC103', + b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7301 4AEEC104', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G7200 160418', + b'\xf1\x00AEE MFC AT USA LHD 1.00 1.00 95740-G2400 180222', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2300 170703', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', + ], + }, + CAR.IONIQ_HEV_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', + b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2700 201027', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x96\xda\xd4\xee', + ], + }, + CAR.SONATA: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00DN8_ SCC F-CU- 1.00 1.00 99110-L0000 ', + b'\xf1\x00DN8_ SCC F-CUP 1.00 1.00 99110-L0000 ', + b'\xf1\x00DN8_ SCC F-CUP 1.00 1.02 99110-L1000 ', + b'\xf1\x00DN8_ SCC FHCUP 1.00 1.00 99110-L0000 ', + b'\xf1\x00DN8_ SCC FHCUP 1.00 1.01 99110-L1000 ', + b'\xf1\x00DN8_ SCC FHCUP 1.00 1.02 99110-L1000 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', + b'\xf1\x00DN ESC \x01 102\x19\x04\x13 58910-L1300', + b'\xf1\x00DN ESC \x03 100 \x08\x01 58910-L0300', + b'\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', + b'\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', + b'\xf1\x00DN ESC \x08 103\x19\x06\x01 58910-L1300', + b'\xf1\x00DN ESC \x07 107"\x08\x07 58910-L0100', + b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', + b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', + b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', + b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', + b'\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', + b'\xf1\x00DN ESC \x06 107 \x07\x03 58910-L1300', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81HM6M1_0a0_F00', + b'\xf1\x82DNBVN5GMCCXXXDCA', + b'\xf1\x82DNBVN5GMCCXXXG2F', + b'\xf1\x82DNBWN5TMDCXXXG2E', + b'\xf1\x82DNCVN5GMCCXXXF0A', + b'\xf1\x82DNCVN5GMCCXXXG2B', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A', + b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M10', + b'\xf1\x87391162M003', + b'\xf1\x87391162M013', + b'\xf1\x87391162M023', + b'\xf1\x87391162M010', + b'HM6M1_0a0_F00', + b'HM6M1_0a0_G20', + b'HM6M2_0a0_BD0', + b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B', + b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M00', + b'\xf1\x8739110-2S042\xf1\x81HM6M1_0a0_M00', + b'\xf1\x81HM6M1_0a0_G20', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC102', + b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', + b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', + b'\xf1\x8756310-L0010\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', + b'\xf1\x8756310-L0210\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC101', + b'\xf1\x8756310-L1010\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1010 4DNDC103', + b'\xf1\x8756310-L1030\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1030 4DNDC103', + b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', + b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', + b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', + b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101', + b'\xf1\x00DN8 MDPS R 1.00 1.02 57700-L1000 4DNDP105', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC102', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0200\x00 4DNAC102', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', + b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.04 99211-L1000 191016', + b'\xf1\x00DN8 MFC AT RUS LHD 1.00 1.03 99211-L1000 190705', + b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.00 99211-L0000 190716', + b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.01 99211-L0000 191016', + b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.03 99211-L0000 210603', + b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.05 99211-L1000 201109', + b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.06 99211-L1000 210325', + b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.07 99211-L1000 211223', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16KB05\x95h%', + b'\xf1\x00HT6TA260BLHT6TA800A1TDN8C20KS4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x00HT6TA260BLHT6TA810A1TDN8M25GS0\x00\x00\x00\x00\x00\x00\xaa\x8c\xd9p', + b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92', + b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:', + b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB4\x00\x00\x00\x00\x00\x00g!l[', + b'\xf1\x00HT6WA280BLHT6WAE10A1SDN8G25NB5\x00\x00\x00\x00\x00\x00\xe0t\xa9\xba', + b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', + b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE', + b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSCF\xe4!Y', + b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', + b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SAKFBA3356084GJ2\x86fvgUUuWgw\x86www\x87wffvf\xb6\xcf\xfc\xffeUO\xff\x12\x19\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SAKFBA3474944GJ2ffvgwwwwg\x88\x86x\x88\x88\x98\x88ffvfeo\xfa\xff\x86fo\xff\t\xae\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SAKFBA3475714GJ2Vfvgvg\x96yx\x88\x97\x88ww\x87ww\x88\x87xs_\xfb\xffvUO\xff\x0f\xff\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALDBA3510954GJ3ww\x87xUUuWx\x88\x87\x88\x87w\x88wvfwfc_\xf9\xff\x98wO\xffl\xe0\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA3573534GJ3\x89\x98\x89\x88EUuWgwvwwwwww\x88\x87xTo\xfa\xff\x86f\x7f\xffo\x0e\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA3601464GJ3\x88\x88\x88\x88ffvggwvwvw\x87gww\x87wvo\xfb\xff\x98\x88\x7f\xffjJ\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA3753044GJ3UUeVff\x86hwwwwvwwgvfgfvo\xf9\xfffU_\xffC\xae\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA3862294GJ3vfvgvefVxw\x87\x87w\x88\x87xwwwwc_\xf9\xff\x87w\x9f\xff\xd5\xdc\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA3873834GJ3fefVwuwWx\x88\x97\x88w\x88\x97xww\x87wU_\xfb\xff\x86f\x8f\xffN\x04\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA4525334GJ3\x89\x99\x99\x99fevWh\x88\x86\x88fwvgw\x88\x87xfo\xfa\xffuDo\xff\xd1>\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA4626804GJ3wwww\x88\x87\x88xx\x88\x87\x88wwgw\x88\x88\x98\x88\x95_\xf9\xffuDo\xff|\xe7\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA4803224GJ3wwwwwvwg\x88\x88\x98\x88wwww\x87\x88\x88xu\x9f\xfc\xff\x87f\x8f\xff\xea\xea\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA6212564GJ3\x87wwwUTuGg\x88\x86xx\x88\x87\x88\x87\x88\x98xu?\xf9\xff\x97f\x7f\xff\xb8\n\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA6347404GJ3wwwwff\x86hx\x88\x97\x88\x88\x88\x88\x88vfgf\x88?\xfc\xff\x86Uo\xff\xec/\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA6901634GJ3UUuWVeVUww\x87wwwwwvUge\x86/\xfb\xff\xbb\x99\x7f\xff]2\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALDBA7077724GJ3\x98\x88\x88\x88ww\x97ygwvwww\x87ww\x88\x87x\x87_\xfd\xff\xba\x99o\xff\x99\x01\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALFBA3525114GJ2wvwgvfvggw\x86wffvffw\x86g\x85_\xf9\xff\xa8wo\xffv\xcd\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA3624024GJ2\x88\x88\x88\x88wv\x87hx\x88\x97\x88x\x88\x97\x88ww\x87w\x86o\xfa\xffvU\x7f\xff\xd1\xec\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA3960824GJ2wwwwff\x86hffvfffffvfwfg_\xf9\xff\xa9\x88\x8f\xffb\x99\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA4011074GJ2fgvwwv\x87hw\x88\x87xww\x87wwfgvu_\xfa\xffefo\xff\x87\xc0\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA4121304GJ2x\x87xwff\x86hwwwwww\x87wwwww\x84_\xfc\xff\x98\x88\x9f\xffi\xa6\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA4195874GJ2EVugvf\x86hgwvwww\x87wgw\x86wc_\xfb\xff\x98\x88\x8f\xff\xe23\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA4625294GJ2eVefeUeVx\x88\x97\x88wwwwwwww\xa7o\xfb\xffvw\x9f\xff\xee.\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA4728774GJ2vfvg\x87vwgww\x87ww\x88\x97xww\x87w\x86_\xfb\xffeD?\xffk0\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA5129064GJ2vfvgwv\x87hx\x88\x87\x88ww\x87www\x87wd_\xfa\xffvfo\xff\x1d\x00\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA5454914GJ2\x98\x88\x88\x88\x87vwgx\x88\x87\x88xww\x87ffvf\xa7\x7f\xf9\xff\xa8w\x7f\xff\x1b\x90\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA5987784GJ2UVugDDtGx\x88\x87\x88w\x88\x87xwwwwd/\xfb\xff\x97fO\xff\xb0h\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA5987864GJ2fgvwUUuWgwvw\x87wxwwwww\x84/\xfc\xff\x97w\x7f\xff\xdf\x1d\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA6337644GJ2vgvwwv\x87hgffvwwwwwwww\x85O\xfa\xff\xa7w\x7f\xff\xc5\xfc\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA6802004GJ2UUuWUUuWgw\x86www\x87www\x87w\x96?\xf9\xff\xa9\x88\x7f\xff\x9fK\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA6892284GJ233S5\x87w\x87xx\x88\x87\x88vwwgww\x87w\x84?\xfb\xff\x98\x88\x8f\xff*\x9e\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x87SALFBA7005534GJ2eUuWfg\x86xxww\x87x\x88\x87\x88\x88w\x88\x87\x87O\xfc\xffuUO\xff\xa3k\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', + b'\xf1\x87SALFBA7152454GJ2gvwgFf\x86hx\x88\x87\x88vfWfffffd?\xfa\xff\xba\x88o\xff,\xcf\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', + b'\xf1\x87SALFBA7485034GJ2ww\x87xww\x87xfwvgwwwwvfgf\xa5/\xfc\xff\xa9w_\xff40\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SAMDBA7743924GJ3wwwwww\x87xgwvw\x88\x88\x88\x88wwww\x85_\xfa\xff\x86f\x7f\xff0\x9d\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SAMDBA7817334GJ3Vgvwvfvgww\x87wwwwwwfgv\x97O\xfd\xff\x88\x88o\xff\x8e\xeb\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SAMDBA8054504GJ3gw\x87xffvgffffwwwweUVUf?\xfc\xffvU_\xff\xddl\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SAMFB41553621GC7ww\x87xUU\x85Xvwwg\x88\x88\x88\x88wwgw\x86\xaf\xfb\xffuDo\xff\xaa\x8f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SAMFB42555421GC7\x88\x88\x88\x88wvwgx\x88\x87\x88wwgw\x87wxw3\x8f\xfc\xff\x98f\x8f\xffga\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SAMFBA7978674GJ2gw\x87xgw\x97ywwwwvUGeUUeU\x87O\xfb\xff\x98w\x8f\xfffF\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SAMFBA9283024GJ2wwwwEUuWwwgwwwwwwwww\x87/\xfb\xff\x98w\x8f\xff<\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SAMFBA9708354GJ2wwwwVf\x86h\x88wx\x87xww\x87\x88\x88\x88\x88w/\xfa\xff\x97w\x8f\xff\x86\xa0\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SANDB45316691GC6\x99\x99\x99\x99\x88\x88\xa8\x8avfwfwwww\x87wxwT\x9f\xfd\xff\x88wo\xff\x1c\xfa\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB3\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SANFB45889451GC7wx\x87\x88gw\x87x\x88\x88x\x88\x87wxw\x87wxw\x87\x8f\xfc\xffeU\x8f\xff+Q\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSA\xb9\x13\xf9p', + ], + }, + CAR.SONATA_LF: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00LF__ SCC F-CUP 1.00 1.00 96401-C2200 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00LF ESC \f 11 \x17\x01\x13 58920-C2610', + b'\xf1\x00LF ESC \t 11 \x17\x01\x13 58920-C2610', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81606D5051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81606D5K51\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81606G1051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00LFF LKAS AT USA LHD 1.00 1.01 95740-C1000 E51', + b'\xf1\x00LFF LKAS AT USA LHD 1.01 1.02 95740-C1000 E52', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', + b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\x00\x00\x00\x00', + b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', + b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24SL2n\x8d\xbe\xd8', + b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2\x00\x00\x00\x00', + b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm', + b'\xf1\x87LAJSG49645724HF0\x87x\x87\x88\x87www\x88\x99\xa8\x89\x88\x99\xa8\x89\x88\x99\xa8\x89S_\xfb\xff\x87f\x7f\xff^2\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm', + ], + }, + CAR.TUCSON: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00TL__ FCA F-CUP 1.00 1.01 99110-D3500 ', + b'\xf1\x00TL__ FCA F-CUP 1.00 1.02 99110-D3510 ', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8971TLC2NAIDDIR002\xf1\x8271TLC2NAIDDIR002', + b'\xf1\x81606G3051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TL MFC AT KOR LHD 1.00 1.02 95895-D3800 180719', + b'\xf1\x00TL MFC AT USA LHD 1.00 1.06 95895-D3800 190107', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87LBJXAN202299KF22\x87x\x87\x88ww\x87xx\x88\x97\x88\x87\x88\x98x\x88\x99\x98\x89\x87o\xf6\xff\x87w\x7f\xff\x12\x9a\xf1\x81U083\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U083\x00\x00\x00\x00\x00\x00TTL2V20KL1\x8fRn\x8a', + b'\xf1\x87KMLDCU585233TJ20wx\x87\x88x\x88\x98\x89vfwfwwww\x87f\x9f\xff\x98\xff\x7f\xf9\xf7s\xf1\x816T6G4051\x00\x00\xf1\x006T6J0_C2\x00\x006T6G4051\x00\x00TTL4G24NH2\x00\x00\x00\x00', + ], + }, + CAR.SANTA_FE: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1210 ', + b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S2000 ', + b'\xf1\x00TM__ SCC F-CUP 1.00 1.02 99110-S2000 ', + b'\xf1\x00TM__ SCC F-CUP 1.00 1.03 99110-S2000 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650', + b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500', + b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650', + b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650', + b'\xf1\x00TM ESC \x02 100\x18\x030 58910-S2600', + b'\xf1\x00TM ESC \x02 102\x18\x07\x01 58910-S2600', + b'\xf1\x00TM ESC \x02 103\x18\x11\x07 58910-S2600', + b'\xf1\x00TM ESC \x02 104\x19\x07\x07 58910-S2600', + b'\xf1\x00TM ESC \x03 103\x18\x11\x07 58910-S2600', + b'\xf1\x00TM ESC \x0c 103\x18\x11\x08 58910-S2650', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81606EA051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81606G1051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81606G3051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409', + b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8A12', + b'\xf1\x00TM MDPS C 1.00 1.01 56340-S2000 9129', + b'\xf1\x00TM MDPS R 1.00 1.02 57700-S1100 4TMDP102', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TM MFC AT EUR LHD 1.00 1.01 99211-S1010 181207', + b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1', + b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', + b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91', + b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00', + b'\xf1\x87LDJUEA6010814HG1\x87w\x87x\x86gvw\x88\x88\x98\x88gw\x86wx\x88\x97\x88\x85o\xf8\xff\x86f_\xff\xd37\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', + b'\xf1\x87LDJUEA6458264HG1ww\x87x\x97x\x87\x88\x88\x99\x98\x89g\x88\x86xw\x88\x97x\x86o\xf7\xffvw\x8f\xff3\x9a\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', + b'\xf1\x87LDKUEA2045844HG1wwww\x98\x88x\x87\x88\x88\xa8\x88x\x99\x97\x89x\x88\xa7\x88U\x7f\xf8\xffvfO\xffC\x1e\xf1\x816W3E0051\x00\x00\xf1\x006W351_C2\x00\x006W3E0051\x00\x00TTM4T20NS3\x00\x00\x00\x00', + b'\xf1\x87LDKUEA9993304HG1\x87www\x97x\x87\x88\x99\x99\xa9\x99x\x99\xa7\x89w\x88\x97x\x86_\xf7\xffwwO\xffl#\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS1R\x7f\x90\n', + b'\xf1\x87LDLUEA6061564HG1\xa9\x99\x89\x98\x87wwwx\x88\x97\x88x\x99\xa7\x89x\x99\xa7\x89sO\xf9\xffvU_\xff<\xde\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', + b'\xf1\x87LDLUEA6159884HG1\x88\x87hv\x99\x99y\x97\x89\xaa\xb8\x9ax\x99\x87\x89y\x99\xb7\x99\xa7?\xf7\xff\x97wo\xff\xf3\x05\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', + b'\xf1\x87LDLUEA6852664HG1\x97wWu\x97www\x89\xaa\xc8\x9ax\x99\x97\x89x\x99\xa7\x89SO\xf7\xff\xa8\x88\x7f\xff\x03z\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', + b'\xf1\x87LDLUEA6898374HG1fevW\x87wwwx\x88\x97\x88h\x88\x96\x88x\x88\xa7\x88ao\xf9\xff\x98\x99\x7f\xffD\xe2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', + b'\xf1\x87LDLUEA6898374HG1fevW\x87wwwx\x88\x97\x88h\x88\x96\x88x\x88\xa7\x88ao\xf9\xff\x98\x99\x7f\xffD\xe2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', + b'\xf1\x87SBJWAA5842214GG0\x88\x87\x88xww\x87x\x89\x99\xa8\x99\x88\x99\x98\x89w\x88\x87xw_\xfa\xfffU_\xff\xd1\x8d\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', + b'\xf1\x87SBJWAA5890864GG0\xa9\x99\x89\x98\x98\x87\x98y\x89\x99\xa8\x99w\x88\x87xww\x87wvo\xfb\xffuD_\xff\x9f\xb5\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', + b'\xf1\x87SBJWAA6562474GG0ffvgeTeFx\x88\x97\x88ww\x87www\x87w\x84o\xfa\xff\x87fO\xff\xc2 \xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x00\x00\x00\x00', + b'\xf1\x87SBJWAA6562474GG0ffvgeTeFx\x88\x97\x88ww\x87www\x87w\x84o\xfa\xff\x87fO\xff\xc2 \xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', + b'\xf1\x87SBJWAA7780564GG0wvwgUUeVwwwwx\x88\x87\x88wwwwd_\xfc\xff\x86f\x7f\xff\xd7*\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS2F\x84<\xc0', + b'\xf1\x87SBJWAA8278284GG0ffvgUU\x85Xx\x88\x87\x88x\x88w\x88ww\x87w\x96o\xfd\xff\xa7U_\xff\xf2\xa0\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS2F\x84<\xc0', + b'\xf1\x87SBLWAA4363244GG0wvwgwv\x87hgw\x86ww\x88\x87xww\x87wdo\xfb\xff\x86f\x7f\xff3$\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS6\x00\x00\x00\x00', + b'\xf1\x87SBLWAA4363244GG0wvwgwv\x87hgw\x86ww\x88\x87xww\x87wdo\xfb\xff\x86f\x7f\xff3$\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS6x0\x17\xfe', + b'\xf1\x87SBLWAA4899564GG0VfvgUU\x85Xx\x88\x87\x88vfgf\x87wxwvO\xfb\xff\x97f\xb1\xffSB\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS7\x00\x00\x00\x00', + b'\xf1\x87SBLWAA6622844GG0wwwwff\x86hwwwwx\x88\x87\x88\x88\x88\x88\x88\x98?\xfd\xff\xa9\x88\x7f\xffn\xe5\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS7u\x1e{\x1c', + b'\xf1\x87SDJXAA7656854GG1DEtWUU\x85X\x88\x88\x98\x88w\x88\x87xx\x88\x87\x88\x96o\xfb\xff\x86f\x7f\xff.\xca\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4G24NS2\x00\x00\x00\x00', + b'\xf1\x87SDJXAA7656854GG1DEtWUU\x85X\x88\x88\x98\x88w\x88\x87xx\x88\x87\x88\x96o\xfb\xff\x86f\x7f\xff.\xca\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4G24NS2K\xdaV0', + b'\xf1\x87SDKXAA2443414GG1vfvgwv\x87h\x88\x88\x88\x88ww\x87wwwww\x99_\xfc\xffvD?\xffl\xd2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4G24NS6\x00\x00\x00\x00', + ], + }, + CAR.SANTA_FE_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1500 ', + b'\xf1\x00TM__ SCC FHCUP 1.00 1.00 99110-S1500 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00TM ESC \x01 102!\x04\x03 58910-S2DA0', + b'\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0', + b'\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0', + b'\xf1\x8758910-S2DA0\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0', + b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0', + b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', + b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', + b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', + b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0', + b'\xf1\x00TM ESC \x02 103"\x07\x08 58910-S2GA0', + b'\xf1\x00TM ESC 103!\x030 58910-S1MA0', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_L50', + b'\xf1\x81HM6M1_0a0_H00', + b'\xf1\x82TACVN5GMI3XXXH0A', + b'\xf1\x82TMBZN5TMD3XXXG2E', + b'\xf1\x82TACVN5GSI3XXXH0A', + b'\xf1\x82TMCFD5MMCXXXXG0A', + b'\xf1\x81HM6M1_0a0_G20', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', + b'\xf1\x81HM6M2_0a0_G00', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', + b'\xf1\x8739101-2STN8\xf1\x81HM6M1_0a0_M00', + b'\xf1\x87 \xf1\x81 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00TM MDPS C 1.00 1.02 56370-S2AA0 0B19', + b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1AB0 4TSDC101', + b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1EB0 4TSDC101', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TM MFC AT MES LHD 1.00 1.05 99211-S1500 220126', + b'\xf1\x00TMA MFC AT MEX LHD 1.00 1.01 99211-S2500 210205', + b'\xf1\x00TMA MFC AT USA LHD 1.00 1.00 99211-S2500 200720', + b'\xf1\x00TM MFC AT EUR LHD 1.00 1.03 99211-S1500 210224', + b'\xf1\x00TMA MFC AT USA LHD 1.00 1.01 99211-S2500 210205', + b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00HT6WA280BLHT6WAD00A1STM2G25NH2\x00\x00\x00\x00\x00\x00\xf8\xc0\xc3\xaa', + b'\xf1\x00HT6WA280BLHT6WAD00A1STM4G25NH1\x00\x00\x00\x00\x00\x00\x9cl\x04\xbc', + b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NSA\xf3\xf4Uj', + b'\xf1\x87SDMXCA9087684GN1VfvgUUeVwwgwwwwwffffU?\xfb\xff\x97\x88\x7f\xff+\xa4\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', + b'\xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', + b'\xf1\x87SDMXCA8653204GN1EVugEUuWwwwwww\x87wwwwwv/\xfb\xff\xa8\x88\x9f\xff\xa5\x9c\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', + b'\xf1\x87KMMYBU034207SB72x\x89\x88\x98h\x88\x98\x89\x87fhvvfWf33_\xff\x87\xff\x8f\xfa\x81\xe5\xf1\x89HT6TAF00A1\xf1\x82STM0M25GS1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6', + b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', + b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', + b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', + b'\xf1\x00T02601BL T02800A1 VTMPT25XXX800NS4\xed\xaf\xed\xf5', + b'\xf1\x00T02601BL T02900A1 VTMPT25XXW900NS1c\x918\xc5', + ], + }, + CAR.SANTA_FE_HEV_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', + b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102', + b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105', + b'\xf1\x00TM MDPS C 1.00 1.02 56310-GA000 4TSHA100', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TMH MFC AT EUR LHD 1.00 1.06 99211-S1500 220727', + b'\xf1\x00TMH MFC AT USA LHD 1.00 1.03 99211-S1500 210224', + b'\xf1\x00TMH MFC AT USA LHD 1.00 1.06 99211-S1500 220727', + b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16SA3\xa3\x1b\xe14', + b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16UA3I\x94\xac\x8f', + b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TTM2H16SA2\x80\xd7l\xb2', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x87391312MTC1', + b'\xf1\x87391312MTE0', + b'\xf1\x87391312MTL0', + ], + }, + CAR.SANTA_FE_PHEV_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', + b'\xf1\x00TMhe SCC FHCUP 1.00 1.01 99110-CL500 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', + b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102', + b'\xf1\x00TM MDPS C 1.00 1.02 56310CLEC0\x00 4TSHC102', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TMP MFC AT USA LHD 1.00 1.03 99211-S1500 210224', + b'\xf1\x00TMP MFC AT USA LHD 1.00 1.06 99211-S1500 220727', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe', + b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', + b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x87391312MTF0', + b'\xf1\x87391312MTF1', + ], + }, + CAR.CUSTIN_1ST_GEN: { + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00KU ESC \x01 101!\x02\x03 58910-O3200', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00KU__ SCC F-CUP 1.00 1.01 99110-O3000 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00KU MDPS C 1.00 1.01 56310/O3100 4KUCC101', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00KU2 MFC AT CHN LHD 1.00 1.02 99211-O3000 220923', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x87391212MEC0', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U928\x00\x00\x00\x00\x00\x00SKU0T15KB2\x92U\xf9M', + ], + }, + CAR.KIA_STINGER: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ', + b'\xf1\x00CK__ SCC F_CUP 1.00 1.03 96400-J5100 ', + b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5000 ', + b'\xf1\x00CK__ SCC F_CUP 1.00 1.02 96400-J5100 ', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81606DE051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640E0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00', + b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00', + b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00', + b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104', + b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5220 4C2VL104', + b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5420 4C4VL104', + b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5420 4C4VL106', + b'\xf1\x00CK MDPS R 1.00 1.07 57700-J5220 4C2VL107', + b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5220 4C2VL106', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 95740-J5000 170822', + b'\xf1\x00CK MFC AT USA LHD 1.00 1.04 95740-J5000 180504', + b'\xf1\x00CK MFC AT EUR LHD 1.00 1.03 95740-J5000 170822', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SCK0T33NB2\xb3\xee\xba\xdc', + b'\xf1\x87VCJLE17622572DK0vd6D\x99\x98y\x97vwVffUfvfC%CuT&Dx\x87o\xff{\x1c\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', + b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', + b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x89E21\x00\x00\x00\x00\x00\x00\x00\xf1\x82SCK0T33NB0', + b'\xf1\x87VDHLG17034412DK2vD6DfVvVTD$D\x99w\x88\x98EDEDeT6DgfO\xff\xc3=\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', + b'\xf1\x87VDHLG17118862DK2\x8awWwgu\x96wVfUVwv\x97xWvfvUTGTx\x87o\xff\xc9\xed\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', + b'\xf1\x87VDKLJ18675252DK6\x89vhgwwwwveVU\x88w\x87w\x99vgf\x97vXfgw_\xff\xc2\xfb\xf1\x89E25\x00\x00\x00\x00\x00\x00\x00\xf1\x82TCK0T33NB2', + b'\xf1\x87WAJTE17552812CH4vfFffvfVeT5DwvvVVdFeegeg\x88\x88o\xff\x1a]\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00TCK2T20NB1\x19\xd2\x00\x94', + b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S', + b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', + ], + }, + CAR.KIA_STINGER_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ', + b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5500 ', + b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5600 ', + b'\xf1\x00CK__ SCC FHCUP 1.00 1.01 99110-J5100 ', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640N2051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81HM6M1_0a0_H00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5300 4C2CL503', + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5320 4C2VL503', + b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622', + b'\xf1\x00CK MFC AT KOR LHD 1.00 1.00 99211-J5500 210622', + b'\xf1\x00CK MFC AT USA LHD 1.00 1.00 99211-J5500 210622', + b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 99211-J5000 201209', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', + b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1', + b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T25KH2B\xfbI\xe2', + b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00TCK0T33NH0%g~\xd3', + ], + }, + CAR.PALISADE: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ', + b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ', + b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ', + b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ', + b'\xf1\x00LX2_ SCC FHCUP 1.00 1.00 99110-S8110 ', + b'\xf1\x00LX2_ SCC FHCUP 1.00 1.04 99110-S8100 ', + b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ', + b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ', + b'\xf1\x00ON__ FCA FHCUP 1.00 1.01 99110-S9110 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360', + b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360', + b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330', + b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330', + b'\xf1\x00LX ESC \x0b 103\x19\t\t 58910-S8350', + b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330', + b'\xf1\x00LX ESC \x0b 103\x19\t\x10 58910-S8360', + b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360', + b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360', + b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', + b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', + b'\xf1\x00ON ESC \x01 101\x19\t\x08 58910-S9360', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640K0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640S1051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103', + b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8000 4LXDC103', + b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103', + b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104', + b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13', + b'\xf1\x00ON MDPS C 1.00 1.01 56340-S9000 9201', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.03 99211-S8100 190125', + b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.05 99211-S8100 190909', + b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 200422', + b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 200903', + b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 181105', + b'\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 200720', + b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226', + b'\xf1\x00ON MFC AT USA LHD 1.00 1.04 99211-S9100 211227', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', + b'\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00TON2G38NB5j\x94.\xde', + b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1', + b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', + b'\xf1\x87LBLUFN655162KF36\x98\x88\x88\x88\x98\x88\x88\x88x\x99\xa7\x89x\x99\xa7\x89x\x99\x97\x89g\x7f\xf7\xffwU_\xff\xe9!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', + b'\xf1\x87LBLUFN731381KF36\xb9\x99\x89\x98\x98\x88\x88\x88\x89\x99\xa8\x99\x88\x99\xa8\x89\x88\x88\x98\x88V\x7f\xf6\xff\x99w\x8f\xff\xad\xd8\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', + b'\xf1\x87LDKVAA0028604HH1\xa8\x88x\x87vgvw\x88\x99\xa8\x89gw\x86ww\x88\x97x\x97o\xf9\xff\x97w\x7f\xffo\x02\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', + b'\xf1\x87LDKVAA3068374HH1wwww\x87xw\x87y\x99\xa7\x99w\x88\x87xw\x88\x97x\x85\xaf\xfa\xffvU/\xffU\xdc\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', + b'\xf1\x87LDKVBN382172KF26\x98\x88\x88\x88\xa8\x88\x88\x88x\x99\xa7\x89\x87\x88\x98x\x98\x99\xa9\x89\xa5_\xf6\xffDDO\xff\xcd\x16\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', + b'\xf1\x87LDKVBN424201KF26\xba\xaa\x9a\xa9\x99\x99\x89\x98\x89\x99\xa8\x99\x88\x99\x98\x89\x88\x99\xa8\x89v\x7f\xf7\xffwf_\xffq\xa6\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', + b'\xf1\x87LDKVBN540766KF37\x87wgv\x87w\x87xx\x99\x97\x89v\x88\x97h\x88\x88\x88\x88x\x7f\xf6\xffvUo\xff\xd3\x01\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', + b'\xf1\x87LDLVAA4225634HH1\x98\x88\x88\x88eUeVx\x88\x87\x88g\x88\x86xx\x88\x87\x88\x86o\xf9\xff\x87w\x7f\xff\xf2\xf7\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x87LDLVAA4777834HH1\x98\x88x\x87\x87wwwx\x88\x87\x88x\x99\x97\x89x\x88\x97\x88\x86o\xfa\xff\x86fO\xff\x1d9\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x87LDLVAA5194534HH1ffvguUUUx\x88\xa7\x88h\x99\x96\x89x\x88\x97\x88ro\xf9\xff\x98wo\xff\xaaM\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x87LDLVAA5949924HH1\xa9\x99y\x97\x87wwwx\x99\x97\x89x\x99\xa7\x89x\x99\xa7\x89\x87_\xfa\xffeD?\xff\xf1\xfd\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x87LDLVBN560098KF26\x86fff\x87vgfg\x88\x96xfw\x86gfw\x86g\x95\xf6\xffeU_\xff\x92c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', + b'\xf1\x87LDLVBN602045KF26\xb9\x99\x89\x98\x97vwgy\xaa\xb7\x9af\x88\x96hw\x99\xa7y\xa9\x7f\xf5\xff\x99w\x7f\xff,\xd3\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN628911KF26\xa9\x99\x89\x98\x98\x88\x88\x88y\x99\xa7\x99fw\x86gw\x88\x87x\x83\x7f\xf6\xff\x98wo\xff2\xda\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN645817KF37\x87www\x98\x87xwx\x99\x97\x89\x99\x99\x99\x99g\x88\x96x\xb6_\xf7\xff\x98fo\xff\xe2\x86\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN662115KF37\x98\x88\x88\x88\xa8\x88\x88\x88x\x99\x97\x89x\x99\xa7\x89\x88\x99\xa8\x89\x88\x7f\xf7\xfffD_\xff\xdc\x84\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN667933KF37\xb9\x99\x89\x98\xb9\x99\x99\x99x\x88\x87\x88w\x88\x87x\x88\x88\x98\x88\xcbo\xf7\xffe3/\xffQ!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN673087KF37\x97www\x86fvgx\x99\x97\x89\x99\xaa\xa9\x9ag\x88\x86x\xe9_\xf8\xff\x98w\x7f\xff"\xad\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN673841KF37\x98\x88x\x87\x86g\x86xy\x99\xa7\x99\x88\x99\xa8\x89w\x88\x97xdo\xf5\xff\x98\x88\x8f\xffT\xec\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN681363KF37\x98\x88\x88\x88\x97x\x87\x88y\xaa\xa7\x9a\x88\x88\x98\x88\x88\x88\x88\x88vo\xf6\xffvD\x7f\xff%v\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN713782KF37\x99\x99y\x97\x98\x88\x88\x88x\x88\x97\x88\x88\x99\x98\x89\x88\x99\xa8\x89\x87o\xf7\xffeU?\xff7,\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN713890KF26\xb9\x99\x89\x98\xa9\x99\x99\x99x\x99\x97\x89\x88\x99\xa8\x89\x88\x99\xb8\x89Do\xf7\xff\xa9\x88o\xffs\r\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN733215KF37\x99\x98y\x87\x97wwwi\x99\xa6\x99x\x99\xa7\x89V\x88\x95h\x86o\xf7\xffeDO\xff\x12\xe7\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN750044KF37\xca\xa9\x8a\x98\xa7wwwy\xaa\xb7\x9ag\x88\x96x\x88\x99\xa8\x89\xb9\x7f\xf6\xff\xa8w\x7f\xff\xbe\xde\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN752612KF37\xba\xaa\x8a\xa8\x87w\x87xy\xaa\xa7\x9a\x88\x99\x98\x89x\x88\x97\x88\x96o\xf6\xffvU_\xffh\x1b\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN755553KF37\x87xw\x87\x97w\x87xy\x99\xa7\x99\x99\x99\xa9\x99Vw\x95gwo\xf6\xffwUO\xff\xb5T\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', + b'\xf1\x87LDLVBN757883KF37\x98\x87xw\x98\x87\x88xy\xaa\xb7\x9ag\x88\x96x\x89\x99\xa8\x99e\x7f\xf6\xff\xa9\x88o\xff5\x15\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', + b'\xf1\x87LDMVBN778156KF37\x87vWe\xa9\x99\x99\x99y\x99\xb7\x99\x99\x99\x99\x99x\x99\x97\x89\xa8\x7f\xf8\xffwf\x7f\xff\x82_\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', + b'\xf1\x87LDMVBN780576KF37\x98\x87hv\x97x\x97\x89x\x99\xa7\x89\x88\x99\x98\x89w\x88\x97x\x98\x7f\xf7\xff\xba\x88\x8f\xff\x1e0\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', + b'\xf1\x87LDMVBN783485KF37\x87www\x87vwgy\x99\xa7\x99\x99\x99\xa9\x99Vw\x95g\x89_\xf6\xff\xa9w_\xff\xc5\xd6\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', + b'\xf1\x87LDMVBN811844KF37\x87vwgvfffx\x99\xa7\x89Vw\x95gg\x88\xa6xe\x8f\xf6\xff\x97wO\xff\t\x80\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', + b'\xf1\x87LDMVBN830601KF37\xa7www\xa8\x87xwx\x99\xa7\x89Uw\x85Ww\x88\x97x\x88o\xf6\xff\x8a\xaa\x7f\xff\xe2:\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', + b'\xf1\x87LDMVBN848789KF37\x87w\x87x\x87w\x87xy\x99\xb7\x99\x87\x88\x98x\x88\x99\xa8\x89\x87\x7f\xf6\xfffUo\xff\xe3!\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN851595KF37\x97wgvvfffx\x99\xb7\x89\x88\x99\x98\x89\x87\x88\x98x\x99\x7f\xf7\xff\x97w\x7f\xff@\xf3\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN873175KF26\xa8\x88\x88\x88vfVex\x99\xb7\x89\x88\x99\x98\x89x\x88\x97\x88f\x7f\xf7\xff\xbb\xaa\x8f\xff,\x04\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN879401KF26veVU\xa8\x88\x88\x88g\x88\xa6xVw\x95gx\x88\xa7\x88v\x8f\xf9\xff\xdd\xbb\xbf\xff\xb3\x99\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN881314KF37\xa8\x88h\x86\x97www\x89\x99\xa8\x99w\x88\x97xx\x99\xa7\x89\xca\x7f\xf8\xff\xba\x99\x8f\xff\xd8v\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN888651KF37\xa9\x99\x89\x98vfff\x88\x99\x98\x89w\x99\xa7y\x88\x88\x98\x88D\x8f\xf9\xff\xcb\x99\x8f\xff\xa5\x1e\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN889419KF37\xa9\x99y\x97\x87w\x87xx\x88\x97\x88w\x88\x97x\x88\x99\x98\x89e\x9f\xf9\xffeUo\xff\x901\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN895969KF37vefV\x87vgfx\x99\xa7\x89\x99\x99\xb9\x99f\x88\x96he_\xf7\xffxwo\xff\x14\xf9\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN899222KF37\xa8\x88x\x87\x97www\x98\x99\x99\x89\x88\x99\x98\x89f\x88\x96hdo\xf7\xff\xbb\xaa\x9f\xff\xe2U\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8", + b'\xf1\x87LDMVBN950669KF37\x97www\x96fffy\x99\xa7\x99\xa9\x99\xaa\x99g\x88\x96x\xb8\x8f\xf9\xffTD/\xff\xa7\xcb\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDLVAA4478824HH1\x87wwwvfvg\x89\x99\xa8\x99w\x88\x87x\x89\x99\xa8\x99\xa6o\xfa\xfffU/\xffu\x92\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x87LDMVBN871852KF37\xb9\x99\x99\x99\xa8\x88\x88\x88y\x99\xa7\x99x\x99\xa7\x89\x88\x88\x98\x88\x89o\xf7\xff\xaa\x88o\xff\x0e\xed\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + ], + }, + CAR.VELOSTER: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JS__ SCC H-CUP 1.00 1.02 95650-J3200 ', + b'\xf1\x00JS__ SCC HNCUP 1.00 1.02 95650-J3100 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816V8RAC00121.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x01TJS-JNU06F200H0A', + b'\x01TJS-JDK06F200H0A', + b'391282BJF5 ', + ], + (Ecu.eps, 0x7d4, None): [b'\xf1\x00JSL MDPS C 1.00 1.03 56340-J3000 8308', ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JS LKAS AT USA LHD 1.00 1.02 95740-J3000 K32', + b'\xf1\x00JS LKAS AT KOR LHD 1.00 1.03 95740-J3000 K33', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\xba\x02\xb8\x80', + b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\x00\x00\x00\x00', + b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16KS2\016\xba\036\xa2', + ], + }, + CAR.GENESIS_G70: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', + b'\xf1\x00IK__ SCC F-CUP 1.00 1.01 96400-G9100 ', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00IK MDPS R 1.00 1.06 57700-G9420 4I4VL106', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', + b'\xf1\x87VDJLT17895112DN4\x88fVf\x99\x88\x88\x88\x87fVe\x88vhwwUFU\x97eFex\x99\xff\xb7\x82\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', + ], + }, + CAR.GENESIS_G70_2020: { + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9220 4I2VL107', + b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9420 4I4VL107', + b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9420 4I4VL108', + b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9200 4I2CL108', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87VCJLP18407832DN3\x88vXfvUVT\x97eFU\x87d7v\x88eVeveFU\x89\x98\x7f\xff\xb2\xb0\xf1\x81E25\x00\x00\x00', + b'\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', + b'\xf1\x87VDKLT18912362DN4wfVfwefeveVUwfvw\x88vWfvUFU\x89\xa9\x8f\xff\x87w\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', + b'\xf1\x87VDJLC18480772DK9\x88eHfwfff\x87eFUeDEU\x98eFe\x86T5DVyo\xff\x87s\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33KB5\x9f\xa5&\x81', + b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T20KB3Wuvz', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', + b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 \xf1\xa01.02', + b'\xf1\x00IK__ SCC FHCUP 1.00 1.02 96400-G9000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', + b'\xf1\x00IK MFC AT KOR LHD 1.00 1.01 95740-G9000 170920', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81606G2051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.GENESIS_G80: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ', + b'\xf1\x00DH__ SCC FHCUP 1.00 1.01 96400-B1110 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014', + b'\xf1\x00DH LKAS AT KOR LHD 1.01 1.02 95895-B1500 170810', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G33KH2\xae\xde\xd5!', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640A4051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.GENESIS_G90: { + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87VDGMD15352242DD3w\x87gxwvgv\x87wvw\x88wXwffVfffUfw\x88o\xff\x06J\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7', + b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7', + ], + (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 '], + (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2020 160302'], + (Ecu.engine, 0x7e0, None): [b'\xf1\x810000000000\x00'], + }, + CAR.KONA: { + (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00OS__ SCC F-CUP 1.00 1.00 95655-J9200 ', ], + (Ecu.abs, 0x7d1, None): [b'\xf1\x816V5RAK00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ], + (Ecu.engine, 0x7e0, None): [b'"\x01TOS-0NU06F301J02', ], + (Ecu.eps, 0x7d4, None): [b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105', ], + (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9300 g21', ], + (Ecu.transmission, 0x7e1, None): [b'\xf1\x816U2VE051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VE051\x00\x00DOS4T16NS3\x00\x00\x00\x00', ], + }, + CAR.KIA_CEED: { + (Ecu.fwdRadar, 0x7D0, None): [b'\xf1\000CD__ SCC F-CUP 1.00 1.02 99110-J7000 ', ], + (Ecu.eps, 0x7D4, None): [b'\xf1\000CD MDPS C 1.00 1.06 56310-XX000 4CDEC106', ], + (Ecu.fwdCamera, 0x7C4, None): [b'\xf1\000CD LKAS AT EUR LHD 1.00 1.01 99211-J7000 B40', ], + (Ecu.engine, 0x7E0, None): [b'\001TCD-JECU4F202H0K', ], + (Ecu.transmission, 0x7E1, None): [ + b'\xf1\x816U2V7051\000\000\xf1\0006U2V0_C2\000\0006U2V7051\000\000DCD0T14US1\000\000\000\000', + b'\xf1\x816U2V7051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V7051\x00\x00DCD0T14US1U\x867Z', + ], + (Ecu.abs, 0x7D1, None): [b'\xf1\000CD ESC \003 102\030\b\005 58920-J7350', ], + }, + CAR.KIA_FORTE: { + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00BD MDPS C 1.00 1.02 56310-XX000 4BD2C102', + b'\xf1\x00BD MDPS C 1.00 1.08 56310/M6300 4BDDC108', + b'\xf1\x00BD MDPS C 1.00 1.08 56310M6300\x00 4BDDC108', + b'\xf1\x00BDm MDPS C A.01 1.03 56310M7800\x00 4BPMC103', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00BD LKAS AT USA LHD 1.00 1.04 95740-M6000 J33', + b'\xf1\x00BDP LKAS AT USA LHD 1.00 1.05 99211-M6500 744', + ], + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00BD__ SCC H-CUP 1.00 1.02 99110-M6000 ', + b'\xf1\x00BDPE_SCC FHCUPC 1.00 1.04 99110-M6500\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x01TBDM1NU06F200H01', + b'391182B945\x00', + b'\xf1\x81616F2051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x816VGRAH00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x8758900-M7AB0 \xf1\x816VQRAD00127.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006V2B0_C2\x00\x006V2C6051\x00\x00CBD0N20NL1\x00\x00\x00\x00', + b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DBD0T16SS0\x00\x00\x00\x00', + b"\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DBD0T16SS0\xcf\x1e'\xc3", + ], + }, + CAR.KIA_K5_2021: { + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', + b'\xf1\x8799110L2000\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', + b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', + b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', + b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', + b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', + b'\xf1\x00DL3_ SCC FHCUP 1.00 1.04 99110-L2100 ', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x8756310-L3110\xf1\000DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', + b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', + b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', + b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', + b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L2220 4DLDC102', + b'\xf1\x00DL3 MDPS C 1.00 1.02 56310L3220\x00 4DLAC102', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915', + b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.04 99210-L3000 210208', + b'\xf1\x00DL3 MFC AT KOR LHD 1.00 1.04 99210-L2000 210527', + b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.05 99210-L3000 211222', + ], + (Ecu.abs, 0x7D1, None): [ + b'\xf1\000DL ESC \006 101 \004\002 58910-L3200', + b'\xf1\x8758910-L3200\xf1\000DL ESC \006 101 \004\002 58910-L3200', + b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', + b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', + b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', + b'\xf1\x00DL ESC \x01 104 \x07\x12 58910-L2200', + b'\xf1\x00DL ESC \x06 103"\x08\x06 58910-L3200', + ], + (Ecu.engine, 0x7E0, None): [ + b'\xf1\x87391212MKT0', + b'\xf1\x87391212MKV0', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DLDWN5TMDCXXXJ1B', + b'\xf1\x81HM6M2_0a0_DQ0', + b'\xf1\x87391212MKT3', + ], + (Ecu.transmission, 0x7E1, None): [ + b'\xf1\000bcsh8p54 U913\000\000\000\000\000\000TDL2T16NB1ia\v\xb8', + b'\xf1\x87SALFEA5652514GK2UUeV\x88\x87\x88xxwg\x87ww\x87wwfwvd/\xfb\xffvU_\xff\x93\xd3\xf1\x81U913\000\000\000\000\000\000\xf1\000bcsh8p54 U913\000\000\000\000\000\000TDL2T16NB1ia\v\xb8', + b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', + b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', + b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', + b'\xf1\x00HT6TA261BLHT6TAB00A1SDL0C20KS0\x00\x00\x00\x00\x00\x00\\\x9f\xa5\x15', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB2.\x13\xf6\xed', + ], + }, + CAR.KIA_K5_HEV_2020: { + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00DLhe SCC FHCUP 1.00 1.02 99110-L7000 ', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7000 4DLHC102', + b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7220 4DLHC102', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.02 99210-L2000 200309', + b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.04 99210-L2000 210527', + ], + (Ecu.engine, 0x7E0, None): [ + b'\xf1\x87391162JLA0', + ], + (Ecu.transmission, 0x7E1, None): [ + b'\xf1\x00PSBG2323 E08\x00\x00\x00\x00\x00\x00\x00TDL2H20KA2\xe3\xc6cz', + b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDL2H20KA5T\xf2\xc9\xc2', + ], + }, + CAR.KONA_EV: { + (Ecu.abs, 0x7D1, None): [ + b'\xf1\x00OS IEB \r 105\x18\t\x18 58520-K4000', + b'\xf1\x00OS IEB \x01 212 \x11\x13 58520-K4000', + b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000', + b'\xf1\x00OS IEB \x03 210 \x02\x14 58520-K4000', + b'\xf1\x00OS IEB \x03 212 \x11\x13 58520-K4000', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00OE2 LKAS AT EUR LHD 1.00 1.00 95740-K4200 200', + b'\xf1\x00OSE LKAS AT EUR LHD 1.00 1.00 95740-K4100 W40', + b'\xf1\x00OSE LKAS AT EUR RHD 1.00 1.00 95740-K4100 W40', + b'\xf1\x00OSE LKAS AT KOR LHD 1.00 1.00 95740-K4100 W40', + b'\xf1\x00OSE LKAS AT USA LHD 1.00 1.00 95740-K4300 W50', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00OS MDPS C 1.00 1.03 56310/K4550 4OEDC103', + b'\xf1\x00OS MDPS C 1.00 1.04 56310K4000\x00 4OEDC104', + b'\xf1\x00OS MDPS C 1.00 1.04 56310K4050\x00 4OEDC104', + ], + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00OSev SCC F-CUP 1.00 1.00 99110-K4000 ', + b'\xf1\x00OSev SCC F-CUP 1.00 1.00 99110-K4100 ', + b'\xf1\x00OSev SCC F-CUP 1.00 1.01 99110-K4000 ', + b'\xf1\x00OSev SCC FNCUP 1.00 1.01 99110-K4000 ', + ], + }, + CAR.KONA_EV_2022: { + (Ecu.abs, 0x7D1, None): [ + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', + b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010', + b'\xf1\x00OS IEB \x02 102"\x05\x16 58520-K4010', + b'\xf1\x00OS IEB \x03 102"\x05\x16 58520-K4010', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', + b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', + b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT EUR LHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT USA LHD 1.00 1.04 99211-J9200 904', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310-K4271 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4971\x00 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4261\x00 4OEPC102', + ], + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', + ], + }, + CAR.KONA_EV_2ND_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00SXev RDR ----- 1.00 1.00 99110-BF000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00SX2EMFC AT KOR LHD 1.00 1.00 99211-BF000 230410', + ], + }, + CAR.KIA_NIRO_EV: { + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', + b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4000 ', + b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ', + b'\xf1\x00DEev SCC F-CUP 1.00 1.03 96400-Q4100 ', + b'\xf1\x00DEev SCC FHCUP 1.00 1.03 96400-Q4000 ', + b'\xf1\x8799110Q4000\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', + b'\xf1\x8799110Q4100\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4100 ', + b'\xf1\x8799110Q4500\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4500 ', + b'\xf1\x8799110Q4600\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4600 ', + b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', + b'\xf1\x8799110Q4600\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105', + b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105', + b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706', + b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4000 191211', + b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211', + b'\xf1\x00DEE MFC AT USA LHD 1.00 1.03 95740-Q4000 180821', + b'\xf1\x00DEE MFC AT USA LHD 1.00 1.01 99211-Q4500 210428', + b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.03 95740-Q4000 180821', + b'\xf1\x00DEE MFC AT KOR LHD 1.00 1.03 95740-Q4000 180821', + ], + }, + CAR.KIA_NIRO_EV_2ND_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00SG2EMFC AT EUR LHD 1.01 1.09 99211-AT000 220801', + b'\xf1\x00SG2EMFC AT USA LHD 1.01 1.09 99211-AT000 220801', + ], + }, + CAR.KIA_NIRO_PHEV: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91", + b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00', + b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00', + b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', + b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117', + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826', + ], + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', + b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ', + ], + }, + CAR.KIA_NIRO_PHEV_2022: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL3\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 99211-G5500 210428', + ], + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00DEhe SCC F-CUP 1.00 1.00 99110-G5600 ', + ], + }, + CAR.KIA_NIRO_HEV_2021: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HDE0G16NL3\x00\x00\x00\x00', + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HDE0G16NL3\xb9\xd3\xfaW', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DEH MFC AT USA LHD 1.00 1.07 99211-G5000 201221', + b'\xf1\x00DEH MFC AT USA LHD 1.00 1.00 99211-G5500 210428', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00DEhe SCC FHCUP 1.00 1.00 99110-G5600 ', + ], + }, + CAR.KIA_SELTOS: { + (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x8799110Q5100\xf1\000SP2_ SCC FHCUP 1.01 1.05 99110-Q5100 ',], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x8758910-Q5450\xf1\000SP ESC \a 101\031\t\005 58910-Q5450', + b'\xf1\x8758910-Q5450\xf1\000SP ESC \t 101\031\t\005 58910-Q5450', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81616D2051\000\000\000\000\000\000\000\000', + b'\xf1\x81616D5051\000\000\000\000\000\000\000\000', + b'\001TSP2KNL06F100J0K', + b'\001TSP2KNL06F200J0K', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\000SP2 MDPS C 1.00 1.04 56300Q5200 ', + b'\xf1\000SP2 MDPS C 1.01 1.05 56300Q5200 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\000SP2 MFC AT USA LHD 1.00 1.04 99210-Q5000 191114', + b'\xf1\000SP2 MFC AT USA LHD 1.00 1.05 99210-Q5000 201012', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87CZLUB49370612JF7h\xa8y\x87\x99\xa7hv\x99\x97fv\x88\x87x\x89x\x96O\xff\x88\xff\xff\xff.@\xf1\x816V2C2051\000\000\xf1\0006V2B0_C2\000\0006V2C2051\000\000CSP4N20NS3\000\000\000\000', + b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS6\xd30\xa5\xb9', + b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b', + ], + }, + CAR.KIA_OPTIMA_G4: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4100 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00JF ESC \x0f 16 \x16\x06\x17 58920-D5080', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JFWGN LDWS AT USA LHD 1.00 1.02 95895-D4100 G21', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6J0051\x00\x00\xf1\x006T6J0_C2\x00\x006T6J0051\x00\x00TJF0T20NSB\x00\x00\x00\x00', + ], + }, + CAR.KIA_OPTIMA_G4_FL: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00JF ESC \x0b 11 \x18\x030 58920-D5180', + b"\xf1\x00JF ESC \t 11 \x18\x03' 58920-D5260", + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32', + b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5100 h32', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', + b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', + b'\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01', + b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', + b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', + b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', + b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01', + b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', + b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B8051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B8051\x00\x00TJFSG24NH27\xa7\xc2\xb4', + ], + }, + CAR.KIA_OPTIMA_H: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JFhe SCC FNCUP 1.00 1.00 96400-A8000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JFP LKAS AT EUR LHD 1.00 1.03 95895-A8100 160711', + ], + }, + CAR.KIA_OPTIMA_H_G4_FL: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JFhe SCC FHCUP 1.00 1.01 99110-A8500 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JFH MFC AT KOR LHD 1.00 1.01 95895-A8200 180323', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.ELANTRA: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00ADP LKAS AT USA LHD 1.00 1.03 99211-F2000 X31', + b'\xf1\x00AD LKAS AT USA LHD 1.01 1.01 95895-F2000 251', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD(\xfcA\x9d', + b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD\x00\x00\x00\x00', + b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\x00\x00\x00\x00', + b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\xc5\x92\x9e\x8a', + b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20SS2.~\x90\x87', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8161698051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x8161657051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816165D051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816165E051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00AD ESC \x11 11 \x18\x05\x06 58910-F2840', + b'\xf1\x00AD ESC \x11 12 \x15\t\t 58920-F2810', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00AD__ SCC H-CUP 1.00 1.00 99110-F2100 ', + b'\xf1\x00AD__ SCC H-CUP 1.00 1.01 96400-F2100 ', + ], + }, + CAR.ELANTRA_GT_I30: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', + b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e', + b'\xf1\x006U2U0_C2\x00\x006U2T0051\x00\x00DPD0D16KS0u\xce\x1fk', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104', + b'\xf1\x00PD MDPS C 1.00 1.00 56310G3300\x00 4PDDC100', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350', + b'\xf1\x00PD ESC \t 104\x18\t\x03 58920-G3350', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ', + b'\xf1\x00PD__ SCC FNCUP 1.01 1.00 96400-G3000 ', + ], + }, + CAR.ELANTRA_2021: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', + b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', + b'\xf1\x00CN7_ SCC FNCUP 1.00 1.01 99110-AA000 ', + b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', + b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', + b'\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AB000 220426', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', + b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800', + b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800', + b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\xe8\xba\xce\xfa', + b'\xf1\x87CXMQFM2135005JB2E\xb9\x89\x98W\xa9y\x97h\xa9\x98\x99wxvwh\x87\177\xffx\xff\xff\xff,,\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', + b'\xf1\x87CXMQFM1916035JB2\x88vvgg\x87Wuwgev\xa9\x98\x88\x98h\x99\x9f\xffh\xff\xff\xff\xa5\xee\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', + b'\xf1\x87CXLQF40189012JL2f\x88\x86\x88\x88vUex\xb8\x88\x88\x88\x87\x88\x89fh?\xffz\xff\xff\xff\x08z\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', + b'\xf1\x87CXMQFM2728305JB2E\x97\x87xw\x87vwgw\x84x\x88\x88w\x89EI\xbf\xff{\xff\xff\xff\xe6\x0e\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', + b'\xf1\x87CXMQFM3806705JB2\x89\x87wwx\x88g\x86\x99\x87\x86xwwv\x88yv\x7f\xffz\xff\xff\xffV\x15\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x82CNCWD0AMFCXCSFFA', + b'\xf1\x81HM6M2_0a0_FF0', + b'\xf1\x82CNCVD0AMFCXCSFFB', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_HC0', + ], + }, + CAR.ELANTRA_HEV_2021: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', + b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', + b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426', + b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.08 99210-AA000 220728', + b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.09 99210-AA000 221108', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', + b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103', + b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', + b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', + b'\xf1\x00CN7 MDPS C 1.00 1.04 56310BY050\x00 4CNHC104', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', + b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000', + b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', + b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', + b'\xf1\x006U3L0_C2\x00\x006U3K9051\x00\x00HCN0G16NS1\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G8051\x00\x00\x00\x00\x00\x00\x00\x00', + ] + }, + CAR.KONA_HEV: { + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00OS IEB \x01 104 \x11 58520-CM000', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00OShe SCC FNCUP 1.00 1.01 99110-CM000 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00OS MDPS C 1.00 1.00 56310CM030\x00 4OHDC100', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00OSH LKAS AT KOR LHD 1.00 1.01 95740-CM000 l31', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HOS0G16DS1\x16\xc7\xb0\xd9', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', + ] + }, + CAR.SONATA_HYBRID: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', + b'\xf1\x8799110L5000\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', + b'\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', + b'\xf1\x8799110L5000\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', + b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102', + b'\xf1\x8756310-L5450\xf1\000DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103', + b'\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', + b'\xf1\x8756310L5450\x00\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.04 99211-L1000 191016', + b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.05 99211-L1000 201109', + b'\xf1\000DN8HMFC AT USA LHD 1.00 1.06 99211-L1000 210325', + b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.07 99211-L1000 211223', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\000PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', + b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E09\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', + b'\xf1\000PSBG2323 E09\000\000\000\000\000\000\000TDN2H20SA5\x97R\x88\x9e', + b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', + b'\xf1\x87PCU\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', + b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x87391162J012', + b'\xf1\x87391162J013', + b'\xf1\x87391162J014', + b'\xf1\x87391062J002', + ], + }, + CAR.KIA_SORENTO: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00UMP LKAS AT USA LHD 1.01 1.01 95740-C6550 d01' + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00UM ESC \x0c 12 \x18\x05\x06 58910-C6330' + ], + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00UM__ SCC F-CUP 1.00 1.00 96400-C6500 ' + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87LDKUAA0348164HE3\x87www\x87www\x88\x88\xa8\x88w\x88\x97xw\x88\x97x\x86o\xf8\xff\x87f\x7f\xff\x15\xe0\xf1\x81U811\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U811\x00\x00\x00\x00\x00\x00TUM4G33NL3V|DG' + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00' + ], + }, + CAR.KIA_SORENTO_PHEV_4TH_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406', + ], + }, + CAR.KIA_EV6: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', + b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027', + b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328', + b'\xf1\x00CV1 MFC AT EUR RHD 1.00 1.00 99210-CV100 220630', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630', + b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.04 99210-CV000 210823', + b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.05 99210-CV000 211027', + b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.06 99210-CV000 220328', + ], + }, + CAR.IONIQ_5: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', + b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', + b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.02 99211-GI010 211206', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.06 99211-GI010 230110', + ], + }, + CAR.IONIQ_6: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CE__ RDR ----- 1.00 1.01 99110-KL000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CE MFC AT EUR LHD 1.00 1.03 99211-KL000 221011', + b'\xf1\x00CE MFC AT USA LHD 1.00 1.04 99211-KL000 221213', + ], + }, + CAR.TUCSON_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', + b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', + ], + }, + CAR.TUCSON_HYBRID_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9250 14W', + b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9260 14Y', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', + b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', + b'\xf1\x00NX4__ 1.00 1.01 99110-N9000 ', + ], + }, + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NQ5 FR_CMR AT GEN LHD 1.00 1.00 99211-P1060 665', + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ', + ], + }, + CAR.SANTA_CRUZ_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW010 14X', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.00 1.00 99110-K5000 ', + b'\xf1\x00NX4__ 1.01 1.00 99110-K5000 ', + ], + }, + CAR.KIA_SPORTAGE_5TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662', + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663', + b'\xf1\x00NQ5 FR_CMR AT AUS RHD 1.00 1.00 99211-P1040 663', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ', + b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ', + b'\xf1\x00NQ5__ 1.01 1.03 99110-P1000 ', + ], + }, + CAR.GENESIS_GV70_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204', + b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR200 220125', + b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR300 220125', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ', + b'\xf1\x00JK1_ SCC FHCUP 1.00 1.00 99110-AR200 ', + b'\xf1\x00JK1_ SCC FHCUP 1.00 1.00 99110-AR300 ', + ], + }, + CAR.GENESIS_GV60_EV_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215', + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU000 211215', + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.03 99211-CU000 221118', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JW1_ RDR ----- 1.00 1.00 99110-CU000 ', + ], + }, + CAR.KIA_SORENTO_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.05 99210-R5000 210623', + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.03 99210-R5000 200903', + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.00 99210-R5100 221019', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', + b'\xf1\x00MQ4_ SCC F-CUP 1.00 1.06 99110-P2000 ', + b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.08 99110-P2000 ', + ], + }, + CAR.KIA_NIRO_HEV_2ND_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.08 99211-AT000 220531', + b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.09 99211-AT000 220801', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', + ], + }, + CAR.GENESIS_GV80: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JX1 MFC AT USA LHD 1.00 1.02 99211-T6110 220513', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JX1_ SCC FHCUP 1.00 1.01 99110-T6100 ', + ], + }, + CAR.KIA_CARNIVAL_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221', + b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', + b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', + b'\xf1\x00KA4c SCC FHCUP 1.00 1.01 99110-I4000 ', + b'\xf1\x00KA4_ SCC FHCUP 1.00 1.00 99110-R0100 ', + ], + }, + CAR.KIA_SORENTO_HEV_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331', + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', + b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.04 99210-P2000 200330', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', + b'\xf1\x00MQhe SCC FHCUP 1.00 1.04 99110-P4000 ', + ], + }, + CAR.KIA_K8_HEV_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.03 99211-L8000 210907', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00GL3_ RDR ----- 1.00 1.02 99110-L8000 ', + ], + }, + CAR.STARIA_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00US4 MFC AT KOR LHD 1.00 1.06 99211-CG000 230524', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00US4_ RDR ----- 1.00 1.00 99110-CG000 ', + ], + }, +} diff --git a/selfdrive/car/hyundai/tests/print_platform_codes.py b/selfdrive/car/hyundai/tests/print_platform_codes.py index 6fe4582f54..f641535678 100755 --- a/selfdrive/car/hyundai/tests/print_platform_codes.py +++ b/selfdrive/car/hyundai/tests/print_platform_codes.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from cereal import car -from openpilot.selfdrive.car.hyundai.values import FW_VERSIONS, PLATFORM_CODE_ECUS, get_platform_codes +from openpilot.selfdrive.car.hyundai.values import PLATFORM_CODE_ECUS, get_platform_codes +from openpilot.selfdrive.car.hyundai.fingerprints import FW_VERSIONS Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index dbf2b38615..cd0ec4484b 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -5,9 +5,10 @@ import unittest from cereal import car from openpilot.selfdrive.car.fw_versions import build_fw_dict from openpilot.selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \ - HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, FW_VERSIONS, LEGACY_SAFETY_MODE_CAR, \ + HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, \ UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \ get_platform_codes +from openpilot.selfdrive.car.hyundai.fingerprints import FW_VERSIONS Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} @@ -193,7 +194,7 @@ class TestHyundaiFingerprint(unittest.TestCase): "subAddress": 0 if sub_addr is None else sub_addr}) CP = car.CarParams.new_message(carFw=car_fw) - matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw)) + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), FW_VERSIONS) if len(matches) == 1: self.assertEqual(list(matches)[0], platform) else: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 08611de790..371478f429 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1,4 +1,3 @@ -# ruff: noqa: E501 import re from dataclasses import dataclass from enum import Enum, IntFlag, StrEnum @@ -309,81 +308,6 @@ class Buttons: GAP_DIST = 3 CANCEL = 4 # on newer models, this is a pause/resume button -FINGERPRINTS = { - CAR.HYUNDAI_GENESIS: [{ - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 - }, - { - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1378: 4, 1379: 8, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 - }, - { - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1268: 8, 1280: 1, 1281: 3, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1437: 8, 1456: 4 - }, - { - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1378: 4, 1379: 8, 1384: 5, 1407: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4 - }, - { - 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4 - }], - CAR.SANTA_FE: [{ - 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1379: 8, 1384: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1456: 4, 1470: 8 - }, - { - 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 6, 764: 8, 809: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1155: 8, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1180: 8, 1183: 8, 1186: 2, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1384: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1456: 4, 1470: 8, 1988: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8 - }, - { - 67: 8, 68: 8, 80: 4, 160: 8, 161: 8, 272: 8, 288: 4, 339: 8, 356: 8, 357: 8, 399: 8, 544: 8, 608: 8, 672: 8, 688: 5, 704: 1, 790: 8, 809: 8, 848: 8, 880: 8, 898: 8, 900: 8, 901: 8, 904: 8, 1056: 8, 1064: 8, 1065: 8, 1072: 8, 1075: 8, 1087: 8, 1088: 8, 1151: 8, 1200: 8, 1201: 8, 1232: 4, 1264: 8, 1265: 8, 1266: 8, 1296: 8, 1306: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1348: 8, 1349: 8, 1369: 8, 1370: 8, 1371: 8, 1407: 8, 1415: 8, 1419: 8, 1440: 8, 1442: 4, 1461: 8, 1470: 8 - }], - CAR.SONATA: [ - {67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 549: 8, 550: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 8, 865: 8, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 908: 8, 909: 8, 912: 7, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1089: 5, 1096: 8, 1107: 5, 1108: 8, 1114: 8, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1170: 8, 1173: 8, 1180: 8, 1183: 8, 1184: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1268: 8, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1330: 8, 1339: 8, 1342: 6, 1343: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1379: 8, 1384: 8, 1394: 8, 1407: 8, 1419: 8, 1427: 6, 1446: 8, 1456: 4, 1460: 8, 1470: 8, 1485: 8, 1504: 3, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8}, - ], - CAR.SONATA_LF: [ - {66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8}, - ], - CAR.KIA_SORENTO: [{ - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1384: 8, 1407: 8, 1411: 8, 1419: 8, 1425: 2, 1427: 6, 1444: 8, 1456: 4, 1470: 8, 1489: 1 - }], - CAR.KIA_STINGER: [{ - 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 576: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 4, 1379: 8, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1456: 4, 1470: 8 - }], - CAR.GENESIS_G80: [{ - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1024: 2, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1191: 2, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1456: 4, 1470: 8 - }, - { - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 546: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1437: 8, 1456: 4, 1470: 8 - }, - { - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1162: 8, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1193: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1437: 8, 1456: 4, 1470: 8 - }], - CAR.GENESIS_G90: [{ - 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 358: 6, 359: 8, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1162: 4, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1281: 3, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 8, 1407: 8, 1419: 8, 1425: 2, 1427: 6, 1434: 2, 1456: 4, 1470: 8, 1988: 8, 2000: 8, 2003: 8, 2004: 8, 2005: 8, 2008: 8, 2011: 8, 2012: 8, 2013: 8 - }], - CAR.IONIQ_EV_2020: [{ - 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 7, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 - }], - CAR.IONIQ: [{ - 68:8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 8, 576:8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1473: 8, 1476: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 - }], - CAR.KONA_EV: [{ - 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 549: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1307: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1378: 4, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 1157: 4, 1193: 8, 1379: 8, 1988: 8, 1996: 8 - }], - CAR.KONA_EV_2022: [{ - 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1069: 8, 1078: 4, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1173: 8, 1183: 8, 1188: 8, 1191: 2, 1193: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1339: 8, 1342: 8, 1343: 8, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1446: 8, 1456: 4, 1470: 8, 1473: 8, 1485: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8 - }], - CAR.KIA_NIRO_EV: [{ - 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 516: 8, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1156: 8, 1157: 4, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1193: 8, 1225: 8, 1260: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1990: 8, 1998: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8 - }], - CAR.KIA_OPTIMA_H: [{ - 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1168: 7, 1173: 8, 1236: 2, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1371: 8, 1407: 8, 1419: 8, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1476: 8, 1535: 8 - }, - { - 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 576: 8, 593: 8, 688: 5, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 909: 8, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1168: 7, 1173: 8, 1180: 8, 1186: 2, 1191: 2, 1265: 4, 1268: 8, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1371: 8, 1407: 8, 1419: 8, 1420: 8, 1425: 2, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1476: 8, 1535: 8 - }], - CAR.PALISADE: [{ - 67: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 547: 8, 548: 8, 549: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1123: 8, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 7, 1170: 8, 1173: 8, 1180: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1384: 8, 1407: 8, 1419: 8, 1427: 6, 1456: 4, 1470: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8 - }], -} - def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[bytes]]]: # Returns unique, platform-specific identification codes for a set of versions @@ -404,7 +328,7 @@ def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[by return codes -def match_fw_to_car_fuzzy(live_fw_versions) -> Set[str]: +def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> Set[str]: # Non-electric CAN FD platforms often do not have platform code specifiers needed # to distinguish between hybrid and ICE. All EVs so far are either exclusively # electric or specify electric in the platform code. @@ -412,7 +336,7 @@ def match_fw_to_car_fuzzy(live_fw_versions) -> Set[str]: fuzzy_platform_blacklist = {str(c) for c in set(CANFD_CAR - EV_CAR)} candidates: Set[str] = set() - for candidate, fws in FW_VERSIONS.items(): + for candidate, fws in offline_fw_versions.items(): # Keep track of ECUs which pass all checks (platform codes, within date range) valid_found_ecus = set() valid_expected_ecus = {ecu[1:] for ecu in fws if ecu[0] in PLATFORM_CODE_ECUS} @@ -541,1603 +465,6 @@ FW_QUERY_CONFIG = FwQueryConfig( match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, ) -FW_VERSIONS = { - CAR.AZERA_6TH_GEN: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00IG__ SCC F-CU- 1.00 1.00 99110-G8100 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00IG MDPS C 1.00 1.02 56310G8510\x00 4IGSC103', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00IG MFC AT MES LHD 1.00 1.04 99211-G8100 200511', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U912\x00\x00\x00\x00\x00\x00SIG0M35MH0\xa4 |.', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81641KA051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.AZERA_HEV_6TH_GEN: { - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029', - b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.00 99211-G8000 180903', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00IG MDPS C 1.00 1.00 56310M9600\x00 4IHSC100', - b'\xf1\x00IG MDPS C 1.00 1.01 56310M9350\x00 4IH8C101', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00IGhe SCC FHCUP 1.00 1.00 99110-M9100 ', - b'\xf1\x00IGhe SCC FHCUP 1.00 1.01 99110-M9000 ', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006T7N0_C2\x00\x006T7VA051\x00\x00TIGSH24KA1\xc7\x85\xe2`', - b'\xf1\x006T7N0_C2\x00\x006T7Q2051\x00\x00TIG2H24KA2\x12@\x11\xb7', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H590051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H570051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.HYUNDAI_GENESIS: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DH LKAS 1.1 -150210', - b'\xf1\x00DH LKAS 1.4 -140110', - b'\xf1\x00DH LKAS 1.5 -140425', - ], - }, - CAR.IONIQ: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2301 4AEHC107', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00AEH MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F2051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3H1051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H1051\x00\x00HAE0G16US2\x00\x00\x00\x00', - ], - }, - CAR.IONIQ_PHEV_2019: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2100 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00AE MDPS C 1.00 1.07 56310/G2501 4AEHC107', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2400 180222', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\xdbD\r\x81', - b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\x00\x00\x00\x00', - ], - }, - CAR.IONIQ_PHEV: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', - b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2200 ', - b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', - b'\xf1\x00AEhe SCC F-CUP 1.00 1.02 99110-G2100 ', - b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101', - b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101', - b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', - b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2310 4APHC101', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', - b'\xf1\x00AEP MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', - b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027', - b'\xf1\x00AEP MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\000\000\xf1\0006U3H1_C2\000\0006U3J9051\000\000PAE0G16NL0\x82zT\xd2', - b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', - b'\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', - ], - }, - CAR.IONIQ_EV_2020: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7000 ', - b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7200 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7310 4APEC101', - b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7560 4APEC101', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.03 95740-G2500 190516', - b'\xf1\x00AEE MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2600 190730', - ], - }, - CAR.IONIQ_EV_LTD: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AEev SCC F-CUP 1.00 1.00 96400-G7000 ', - b'\xf1\x00AEev SCC F-CUP 1.00 1.00 96400-G7100 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00AE MDPS C 1.00 1.02 56310G7300\x00 4AEEC102', - b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7501 4AEEC104', - b'\xf1\x00AE MDPS C 1.00 1.03 56310/G7300 4AEEC103', - b'\xf1\x00AE MDPS C 1.00 1.03 56310G7300\x00 4AEEC103', - b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7301 4AEEC104', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G7200 160418', - b'\xf1\x00AEE MFC AT USA LHD 1.00 1.00 95740-G2400 180222', - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2300 170703', - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', - ], - }, - CAR.IONIQ_HEV_2022: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', - b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00AEH MFC AT USA LHD 1.00 1.00 95740-G2700 201027', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00', - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x96\xda\xd4\xee', - ], - }, - CAR.SONATA: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00DN8_ SCC F-CU- 1.00 1.00 99110-L0000 ', - b'\xf1\x00DN8_ SCC F-CUP 1.00 1.00 99110-L0000 ', - b'\xf1\x00DN8_ SCC F-CUP 1.00 1.02 99110-L1000 ', - b'\xf1\x00DN8_ SCC FHCUP 1.00 1.00 99110-L0000 ', - b'\xf1\x00DN8_ SCC FHCUP 1.00 1.01 99110-L1000 ', - b'\xf1\x00DN8_ SCC FHCUP 1.00 1.02 99110-L1000 ', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', - b'\xf1\x00DN ESC \x01 102\x19\x04\x13 58910-L1300', - b'\xf1\x00DN ESC \x03 100 \x08\x01 58910-L0300', - b'\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', - b'\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', - b'\xf1\x00DN ESC \x08 103\x19\x06\x01 58910-L1300', - b'\xf1\x00DN ESC \x07 107"\x08\x07 58910-L0100', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', - b'\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', - b'\xf1\x00DN ESC \x06 107 \x07\x03 58910-L1300', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81HM6M1_0a0_F00', - b'\xf1\x82DNBVN5GMCCXXXDCA', - b'\xf1\x82DNBVN5GMCCXXXG2F', - b'\xf1\x82DNBWN5TMDCXXXG2E', - b'\xf1\x82DNCVN5GMCCXXXF0A', - b'\xf1\x82DNCVN5GMCCXXXG2B', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A', - b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M10', - b'\xf1\x87391162M003', - b'\xf1\x87391162M013', - b'\xf1\x87391162M023', - b'\xf1\x87391162M010', - b'HM6M1_0a0_F00', - b'HM6M1_0a0_G20', - b'HM6M2_0a0_BD0', - b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B', - b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M00', - b'\xf1\x8739110-2S042\xf1\x81HM6M1_0a0_M00', - b'\xf1\x81HM6M1_0a0_G20', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware - b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC102', - b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware - b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', - b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', - b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', - b'\xf1\x8756310-L0010\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', - b'\xf1\x8756310-L0210\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC101', - b'\xf1\x8756310-L1010\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1010 4DNDC103', - b'\xf1\x8756310-L1030\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1030 4DNDC103', - b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', - b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', - b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', - b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101', - b'\xf1\x00DN8 MDPS R 1.00 1.02 57700-L1000 4DNDP105', - b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC102', - b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0200\x00 4DNAC102', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', - b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.04 99211-L1000 191016', - b'\xf1\x00DN8 MFC AT RUS LHD 1.00 1.03 99211-L1000 190705', - b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.00 99211-L0000 190716', - b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.01 99211-L0000 191016', - b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.03 99211-L0000 210603', - b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.05 99211-L1000 201109', - b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.06 99211-L1000 210325', - b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.07 99211-L1000 211223', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16KB05\x95h%', - b'\xf1\x00HT6TA260BLHT6TA800A1TDN8C20KS4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x00HT6TA260BLHT6TA810A1TDN8M25GS0\x00\x00\x00\x00\x00\x00\xaa\x8c\xd9p', - b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92', - b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:', - b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB4\x00\x00\x00\x00\x00\x00g!l[', - b'\xf1\x00HT6WA280BLHT6WAE10A1SDN8G25NB5\x00\x00\x00\x00\x00\x00\xe0t\xa9\xba', - b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', - b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE', - b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSCF\xe4!Y', - b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', - b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SAKFBA3356084GJ2\x86fvgUUuWgw\x86www\x87wffvf\xb6\xcf\xfc\xffeUO\xff\x12\x19\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SAKFBA3474944GJ2ffvgwwwwg\x88\x86x\x88\x88\x98\x88ffvfeo\xfa\xff\x86fo\xff\t\xae\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SAKFBA3475714GJ2Vfvgvg\x96yx\x88\x97\x88ww\x87ww\x88\x87xs_\xfb\xffvUO\xff\x0f\xff\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALDBA3510954GJ3ww\x87xUUuWx\x88\x87\x88\x87w\x88wvfwfc_\xf9\xff\x98wO\xffl\xe0\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3573534GJ3\x89\x98\x89\x88EUuWgwvwwwwww\x88\x87xTo\xfa\xff\x86f\x7f\xffo\x0e\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3601464GJ3\x88\x88\x88\x88ffvggwvwvw\x87gww\x87wvo\xfb\xff\x98\x88\x7f\xffjJ\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3753044GJ3UUeVff\x86hwwwwvwwgvfgfvo\xf9\xfffU_\xffC\xae\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3862294GJ3vfvgvefVxw\x87\x87w\x88\x87xwwwwc_\xf9\xff\x87w\x9f\xff\xd5\xdc\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA3873834GJ3fefVwuwWx\x88\x97\x88w\x88\x97xww\x87wU_\xfb\xff\x86f\x8f\xffN\x04\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA4525334GJ3\x89\x99\x99\x99fevWh\x88\x86\x88fwvgw\x88\x87xfo\xfa\xffuDo\xff\xd1>\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA4626804GJ3wwww\x88\x87\x88xx\x88\x87\x88wwgw\x88\x88\x98\x88\x95_\xf9\xffuDo\xff|\xe7\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA4803224GJ3wwwwwvwg\x88\x88\x98\x88wwww\x87\x88\x88xu\x9f\xfc\xff\x87f\x8f\xff\xea\xea\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA6212564GJ3\x87wwwUTuGg\x88\x86xx\x88\x87\x88\x87\x88\x98xu?\xf9\xff\x97f\x7f\xff\xb8\n\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA6347404GJ3wwwwff\x86hx\x88\x97\x88\x88\x88\x88\x88vfgf\x88?\xfc\xff\x86Uo\xff\xec/\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA6901634GJ3UUuWVeVUww\x87wwwwwvUge\x86/\xfb\xff\xbb\x99\x7f\xff]2\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALDBA7077724GJ3\x98\x88\x88\x88ww\x97ygwvwww\x87ww\x88\x87x\x87_\xfd\xff\xba\x99o\xff\x99\x01\xf1\x89HT6WA910A1\xf1\x82SDN8G25NB1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALFBA3525114GJ2wvwgvfvggw\x86wffvffw\x86g\x85_\xf9\xff\xa8wo\xffv\xcd\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA3624024GJ2\x88\x88\x88\x88wv\x87hx\x88\x97\x88x\x88\x97\x88ww\x87w\x86o\xfa\xffvU\x7f\xff\xd1\xec\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA3960824GJ2wwwwff\x86hffvfffffvfwfg_\xf9\xff\xa9\x88\x8f\xffb\x99\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4011074GJ2fgvwwv\x87hw\x88\x87xww\x87wwfgvu_\xfa\xffefo\xff\x87\xc0\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4121304GJ2x\x87xwff\x86hwwwwww\x87wwwww\x84_\xfc\xff\x98\x88\x9f\xffi\xa6\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4195874GJ2EVugvf\x86hgwvwww\x87wgw\x86wc_\xfb\xff\x98\x88\x8f\xff\xe23\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4625294GJ2eVefeUeVx\x88\x97\x88wwwwwwww\xa7o\xfb\xffvw\x9f\xff\xee.\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA4728774GJ2vfvg\x87vwgww\x87ww\x88\x97xww\x87w\x86_\xfb\xffeD?\xffk0\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA5129064GJ2vfvgwv\x87hx\x88\x87\x88ww\x87www\x87wd_\xfa\xffvfo\xff\x1d\x00\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA5454914GJ2\x98\x88\x88\x88\x87vwgx\x88\x87\x88xww\x87ffvf\xa7\x7f\xf9\xff\xa8w\x7f\xff\x1b\x90\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA5987784GJ2UVugDDtGx\x88\x87\x88w\x88\x87xwwwwd/\xfb\xff\x97fO\xff\xb0h\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA5987864GJ2fgvwUUuWgwvw\x87wxwwwww\x84/\xfc\xff\x97w\x7f\xff\xdf\x1d\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA6337644GJ2vgvwwv\x87hgffvwwwwwwww\x85O\xfa\xff\xa7w\x7f\xff\xc5\xfc\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA6802004GJ2UUuWUUuWgw\x86www\x87www\x87w\x96?\xf9\xff\xa9\x88\x7f\xff\x9fK\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA6892284GJ233S5\x87w\x87xx\x88\x87\x88vwwgww\x87w\x84?\xfb\xff\x98\x88\x8f\xff*\x9e\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x87SALFBA7005534GJ2eUuWfg\x86xxww\x87x\x88\x87\x88\x88w\x88\x87\x87O\xfc\xffuUO\xff\xa3k\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', - b'\xf1\x87SALFBA7152454GJ2gvwgFf\x86hx\x88\x87\x88vfWfffffd?\xfa\xff\xba\x88o\xff,\xcf\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', - b'\xf1\x87SALFBA7485034GJ2ww\x87xww\x87xfwvgwwwwvfgf\xa5/\xfc\xff\xa9w_\xff40\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMDBA7743924GJ3wwwwww\x87xgwvw\x88\x88\x88\x88wwww\x85_\xfa\xff\x86f\x7f\xff0\x9d\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SAMDBA7817334GJ3Vgvwvfvgww\x87wwwwwwfgv\x97O\xfd\xff\x88\x88o\xff\x8e\xeb\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SAMDBA8054504GJ3gw\x87xffvgffffwwwweUVUf?\xfc\xffvU_\xff\xddl\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SAMFB41553621GC7ww\x87xUU\x85Xvwwg\x88\x88\x88\x88wwgw\x86\xaf\xfb\xffuDo\xff\xaa\x8f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFB42555421GC7\x88\x88\x88\x88wvwgx\x88\x87\x88wwgw\x87wxw3\x8f\xfc\xff\x98f\x8f\xffga\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA7978674GJ2gw\x87xgw\x97ywwwwvUGeUUeU\x87O\xfb\xff\x98w\x8f\xfffF\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA9283024GJ2wwwwEUuWwwgwwwwwwwww\x87/\xfb\xff\x98w\x8f\xff<\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA9708354GJ2wwwwVf\x86h\x88wx\x87xww\x87\x88\x88\x88\x88w/\xfa\xff\x97w\x8f\xff\x86\xa0\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SANDB45316691GC6\x99\x99\x99\x99\x88\x88\xa8\x8avfwfwwww\x87wxwT\x9f\xfd\xff\x88wo\xff\x1c\xfa\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB3\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SANFB45889451GC7wx\x87\x88gw\x87x\x88\x88x\x88\x87wxw\x87wxw\x87\x8f\xfc\xffeU\x8f\xff+Q\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSA\xb9\x13\xf9p', - ], - }, - CAR.SONATA_LF: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00LF__ SCC F-CUP 1.00 1.00 96401-C2200 ', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00LF ESC \f 11 \x17\x01\x13 58920-C2610', - b'\xf1\x00LF ESC \t 11 \x17\x01\x13 58920-C2610', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81606D5051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606D5K51\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606G1051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00LFF LKAS AT USA LHD 1.00 1.01 95740-C1000 E51', - b'\xf1\x00LFF LKAS AT USA LHD 1.01 1.02 95740-C1000 E52', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\x00\x00\x00\x00', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24SL2n\x8d\xbe\xd8', - b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2\x00\x00\x00\x00', - b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm', - b'\xf1\x87LAJSG49645724HF0\x87x\x87\x88\x87www\x88\x99\xa8\x89\x88\x99\xa8\x89\x88\x99\xa8\x89S_\xfb\xff\x87f\x7f\xff^2\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm', - ], - }, - CAR.TUCSON: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00TL__ FCA F-CUP 1.00 1.01 99110-D3500 ', - b'\xf1\x00TL__ FCA F-CUP 1.00 1.02 99110-D3510 ', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8971TLC2NAIDDIR002\xf1\x8271TLC2NAIDDIR002', - b'\xf1\x81606G3051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00TL MFC AT KOR LHD 1.00 1.02 95895-D3800 180719', - b'\xf1\x00TL MFC AT USA LHD 1.00 1.06 95895-D3800 190107', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87LBJXAN202299KF22\x87x\x87\x88ww\x87xx\x88\x97\x88\x87\x88\x98x\x88\x99\x98\x89\x87o\xf6\xff\x87w\x7f\xff\x12\x9a\xf1\x81U083\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U083\x00\x00\x00\x00\x00\x00TTL2V20KL1\x8fRn\x8a', - b'\xf1\x87KMLDCU585233TJ20wx\x87\x88x\x88\x98\x89vfwfwwww\x87f\x9f\xff\x98\xff\x7f\xf9\xf7s\xf1\x816T6G4051\x00\x00\xf1\x006T6J0_C2\x00\x006T6G4051\x00\x00TTL4G24NH2\x00\x00\x00\x00', - ], - }, - CAR.SANTA_FE: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1210 ', - b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S2000 ', - b'\xf1\x00TM__ SCC F-CUP 1.00 1.02 99110-S2000 ', - b'\xf1\x00TM__ SCC F-CUP 1.00 1.03 99110-S2000 ', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650', - b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500', - b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650', - b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650', - b'\xf1\x00TM ESC \x02 100\x18\x030 58910-S2600', - b'\xf1\x00TM ESC \x02 102\x18\x07\x01 58910-S2600', - b'\xf1\x00TM ESC \x02 103\x18\x11\x07 58910-S2600', - b'\xf1\x00TM ESC \x02 104\x19\x07\x07 58910-S2600', - b'\xf1\x00TM ESC \x03 103\x18\x11\x07 58910-S2600', - b'\xf1\x00TM ESC \x0c 103\x18\x11\x08 58910-S2650', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81606EA051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606G1051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606G3051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409', - b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8A12', - b'\xf1\x00TM MDPS C 1.00 1.01 56340-S2000 9129', - b'\xf1\x00TM MDPS R 1.00 1.02 57700-S1100 4TMDP102', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00TM MFC AT EUR LHD 1.00 1.01 99211-S1010 181207', - b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1', - b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', - b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91', - b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00', - b'\xf1\x87LDJUEA6010814HG1\x87w\x87x\x86gvw\x88\x88\x98\x88gw\x86wx\x88\x97\x88\x85o\xf8\xff\x86f_\xff\xd37\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', - b'\xf1\x87LDJUEA6458264HG1ww\x87x\x97x\x87\x88\x88\x99\x98\x89g\x88\x86xw\x88\x97x\x86o\xf7\xffvw\x8f\xff3\x9a\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', - b'\xf1\x87LDKUEA2045844HG1wwww\x98\x88x\x87\x88\x88\xa8\x88x\x99\x97\x89x\x88\xa7\x88U\x7f\xf8\xffvfO\xffC\x1e\xf1\x816W3E0051\x00\x00\xf1\x006W351_C2\x00\x006W3E0051\x00\x00TTM4T20NS3\x00\x00\x00\x00', - b'\xf1\x87LDKUEA9993304HG1\x87www\x97x\x87\x88\x99\x99\xa9\x99x\x99\xa7\x89w\x88\x97x\x86_\xf7\xffwwO\xffl#\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS1R\x7f\x90\n', - b'\xf1\x87LDLUEA6061564HG1\xa9\x99\x89\x98\x87wwwx\x88\x97\x88x\x99\xa7\x89x\x99\xa7\x89sO\xf9\xffvU_\xff<\xde\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', - b'\xf1\x87LDLUEA6159884HG1\x88\x87hv\x99\x99y\x97\x89\xaa\xb8\x9ax\x99\x87\x89y\x99\xb7\x99\xa7?\xf7\xff\x97wo\xff\xf3\x05\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', - b'\xf1\x87LDLUEA6852664HG1\x97wWu\x97www\x89\xaa\xc8\x9ax\x99\x97\x89x\x99\xa7\x89SO\xf7\xff\xa8\x88\x7f\xff\x03z\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', - b'\xf1\x87LDLUEA6898374HG1fevW\x87wwwx\x88\x97\x88h\x88\x96\x88x\x88\xa7\x88ao\xf9\xff\x98\x99\x7f\xffD\xe2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', - b'\xf1\x87LDLUEA6898374HG1fevW\x87wwwx\x88\x97\x88h\x88\x96\x88x\x88\xa7\x88ao\xf9\xff\x98\x99\x7f\xffD\xe2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS50\xcb\xc3\xed', - b'\xf1\x87SBJWAA5842214GG0\x88\x87\x88xww\x87x\x89\x99\xa8\x99\x88\x99\x98\x89w\x88\x87xw_\xfa\xfffU_\xff\xd1\x8d\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', - b'\xf1\x87SBJWAA5890864GG0\xa9\x99\x89\x98\x98\x87\x98y\x89\x99\xa8\x99w\x88\x87xww\x87wvo\xfb\xffuD_\xff\x9f\xb5\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', - b'\xf1\x87SBJWAA6562474GG0ffvgeTeFx\x88\x97\x88ww\x87www\x87w\x84o\xfa\xff\x87fO\xff\xc2 \xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x00\x00\x00\x00', - b'\xf1\x87SBJWAA6562474GG0ffvgeTeFx\x88\x97\x88ww\x87www\x87w\x84o\xfa\xff\x87fO\xff\xc2 \xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS1\x98{|\xe3', - b'\xf1\x87SBJWAA7780564GG0wvwgUUeVwwwwx\x88\x87\x88wwwwd_\xfc\xff\x86f\x7f\xff\xd7*\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS2F\x84<\xc0', - b'\xf1\x87SBJWAA8278284GG0ffvgUU\x85Xx\x88\x87\x88x\x88w\x88ww\x87w\x96o\xfd\xff\xa7U_\xff\xf2\xa0\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2G24NS2F\x84<\xc0', - b'\xf1\x87SBLWAA4363244GG0wvwgwv\x87hgw\x86ww\x88\x87xww\x87wdo\xfb\xff\x86f\x7f\xff3$\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS6\x00\x00\x00\x00', - b'\xf1\x87SBLWAA4363244GG0wvwgwv\x87hgw\x86ww\x88\x87xww\x87wdo\xfb\xff\x86f\x7f\xff3$\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS6x0\x17\xfe', - b'\xf1\x87SBLWAA4899564GG0VfvgUU\x85Xx\x88\x87\x88vfgf\x87wxwvO\xfb\xff\x97f\xb1\xffSB\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS7\x00\x00\x00\x00', - b'\xf1\x87SBLWAA6622844GG0wwwwff\x86hwwwwx\x88\x87\x88\x88\x88\x88\x88\x98?\xfd\xff\xa9\x88\x7f\xffn\xe5\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM2G24NS7u\x1e{\x1c', - b'\xf1\x87SDJXAA7656854GG1DEtWUU\x85X\x88\x88\x98\x88w\x88\x87xx\x88\x87\x88\x96o\xfb\xff\x86f\x7f\xff.\xca\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4G24NS2\x00\x00\x00\x00', - b'\xf1\x87SDJXAA7656854GG1DEtWUU\x85X\x88\x88\x98\x88w\x88\x87xx\x88\x87\x88\x96o\xfb\xff\x86f\x7f\xff.\xca\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4G24NS2K\xdaV0', - b'\xf1\x87SDKXAA2443414GG1vfvgwv\x87h\x88\x88\x88\x88ww\x87wwwww\x99_\xfc\xffvD?\xffl\xd2\xf1\x816W3E1051\x00\x00\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4G24NS6\x00\x00\x00\x00', - ], - }, - CAR.SANTA_FE_2022: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1500 ', - b'\xf1\x00TM__ SCC FHCUP 1.00 1.00 99110-S1500 ', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00TM ESC \x01 102!\x04\x03 58910-S2DA0', - b'\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0', - b'\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0', - b'\xf1\x8758910-S2DA0\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0', - b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0', - b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', - b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', - b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', - b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0', - b'\xf1\x00TM ESC \x02 103"\x07\x08 58910-S2GA0', - b'\xf1\x00TM ESC 103!\x030 58910-S1MA0', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_L50', - b'\xf1\x81HM6M1_0a0_H00', - b'\xf1\x82TACVN5GMI3XXXH0A', - b'\xf1\x82TMBZN5TMD3XXXG2E', - b'\xf1\x82TACVN5GSI3XXXH0A', - b'\xf1\x82TMCFD5MMCXXXXG0A', - b'\xf1\x81HM6M1_0a0_G20', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', - b'\xf1\x81HM6M2_0a0_G00', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', - b'\xf1\x8739101-2STN8\xf1\x81HM6M1_0a0_M00', - b'\xf1\x87 \xf1\x81 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00TM MDPS C 1.00 1.02 56370-S2AA0 0B19', - b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1AB0 4TSDC101', - b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1EB0 4TSDC101', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00TM MFC AT MES LHD 1.00 1.05 99211-S1500 220126', - b'\xf1\x00TMA MFC AT MEX LHD 1.00 1.01 99211-S2500 210205', - b'\xf1\x00TMA MFC AT USA LHD 1.00 1.00 99211-S2500 200720', - b'\xf1\x00TM MFC AT EUR LHD 1.00 1.03 99211-S1500 210224', - b'\xf1\x00TMA MFC AT USA LHD 1.00 1.01 99211-S2500 210205', - b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00HT6WA280BLHT6WAD00A1STM2G25NH2\x00\x00\x00\x00\x00\x00\xf8\xc0\xc3\xaa', - b'\xf1\x00HT6WA280BLHT6WAD00A1STM4G25NH1\x00\x00\x00\x00\x00\x00\x9cl\x04\xbc', - b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NSA\xf3\xf4Uj', - b'\xf1\x87SDMXCA9087684GN1VfvgUUeVwwgwwwwwffffU?\xfb\xff\x97\x88\x7f\xff+\xa4\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', - b'\xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', - b'\xf1\x87SDMXCA8653204GN1EVugEUuWwwwwww\x87wwwwwv/\xfb\xff\xa8\x88\x9f\xff\xa5\x9c\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', - b'\xf1\x87KMMYBU034207SB72x\x89\x88\x98h\x88\x98\x89\x87fhvvfWf33_\xff\x87\xff\x8f\xfa\x81\xe5\xf1\x89HT6TAF00A1\xf1\x82STM0M25GS1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6', - b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', - b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', - b'\xf1\x00T02601BL T02800A1 VTMPT25XXX800NS4\xed\xaf\xed\xf5', - b'\xf1\x00T02601BL T02900A1 VTMPT25XXW900NS1c\x918\xc5', - ], - }, - CAR.SANTA_FE_HEV_2022: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', - b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102', - b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105', - b'\xf1\x00TM MDPS C 1.00 1.02 56310-GA000 4TSHA100', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00TMH MFC AT EUR LHD 1.00 1.06 99211-S1500 220727', - b'\xf1\x00TMH MFC AT USA LHD 1.00 1.03 99211-S1500 210224', - b'\xf1\x00TMH MFC AT USA LHD 1.00 1.06 99211-S1500 220727', - b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16SA3\xa3\x1b\xe14', - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16UA3I\x94\xac\x8f', - b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TTM2H16SA2\x80\xd7l\xb2', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391312MTC1', - b'\xf1\x87391312MTE0', - b'\xf1\x87391312MTL0', - ], - }, - CAR.SANTA_FE_PHEV_2022: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', - b'\xf1\x00TMhe SCC FHCUP 1.00 1.01 99110-CL500 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', - b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102', - b'\xf1\x00TM MDPS C 1.00 1.02 56310CLEC0\x00 4TSHC102', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00TMP MFC AT USA LHD 1.00 1.03 99211-S1500 210224', - b'\xf1\x00TMP MFC AT USA LHD 1.00 1.06 99211-S1500 220727', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe', - b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391312MTF0', - b'\xf1\x87391312MTF1', - ], - }, - CAR.CUSTIN_1ST_GEN: { - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00KU ESC \x01 101!\x02\x03 58910-O3200', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00KU__ SCC F-CUP 1.00 1.01 99110-O3000 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00KU MDPS C 1.00 1.01 56310/O3100 4KUCC101', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00KU2 MFC AT CHN LHD 1.00 1.02 99211-O3000 220923', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391212MEC0', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U928\x00\x00\x00\x00\x00\x00SKU0T15KB2\x92U\xf9M', - ], - }, - CAR.KIA_STINGER: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ', - b'\xf1\x00CK__ SCC F_CUP 1.00 1.03 96400-J5100 ', - b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5000 ', - b'\xf1\x00CK__ SCC F_CUP 1.00 1.02 96400-J5100 ', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81606DE051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640E0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00', - b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00', - b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00', - b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104', - b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5220 4C2VL104', - b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5420 4C4VL104', - b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5420 4C4VL106', - b'\xf1\x00CK MDPS R 1.00 1.07 57700-J5220 4C2VL107', - b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5220 4C2VL106', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 95740-J5000 170822', - b'\xf1\x00CK MFC AT USA LHD 1.00 1.04 95740-J5000 180504', - b'\xf1\x00CK MFC AT EUR LHD 1.00 1.03 95740-J5000 170822', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SCK0T33NB2\xb3\xee\xba\xdc', - b'\xf1\x87VCJLE17622572DK0vd6D\x99\x98y\x97vwVffUfvfC%CuT&Dx\x87o\xff{\x1c\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x89E21\x00\x00\x00\x00\x00\x00\x00\xf1\x82SCK0T33NB0', - b'\xf1\x87VDHLG17034412DK2vD6DfVvVTD$D\x99w\x88\x98EDEDeT6DgfO\xff\xc3=\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x87VDHLG17118862DK2\x8awWwgu\x96wVfUVwv\x97xWvfvUTGTx\x87o\xff\xc9\xed\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - b'\xf1\x87VDKLJ18675252DK6\x89vhgwwwwveVU\x88w\x87w\x99vgf\x97vXfgw_\xff\xc2\xfb\xf1\x89E25\x00\x00\x00\x00\x00\x00\x00\xf1\x82TCK0T33NB2', - b'\xf1\x87WAJTE17552812CH4vfFffvfVeT5DwvvVVdFeegeg\x88\x88o\xff\x1a]\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00TCK2T20NB1\x19\xd2\x00\x94', - b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S', - b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', - ], - }, - CAR.KIA_STINGER_2022: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ', - b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5500 ', - b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5600 ', - b'\xf1\x00CK__ SCC FHCUP 1.00 1.01 99110-J5100 ', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640N2051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81HM6M1_0a0_H00', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', - b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5300 4C2CL503', - b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5320 4C2VL503', - b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622', - b'\xf1\x00CK MFC AT KOR LHD 1.00 1.00 99211-J5500 210622', - b'\xf1\x00CK MFC AT USA LHD 1.00 1.00 99211-J5500 210622', - b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 99211-J5000 201209', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', - b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1', - b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T25KH2B\xfbI\xe2', - b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00TCK0T33NH0%g~\xd3', - ], - }, - CAR.PALISADE: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ', - b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ', - b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ', - b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ', - b'\xf1\x00LX2_ SCC FHCUP 1.00 1.00 99110-S8110 ', - b'\xf1\x00LX2_ SCC FHCUP 1.00 1.04 99110-S8100 ', - b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ', - b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ', - b'\xf1\x00ON__ FCA FHCUP 1.00 1.01 99110-S9110 ', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360', - b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360', - b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330', - b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330', - b'\xf1\x00LX ESC \x0b 103\x19\t\t 58910-S8350', - b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330', - b'\xf1\x00LX ESC \x0b 103\x19\t\x10 58910-S8360', - b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360', - b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360', - b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', - b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', - b'\xf1\x00ON ESC \x01 101\x19\t\x08 58910-S9360', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640K0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640S1051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103', - b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8000 4LXDC103', - b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103', - b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104', - b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13', - b'\xf1\x00ON MDPS C 1.00 1.01 56340-S9000 9201', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.03 99211-S8100 190125', - b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.05 99211-S8100 190909', - b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 200422', - b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 200903', - b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 181105', - b'\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 200720', - b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226', - b'\xf1\x00ON MFC AT USA LHD 1.00 1.04 99211-S9100 211227', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', - b'\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00TON2G38NB5j\x94.\xde', - b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1', - b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', - b'\xf1\x87LBLUFN655162KF36\x98\x88\x88\x88\x98\x88\x88\x88x\x99\xa7\x89x\x99\xa7\x89x\x99\x97\x89g\x7f\xf7\xffwU_\xff\xe9!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', - b'\xf1\x87LBLUFN731381KF36\xb9\x99\x89\x98\x98\x88\x88\x88\x89\x99\xa8\x99\x88\x99\xa8\x89\x88\x88\x98\x88V\x7f\xf6\xff\x99w\x8f\xff\xad\xd8\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', - b'\xf1\x87LDKVAA0028604HH1\xa8\x88x\x87vgvw\x88\x99\xa8\x89gw\x86ww\x88\x97x\x97o\xf9\xff\x97w\x7f\xffo\x02\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', - b'\xf1\x87LDKVAA3068374HH1wwww\x87xw\x87y\x99\xa7\x99w\x88\x87xw\x88\x97x\x85\xaf\xfa\xffvU/\xffU\xdc\xf1\x81U872\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', - b'\xf1\x87LDKVBN382172KF26\x98\x88\x88\x88\xa8\x88\x88\x88x\x99\xa7\x89\x87\x88\x98x\x98\x99\xa9\x89\xa5_\xf6\xffDDO\xff\xcd\x16\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', - b'\xf1\x87LDKVBN424201KF26\xba\xaa\x9a\xa9\x99\x99\x89\x98\x89\x99\xa8\x99\x88\x99\x98\x89\x88\x99\xa8\x89v\x7f\xf7\xffwf_\xffq\xa6\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', - b'\xf1\x87LDKVBN540766KF37\x87wgv\x87w\x87xx\x99\x97\x89v\x88\x97h\x88\x88\x88\x88x\x7f\xf6\xffvUo\xff\xd3\x01\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', - b'\xf1\x87LDLVAA4225634HH1\x98\x88\x88\x88eUeVx\x88\x87\x88g\x88\x86xx\x88\x87\x88\x86o\xf9\xff\x87w\x7f\xff\xf2\xf7\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVAA4777834HH1\x98\x88x\x87\x87wwwx\x88\x87\x88x\x99\x97\x89x\x88\x97\x88\x86o\xfa\xff\x86fO\xff\x1d9\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVAA5194534HH1ffvguUUUx\x88\xa7\x88h\x99\x96\x89x\x88\x97\x88ro\xf9\xff\x98wo\xff\xaaM\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVAA5949924HH1\xa9\x99y\x97\x87wwwx\x99\x97\x89x\x99\xa7\x89x\x99\xa7\x89\x87_\xfa\xffeD?\xff\xf1\xfd\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDLVBN560098KF26\x86fff\x87vgfg\x88\x96xfw\x86gfw\x86g\x95\xf6\xffeU_\xff\x92c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', - b'\xf1\x87LDLVBN602045KF26\xb9\x99\x89\x98\x97vwgy\xaa\xb7\x9af\x88\x96hw\x99\xa7y\xa9\x7f\xf5\xff\x99w\x7f\xff,\xd3\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN628911KF26\xa9\x99\x89\x98\x98\x88\x88\x88y\x99\xa7\x99fw\x86gw\x88\x87x\x83\x7f\xf6\xff\x98wo\xff2\xda\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN645817KF37\x87www\x98\x87xwx\x99\x97\x89\x99\x99\x99\x99g\x88\x96x\xb6_\xf7\xff\x98fo\xff\xe2\x86\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN662115KF37\x98\x88\x88\x88\xa8\x88\x88\x88x\x99\x97\x89x\x99\xa7\x89\x88\x99\xa8\x89\x88\x7f\xf7\xfffD_\xff\xdc\x84\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN667933KF37\xb9\x99\x89\x98\xb9\x99\x99\x99x\x88\x87\x88w\x88\x87x\x88\x88\x98\x88\xcbo\xf7\xffe3/\xffQ!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN673087KF37\x97www\x86fvgx\x99\x97\x89\x99\xaa\xa9\x9ag\x88\x86x\xe9_\xf8\xff\x98w\x7f\xff"\xad\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN673841KF37\x98\x88x\x87\x86g\x86xy\x99\xa7\x99\x88\x99\xa8\x89w\x88\x97xdo\xf5\xff\x98\x88\x8f\xffT\xec\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN681363KF37\x98\x88\x88\x88\x97x\x87\x88y\xaa\xa7\x9a\x88\x88\x98\x88\x88\x88\x88\x88vo\xf6\xffvD\x7f\xff%v\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN713782KF37\x99\x99y\x97\x98\x88\x88\x88x\x88\x97\x88\x88\x99\x98\x89\x88\x99\xa8\x89\x87o\xf7\xffeU?\xff7,\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN713890KF26\xb9\x99\x89\x98\xa9\x99\x99\x99x\x99\x97\x89\x88\x99\xa8\x89\x88\x99\xb8\x89Do\xf7\xff\xa9\x88o\xffs\r\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN733215KF37\x99\x98y\x87\x97wwwi\x99\xa6\x99x\x99\xa7\x89V\x88\x95h\x86o\xf7\xffeDO\xff\x12\xe7\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN750044KF37\xca\xa9\x8a\x98\xa7wwwy\xaa\xb7\x9ag\x88\x96x\x88\x99\xa8\x89\xb9\x7f\xf6\xff\xa8w\x7f\xff\xbe\xde\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN752612KF37\xba\xaa\x8a\xa8\x87w\x87xy\xaa\xa7\x9a\x88\x99\x98\x89x\x88\x97\x88\x96o\xf6\xffvU_\xffh\x1b\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN755553KF37\x87xw\x87\x97w\x87xy\x99\xa7\x99\x99\x99\xa9\x99Vw\x95gwo\xf6\xffwUO\xff\xb5T\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', - b'\xf1\x87LDLVBN757883KF37\x98\x87xw\x98\x87\x88xy\xaa\xb7\x9ag\x88\x96x\x89\x99\xa8\x99e\x7f\xf6\xff\xa9\x88o\xff5\x15\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN778156KF37\x87vWe\xa9\x99\x99\x99y\x99\xb7\x99\x99\x99\x99\x99x\x99\x97\x89\xa8\x7f\xf8\xffwf\x7f\xff\x82_\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN780576KF37\x98\x87hv\x97x\x97\x89x\x99\xa7\x89\x88\x99\x98\x89w\x88\x97x\x98\x7f\xf7\xff\xba\x88\x8f\xff\x1e0\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN783485KF37\x87www\x87vwgy\x99\xa7\x99\x99\x99\xa9\x99Vw\x95g\x89_\xf6\xff\xa9w_\xff\xc5\xd6\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN811844KF37\x87vwgvfffx\x99\xa7\x89Vw\x95gg\x88\xa6xe\x8f\xf6\xff\x97wO\xff\t\x80\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN830601KF37\xa7www\xa8\x87xwx\x99\xa7\x89Uw\x85Ww\x88\x97x\x88o\xf6\xff\x8a\xaa\x7f\xff\xe2:\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', - b'\xf1\x87LDMVBN848789KF37\x87w\x87x\x87w\x87xy\x99\xb7\x99\x87\x88\x98x\x88\x99\xa8\x89\x87\x7f\xf6\xfffUo\xff\xe3!\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN851595KF37\x97wgvvfffx\x99\xb7\x89\x88\x99\x98\x89\x87\x88\x98x\x99\x7f\xf7\xff\x97w\x7f\xff@\xf3\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN873175KF26\xa8\x88\x88\x88vfVex\x99\xb7\x89\x88\x99\x98\x89x\x88\x97\x88f\x7f\xf7\xff\xbb\xaa\x8f\xff,\x04\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN879401KF26veVU\xa8\x88\x88\x88g\x88\xa6xVw\x95gx\x88\xa7\x88v\x8f\xf9\xff\xdd\xbb\xbf\xff\xb3\x99\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN881314KF37\xa8\x88h\x86\x97www\x89\x99\xa8\x99w\x88\x97xx\x99\xa7\x89\xca\x7f\xf8\xff\xba\x99\x8f\xff\xd8v\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN888651KF37\xa9\x99\x89\x98vfff\x88\x99\x98\x89w\x99\xa7y\x88\x88\x98\x88D\x8f\xf9\xff\xcb\x99\x8f\xff\xa5\x1e\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN889419KF37\xa9\x99y\x97\x87w\x87xx\x88\x97\x88w\x88\x97x\x88\x99\x98\x89e\x9f\xf9\xffeUo\xff\x901\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN895969KF37vefV\x87vgfx\x99\xa7\x89\x99\x99\xb9\x99f\x88\x96he_\xf7\xffxwo\xff\x14\xf9\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDMVBN899222KF37\xa8\x88x\x87\x97www\x98\x99\x99\x89\x88\x99\x98\x89f\x88\x96hdo\xf7\xff\xbb\xaa\x9f\xff\xe2U\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8", - b'\xf1\x87LDMVBN950669KF37\x97www\x96fffy\x99\xa7\x99\xa9\x99\xaa\x99g\x88\x96x\xb8\x8f\xf9\xffTD/\xff\xa7\xcb\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDLVAA4478824HH1\x87wwwvfvg\x89\x99\xa8\x99w\x88\x87x\x89\x99\xa8\x99\xa6o\xfa\xfffU/\xffu\x92\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDMVBN871852KF37\xb9\x99\x99\x99\xa8\x88\x88\x88y\x99\xa7\x99x\x99\xa7\x89\x88\x88\x98\x88\x89o\xf7\xff\xaa\x88o\xff\x0e\xed\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - ], - }, - CAR.VELOSTER: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JS__ SCC H-CUP 1.00 1.02 95650-J3200 ', - b'\xf1\x00JS__ SCC HNCUP 1.00 1.02 95650-J3100 ', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816V8RAC00121.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x01TJS-JNU06F200H0A', - b'\x01TJS-JDK06F200H0A', - b'391282BJF5 ', - ], - (Ecu.eps, 0x7d4, None): [b'\xf1\x00JSL MDPS C 1.00 1.03 56340-J3000 8308', ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JS LKAS AT USA LHD 1.00 1.02 95740-J3000 K32', - b'\xf1\x00JS LKAS AT KOR LHD 1.00 1.03 95740-J3000 K33', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\xba\x02\xb8\x80', - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\x00\x00\x00\x00', - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16KS2\016\xba\036\xa2', - ], - }, - CAR.GENESIS_G70: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', - b'\xf1\x00IK__ SCC F-CUP 1.00 1.01 96400-G9100 ', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00IK MDPS R 1.00 1.06 57700-G9420 4I4VL106', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', - b'\xf1\x87VDJLT17895112DN4\x88fVf\x99\x88\x88\x88\x87fVe\x88vhwwUFU\x97eFex\x99\xff\xb7\x82\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', - ], - }, - CAR.GENESIS_G70_2020: { - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9220 4I2VL107', - b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9420 4I4VL107', - b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9420 4I4VL108', - b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9200 4I2CL108', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87VCJLP18407832DN3\x88vXfvUVT\x97eFU\x87d7v\x88eVeveFU\x89\x98\x7f\xff\xb2\xb0\xf1\x81E25\x00\x00\x00', - b'\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', - b'\xf1\x87VDKLT18912362DN4wfVfwefeveVUwfvw\x88vWfvUFU\x89\xa9\x8f\xff\x87w\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', - b'\xf1\x87VDJLC18480772DK9\x88eHfwfff\x87eFUeDEU\x98eFe\x86T5DVyo\xff\x87s\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33KB5\x9f\xa5&\x81', - b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T20KB3Wuvz', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', - b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 \xf1\xa01.02', - b'\xf1\x00IK__ SCC FHCUP 1.00 1.02 96400-G9000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', - b'\xf1\x00IK MFC AT KOR LHD 1.00 1.01 95740-G9000 170920', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81606G2051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.GENESIS_G80: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ', - b'\xf1\x00DH__ SCC FHCUP 1.00 1.01 96400-B1110 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713', - b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810', - b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014', - b'\xf1\x00DH LKAS AT KOR LHD 1.01 1.02 95895-B1500 170810', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G33KH2\xae\xde\xd5!', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640A4051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.GENESIS_G90: { - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87VDGMD15352242DD3w\x87gxwvgv\x87wvw\x88wXwffVfffUfw\x88o\xff\x06J\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7', - b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7', - ], - (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 '], - (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2020 160302'], - (Ecu.engine, 0x7e0, None): [b'\xf1\x810000000000\x00'], - }, - CAR.KONA: { - (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00OS__ SCC F-CUP 1.00 1.00 95655-J9200 ', ], - (Ecu.abs, 0x7d1, None): [b'\xf1\x816V5RAK00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [b'"\x01TOS-0NU06F301J02', ], - (Ecu.eps, 0x7d4, None): [b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105', ], - (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9300 g21', ], - (Ecu.transmission, 0x7e1, None): [b'\xf1\x816U2VE051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VE051\x00\x00DOS4T16NS3\x00\x00\x00\x00', ], - }, - CAR.KIA_CEED: { - (Ecu.fwdRadar, 0x7D0, None): [b'\xf1\000CD__ SCC F-CUP 1.00 1.02 99110-J7000 ', ], - (Ecu.eps, 0x7D4, None): [b'\xf1\000CD MDPS C 1.00 1.06 56310-XX000 4CDEC106', ], - (Ecu.fwdCamera, 0x7C4, None): [b'\xf1\000CD LKAS AT EUR LHD 1.00 1.01 99211-J7000 B40', ], - (Ecu.engine, 0x7E0, None): [b'\001TCD-JECU4F202H0K', ], - (Ecu.transmission, 0x7E1, None): [ - b'\xf1\x816U2V7051\000\000\xf1\0006U2V0_C2\000\0006U2V7051\000\000DCD0T14US1\000\000\000\000', - b'\xf1\x816U2V7051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V7051\x00\x00DCD0T14US1U\x867Z', - ], - (Ecu.abs, 0x7D1, None): [b'\xf1\000CD ESC \003 102\030\b\005 58920-J7350', ], - }, - CAR.KIA_FORTE: { - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x00BD MDPS C 1.00 1.02 56310-XX000 4BD2C102', - b'\xf1\x00BD MDPS C 1.00 1.08 56310/M6300 4BDDC108', - b'\xf1\x00BD MDPS C 1.00 1.08 56310M6300\x00 4BDDC108', - b'\xf1\x00BDm MDPS C A.01 1.03 56310M7800\x00 4BPMC103', - ], - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00BD LKAS AT USA LHD 1.00 1.04 95740-M6000 J33', - b'\xf1\x00BDP LKAS AT USA LHD 1.00 1.05 99211-M6500 744', - ], - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00BD__ SCC H-CUP 1.00 1.02 99110-M6000 ', - b'\xf1\x00BDPE_SCC FHCUPC 1.00 1.04 99110-M6500\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x01TBDM1NU06F200H01', - b'391182B945\x00', - b'\xf1\x81616F2051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x816VGRAH00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x8758900-M7AB0 \xf1\x816VQRAD00127.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006V2B0_C2\x00\x006V2C6051\x00\x00CBD0N20NL1\x00\x00\x00\x00', - b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DBD0T16SS0\x00\x00\x00\x00', - b"\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DBD0T16SS0\xcf\x1e'\xc3", - ], - }, - CAR.KIA_K5_2021: { - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', - b'\xf1\x8799110L2000\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', - b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', - b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', - b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', - b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', - b'\xf1\x00DL3_ SCC FHCUP 1.00 1.04 99110-L2100 ', - ], - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x8756310-L3110\xf1\000DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', - b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', - b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', - b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', - b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L2220 4DLDC102', - b'\xf1\x00DL3 MDPS C 1.00 1.02 56310L3220\x00 4DLAC102', - ], - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915', - b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.04 99210-L3000 210208', - b'\xf1\x00DL3 MFC AT KOR LHD 1.00 1.04 99210-L2000 210527', - b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.05 99210-L3000 211222', - ], - (Ecu.abs, 0x7D1, None): [ - b'\xf1\000DL ESC \006 101 \004\002 58910-L3200', - b'\xf1\x8758910-L3200\xf1\000DL ESC \006 101 \004\002 58910-L3200', - b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', - b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', - b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', - b'\xf1\x00DL ESC \x01 104 \x07\x12 58910-L2200', - b'\xf1\x00DL ESC \x06 103"\x08\x06 58910-L3200', - ], - (Ecu.engine, 0x7E0, None): [ - b'\xf1\x87391212MKT0', - b'\xf1\x87391212MKV0', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DLDWN5TMDCXXXJ1B', - b'\xf1\x81HM6M2_0a0_DQ0', - b'\xf1\x87391212MKT3', - ], - (Ecu.transmission, 0x7E1, None): [ - b'\xf1\000bcsh8p54 U913\000\000\000\000\000\000TDL2T16NB1ia\v\xb8', - b'\xf1\x87SALFEA5652514GK2UUeV\x88\x87\x88xxwg\x87ww\x87wwfwvd/\xfb\xffvU_\xff\x93\xd3\xf1\x81U913\000\000\000\000\000\000\xf1\000bcsh8p54 U913\000\000\000\000\000\000TDL2T16NB1ia\v\xb8', - b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', - b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', - b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', - b'\xf1\x00HT6TA261BLHT6TAB00A1SDL0C20KS0\x00\x00\x00\x00\x00\x00\\\x9f\xa5\x15', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB2.\x13\xf6\xed', - ], - }, - CAR.KIA_K5_HEV_2020: { - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00DLhe SCC FHCUP 1.00 1.02 99110-L7000 ', - ], - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7000 4DLHC102', - b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7220 4DLHC102', - ], - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.02 99210-L2000 200309', - b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.04 99210-L2000 210527', - ], - (Ecu.engine, 0x7E0, None): [ - b'\xf1\x87391162JLA0', - ], - (Ecu.transmission, 0x7E1, None): [ - b'\xf1\x00PSBG2323 E08\x00\x00\x00\x00\x00\x00\x00TDL2H20KA2\xe3\xc6cz', - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDL2H20KA5T\xf2\xc9\xc2', - ], - }, - CAR.KONA_EV: { - (Ecu.abs, 0x7D1, None): [ - b'\xf1\x00OS IEB \r 105\x18\t\x18 58520-K4000', - b'\xf1\x00OS IEB \x01 212 \x11\x13 58520-K4000', - b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000', - b'\xf1\x00OS IEB \x03 210 \x02\x14 58520-K4000', - b'\xf1\x00OS IEB \x03 212 \x11\x13 58520-K4000', - ], - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00OE2 LKAS AT EUR LHD 1.00 1.00 95740-K4200 200', - b'\xf1\x00OSE LKAS AT EUR LHD 1.00 1.00 95740-K4100 W40', - b'\xf1\x00OSE LKAS AT EUR RHD 1.00 1.00 95740-K4100 W40', - b'\xf1\x00OSE LKAS AT KOR LHD 1.00 1.00 95740-K4100 W40', - b'\xf1\x00OSE LKAS AT USA LHD 1.00 1.00 95740-K4300 W50', - ], - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x00OS MDPS C 1.00 1.03 56310/K4550 4OEDC103', - b'\xf1\x00OS MDPS C 1.00 1.04 56310K4000\x00 4OEDC104', - b'\xf1\x00OS MDPS C 1.00 1.04 56310K4050\x00 4OEDC104', - ], - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00OSev SCC F-CUP 1.00 1.00 99110-K4000 ', - b'\xf1\x00OSev SCC F-CUP 1.00 1.00 99110-K4100 ', - b'\xf1\x00OSev SCC F-CUP 1.00 1.01 99110-K4000 ', - b'\xf1\x00OSev SCC FNCUP 1.00 1.01 99110-K4000 ', - ], - }, - CAR.KONA_EV_2022: { - (Ecu.abs, 0x7D1, None): [ - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', - b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010', - b'\xf1\x00OS IEB \x02 102"\x05\x16 58520-K4010', - b'\xf1\x00OS IEB \x03 102"\x05\x16 58520-K4010', - ], - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', - b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', - b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', - b'\xf1\x00OSP LKA AT EUR LHD 1.00 1.04 99211-J9200 904', - b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.04 99211-J9200 904', - b'\xf1\x00OSP LKA AT USA LHD 1.00 1.04 99211-J9200 904', - ], - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', - b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', - b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102', - b'\xf1\x00OSP MDPS C 1.00 1.02 56310-K4271 4OEPC102', - b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4971\x00 4OEPC102', - b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4261\x00 4OEPC102', - ], - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', - ], - }, - CAR.KONA_EV_2ND_GEN: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00SXev RDR ----- 1.00 1.00 99110-BF000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00SX2EMFC AT KOR LHD 1.00 1.00 99211-BF000 230410', - ], - }, - CAR.KIA_NIRO_EV: { - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', - b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4000 ', - b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ', - b'\xf1\x00DEev SCC F-CUP 1.00 1.03 96400-Q4100 ', - b'\xf1\x00DEev SCC FHCUP 1.00 1.03 96400-Q4000 ', - b'\xf1\x8799110Q4000\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', - b'\xf1\x8799110Q4100\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4100 ', - b'\xf1\x8799110Q4500\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4500 ', - b'\xf1\x8799110Q4600\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4600 ', - b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', - b'\xf1\x8799110Q4600\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ', - ], - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105', - b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105', - b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104', - ], - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706', - b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4000 191211', - b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211', - b'\xf1\x00DEE MFC AT USA LHD 1.00 1.03 95740-Q4000 180821', - b'\xf1\x00DEE MFC AT USA LHD 1.00 1.01 99211-Q4500 210428', - b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.03 95740-Q4000 180821', - b'\xf1\x00DEE MFC AT KOR LHD 1.00 1.03 95740-Q4000 180821', - ], - }, - CAR.KIA_NIRO_EV_2ND_GEN: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00SG2EMFC AT EUR LHD 1.01 1.09 99211-AT000 220801', - b'\xf1\x00SG2EMFC AT USA LHD 1.01 1.09 99211-AT000 220801', - ], - }, - CAR.KIA_NIRO_PHEV: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91", - b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00', - b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00', - b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92', - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01', - ], - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', - b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', - ], - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', - b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117', - b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826', - ], - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', - b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ', - ], - }, - CAR.KIA_NIRO_PHEV_2022: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL3\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', - ], - (Ecu.fwdCamera, 0x7C4, None): [ - b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 99211-G5500 210428', - ], - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00DEhe SCC F-CUP 1.00 1.00 99110-G5600 ', - ], - }, - CAR.KIA_NIRO_HEV_2021: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HDE0G16NL3\x00\x00\x00\x00', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HDE0G16NL3\xb9\xd3\xfaW', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DEH MFC AT USA LHD 1.00 1.07 99211-G5000 201221', - b'\xf1\x00DEH MFC AT USA LHD 1.00 1.00 99211-G5500 210428', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00DEhe SCC FHCUP 1.00 1.00 99110-G5600 ', - ], - }, - CAR.KIA_SELTOS: { - (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x8799110Q5100\xf1\000SP2_ SCC FHCUP 1.01 1.05 99110-Q5100 ',], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x8758910-Q5450\xf1\000SP ESC \a 101\031\t\005 58910-Q5450', - b'\xf1\x8758910-Q5450\xf1\000SP ESC \t 101\031\t\005 58910-Q5450', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81616D2051\000\000\000\000\000\000\000\000', - b'\xf1\x81616D5051\000\000\000\000\000\000\000\000', - b'\001TSP2KNL06F100J0K', - b'\001TSP2KNL06F200J0K', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\000SP2 MDPS C 1.00 1.04 56300Q5200 ', - b'\xf1\000SP2 MDPS C 1.01 1.05 56300Q5200 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\000SP2 MFC AT USA LHD 1.00 1.04 99210-Q5000 191114', - b'\xf1\000SP2 MFC AT USA LHD 1.00 1.05 99210-Q5000 201012', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87CZLUB49370612JF7h\xa8y\x87\x99\xa7hv\x99\x97fv\x88\x87x\x89x\x96O\xff\x88\xff\xff\xff.@\xf1\x816V2C2051\000\000\xf1\0006V2B0_C2\000\0006V2C2051\000\000CSP4N20NS3\000\000\000\000', - b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS6\xd30\xa5\xb9', - b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b', - ], - }, - CAR.KIA_OPTIMA_G4: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4100 ', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00JF ESC \x0f 16 \x16\x06\x17 58920-D5080', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JFWGN LDWS AT USA LHD 1.00 1.02 95895-D4100 G21', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6J0051\x00\x00\xf1\x006T6J0_C2\x00\x006T6J0051\x00\x00TJF0T20NSB\x00\x00\x00\x00', - ], - }, - CAR.KIA_OPTIMA_G4_FL: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 ', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00JF ESC \x0b 11 \x18\x030 58920-D5180', - b"\xf1\x00JF ESC \t 11 \x18\x03' 58920-D5260", - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32', - b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5100 h32', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', - b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', - b'\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01', - b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', - b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', - b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01', - b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B8051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B8051\x00\x00TJFSG24NH27\xa7\xc2\xb4', - ], - }, - CAR.KIA_OPTIMA_H: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JFhe SCC FNCUP 1.00 1.00 96400-A8000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JFP LKAS AT EUR LHD 1.00 1.03 95895-A8100 160711', - ], - }, - CAR.KIA_OPTIMA_H_G4_FL: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JFhe SCC FHCUP 1.00 1.01 99110-A8500 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JFH MFC AT KOR LHD 1.00 1.01 95895-A8200 180323', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.ELANTRA: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00ADP LKAS AT USA LHD 1.00 1.03 99211-F2000 X31', - b'\xf1\x00AD LKAS AT USA LHD 1.01 1.01 95895-F2000 251', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD(\xfcA\x9d', - b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD\x00\x00\x00\x00', - b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\x00\x00\x00\x00', - b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\xc5\x92\x9e\x8a', - b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20SS2.~\x90\x87', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8161698051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x8161657051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816165D051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816165E051\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00AD ESC \x11 11 \x18\x05\x06 58910-F2840', - b'\xf1\x00AD ESC \x11 12 \x15\t\t 58920-F2810', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AD__ SCC H-CUP 1.00 1.00 99110-F2100 ', - b'\xf1\x00AD__ SCC H-CUP 1.00 1.01 96400-F2100 ', - ], - }, - CAR.ELANTRA_GT_I30: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', - b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e', - b'\xf1\x006U2U0_C2\x00\x006U2T0051\x00\x00DPD0D16KS0u\xce\x1fk', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104', - b'\xf1\x00PD MDPS C 1.00 1.00 56310G3300\x00 4PDDC100', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350', - b'\xf1\x00PD ESC \t 104\x18\t\x03 58920-G3350', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ', - b'\xf1\x00PD__ SCC FNCUP 1.01 1.00 96400-G3000 ', - ], - }, - CAR.ELANTRA_2021: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', - b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', - b'\xf1\x00CN7_ SCC FNCUP 1.00 1.01 99110-AA000 ', - b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', - b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', - b'\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', - b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819', - b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205', - b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111', - b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AB000 220426', - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', - b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800', - b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800', - b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\xe8\xba\xce\xfa', - b'\xf1\x87CXMQFM2135005JB2E\xb9\x89\x98W\xa9y\x97h\xa9\x98\x99wxvwh\x87\177\xffx\xff\xff\xff,,\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXMQFM1916035JB2\x88vvgg\x87Wuwgev\xa9\x98\x88\x98h\x99\x9f\xffh\xff\xff\xff\xa5\xee\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXLQF40189012JL2f\x88\x86\x88\x88vUex\xb8\x88\x88\x88\x87\x88\x89fh?\xffz\xff\xff\xff\x08z\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXMQFM2728305JB2E\x97\x87xw\x87vwgw\x84x\x88\x88w\x89EI\xbf\xff{\xff\xff\xff\xe6\x0e\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXMQFM3806705JB2\x89\x87wwx\x88g\x86\x99\x87\x86xwwv\x88yv\x7f\xffz\xff\xff\xffV\x15\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x82CNCWD0AMFCXCSFFA', - b'\xf1\x81HM6M2_0a0_FF0', - b'\xf1\x82CNCVD0AMFCXCSFFB', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_HC0', - ], - }, - CAR.ELANTRA_HEV_2021: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', - b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', - b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426', - b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.08 99210-AA000 220728', - b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.09 99210-AA000 221108', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', - b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103', - b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', - b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', - b'\xf1\x00CN7 MDPS C 1.00 1.04 56310BY050\x00 4CNHC104', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', - b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000', - b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', - b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', - b'\xf1\x006U3L0_C2\x00\x006U3K9051\x00\x00HCN0G16NS1\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6G8051\x00\x00\x00\x00\x00\x00\x00\x00', - ] - }, - CAR.KONA_HEV: { - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00OS IEB \x01 104 \x11 58520-CM000', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00OShe SCC FNCUP 1.00 1.01 99110-CM000 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00OS MDPS C 1.00 1.00 56310CM030\x00 4OHDC100', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00OSH LKAS AT KOR LHD 1.00 1.01 95740-CM000 l31', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HOS0G16DS1\x16\xc7\xb0\xd9', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - ] - }, - CAR.SONATA_HYBRID: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', - b'\xf1\x8799110L5000\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', - b'\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', - b'\xf1\x8799110L5000\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', - ], - (Ecu.eps, 0x7d4, None): [ - b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', - b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102', - b'\xf1\x8756310-L5450\xf1\000DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103', - b'\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', - b'\xf1\x8756310L5450\x00\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.04 99211-L1000 191016', - b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.05 99211-L1000 201109', - b'\xf1\000DN8HMFC AT USA LHD 1.00 1.06 99211-L1000 210325', - b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.07 99211-L1000 211223', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\000PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', - b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E09\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', - b'\xf1\000PSBG2323 E09\000\000\000\000\000\000\000TDN2H20SA5\x97R\x88\x9e', - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', - b'\xf1\x87PCU\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', - b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391162J012', - b'\xf1\x87391162J013', - b'\xf1\x87391162J014', - b'\xf1\x87391062J002', - ], - }, - CAR.KIA_SORENTO: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00UMP LKAS AT USA LHD 1.01 1.01 95740-C6550 d01' - ], - (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00UM ESC \x0c 12 \x18\x05\x06 58910-C6330' - ], - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00UM__ SCC F-CUP 1.00 1.00 96400-C6500 ' - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87LDKUAA0348164HE3\x87www\x87www\x88\x88\xa8\x88w\x88\x97xw\x88\x97x\x86o\xf8\xff\x87f\x7f\xff\x15\xe0\xf1\x81U811\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U811\x00\x00\x00\x00\x00\x00TUM4G33NL3V|DG' - ], - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00' - ], - }, - CAR.KIA_SORENTO_PHEV_4TH_GEN: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', - b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406', - ], - }, - CAR.KIA_EV6: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', - b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', - b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027', - b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328', - b'\xf1\x00CV1 MFC AT EUR RHD 1.00 1.00 99210-CV100 220630', - b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630', - b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.04 99210-CV000 210823', - b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.05 99210-CV000 211027', - b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.06 99210-CV000 220328', - ], - }, - CAR.IONIQ_5: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', - b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', - b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.05 99211-GI010 220614', - b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007', - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', - b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.02 99211-GI010 211206', - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.06 99211-GI010 230110', - ], - }, - CAR.IONIQ_6: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00CE__ RDR ----- 1.00 1.01 99110-KL000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00CE MFC AT EUR LHD 1.00 1.03 99211-KL000 221011', - b'\xf1\x00CE MFC AT USA LHD 1.00 1.04 99211-KL000 221213', - ], - }, - CAR.TUCSON_4TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', - b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', - ], - }, - CAR.TUCSON_HYBRID_4TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9250 14W', - b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9260 14Y', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', - b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', - b'\xf1\x00NX4__ 1.00 1.01 99110-N9000 ', - ], - }, - CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NQ5 FR_CMR AT GEN LHD 1.00 1.00 99211-P1060 665', - b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ', - ], - }, - CAR.SANTA_CRUZ_1ST_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW010 14X', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00NX4__ 1.00 1.00 99110-K5000 ', - b'\xf1\x00NX4__ 1.01 1.00 99110-K5000 ', - ], - }, - CAR.KIA_SPORTAGE_5TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662', - b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663', - b'\xf1\x00NQ5 FR_CMR AT AUS RHD 1.00 1.00 99211-P1040 663', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ', - b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ', - b'\xf1\x00NQ5__ 1.01 1.03 99110-P1000 ', - ], - }, - CAR.GENESIS_GV70_1ST_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204', - b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR200 220125', - b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR300 220125', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ', - b'\xf1\x00JK1_ SCC FHCUP 1.00 1.00 99110-AR200 ', - b'\xf1\x00JK1_ SCC FHCUP 1.00 1.00 99110-AR300 ', - ], - }, - CAR.GENESIS_GV60_EV_1ST_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215', - b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU000 211215', - b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.03 99211-CU000 221118', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JW1_ RDR ----- 1.00 1.00 99110-CU000 ', - ], - }, - CAR.KIA_SORENTO_4TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.05 99210-R5000 210623', - b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.03 99210-R5000 200903', - b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.00 99210-R5100 221019', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', - b'\xf1\x00MQ4_ SCC F-CUP 1.00 1.06 99110-P2000 ', - b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.08 99110-P2000 ', - ], - }, - CAR.KIA_NIRO_HEV_2ND_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.08 99211-AT000 220531', - b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.09 99211-AT000 220801', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', - ], - }, - CAR.GENESIS_GV80: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JX1 MFC AT USA LHD 1.00 1.02 99211-T6110 220513', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JX1_ SCC FHCUP 1.00 1.01 99110-T6100 ', - ], - }, - CAR.KIA_CARNIVAL_4TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221', - b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', - b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', - b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', - b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', - b'\xf1\x00KA4c SCC FHCUP 1.00 1.01 99110-I4000 ', - b'\xf1\x00KA4_ SCC FHCUP 1.00 1.00 99110-R0100 ', - ], - }, - CAR.KIA_SORENTO_HEV_4TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331', - b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', - b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.04 99210-P2000 200330', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', - b'\xf1\x00MQhe SCC FHCUP 1.00 1.04 99110-P4000 ', - ], - }, - CAR.KIA_K8_HEV_1ST_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.03 99211-L8000 210907', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00GL3_ RDR ----- 1.00 1.02 99110-L8000 ', - ], - }, - CAR.STARIA_4TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00US4 MFC AT KOR LHD 1.00 1.06 99211-CG000 230524', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00US4_ RDR ----- 1.00 1.00 99110-CG000 ', - ], - }, -} CHECKSUM = { "crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 3bbf16c789..0b787897e7 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -433,6 +433,11 @@ class CarStateBase(ABC): return None +INTERFACE_ATTR_FILE = { + "FINGERPRINTS": "fingerprints", + "FW_VERSIONS": "fingerprints", +} + # interface-specific helpers def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> Dict[str, Any]: @@ -443,7 +448,7 @@ def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: boo for car_folder in sorted([x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]): try: brand_name = car_folder.split('/')[-1] - brand_values = __import__(f'openpilot.selfdrive.car.{brand_name}.values', fromlist=[attr]) + brand_values = __import__(f'openpilot.selfdrive.car.{brand_name}.{INTERFACE_ATTR_FILE.get(attr, "values")}', fromlist=[attr]) if hasattr(brand_values, attr) or not ignore_none: attr_data = getattr(brand_values, attr, None) else: diff --git a/selfdrive/car/mazda/fingerprints.py b/selfdrive/car/mazda/fingerprints.py new file mode 100644 index 0000000000..7c2d321c28 --- /dev/null +++ b/selfdrive/car/mazda/fingerprints.py @@ -0,0 +1,263 @@ +from cereal import car +from openpilot.selfdrive.car.mazda.values import CAR + +Ecu = car.CarParams.Ecu + +FW_VERSIONS = { + CAR.CX5_2022: { + (Ecu.eps, 0x730, None): [ + b'KSD5-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2H-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX85-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXFG-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'KGWD-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KSD5-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXDL-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXFG-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXFG-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PG69-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.CX5: { + (Ecu.eps, 0x730, None): [ + b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KCB8-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KJ01-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KJ01-3210X-M-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'PA53-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PAR4-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFA-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2E-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2F-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2G-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2H-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2H-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2H-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2K-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX38-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX42-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX68-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'SHKT-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'K123-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'K123-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KBJ5-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KL2K-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KN0W-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'B61L-67XK2-R\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-V\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-R\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'PA66-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PA66-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX39-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX39-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX68-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB1-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB1-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB1-21PS1-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYNC-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'SH9T-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + + CAR.CX9 : { + (Ecu.eps, 0x730, None): [ + b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KJ01-3210X-L-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'PX23-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX24-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM4-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXN8-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXN8-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYD7-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYD8-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFM-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFM-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'K123-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'TK80-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'TK80-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'TA0B-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'TK79-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'TK79-437K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'TM53-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'TN40-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'B61L-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-V\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-K\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'TK80-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'PXM4-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM7-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFM-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFM-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYD5-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYD5-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYD6-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYD6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + + CAR.MAZDA3: { + (Ecu.eps, 0x730, None): [ + b'BHN1-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KR11-3210X-K-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + + ], + (Ecu.engine, 0x7e0, None): [ + b'P5JD-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PY2P-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYJW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYKC-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYKE-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'B63C-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GHP9-67Y10---41\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'B45A-437AS-0-08\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'B61L-67XK2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'PY2S-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'P52G-21PS1-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYKA-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYKE-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYKE-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + + CAR.MAZDA6: { + (Ecu.eps, 0x730, None): [ + b'GBEF-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GBEF-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GFBC-3210X-A-00\000\000\000\000\000\000\000\000\000', + ], + (Ecu.engine, 0x7e0, None): [ + b'PA34-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX4F-188K2-D\000\000\000\000\000\000\000\000\000\000\000\000', + b'PYH7-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYH7-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'K131-67XK2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-E\000\000\000\000\000\000\000\000\000\000\000\000', + ], + (Ecu.abs, 0x760, None): [ + b'GBVH-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GBVH-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GDDM-437K2-A\000\000\000\000\000\000\000\000\000\000\000\000', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'B61L-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-P\000\000\000\000\000\000\000\000\000\000\000\000', + ], + (Ecu.transmission, 0x7e1, None): [ + b'PA28-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYH3-21PS1-D\000\000\000\000\000\000\000\000\000\000\000\000', + b'PYH7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + + CAR.CX9_2021 : { + (Ecu.eps, 0x730, None): [ + b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'PXGW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXGW-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'TA0B-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.transmission, 0x7e1, None): [ + b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + } +} diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 4dc5726f34..c95ae162f9 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -81,265 +81,6 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) -FW_VERSIONS = { - CAR.CX5_2022: { - (Ecu.eps, 0x730, None): [ - b'KSD5-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2H-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX85-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXFG-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'KGWD-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KSD5-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB2-21PS1-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXDL-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXFG-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXFG-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PG69-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - CAR.CX5: { - (Ecu.eps, 0x730, None): [ - b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KCB8-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KJ01-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KJ01-3210X-M-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'PA53-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PAR4-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFA-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2E-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2F-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2G-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2H-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2H-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2H-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX2K-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX38-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX42-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX68-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'SHKT-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'K123-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'K123-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KBJ5-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KL2K-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KN0W-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'B61L-67XK2-R\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'B61L-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'B61L-67XK2-V\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-R\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'PA66-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PA66-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX39-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX39-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX68-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB1-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB1-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB1-21PS1-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB2-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB2-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB2-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB2-21PS1-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYNC-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'SH9T-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - - CAR.CX9 : { - (Ecu.eps, 0x730, None): [ - b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KJ01-3210X-L-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'PX23-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX24-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM4-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXN8-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXN8-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYD7-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYD8-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFM-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFM-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'K123-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'TK80-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'TK80-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'TA0B-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'TK79-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'TK79-437K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'TM53-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'TN40-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'B61L-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'B61L-67XK2-V\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-K\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'TK80-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'PXM4-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM7-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFM-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFM-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYD5-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYD5-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYD6-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYD6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - - CAR.MAZDA3: { - (Ecu.eps, 0x730, None): [ - b'BHN1-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'KR11-3210X-K-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - - ], - (Ecu.engine, 0x7e0, None): [ - b'P5JD-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PY2P-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYJW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYKC-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYKE-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'B63C-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GHP9-67Y10---41\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'B45A-437AS-0-08\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'B61L-67XK2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'B61L-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'B61L-67XK2-Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'PY2S-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'P52G-21PS1-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYKA-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYKE-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYKE-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - - CAR.MAZDA6: { - (Ecu.eps, 0x730, None): [ - b'GBEF-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GBEF-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GFBC-3210X-A-00\000\000\000\000\000\000\000\000\000', - ], - (Ecu.engine, 0x7e0, None): [ - b'PA34-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX4F-188K2-D\000\000\000\000\000\000\000\000\000\000\000\000', - b'PYH7-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYH7-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'K131-67XK2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-E\000\000\000\000\000\000\000\000\000\000\000\000', - ], - (Ecu.abs, 0x760, None): [ - b'GBVH-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GBVH-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GDDM-437K2-A\000\000\000\000\000\000\000\000\000\000\000\000', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'B61L-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-P\000\000\000\000\000\000\000\000\000\000\000\000', - ], - (Ecu.transmission, 0x7e1, None): [ - b'PA28-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYH3-21PS1-D\000\000\000\000\000\000\000\000\000\000\000\000', - b'PYH7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - }, - - CAR.CX9_2021 : { - (Ecu.eps, 0x730, None): [ - b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'PXGW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXGW-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x764, None): [ - b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x760, None): [ - b'TA0B-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x706, None): [ - b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.transmission, 0x7e1, None): [ - b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], - } -} - DBC = { CAR.CX5: dbc_dict('mazda_2017', None), diff --git a/selfdrive/car/nissan/fingerprints.py b/selfdrive/car/nissan/fingerprints.py new file mode 100644 index 0000000000..888ae8184a --- /dev/null +++ b/selfdrive/car/nissan/fingerprints.py @@ -0,0 +1,116 @@ +# ruff: noqa: E501 +from cereal import car +from openpilot.selfdrive.car.nissan.values import CAR + +Ecu = car.CarParams.Ecu + +FINGERPRINTS = { + CAR.XTRAIL: [ + { + 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 2, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1821: 8, 1823: 8, 1837: 8, 2015: 8, 2016: 8, 2024: 8 + }, + { + 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 527: 1, 548: 8, 637: 4, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 8, 1497: 3, 1534: 6, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 2015: 8, 2016: 8, 2024: 8 + }, + ], + CAR.LEAF: [ + { + 2: 5, 42: 6, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 944: 1, 976: 6, 1008: 7, 1011: 7, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8, 1856: 8, 1859: 8, 1861: 8, 1864: 8, 1874: 8, 1888: 8, 1891: 8, 1893: 8, 1906: 8, 1947: 8, 1949: 8, 1979: 8, 1981: 8, 2016: 8, 2017: 8, 2021: 8, 643: 5, 1792: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8 + }, + # 2020 Leaf SV Plus + { + 2: 5, 42: 8, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 976: 6, 1008: 7, 1009: 8, 1010: 8, 1011: 7, 1012: 8, 1013: 8, 1019: 8, 1020: 8, 1021: 8, 1022: 8, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1402: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8 + }, + ], + CAR.LEAF_IC: [ + { + 2: 5, 42: 6, 264: 3, 282: 8, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 756: 5, 758: 3, 761: 2, 783: 3, 830: 2, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 1001: 6, 1057: 3, 1227: 8, 1228: 8, 1229: 8, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1514: 6, 1549: 8, 1573: 6, 1792: 8, 1821: 8, 1822: 8, 1837: 8, 1838: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8 + }, + ], + CAR.ROGUE: [ + { + 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 634: 7, 643: 5, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1042: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1534: 7, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1839: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }, + ], + CAR.ALTIMA: [ + { + 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 438: 8, 451: 8, 517: 8, 520: 2, 522: 8, 523: 6, 539: 8, 541: 7, 542: 8, 543: 8, 544: 8, 545: 8, 546: 8, 547: 8, 548: 8, 570: 8, 576: 8, 577: 8, 582: 8, 583: 8, 584: 8, 586: 8, 587: 8, 588: 8, 589: 8, 590: 8, 591: 8, 592: 8, 600: 8, 601: 8, 610: 8, 611: 8, 612: 8, 614: 8, 615: 8, 616: 8, 617: 8, 622: 8, 623: 8, 634: 7, 638: 8, 645: 8, 648: 5, 654: 6, 658: 8, 659: 8, 660: 8, 661: 8, 665: 8, 666: 8, 674: 2, 675: 8, 676: 8, 682: 8, 683: 8, 684: 8, 685: 8, 686: 8, 687: 8, 689: 8, 690: 8, 703: 8, 708: 7, 709: 7, 711: 7, 712: 7, 713: 7, 714: 8, 715: 8, 716: 8, 717: 7, 718: 7, 719: 7, 720: 7, 723: 8, 726: 7, 727: 7, 728: 7, 735: 8, 746: 8, 748: 6, 749: 6, 750: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 779: 7, 781: 7, 782: 7, 783: 3, 851: 8, 855: 5, 1001: 6, 1041: 8, 1042: 8, 1055: 3, 1100: 7, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1144: 7, 1145: 7, 1227: 8, 1228: 8, 1229: 8, 1232: 8, 1247: 4, 1258: 8, 1259: 8, 1266: 8, 1273: 7, 1306: 1, 1314: 8, 1323: 8, 1324: 8, 1342: 1, 1376: 8, 1401: 8, 1454: 8, 1497: 3, 1514: 6, 1526: 8, 1527: 5, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }, + ] +} + + +FW_VERSIONS = { + CAR.ALTIMA: { + (Ecu.fwdCamera, 0x707, None): [ + b'284N86CA1D', + ], + (Ecu.eps, 0x742, None): [ + b'6CA2B\xa9A\x02\x02G8A89P90D6A\x00\x00\x01\x80', + ], + (Ecu.engine, 0x7e0, None): [ + b'237109HE2B', + ], + (Ecu.gateway, 0x18dad0f1, None): [ + b'284U29HE0A', + ], + }, + CAR.LEAF: { + (Ecu.abs, 0x740, None): [ + b'476606WK9B', + ], + (Ecu.eps, 0x742, None): [ + b'5SN2A\xb7A\x05\x02N126F\x15\xb2\x00\x00\x00\x00\x00\x00\x00\x80', + ], + (Ecu.fwdCamera, 0x707, None): [ + b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80', + ], + (Ecu.gateway, 0x18dad0f1, None): [ + b'284U26WK0C', + ], + }, + CAR.LEAF_IC: { + (Ecu.fwdCamera, 0x707, None): [ + b'5SH1BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', + b'5SK0ADB\x04\x18\x00\x00\x00\x00\x00_(5\x07\x9aQ\x00\x00\x00\x80', + b'5SH4BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', + ], + (Ecu.abs, 0x740, None): [ + b'476605SH1D', + b'476605SK2A', + b'476605SD2E', + ], + (Ecu.eps, 0x742, None): [ + b'5SH2A\x99A\x05\x02N123F\x15\x81\x00\x00\x00\x00\x00\x00\x00\x80', + b'5SK3A\x99A\x05\x02N123F\x15u\x00\x00\x00\x00\x00\x00\x00\x80', + b'5SH2C\xb7A\x05\x02N123F\x15\xa3\x00\x00\x00\x00\x00\x00\x00\x80', + ], + (Ecu.gateway, 0x18dad0f1, None): [ + b'284U25SH3A', + b'284U25SK2D', + b'284U25SF0C', + ], + }, + CAR.XTRAIL: { + (Ecu.fwdCamera, 0x707, None): [ + b'284N86FR2A', + ], + (Ecu.abs, 0x740, None): [ + b'6FU1BD\x11\x02\x00\x02e\x95e\x80iX#\x01\x00\x00\x00\x00\x00\x80', + b'6FU0AD\x11\x02\x00\x02e\x95e\x80iQ#\x01\x00\x00\x00\x00\x00\x80', + ], + (Ecu.eps, 0x742, None): [ + b'6FP2A\x99A\x05\x02N123F\x18\x02\x00\x00\x00\x00\x00\x00\x00\x80', + ], + (Ecu.combinationMeter, 0x743, None): [ + b'6FR2A\x18B\x05\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', + ], + (Ecu.engine, 0x7e0, None): [ + b'6FU9B\xa0A\x06\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', + b'6FR9A\xa0A\x06\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', + ], + (Ecu.gateway, 0x18dad0f1, None): [ + b'284U26FR0E', + ], + }, +} diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index 474cb15e7f..4ee23c32c3 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -1,4 +1,3 @@ -# ruff: noqa: E501 from dataclasses import dataclass, field from enum import StrEnum from typing import Dict, List, Optional, Union @@ -46,41 +45,6 @@ CAR_INFO: Dict[str, Optional[Union[NissanCarInfo, List[NissanCarInfo]]]] = { CAR.ALTIMA: NissanCarInfo("Nissan Altima 2019-20", car_parts=CarParts.common([CarHarness.nissan_b])), } -FINGERPRINTS = { - CAR.XTRAIL: [ - { - 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 2, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1821: 8, 1823: 8, 1837: 8, 2015: 8, 2016: 8, 2024: 8 - }, - { - 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 527: 1, 548: 8, 637: 4, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 8, 1497: 3, 1534: 6, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 2015: 8, 2016: 8, 2024: 8 - }, - ], - CAR.LEAF: [ - { - 2: 5, 42: 6, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 944: 1, 976: 6, 1008: 7, 1011: 7, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8, 1856: 8, 1859: 8, 1861: 8, 1864: 8, 1874: 8, 1888: 8, 1891: 8, 1893: 8, 1906: 8, 1947: 8, 1949: 8, 1979: 8, 1981: 8, 2016: 8, 2017: 8, 2021: 8, 643: 5, 1792: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8 - }, - # 2020 Leaf SV Plus - { - 2: 5, 42: 8, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 976: 6, 1008: 7, 1009: 8, 1010: 8, 1011: 7, 1012: 8, 1013: 8, 1019: 8, 1020: 8, 1021: 8, 1022: 8, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1402: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8 - }, - ], - CAR.LEAF_IC: [ - { - 2: 5, 42: 6, 264: 3, 282: 8, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 756: 5, 758: 3, 761: 2, 783: 3, 830: 2, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 1001: 6, 1057: 3, 1227: 8, 1228: 8, 1229: 8, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1514: 6, 1549: 8, 1573: 6, 1792: 8, 1821: 8, 1822: 8, 1837: 8, 1838: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8 - }, - ], - CAR.ROGUE: [ - { - 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 634: 7, 643: 5, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1042: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1534: 7, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1839: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }, - ], - CAR.ALTIMA: [ - { - 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 438: 8, 451: 8, 517: 8, 520: 2, 522: 8, 523: 6, 539: 8, 541: 7, 542: 8, 543: 8, 544: 8, 545: 8, 546: 8, 547: 8, 548: 8, 570: 8, 576: 8, 577: 8, 582: 8, 583: 8, 584: 8, 586: 8, 587: 8, 588: 8, 589: 8, 590: 8, 591: 8, 592: 8, 600: 8, 601: 8, 610: 8, 611: 8, 612: 8, 614: 8, 615: 8, 616: 8, 617: 8, 622: 8, 623: 8, 634: 7, 638: 8, 645: 8, 648: 5, 654: 6, 658: 8, 659: 8, 660: 8, 661: 8, 665: 8, 666: 8, 674: 2, 675: 8, 676: 8, 682: 8, 683: 8, 684: 8, 685: 8, 686: 8, 687: 8, 689: 8, 690: 8, 703: 8, 708: 7, 709: 7, 711: 7, 712: 7, 713: 7, 714: 8, 715: 8, 716: 8, 717: 7, 718: 7, 719: 7, 720: 7, 723: 8, 726: 7, 727: 7, 728: 7, 735: 8, 746: 8, 748: 6, 749: 6, 750: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 779: 7, 781: 7, 782: 7, 783: 3, 851: 8, 855: 5, 1001: 6, 1041: 8, 1042: 8, 1055: 3, 1100: 7, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1144: 7, 1145: 7, 1227: 8, 1228: 8, 1229: 8, 1232: 8, 1247: 4, 1258: 8, 1259: 8, 1266: 8, 1273: 7, 1306: 1, 1314: 8, 1323: 8, 1324: 8, 1342: 1, 1376: 8, 1401: 8, 1454: 8, 1497: 3, 1514: 6, 1526: 8, 1527: 5, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }, - ] -} - # Default diagnostic session NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, 0x81]) NISSAN_DIAGNOSTIC_RESPONSE_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40, 0x81]) @@ -118,81 +82,6 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) -FW_VERSIONS = { - CAR.ALTIMA: { - (Ecu.fwdCamera, 0x707, None): [ - b'284N86CA1D', - ], - (Ecu.eps, 0x742, None): [ - b'6CA2B\xa9A\x02\x02G8A89P90D6A\x00\x00\x01\x80', - ], - (Ecu.engine, 0x7e0, None): [ - b'237109HE2B', - ], - (Ecu.gateway, 0x18dad0f1, None): [ - b'284U29HE0A', - ], - }, - CAR.LEAF: { - (Ecu.abs, 0x740, None): [ - b'476606WK9B', - ], - (Ecu.eps, 0x742, None): [ - b'5SN2A\xb7A\x05\x02N126F\x15\xb2\x00\x00\x00\x00\x00\x00\x00\x80', - ], - (Ecu.fwdCamera, 0x707, None): [ - b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80', - ], - (Ecu.gateway, 0x18dad0f1, None): [ - b'284U26WK0C', - ], - }, - CAR.LEAF_IC: { - (Ecu.fwdCamera, 0x707, None): [ - b'5SH1BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', - b'5SK0ADB\x04\x18\x00\x00\x00\x00\x00_(5\x07\x9aQ\x00\x00\x00\x80', - b'5SH4BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', - ], - (Ecu.abs, 0x740, None): [ - b'476605SH1D', - b'476605SK2A', - b'476605SD2E', - ], - (Ecu.eps, 0x742, None): [ - b'5SH2A\x99A\x05\x02N123F\x15\x81\x00\x00\x00\x00\x00\x00\x00\x80', - b'5SK3A\x99A\x05\x02N123F\x15u\x00\x00\x00\x00\x00\x00\x00\x80', - b'5SH2C\xb7A\x05\x02N123F\x15\xa3\x00\x00\x00\x00\x00\x00\x00\x80', - ], - (Ecu.gateway, 0x18dad0f1, None): [ - b'284U25SH3A', - b'284U25SK2D', - b'284U25SF0C', - ], - }, - CAR.XTRAIL: { - (Ecu.fwdCamera, 0x707, None): [ - b'284N86FR2A', - ], - (Ecu.abs, 0x740, None): [ - b'6FU1BD\x11\x02\x00\x02e\x95e\x80iX#\x01\x00\x00\x00\x00\x00\x80', - b'6FU0AD\x11\x02\x00\x02e\x95e\x80iQ#\x01\x00\x00\x00\x00\x00\x80', - ], - (Ecu.eps, 0x742, None): [ - b'6FP2A\x99A\x05\x02N123F\x18\x02\x00\x00\x00\x00\x00\x00\x00\x80', - ], - (Ecu.combinationMeter, 0x743, None): [ - b'6FR2A\x18B\x05\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', - ], - (Ecu.engine, 0x7e0, None): [ - b'6FU9B\xa0A\x06\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', - b'6FR9A\xa0A\x06\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', - ], - (Ecu.gateway, 0x18dad0f1, None): [ - b'284U26FR0E', - ], - }, -} - DBC = { CAR.XTRAIL: dbc_dict('nissan_x_trail_2017_generated', None), CAR.LEAF: dbc_dict('nissan_leaf_2018_generated', None), diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py new file mode 100644 index 0000000000..215e32e538 --- /dev/null +++ b/selfdrive/car/subaru/fingerprints.py @@ -0,0 +1,563 @@ +from cereal import car +from openpilot.selfdrive.car.subaru.values import CAR + +Ecu = car.CarParams.Ecu + +FW_VERSIONS = { + CAR.ASCENT: { + (Ecu.abs, 0x7b0, None): [ + b'\xa5 \x19\x02\x00', + b'\xa5 !\002\000', + b'\xf1\x82\xa5 \x19\x02\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x85\xc0\xd0\x00', + b'\005\xc0\xd0\000', + b'\x95\xc0\xd0\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00d\xb9\x1f@ \x10', + b'\000\000e~\037@ \'', + b'\x00\x00e@\x1f@ $', + b'\x00\x00d\xb9\x00\x00\x00\x00', + b'\x00\x00e@\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xbb,\xa0t\a', + b'\xf1\x82\xbb,\xa0t\x87', + b'\xf1\x82\xbb,\xa0t\a', + b'\xf1\x82\xd9,\xa0@\a', + b'\xf1\x82\xd1,\xa0q\x07', + b'\xd1,\xa0q\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\x00\xfe\xf7\x00\x00', + b'\001\xfe\xf9\000\000', + b'\x01\xfe\xf7\x00\x00', + b'\x01\xfe\xfa\x00\x00', + ], + }, + CAR.ASCENT_2023: { + (Ecu.abs, 0x7b0, None): [ + b'\xa5 #\x03\x00', + ], + (Ecu.eps, 0x746, None): [ + b'%\xc0\xd0\x11', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x05!\x08\x1dK\x05!\x08\x01/', + ], + (Ecu.engine, 0x7a2, None): [ + b'\xe5,\xa0P\x07', + ], + (Ecu.transmission, 0x7a3, None): [ + b'\x04\xfe\xf3\x00\x00', + ], + }, + CAR.LEGACY: { + (Ecu.abs, 0x7b0, None): [ + b'\xa1\\ x04\x01', + b'\xa1 \x03\x03', + b'\xa1 \x02\x01', + b'\xa1 \x02\x02', + ], + (Ecu.eps, 0x746, None): [ + b'\x9b\xc0\x11\x00', + b'\x9b\xc0\x11\x02', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00e\x80\x00\x1f@ \x19\x00', + b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xde\"a0\x07', + b'\xe2"aq\x07', + b'\xde,\xa0@\x07', + b'\xe2,\xa0@\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xa5\xf6\x05@\x00', + b'\xa7\xf6\x04@\x00', + b'\xa5\xfe\xc7@\x00', + b'\xa7\xfe\xc4@\x00', + ], + }, + CAR.IMPREZA: { + (Ecu.abs, 0x7b0, None): [ + b'\x7a\x94\x3f\x90\x00', + b'\xa2 \x185\x00', + b'\xa2 \x193\x00', + b'\xa2 \x194\x00', + b'z\x94.\x90\x00', + b'z\x94\b\x90\x01', + b'\xa2 \x19`\x00', + b'z\x94\f\x90\001', + b'z\x9c\x19\x80\x01', + b'z\x94\x08\x90\x00', + b'z\x84\x19\x90\x00', + b'\xf1\x00\xb2\x06\x04', + b'z\x94\x0c\x90\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x7a\xc0\x0c\x00', + b'z\xc0\x08\x00', + b'\x8a\xc0\x00\x00', + b'z\xc0\x04\x00', + b'z\xc0\x00\x00', + b'\x8a\xc0\x10\x00', + b'z\xc0\n\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00\x64\xb5\x1f\x40\x20\x0e', + b'\x00\x00d\xdc\x1f@ \x0e', + b'\x00\x00e\x1c\x1f@ \x14', + b'\x00\x00d)\x1f@ \a', + b'\x00\x00e+\x1f@ \x14', + b'\000\000e+\000\000\000\000', + b'\000\000dd\037@ \016', + b'\000\000e\002\037@ \024', + b'\x00\x00d)\x00\x00\x00\x00', + b'\x00\x00c\xf4\x00\x00\x00\x00', + b'\x00\x00d\xdc\x00\x00\x00\x00', + b'\x00\x00dd\x00\x00\x00\x00', + b'\x00\x00c\xf4\x1f@ \x07', + b'\x00\x00e\x1c\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xaa\x61\x66\x73\x07', + b'\xbeacr\a', + b'\xc5!`r\a', + b'\xaa!ds\a', + b'\xaa!`u\a', + b'\xaa!dq\a', + b'\xaa!dt\a', + b'\xc5!ar\a', + b'\xbe!as\a', + b'\xc5!as\x07', + b'\xc5!ds\a', + b'\xc5!`s\a', + b'\xaa!au\a', + b'\xbe!at\a', + b'\xaa\x00Bu\x07', + b'\xc5!dr\x07', + b'\xaa!aw\x07', + b'\xaa!av\x07', + b'\xaa\x01bt\x07', + b'\xc5!ap\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xe3\xe5\x46\x31\x00', + b'\xe4\xe5\x061\x00', + b'\xe5\xf5\x04\x00\x00', + b'\xe3\xf5G\x00\x00', + b'\xe3\xf5\a\x00\x00', + b'\xe3\xf5C\x00\x00', + b'\xe5\xf5B\x00\x00', + b'\xe5\xf5$\000\000', + b'\xe4\xf5\a\000\000', + b'\xe3\xf5F\000\000', + b'\xe4\xf5\002\000\000', + b'\xe3\xd0\x081\x00', + b'\xe3\xf5\x06\x00\x00', + b'\xe3\xd5\x161\x00', + ], + }, + CAR.IMPREZA_2020: { + (Ecu.abs, 0x7b0, None): [ + b'\xa2 \0314\000', + b'\xa2 \0313\000', + b'\xa2 !i\000', + b'\xa2 !`\000', + b'\xf1\x00\xb2\x06\x04', + b'\xa2 `\x00', + b'\xa2 !3\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x9a\xc0\000\000', + b'\n\xc0\004\000', + b'\x9a\xc0\x04\x00', + b'\n\xc0\x04\x01', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\000\000eb\037@ \"', + b'\x00\x00e\x8f\x1f@ )', + b'\x00\x00eq\x1f@ "', + b'\x00\x00eq\x00\x00\x00\x00', + b'\x00\x00e\x8f\x00\x00\x00\x00', + b'\x00\x00e\x92\x00\x00\x00\x00', + b'\x00\x00e\xa4\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xca!ap\a', + b'\xca!`p\a', + b'\xca!`0\a', + b'\xcc\"f0\a', + b'\xcc!fp\a', + b'\xcc!`p\x07', + b'\xca!f@\x07', + b'\xca!fp\x07', + b'\xf3"f@\x07', + b'\xe6!fp\x07', + b'\xf3"fp\x07', + b'\xe6"f0\x07', + b'\xe6"fp\x07', + b'\xe6!`@\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xe6\xf5\004\000\000', + b'\xe6\xf5$\000\000', + b'\xe7\xf5\x04\x00\x00', + b'\xe7\xf6B0\000', + b'\xe7\xf5D0\000', + b'\xf1\x00\xd7\x10@', + b'\xe6\xf5D0\x00', + b'\xe9\xf6F0\x00', + b'\xe9\xf5B0\x00', + b'\xe9\xf6B0\x00', + b'\xe9\xf5"\x00\x00', + ], + }, + CAR.CROSSTREK_HYBRID: { + (Ecu.abs, 0x7b0, None): [ + b'\xa2 \x19e\x01', + b'\xa2 !e\x01', + ], + (Ecu.eps, 0x746, None): [ + b'\x9a\xc2\x01\x00', + b'\n\xc2\x01\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00el\x1f@ #', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xd7!`@\x07', + b'\xd7!`p\a', + b'\xf4!`0\x07', + ], + }, + CAR.FORESTER: { + (Ecu.abs, 0x7b0, None): [ + b'\xa3 \x18\x14\x00', + b'\xa3 \024\000', + b'\xa3 \031\024\000', + b'\xa3 \x14\x01', + b'\xf1\x00\xbb\r\x05', + b'\xa3 \x18&\x00', + b'\xa3 \x19&\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x8d\xc0\x04\x00', + b'\x8d\xc0\x00\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00e!\x1f@ \x11', + b'\x00\x00e\x97\x1f@ 0', + b'\000\000e`\037@ ', + b'\xf1\x00\xac\x02\x00', + b'\x00\x00e!\x00\x00\x00\x00', + b'\x00\x00e\x97\x00\x00\x00\x00', + b'\x00\x00e^\x1f@ !', + b'\x00\x00e^\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xb6"`A\x07', + b'\xcf"`0\x07', + b'\xcb\"`@\a', + b'\xcb\"`p\a', + b'\xf1\x00\xa2\x10\n', + b'\xcf"`p\x07', + b'\xb6\xa2`A\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\032\xf6B0\000', + b'\x1a\xf6F`\x00', + b'\032\xf6b`\000', + b'\x1a\xf6B`\x00', + b'\x1a\xf6b0\x00', + b'\x1a\xe6B1\x00', + b'\x1a\xe6F1\x00', + ], + }, + CAR.FORESTER_HYBRID: { + (Ecu.abs, 0x7b0, None): [ + b'\xa3 \x19T\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x8d\xc2\x00\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00eY\x1f@ !', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xd2\xa1`r\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\x1b\xa7@a\x00', + ], + }, + CAR.FORESTER_PREGLOBAL: { + (Ecu.abs, 0x7b0, None): [ + b'\x7d\x97\x14\x40', + b'\xf1\x00\xbb\x0c\x04', + b'm\x97\x14@', + ], + (Ecu.eps, 0x746, None): [ + b'}\xc0\x10\x00', + b'm\xc0\x10\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00\x64\x35\x1f\x40\x20\x09', + b'\x00\x00c\xe9\x1f@ \x03', + b'\x00\x00d\xd3\x1f@ \t', + b'\x00\x00c\xe9\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xba"@p\a', + b'\xa7)\xa0q\a', + b'\xf1\x82\xa7)\xa0q\a', + b'\xba"@@\a', + b'\xa7"@p\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xdc\xf2\x60\x60\x00', + b'\xdc\xf2@`\x00', + b'\xda\xfd\xe0\x80\x00', + b'\xdc\xf2`\x81\000', + b'\xdc\xf2`\x80\x00', + b'\x1a\xf6F`\x00', + b'\xda\xf2`\x80\x00', + ], + }, + CAR.LEGACY_PREGLOBAL: { + (Ecu.abs, 0x7b0, None): [ + b'k\x97D\x00', + b'[\xba\xc4\x03', + b'{\x97D\x00', + b'[\x97D\000', + b'k\x9aD\x00', + ], + (Ecu.eps, 0x746, None): [ + b'[\xb0\x00\x01', + b'K\xb0\x00\x01', + b'k\xb0\x00\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00c\xb7\x1f@\x10\x16', + b'\x00\x00c\x94\x1f@\x10\x08', + b'\x00\x00c\xec\x1f@ \x04', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xab*@r\a', + b'\xa0+@p\x07', + b'\xb4"@0\x07', + b'\xa0"@q\a', + b'\xab+@p\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xbe\xf2\x00p\x00', + b'\xbf\xfb\xc0\x80\x00', + b'\xbd\xf2\x00`\x00', + b'\xbf\xf2\000\x80\000', + b'\xbe\xfb\xc0p\x00', + ], + }, + CAR.OUTBACK_PREGLOBAL: { + (Ecu.abs, 0x7b0, None): [ + b'{\x9a\xac\x00', + b'k\x97\xac\x00', + b'\x5b\xf7\xbc\x03', + b'[\xf7\xac\x03', + b'{\x97\xac\x00', + b'k\x9a\xac\000', + b'[\xba\xac\x03', + b'[\xf7\xac\000', + ], + (Ecu.eps, 0x746, None): [ + b'k\xb0\x00\x00', + b'[\xb0\x00\x00', + b'\x4b\xb0\x00\x02', + b'K\xb0\x00\x00', + b'{\xb0\x00\x01', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00c\xec\x1f@ \x04', + b'\x00\x00c\xd1\x1f@\x10\x17', + b'\xf1\x00\xf0\xe0\x0e', + b'\x00\x00c\x94\x00\x00\x00\x00', + b'\x00\x00c\x94\x1f@\x10\b', + b'\x00\x00c\xb7\x1f@\x10\x16', + b'\000\000c\x90\037@\020\016', + b'\x00\x00c\xec\x37@\x04', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xb4+@p\a', + b'\xab\"@@\a', + b'\xa0\x62\x41\x71\x07', + b'\xa0*@q\a', + b'\xab*@@\a', + b'\xb4"@0\a', + b'\xb4"@p\a', + b'\xab"@s\a', + b'\xab+@@\a', + b'\xb4"@r\a', + b'\xa0+@@\x07', + b'\xa0\"@\x80\a', + b'\xa0*@u\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xbd\xfb\xe0\x80\x00', + b'\xbe\xf2@\x80\x00', + b'\xbf\xe2\x40\x80\x00', + b'\xbf\xf2@\x80\x00', + b'\xbe\xf2@p\x00', + b'\xbd\xf2@`\x00', + b'\xbd\xf2@\x81\000', + b'\xbe\xfb\xe0p\000', + b'\xbf\xfb\xe0b\x00', + ], + }, + CAR.OUTBACK_PREGLOBAL_2018: { + (Ecu.abs, 0x7b0, None): [ + b'\x8b\x97\xac\x00', + b'\x8b\x9a\xac\x00', + b'\x9b\x97\xac\x00', + b'\x8b\x97\xbc\x00', + b'\x8b\x99\xac\x00', + b'\x9b\x9a\xac\000', + b'\x9b\x97\xbe\x10', + ], + (Ecu.eps, 0x746, None): [ + b'{\xb0\x00\x00', + b'{\xb0\x00\x01', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00df\x1f@ \n', + b'\x00\x00d\xfe\x1f@ \x15', + b'\x00\x00d\x95\x00\x00\x00\x00', + b'\x00\x00d\x95\x1f@ \x0f', + b'\x00\x00d\xfe\x00\x00\x00\x00', + b'\x00\x00e\x19\x1f@ \x15', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xb5"@p\a', + b'\xb5+@@\a', + b'\xb5"@P\a', + b'\xc4"@0\a', + b'\xb5b@1\x07', + b'\xb5q\xe0@\a', + b'\xc4+@0\a', + b'\xc4b@p\a', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xbc\xf2@\x81\x00', + b'\xbc\xfb\xe0\x80\x00', + b'\xbc\xf2@\x80\x00', + b'\xbb\xf2@`\x00', + b'\xbc\xe2@\x80\x00', + b'\xbc\xfb\xe0`\x00', + b'\xbc\xaf\xe0`\x00', + b'\xbb\xfb\xe0`\000', + ], + }, + CAR.OUTBACK: { + (Ecu.abs, 0x7b0, None): [ + b'\xa1 \x06\x01', + b'\xa1 \a\x00', + b'\xa1 \b\001', + b'\xa1 \x06\x00', + b'\xa1 "\t\x01', + b'\xa1 \x08\x02', + b'\xa1 \x06\x02', + b'\xa1 \x07\x02', + b'\xa1 \x08\x00', + b'\xa1 "\t\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x9b\xc0\x10\x00', + b'\x9b\xc0\x20\x00', + b'\x1b\xc0\x10\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00eJ\x00\x1f@ \x19\x00', + b'\000\000e\x80\000\037@ \031\000', + b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00', + b'\x00\x00e\x9a\x00\x1f@ 1\x00', + b'\x00\x00eJ\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xbc,\xa0q\x07', + b'\xbc\"`@\a', + b'\xde"`0\a', + b'\xf1\x82\xbc,\xa0q\a', + b'\xf1\x82\xe3,\xa0@\x07', + b'\xe2"`0\x07', + b'\xe2"`p\x07', + b'\xf1\x82\xe2,\xa0@\x07', + b'\xbc"`q\x07', + b'\xe3,\xa0@\x07', + b'\xbc,\xa0u\x07', + b'\xde,\xa0@\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xa5\xfe\xf7@\x00', + b'\xa5\xf6D@\x00', + b'\xa5\xfe\xf6@\x00', + b'\xa7\x8e\xf40\x00', + b'\xf1\x82\xa7\xf6D@\x00', + b'\xa7\xf6D@\x00', + b'\xa7\xfe\xf4@\x00', + b'\xa5\xfe\xf8@\x00', + ], + }, + CAR.FORESTER_2022: { + (Ecu.abs, 0x7b0, None): [ + b'\xa3 !x\x00', + b'\xa3 !v\x00', + b'\xa3 "v\x00', + b'\xa3 "x\x00', + ], + (Ecu.eps, 0x746, None): [ + b'-\xc0%0', + b'-\xc0\x040', + b'=\xc0%\x02', + b'=\xc04\x02', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x04!\x01\x1eD\x07!\x00\x04,', + b'\x04!\x08\x01.\x07!\x08\x022', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xd5"a0\x07', + b'\xd5"`0\x07', + b'\xf1"aq\x07', + b'\xf1"`q\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\x1d\x86B0\x00', + b'\x1d\xf6B0\x00', + b'\x1e\x86B0\x00', + b'\x1e\xf6D0\x00', + ], + }, + CAR.OUTBACK_2023: { + (Ecu.abs, 0x7b0, None): [ + b'\xa1 #\x14\x00', + b'\xa1 #\x17\x00', + ], + (Ecu.eps, 0x746, None): [ + b'+\xc0\x10\x11\x00', + b'+\xc0\x12\x11\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\t!\x08\x046\x05!\x08\x01/', + ], + (Ecu.engine, 0x7a2, None): [ + b'\xed,\xa0q\x07', + b'\xed,\xa2q\x07', + ], + (Ecu.transmission, 0x7a3, None): [ + b'\xa8\x8e\xf41\x00', + b'\xa8\xfe\xf41\x00', + ] + } +} diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index a0753d99cc..80251e7e7e 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -175,565 +175,6 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) -FW_VERSIONS = { - CAR.ASCENT: { - (Ecu.abs, 0x7b0, None): [ - b'\xa5 \x19\x02\x00', - b'\xa5 !\002\000', - b'\xf1\x82\xa5 \x19\x02\x00', - ], - (Ecu.eps, 0x746, None): [ - b'\x85\xc0\xd0\x00', - b'\005\xc0\xd0\000', - b'\x95\xc0\xd0\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00d\xb9\x1f@ \x10', - b'\000\000e~\037@ \'', - b'\x00\x00e@\x1f@ $', - b'\x00\x00d\xb9\x00\x00\x00\x00', - b'\x00\x00e@\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xbb,\xa0t\a', - b'\xf1\x82\xbb,\xa0t\x87', - b'\xf1\x82\xbb,\xa0t\a', - b'\xf1\x82\xd9,\xa0@\a', - b'\xf1\x82\xd1,\xa0q\x07', - b'\xd1,\xa0q\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\x00\xfe\xf7\x00\x00', - b'\001\xfe\xf9\000\000', - b'\x01\xfe\xf7\x00\x00', - b'\x01\xfe\xfa\x00\x00', - ], - }, - CAR.ASCENT_2023: { - (Ecu.abs, 0x7b0, None): [ - b'\xa5 #\x03\x00', - ], - (Ecu.eps, 0x746, None): [ - b'%\xc0\xd0\x11', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x05!\x08\x1dK\x05!\x08\x01/', - ], - (Ecu.engine, 0x7a2, None): [ - b'\xe5,\xa0P\x07', - ], - (Ecu.transmission, 0x7a3, None): [ - b'\x04\xfe\xf3\x00\x00', - ], - }, - CAR.LEGACY: { - (Ecu.abs, 0x7b0, None): [ - b'\xa1\\ x04\x01', - b'\xa1 \x03\x03', - b'\xa1 \x02\x01', - b'\xa1 \x02\x02', - ], - (Ecu.eps, 0x746, None): [ - b'\x9b\xc0\x11\x00', - b'\x9b\xc0\x11\x02', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00e\x80\x00\x1f@ \x19\x00', - b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xde\"a0\x07', - b'\xe2"aq\x07', - b'\xde,\xa0@\x07', - b'\xe2,\xa0@\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xa5\xf6\x05@\x00', - b'\xa7\xf6\x04@\x00', - b'\xa5\xfe\xc7@\x00', - b'\xa7\xfe\xc4@\x00', - ], - }, - CAR.IMPREZA: { - (Ecu.abs, 0x7b0, None): [ - b'\x7a\x94\x3f\x90\x00', - b'\xa2 \x185\x00', - b'\xa2 \x193\x00', - b'\xa2 \x194\x00', - b'z\x94.\x90\x00', - b'z\x94\b\x90\x01', - b'\xa2 \x19`\x00', - b'z\x94\f\x90\001', - b'z\x9c\x19\x80\x01', - b'z\x94\x08\x90\x00', - b'z\x84\x19\x90\x00', - b'\xf1\x00\xb2\x06\x04', - b'z\x94\x0c\x90\x00', - ], - (Ecu.eps, 0x746, None): [ - b'\x7a\xc0\x0c\x00', - b'z\xc0\x08\x00', - b'\x8a\xc0\x00\x00', - b'z\xc0\x04\x00', - b'z\xc0\x00\x00', - b'\x8a\xc0\x10\x00', - b'z\xc0\n\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00\x64\xb5\x1f\x40\x20\x0e', - b'\x00\x00d\xdc\x1f@ \x0e', - b'\x00\x00e\x1c\x1f@ \x14', - b'\x00\x00d)\x1f@ \a', - b'\x00\x00e+\x1f@ \x14', - b'\000\000e+\000\000\000\000', - b'\000\000dd\037@ \016', - b'\000\000e\002\037@ \024', - b'\x00\x00d)\x00\x00\x00\x00', - b'\x00\x00c\xf4\x00\x00\x00\x00', - b'\x00\x00d\xdc\x00\x00\x00\x00', - b'\x00\x00dd\x00\x00\x00\x00', - b'\x00\x00c\xf4\x1f@ \x07', - b'\x00\x00e\x1c\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xaa\x61\x66\x73\x07', - b'\xbeacr\a', - b'\xc5!`r\a', - b'\xaa!ds\a', - b'\xaa!`u\a', - b'\xaa!dq\a', - b'\xaa!dt\a', - b'\xc5!ar\a', - b'\xbe!as\a', - b'\xc5!as\x07', - b'\xc5!ds\a', - b'\xc5!`s\a', - b'\xaa!au\a', - b'\xbe!at\a', - b'\xaa\x00Bu\x07', - b'\xc5!dr\x07', - b'\xaa!aw\x07', - b'\xaa!av\x07', - b'\xaa\x01bt\x07', - b'\xc5!ap\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xe3\xe5\x46\x31\x00', - b'\xe4\xe5\x061\x00', - b'\xe5\xf5\x04\x00\x00', - b'\xe3\xf5G\x00\x00', - b'\xe3\xf5\a\x00\x00', - b'\xe3\xf5C\x00\x00', - b'\xe5\xf5B\x00\x00', - b'\xe5\xf5$\000\000', - b'\xe4\xf5\a\000\000', - b'\xe3\xf5F\000\000', - b'\xe4\xf5\002\000\000', - b'\xe3\xd0\x081\x00', - b'\xe3\xf5\x06\x00\x00', - b'\xe3\xd5\x161\x00', - ], - }, - CAR.IMPREZA_2020: { - (Ecu.abs, 0x7b0, None): [ - b'\xa2 \0314\000', - b'\xa2 \0313\000', - b'\xa2 !i\000', - b'\xa2 !`\000', - b'\xf1\x00\xb2\x06\x04', - b'\xa2 `\x00', - b'\xa2 !3\x00', - ], - (Ecu.eps, 0x746, None): [ - b'\x9a\xc0\000\000', - b'\n\xc0\004\000', - b'\x9a\xc0\x04\x00', - b'\n\xc0\x04\x01', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\000\000eb\037@ \"', - b'\x00\x00e\x8f\x1f@ )', - b'\x00\x00eq\x1f@ "', - b'\x00\x00eq\x00\x00\x00\x00', - b'\x00\x00e\x8f\x00\x00\x00\x00', - b'\x00\x00e\x92\x00\x00\x00\x00', - b'\x00\x00e\xa4\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xca!ap\a', - b'\xca!`p\a', - b'\xca!`0\a', - b'\xcc\"f0\a', - b'\xcc!fp\a', - b'\xcc!`p\x07', - b'\xca!f@\x07', - b'\xca!fp\x07', - b'\xf3"f@\x07', - b'\xe6!fp\x07', - b'\xf3"fp\x07', - b'\xe6"f0\x07', - b'\xe6"fp\x07', - b'\xe6!`@\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xe6\xf5\004\000\000', - b'\xe6\xf5$\000\000', - b'\xe7\xf5\x04\x00\x00', - b'\xe7\xf6B0\000', - b'\xe7\xf5D0\000', - b'\xf1\x00\xd7\x10@', - b'\xe6\xf5D0\x00', - b'\xe9\xf6F0\x00', - b'\xe9\xf5B0\x00', - b'\xe9\xf6B0\x00', - b'\xe9\xf5"\x00\x00', - ], - }, - CAR.CROSSTREK_HYBRID: { - (Ecu.abs, 0x7b0, None): [ - b'\xa2 \x19e\x01', - b'\xa2 !e\x01', - ], - (Ecu.eps, 0x746, None): [ - b'\x9a\xc2\x01\x00', - b'\n\xc2\x01\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00el\x1f@ #', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xd7!`@\x07', - b'\xd7!`p\a', - b'\xf4!`0\x07', - ], - }, - CAR.FORESTER: { - (Ecu.abs, 0x7b0, None): [ - b'\xa3 \x18\x14\x00', - b'\xa3 \024\000', - b'\xa3 \031\024\000', - b'\xa3 \x14\x01', - b'\xf1\x00\xbb\r\x05', - b'\xa3 \x18&\x00', - b'\xa3 \x19&\x00', - ], - (Ecu.eps, 0x746, None): [ - b'\x8d\xc0\x04\x00', - b'\x8d\xc0\x00\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00e!\x1f@ \x11', - b'\x00\x00e\x97\x1f@ 0', - b'\000\000e`\037@ ', - b'\xf1\x00\xac\x02\x00', - b'\x00\x00e!\x00\x00\x00\x00', - b'\x00\x00e\x97\x00\x00\x00\x00', - b'\x00\x00e^\x1f@ !', - b'\x00\x00e^\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xb6"`A\x07', - b'\xcf"`0\x07', - b'\xcb\"`@\a', - b'\xcb\"`p\a', - b'\xf1\x00\xa2\x10\n', - b'\xcf"`p\x07', - b'\xb6\xa2`A\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\032\xf6B0\000', - b'\x1a\xf6F`\x00', - b'\032\xf6b`\000', - b'\x1a\xf6B`\x00', - b'\x1a\xf6b0\x00', - b'\x1a\xe6B1\x00', - b'\x1a\xe6F1\x00', - ], - }, - CAR.FORESTER_HYBRID: { - (Ecu.abs, 0x7b0, None): [ - b'\xa3 \x19T\x00', - ], - (Ecu.eps, 0x746, None): [ - b'\x8d\xc2\x00\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00eY\x1f@ !', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xd2\xa1`r\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\x1b\xa7@a\x00', - ], - }, - CAR.FORESTER_PREGLOBAL: { - (Ecu.abs, 0x7b0, None): [ - b'\x7d\x97\x14\x40', - b'\xf1\x00\xbb\x0c\x04', - b'm\x97\x14@', - ], - (Ecu.eps, 0x746, None): [ - b'}\xc0\x10\x00', - b'm\xc0\x10\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00\x64\x35\x1f\x40\x20\x09', - b'\x00\x00c\xe9\x1f@ \x03', - b'\x00\x00d\xd3\x1f@ \t', - b'\x00\x00c\xe9\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xba"@p\a', - b'\xa7)\xa0q\a', - b'\xf1\x82\xa7)\xa0q\a', - b'\xba"@@\a', - b'\xa7"@p\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xdc\xf2\x60\x60\x00', - b'\xdc\xf2@`\x00', - b'\xda\xfd\xe0\x80\x00', - b'\xdc\xf2`\x81\000', - b'\xdc\xf2`\x80\x00', - b'\x1a\xf6F`\x00', - b'\xda\xf2`\x80\x00', - ], - }, - CAR.LEGACY_PREGLOBAL: { - (Ecu.abs, 0x7b0, None): [ - b'k\x97D\x00', - b'[\xba\xc4\x03', - b'{\x97D\x00', - b'[\x97D\000', - b'k\x9aD\x00', - ], - (Ecu.eps, 0x746, None): [ - b'[\xb0\x00\x01', - b'K\xb0\x00\x01', - b'k\xb0\x00\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00c\xb7\x1f@\x10\x16', - b'\x00\x00c\x94\x1f@\x10\x08', - b'\x00\x00c\xec\x1f@ \x04', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xab*@r\a', - b'\xa0+@p\x07', - b'\xb4"@0\x07', - b'\xa0"@q\a', - b'\xab+@p\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xbe\xf2\x00p\x00', - b'\xbf\xfb\xc0\x80\x00', - b'\xbd\xf2\x00`\x00', - b'\xbf\xf2\000\x80\000', - b'\xbe\xfb\xc0p\x00', - ], - }, - CAR.OUTBACK_PREGLOBAL: { - (Ecu.abs, 0x7b0, None): [ - b'{\x9a\xac\x00', - b'k\x97\xac\x00', - b'\x5b\xf7\xbc\x03', - b'[\xf7\xac\x03', - b'{\x97\xac\x00', - b'k\x9a\xac\000', - b'[\xba\xac\x03', - b'[\xf7\xac\000', - ], - (Ecu.eps, 0x746, None): [ - b'k\xb0\x00\x00', - b'[\xb0\x00\x00', - b'\x4b\xb0\x00\x02', - b'K\xb0\x00\x00', - b'{\xb0\x00\x01', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00c\xec\x1f@ \x04', - b'\x00\x00c\xd1\x1f@\x10\x17', - b'\xf1\x00\xf0\xe0\x0e', - b'\x00\x00c\x94\x00\x00\x00\x00', - b'\x00\x00c\x94\x1f@\x10\b', - b'\x00\x00c\xb7\x1f@\x10\x16', - b'\000\000c\x90\037@\020\016', - b'\x00\x00c\xec\x37@\x04', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xb4+@p\a', - b'\xab\"@@\a', - b'\xa0\x62\x41\x71\x07', - b'\xa0*@q\a', - b'\xab*@@\a', - b'\xb4"@0\a', - b'\xb4"@p\a', - b'\xab"@s\a', - b'\xab+@@\a', - b'\xb4"@r\a', - b'\xa0+@@\x07', - b'\xa0\"@\x80\a', - b'\xa0*@u\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xbd\xfb\xe0\x80\x00', - b'\xbe\xf2@\x80\x00', - b'\xbf\xe2\x40\x80\x00', - b'\xbf\xf2@\x80\x00', - b'\xbe\xf2@p\x00', - b'\xbd\xf2@`\x00', - b'\xbd\xf2@\x81\000', - b'\xbe\xfb\xe0p\000', - b'\xbf\xfb\xe0b\x00', - ], - }, - CAR.OUTBACK_PREGLOBAL_2018: { - (Ecu.abs, 0x7b0, None): [ - b'\x8b\x97\xac\x00', - b'\x8b\x9a\xac\x00', - b'\x9b\x97\xac\x00', - b'\x8b\x97\xbc\x00', - b'\x8b\x99\xac\x00', - b'\x9b\x9a\xac\000', - b'\x9b\x97\xbe\x10', - ], - (Ecu.eps, 0x746, None): [ - b'{\xb0\x00\x00', - b'{\xb0\x00\x01', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00df\x1f@ \n', - b'\x00\x00d\xfe\x1f@ \x15', - b'\x00\x00d\x95\x00\x00\x00\x00', - b'\x00\x00d\x95\x1f@ \x0f', - b'\x00\x00d\xfe\x00\x00\x00\x00', - b'\x00\x00e\x19\x1f@ \x15', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xb5"@p\a', - b'\xb5+@@\a', - b'\xb5"@P\a', - b'\xc4"@0\a', - b'\xb5b@1\x07', - b'\xb5q\xe0@\a', - b'\xc4+@0\a', - b'\xc4b@p\a', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xbc\xf2@\x81\x00', - b'\xbc\xfb\xe0\x80\x00', - b'\xbc\xf2@\x80\x00', - b'\xbb\xf2@`\x00', - b'\xbc\xe2@\x80\x00', - b'\xbc\xfb\xe0`\x00', - b'\xbc\xaf\xe0`\x00', - b'\xbb\xfb\xe0`\000', - ], - }, - CAR.OUTBACK: { - (Ecu.abs, 0x7b0, None): [ - b'\xa1 \x06\x01', - b'\xa1 \a\x00', - b'\xa1 \b\001', - b'\xa1 \x06\x00', - b'\xa1 "\t\x01', - b'\xa1 \x08\x02', - b'\xa1 \x06\x02', - b'\xa1 \x07\x02', - b'\xa1 \x08\x00', - b'\xa1 "\t\x00', - ], - (Ecu.eps, 0x746, None): [ - b'\x9b\xc0\x10\x00', - b'\x9b\xc0\x20\x00', - b'\x1b\xc0\x10\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00eJ\x00\x1f@ \x19\x00', - b'\000\000e\x80\000\037@ \031\000', - b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00', - b'\x00\x00e\x9a\x00\x1f@ 1\x00', - b'\x00\x00eJ\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xbc,\xa0q\x07', - b'\xbc\"`@\a', - b'\xde"`0\a', - b'\xf1\x82\xbc,\xa0q\a', - b'\xf1\x82\xe3,\xa0@\x07', - b'\xe2"`0\x07', - b'\xe2"`p\x07', - b'\xf1\x82\xe2,\xa0@\x07', - b'\xbc"`q\x07', - b'\xe3,\xa0@\x07', - b'\xbc,\xa0u\x07', - b'\xde,\xa0@\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xa5\xfe\xf7@\x00', - b'\xa5\xf6D@\x00', - b'\xa5\xfe\xf6@\x00', - b'\xa7\x8e\xf40\x00', - b'\xf1\x82\xa7\xf6D@\x00', - b'\xa7\xf6D@\x00', - b'\xa7\xfe\xf4@\x00', - b'\xa5\xfe\xf8@\x00', - ], - }, - CAR.FORESTER_2022: { - (Ecu.abs, 0x7b0, None): [ - b'\xa3 !x\x00', - b'\xa3 !v\x00', - b'\xa3 "v\x00', - b'\xa3 "x\x00', - ], - (Ecu.eps, 0x746, None): [ - b'-\xc0%0', - b'-\xc0\x040', - b'=\xc0%\x02', - b'=\xc04\x02', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\x04!\x01\x1eD\x07!\x00\x04,', - b'\x04!\x08\x01.\x07!\x08\x022', - ], - (Ecu.engine, 0x7e0, None): [ - b'\xd5"a0\x07', - b'\xd5"`0\x07', - b'\xf1"aq\x07', - b'\xf1"`q\x07', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\x1d\x86B0\x00', - b'\x1d\xf6B0\x00', - b'\x1e\x86B0\x00', - b'\x1e\xf6D0\x00', - ], - }, - CAR.OUTBACK_2023: { - (Ecu.abs, 0x7b0, None): [ - b'\xa1 #\x14\x00', - b'\xa1 #\x17\x00', - ], - (Ecu.eps, 0x746, None): [ - b'+\xc0\x10\x11\x00', - b'+\xc0\x12\x11\x00', - ], - (Ecu.fwdCamera, 0x787, None): [ - b'\t!\x08\x046\x05!\x08\x01/', - ], - (Ecu.engine, 0x7a2, None): [ - b'\xed,\xa0q\x07', - b'\xed,\xa2q\x07', - ], - (Ecu.transmission, 0x7a3, None): [ - b'\xa8\x8e\xf41\x00', - b'\xa8\xfe\xf41\x00', - ] - } -} - DBC = { CAR.ASCENT: dbc_dict('subaru_global_2017_generated', None), CAR.ASCENT_2023: dbc_dict('subaru_global_2017_generated', None), diff --git a/selfdrive/car/tesla/fingerprints.py b/selfdrive/car/tesla/fingerprints.py new file mode 100644 index 0000000000..1d41ddbb3a --- /dev/null +++ b/selfdrive/car/tesla/fingerprints.py @@ -0,0 +1,30 @@ +# ruff: noqa: E501 +from cereal import car +from openpilot.selfdrive.car.tesla.values import CAR + +Ecu = car.CarParams.Ecu + +FINGERPRINTS = { + CAR.AP1_MODELS: [ + { + 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4 + }, + ], +} + +FW_VERSIONS = { + CAR.AP2_MODELS: { + (Ecu.adas, 0x649, None): [ + b'\x01\x00\x8b\x07\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11', + ], + (Ecu.electricBrakeBooster, 0x64d, None): [ + b'1037123-00-A', + ], + (Ecu.fwdRadar, 0x671, None): [ + b'\x01\x00W\x00\x00\x00\x07\x00\x00\x00\x00\x08\x01\x00\x00\x00\x07\xff\xfe', + ], + (Ecu.eps, 0x730, None): [ + b'\x10#\x01', + ], + }, +} diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index 548809af1b..12877f1344 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -1,4 +1,3 @@ -# ruff: noqa: E501 from collections import namedtuple from enum import StrEnum from typing import Dict, List, Union @@ -23,13 +22,6 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { CAR.AP2_MODELS: CarInfo("Tesla AP2 Model S", "All"), } -FINGERPRINTS = { - CAR.AP1_MODELS: [ - { - 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4 - }, - ], -} DBC = { CAR.AP2_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'), @@ -55,22 +47,6 @@ FW_QUERY_CONFIG = FwQueryConfig( ] ) -FW_VERSIONS = { - CAR.AP2_MODELS: { - (Ecu.adas, 0x649, None): [ - b'\x01\x00\x8b\x07\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11', - ], - (Ecu.electricBrakeBooster, 0x64d, None): [ - b'1037123-00-A', - ], - (Ecu.fwdRadar, 0x671, None): [ - b'\x01\x00W\x00\x00\x00\x07\x00\x00\x00\x00\x08\x01\x00\x00\x00\x07\xff\xfe', - ], - (Ecu.eps, 0x730, None): [ - b'\x10#\x01', - ], - }, -} class CANBUS: # Lateral harness diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 5721896787..257a40f6a3 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -64,7 +64,7 @@ class TestFwFingerprint(unittest.TestCase): "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) CP.carFw = fw _, matches = match_fw_to_car(CP.carFw, allow_exact=False, log=False) - brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw)) + brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), VERSIONS[brand]) # If both have matches, they must agree if len(matches) == 1 and len(brand_matches) == 1: diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py new file mode 100644 index 0000000000..7e89dfc7ac --- /dev/null +++ b/selfdrive/car/toyota/fingerprints.py @@ -0,0 +1,1641 @@ +from cereal import car +from openpilot.selfdrive.car.toyota.values import CAR + +Ecu = car.CarParams.Ecu + +FW_VERSIONS = { + CAR.AVALON: { + (Ecu.abs, 0x7b0, None): [ + b'F152607060\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881510701300\x00\x00\x00\x00', + b'881510705100\x00\x00\x00\x00', + b'881510705200\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B41051\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0230721100\x00\x00\x00\x00\x00\x00\x00\x00A0C01000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230721200\x00\x00\x00\x00\x00\x00\x00\x00A0C01000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702000\x00\x00\x00\x00', + b'8821F4702100\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F0701100\x00\x00\x00\x00', + b'8646F0703000\x00\x00\x00\x00', + ], + }, + CAR.AVALON_2019: { + (Ecu.abs, 0x7b0, None): [ + b'F152607140\x00\x00\x00\x00\x00\x00', + b'F152607171\x00\x00\x00\x00\x00\x00', + b'F152607110\x00\x00\x00\x00\x00\x00', + b'F152607180\x00\x00\x00\x00\x00\x00', + b'F152641040\x00\x00\x00\x00\x00\x00', + b'F152641061\x00\x00\x00\x00\x00\x00', + b'F152641050\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881510703200\x00\x00\x00\x00', + b'881510704200\x00\x00\x00\x00', + b'881514107100\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B41080\x00\x00\x00\x00\x00\x00', + b'8965B07010\x00\x00\x00\x00\x00\x00', + b'8965B41090\x00\x00\x00\x00\x00\x00', + b'8965B41070\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x01896630725200\x00\x00\x00\x00', + b'\x01896630725300\x00\x00\x00\x00', + b'\x01896630735100\x00\x00\x00\x00', + b'\x01896630738000\x00\x00\x00\x00', + b'\x02896630724000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x02896630737000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x02896630728000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F0702100\x00\x00\x00\x00', + ], + }, + CAR.AVALON_TSS2: { + (Ecu.abs, 0x7b0, None): [ + b'\x01F152607240\x00\x00\x00\x00\x00\x00', + b'\x01F152607250\x00\x00\x00\x00\x00\x00', + b'\x01F152607280\x00\x00\x00\x00\x00\x00', + b'F152641080\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B41110\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x01896630742000\x00\x00\x00\x00', + b'\x01896630743000\x00\x00\x00\x00', + b'\x018966306Q6000\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F6201200\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + ], + }, + CAR.CAMRY: { + (Ecu.engine, 0x700, None): [ + b'\x018966306L3100\x00\x00\x00\x00', + b'\x018966306L4200\x00\x00\x00\x00', + b'\x018966306L5200\x00\x00\x00\x00', + b'\x018966306P8000\x00\x00\x00\x00', + b'\x018966306Q3100\x00\x00\x00\x00', + b'\x018966306Q4000\x00\x00\x00\x00', + b'\x018966306Q4100\x00\x00\x00\x00', + b'\x018966306Q4200\x00\x00\x00\x00', + b'\x018966333Q9200\x00\x00\x00\x00', + b'\x018966333P3100\x00\x00\x00\x00', + b'\x018966333P3200\x00\x00\x00\x00', + b'\x018966333P4200\x00\x00\x00\x00', + b'\x018966333P4300\x00\x00\x00\x00', + b'\x018966333P4400\x00\x00\x00\x00', + b'\x018966333P4500\x00\x00\x00\x00', + b'\x018966333P4700\x00\x00\x00\x00', + b'\x018966333P4900\x00\x00\x00\x00', + b'\x018966333Q6000\x00\x00\x00\x00', + b'\x018966333Q6200\x00\x00\x00\x00', + b'\x018966333Q6300\x00\x00\x00\x00', + b'\x018966333Q6500\x00\x00\x00\x00', + b'\x018966333W6000\x00\x00\x00\x00', + b'\x018966306Q6000\x00\x00\x00\x00', + b'\x018966333N1100\x00\x00\x00\x00', + b'\x018966333N4300\x00\x00\x00\x00', + b'\x018966333X0000\x00\x00\x00\x00', + b'\x018966333X4000\x00\x00\x00\x00', + b'\x01896633T16000\x00\x00\x00\x00', + b'\x018966306L9000\x00\x00\x00\x00', + b'\x028966306B2100\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306B2300\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306B2500\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306N8100\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306N8200\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306N8300\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306N8400\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306R5000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306R5000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x028966306R6000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966306R6000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x028966306S0000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x028966306S0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x028966306S1100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x02333P1100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'8821F0601200 ', + b'8821F0601300 ', + b'8821F0602000 ', + b'8821F0603300 ', + b'8821F0604100 ', + b'8821F0605200 ', + b'8821F0607200 ', + b'8821F0608000 ', + b'8821F0608200 ', + b'8821F0609100 ', + b'8821F0603400 ', + b'8821F0604000 ', + b'8821F0604200 ', + b'8821F0606200 ', + b'8821F0609000 ', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152606210\x00\x00\x00\x00\x00\x00', + b'F152606230\x00\x00\x00\x00\x00\x00', + b'F152606270\x00\x00\x00\x00\x00\x00', + b'F152606290\x00\x00\x00\x00\x00\x00', + b'F152606410\x00\x00\x00\x00\x00\x00', + b'F152633540\x00\x00\x00\x00\x00\x00', + b'F152633A10\x00\x00\x00\x00\x00\x00', + b'F152633A20\x00\x00\x00\x00\x00\x00', + b'F152633214\x00\x00\x00\x00\x00\x00', + b'F152633660\x00\x00\x00\x00\x00\x00', + b'F152633712\x00\x00\x00\x00\x00\x00', + b'F152633713\x00\x00\x00\x00\x00\x00', + b'F152633B51\x00\x00\x00\x00\x00\x00', + b'F152633B60\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B33540\x00\x00\x00\x00\x00\x00', + b'8965B33542\x00\x00\x00\x00\x00\x00', + b'8965B33580\x00\x00\x00\x00\x00\x00', + b'8965B33581\x00\x00\x00\x00\x00\x00', + b'8965B33621\x00\x00\x00\x00\x00\x00', + b'8965B33550\x00\x00\x00\x00\x00\x00', + b'8965B33551\x00\x00\x00\x00\x00\x00', + b'8965B33611\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ # Same as 0x791 + b'8821F0601200 ', + b'8821F0601300 ', + b'8821F0602000 ', + b'8821F0603300 ', + b'8821F0604100 ', + b'8821F0605200 ', + b'8821F0607200 ', + b'8821F0608000 ', + b'8821F0608200 ', + b'8821F0609100 ', + b'8821F0603400 ', + b'8821F0604000 ', + b'8821F0604200 ', + b'8821F0606200 ', + b'8821F0609000 ', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F0601200 ', + b'8646F0601300 ', + b'8646F0601400 ', + b'8646F0603400 ', + b'8646F0604100 ', + b'8646F0605000 ', + b'8646F0606000 ', + b'8646F0606100 ', + b'8646F0607100 ', + b'8646F0603500 ', + b'8646F0607000 ', + ], + }, + CAR.CAMRY_TSS2: { + (Ecu.eps, 0x7a1, None): [ + b'8965B33630\x00\x00\x00\x00\x00\x00', + b'8965B33640\x00\x00\x00\x00\x00\x00', + b'8965B33650\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F152606370\x00\x00\x00\x00\x00\x00', + b'\x01F152606390\x00\x00\x00\x00\x00\x00', + b'\x01F152606400\x00\x00\x00\x00\x00\x00', + b'\x01F152606431\x00\x00\x00\x00\x00\x00', + b'F152633D00\x00\x00\x00\x00\x00\x00', + b'F152633D60\x00\x00\x00\x00\x00\x00', + b'F152633310\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x018966306Q5000\x00\x00\x00\x00', + b'\x018966306Q9000\x00\x00\x00\x00', + b'\x018966306R3000\x00\x00\x00\x00', + b'\x018966306R8000\x00\x00\x00\x00', + b'\x018966306T3100\x00\x00\x00\x00', + b'\x018966306T3200\x00\x00\x00\x00', + b'\x018966306T4000\x00\x00\x00\x00', + b'\x018966306T4100\x00\x00\x00\x00', + b'\x018966306Q6000\x00\x00\x00\x00', + b'\x018966306Q7000\x00\x00\x00\x00', + b'\x018966306T0000\x00\x00\x00\x00', + b'\x018966306V1000\x00\x00\x00\x00', + b'\x01896633T20000\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F6201200\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', + b'\x018821F6201400\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0602100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F0602200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F0602300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F3305200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + ], + }, + CAR.CHR: { + (Ecu.engine, 0x700, None): [ + b'\x01896631021100\x00\x00\x00\x00', + b'\x01896631017100\x00\x00\x00\x00', + b'\x01896631017200\x00\x00\x00\x00', + b'\x0189663F413100\x00\x00\x00\x00', + b'\x0189663F414100\x00\x00\x00\x00', + b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896631013200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0289663F405000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0289663F418000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0289663F423000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0289663F431000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0189663F438000\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'8821F0W01000 ', + b'8821F0W01100 ', + b'8821FF401600 ', + b'8821FF404000 ', + b'8821FF404100 ', + b'8821FF405100 ', + b'8821FF406000 ', + b'8821FF407100 ', + b'8821FF402300 ', + b'8821FF402400 ', + b'8821FF405000 ', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152610020\x00\x00\x00\x00\x00\x00', + b'F152610153\x00\x00\x00\x00\x00\x00', + b'F152610210\x00\x00\x00\x00\x00\x00', + b'F1526F4034\x00\x00\x00\x00\x00\x00', + b'F1526F4044\x00\x00\x00\x00\x00\x00', + b'F1526F4073\x00\x00\x00\x00\x00\x00', + b'F1526F4121\x00\x00\x00\x00\x00\x00', + b'F1526F4122\x00\x00\x00\x00\x00\x00', + b'F152610012\x00\x00\x00\x00\x00\x00', + b'F152610013\x00\x00\x00\x00\x00\x00', + b'F152610014\x00\x00\x00\x00\x00\x00', + b'F152610040\x00\x00\x00\x00\x00\x00', + b'F152610190\x00\x00\x00\x00\x00\x00', + b'F152610200\x00\x00\x00\x00\x00\x00', + b'F152610220\x00\x00\x00\x00\x00\x00', + b'F152610230\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B10011\x00\x00\x00\x00\x00\x00', + b'8965B10040\x00\x00\x00\x00\x00\x00', + b'8965B10070\x00\x00\x00\x00\x00\x00', + b'8965B10020\x00\x00\x00\x00\x00\x00', + b'8965B10050\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0331024000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', + b'\x0331024000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', + b'\x0331036000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', + b'\x033F401100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203102\x00\x00\x00\x00', + b'\x033F401200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', + b'\x033F424000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', + b'\x033F424000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F0W01000 ', + b'8821FF401600 ', + b'8821FF404000 ', + b'8821FF404100 ', + b'8821FF405100 ', + b'8821FF406000 ', + b'8821FF407100 ', + b'8821F0W01100 ', + b'8821FF402300 ', + b'8821FF402400 ', + b'8821FF405000 ', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646FF401700 ', + b'8646FF401800 ', + b'8646FF404000 ', + b'8646FF406000 ', + b'8646FF407000 ', + b'8646FF402100 ', + b'8646FF407100 ', + ], + }, + CAR.CHR_TSS2: { + (Ecu.abs, 0x7b0, None): [ + b'F152610260\x00\x00\x00\x00\x00\x00', + b'F1526F4270\x00\x00\x00\x00\x00\x00', + b'F152610041\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B10091\x00\x00\x00\x00\x00\x00', + b'8965B10110\x00\x00\x00\x00\x00\x00', + b'8965B10092\x00\x00\x00\x00\x00\x00', + b'8965B10111\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x0189663F459000\x00\x00\x00\x00', + b'\x0189663F438000\x00\x00\x00\x00', + b'\x02896631025000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0289663F453000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0331014000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821FF410200\x00\x00\x00\x00', + b'\x018821FF410300\x00\x00\x00\x00', + b'\x018821FF410500\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646FF410200\x00\x00\x00\x008646GF408200\x00\x00\x00\x00', + b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00', + b'\x028646FF413100\x00\x00\x00\x008646GF411100\x00\x00\x00\x00', + ], + }, + CAR.COROLLA: { + (Ecu.engine, 0x7e0, None): [ + b'\x0230ZC2000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZC2100\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZC2200\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZC2300\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZC3000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZC3100\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZC3200\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZC3300\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0330ZC1200\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881510201100\x00\x00\x00\x00', + b'881510201200\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152602190\x00\x00\x00\x00\x00\x00', + b'F152602191\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B02181\x00\x00\x00\x00\x00\x00', + b'8965B02191\x00\x00\x00\x00\x00\x00', + b'8965B48150\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F0201101\x00\x00\x00\x00', + b'8646F0201200\x00\x00\x00\x00', + ], + }, + CAR.COROLLA_TSS2: { + (Ecu.engine, 0x700, None): [ + b'\x01896630A22000\x00\x00\x00\x00', + b'\x01896630ZG2000\x00\x00\x00\x00', + b'\x01896630ZG5000\x00\x00\x00\x00', + b'\x01896630ZG5100\x00\x00\x00\x00', + b'\x01896630ZG5200\x00\x00\x00\x00', + b'\x01896630ZG5300\x00\x00\x00\x00', + b'\x01896630ZP1000\x00\x00\x00\x00', + b'\x01896630ZP2000\x00\x00\x00\x00', + b'\x01896630ZQ5000\x00\x00\x00\x00', + b'\x01896630ZU9000\x00\x00\x00\x00', + b'\x01896630ZX4000\x00\x00\x00\x00', + b'\x018966312L8000\x00\x00\x00\x00', + b'\x018966312M0000\x00\x00\x00\x00', + b'\x018966312M9000\x00\x00\x00\x00', + b'\x018966312P9000\x00\x00\x00\x00', + b'\x018966312P9100\x00\x00\x00\x00', + b'\x018966312P9200\x00\x00\x00\x00', + b'\x018966312P9300\x00\x00\x00\x00', + b'\x018966312Q2300\x00\x00\x00\x00', + b'\x018966312Q8000\x00\x00\x00\x00', + b'\x018966312R0000\x00\x00\x00\x00', + b'\x018966312R0100\x00\x00\x00\x00', + b'\x018966312R0200\x00\x00\x00\x00', + b'\x018966312R1000\x00\x00\x00\x00', + b'\x018966312R1100\x00\x00\x00\x00', + b'\x018966312R3100\x00\x00\x00\x00', + b'\x018966312S5000\x00\x00\x00\x00', + b'\x018966312S7000\x00\x00\x00\x00', + b'\x018966312W3000\x00\x00\x00\x00', + b'\x018966312W9000\x00\x00\x00\x00', + b'\x01896637644000\x00\x00\x00\x00', + b'\x01896630ZJ1000\x00\x00\x00\x00', + b'\x01896630ZU8000\x00\x00\x00\x00', + b'\x01896637621000\x00\x00\x00\x00', + b'\x01896637623000\x00\x00\x00\x00', + b'\x01896637624000\x00\x00\x00\x00', + b'\x01896637626000\x00\x00\x00\x00', + b'\x01896637639000\x00\x00\x00\x00', + b'\x01896637648000\x00\x00\x00\x00', + b'\x01896637643000\x00\x00\x00\x00', + b'\x02896630A07000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630A21000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZJ5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZK8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZN8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZQ3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZR2000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZT8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZT9000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZZ0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966312K6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966312L0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966312Q3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966312Q3100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966312Q4000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x038966312L7000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF1205001\x00\x00\x00\x00', + b'\x038966312N1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x038966312T3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF1205001\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0230A10000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230A11000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZN4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230ZN5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x03312K7000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + b'\x03312M3000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', + b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', + b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', + b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', + b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', + b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + b'\x02312K4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02312U5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'\x018965B12350\x00\x00\x00\x00\x00\x00', + b'\x018965B12470\x00\x00\x00\x00\x00\x00', + b'\x018965B12490\x00\x00\x00\x00\x00\x00', + b'\x018965B12500\x00\x00\x00\x00\x00\x00', + b'\x018965B12520\x00\x00\x00\x00\x00\x00', + b'\x018965B12530\x00\x00\x00\x00\x00\x00', + b'\x018965B1255000\x00\x00\x00\x00', + b'8965B12361\x00\x00\x00\x00\x00\x00', + b'8965B16011\x00\x00\x00\x00\x00\x00', + b'8965B76012\x00\x00\x00\x00\x00\x00', + b'\x018965B12510\x00\x00\x00\x00\x00\x00', + b'\x018965B1256000\x00\x00\x00\x00', + b'8965B12451\x00\x00\x00\x00\x00\x00', + b'8965B16101\x00\x00\x00\x00\x00\x00', + b'8965B16170\x00\x00\x00\x00\x00\x00', + b'8965B76050\x00\x00\x00\x00\x00\x00', + b'8965B76091\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F152602280\x00\x00\x00\x00\x00\x00', + b'\x01F152602560\x00\x00\x00\x00\x00\x00', + b'\x01F152602590\x00\x00\x00\x00\x00\x00', + b'\x01F152602650\x00\x00\x00\x00\x00\x00', + b'\x01F15260A010\x00\x00\x00\x00\x00\x00', + b'\x01F15260A050\x00\x00\x00\x00\x00\x00', + b'\x01F152612641\x00\x00\x00\x00\x00\x00', + b'\x01F152612651\x00\x00\x00\x00\x00\x00', + b'\x01F152612B10\x00\x00\x00\x00\x00\x00', + b'\x01F152612B51\x00\x00\x00\x00\x00\x00', + b'\x01F152612B60\x00\x00\x00\x00\x00\x00', + b'\x01F152612B61\x00\x00\x00\x00\x00\x00', + b'\x01F152612B62\x00\x00\x00\x00\x00\x00', + b'\x01F152612B70\x00\x00\x00\x00\x00\x00', + b'\x01F152612B71\x00\x00\x00\x00\x00\x00', + b'\x01F152612B81\x00\x00\x00\x00\x00\x00', + b'\x01F152612B90\x00\x00\x00\x00\x00\x00', + b'\x01F152612C00\x00\x00\x00\x00\x00\x00', + b'\x01F152612862\x00\x00\x00\x00\x00\x00', + b'\x01F152612B91\x00\x00\x00\x00\x00\x00', + b'\x01F15260A070\x00\x00\x00\x00\x00\x00', + b'\x01F152676250\x00\x00\x00\x00\x00\x00', + b'\x01F152602470\x00\x00\x00\x00\x00\x00', + b'F152612590\x00\x00\x00\x00\x00\x00', + b'F152612691\x00\x00\x00\x00\x00\x00', + b'F152612692\x00\x00\x00\x00\x00\x00', + b'F152612700\x00\x00\x00\x00\x00\x00', + b'F152612710\x00\x00\x00\x00\x00\x00', + b'F152612790\x00\x00\x00\x00\x00\x00', + b'F152612800\x00\x00\x00\x00\x00\x00', + b'F152612820\x00\x00\x00\x00\x00\x00', + b'F152612840\x00\x00\x00\x00\x00\x00', + b'F152612842\x00\x00\x00\x00\x00\x00', + b'F152612890\x00\x00\x00\x00\x00\x00', + b'F152612A00\x00\x00\x00\x00\x00\x00', + b'F152612A10\x00\x00\x00\x00\x00\x00', + b'F152612D00\x00\x00\x00\x00\x00\x00', + b'F152616011\x00\x00\x00\x00\x00\x00', + b'F152616060\x00\x00\x00\x00\x00\x00', + b'F152616030\x00\x00\x00\x00\x00\x00', + b'F152642540\x00\x00\x00\x00\x00\x00', + b'F152676293\x00\x00\x00\x00\x00\x00', + b'F152676303\x00\x00\x00\x00\x00\x00', + b'F152676304\x00\x00\x00\x00\x00\x00', + b'F152676371\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F3301100\x00\x00\x00\x00', + b'\x018821F3301200\x00\x00\x00\x00', + b'\x018821F3301300\x00\x00\x00\x00', + b'\x018821F3301400\x00\x00\x00\x00', + b'\x018821F6201400\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F12010D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F1201100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F1201200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F1201300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1201400\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b'\x028646F1202000\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1601200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F7603100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F7603200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F7605100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + ], + }, + CAR.HIGHLANDER: { + (Ecu.engine, 0x700, None): [ + b'\x01896630E09000\x00\x00\x00\x00', + b'\x01896630E43000\x00\x00\x00\x00', + b'\x01896630E43100\x00\x00\x00\x00', + b'\x01896630E43200\x00\x00\x00\x00', + b'\x01896630E44200\x00\x00\x00\x00', + b'\x01896630E45000\x00\x00\x00\x00', + b'\x01896630E45100\x00\x00\x00\x00', + b'\x01896630E45200\x00\x00\x00\x00', + b'\x01896630E46000\x00\x00\x00\x00', + b'\x01896630E46200\x00\x00\x00\x00', + b'\x01896630E74000\x00\x00\x00\x00', + b'\x01896630E75000\x00\x00\x00\x00', + b'\x01896630E76000\x00\x00\x00\x00', + b'\x01896630E77000\x00\x00\x00\x00', + b'\x01896630E83000\x00\x00\x00\x00', + b'\x01896630E84000\x00\x00\x00\x00', + b'\x01896630E85000\x00\x00\x00\x00', + b'\x01896630E86000\x00\x00\x00\x00', + b'\x01896630E88000\x00\x00\x00\x00', + b'\x01896630EA0000\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B48140\x00\x00\x00\x00\x00\x00', + b'8965B48150\x00\x00\x00\x00\x00\x00', + b'8965B48210\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [b'F15260E011\x00\x00\x00\x00\x00\x00'], + (Ecu.dsu, 0x791, None): [ + b'881510E01100\x00\x00\x00\x00', + b'881510E01200\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F0E01200\x00\x00\x00\x00', + b'8646F0E01300\x00\x00\x00\x00', + ], + }, + CAR.HIGHLANDERH: { + (Ecu.eps, 0x7a1, None): [ + b'8965B48160\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152648541\x00\x00\x00\x00\x00\x00', + b'F152648542\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0230E40000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230E40100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230EA2000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230EA2100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F0E01200\x00\x00\x00\x00', + b'8646F0E01300\x00\x00\x00\x00', + ], + }, + CAR.HIGHLANDER_TSS2: { + (Ecu.eps, 0x7a1, None): [ + b'8965B48241\x00\x00\x00\x00\x00\x00', + b'8965B48310\x00\x00\x00\x00\x00\x00', + b'8965B48320\x00\x00\x00\x00\x00\x00', + b'8965B48400\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F15260E051\x00\x00\x00\x00\x00\x00', + b'\x01F15260E061\x00\x00\x00\x00\x00\x00', + b'\x01F15260E110\x00\x00\x00\x00\x00\x00', + b'\x01F15260E170\x00\x00\x00\x00\x00\x00', + b'\x01F15260E05300\x00\x00\x00\x00', + b'\x01F15264872300\x00\x00\x00\x00', + b'\x01F15264872400\x00\x00\x00\x00', + b'\x01F15264872500\x00\x00\x00\x00', + b'\x01F15264872600\x00\x00\x00\x00', + b'\x01F15264872700\x00\x00\x00\x00', + b'\x01F15264873500\x00\x00\x00\x00', + b'\x01F152648C6300\x00\x00\x00\x00', + b'\x01F152648J4000\x00\x00\x00\x00', + b'\x01F152648J5000\x00\x00\x00\x00', + b'\x01F152648J6000\x00\x00\x00\x00', + b'\x01F152648J7000\x00\x00\x00\x00', + b'\x01F152648L5000\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x01896630E62100\x00\x00\x00\x00', + b'\x01896630E62200\x00\x00\x00\x00', + b'\x01896630E64100\x00\x00\x00\x00', + b'\x01896630E64200\x00\x00\x00\x00', + b'\x01896630E64400\x00\x00\x00\x00', + b'\x01896630EB1000\x00\x00\x00\x00', + b'\x01896630EB1100\x00\x00\x00\x00', + b'\x01896630EB1200\x00\x00\x00\x00', + b'\x01896630EB2000\x00\x00\x00\x00', + b'\x01896630EB2100\x00\x00\x00\x00', + b'\x01896630EB2200\x00\x00\x00\x00', + b'\x01896630EC4000\x00\x00\x00\x00', + b'\x01896630ED9000\x00\x00\x00\x00', + b'\x01896630ED9100\x00\x00\x00\x00', + b'\x01896630EE1000\x00\x00\x00\x00', + b'\x01896630EE1100\x00\x00\x00\x00', + b'\x01896630EG3000\x00\x00\x00\x00', + b'\x01896630EG5000\x00\x00\x00\x00', + b'\x01896630E67000\x00\x00\x00\x00', + b'\x01896630EA1000\x00\x00\x00\x00', + b'\x01896630EE4000\x00\x00\x00\x00', + b'\x01896630EE4100\x00\x00\x00\x00', + b'\x01896630EE5000\x00\x00\x00\x00', + b'\x01896630EE6000\x00\x00\x00\x00', + b'\x01896630EE7000\x00\x00\x00\x00', + b'\x01896630EF8000\x00\x00\x00\x00', + b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896630EB3000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896630EB3100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F3301400\x00\x00\x00\x00', + b'\x018821F6201200\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_IS: { + (Ecu.engine, 0x700, None): [ + b'\x018966353M7000\x00\x00\x00\x00', + b'\x018966353M7100\x00\x00\x00\x00', + b'\x018966353Q2000\x00\x00\x00\x00', + b'\x018966353Q2300\x00\x00\x00\x00', + b'\x018966353Q4000\x00\x00\x00\x00', + b'\x018966353R1100\x00\x00\x00\x00', + b'\x018966353R7100\x00\x00\x00\x00', + b'\x018966353R8100\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0232480000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02353P7000\x00\x00\x00\x00\x00\x00\x00\x00530J5000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02353P9000\x00\x00\x00\x00\x00\x00\x00\x00553C1000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152653300\x00\x00\x00\x00\x00\x00', + b'F152653301\x00\x00\x00\x00\x00\x00', + b'F152653310\x00\x00\x00\x00\x00\x00', + b'F152653330\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881515306200\x00\x00\x00\x00', + b'881515306400\x00\x00\x00\x00', + b'881515306500\x00\x00\x00\x00', + b'881515307400\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B53270\x00\x00\x00\x00\x00\x00', + b'8965B53271\x00\x00\x00\x00\x00\x00', + b'8965B53280\x00\x00\x00\x00\x00\x00', + b'8965B53281\x00\x00\x00\x00\x00\x00', + b'8965B53311\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702300\x00\x00\x00\x00', + b'8821F4702100\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F5301101\x00\x00\x00\x00', + b'8646F5301200\x00\x00\x00\x00', + b'8646F5301300\x00\x00\x00\x00', + b'8646F5301400\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_IS_TSS2: { + (Ecu.engine, 0x700, None): [ + b'\x018966353S1000\x00\x00\x00\x00', + b'\x018966353S2000\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F15265337200\x00\x00\x00\x00', + b'\x01F15265342000\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B53450\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F6201200\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F5303300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F5303400\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + ], + }, + CAR.PRIUS: { + (Ecu.engine, 0x700, None): [ + b'\x02896634761000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634761100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634761200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634762000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634763000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634763100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634765000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634765100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634769000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634769100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634769200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634770000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634774000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634774100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634774200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634782000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634784000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966347A0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966347A5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966347A8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966347B0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x03896634759100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', + b'\x03896634759200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', + b'\x03896634759200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', + b'\x03896634759300\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', + b'\x03896634760000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701002\x00\x00\x00\x00', + b'\x03896634760000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', + b'\x03896634760000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', + b'\x03896634760100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', + b'\x03896634760200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', + b'\x03896634760200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', + b'\x03896634760300\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', + b'\x03896634768000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4703001\x00\x00\x00\x00', + b'\x03896634768000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4703002\x00\x00\x00\x00', + b'\x03896634768100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4703002\x00\x00\x00\x00', + b'\x03896634785000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4705001\x00\x00\x00\x00', + b'\x03896634785000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710001\x00\x00\x00\x00', + b'\x03896634786000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4705001\x00\x00\x00\x00', + b'\x03896634786000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710001\x00\x00\x00\x00', + b'\x03896634789000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4703002\x00\x00\x00\x00', + b'\x038966347A3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', + b'\x038966347A3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707001\x00\x00\x00\x00', + b'\x038966347B6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710001\x00\x00\x00\x00', + b'\x038966347B7000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710001\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B47021\x00\x00\x00\x00\x00\x00', + b'8965B47022\x00\x00\x00\x00\x00\x00', + b'8965B47023\x00\x00\x00\x00\x00\x00', + b'8965B47050\x00\x00\x00\x00\x00\x00', + b'8965B47060\x00\x00\x00\x00\x00\x00', # This is the EPS with good angle sensor + ], + (Ecu.abs, 0x7b0, None): [ + b'F152647290\x00\x00\x00\x00\x00\x00', + b'F152647300\x00\x00\x00\x00\x00\x00', + b'F152647310\x00\x00\x00\x00\x00\x00', + b'F152647414\x00\x00\x00\x00\x00\x00', + b'F152647415\x00\x00\x00\x00\x00\x00', + b'F152647416\x00\x00\x00\x00\x00\x00', + b'F152647417\x00\x00\x00\x00\x00\x00', + b'F152647470\x00\x00\x00\x00\x00\x00', + b'F152647490\x00\x00\x00\x00\x00\x00', + b'F152647682\x00\x00\x00\x00\x00\x00', + b'F152647683\x00\x00\x00\x00\x00\x00', + b'F152647684\x00\x00\x00\x00\x00\x00', + b'F152647862\x00\x00\x00\x00\x00\x00', + b'F152647863\x00\x00\x00\x00\x00\x00', + b'F152647864\x00\x00\x00\x00\x00\x00', + b'F152647865\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881514702300\x00\x00\x00\x00', + b'881514702400\x00\x00\x00\x00', + b'881514703100\x00\x00\x00\x00', + b'881514704100\x00\x00\x00\x00', + b'881514706000\x00\x00\x00\x00', + b'881514706100\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702000\x00\x00\x00\x00', + b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F4701300\x00\x00\x00\x00', + b'8646F4702001\x00\x00\x00\x00', + b'8646F4702100\x00\x00\x00\x00', + b'8646F4702200\x00\x00\x00\x00', + b'8646F4705000\x00\x00\x00\x00', + b'8646F4705200\x00\x00\x00\x00', + ], + }, + CAR.PRIUS_V: { + (Ecu.abs, 0x7b0, None): [ + b'F152647280\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0234781000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881514705100\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F4703300\x00\x00\x00\x00', + ], + }, + CAR.RAV4: { + (Ecu.engine, 0x7e0, None): [ + b'\x02342Q1000\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342Q1100\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342Q1200\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342Q1300\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342Q2000\x00\x00\x00\x00\x00\x00\x00\x0054213000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342Q2100\x00\x00\x00\x00\x00\x00\x00\x0054213000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342Q2200\x00\x00\x00\x00\x00\x00\x00\x0054213000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342Q4000\x00\x00\x00\x00\x00\x00\x00\x0054215000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B42063\x00\x00\x00\x00\x00\x00', + b'8965B42073\x00\x00\x00\x00\x00\x00', + b'8965B42082\x00\x00\x00\x00\x00\x00', + b'8965B42083\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F15260R102\x00\x00\x00\x00\x00\x00', + b'F15260R103\x00\x00\x00\x00\x00\x00', + b'F152642493\x00\x00\x00\x00\x00\x00', + b'F152642492\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881514201200\x00\x00\x00\x00', + b'881514201300\x00\x00\x00\x00', + b'881514201400\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702000\x00\x00\x00\x00', + b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F4201100\x00\x00\x00\x00', + b'8646F4201200\x00\x00\x00\x00', + b'8646F4202001\x00\x00\x00\x00', + b'8646F4202100\x00\x00\x00\x00', + b'8646F4204000\x00\x00\x00\x00', + ], + }, + CAR.RAV4H: { + (Ecu.engine, 0x7e0, None): [ + b'\x02342N9000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342N9100\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02342P0000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B42102\x00\x00\x00\x00\x00\x00', + b'8965B42103\x00\x00\x00\x00\x00\x00', + b'8965B42112\x00\x00\x00\x00\x00\x00', + b'8965B42162\x00\x00\x00\x00\x00\x00', + b'8965B42163\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152642090\x00\x00\x00\x00\x00\x00', + b'F152642110\x00\x00\x00\x00\x00\x00', + b'F152642120\x00\x00\x00\x00\x00\x00', + b'F152642400\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881514202200\x00\x00\x00\x00', + b'881514202300\x00\x00\x00\x00', + b'881514202400\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702000\x00\x00\x00\x00', + b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F4201100\x00\x00\x00\x00', + b'8646F4201200\x00\x00\x00\x00', + b'8646F4202001\x00\x00\x00\x00', + b'8646F4202100\x00\x00\x00\x00', + b'8646F4204000\x00\x00\x00\x00', + ], + }, + CAR.RAV4_TSS2: { + (Ecu.engine, 0x700, None): [ + b'\x01896630R58000\x00\x00\x00\x00', + b'\x01896630R58100\x00\x00\x00\x00', + b'\x018966342E2000\x00\x00\x00\x00', + b'\x018966342M8000\x00\x00\x00\x00', + b'\x018966342S9000\x00\x00\x00\x00', + b'\x018966342T1000\x00\x00\x00\x00', + b'\x018966342T6000\x00\x00\x00\x00', + b'\x018966342T9000\x00\x00\x00\x00', + b'\x018966342U4000\x00\x00\x00\x00', + b'\x018966342U4100\x00\x00\x00\x00', + b'\x018966342U5100\x00\x00\x00\x00', + b'\x018966342V0000\x00\x00\x00\x00', + b'\x018966342V3000\x00\x00\x00\x00', + b'\x018966342V3100\x00\x00\x00\x00', + b'\x018966342V3200\x00\x00\x00\x00', + b'\x01896634A05000\x00\x00\x00\x00', + b'\x01896634A19000\x00\x00\x00\x00', + b'\x01896634A19100\x00\x00\x00\x00', + b'\x01896634A20000\x00\x00\x00\x00', + b'\x01896634A20100\x00\x00\x00\x00', + b'\x01896634A22000\x00\x00\x00\x00', + b'\x01896634A22100\x00\x00\x00\x00', + b'\x01896634A30000\x00\x00\x00\x00', + b'\x01896634A44000\x00\x00\x00\x00', + b'\x01896634A45000\x00\x00\x00\x00', + b'\x01896634A46000\x00\x00\x00\x00', + b'\x028966342M7000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', + b'\x028966342T0000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', + b'\x028966342V1000\x00\x00\x00\x00897CF1202001\x00\x00\x00\x00', + b'\x028966342Y8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', + b'\x02896634A18000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', + b'\x02896634A18100\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', + b'\x02896634A43000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00', + b'\x02896634A47000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00', + b'\x028966342Z8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', + b'\x01896634A15000\x00\x00\x00\x00', + b'\x018966342M5000\x00\x00\x00\x00', + b'\x018966342W8000\x00\x00\x00\x00', + b'\x018966342X5000\x00\x00\x00\x00', + b'\x018966342X6000\x00\x00\x00\x00', + b'\x01896634A25000\x00\x00\x00\x00', + b'\x018966342W5000\x00\x00\x00\x00', + b'\x018966342W7000\x00\x00\x00\x00', + b'\x028966342W4001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x02896634A13000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02896634A13001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896634A13101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x02896634A23000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02896634A23001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x02896634A23101\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F15260R210\x00\x00\x00\x00\x00\x00', + b'\x01F15260R220\x00\x00\x00\x00\x00\x00', + b'\x01F15260R290\x00\x00\x00\x00\x00\x00', + b'\x01F15260R300\x00\x00\x00\x00\x00\x00', + b'\x01F15260R302\x00\x00\x00\x00\x00\x00', + b'\x01F152642551\x00\x00\x00\x00\x00\x00', + b'\x01F152642561\x00\x00\x00\x00\x00\x00', + b'\x01F152642601\x00\x00\x00\x00\x00\x00', + b'\x01F152642700\x00\x00\x00\x00\x00\x00', + b'\x01F152642701\x00\x00\x00\x00\x00\x00', + b'\x01F152642710\x00\x00\x00\x00\x00\x00', + b'\x01F152642711\x00\x00\x00\x00\x00\x00', + b'\x01F152642750\x00\x00\x00\x00\x00\x00', + b'\x01F152642751\x00\x00\x00\x00\x00\x00', + b'\x01F15260R292\x00\x00\x00\x00\x00\x00', + b'F152642291\x00\x00\x00\x00\x00\x00', + b'F152642290\x00\x00\x00\x00\x00\x00', + b'F152642322\x00\x00\x00\x00\x00\x00', + b'F152642330\x00\x00\x00\x00\x00\x00', + b'F152642331\x00\x00\x00\x00\x00\x00', + b'F152642531\x00\x00\x00\x00\x00\x00', + b'F152642532\x00\x00\x00\x00\x00\x00', + b'F152642520\x00\x00\x00\x00\x00\x00', + b'F152642521\x00\x00\x00\x00\x00\x00', + b'F152642540\x00\x00\x00\x00\x00\x00', + b'F152642541\x00\x00\x00\x00\x00\x00', + b'F152642542\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B42170\x00\x00\x00\x00\x00\x00', + b'8965B42171\x00\x00\x00\x00\x00\x00', + b'8965B42180\x00\x00\x00\x00\x00\x00', + b'8965B42181\x00\x00\x00\x00\x00\x00', + b'\x028965B0R01200\x00\x00\x00\x008965B0R02200\x00\x00\x00\x00', + b'\x028965B0R01300\x00\x00\x00\x008965B0R02300\x00\x00\x00\x00', + b'\x028965B0R01400\x00\x00\x00\x008965B0R02400\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F3301100\x00\x00\x00\x00', + b'\x018821F3301200\x00\x00\x00\x00', + b'\x018821F3301300\x00\x00\x00\x00', + b'\x018821F3301400\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F4203200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F4203300\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F4203500\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F4203700\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F4203800\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + ], + }, + CAR.RAV4_TSS2_2022: { + (Ecu.abs, 0x7b0, None): [ + b'\x01F15260R350\x00\x00\x00\x00\x00\x00', + b'\x01F15260R361\x00\x00\x00\x00\x00\x00', + b'\x01F15264283100\x00\x00\x00\x00', + b'\x01F15264286200\x00\x00\x00\x00', + b'\x01F15264286100\x00\x00\x00\x00', + b'\x01F15264283200\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00', + b'8965B42182\x00\x00\x00\x00\x00\x00', + b'8965B42172\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x01896634AA0000\x00\x00\x00\x00', + b'\x01896634AA0100\x00\x00\x00\x00', + b'\x01896634AA1000\x00\x00\x00\x00', + b'\x01896634A88000\x00\x00\x00\x00', + b'\x01896634A89000\x00\x00\x00\x00', + b'\x01896634A89100\x00\x00\x00\x00', + b'\x01896634A02001\x00\x00\x00\x00', + b'\x01896634A03000\x00\x00\x00\x00', + b'\x01896634A08000\x00\x00\x00\x00', + b'\x01896634A61000\x00\x00\x00\x00', + b'\x01896634A62000\x00\x00\x00\x00', + b'\x01896634A62100\x00\x00\x00\x00', + b'\x01896634A63000\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F0R01100\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00', + ], + }, + CAR.RAV4_TSS2_2023: { + (Ecu.abs, 0x7b0, None): [ + b'\x01F15260R450\x00\x00\x00\x00\x00\x00', + b'\x01F15264283200\x00\x00\x00\x00', + b'\x01F15264283300\x00\x00\x00\x00', + b'\x01F152642F1000\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', + b'8965B42371\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x01896634A88100\x00\x00\x00\x00', + b'\x01896634AJ2000\x00\x00\x00\x00', + b'\x01896634A89100\x00\x00\x00\x00', + b'\x01896634AE1001\x00\x00\x00\x00', + b'\x01896634AF0000\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F0R03100\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0R05100\x00\x00\x00\x008646G0R02100\x00\x00\x00\x00', + b'\x028646F0R05200\x00\x00\x00\x008646G0R02200\x00\x00\x00\x00', + ], + }, + CAR.SIENNA: { + (Ecu.engine, 0x700, None): [ + b'\x01896630832100\x00\x00\x00\x00', + b'\x01896630832200\x00\x00\x00\x00', + b'\x01896630838000\x00\x00\x00\x00', + b'\x01896630838100\x00\x00\x00\x00', + b'\x01896630842000\x00\x00\x00\x00', + b'\x01896630843000\x00\x00\x00\x00', + b'\x01896630851000\x00\x00\x00\x00', + b'\x01896630851100\x00\x00\x00\x00', + b'\x01896630851200\x00\x00\x00\x00', + b'\x01896630852000\x00\x00\x00\x00', + b'\x01896630852100\x00\x00\x00\x00', + b'\x01896630859000\x00\x00\x00\x00', + b'\x01896630860000\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B45070\x00\x00\x00\x00\x00\x00', + b'8965B45080\x00\x00\x00\x00\x00\x00', + b'8965B45082\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152608130\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881510801100\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702100\x00\x00\x00\x00', + b'8821F4702200\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F0801100\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_CTH: { + (Ecu.dsu, 0x791, None): [ + b'881517601100\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152676144\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0237635000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F7601100\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_ES_TSS2: { + (Ecu.engine, 0x700, None): [ + b'\x018966306U6000\x00\x00\x00\x00', + b'\x018966333T5000\x00\x00\x00\x00', + b'\x018966333T5100\x00\x00\x00\x00', + b'\x018966333X6000\x00\x00\x00\x00', + b'\x01896633T07000\x00\x00\x00\x00', + b'\x028966333S8000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x028966333S8000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x028966333T0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x028966333V4000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x028966333W1000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x02896633T09000\x00\x00\x00\x00897CF3307001\x00\x00\x00\x00', + b'\x01896633T38000\x00\x00\x00\x00', + b'\x01896633T58000\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F152606281\x00\x00\x00\x00\x00\x00', + b'\x01F152606340\x00\x00\x00\x00\x00\x00', + b'\x01F152606461\x00\x00\x00\x00\x00\x00', + b'\x01F15260646200\x00\x00\x00\x00', + b'F152633423\x00\x00\x00\x00\x00\x00', + b'F152633680\x00\x00\x00\x00\x00\x00', + b'F152633681\x00\x00\x00\x00\x00\x00', + b'F152633F50\x00\x00\x00\x00\x00\x00', + b'F152633F51\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B33252\x00\x00\x00\x00\x00\x00', + b'8965B33590\x00\x00\x00\x00\x00\x00', + b'8965B33690\x00\x00\x00\x00\x00\x00', + b'8965B33721\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F3301100\x00\x00\x00\x00', + b'\x018821F3301200\x00\x00\x00\x00', + b'\x018821F3301400\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', + b'\x018821F3301300\x00\x00\x00\x00', + b'\x018821F6201400\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F0610000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3303100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F3304200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_ES: { + (Ecu.engine, 0x7e0, None): [ + b'\x02333R0000\x00\x00\x00\x00\x00\x00\x00\x00A0C01000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152606202\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881513309500\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B33502\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4701200\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F3302200\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_ESH: { + (Ecu.engine, 0x7e0, None): [ + b'\x02333M4200\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152633171\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881513310400\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B33512\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4701100\x00\x00\x00\x00', + b'8821F4701300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F3302001\x00\x00\x00\x00', + b'8646F3302200\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_GS_F: { + (Ecu.engine, 0x7E0, None): [ + b'\x0233075200\x00\x00\x00\x00\x00\x00\x00\x00530B9000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152630700\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881513016200\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B30551\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702000\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F3002100\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_NX: { + (Ecu.engine, 0x700, None): [ + b'\x01896637850000\x00\x00\x00\x00', + b'\x01896637851000\x00\x00\x00\x00', + b'\x01896637852000\x00\x00\x00\x00', + b'\x01896637854000\x00\x00\x00\x00', + b'\x01896637878000\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0237841000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0237842000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0237880000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0237882000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0237886000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152678130\x00\x00\x00\x00\x00\x00', + b'F152678140\x00\x00\x00\x00\x00\x00', + b'F152678160\x00\x00\x00\x00\x00\x00', + b'F152678170\x00\x00\x00\x00\x00\x00', + b'F152678171\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881517803100\x00\x00\x00\x00', + b'881517803300\x00\x00\x00\x00', + b'881517804300\x00\x00\x00\x00', + b'881517804100\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B78060\x00\x00\x00\x00\x00\x00', + b'8965B78080\x00\x00\x00\x00\x00\x00', + b'8965B78100\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F7801100\x00\x00\x00\x00', + b'8646F7801300\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_NX_TSS2: { + (Ecu.engine, 0x700, None): [ + b'\x018966378B2100\x00\x00\x00\x00', + b'\x018966378B3000\x00\x00\x00\x00', + b'\x018966378B4100\x00\x00\x00\x00', + b'\x018966378G2000\x00\x00\x00\x00', + b'\x018966378G3000\x00\x00\x00\x00', + b'\x018966378B2000\x00\x00\x00\x00', + b'\x018966378B3100\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0237887000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02378A0000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02378F4000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F152678221\x00\x00\x00\x00\x00\x00', + b'F152678210\x00\x00\x00\x00\x00\x00', + b'F152678211\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B78120\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b"\x018821F3301400\x00\x00\x00\x00", + b'\x018821F3301200\x00\x00\x00\x00', + b'\x018821F3301300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F78030A0\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F7803100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_RC: { + (Ecu.engine, 0x700, None): [ + b'\x01896632461100\x00\x00\x00\x00', + b'\x01896632478200\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152624150\x00\x00\x00\x00\x00\x00', + b'F152624221\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881512404100\x00\x00\x00\x00', + b'881512407000\x00\x00\x00\x00', + b'881512409100\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B24081\x00\x00\x00\x00\x00\x00', + b'8965B24240\x00\x00\x00\x00\x00\x00', + b'8965B24320\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4702300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F2401100\x00\x00\x00\x00', + b'8646F2401200\x00\x00\x00\x00', + b'8646F2402200\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_RX: { + (Ecu.engine, 0x700, None): [ + b'\x01896630E36200\x00\x00\x00\x00', + b'\x01896630E36300\x00\x00\x00\x00', + b'\x01896630E37200\x00\x00\x00\x00', + b'\x01896630E37300\x00\x00\x00\x00', + b'\x01896630E41000\x00\x00\x00\x00', + b'\x01896630E41100\x00\x00\x00\x00', + b'\x01896630E41200\x00\x00\x00\x00', + b'\x01896630E41500\x00\x00\x00\x00', + b'\x01896630EA3100\x00\x00\x00\x00', + b'\x01896630EA3400\x00\x00\x00\x00', + b'\x01896630EA4100\x00\x00\x00\x00', + b'\x01896630EA4300\x00\x00\x00\x00', + b'\x01896630EA4400\x00\x00\x00\x00', + b'\x01896630EA6300\x00\x00\x00\x00', + b'\x018966348R1300\x00\x00\x00\x00', + b'\x018966348R8500\x00\x00\x00\x00', + b'\x018966348W1300\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152648472\x00\x00\x00\x00\x00\x00', + b'F152648473\x00\x00\x00\x00\x00\x00', + b'F152648492\x00\x00\x00\x00\x00\x00', + b'F152648493\x00\x00\x00\x00\x00\x00', + b'F152648474\x00\x00\x00\x00\x00\x00', + b'F152648630\x00\x00\x00\x00\x00\x00', + b'F152648494\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881514810300\x00\x00\x00\x00', + b'881514810500\x00\x00\x00\x00', + b'881514810700\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B0E011\x00\x00\x00\x00\x00\x00', + b'8965B0E012\x00\x00\x00\x00\x00\x00', + b'8965B48102\x00\x00\x00\x00\x00\x00', + b'8965B48111\x00\x00\x00\x00\x00\x00', + b'8965B48112\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4701000\x00\x00\x00\x00', + b'8821F4701100\x00\x00\x00\x00', + b'8821F4701200\x00\x00\x00\x00', + b'8821F4701300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F4801100\x00\x00\x00\x00', + b'8646F4801200\x00\x00\x00\x00', + b'8646F4802001\x00\x00\x00\x00', + b'8646F4802100\x00\x00\x00\x00', + b'8646F4802200\x00\x00\x00\x00', + b'8646F4809000\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_RXH: { + (Ecu.engine, 0x7e0, None): [ + b'\x02348J7000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348N0000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348Q4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348Q4100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348T1100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348T3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348V6000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348Z3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152648361\x00\x00\x00\x00\x00\x00', + b'F152648501\x00\x00\x00\x00\x00\x00', + b'F152648502\x00\x00\x00\x00\x00\x00', + b'F152648504\x00\x00\x00\x00\x00\x00', + b'F152648740\x00\x00\x00\x00\x00\x00', + b'F152648A30\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881514811300\x00\x00\x00\x00', + b'881514811500\x00\x00\x00\x00', + b'881514811700\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B0E011\x00\x00\x00\x00\x00\x00', + b'8965B0E012\x00\x00\x00\x00\x00\x00', + b'8965B48111\x00\x00\x00\x00\x00\x00', + b'8965B48112\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4701000\x00\x00\x00\x00', + b'8821F4701100\x00\x00\x00\x00', + b'8821F4701200\x00\x00\x00\x00', + b'8821F4701300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F4801200\x00\x00\x00\x00', + b'8646F4802001\x00\x00\x00\x00', + b'8646F4802100\x00\x00\x00\x00', + b'8646F4802200\x00\x00\x00\x00', + b'8646F4809000\x00\x00\x00\x00', + ], + }, + CAR.LEXUS_RX_TSS2: { + (Ecu.engine, 0x700, None): [ + b'\x01896630EA9000\x00\x00\x00\x00', + b'\x01896630EB0000\x00\x00\x00\x00', + b'\x01896630EC9000\x00\x00\x00\x00', + b'\x01896630EC9100\x00\x00\x00\x00', + b'\x01896630ED0000\x00\x00\x00\x00', + b'\x01896630ED0100\x00\x00\x00\x00', + b'\x01896630ED6000\x00\x00\x00\x00', + b'\x018966348T8000\x00\x00\x00\x00', + b'\x018966348W5100\x00\x00\x00\x00', + b'\x018966348W9000\x00\x00\x00\x00', + b'\x01896634D12000\x00\x00\x00\x00', + b'\x01896634D12100\x00\x00\x00\x00', + b'\x01896634D43000\x00\x00\x00\x00', + b'\x01896634D44000\x00\x00\x00\x00', + b'\x018966348X0000\x00\x00\x00\x00', + b'\x01896630ED5000\x00\x00\x00\x00', + b'\x018966348R9200\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x02348X4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348X5000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348X8000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348Y3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0234D14000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0234D16000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348U2000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'\x01F15260E031\x00\x00\x00\x00\x00\x00', + b'\x01F15260E041\x00\x00\x00\x00\x00\x00', + b'\x01F152648781\x00\x00\x00\x00\x00\x00', + b'\x01F152648801\x00\x00\x00\x00\x00\x00', + b'F152648831\x00\x00\x00\x00\x00\x00', + b'F152648891\x00\x00\x00\x00\x00\x00', + b'F152648D00\x00\x00\x00\x00\x00\x00', + b'F152648D60\x00\x00\x00\x00\x00\x00', + b'F152648811\x00\x00\x00\x00\x00\x00', + b'F152648C80\x00\x00\x00\x00\x00\x00', + b'F152648493\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B48261\x00\x00\x00\x00\x00\x00', + b'8965B48271\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F3301100\x00\x00\x00\x00', + b'\x018821F3301300\x00\x00\x00\x00', + b'\x018821F3301400\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F4810100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F4810300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F4810400\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + ], + }, + CAR.PRIUS_TSS2: { + (Ecu.engine, 0x700, None): [ + b'\x028966347B1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966347C4000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966347C6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966347C7000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x028966347C8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', + b'\x038966347C1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', + b'\x038966347C5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00', + b'\x038966347C5100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152647500\x00\x00\x00\x00\x00\x00', + b'F152647510\x00\x00\x00\x00\x00\x00', + b'F152647520\x00\x00\x00\x00\x00\x00', + b'F152647521\x00\x00\x00\x00\x00\x00', + b'F152647531\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B47070\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F3301400\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F4707000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F4710000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b'\x028646F4712000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + ], + }, + CAR.MIRAI: { + (Ecu.abs, 0x7D1, None): [b'\x01898A36203000\x00\x00\x00\x00',], + (Ecu.abs, 0x7B0, None): [ # a second ABS ECU + b'\x01F15266203200\x00\x00\x00\x00', + b'\x01F15266203500\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7A1, None): [b'\x028965B6204100\x00\x00\x00\x008965B6203100\x00\x00\x00\x00',], + (Ecu.fwdRadar, 0x750, 0xf): [b'\x018821F6201200\x00\x00\x00\x00',], + (Ecu.fwdCamera, 0x750, 0x6d): [b'\x028646F6201400\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',], + }, + CAR.ALPHARD_TSS2: { + (Ecu.engine, 0x7e0, None): [ + b'\x0235870000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0235883000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B58040\x00\x00\x00\x00\x00\x00', + b'8965B58052\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152658341\x00\x00\x00\x00\x00\x00' + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F3301200\x00\x00\x00\x00', + b'\x018821F3301400\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F58010C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F5803200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646FV201000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + ], + }, +} diff --git a/selfdrive/car/toyota/tests/print_platform_codes.py b/selfdrive/car/toyota/tests/print_platform_codes.py index 636a013242..9ec7a14cd3 100755 --- a/selfdrive/car/toyota/tests/print_platform_codes.py +++ b/selfdrive/car/toyota/tests/print_platform_codes.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 from collections import defaultdict from cereal import car -from openpilot.selfdrive.car.toyota.values import FW_VERSIONS, PLATFORM_CODE_ECUS, get_platform_codes +from openpilot.selfdrive.car.toyota.values import PLATFORM_CODE_ECUS, get_platform_codes +from openpilot.selfdrive.car.toyota.fingerprints import FW_VERSIONS Ecu = car.CarParams.Ecu ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py index eae59a861f..f4bf18dc5b 100755 --- a/selfdrive/car/toyota/tests/test_toyota.py +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -4,7 +4,8 @@ import unittest from cereal import car from openpilot.selfdrive.car.fw_versions import build_fw_dict -from openpilot.selfdrive.car.toyota.values import CAR, DBC, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, FW_VERSIONS, \ +from openpilot.selfdrive.car.toyota.fingerprints import FW_VERSIONS +from openpilot.selfdrive.car.toyota.values import CAR, DBC, TSS2_CAR, ANGLE_CONTROL_CAR, RADAR_ACC_CAR, \ FW_QUERY_CONFIG, PLATFORM_CODE_ECUS, FUZZY_EXCLUDED_PLATFORMS, \ get_platform_codes @@ -138,7 +139,7 @@ class TestToyotaFingerprint(unittest.TestCase): "subAddress": 0 if sub_addr is None else sub_addr}) CP = car.CarParams.new_message(carFw=car_fw) - matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw)) + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), FW_VERSIONS) if len(matches) == 1: self.assertEqual(list(matches)[0], platform) else: diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 10702269d6..a087e25934 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -296,10 +296,10 @@ def get_platform_codes(fw_versions: List[bytes]) -> Dict[bytes, Set[bytes]]: return dict(codes) -def match_fw_to_car_fuzzy(live_fw_versions) -> Set[str]: +def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> Set[str]: candidates = set() - for candidate, fws in FW_VERSIONS.items(): + for candidate, fws in offline_fw_versions.items(): # Keep track of ECUs which pass all checks (platform codes, within sub-version range) valid_found_ecus = set() valid_expected_ecus = {ecu[1:] for ecu in fws if ecu[0] in PLATFORM_CODE_ECUS} @@ -433,1642 +433,6 @@ FW_QUERY_CONFIG = FwQueryConfig( match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, ) -FW_VERSIONS = { - CAR.AVALON: { - (Ecu.abs, 0x7b0, None): [ - b'F152607060\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881510701300\x00\x00\x00\x00', - b'881510705100\x00\x00\x00\x00', - b'881510705200\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B41051\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0230721100\x00\x00\x00\x00\x00\x00\x00\x00A0C01000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230721200\x00\x00\x00\x00\x00\x00\x00\x00A0C01000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702000\x00\x00\x00\x00', - b'8821F4702100\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0701100\x00\x00\x00\x00', - b'8646F0703000\x00\x00\x00\x00', - ], - }, - CAR.AVALON_2019: { - (Ecu.abs, 0x7b0, None): [ - b'F152607140\x00\x00\x00\x00\x00\x00', - b'F152607171\x00\x00\x00\x00\x00\x00', - b'F152607110\x00\x00\x00\x00\x00\x00', - b'F152607180\x00\x00\x00\x00\x00\x00', - b'F152641040\x00\x00\x00\x00\x00\x00', - b'F152641061\x00\x00\x00\x00\x00\x00', - b'F152641050\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881510703200\x00\x00\x00\x00', - b'881510704200\x00\x00\x00\x00', - b'881514107100\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B41080\x00\x00\x00\x00\x00\x00', - b'8965B07010\x00\x00\x00\x00\x00\x00', - b'8965B41090\x00\x00\x00\x00\x00\x00', - b'8965B41070\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x01896630725200\x00\x00\x00\x00', - b'\x01896630725300\x00\x00\x00\x00', - b'\x01896630735100\x00\x00\x00\x00', - b'\x01896630738000\x00\x00\x00\x00', - b'\x02896630724000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x02896630737000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x02896630728000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0702100\x00\x00\x00\x00', - ], - }, - CAR.AVALON_TSS2: { - (Ecu.abs, 0x7b0, None): [ - b'\x01F152607240\x00\x00\x00\x00\x00\x00', - b'\x01F152607250\x00\x00\x00\x00\x00\x00', - b'\x01F152607280\x00\x00\x00\x00\x00\x00', - b'F152641080\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B41110\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x01896630742000\x00\x00\x00\x00', - b'\x01896630743000\x00\x00\x00\x00', - b'\x018966306Q6000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F6201200\x00\x00\x00\x00', - b'\x018821F6201300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - ], - }, - CAR.CAMRY: { - (Ecu.engine, 0x700, None): [ - b'\x018966306L3100\x00\x00\x00\x00', - b'\x018966306L4200\x00\x00\x00\x00', - b'\x018966306L5200\x00\x00\x00\x00', - b'\x018966306P8000\x00\x00\x00\x00', - b'\x018966306Q3100\x00\x00\x00\x00', - b'\x018966306Q4000\x00\x00\x00\x00', - b'\x018966306Q4100\x00\x00\x00\x00', - b'\x018966306Q4200\x00\x00\x00\x00', - b'\x018966333Q9200\x00\x00\x00\x00', - b'\x018966333P3100\x00\x00\x00\x00', - b'\x018966333P3200\x00\x00\x00\x00', - b'\x018966333P4200\x00\x00\x00\x00', - b'\x018966333P4300\x00\x00\x00\x00', - b'\x018966333P4400\x00\x00\x00\x00', - b'\x018966333P4500\x00\x00\x00\x00', - b'\x018966333P4700\x00\x00\x00\x00', - b'\x018966333P4900\x00\x00\x00\x00', - b'\x018966333Q6000\x00\x00\x00\x00', - b'\x018966333Q6200\x00\x00\x00\x00', - b'\x018966333Q6300\x00\x00\x00\x00', - b'\x018966333Q6500\x00\x00\x00\x00', - b'\x018966333W6000\x00\x00\x00\x00', - b'\x018966306Q6000\x00\x00\x00\x00', - b'\x018966333N1100\x00\x00\x00\x00', - b'\x018966333N4300\x00\x00\x00\x00', - b'\x018966333X0000\x00\x00\x00\x00', - b'\x018966333X4000\x00\x00\x00\x00', - b'\x01896633T16000\x00\x00\x00\x00', - b'\x018966306L9000\x00\x00\x00\x00', - b'\x028966306B2100\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306B2300\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306B2500\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306N8100\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306N8200\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306N8300\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306N8400\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306R5000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306R5000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x028966306R6000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966306R6000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x028966306S0000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x028966306S0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x028966306S1100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x02333P1100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'8821F0601200 ', - b'8821F0601300 ', - b'8821F0602000 ', - b'8821F0603300 ', - b'8821F0604100 ', - b'8821F0605200 ', - b'8821F0607200 ', - b'8821F0608000 ', - b'8821F0608200 ', - b'8821F0609100 ', - b'8821F0603400 ', - b'8821F0604000 ', - b'8821F0604200 ', - b'8821F0606200 ', - b'8821F0609000 ', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152606210\x00\x00\x00\x00\x00\x00', - b'F152606230\x00\x00\x00\x00\x00\x00', - b'F152606270\x00\x00\x00\x00\x00\x00', - b'F152606290\x00\x00\x00\x00\x00\x00', - b'F152606410\x00\x00\x00\x00\x00\x00', - b'F152633540\x00\x00\x00\x00\x00\x00', - b'F152633A10\x00\x00\x00\x00\x00\x00', - b'F152633A20\x00\x00\x00\x00\x00\x00', - b'F152633214\x00\x00\x00\x00\x00\x00', - b'F152633660\x00\x00\x00\x00\x00\x00', - b'F152633712\x00\x00\x00\x00\x00\x00', - b'F152633713\x00\x00\x00\x00\x00\x00', - b'F152633B51\x00\x00\x00\x00\x00\x00', - b'F152633B60\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B33540\x00\x00\x00\x00\x00\x00', - b'8965B33542\x00\x00\x00\x00\x00\x00', - b'8965B33580\x00\x00\x00\x00\x00\x00', - b'8965B33581\x00\x00\x00\x00\x00\x00', - b'8965B33621\x00\x00\x00\x00\x00\x00', - b'8965B33550\x00\x00\x00\x00\x00\x00', - b'8965B33551\x00\x00\x00\x00\x00\x00', - b'8965B33611\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ # Same as 0x791 - b'8821F0601200 ', - b'8821F0601300 ', - b'8821F0602000 ', - b'8821F0603300 ', - b'8821F0604100 ', - b'8821F0605200 ', - b'8821F0607200 ', - b'8821F0608000 ', - b'8821F0608200 ', - b'8821F0609100 ', - b'8821F0603400 ', - b'8821F0604000 ', - b'8821F0604200 ', - b'8821F0606200 ', - b'8821F0609000 ', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0601200 ', - b'8646F0601300 ', - b'8646F0601400 ', - b'8646F0603400 ', - b'8646F0604100 ', - b'8646F0605000 ', - b'8646F0606000 ', - b'8646F0606100 ', - b'8646F0607100 ', - b'8646F0603500 ', - b'8646F0607000 ', - ], - }, - CAR.CAMRY_TSS2: { - (Ecu.eps, 0x7a1, None): [ - b'8965B33630\x00\x00\x00\x00\x00\x00', - b'8965B33640\x00\x00\x00\x00\x00\x00', - b'8965B33650\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F152606370\x00\x00\x00\x00\x00\x00', - b'\x01F152606390\x00\x00\x00\x00\x00\x00', - b'\x01F152606400\x00\x00\x00\x00\x00\x00', - b'\x01F152606431\x00\x00\x00\x00\x00\x00', - b'F152633D00\x00\x00\x00\x00\x00\x00', - b'F152633D60\x00\x00\x00\x00\x00\x00', - b'F152633310\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x018966306Q5000\x00\x00\x00\x00', - b'\x018966306Q9000\x00\x00\x00\x00', - b'\x018966306R3000\x00\x00\x00\x00', - b'\x018966306R8000\x00\x00\x00\x00', - b'\x018966306T3100\x00\x00\x00\x00', - b'\x018966306T3200\x00\x00\x00\x00', - b'\x018966306T4000\x00\x00\x00\x00', - b'\x018966306T4100\x00\x00\x00\x00', - b'\x018966306Q6000\x00\x00\x00\x00', - b'\x018966306Q7000\x00\x00\x00\x00', - b'\x018966306T0000\x00\x00\x00\x00', - b'\x018966306V1000\x00\x00\x00\x00', - b'\x01896633T20000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F6201200\x00\x00\x00\x00', - b'\x018821F6201300\x00\x00\x00\x00', - b'\x018821F6201400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F0602100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F0602200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F0602300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F3305200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - ], - }, - CAR.CHR: { - (Ecu.engine, 0x700, None): [ - b'\x01896631021100\x00\x00\x00\x00', - b'\x01896631017100\x00\x00\x00\x00', - b'\x01896631017200\x00\x00\x00\x00', - b'\x0189663F413100\x00\x00\x00\x00', - b'\x0189663F414100\x00\x00\x00\x00', - b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896631013200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x0289663F405000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x0289663F418000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x0289663F423000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x0289663F431000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x0189663F438000\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'8821F0W01000 ', - b'8821F0W01100 ', - b'8821FF401600 ', - b'8821FF404000 ', - b'8821FF404100 ', - b'8821FF405100 ', - b'8821FF406000 ', - b'8821FF407100 ', - b'8821FF402300 ', - b'8821FF402400 ', - b'8821FF405000 ', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152610020\x00\x00\x00\x00\x00\x00', - b'F152610153\x00\x00\x00\x00\x00\x00', - b'F152610210\x00\x00\x00\x00\x00\x00', - b'F1526F4034\x00\x00\x00\x00\x00\x00', - b'F1526F4044\x00\x00\x00\x00\x00\x00', - b'F1526F4073\x00\x00\x00\x00\x00\x00', - b'F1526F4121\x00\x00\x00\x00\x00\x00', - b'F1526F4122\x00\x00\x00\x00\x00\x00', - b'F152610012\x00\x00\x00\x00\x00\x00', - b'F152610013\x00\x00\x00\x00\x00\x00', - b'F152610014\x00\x00\x00\x00\x00\x00', - b'F152610040\x00\x00\x00\x00\x00\x00', - b'F152610190\x00\x00\x00\x00\x00\x00', - b'F152610200\x00\x00\x00\x00\x00\x00', - b'F152610220\x00\x00\x00\x00\x00\x00', - b'F152610230\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B10011\x00\x00\x00\x00\x00\x00', - b'8965B10040\x00\x00\x00\x00\x00\x00', - b'8965B10070\x00\x00\x00\x00\x00\x00', - b'8965B10020\x00\x00\x00\x00\x00\x00', - b'8965B10050\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0331024000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', - b'\x0331024000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', - b'\x0331036000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', - b'\x033F401100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203102\x00\x00\x00\x00', - b'\x033F401200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', - b'\x033F424000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', - b'\x033F424000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F0W01000 ', - b'8821FF401600 ', - b'8821FF404000 ', - b'8821FF404100 ', - b'8821FF405100 ', - b'8821FF406000 ', - b'8821FF407100 ', - b'8821F0W01100 ', - b'8821FF402300 ', - b'8821FF402400 ', - b'8821FF405000 ', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646FF401700 ', - b'8646FF401800 ', - b'8646FF404000 ', - b'8646FF406000 ', - b'8646FF407000 ', - b'8646FF402100 ', - b'8646FF407100 ', - ], - }, - CAR.CHR_TSS2: { - (Ecu.abs, 0x7b0, None): [ - b'F152610260\x00\x00\x00\x00\x00\x00', - b'F1526F4270\x00\x00\x00\x00\x00\x00', - b'F152610041\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B10091\x00\x00\x00\x00\x00\x00', - b'8965B10110\x00\x00\x00\x00\x00\x00', - b'8965B10092\x00\x00\x00\x00\x00\x00', - b'8965B10111\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x0189663F459000\x00\x00\x00\x00', - b'\x0189663F438000\x00\x00\x00\x00', - b'\x02896631025000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x0289663F453000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0331014000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821FF410200\x00\x00\x00\x00', - b'\x018821FF410300\x00\x00\x00\x00', - b'\x018821FF410500\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646FF410200\x00\x00\x00\x008646GF408200\x00\x00\x00\x00', - b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00', - b'\x028646FF413100\x00\x00\x00\x008646GF411100\x00\x00\x00\x00', - ], - }, - CAR.COROLLA: { - (Ecu.engine, 0x7e0, None): [ - b'\x0230ZC2000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZC2100\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZC2200\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZC2300\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZC3000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZC3100\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZC3200\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZC3300\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0330ZC1200\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881510201100\x00\x00\x00\x00', - b'881510201200\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152602190\x00\x00\x00\x00\x00\x00', - b'F152602191\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B02181\x00\x00\x00\x00\x00\x00', - b'8965B02191\x00\x00\x00\x00\x00\x00', - b'8965B48150\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0201101\x00\x00\x00\x00', - b'8646F0201200\x00\x00\x00\x00', - ], - }, - CAR.COROLLA_TSS2: { - (Ecu.engine, 0x700, None): [ - b'\x01896630A22000\x00\x00\x00\x00', - b'\x01896630ZG2000\x00\x00\x00\x00', - b'\x01896630ZG5000\x00\x00\x00\x00', - b'\x01896630ZG5100\x00\x00\x00\x00', - b'\x01896630ZG5200\x00\x00\x00\x00', - b'\x01896630ZG5300\x00\x00\x00\x00', - b'\x01896630ZP1000\x00\x00\x00\x00', - b'\x01896630ZP2000\x00\x00\x00\x00', - b'\x01896630ZQ5000\x00\x00\x00\x00', - b'\x01896630ZU9000\x00\x00\x00\x00', - b'\x01896630ZX4000\x00\x00\x00\x00', - b'\x018966312L8000\x00\x00\x00\x00', - b'\x018966312M0000\x00\x00\x00\x00', - b'\x018966312M9000\x00\x00\x00\x00', - b'\x018966312P9000\x00\x00\x00\x00', - b'\x018966312P9100\x00\x00\x00\x00', - b'\x018966312P9200\x00\x00\x00\x00', - b'\x018966312P9300\x00\x00\x00\x00', - b'\x018966312Q2300\x00\x00\x00\x00', - b'\x018966312Q8000\x00\x00\x00\x00', - b'\x018966312R0000\x00\x00\x00\x00', - b'\x018966312R0100\x00\x00\x00\x00', - b'\x018966312R0200\x00\x00\x00\x00', - b'\x018966312R1000\x00\x00\x00\x00', - b'\x018966312R1100\x00\x00\x00\x00', - b'\x018966312R3100\x00\x00\x00\x00', - b'\x018966312S5000\x00\x00\x00\x00', - b'\x018966312S7000\x00\x00\x00\x00', - b'\x018966312W3000\x00\x00\x00\x00', - b'\x018966312W9000\x00\x00\x00\x00', - b'\x01896637644000\x00\x00\x00\x00', - b'\x01896630ZJ1000\x00\x00\x00\x00', - b'\x01896630ZU8000\x00\x00\x00\x00', - b'\x01896637621000\x00\x00\x00\x00', - b'\x01896637623000\x00\x00\x00\x00', - b'\x01896637624000\x00\x00\x00\x00', - b'\x01896637626000\x00\x00\x00\x00', - b'\x01896637639000\x00\x00\x00\x00', - b'\x01896637648000\x00\x00\x00\x00', - b'\x01896637643000\x00\x00\x00\x00', - b'\x02896630A07000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630A21000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630ZJ5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630ZK8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630ZN8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630ZQ3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630ZR2000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630ZT8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630ZT9000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896630ZZ0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966312K6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966312L0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966312Q3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966312Q3100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966312Q4000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x038966312L7000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF1205001\x00\x00\x00\x00', - b'\x038966312N1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', - b'\x038966312T3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF1205001\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0230A10000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230A11000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZN4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230ZN5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x03312K7000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', - b'\x03312M3000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', - b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', - b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', - b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', - b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', - b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', - b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', - b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', - b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', - b'\x02312K4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02312U5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'\x018965B12350\x00\x00\x00\x00\x00\x00', - b'\x018965B12470\x00\x00\x00\x00\x00\x00', - b'\x018965B12490\x00\x00\x00\x00\x00\x00', - b'\x018965B12500\x00\x00\x00\x00\x00\x00', - b'\x018965B12520\x00\x00\x00\x00\x00\x00', - b'\x018965B12530\x00\x00\x00\x00\x00\x00', - b'\x018965B1255000\x00\x00\x00\x00', - b'8965B12361\x00\x00\x00\x00\x00\x00', - b'8965B16011\x00\x00\x00\x00\x00\x00', - b'8965B76012\x00\x00\x00\x00\x00\x00', - b'\x018965B12510\x00\x00\x00\x00\x00\x00', - b'\x018965B1256000\x00\x00\x00\x00', - b'8965B12451\x00\x00\x00\x00\x00\x00', - b'8965B16101\x00\x00\x00\x00\x00\x00', - b'8965B16170\x00\x00\x00\x00\x00\x00', - b'8965B76050\x00\x00\x00\x00\x00\x00', - b'8965B76091\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F152602280\x00\x00\x00\x00\x00\x00', - b'\x01F152602560\x00\x00\x00\x00\x00\x00', - b'\x01F152602590\x00\x00\x00\x00\x00\x00', - b'\x01F152602650\x00\x00\x00\x00\x00\x00', - b'\x01F15260A010\x00\x00\x00\x00\x00\x00', - b'\x01F15260A050\x00\x00\x00\x00\x00\x00', - b'\x01F152612641\x00\x00\x00\x00\x00\x00', - b'\x01F152612651\x00\x00\x00\x00\x00\x00', - b'\x01F152612B10\x00\x00\x00\x00\x00\x00', - b'\x01F152612B51\x00\x00\x00\x00\x00\x00', - b'\x01F152612B60\x00\x00\x00\x00\x00\x00', - b'\x01F152612B61\x00\x00\x00\x00\x00\x00', - b'\x01F152612B62\x00\x00\x00\x00\x00\x00', - b'\x01F152612B70\x00\x00\x00\x00\x00\x00', - b'\x01F152612B71\x00\x00\x00\x00\x00\x00', - b'\x01F152612B81\x00\x00\x00\x00\x00\x00', - b'\x01F152612B90\x00\x00\x00\x00\x00\x00', - b'\x01F152612C00\x00\x00\x00\x00\x00\x00', - b'\x01F152612862\x00\x00\x00\x00\x00\x00', - b'\x01F152612B91\x00\x00\x00\x00\x00\x00', - b'\x01F15260A070\x00\x00\x00\x00\x00\x00', - b'\x01F152676250\x00\x00\x00\x00\x00\x00', - b'\x01F152602470\x00\x00\x00\x00\x00\x00', - b'F152612590\x00\x00\x00\x00\x00\x00', - b'F152612691\x00\x00\x00\x00\x00\x00', - b'F152612692\x00\x00\x00\x00\x00\x00', - b'F152612700\x00\x00\x00\x00\x00\x00', - b'F152612710\x00\x00\x00\x00\x00\x00', - b'F152612790\x00\x00\x00\x00\x00\x00', - b'F152612800\x00\x00\x00\x00\x00\x00', - b'F152612820\x00\x00\x00\x00\x00\x00', - b'F152612840\x00\x00\x00\x00\x00\x00', - b'F152612842\x00\x00\x00\x00\x00\x00', - b'F152612890\x00\x00\x00\x00\x00\x00', - b'F152612A00\x00\x00\x00\x00\x00\x00', - b'F152612A10\x00\x00\x00\x00\x00\x00', - b'F152612D00\x00\x00\x00\x00\x00\x00', - b'F152616011\x00\x00\x00\x00\x00\x00', - b'F152616060\x00\x00\x00\x00\x00\x00', - b'F152616030\x00\x00\x00\x00\x00\x00', - b'F152642540\x00\x00\x00\x00\x00\x00', - b'F152676293\x00\x00\x00\x00\x00\x00', - b'F152676303\x00\x00\x00\x00\x00\x00', - b'F152676304\x00\x00\x00\x00\x00\x00', - b'F152676371\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301100\x00\x00\x00\x00', - b'\x018821F3301200\x00\x00\x00\x00', - b'\x018821F3301300\x00\x00\x00\x00', - b'\x018821F3301400\x00\x00\x00\x00', - b'\x018821F6201400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F12010D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F1201100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F1201200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F1201300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F1201400\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', - b'\x028646F1202000\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', - b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F1601200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F7603100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F7603200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F7605100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - ], - }, - CAR.HIGHLANDER: { - (Ecu.engine, 0x700, None): [ - b'\x01896630E09000\x00\x00\x00\x00', - b'\x01896630E43000\x00\x00\x00\x00', - b'\x01896630E43100\x00\x00\x00\x00', - b'\x01896630E43200\x00\x00\x00\x00', - b'\x01896630E44200\x00\x00\x00\x00', - b'\x01896630E45000\x00\x00\x00\x00', - b'\x01896630E45100\x00\x00\x00\x00', - b'\x01896630E45200\x00\x00\x00\x00', - b'\x01896630E46000\x00\x00\x00\x00', - b'\x01896630E46200\x00\x00\x00\x00', - b'\x01896630E74000\x00\x00\x00\x00', - b'\x01896630E75000\x00\x00\x00\x00', - b'\x01896630E76000\x00\x00\x00\x00', - b'\x01896630E77000\x00\x00\x00\x00', - b'\x01896630E83000\x00\x00\x00\x00', - b'\x01896630E84000\x00\x00\x00\x00', - b'\x01896630E85000\x00\x00\x00\x00', - b'\x01896630E86000\x00\x00\x00\x00', - b'\x01896630E88000\x00\x00\x00\x00', - b'\x01896630EA0000\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B48140\x00\x00\x00\x00\x00\x00', - b'8965B48150\x00\x00\x00\x00\x00\x00', - b'8965B48210\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [b'F15260E011\x00\x00\x00\x00\x00\x00'], - (Ecu.dsu, 0x791, None): [ - b'881510E01100\x00\x00\x00\x00', - b'881510E01200\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0E01200\x00\x00\x00\x00', - b'8646F0E01300\x00\x00\x00\x00', - ], - }, - CAR.HIGHLANDERH: { - (Ecu.eps, 0x7a1, None): [ - b'8965B48160\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152648541\x00\x00\x00\x00\x00\x00', - b'F152648542\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0230E40000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230E40100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230EA2000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0230EA2100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0E01200\x00\x00\x00\x00', - b'8646F0E01300\x00\x00\x00\x00', - ], - }, - CAR.HIGHLANDER_TSS2: { - (Ecu.eps, 0x7a1, None): [ - b'8965B48241\x00\x00\x00\x00\x00\x00', - b'8965B48310\x00\x00\x00\x00\x00\x00', - b'8965B48320\x00\x00\x00\x00\x00\x00', - b'8965B48400\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F15260E051\x00\x00\x00\x00\x00\x00', - b'\x01F15260E061\x00\x00\x00\x00\x00\x00', - b'\x01F15260E110\x00\x00\x00\x00\x00\x00', - b'\x01F15260E170\x00\x00\x00\x00\x00\x00', - b'\x01F15260E05300\x00\x00\x00\x00', - b'\x01F15264872300\x00\x00\x00\x00', - b'\x01F15264872400\x00\x00\x00\x00', - b'\x01F15264872500\x00\x00\x00\x00', - b'\x01F15264872600\x00\x00\x00\x00', - b'\x01F15264872700\x00\x00\x00\x00', - b'\x01F15264873500\x00\x00\x00\x00', - b'\x01F152648C6300\x00\x00\x00\x00', - b'\x01F152648J4000\x00\x00\x00\x00', - b'\x01F152648J5000\x00\x00\x00\x00', - b'\x01F152648J6000\x00\x00\x00\x00', - b'\x01F152648J7000\x00\x00\x00\x00', - b'\x01F152648L5000\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x01896630E62100\x00\x00\x00\x00', - b'\x01896630E62200\x00\x00\x00\x00', - b'\x01896630E64100\x00\x00\x00\x00', - b'\x01896630E64200\x00\x00\x00\x00', - b'\x01896630E64400\x00\x00\x00\x00', - b'\x01896630EB1000\x00\x00\x00\x00', - b'\x01896630EB1100\x00\x00\x00\x00', - b'\x01896630EB1200\x00\x00\x00\x00', - b'\x01896630EB2000\x00\x00\x00\x00', - b'\x01896630EB2100\x00\x00\x00\x00', - b'\x01896630EB2200\x00\x00\x00\x00', - b'\x01896630EC4000\x00\x00\x00\x00', - b'\x01896630ED9000\x00\x00\x00\x00', - b'\x01896630ED9100\x00\x00\x00\x00', - b'\x01896630EE1000\x00\x00\x00\x00', - b'\x01896630EE1100\x00\x00\x00\x00', - b'\x01896630EG3000\x00\x00\x00\x00', - b'\x01896630EG5000\x00\x00\x00\x00', - b'\x01896630E67000\x00\x00\x00\x00', - b'\x01896630EA1000\x00\x00\x00\x00', - b'\x01896630EE4000\x00\x00\x00\x00', - b'\x01896630EE4100\x00\x00\x00\x00', - b'\x01896630EE5000\x00\x00\x00\x00', - b'\x01896630EE6000\x00\x00\x00\x00', - b'\x01896630EE7000\x00\x00\x00\x00', - b'\x01896630EF8000\x00\x00\x00\x00', - b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - b'\x02896630EB3000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - b'\x02896630EB3100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301400\x00\x00\x00\x00', - b'\x018821F6201200\x00\x00\x00\x00', - b'\x018821F6201300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_IS: { - (Ecu.engine, 0x700, None): [ - b'\x018966353M7000\x00\x00\x00\x00', - b'\x018966353M7100\x00\x00\x00\x00', - b'\x018966353Q2000\x00\x00\x00\x00', - b'\x018966353Q2300\x00\x00\x00\x00', - b'\x018966353Q4000\x00\x00\x00\x00', - b'\x018966353R1100\x00\x00\x00\x00', - b'\x018966353R7100\x00\x00\x00\x00', - b'\x018966353R8100\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0232480000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02353P7000\x00\x00\x00\x00\x00\x00\x00\x00530J5000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02353P9000\x00\x00\x00\x00\x00\x00\x00\x00553C1000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152653300\x00\x00\x00\x00\x00\x00', - b'F152653301\x00\x00\x00\x00\x00\x00', - b'F152653310\x00\x00\x00\x00\x00\x00', - b'F152653330\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881515306200\x00\x00\x00\x00', - b'881515306400\x00\x00\x00\x00', - b'881515306500\x00\x00\x00\x00', - b'881515307400\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B53270\x00\x00\x00\x00\x00\x00', - b'8965B53271\x00\x00\x00\x00\x00\x00', - b'8965B53280\x00\x00\x00\x00\x00\x00', - b'8965B53281\x00\x00\x00\x00\x00\x00', - b'8965B53311\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702300\x00\x00\x00\x00', - b'8821F4702100\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F5301101\x00\x00\x00\x00', - b'8646F5301200\x00\x00\x00\x00', - b'8646F5301300\x00\x00\x00\x00', - b'8646F5301400\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_IS_TSS2: { - (Ecu.engine, 0x700, None): [ - b'\x018966353S1000\x00\x00\x00\x00', - b'\x018966353S2000\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F15265337200\x00\x00\x00\x00', - b'\x01F15265342000\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B53450\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F6201200\x00\x00\x00\x00', - b'\x018821F6201300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F5303300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', - b'\x028646F5303400\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - ], - }, - CAR.PRIUS: { - (Ecu.engine, 0x700, None): [ - b'\x02896634761000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634761100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634761200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634762000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634763000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634763100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634765000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634765100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634769000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634769100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634769200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634770000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634774000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634774100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634774200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634782000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x02896634784000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966347A0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966347A5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966347A8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966347B0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x03896634759100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', - b'\x03896634759200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', - b'\x03896634759200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', - b'\x03896634759300\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', - b'\x03896634760000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701002\x00\x00\x00\x00', - b'\x03896634760000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', - b'\x03896634760000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', - b'\x03896634760100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', - b'\x03896634760200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', - b'\x03896634760200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', - b'\x03896634760300\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', - b'\x03896634768000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4703001\x00\x00\x00\x00', - b'\x03896634768000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4703002\x00\x00\x00\x00', - b'\x03896634768100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4703002\x00\x00\x00\x00', - b'\x03896634785000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4705001\x00\x00\x00\x00', - b'\x03896634785000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710001\x00\x00\x00\x00', - b'\x03896634786000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4705001\x00\x00\x00\x00', - b'\x03896634786000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710001\x00\x00\x00\x00', - b'\x03896634789000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4703002\x00\x00\x00\x00', - b'\x038966347A3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', - b'\x038966347A3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707001\x00\x00\x00\x00', - b'\x038966347B6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710001\x00\x00\x00\x00', - b'\x038966347B7000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710001\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B47021\x00\x00\x00\x00\x00\x00', - b'8965B47022\x00\x00\x00\x00\x00\x00', - b'8965B47023\x00\x00\x00\x00\x00\x00', - b'8965B47050\x00\x00\x00\x00\x00\x00', - b'8965B47060\x00\x00\x00\x00\x00\x00', # This is the EPS with good angle sensor - ], - (Ecu.abs, 0x7b0, None): [ - b'F152647290\x00\x00\x00\x00\x00\x00', - b'F152647300\x00\x00\x00\x00\x00\x00', - b'F152647310\x00\x00\x00\x00\x00\x00', - b'F152647414\x00\x00\x00\x00\x00\x00', - b'F152647415\x00\x00\x00\x00\x00\x00', - b'F152647416\x00\x00\x00\x00\x00\x00', - b'F152647417\x00\x00\x00\x00\x00\x00', - b'F152647470\x00\x00\x00\x00\x00\x00', - b'F152647490\x00\x00\x00\x00\x00\x00', - b'F152647682\x00\x00\x00\x00\x00\x00', - b'F152647683\x00\x00\x00\x00\x00\x00', - b'F152647684\x00\x00\x00\x00\x00\x00', - b'F152647862\x00\x00\x00\x00\x00\x00', - b'F152647863\x00\x00\x00\x00\x00\x00', - b'F152647864\x00\x00\x00\x00\x00\x00', - b'F152647865\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881514702300\x00\x00\x00\x00', - b'881514702400\x00\x00\x00\x00', - b'881514703100\x00\x00\x00\x00', - b'881514704100\x00\x00\x00\x00', - b'881514706000\x00\x00\x00\x00', - b'881514706100\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702000\x00\x00\x00\x00', - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F4701300\x00\x00\x00\x00', - b'8646F4702001\x00\x00\x00\x00', - b'8646F4702100\x00\x00\x00\x00', - b'8646F4702200\x00\x00\x00\x00', - b'8646F4705000\x00\x00\x00\x00', - b'8646F4705200\x00\x00\x00\x00', - ], - }, - CAR.PRIUS_V: { - (Ecu.abs, 0x7b0, None): [ - b'F152647280\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0234781000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881514705100\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F4703300\x00\x00\x00\x00', - ], - }, - CAR.RAV4: { - (Ecu.engine, 0x7e0, None): [ - b'\x02342Q1000\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342Q1100\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342Q1200\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342Q1300\x00\x00\x00\x00\x00\x00\x00\x0054212000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342Q2000\x00\x00\x00\x00\x00\x00\x00\x0054213000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342Q2100\x00\x00\x00\x00\x00\x00\x00\x0054213000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342Q2200\x00\x00\x00\x00\x00\x00\x00\x0054213000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342Q4000\x00\x00\x00\x00\x00\x00\x00\x0054215000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B42063\x00\x00\x00\x00\x00\x00', - b'8965B42073\x00\x00\x00\x00\x00\x00', - b'8965B42082\x00\x00\x00\x00\x00\x00', - b'8965B42083\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F15260R102\x00\x00\x00\x00\x00\x00', - b'F15260R103\x00\x00\x00\x00\x00\x00', - b'F152642493\x00\x00\x00\x00\x00\x00', - b'F152642492\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881514201200\x00\x00\x00\x00', - b'881514201300\x00\x00\x00\x00', - b'881514201400\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702000\x00\x00\x00\x00', - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F4201100\x00\x00\x00\x00', - b'8646F4201200\x00\x00\x00\x00', - b'8646F4202001\x00\x00\x00\x00', - b'8646F4202100\x00\x00\x00\x00', - b'8646F4204000\x00\x00\x00\x00', - ], - }, - CAR.RAV4H: { - (Ecu.engine, 0x7e0, None): [ - b'\x02342N9000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342N9100\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02342P0000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B42102\x00\x00\x00\x00\x00\x00', - b'8965B42103\x00\x00\x00\x00\x00\x00', - b'8965B42112\x00\x00\x00\x00\x00\x00', - b'8965B42162\x00\x00\x00\x00\x00\x00', - b'8965B42163\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152642090\x00\x00\x00\x00\x00\x00', - b'F152642110\x00\x00\x00\x00\x00\x00', - b'F152642120\x00\x00\x00\x00\x00\x00', - b'F152642400\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881514202200\x00\x00\x00\x00', - b'881514202300\x00\x00\x00\x00', - b'881514202400\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702000\x00\x00\x00\x00', - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F4201100\x00\x00\x00\x00', - b'8646F4201200\x00\x00\x00\x00', - b'8646F4202001\x00\x00\x00\x00', - b'8646F4202100\x00\x00\x00\x00', - b'8646F4204000\x00\x00\x00\x00', - ], - }, - CAR.RAV4_TSS2: { - (Ecu.engine, 0x700, None): [ - b'\x01896630R58000\x00\x00\x00\x00', - b'\x01896630R58100\x00\x00\x00\x00', - b'\x018966342E2000\x00\x00\x00\x00', - b'\x018966342M8000\x00\x00\x00\x00', - b'\x018966342S9000\x00\x00\x00\x00', - b'\x018966342T1000\x00\x00\x00\x00', - b'\x018966342T6000\x00\x00\x00\x00', - b'\x018966342T9000\x00\x00\x00\x00', - b'\x018966342U4000\x00\x00\x00\x00', - b'\x018966342U4100\x00\x00\x00\x00', - b'\x018966342U5100\x00\x00\x00\x00', - b'\x018966342V0000\x00\x00\x00\x00', - b'\x018966342V3000\x00\x00\x00\x00', - b'\x018966342V3100\x00\x00\x00\x00', - b'\x018966342V3200\x00\x00\x00\x00', - b'\x01896634A05000\x00\x00\x00\x00', - b'\x01896634A19000\x00\x00\x00\x00', - b'\x01896634A19100\x00\x00\x00\x00', - b'\x01896634A20000\x00\x00\x00\x00', - b'\x01896634A20100\x00\x00\x00\x00', - b'\x01896634A22000\x00\x00\x00\x00', - b'\x01896634A22100\x00\x00\x00\x00', - b'\x01896634A30000\x00\x00\x00\x00', - b'\x01896634A44000\x00\x00\x00\x00', - b'\x01896634A45000\x00\x00\x00\x00', - b'\x01896634A46000\x00\x00\x00\x00', - b'\x028966342M7000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x028966342T0000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x028966342V1000\x00\x00\x00\x00897CF1202001\x00\x00\x00\x00', - b'\x028966342Y8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x02896634A18000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x02896634A18100\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x02896634A43000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00', - b'\x02896634A47000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00', - b'\x028966342Z8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x01896634A15000\x00\x00\x00\x00', - b'\x018966342M5000\x00\x00\x00\x00', - b'\x018966342W8000\x00\x00\x00\x00', - b'\x018966342X5000\x00\x00\x00\x00', - b'\x018966342X6000\x00\x00\x00\x00', - b'\x01896634A25000\x00\x00\x00\x00', - b'\x018966342W5000\x00\x00\x00\x00', - b'\x018966342W7000\x00\x00\x00\x00', - b'\x028966342W4001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', - b'\x02896634A13000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02896634A13001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - b'\x02896634A13101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', - b'\x02896634A23000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02896634A23001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', - b'\x02896634A23101\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', - b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F15260R210\x00\x00\x00\x00\x00\x00', - b'\x01F15260R220\x00\x00\x00\x00\x00\x00', - b'\x01F15260R290\x00\x00\x00\x00\x00\x00', - b'\x01F15260R300\x00\x00\x00\x00\x00\x00', - b'\x01F15260R302\x00\x00\x00\x00\x00\x00', - b'\x01F152642551\x00\x00\x00\x00\x00\x00', - b'\x01F152642561\x00\x00\x00\x00\x00\x00', - b'\x01F152642601\x00\x00\x00\x00\x00\x00', - b'\x01F152642700\x00\x00\x00\x00\x00\x00', - b'\x01F152642701\x00\x00\x00\x00\x00\x00', - b'\x01F152642710\x00\x00\x00\x00\x00\x00', - b'\x01F152642711\x00\x00\x00\x00\x00\x00', - b'\x01F152642750\x00\x00\x00\x00\x00\x00', - b'\x01F152642751\x00\x00\x00\x00\x00\x00', - b'\x01F15260R292\x00\x00\x00\x00\x00\x00', - b'F152642291\x00\x00\x00\x00\x00\x00', - b'F152642290\x00\x00\x00\x00\x00\x00', - b'F152642322\x00\x00\x00\x00\x00\x00', - b'F152642330\x00\x00\x00\x00\x00\x00', - b'F152642331\x00\x00\x00\x00\x00\x00', - b'F152642531\x00\x00\x00\x00\x00\x00', - b'F152642532\x00\x00\x00\x00\x00\x00', - b'F152642520\x00\x00\x00\x00\x00\x00', - b'F152642521\x00\x00\x00\x00\x00\x00', - b'F152642540\x00\x00\x00\x00\x00\x00', - b'F152642541\x00\x00\x00\x00\x00\x00', - b'F152642542\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B42170\x00\x00\x00\x00\x00\x00', - b'8965B42171\x00\x00\x00\x00\x00\x00', - b'8965B42180\x00\x00\x00\x00\x00\x00', - b'8965B42181\x00\x00\x00\x00\x00\x00', - b'\x028965B0R01200\x00\x00\x00\x008965B0R02200\x00\x00\x00\x00', - b'\x028965B0R01300\x00\x00\x00\x008965B0R02300\x00\x00\x00\x00', - b'\x028965B0R01400\x00\x00\x00\x008965B0R02400\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301100\x00\x00\x00\x00', - b'\x018821F3301200\x00\x00\x00\x00', - b'\x018821F3301300\x00\x00\x00\x00', - b'\x018821F3301400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F4203200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F4203300\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F4203500\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F4203700\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F4203800\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', - ], - }, - CAR.RAV4_TSS2_2022: { - (Ecu.abs, 0x7b0, None): [ - b'\x01F15260R350\x00\x00\x00\x00\x00\x00', - b'\x01F15260R361\x00\x00\x00\x00\x00\x00', - b'\x01F15264283100\x00\x00\x00\x00', - b'\x01F15264286200\x00\x00\x00\x00', - b'\x01F15264286100\x00\x00\x00\x00', - b'\x01F15264283200\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00', - b'8965B42182\x00\x00\x00\x00\x00\x00', - b'8965B42172\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x01896634AA0000\x00\x00\x00\x00', - b'\x01896634AA0100\x00\x00\x00\x00', - b'\x01896634AA1000\x00\x00\x00\x00', - b'\x01896634A88000\x00\x00\x00\x00', - b'\x01896634A89000\x00\x00\x00\x00', - b'\x01896634A89100\x00\x00\x00\x00', - b'\x01896634A02001\x00\x00\x00\x00', - b'\x01896634A03000\x00\x00\x00\x00', - b'\x01896634A08000\x00\x00\x00\x00', - b'\x01896634A61000\x00\x00\x00\x00', - b'\x01896634A62000\x00\x00\x00\x00', - b'\x01896634A62100\x00\x00\x00\x00', - b'\x01896634A63000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F0R01100\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00', - ], - }, - CAR.RAV4_TSS2_2023: { - (Ecu.abs, 0x7b0, None): [ - b'\x01F15260R450\x00\x00\x00\x00\x00\x00', - b'\x01F15264283200\x00\x00\x00\x00', - b'\x01F15264283300\x00\x00\x00\x00', - b'\x01F152642F1000\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', - b'8965B42371\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x700, None): [ - b'\x01896634A88100\x00\x00\x00\x00', - b'\x01896634AJ2000\x00\x00\x00\x00', - b'\x01896634A89100\x00\x00\x00\x00', - b'\x01896634AE1001\x00\x00\x00\x00', - b'\x01896634AF0000\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F0R03100\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F0R05100\x00\x00\x00\x008646G0R02100\x00\x00\x00\x00', - b'\x028646F0R05200\x00\x00\x00\x008646G0R02200\x00\x00\x00\x00', - ], - }, - CAR.SIENNA: { - (Ecu.engine, 0x700, None): [ - b'\x01896630832100\x00\x00\x00\x00', - b'\x01896630832200\x00\x00\x00\x00', - b'\x01896630838000\x00\x00\x00\x00', - b'\x01896630838100\x00\x00\x00\x00', - b'\x01896630842000\x00\x00\x00\x00', - b'\x01896630843000\x00\x00\x00\x00', - b'\x01896630851000\x00\x00\x00\x00', - b'\x01896630851100\x00\x00\x00\x00', - b'\x01896630851200\x00\x00\x00\x00', - b'\x01896630852000\x00\x00\x00\x00', - b'\x01896630852100\x00\x00\x00\x00', - b'\x01896630859000\x00\x00\x00\x00', - b'\x01896630860000\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B45070\x00\x00\x00\x00\x00\x00', - b'8965B45080\x00\x00\x00\x00\x00\x00', - b'8965B45082\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152608130\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881510801100\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702200\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0801100\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_CTH: { - (Ecu.dsu, 0x791, None): [ - b'881517601100\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152676144\x00\x00\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0237635000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F7601100\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_ES_TSS2: { - (Ecu.engine, 0x700, None): [ - b'\x018966306U6000\x00\x00\x00\x00', - b'\x018966333T5000\x00\x00\x00\x00', - b'\x018966333T5100\x00\x00\x00\x00', - b'\x018966333X6000\x00\x00\x00\x00', - b'\x01896633T07000\x00\x00\x00\x00', - b'\x028966333S8000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x028966333S8000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x028966333T0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x028966333V4000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x028966333W1000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', - b'\x02896633T09000\x00\x00\x00\x00897CF3307001\x00\x00\x00\x00', - b'\x01896633T38000\x00\x00\x00\x00', - b'\x01896633T58000\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F152606281\x00\x00\x00\x00\x00\x00', - b'\x01F152606340\x00\x00\x00\x00\x00\x00', - b'\x01F152606461\x00\x00\x00\x00\x00\x00', - b'\x01F15260646200\x00\x00\x00\x00', - b'F152633423\x00\x00\x00\x00\x00\x00', - b'F152633680\x00\x00\x00\x00\x00\x00', - b'F152633681\x00\x00\x00\x00\x00\x00', - b'F152633F50\x00\x00\x00\x00\x00\x00', - b'F152633F51\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B33252\x00\x00\x00\x00\x00\x00', - b'8965B33590\x00\x00\x00\x00\x00\x00', - b'8965B33690\x00\x00\x00\x00\x00\x00', - b'8965B33721\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301100\x00\x00\x00\x00', - b'\x018821F3301200\x00\x00\x00\x00', - b'\x018821F3301400\x00\x00\x00\x00', - b'\x018821F6201300\x00\x00\x00\x00', - b'\x018821F3301300\x00\x00\x00\x00', - b'\x018821F6201400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', - b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F0610000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3303100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F3304200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_ES: { - (Ecu.engine, 0x7e0, None): [ - b'\x02333R0000\x00\x00\x00\x00\x00\x00\x00\x00A0C01000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152606202\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881513309500\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B33502\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4701200\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F3302200\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_ESH: { - (Ecu.engine, 0x7e0, None): [ - b'\x02333M4200\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152633171\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881513310400\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B33512\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4701100\x00\x00\x00\x00', - b'8821F4701300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F3302001\x00\x00\x00\x00', - b'8646F3302200\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_GS_F: { - (Ecu.engine, 0x7E0, None): [ - b'\x0233075200\x00\x00\x00\x00\x00\x00\x00\x00530B9000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152630700\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881513016200\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B30551\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702000\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F3002100\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_NX: { - (Ecu.engine, 0x700, None): [ - b'\x01896637850000\x00\x00\x00\x00', - b'\x01896637851000\x00\x00\x00\x00', - b'\x01896637852000\x00\x00\x00\x00', - b'\x01896637854000\x00\x00\x00\x00', - b'\x01896637878000\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0237841000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0237842000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0237880000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0237882000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0237886000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152678130\x00\x00\x00\x00\x00\x00', - b'F152678140\x00\x00\x00\x00\x00\x00', - b'F152678160\x00\x00\x00\x00\x00\x00', - b'F152678170\x00\x00\x00\x00\x00\x00', - b'F152678171\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881517803100\x00\x00\x00\x00', - b'881517803300\x00\x00\x00\x00', - b'881517804300\x00\x00\x00\x00', - b'881517804100\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B78060\x00\x00\x00\x00\x00\x00', - b'8965B78080\x00\x00\x00\x00\x00\x00', - b'8965B78100\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F7801100\x00\x00\x00\x00', - b'8646F7801300\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_NX_TSS2: { - (Ecu.engine, 0x700, None): [ - b'\x018966378B2100\x00\x00\x00\x00', - b'\x018966378B3000\x00\x00\x00\x00', - b'\x018966378B4100\x00\x00\x00\x00', - b'\x018966378G2000\x00\x00\x00\x00', - b'\x018966378G3000\x00\x00\x00\x00', - b'\x018966378B2000\x00\x00\x00\x00', - b'\x018966378B3100\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0237887000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02378A0000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02378F4000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F152678221\x00\x00\x00\x00\x00\x00', - b'F152678210\x00\x00\x00\x00\x00\x00', - b'F152678211\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B78120\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b"\x018821F3301400\x00\x00\x00\x00", - b'\x018821F3301200\x00\x00\x00\x00', - b'\x018821F3301300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F78030A0\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F7803100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_RC: { - (Ecu.engine, 0x700, None): [ - b'\x01896632461100\x00\x00\x00\x00', - b'\x01896632478200\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152624150\x00\x00\x00\x00\x00\x00', - b'F152624221\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881512404100\x00\x00\x00\x00', - b'881512407000\x00\x00\x00\x00', - b'881512409100\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B24081\x00\x00\x00\x00\x00\x00', - b'8965B24240\x00\x00\x00\x00\x00\x00', - b'8965B24320\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F2401100\x00\x00\x00\x00', - b'8646F2401200\x00\x00\x00\x00', - b'8646F2402200\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_RX: { - (Ecu.engine, 0x700, None): [ - b'\x01896630E36200\x00\x00\x00\x00', - b'\x01896630E36300\x00\x00\x00\x00', - b'\x01896630E37200\x00\x00\x00\x00', - b'\x01896630E37300\x00\x00\x00\x00', - b'\x01896630E41000\x00\x00\x00\x00', - b'\x01896630E41100\x00\x00\x00\x00', - b'\x01896630E41200\x00\x00\x00\x00', - b'\x01896630E41500\x00\x00\x00\x00', - b'\x01896630EA3100\x00\x00\x00\x00', - b'\x01896630EA3400\x00\x00\x00\x00', - b'\x01896630EA4100\x00\x00\x00\x00', - b'\x01896630EA4300\x00\x00\x00\x00', - b'\x01896630EA4400\x00\x00\x00\x00', - b'\x01896630EA6300\x00\x00\x00\x00', - b'\x018966348R1300\x00\x00\x00\x00', - b'\x018966348R8500\x00\x00\x00\x00', - b'\x018966348W1300\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152648472\x00\x00\x00\x00\x00\x00', - b'F152648473\x00\x00\x00\x00\x00\x00', - b'F152648492\x00\x00\x00\x00\x00\x00', - b'F152648493\x00\x00\x00\x00\x00\x00', - b'F152648474\x00\x00\x00\x00\x00\x00', - b'F152648630\x00\x00\x00\x00\x00\x00', - b'F152648494\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881514810300\x00\x00\x00\x00', - b'881514810500\x00\x00\x00\x00', - b'881514810700\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B0E011\x00\x00\x00\x00\x00\x00', - b'8965B0E012\x00\x00\x00\x00\x00\x00', - b'8965B48102\x00\x00\x00\x00\x00\x00', - b'8965B48111\x00\x00\x00\x00\x00\x00', - b'8965B48112\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4701000\x00\x00\x00\x00', - b'8821F4701100\x00\x00\x00\x00', - b'8821F4701200\x00\x00\x00\x00', - b'8821F4701300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F4801100\x00\x00\x00\x00', - b'8646F4801200\x00\x00\x00\x00', - b'8646F4802001\x00\x00\x00\x00', - b'8646F4802100\x00\x00\x00\x00', - b'8646F4802200\x00\x00\x00\x00', - b'8646F4809000\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_RXH: { - (Ecu.engine, 0x7e0, None): [ - b'\x02348J7000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348N0000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348Q4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348Q4100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348T1100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348T3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348V6000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348Z3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152648361\x00\x00\x00\x00\x00\x00', - b'F152648501\x00\x00\x00\x00\x00\x00', - b'F152648502\x00\x00\x00\x00\x00\x00', - b'F152648504\x00\x00\x00\x00\x00\x00', - b'F152648740\x00\x00\x00\x00\x00\x00', - b'F152648A30\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881514811300\x00\x00\x00\x00', - b'881514811500\x00\x00\x00\x00', - b'881514811700\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B0E011\x00\x00\x00\x00\x00\x00', - b'8965B0E012\x00\x00\x00\x00\x00\x00', - b'8965B48111\x00\x00\x00\x00\x00\x00', - b'8965B48112\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4701000\x00\x00\x00\x00', - b'8821F4701100\x00\x00\x00\x00', - b'8821F4701200\x00\x00\x00\x00', - b'8821F4701300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F4801200\x00\x00\x00\x00', - b'8646F4802001\x00\x00\x00\x00', - b'8646F4802100\x00\x00\x00\x00', - b'8646F4802200\x00\x00\x00\x00', - b'8646F4809000\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_RX_TSS2: { - (Ecu.engine, 0x700, None): [ - b'\x01896630EA9000\x00\x00\x00\x00', - b'\x01896630EB0000\x00\x00\x00\x00', - b'\x01896630EC9000\x00\x00\x00\x00', - b'\x01896630EC9100\x00\x00\x00\x00', - b'\x01896630ED0000\x00\x00\x00\x00', - b'\x01896630ED0100\x00\x00\x00\x00', - b'\x01896630ED6000\x00\x00\x00\x00', - b'\x018966348T8000\x00\x00\x00\x00', - b'\x018966348W5100\x00\x00\x00\x00', - b'\x018966348W9000\x00\x00\x00\x00', - b'\x01896634D12000\x00\x00\x00\x00', - b'\x01896634D12100\x00\x00\x00\x00', - b'\x01896634D43000\x00\x00\x00\x00', - b'\x01896634D44000\x00\x00\x00\x00', - b'\x018966348X0000\x00\x00\x00\x00', - b'\x01896630ED5000\x00\x00\x00\x00', - b'\x018966348R9200\x00\x00\x00\x00', - ], - (Ecu.engine, 0x7e0, None): [ - b'\x02348X4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348X5000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348X8000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348Y3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0234D14000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0234D16000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348U2000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'\x01F15260E031\x00\x00\x00\x00\x00\x00', - b'\x01F15260E041\x00\x00\x00\x00\x00\x00', - b'\x01F152648781\x00\x00\x00\x00\x00\x00', - b'\x01F152648801\x00\x00\x00\x00\x00\x00', - b'F152648831\x00\x00\x00\x00\x00\x00', - b'F152648891\x00\x00\x00\x00\x00\x00', - b'F152648D00\x00\x00\x00\x00\x00\x00', - b'F152648D60\x00\x00\x00\x00\x00\x00', - b'F152648811\x00\x00\x00\x00\x00\x00', - b'F152648C80\x00\x00\x00\x00\x00\x00', - b'F152648493\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B48261\x00\x00\x00\x00\x00\x00', - b'8965B48271\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301100\x00\x00\x00\x00', - b'\x018821F3301300\x00\x00\x00\x00', - b'\x018821F3301400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F4810100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F4810200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F4810300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F4810400\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - ], - }, - CAR.PRIUS_TSS2: { - (Ecu.engine, 0x700, None): [ - b'\x028966347B1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966347C4000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966347C6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966347C7000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x028966347C8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', - b'\x038966347C1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', - b'\x038966347C5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00', - b'\x038966347C5100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4707101\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152647500\x00\x00\x00\x00\x00\x00', - b'F152647510\x00\x00\x00\x00\x00\x00', - b'F152647520\x00\x00\x00\x00\x00\x00', - b'F152647521\x00\x00\x00\x00\x00\x00', - b'F152647531\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B47070\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F4707000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F4710000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', - b'\x028646F4712000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', - ], - }, - CAR.MIRAI: { - (Ecu.abs, 0x7D1, None): [b'\x01898A36203000\x00\x00\x00\x00',], - (Ecu.abs, 0x7B0, None): [ # a second ABS ECU - b'\x01F15266203200\x00\x00\x00\x00', - b'\x01F15266203500\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7A1, None): [b'\x028965B6204100\x00\x00\x00\x008965B6203100\x00\x00\x00\x00',], - (Ecu.fwdRadar, 0x750, 0xf): [b'\x018821F6201200\x00\x00\x00\x00',], - (Ecu.fwdCamera, 0x750, 0x6d): [b'\x028646F6201400\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',], - }, - CAR.ALPHARD_TSS2: { - (Ecu.engine, 0x7e0, None): [ - b'\x0235870000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0235883000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B58040\x00\x00\x00\x00\x00\x00', - b'8965B58052\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152658341\x00\x00\x00\x00\x00\x00' - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301200\x00\x00\x00\x00', - b'\x018821F3301400\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F58010C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F5803200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646FV201000\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - ], - }, -} STEER_THRESHOLD = 100 diff --git a/selfdrive/car/volkswagen/fingerprints.py b/selfdrive/car/volkswagen/fingerprints.py new file mode 100644 index 0000000000..a9c1ccef4b --- /dev/null +++ b/selfdrive/car/volkswagen/fingerprints.py @@ -0,0 +1,1154 @@ +from cereal import car +from openpilot.selfdrive.car.volkswagen.values import CAR + +Ecu = car.CarParams.Ecu + + +FW_VERSIONS = { + CAR.ARTEON_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x873G0906259AH\xf1\x890001', + b'\xf1\x873G0906259F \xf1\x890004', + b'\xf1\x873G0906259G \xf1\x890004', + b'\xf1\x873G0906259G \xf1\x890005', + b'\xf1\x873G0906259M \xf1\x890003', + b'\xf1\x873G0906259N \xf1\x890004', + b'\xf1\x873G0906259P \xf1\x890001', + b'\xf1\x875NA907115H \xf1\x890002', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158L \xf1\x893611', + b'\xf1\x870DL300014C \xf1\x893704', + b'\xf1\x870GC300011L \xf1\x891401', + b'\xf1\x870GC300014M \xf1\x892802', + b'\xf1\x870GC300019G \xf1\x892804', + b'\xf1\x870GC300040P \xf1\x891401', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121157161111572900', + b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121177161113772900', + b'\xf1\x873Q0959655CK\xf1\x890711\xf1\x82\x0e1712141712141105121122052900', + b'\xf1\x873Q0959655DA\xf1\x890720\xf1\x82\x0e1712141712141105121122052900', + b'\xf1\x873Q0959655DL\xf1\x890732\xf1\x82\x0e1812141812171105141123052J00', + b'\xf1\x875QF959655AP\xf1\x890755\xf1\x82\x1311110011111311111100110200--1611125F49', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571B41815A1', + b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571B00817A1', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020800', + b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002MB4092M7N', + b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002NB4202N7N', + b'\xf1\x875WA907145Q \xf1\x891063\xf1\x82\x002KB4092KOM', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x875Q0907572J \xf1\x890654', + b'\xf1\x875Q0907572R \xf1\x890771', + ], + }, + CAR.ATLAS_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8703H906026AA\xf1\x899970', + b'\xf1\x8703H906026AG\xf1\x899973', + b'\xf1\x8703H906026AJ\xf1\x890638', + b'\xf1\x8703H906026AJ\xf1\x891017', + b'\xf1\x8703H906026AT\xf1\x891922', + b'\xf1\x8703H906026BC\xf1\x892664', + b'\xf1\x8703H906026F \xf1\x896696', + b'\xf1\x8703H906026F \xf1\x899970', + b'\xf1\x8703H906026J \xf1\x896026', + b'\xf1\x8703H906026J \xf1\x899971', + b'\xf1\x8703H906026S \xf1\x896693', + b'\xf1\x8703H906026S \xf1\x899970', + b'\xf1\x873CN906259 \xf1\x890005', + b'\xf1\x873CN906259F \xf1\x890002', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158A \xf1\x893387', + b'\xf1\x8709G927158DR\xf1\x893536', + b'\xf1\x8709G927158DR\xf1\x893742', + b'\xf1\x8709G927158EN\xf1\x893691', + b'\xf1\x8709G927158F \xf1\x893489', + b'\xf1\x8709G927158FT\xf1\x893835', + b'\xf1\x8709G927158GL\xf1\x893939', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655BC\xf1\x890503\xf1\x82\0161914151912001103111122031200', + b'\xf1\x873Q0959655BN\xf1\x890713\xf1\x82\0162214152212001105141122052900', + b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\x0e1114151112001105111122052900', + b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\0162214152212001105141122052900', + b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1114151112001105161122052J00', + b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1115151112001105171122052J00', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\00571B60924A1', + b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6G920A1', + b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6M921A1', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6080105', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6090105', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x875Q0907572H \xf1\x890620', + b'\xf1\x875Q0907572J \xf1\x890654', + b'\xf1\x875Q0907572P \xf1\x890682', + ], + }, + CAR.CRAFTER_MK2: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906056BP\xf1\x894729', + b'\xf1\x8704L906056EK\xf1\x896391', + b'\xf1\x8705L906023BC\xf1\x892688', + ], + # Only current upstreamed vehicle has a manual transmission + #(Ecu.transmission, 0x7e1, None): [ + #], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655AL\xf1\x890505\xf1\x82\x0e1411001413001203151311031100', + b'\xf1\x873Q0959655BG\xf1\x890703\xf1\x82\x0e16120016130012051G1313052900', + b'\xf1\x875QF959655AS\xf1\x890755\xf1\x82\x1315140015150011111100050200--1311120749', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872N0909143D\x00\xf1\x897010\xf1\x82\x05183AZ306A2', + b'\xf1\x872N0909143E \xf1\x897021\xf1\x82\x05163AZ306A2', + b'\xf1\x872N0909144K \xf1\x897045\xf1\x82\x05233AZ810A2', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572J \xf1\x890156', + b'\xf1\x872Q0907572M \xf1\x890233', + ], + }, + CAR.GOLF_MK7: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906016A \xf1\x897697', + b'\xf1\x8704E906016N \xf1\x899105', + b'\xf1\x8704E906016AD\xf1\x895758', + b'\xf1\x8704E906016CE\xf1\x899096', + b'\xf1\x8704E906016CH\xf1\x899226', + b'\xf1\x8704E906023AG\xf1\x891726', + b'\xf1\x8704E906023BN\xf1\x894518', + b'\xf1\x8704E906024K \xf1\x896811', + b'\xf1\x8704E906027GR\xf1\x892394', + b'\xf1\x8704E906027HD\xf1\x892603', + b'\xf1\x8704E906027HD\xf1\x893742', + b'\xf1\x8704E906027MA\xf1\x894958', + b'\xf1\x8704L906021DT\xf1\x895520', + b'\xf1\x8704L906021DT\xf1\x898127', + b'\xf1\x8704L906021N \xf1\x895518', + b'\xf1\x8704L906021N \xf1\x898138', + b'\xf1\x8704L906026BN\xf1\x891197', + b'\xf1\x8704L906026BP\xf1\x897608', + b'\xf1\x8704L906026NF\xf1\x899528', + b'\xf1\x8704L906056CL\xf1\x893823', + b'\xf1\x8704L906056CR\xf1\x895813', + b'\xf1\x8704L906056HE\xf1\x893758', + b'\xf1\x8704L906056HN\xf1\x896590', + b'\xf1\x8704L906056HT\xf1\x896591', + b'\xf1\x870EA906016A \xf1\x898343', + b'\xf1\x870EA906016E \xf1\x894219', + b'\xf1\x870EA906016F \xf1\x894238', + b'\xf1\x870EA906016F \xf1\x895002', + b'\xf1\x870EA906016Q \xf1\x895993', + b'\xf1\x870EA906016S \xf1\x897207', + b'\xf1\x875G0906259 \xf1\x890007', + b'\xf1\x875G0906259D \xf1\x890002', + b'\xf1\x875G0906259J \xf1\x890002', + b'\xf1\x875G0906259L \xf1\x890002', + b'\xf1\x875G0906259N \xf1\x890003', + b'\xf1\x875G0906259Q \xf1\x890002', + b'\xf1\x875G0906259Q \xf1\x892313', + b'\xf1\x875G0906259T \xf1\x890003', + b'\xf1\x878V0906259H \xf1\x890002', + b'\xf1\x878V0906259J \xf1\x890003', + b'\xf1\x878V0906259K \xf1\x890001', + b'\xf1\x878V0906259K \xf1\x890003', + b'\xf1\x878V0906259P \xf1\x890001', + b'\xf1\x878V0906259Q \xf1\x890002', + b'\xf1\x878V0906259R \xf1\x890002', + b'\xf1\x878V0906264F \xf1\x890003', + b'\xf1\x878V0906264L \xf1\x890002', + b'\xf1\x878V0906264M \xf1\x890001', + b'\xf1\x878V09C0BB01 \xf1\x890001', + b'\xf1\x8704E906024K \xf1\x899970', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927749AP\xf1\x892943', + b'\xf1\x8709S927158A \xf1\x893585', + b'\xf1\x870CW300040H \xf1\x890606', + b'\xf1\x870CW300041D \xf1\x891004', + b'\xf1\x870CW300041H \xf1\x891010', + b'\xf1\x870CW300042F \xf1\x891604', + b'\xf1\x870CW300043B \xf1\x891601', + b'\xf1\x870CW300043E \xf1\x891603', + b'\xf1\x870CW300044S \xf1\x894530', + b'\xf1\x870CW300044T \xf1\x895245', + b'\xf1\x870CW300045 \xf1\x894531', + b'\xf1\x870CW300047D \xf1\x895261', + b'\xf1\x870CW300047E \xf1\x895261', + b'\xf1\x870CW300048J \xf1\x890611', + b'\xf1\x870CW300049H \xf1\x890905', + b'\xf1\x870CW300050G \xf1\x891905', + b'\xf1\x870D9300012 \xf1\x894904', + b'\xf1\x870D9300012 \xf1\x894913', + b'\xf1\x870D9300012 \xf1\x894937', + b'\xf1\x870D9300012 \xf1\x895045', + b'\xf1\x870D9300012 \xf1\x895046', + b'\xf1\x870D9300014M \xf1\x895004', + b'\xf1\x870D9300014Q \xf1\x895006', + b'\xf1\x870D9300020J \xf1\x894902', + b'\xf1\x870D9300020Q \xf1\x895201', + b'\xf1\x870D9300020S \xf1\x895201', + b'\xf1\x870D9300040A \xf1\x893613', + b'\xf1\x870D9300040S \xf1\x894311', + b'\xf1\x870D9300041H \xf1\x895220', + b'\xf1\x870D9300041P \xf1\x894507', + b'\xf1\x870DD300045K \xf1\x891120', + b'\xf1\x870DD300046F \xf1\x891601', + b'\xf1\x870GC300012A \xf1\x891401', + b'\xf1\x870GC300012A \xf1\x891403', + b'\xf1\x870GC300014B \xf1\x892401', + b'\xf1\x870GC300014B \xf1\x892403', + b'\xf1\x870GC300014B \xf1\x892405', + b'\xf1\x870GC300020G \xf1\x892401', + b'\xf1\x870GC300020G \xf1\x892403', + b'\xf1\x870GC300020G \xf1\x892404', + b'\xf1\x870GC300020N \xf1\x892804', + b'\xf1\x870GC300043T \xf1\x899999', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\x111413001113120043114317121C111C9113', + b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\x111413001113120053114317121C111C9113', + b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120043114317121C111C9113', + b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120043114417121411149113', + b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120053114317121C111C9113', + b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\x13141500111233003142114A2131219333313100', + b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333423100', + b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100', + b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2251229333463100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2251229333463100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', + b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', + b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', + b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271212111312--071104171838103891131211', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13341512112212--071104172328102891131211', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13272512111312--07110417182C102C91131211', + b'\xf1\x875Q0959655M \xf1\x890361\xf1\x82\x111413001112120041114115121611169112', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200621143171717111791132111', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200061104171724102491132111', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200621143171724112491132111', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200061104171717101791132111', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200631143171724122491132111', + b'\xf1\x875Q0959655T \xf1\x890825\xf1\x82\x13271200111312--071104171837103791132111', + b'\xf1\x875Q0959655T \xf1\x890830\xf1\x82\x13271100111312--071104171826102691131211', + b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111413001113120006110417121D101D9112', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561A01612A0', + b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\x0566A0J612A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A00514A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01613A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1', + b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1', + b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1', + b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A01A18A1', + b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A02A16A1', + b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A0JA16A1', + b'\xf1\x873QM909144 \xf1\x895072\xf1\x82\x0571A01714A1', + b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820519A9040203', + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00441A1', + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00608A1', + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00641A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00442A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00642A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A07B05A1', + b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00602A0', + b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0522A00402A0', + b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00502A0', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511A00403A0', + b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00404A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00504A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00604A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A07A02A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00407A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00507A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A07B04A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A20B03A1', + b'\xf1\x875QD909144B \xf1\x891072\xf1\x82\x0521A00507A1', + b'\xf1\x875QM909144A \xf1\x891072\xf1\x82\x0521A20B03A1', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00442A1', + b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A16A1', + b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A18A1', + b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A17A1', + b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\x0571A01A18A1', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000400', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\x0101', + b'\xf1\x875Q0907567J \xf1\x890396\xf1\x82\x0101', + b'\xf1\x875Q0907567L \xf1\x890098\xf1\x82\x0101', + b'\xf1\x875Q0907572A \xf1\x890141\xf1\x82\x0101', + b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\x0101', + b'\xf1\x875Q0907572C \xf1\x890210\xf1\x82\x0101', + b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', + b'\xf1\x875Q0907572E \xf1\x89X310\xf1\x82\x0101', + b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', + b'\xf1\x875Q0907572G \xf1\x890571', + b'\xf1\x875Q0907572H \xf1\x890620', + b'\xf1\x875Q0907572J \xf1\x890653', + b'\xf1\x875Q0907572J \xf1\x890654', + b'\xf1\x875Q0907572P \xf1\x890682', + b'\xf1\x875Q0907572R \xf1\x890771', + b'\xf1\x875Q0907572S \xf1\x890780', + ], + }, + CAR.JETTA_MK7: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906024AK\xf1\x899937', + b'\xf1\x8704E906024AS\xf1\x899912', + b'\xf1\x8704E906024BC\xf1\x899971', + b'\xf1\x8704E906024BG\xf1\x891057', + b'\xf1\x8704E906024B \xf1\x895594', + b'\xf1\x8704E906024C \xf1\x899970', + b'\xf1\x8704E906024L \xf1\x895595', + b'\xf1\x8704E906024L \xf1\x899970', + b'\xf1\x8704E906027MS\xf1\x896223', + b'\xf1\x875G0906259T \xf1\x890003', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158BQ\xf1\x893545', + b'\xf1\x8709S927158BS\xf1\x893642', + b'\xf1\x8709S927158BS\xf1\x893694', + b'\xf1\x8709S927158CK\xf1\x893770', + b'\xf1\x8709S927158R \xf1\x893552', + b'\xf1\x8709S927158R \xf1\x893587', + b'\xf1\x870GC300020N \xf1\x892803', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AG\xf1\x890336\xf1\x82\02314171231313500314611011630169333463100', + b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1314171231313500314611011630169333463100', + b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314642011650169333463100', + b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314643011650169333463100', + b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02311170031313300314240011150119333433100', + b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02319170031313300314240011550159333463100', + b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314643021650169333613100', + b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314642021650169333613100', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A10A11A1', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\00521A10A01A1', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521B00404A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A00642A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A10A01A1', + b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\00571A10A11A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x875Q0907572N \xf1\x890681', + b'\xf1\x875Q0907572P \xf1\x890682', + b'\xf1\x875Q0907572R \xf1\x890771', + ], + }, + CAR.PASSAT_MK8: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8703N906026E \xf1\x892114', + b'\xf1\x8704E906023AH\xf1\x893379', + b'\xf1\x8704E906023BM\xf1\x894522', + b'\xf1\x8704L906026DP\xf1\x891538', + b'\xf1\x8704L906026ET\xf1\x891990', + b'\xf1\x8704L906026FP\xf1\x892012', + b'\xf1\x8704L906026GA\xf1\x892013', + b'\xf1\x8704L906026KD\xf1\x894798', + b'\xf1\x873G0906259B \xf1\x890002', + b'\xf1\x873G0906259 \xf1\x890004', + b'\xf1\x873G0906264 \xf1\x890004', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300043H \xf1\x891601', + b'\xf1\x870CW300048R \xf1\x890610', + b'\xf1\x870D9300013A \xf1\x894905', + b'\xf1\x870D9300014L \xf1\x895002', + b'\xf1\x870D9300041A \xf1\x894801', + b'\xf1\x870DD300045T \xf1\x891601', + b'\xf1\x870DD300046H \xf1\x891601', + b'\xf1\x870DL300011H \xf1\x895201', + b'\xf1\x870CW300042H \xf1\x891601', + b'\xf1\x870CW300042H \xf1\x891607', + b'\xf1\x870GC300042H \xf1\x891404', + b'\xf1\x870D9300018C \xf1\x895297', + b'\xf1\x870D9300042H \xf1\x894901', + b'\xf1\x870GC300043 \xf1\x892301', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111', + b'\xf1\x873Q0959655AF\xf1\x890195\xf1\x82\r56140056130012026612120211', + b'\xf1\x873Q0959655AN\xf1\x890305\xf1\x82\r58160058140013036914110311', + b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311', + b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111', + b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', + b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e5915005914001305701311052900', + b'\xf1\x873Q0959655BG\xf1\x890712\xf1\x82\x0e5915005914001305701311052900', + b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900', + b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e5915005914001354701311542900', + b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900', + b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012416612124111', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00711A1', + b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514B0060703', + b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803', + b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526B0060905', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516B00501A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521B00703A1', + b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563B0000600', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00603A1', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x873Q0907572A \xf1\x890126', + b'\xf1\x873Q0907572A \xf1\x890130', + b'\xf1\x873Q0907572B \xf1\x890192', + b'\xf1\x873Q0907572C \xf1\x890195', + b'\xf1\x873Q0907572C \xf1\x890196', + b'\xf1\x875Q0907572P \xf1\x890682', + b'\xf1\x873Q0907572B \xf1\x890194', + b'\xf1\x875Q0907572R \xf1\x890771', + ], + }, + CAR.PASSAT_NMS: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8706K906016C \xf1\x899609', + b'\xf1\x8706K906016E \xf1\x899830', + b'\xf1\x8706K906016G \xf1\x891124', + b'\xf1\x8706K906071BJ\xf1\x894891', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158AB\xf1\x893318', + b'\xf1\x8709G927158BD\xf1\x893121', + b'\xf1\x8709G927158DK\xf1\x893594', + b'\xf1\x8709G927158FQ\xf1\x893745', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x87561959655 \xf1\x890210\xf1\x82\02212121111113000102011--121012--101312', + b'\xf1\x87561959655C \xf1\x890508\xf1\x82\02215141111121100314919--153015--304831', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x87561907567A \xf1\x890132', + b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\00152', + ], + }, + CAR.POLO_MK6: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704C906025H \xf1\x895177', + b'\xf1\x8705C906032J \xf1\x891702', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300042D \xf1\x891612', + b'\xf1\x870CW300050D \xf1\x891908', + b'\xf1\x870CW300051G \xf1\x891909', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AG\xf1\x890248\xf1\x82\x1218130411110411--04040404231811152H14', + b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1248130411110416--04040404784811152H14', + b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1384830511110516041405820599841215391471', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872Q1909144M \xf1\x896041', + b'\xf1\x872Q2909144AB\xf1\x896050', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572R \xf1\x890372', + ], + }, + CAR.SHARAN_MK2: { + # TODO: Sharan Mk2 EPS and DQ250 auto trans both require KWP2000 support for fingerprinting + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906016HE\xf1\x894635', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x877N0959655D \xf1\x890016\xf1\x82\x0801100705----10--', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\x0153', + ], + }, + CAR.TAOS_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906027NJ\xf1\x891445', + b'\xf1\x8704E906027NP\xf1\x891286', + b'\xf1\x8705E906013BD\xf1\x892496', + b'\xf1\x8705E906013E \xf1\x891624', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158EM\xf1\x893812', + b'\xf1\x8709S927158BL\xf1\x893791', + b'\xf1\x8709S927158DN\xf1\x893946', + b'\xf1\x8709S927158FF\xf1\x893876', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1311111111333500314646021450149333613100', + b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1312111111333500314646021550159333613100', + b'\xf1\x875Q0959655CE\xf1\x890421\xf1\x82\x1311110011333300314240021350139333613100', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x001O06081OOM', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060405A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060605A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572T \xf1\x890383', + ], + }, + CAR.TCROSS_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704C906025AK\xf1\x897053', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300050E \xf1\x891903', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\02212130411110411--04041104141311152H14', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872Q1909144M \xf1\x896041', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572T \xf1\x890383', + ], + }, + CAR.TIGUAN_MK2: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8703N906026D \xf1\x893680', + b'\xf1\x8704E906027NB\xf1\x899504', + b'\xf1\x8704L906026EJ\xf1\x893661', + b'\xf1\x8704L906027G \xf1\x899893', + b'\xf1\x8705E906018BS\xf1\x890914', + b'\xf1\x875N0906259 \xf1\x890002', + b'\xf1\x875NA906259H \xf1\x890002', + b'\xf1\x875NA907115E \xf1\x890003', + b'\xf1\x875NA907115E \xf1\x890005', + b'\xf1\x875NA907115J \xf1\x890002', + b'\xf1\x8783A907115B \xf1\x890005', + b'\xf1\x8783A907115F \xf1\x890002', + b'\xf1\x8783A907115G \xf1\x890001', + b'\xf1\x8783A907115K \xf1\x890001', + b'\xf1\x8783A907115K \xf1\x890002', + b'\xf1\x8704E906024AP\xf1\x891461', + b'\xf1\x8783A907115 \xf1\x890007', + b'\xf1\x8783A907115Q \xf1\x890001', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158DT\xf1\x893698', + b'\xf1\x8709G927158FM\xf1\x893757', + b'\xf1\x8709G927158GC\xf1\x893821', + b'\xf1\x8709G927158GD\xf1\x893820', + b'\xf1\x8709G927158GM\xf1\x893936', + b'\xf1\x8709G927158GN\xf1\x893938', + b'\xf1\x8709G927158HB\xf1\x894069', + b'\xf1\x870D9300043 \xf1\x895202', + b'\xf1\x870DL300011N \xf1\x892001', + b'\xf1\x870DL300011N \xf1\x892012', + b'\xf1\x870DL300012M \xf1\x892107', + b'\xf1\x870DL300012P \xf1\x892103', + b'\xf1\x870DL300013A \xf1\x893005', + b'\xf1\x870DL300013G \xf1\x892119', + b'\xf1\x870DL300013G \xf1\x892120', + b'\xf1\x870DL300014C \xf1\x893703', + b'\xf1\x870DD300046K \xf1\x892302', + b'\xf1\x870GC300013P \xf1\x892401', + b'\xf1\x870GC300046Q \xf1\x892802', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100', + b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', + b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1331310031333334313132013730379333423100', + b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100', + b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1312110031333300314240013750379333423100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100', + b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100', + b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100', + b'\xf1\x875Q0959655CD\xf1\x890421\xf1\x82\x13123112313333003145406F6154619333613100', + b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1316143231313500314617011730179333423100', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6050705', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', + b'\xf1\x875QF909144A \xf1\x895581\xf1\x82\x0571A60834A1', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\00571A60634A1', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571A62A32A1', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1', + b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002RA60A2ROM', + b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002SA6092SOM', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60804A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572AB\xf1\x890397', + b'\xf1\x872Q0907572J \xf1\x890156', + b'\xf1\x872Q0907572M \xf1\x890233', + b'\xf1\x872Q0907572Q \xf1\x890342', + b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572T \xf1\x890383', + ], + }, + CAR.TOURAN_MK2: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906026HM\xf1\x893017', + b'\xf1\x8705E906018CQ\xf1\x890808', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300041E \xf1\x891005', + b'\xf1\x870CW300051M \xf1\x891926', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AS\xf1\x890318\xf1\x82\023363500213533353141324C4732479333313100', + b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1336350021353336314740025250529333613100', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820531B0062105', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A8090400', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x873Q0907572C \xf1\x890195', + b'\xf1\x872Q0907572AA\xf1\x890396', + ], + }, + CAR.TRANSPORTER_T61: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906056AG\xf1\x899970', + b'\xf1\x8704L906056AL\xf1\x899970', + b'\xf1\x8704L906057AP\xf1\x891186', + b'\xf1\x8704L906057N \xf1\x890413', + b'\xf1\x8705L906023E \xf1\x891352', + b'\xf1\x8705L906023MR\xf1\x892582', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870BT300012G \xf1\x893102', + b'\xf1\x870BT300012E \xf1\x893105', + b'\xf1\x870BT300046R \xf1\x893102', + b'\xf1\x870DV300012B \xf1\x893701', + b'\xf1\x870DV300012B \xf1\x893702', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AE\xf1\x890506\xf1\x82\x1316170411110411--04041704161611152S1411', + b'\xf1\x872Q0959655AE\xf1\x890506\xf1\x82\x1316170411110411--04041704171711152S1411', + b'\xf1\x872Q0959655AF\xf1\x890506\xf1\x82\x1316171111110411--04041711121211152S1413', + b'\xf1\x872Q0959655AQ\xf1\x890511\xf1\x82\x1316170411110411--0404170426261215391421', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\x0532380518A2', + b'\xf1\x877LA909144G \xf1\x897160\xf1\x82\x05333A5519A2', + b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\x05323A5519A2', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572AA\xf1\x890396', + ], + }, + CAR.TROC_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8705E906018AT\xf1\x899640', + b'\xf1\x8705E906018CK\xf1\x890863', + b'\xf1\x8705E906018P \xf1\x896020', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300050J \xf1\x891911', + b'\xf1\x870CW300051M \xf1\x891925', + b'\xf1\x870CW300051M \xf1\x891928', + b'\xf1\x870CW300041S \xf1\x891615', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1311110012333300314240681152119333463100', + b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x13111100123333003142404M1152119333613100', + b'\xf1\x875Q0959655CF\xf1\x890421\xf1\x82\x1311110012333300314240021150119333613100', + b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1111001111001105111111052900', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521060405A1', + b'\xf1\x875WA907144M \xf1\x891051\xf1\x82\x001T06081T7N', + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521060403A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x872Q0907572M \xf1\x890233', + ], + }, + CAR.AUDI_A3_MK3: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906023AN\xf1\x893695', + b'\xf1\x8704E906023AR\xf1\x893440', + b'\xf1\x8704E906023BL\xf1\x895190', + b'\xf1\x8704E906027CJ\xf1\x897798', + b'\xf1\x8704L997022N \xf1\x899459', + b'\xf1\x875G0906259A \xf1\x890004', + b'\xf1\x875G0906259D \xf1\x890002', + b'\xf1\x875G0906259L \xf1\x890002', + b'\xf1\x875G0906259Q \xf1\x890002', + b'\xf1\x878V0906259E \xf1\x890001', + b'\xf1\x878V0906259F \xf1\x890002', + b'\xf1\x878V0906259H \xf1\x890002', + b'\xf1\x878V0906259J \xf1\x890002', + b'\xf1\x878V0906259K \xf1\x890001', + b'\xf1\x878V0906264B \xf1\x890003', + b'\xf1\x878V0907115B \xf1\x890007', + b'\xf1\x878V0907404A \xf1\x890005', + b'\xf1\x878V0907404G \xf1\x890005', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300044T \xf1\x895245', + b'\xf1\x870CW300048 \xf1\x895201', + b'\xf1\x870D9300012 \xf1\x894912', + b'\xf1\x870D9300012 \xf1\x894931', + b'\xf1\x870D9300012L \xf1\x894521', + b'\xf1\x870D9300012K \xf1\x894513', + b'\xf1\x870D9300013B \xf1\x894902', + b'\xf1\x870D9300013B \xf1\x894931', + b'\xf1\x870D9300041N \xf1\x894512', + b'\xf1\x870D9300043T \xf1\x899699', + b'\xf1\x870DD300046 \xf1\x891604', + b'\xf1\x870DD300046A \xf1\x891602', + b'\xf1\x870DD300046F \xf1\x891602', + b'\xf1\x870DD300046G \xf1\x891601', + b'\xf1\x870DL300012E \xf1\x892012', + b'\xf1\x870DL300012H \xf1\x892112', + b'\xf1\x870GC300011 \xf1\x890403', + b'\xf1\x870GC300013M \xf1\x892402', + b'\xf1\x870GC300042J \xf1\x891402', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AB\xf1\x890388\xf1\x82\0211111001111111206110412111321139114', + b'\xf1\x875Q0959655AM\xf1\x890315\xf1\x82\x1311111111111111311411011231129321212100', + b'\xf1\x875Q0959655AM\xf1\x890318\xf1\x82\x1311111111111112311411011531159321212100', + b'\xf1\x875Q0959655AR\xf1\x890315\xf1\x82\x1311110011131115311211012331239321212100', + b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1311110011131100311111011731179321342100', + b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', + b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13111112111111--241115141112221291163221', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111211--261117141112231291163221', + b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112110004110411111421149114', + b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561G01A13A0', + b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566G0HA14A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566G0HA14A1', + b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1', + b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1', + b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0JA13A1', + b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521G0G809A1', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00303A0', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00803A0', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0503G0G803A0', + b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\00516G00804A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516G00804A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521G00807A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x875Q0907567M \xf1\x890398\xf1\x82\x0101', + b'\xf1\x875Q0907567N \xf1\x890400\xf1\x82\x0101', + b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', + b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', + b'\xf1\x875Q0907572G \xf1\x890571', + b'\xf1\x875Q0907572H \xf1\x890620', + b'\xf1\x875Q0907572P \xf1\x890682', + ], + }, + CAR.AUDI_Q2_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906027JT\xf1\x894145', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300041F \xf1\x891006', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655BD\xf1\x890336\xf1\x82\x1311111111111100311211011231129321312111', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571F60511A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572M \xf1\x890233', + ], + }, + CAR.AUDI_Q3_MK2: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8705E906018N \xf1\x899970', + b'\xf1\x8705L906022M \xf1\x890901', + b'\xf1\x8783A906259 \xf1\x890001', + b'\xf1\x8783A906259 \xf1\x890005', + b'\xf1\x8783A906259C \xf1\x890002', + b'\xf1\x8783A906259D \xf1\x890001', + b'\xf1\x8783A906259F \xf1\x890001', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158CN\xf1\x893608', + b'\xf1\x8709G927158FL\xf1\x893758', + b'\xf1\x8709G927158GG\xf1\x893825', + b'\xf1\x8709G927158GP\xf1\x893937', + b'\xf1\x870GC300045D \xf1\x892802', + b'\xf1\x870GC300046F \xf1\x892701', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655BF\xf1\x890403\xf1\x82\x1321211111211200311121232152219321422111', + b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218C219321532111', + b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111224118A119321532111', + b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111', + b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218A219321532111', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000800', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571G60533A1', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571G60733A1', + b'\xf1\x875TA907145D \xf1\x891051\xf1\x82\x001PG60A1P7N', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572T \xf1\x890383', + ], + }, + CAR.SEAT_ATECA_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906027KA\xf1\x893749', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870D9300014S \xf1\x895202', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0161212001211001305121211052900', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\00571N60511A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572M \xf1\x890233', + ], + }, + CAR.SEAT_LEON_MK3: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906021EL\xf1\x897542', + b'\xf1\x8704L906026BP\xf1\x891198', + b'\xf1\x8704L906026BP\xf1\x897608', + b'\xf1\x8704L906056CR\xf1\x892181', + b'\xf1\x8704L906056CR\xf1\x892797', + b'\xf1\x8705E906018AS\xf1\x899596', + b'\xf1\x878V0906264H \xf1\x890005', + b'\xf1\x878V0907115E \xf1\x890002', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300041D \xf1\x891004', + b'\xf1\x870CW300041G \xf1\x891003', + b'\xf1\x870CW300050J \xf1\x891908', + b'\xf1\x870D9300042M \xf1\x895016', + b'\xf1\x870GC300043A \xf1\x892304', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655AC\xf1\x890189\xf1\x82\r11110011110011021511110200', + b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11110011110011021511110200', + b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r12110012120012021612110200', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1312001313001305171311052900', + b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1312001313001305171311052900', + b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\0161312001313001305171311052900', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521N01842A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521N01342A1', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00511N01805A0', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N01309A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521N05808A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101', + b'\xf1\x875Q0907572H \xf1\x890620', + b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', + b'\xf1\x875Q0907572P \xf1\x890682', + b'\xf1\x875Q0907572R \xf1\x890771', + ], + }, + CAR.SKODA_FABIA_MK4: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8705E906018CF\xf1\x891905', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300051M \xf1\x891936', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100110200--1111120749', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872Q1909144S \xf1\x896042', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + ], + }, + CAR.SKODA_KAMIQ_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8705C906032M \xf1\x891333', + b'\xf1\x8705E906013CK\xf1\x892540', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300020 \xf1\x891906', + b'\xf1\x870CW300020T \xf1\x892204', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\0222221042111042121040404042E2711152H14', + b'\xf1\x872Q0959655BJ\xf1\x890412\xf1\x82\x132223042111042121040404042B251215391423', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872Q1909144M \xf1\x896041', + b'\xf1\x872Q1909144AB\xf1\x896050', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x872Q0907572AA\xf1\x890396', + ], + }, + CAR.SKODA_KAROQ_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8705E906013H \xf1\x892407', + b'\xf1\x8705E906018P \xf1\x895472', + b'\xf1\x8705E906018P \xf1\x896020', + b'\xf1\x8705L906022BS\xf1\x890913', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300020T \xf1\x892202', + b'\xf1\x870CW300041S \xf1\x891615', + b'\xf1\x870GC300014L \xf1\x892802', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1312110012120011111100010200--2521210749', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001101131112012100', + b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\0161213001211001101131122012100', + b'\xf1\x873Q0959655DE\xf1\x890731\xf1\x82\x0e1213001211001101131121012J00', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563T6090500', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100500', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100700', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572AB\xf1\x890397', + b'\xf1\x872Q0907572M \xf1\x890233', + b'\xf1\x872Q0907572T \xf1\x890383', + ], + }, + CAR.SKODA_KODIAQ_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906027DD\xf1\x893123', + b'\xf1\x8704E906027NB\xf1\x899504', + b'\xf1\x8704E906027NB\xf1\x896517', + b'\xf1\x8704L906026DE\xf1\x895418', + b'\xf1\x8704L906026EJ\xf1\x893661', + b'\xf1\x8704L906026HT\xf1\x893617', + b'\xf1\x8783A907115E \xf1\x890001', + b'\xf1\x8705E906018DJ\xf1\x890915', + b'\xf1\x8705E906018DJ\xf1\x891903', + b'\xf1\x875NA907115E \xf1\x890003', + b'\xf1\x875NA907115E \xf1\x890005', + b'\xf1\x875NA906259E \xf1\x890003', + b'\xf1\x8704E906027LD\xf1\x893433', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870D9300043 \xf1\x895202', + b'\xf1\x870DL300011N \xf1\x892014', + b'\xf1\x870DL300012M \xf1\x892107', + b'\xf1\x870DL300012N \xf1\x892110', + b'\xf1\x870DL300013G \xf1\x892119', + b'\xf1\x870GC300014N \xf1\x892801', + b'\xf1\x870GC300019H \xf1\x892806', + b'\xf1\x870GC300046Q \xf1\x892802', + b'\xf1\x870D9300014S \xf1\x895201', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211', + b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', + b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1213001211001244212111442100', + b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e1213001211001205212112052100', + b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', + b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', + b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121240749', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6060405', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6070405', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G500', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G600', + b'\xf1\x875TA907145F \xf1\x891063\xf1\x82\x002LT61A2LOM', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572Q \xf1\x890342', + b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572AB\xf1\x890397', + b'\xf1\x872Q0907572M \xf1\x890233', + ], + }, + CAR.SKODA_OCTAVIA_MK3: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704C906025L \xf1\x896198', + b'\xf1\x8704E906016ER\xf1\x895823', + b'\xf1\x8704E906027HD\xf1\x893742', + b'\xf1\x8704E906027MH\xf1\x894786', + b'\xf1\x8704L906021DT\xf1\x898127', + b'\xf1\x8704L906026BP\xf1\x897608', + b'\xf1\x8704L906026BS\xf1\x891541', + b'\xf1\x875G0906259C \xf1\x890002', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300041L \xf1\x891601', + b'\xf1\x870CW300041N \xf1\x891605', + b'\xf1\x870CW300043B \xf1\x891601', + b'\xf1\x870CW300043P \xf1\x891605', + b'\xf1\x870D9300041C \xf1\x894936', + b'\xf1\x870D9300041H \xf1\x895220', + b'\xf1\x870D9300041J \xf1\x894902', + b'\xf1\x870D9300041P \xf1\x894507', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655AC\xf1\x890200\xf1\x82\r11120011100010022212110200', + b'\xf1\x873Q0959655AP\xf1\x890305\xf1\x82\r11110011110011213331312131', + b'\xf1\x873Q0959655AQ\xf1\x890200\xf1\x82\r11120011100010312212113100', + b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0163221003221002105755331052100', + b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', + b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', + b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111101000011110006110411111111119111', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A01513A1', + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\00521T00403A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521T00403A1', + b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521T00601A1', + b'\xf1\x875QD909144E \xf1\x891081\xf1\x82\x0521T00503A1', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', + b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\00101', + b'\xf1\x875Q0907572J \xf1\x890654', + b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', + b'\xf1\x875Q0907572P \xf1\x890682', + b'\xf1\x875Q0907572R \xf1\x890771', + ], + }, + CAR.SKODA_SCALA_MK1: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704C906025AK\xf1\x897053', + b'\xf1\x8705C906032M \xf1\x892365', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300020 \xf1\x891907', + b'\xf1\x870CW300050 \xf1\x891709', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14', + b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14', + b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1311150411110411210404040417151215391413', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872Q1909144M \xf1\x896041', + b'\xf1\x872Q1909144AB\xf1\x896050', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572AA\xf1\x890396', + ], + }, + CAR.SKODA_SUPERB_MK3: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906027BS\xf1\x892887', + b'\xf1\x8704E906027BT\xf1\x899042', + b'\xf1\x8704L906026ET\xf1\x891343', + b'\xf1\x8704L906026ET\xf1\x891990', + b'\xf1\x8704L906026FP\xf1\x891196', + b'\xf1\x8704L906026KA\xf1\x896014', + b'\xf1\x8704L906026KB\xf1\x894071', + b'\xf1\x8704L906026KD\xf1\x894798', + b'\xf1\x8704L906026MT\xf1\x893076', + b'\xf1\x8705L906022BK\xf1\x899971', + b'\xf1\x873G0906259 \xf1\x890004', + b'\xf1\x873G0906259B \xf1\x890002', + b'\xf1\x873G0906259L \xf1\x890003', + b'\xf1\x873G0906264A \xf1\x890002', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300042H \xf1\x891601', + b'\xf1\x870CW300043B \xf1\x891603', + b'\xf1\x870CW300049Q \xf1\x890906', + b'\xf1\x870D9300011T \xf1\x894801', + b'\xf1\x870D9300012 \xf1\x894940', + b'\xf1\x870D9300013A \xf1\x894905', + b'\xf1\x870D9300014K \xf1\x895006', + b'\xf1\x870D9300041H \xf1\x894905', + b'\xf1\x870D9300042M \xf1\x895013', + b'\xf1\x870D9300043F \xf1\x895202', + b'\xf1\x870GC300013K \xf1\x892403', + b'\xf1\x870GC300014M \xf1\x892801', + b'\xf1\x870GC300019G \xf1\x892803', + b'\xf1\x870GC300043 \xf1\x892301', + b'\xf1\x870GC300046D \xf1\x892402', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111', + b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111', + b'\xf1\x875Q0959655AK\xf1\x890130\xf1\x82\022111200111121001121110012211292221111', + b'\xf1\x875Q0959655AS\xf1\x890317\xf1\x82\x1331310031313100313131823133319331313100', + b'\xf1\x875Q0959655AT\xf1\x890317\xf1\x82\x1331310031313100313131013131319331313100', + b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100', + b'\xf1\x875Q0959655BK\xf1\x890336\xf1\x82\x1331310031313100313131013141319331413100', + b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1333310031313100313152015351539331423100', + b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151013141319331423100', + b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151823143319331423100', + b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1333310031313100313152025350539331463100', + b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1333310031313100313152855372539331463100', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514UZ070203', + b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ050303', + b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ070303', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ070505', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ060505', + b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\00563UZ060700', + b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060600', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070500', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070600', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070700', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x873Q0907572B \xf1\x890192', + b'\xf1\x873Q0907572B \xf1\x890194', + b'\xf1\x873Q0907572C \xf1\x890195', + b'\xf1\x875Q0907572R \xf1\x890771', + b'\xf1\x875Q0907572S \xf1\x890780', + ], + }, +} diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index c47dd2d161..35e88895db 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -303,1152 +303,3 @@ FW_QUERY_CONFIG = FwQueryConfig( ), ], ) - -FW_VERSIONS = { - CAR.ARTEON_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x873G0906259AH\xf1\x890001', - b'\xf1\x873G0906259F \xf1\x890004', - b'\xf1\x873G0906259G \xf1\x890004', - b'\xf1\x873G0906259G \xf1\x890005', - b'\xf1\x873G0906259M \xf1\x890003', - b'\xf1\x873G0906259N \xf1\x890004', - b'\xf1\x873G0906259P \xf1\x890001', - b'\xf1\x875NA907115H \xf1\x890002', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8709G927158L \xf1\x893611', - b'\xf1\x870DL300014C \xf1\x893704', - b'\xf1\x870GC300011L \xf1\x891401', - b'\xf1\x870GC300014M \xf1\x892802', - b'\xf1\x870GC300019G \xf1\x892804', - b'\xf1\x870GC300040P \xf1\x891401', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121157161111572900', - b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121177161113772900', - b'\xf1\x873Q0959655CK\xf1\x890711\xf1\x82\x0e1712141712141105121122052900', - b'\xf1\x873Q0959655DA\xf1\x890720\xf1\x82\x0e1712141712141105121122052900', - b'\xf1\x873Q0959655DL\xf1\x890732\xf1\x82\x0e1812141812171105141123052J00', - b'\xf1\x875QF959655AP\xf1\x890755\xf1\x82\x1311110011111311111100110200--1611125F49', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571B41815A1', - b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571B00817A1', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020800', - b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002MB4092M7N', - b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002NB4202N7N', - b'\xf1\x875WA907145Q \xf1\x891063\xf1\x82\x002KB4092KOM', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572T \xf1\x890383', - b'\xf1\x875Q0907572J \xf1\x890654', - b'\xf1\x875Q0907572R \xf1\x890771', - ], - }, - CAR.ATLAS_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8703H906026AA\xf1\x899970', - b'\xf1\x8703H906026AG\xf1\x899973', - b'\xf1\x8703H906026AJ\xf1\x890638', - b'\xf1\x8703H906026AJ\xf1\x891017', - b'\xf1\x8703H906026AT\xf1\x891922', - b'\xf1\x8703H906026BC\xf1\x892664', - b'\xf1\x8703H906026F \xf1\x896696', - b'\xf1\x8703H906026F \xf1\x899970', - b'\xf1\x8703H906026J \xf1\x896026', - b'\xf1\x8703H906026J \xf1\x899971', - b'\xf1\x8703H906026S \xf1\x896693', - b'\xf1\x8703H906026S \xf1\x899970', - b'\xf1\x873CN906259 \xf1\x890005', - b'\xf1\x873CN906259F \xf1\x890002', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8709G927158A \xf1\x893387', - b'\xf1\x8709G927158DR\xf1\x893536', - b'\xf1\x8709G927158DR\xf1\x893742', - b'\xf1\x8709G927158EN\xf1\x893691', - b'\xf1\x8709G927158F \xf1\x893489', - b'\xf1\x8709G927158FT\xf1\x893835', - b'\xf1\x8709G927158GL\xf1\x893939', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655BC\xf1\x890503\xf1\x82\0161914151912001103111122031200', - b'\xf1\x873Q0959655BN\xf1\x890713\xf1\x82\0162214152212001105141122052900', - b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\x0e1114151112001105111122052900', - b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\0162214152212001105141122052900', - b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1114151112001105161122052J00', - b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1115151112001105171122052J00', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\00571B60924A1', - b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6G920A1', - b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6M921A1', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6080105', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6090105', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572R \xf1\x890372', - b'\xf1\x872Q0907572T \xf1\x890383', - b'\xf1\x875Q0907572H \xf1\x890620', - b'\xf1\x875Q0907572J \xf1\x890654', - b'\xf1\x875Q0907572P \xf1\x890682', - ], - }, - CAR.CRAFTER_MK2: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704L906056BP\xf1\x894729', - b'\xf1\x8704L906056EK\xf1\x896391', - b'\xf1\x8705L906023BC\xf1\x892688', - ], - # Only current upstreamed vehicle has a manual transmission - #(Ecu.transmission, 0x7e1, None): [ - #], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655AL\xf1\x890505\xf1\x82\x0e1411001413001203151311031100', - b'\xf1\x873Q0959655BG\xf1\x890703\xf1\x82\x0e16120016130012051G1313052900', - b'\xf1\x875QF959655AS\xf1\x890755\xf1\x82\x1315140015150011111100050200--1311120749', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x872N0909143D\x00\xf1\x897010\xf1\x82\x05183AZ306A2', - b'\xf1\x872N0909143E \xf1\x897021\xf1\x82\x05163AZ306A2', - b'\xf1\x872N0909144K \xf1\x897045\xf1\x82\x05233AZ810A2', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572J \xf1\x890156', - b'\xf1\x872Q0907572M \xf1\x890233', - ], - }, - CAR.GOLF_MK7: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704E906016A \xf1\x897697', - b'\xf1\x8704E906016N \xf1\x899105', - b'\xf1\x8704E906016AD\xf1\x895758', - b'\xf1\x8704E906016CE\xf1\x899096', - b'\xf1\x8704E906016CH\xf1\x899226', - b'\xf1\x8704E906023AG\xf1\x891726', - b'\xf1\x8704E906023BN\xf1\x894518', - b'\xf1\x8704E906024K \xf1\x896811', - b'\xf1\x8704E906027GR\xf1\x892394', - b'\xf1\x8704E906027HD\xf1\x892603', - b'\xf1\x8704E906027HD\xf1\x893742', - b'\xf1\x8704E906027MA\xf1\x894958', - b'\xf1\x8704L906021DT\xf1\x895520', - b'\xf1\x8704L906021DT\xf1\x898127', - b'\xf1\x8704L906021N \xf1\x895518', - b'\xf1\x8704L906021N \xf1\x898138', - b'\xf1\x8704L906026BN\xf1\x891197', - b'\xf1\x8704L906026BP\xf1\x897608', - b'\xf1\x8704L906026NF\xf1\x899528', - b'\xf1\x8704L906056CL\xf1\x893823', - b'\xf1\x8704L906056CR\xf1\x895813', - b'\xf1\x8704L906056HE\xf1\x893758', - b'\xf1\x8704L906056HN\xf1\x896590', - b'\xf1\x8704L906056HT\xf1\x896591', - b'\xf1\x870EA906016A \xf1\x898343', - b'\xf1\x870EA906016E \xf1\x894219', - b'\xf1\x870EA906016F \xf1\x894238', - b'\xf1\x870EA906016F \xf1\x895002', - b'\xf1\x870EA906016Q \xf1\x895993', - b'\xf1\x870EA906016S \xf1\x897207', - b'\xf1\x875G0906259 \xf1\x890007', - b'\xf1\x875G0906259D \xf1\x890002', - b'\xf1\x875G0906259J \xf1\x890002', - b'\xf1\x875G0906259L \xf1\x890002', - b'\xf1\x875G0906259N \xf1\x890003', - b'\xf1\x875G0906259Q \xf1\x890002', - b'\xf1\x875G0906259Q \xf1\x892313', - b'\xf1\x875G0906259T \xf1\x890003', - b'\xf1\x878V0906259H \xf1\x890002', - b'\xf1\x878V0906259J \xf1\x890003', - b'\xf1\x878V0906259K \xf1\x890001', - b'\xf1\x878V0906259K \xf1\x890003', - b'\xf1\x878V0906259P \xf1\x890001', - b'\xf1\x878V0906259Q \xf1\x890002', - b'\xf1\x878V0906259R \xf1\x890002', - b'\xf1\x878V0906264F \xf1\x890003', - b'\xf1\x878V0906264L \xf1\x890002', - b'\xf1\x878V0906264M \xf1\x890001', - b'\xf1\x878V09C0BB01 \xf1\x890001', - b'\xf1\x8704E906024K \xf1\x899970', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8709G927749AP\xf1\x892943', - b'\xf1\x8709S927158A \xf1\x893585', - b'\xf1\x870CW300040H \xf1\x890606', - b'\xf1\x870CW300041D \xf1\x891004', - b'\xf1\x870CW300041H \xf1\x891010', - b'\xf1\x870CW300042F \xf1\x891604', - b'\xf1\x870CW300043B \xf1\x891601', - b'\xf1\x870CW300043E \xf1\x891603', - b'\xf1\x870CW300044S \xf1\x894530', - b'\xf1\x870CW300044T \xf1\x895245', - b'\xf1\x870CW300045 \xf1\x894531', - b'\xf1\x870CW300047D \xf1\x895261', - b'\xf1\x870CW300047E \xf1\x895261', - b'\xf1\x870CW300048J \xf1\x890611', - b'\xf1\x870CW300049H \xf1\x890905', - b'\xf1\x870CW300050G \xf1\x891905', - b'\xf1\x870D9300012 \xf1\x894904', - b'\xf1\x870D9300012 \xf1\x894913', - b'\xf1\x870D9300012 \xf1\x894937', - b'\xf1\x870D9300012 \xf1\x895045', - b'\xf1\x870D9300012 \xf1\x895046', - b'\xf1\x870D9300014M \xf1\x895004', - b'\xf1\x870D9300014Q \xf1\x895006', - b'\xf1\x870D9300020J \xf1\x894902', - b'\xf1\x870D9300020Q \xf1\x895201', - b'\xf1\x870D9300020S \xf1\x895201', - b'\xf1\x870D9300040A \xf1\x893613', - b'\xf1\x870D9300040S \xf1\x894311', - b'\xf1\x870D9300041H \xf1\x895220', - b'\xf1\x870D9300041P \xf1\x894507', - b'\xf1\x870DD300045K \xf1\x891120', - b'\xf1\x870DD300046F \xf1\x891601', - b'\xf1\x870GC300012A \xf1\x891401', - b'\xf1\x870GC300012A \xf1\x891403', - b'\xf1\x870GC300014B \xf1\x892401', - b'\xf1\x870GC300014B \xf1\x892403', - b'\xf1\x870GC300014B \xf1\x892405', - b'\xf1\x870GC300020G \xf1\x892401', - b'\xf1\x870GC300020G \xf1\x892403', - b'\xf1\x870GC300020G \xf1\x892404', - b'\xf1\x870GC300020N \xf1\x892804', - b'\xf1\x870GC300043T \xf1\x899999', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\x111413001113120043114317121C111C9113', - b'\xf1\x875Q0959655AA\xf1\x890386\xf1\x82\x111413001113120053114317121C111C9113', - b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120043114317121C111C9113', - b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120043114417121411149113', - b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120053114317121C111C9113', - b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\x13141500111233003142114A2131219333313100', - b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333423100', - b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100', - b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2251229333463100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2251229333463100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', - b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', - b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', - b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271212111312--071104171838103891131211', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13341512112212--071104172328102891131211', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13272512111312--07110417182C102C91131211', - b'\xf1\x875Q0959655M \xf1\x890361\xf1\x82\x111413001112120041114115121611169112', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200621143171717111791132111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200061104171724102491132111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200621143171724112491132111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200061104171717101791132111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200631143171724122491132111', - b'\xf1\x875Q0959655T \xf1\x890825\xf1\x82\x13271200111312--071104171837103791132111', - b'\xf1\x875Q0959655T \xf1\x890830\xf1\x82\x13271100111312--071104171826102691131211', - b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111413001113120006110417121D101D9112', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561A01612A0', - b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\x0566A0J612A1', - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A00514A1', - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01613A1', - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1', - b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1', - b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1', - b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A01A18A1', - b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A02A16A1', - b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A0JA16A1', - b'\xf1\x873QM909144 \xf1\x895072\xf1\x82\x0571A01714A1', - b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820519A9040203', - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00441A1', - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00608A1', - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521A00641A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00442A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00642A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A07B05A1', - b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00602A0', - b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0522A00402A0', - b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00502A0', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511A00403A0', - b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00404A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00504A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00604A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A07A02A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00407A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00507A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A07B04A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A20B03A1', - b'\xf1\x875QD909144B \xf1\x891072\xf1\x82\x0521A00507A1', - b'\xf1\x875QM909144A \xf1\x891072\xf1\x82\x0521A20B03A1', - b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00442A1', - b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A16A1', - b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A18A1', - b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A17A1', - b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\x0571A01A18A1', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000400', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\x0101', - b'\xf1\x875Q0907567J \xf1\x890396\xf1\x82\x0101', - b'\xf1\x875Q0907567L \xf1\x890098\xf1\x82\x0101', - b'\xf1\x875Q0907572A \xf1\x890141\xf1\x82\x0101', - b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\x0101', - b'\xf1\x875Q0907572C \xf1\x890210\xf1\x82\x0101', - b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', - b'\xf1\x875Q0907572E \xf1\x89X310\xf1\x82\x0101', - b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', - b'\xf1\x875Q0907572G \xf1\x890571', - b'\xf1\x875Q0907572H \xf1\x890620', - b'\xf1\x875Q0907572J \xf1\x890653', - b'\xf1\x875Q0907572J \xf1\x890654', - b'\xf1\x875Q0907572P \xf1\x890682', - b'\xf1\x875Q0907572R \xf1\x890771', - b'\xf1\x875Q0907572S \xf1\x890780', - ], - }, - CAR.JETTA_MK7: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704E906024AK\xf1\x899937', - b'\xf1\x8704E906024AS\xf1\x899912', - b'\xf1\x8704E906024BC\xf1\x899971', - b'\xf1\x8704E906024BG\xf1\x891057', - b'\xf1\x8704E906024B \xf1\x895594', - b'\xf1\x8704E906024C \xf1\x899970', - b'\xf1\x8704E906024L \xf1\x895595', - b'\xf1\x8704E906024L \xf1\x899970', - b'\xf1\x8704E906027MS\xf1\x896223', - b'\xf1\x875G0906259T \xf1\x890003', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8709G927158BQ\xf1\x893545', - b'\xf1\x8709S927158BS\xf1\x893642', - b'\xf1\x8709S927158BS\xf1\x893694', - b'\xf1\x8709S927158CK\xf1\x893770', - b'\xf1\x8709S927158R \xf1\x893552', - b'\xf1\x8709S927158R \xf1\x893587', - b'\xf1\x870GC300020N \xf1\x892803', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AG\xf1\x890336\xf1\x82\02314171231313500314611011630169333463100', - b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1314171231313500314611011630169333463100', - b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314642011650169333463100', - b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314643011650169333463100', - b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02311170031313300314240011150119333433100', - b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02319170031313300314240011550159333463100', - b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314643021650169333613100', - b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314642021650169333613100', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A10A11A1', - b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\00521A10A01A1', - b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521B00404A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A00642A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A10A01A1', - b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\00571A10A11A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x875Q0907572N \xf1\x890681', - b'\xf1\x875Q0907572P \xf1\x890682', - b'\xf1\x875Q0907572R \xf1\x890771', - ], - }, - CAR.PASSAT_MK8: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8703N906026E \xf1\x892114', - b'\xf1\x8704E906023AH\xf1\x893379', - b'\xf1\x8704E906023BM\xf1\x894522', - b'\xf1\x8704L906026DP\xf1\x891538', - b'\xf1\x8704L906026ET\xf1\x891990', - b'\xf1\x8704L906026FP\xf1\x892012', - b'\xf1\x8704L906026GA\xf1\x892013', - b'\xf1\x8704L906026KD\xf1\x894798', - b'\xf1\x873G0906259B \xf1\x890002', - b'\xf1\x873G0906259 \xf1\x890004', - b'\xf1\x873G0906264 \xf1\x890004', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300043H \xf1\x891601', - b'\xf1\x870CW300048R \xf1\x890610', - b'\xf1\x870D9300013A \xf1\x894905', - b'\xf1\x870D9300014L \xf1\x895002', - b'\xf1\x870D9300041A \xf1\x894801', - b'\xf1\x870DD300045T \xf1\x891601', - b'\xf1\x870DD300046H \xf1\x891601', - b'\xf1\x870DL300011H \xf1\x895201', - b'\xf1\x870CW300042H \xf1\x891601', - b'\xf1\x870CW300042H \xf1\x891607', - b'\xf1\x870GC300042H \xf1\x891404', - b'\xf1\x870D9300018C \xf1\x895297', - b'\xf1\x870D9300042H \xf1\x894901', - b'\xf1\x870GC300043 \xf1\x892301', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111', - b'\xf1\x873Q0959655AF\xf1\x890195\xf1\x82\r56140056130012026612120211', - b'\xf1\x873Q0959655AN\xf1\x890305\xf1\x82\r58160058140013036914110311', - b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311', - b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111', - b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', - b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e5915005914001305701311052900', - b'\xf1\x873Q0959655BG\xf1\x890712\xf1\x82\x0e5915005914001305701311052900', - b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900', - b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e5915005914001354701311542900', - b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900', - b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012416612124111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1', - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00711A1', - b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514B0060703', - b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803', - b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526B0060905', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516B00501A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521B00703A1', - b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563B0000600', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00603A1', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x873Q0907572A \xf1\x890126', - b'\xf1\x873Q0907572A \xf1\x890130', - b'\xf1\x873Q0907572B \xf1\x890192', - b'\xf1\x873Q0907572C \xf1\x890195', - b'\xf1\x873Q0907572C \xf1\x890196', - b'\xf1\x875Q0907572P \xf1\x890682', - b'\xf1\x873Q0907572B \xf1\x890194', - b'\xf1\x875Q0907572R \xf1\x890771', - ], - }, - CAR.PASSAT_NMS: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8706K906016C \xf1\x899609', - b'\xf1\x8706K906016E \xf1\x899830', - b'\xf1\x8706K906016G \xf1\x891124', - b'\xf1\x8706K906071BJ\xf1\x894891', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8709G927158AB\xf1\x893318', - b'\xf1\x8709G927158BD\xf1\x893121', - b'\xf1\x8709G927158DK\xf1\x893594', - b'\xf1\x8709G927158FQ\xf1\x893745', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x87561959655 \xf1\x890210\xf1\x82\02212121111113000102011--121012--101312', - b'\xf1\x87561959655C \xf1\x890508\xf1\x82\02215141111121100314919--153015--304831', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x87561907567A \xf1\x890132', - b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\00152', - ], - }, - CAR.POLO_MK6: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704C906025H \xf1\x895177', - b'\xf1\x8705C906032J \xf1\x891702', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300042D \xf1\x891612', - b'\xf1\x870CW300050D \xf1\x891908', - b'\xf1\x870CW300051G \xf1\x891909', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x872Q0959655AG\xf1\x890248\xf1\x82\x1218130411110411--04040404231811152H14', - b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1248130411110416--04040404784811152H14', - b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1384830511110516041405820599841215391471', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x872Q1909144M \xf1\x896041', - b'\xf1\x872Q2909144AB\xf1\x896050', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572R \xf1\x890372', - ], - }, - CAR.SHARAN_MK2: { - # TODO: Sharan Mk2 EPS and DQ250 auto trans both require KWP2000 support for fingerprinting - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704L906016HE\xf1\x894635', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x877N0959655D \xf1\x890016\xf1\x82\x0801100705----10--', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\x0153', - ], - }, - CAR.TAOS_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704E906027NJ\xf1\x891445', - b'\xf1\x8704E906027NP\xf1\x891286', - b'\xf1\x8705E906013BD\xf1\x892496', - b'\xf1\x8705E906013E \xf1\x891624', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8709G927158EM\xf1\x893812', - b'\xf1\x8709S927158BL\xf1\x893791', - b'\xf1\x8709S927158DN\xf1\x893946', - b'\xf1\x8709S927158FF\xf1\x893876', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1311111111333500314646021450149333613100', - b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1312111111333500314646021550159333613100', - b'\xf1\x875Q0959655CE\xf1\x890421\xf1\x82\x1311110011333300314240021350139333613100', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x001O06081OOM', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060405A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521060605A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572T \xf1\x890383', - ], - }, - CAR.TCROSS_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704C906025AK\xf1\x897053', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300050E \xf1\x891903', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\02212130411110411--04041104141311152H14', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x872Q1909144M \xf1\x896041', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572T \xf1\x890383', - ], - }, - CAR.TIGUAN_MK2: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8703N906026D \xf1\x893680', - b'\xf1\x8704E906027NB\xf1\x899504', - b'\xf1\x8704L906026EJ\xf1\x893661', - b'\xf1\x8704L906027G \xf1\x899893', - b'\xf1\x8705E906018BS\xf1\x890914', - b'\xf1\x875N0906259 \xf1\x890002', - b'\xf1\x875NA906259H \xf1\x890002', - b'\xf1\x875NA907115E \xf1\x890003', - b'\xf1\x875NA907115E \xf1\x890005', - b'\xf1\x875NA907115J \xf1\x890002', - b'\xf1\x8783A907115B \xf1\x890005', - b'\xf1\x8783A907115F \xf1\x890002', - b'\xf1\x8783A907115G \xf1\x890001', - b'\xf1\x8783A907115K \xf1\x890001', - b'\xf1\x8783A907115K \xf1\x890002', - b'\xf1\x8704E906024AP\xf1\x891461', - b'\xf1\x8783A907115 \xf1\x890007', - b'\xf1\x8783A907115Q \xf1\x890001', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8709G927158DT\xf1\x893698', - b'\xf1\x8709G927158FM\xf1\x893757', - b'\xf1\x8709G927158GC\xf1\x893821', - b'\xf1\x8709G927158GD\xf1\x893820', - b'\xf1\x8709G927158GM\xf1\x893936', - b'\xf1\x8709G927158GN\xf1\x893938', - b'\xf1\x8709G927158HB\xf1\x894069', - b'\xf1\x870D9300043 \xf1\x895202', - b'\xf1\x870DL300011N \xf1\x892001', - b'\xf1\x870DL300011N \xf1\x892012', - b'\xf1\x870DL300012M \xf1\x892107', - b'\xf1\x870DL300012P \xf1\x892103', - b'\xf1\x870DL300013A \xf1\x893005', - b'\xf1\x870DL300013G \xf1\x892119', - b'\xf1\x870DL300013G \xf1\x892120', - b'\xf1\x870DL300014C \xf1\x893703', - b'\xf1\x870DD300046K \xf1\x892302', - b'\xf1\x870GC300013P \xf1\x892401', - b'\xf1\x870GC300046Q \xf1\x892802', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100', - b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', - b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1331310031333334313132013730379333423100', - b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100', - b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1312110031333300314240013750379333423100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100', - b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100', - b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100', - b'\xf1\x875Q0959655CD\xf1\x890421\xf1\x82\x13123112313333003145406F6154619333613100', - b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1316143231313500314617011730179333423100', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6050705', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', - b'\xf1\x875QF909144A \xf1\x895581\xf1\x82\x0571A60834A1', - b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\00571A60634A1', - b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571A62A32A1', - b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1', - b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002RA60A2ROM', - b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002SA6092SOM', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', - b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60804A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572AB\xf1\x890397', - b'\xf1\x872Q0907572J \xf1\x890156', - b'\xf1\x872Q0907572M \xf1\x890233', - b'\xf1\x872Q0907572Q \xf1\x890342', - b'\xf1\x872Q0907572R \xf1\x890372', - b'\xf1\x872Q0907572T \xf1\x890383', - ], - }, - CAR.TOURAN_MK2: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704L906026HM\xf1\x893017', - b'\xf1\x8705E906018CQ\xf1\x890808', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300041E \xf1\x891005', - b'\xf1\x870CW300051M \xf1\x891926', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AS\xf1\x890318\xf1\x82\023363500213533353141324C4732479333313100', - b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1336350021353336314740025250529333613100', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820531B0062105', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A8090400', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x873Q0907572C \xf1\x890195', - b'\xf1\x872Q0907572AA\xf1\x890396', - ], - }, - CAR.TRANSPORTER_T61: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704L906056AG\xf1\x899970', - b'\xf1\x8704L906056AL\xf1\x899970', - b'\xf1\x8704L906057AP\xf1\x891186', - b'\xf1\x8704L906057N \xf1\x890413', - b'\xf1\x8705L906023E \xf1\x891352', - b'\xf1\x8705L906023MR\xf1\x892582', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870BT300012G \xf1\x893102', - b'\xf1\x870BT300012E \xf1\x893105', - b'\xf1\x870BT300046R \xf1\x893102', - b'\xf1\x870DV300012B \xf1\x893701', - b'\xf1\x870DV300012B \xf1\x893702', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x872Q0959655AE\xf1\x890506\xf1\x82\x1316170411110411--04041704161611152S1411', - b'\xf1\x872Q0959655AE\xf1\x890506\xf1\x82\x1316170411110411--04041704171711152S1411', - b'\xf1\x872Q0959655AF\xf1\x890506\xf1\x82\x1316171111110411--04041711121211152S1413', - b'\xf1\x872Q0959655AQ\xf1\x890511\xf1\x82\x1316170411110411--0404170426261215391421', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\x0532380518A2', - b'\xf1\x877LA909144G \xf1\x897160\xf1\x82\x05333A5519A2', - b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\x05323A5519A2', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572R \xf1\x890372', - b'\xf1\x872Q0907572AA\xf1\x890396', - ], - }, - CAR.TROC_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8705E906018AT\xf1\x899640', - b'\xf1\x8705E906018CK\xf1\x890863', - b'\xf1\x8705E906018P \xf1\x896020', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300050J \xf1\x891911', - b'\xf1\x870CW300051M \xf1\x891925', - b'\xf1\x870CW300051M \xf1\x891928', - b'\xf1\x870CW300041S \xf1\x891615', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1311110012333300314240681152119333463100', - b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x13111100123333003142404M1152119333613100', - b'\xf1\x875Q0959655CF\xf1\x890421\xf1\x82\x1311110012333300314240021150119333613100', - b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1111001111001105111111052900', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521060405A1', - b'\xf1\x875WA907144M \xf1\x891051\xf1\x82\x001T06081T7N', - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521060403A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572T \xf1\x890383', - b'\xf1\x872Q0907572M \xf1\x890233', - ], - }, - CAR.AUDI_A3_MK3: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704E906023AN\xf1\x893695', - b'\xf1\x8704E906023AR\xf1\x893440', - b'\xf1\x8704E906023BL\xf1\x895190', - b'\xf1\x8704E906027CJ\xf1\x897798', - b'\xf1\x8704L997022N \xf1\x899459', - b'\xf1\x875G0906259A \xf1\x890004', - b'\xf1\x875G0906259D \xf1\x890002', - b'\xf1\x875G0906259L \xf1\x890002', - b'\xf1\x875G0906259Q \xf1\x890002', - b'\xf1\x878V0906259E \xf1\x890001', - b'\xf1\x878V0906259F \xf1\x890002', - b'\xf1\x878V0906259H \xf1\x890002', - b'\xf1\x878V0906259J \xf1\x890002', - b'\xf1\x878V0906259K \xf1\x890001', - b'\xf1\x878V0906264B \xf1\x890003', - b'\xf1\x878V0907115B \xf1\x890007', - b'\xf1\x878V0907404A \xf1\x890005', - b'\xf1\x878V0907404G \xf1\x890005', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300044T \xf1\x895245', - b'\xf1\x870CW300048 \xf1\x895201', - b'\xf1\x870D9300012 \xf1\x894912', - b'\xf1\x870D9300012 \xf1\x894931', - b'\xf1\x870D9300012L \xf1\x894521', - b'\xf1\x870D9300012K \xf1\x894513', - b'\xf1\x870D9300013B \xf1\x894902', - b'\xf1\x870D9300013B \xf1\x894931', - b'\xf1\x870D9300041N \xf1\x894512', - b'\xf1\x870D9300043T \xf1\x899699', - b'\xf1\x870DD300046 \xf1\x891604', - b'\xf1\x870DD300046A \xf1\x891602', - b'\xf1\x870DD300046F \xf1\x891602', - b'\xf1\x870DD300046G \xf1\x891601', - b'\xf1\x870DL300012E \xf1\x892012', - b'\xf1\x870DL300012H \xf1\x892112', - b'\xf1\x870GC300011 \xf1\x890403', - b'\xf1\x870GC300013M \xf1\x892402', - b'\xf1\x870GC300042J \xf1\x891402', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AB\xf1\x890388\xf1\x82\0211111001111111206110412111321139114', - b'\xf1\x875Q0959655AM\xf1\x890315\xf1\x82\x1311111111111111311411011231129321212100', - b'\xf1\x875Q0959655AM\xf1\x890318\xf1\x82\x1311111111111112311411011531159321212100', - b'\xf1\x875Q0959655AR\xf1\x890315\xf1\x82\x1311110011131115311211012331239321212100', - b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1311110011131100311111011731179321342100', - b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', - b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13111112111111--241115141112221291163221', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111211--261117141112231291163221', - b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112110004110411111421149114', - b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561G01A13A0', - b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566G0HA14A1', - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566G0HA14A1', - b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1', - b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1', - b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0JA13A1', - b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521G0G809A1', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00303A0', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00803A0', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0503G0G803A0', - b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\00516G00804A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516G00804A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521G00807A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x875Q0907567M \xf1\x890398\xf1\x82\x0101', - b'\xf1\x875Q0907567N \xf1\x890400\xf1\x82\x0101', - b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', - b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', - b'\xf1\x875Q0907572G \xf1\x890571', - b'\xf1\x875Q0907572H \xf1\x890620', - b'\xf1\x875Q0907572P \xf1\x890682', - ], - }, - CAR.AUDI_Q2_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704E906027JT\xf1\x894145', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300041F \xf1\x891006', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655BD\xf1\x890336\xf1\x82\x1311111111111100311211011231129321312111', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571F60511A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572M \xf1\x890233', - ], - }, - CAR.AUDI_Q3_MK2: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8705E906018N \xf1\x899970', - b'\xf1\x8705L906022M \xf1\x890901', - b'\xf1\x8783A906259 \xf1\x890001', - b'\xf1\x8783A906259 \xf1\x890005', - b'\xf1\x8783A906259C \xf1\x890002', - b'\xf1\x8783A906259D \xf1\x890001', - b'\xf1\x8783A906259F \xf1\x890001', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x8709G927158CN\xf1\x893608', - b'\xf1\x8709G927158FL\xf1\x893758', - b'\xf1\x8709G927158GG\xf1\x893825', - b'\xf1\x8709G927158GP\xf1\x893937', - b'\xf1\x870GC300045D \xf1\x892802', - b'\xf1\x870GC300046F \xf1\x892701', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655BF\xf1\x890403\xf1\x82\x1321211111211200311121232152219321422111', - b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218C219321532111', - b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111224118A119321532111', - b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111', - b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218A219321532111', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000800', - b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571G60533A1', - b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571G60733A1', - b'\xf1\x875TA907145D \xf1\x891051\xf1\x82\x001PG60A1P7N', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572R \xf1\x890372', - b'\xf1\x872Q0907572T \xf1\x890383', - ], - }, - CAR.SEAT_ATECA_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704E906027KA\xf1\x893749', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870D9300014S \xf1\x895202', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0161212001211001305121211052900', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\00571N60511A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572M \xf1\x890233', - ], - }, - CAR.SEAT_LEON_MK3: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704L906021EL\xf1\x897542', - b'\xf1\x8704L906026BP\xf1\x891198', - b'\xf1\x8704L906026BP\xf1\x897608', - b'\xf1\x8704L906056CR\xf1\x892181', - b'\xf1\x8704L906056CR\xf1\x892797', - b'\xf1\x8705E906018AS\xf1\x899596', - b'\xf1\x878V0906264H \xf1\x890005', - b'\xf1\x878V0907115E \xf1\x890002', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300041D \xf1\x891004', - b'\xf1\x870CW300041G \xf1\x891003', - b'\xf1\x870CW300050J \xf1\x891908', - b'\xf1\x870D9300042M \xf1\x895016', - b'\xf1\x870GC300043A \xf1\x892304', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655AC\xf1\x890189\xf1\x82\r11110011110011021511110200', - b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11110011110011021511110200', - b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r12110012120012021612110200', - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1312001313001305171311052900', - b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1312001313001305171311052900', - b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\0161312001313001305171311052900', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521N01842A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521N01342A1', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00511N01805A0', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N01309A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521N05808A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101', - b'\xf1\x875Q0907572H \xf1\x890620', - b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', - b'\xf1\x875Q0907572P \xf1\x890682', - b'\xf1\x875Q0907572R \xf1\x890771', - ], - }, - CAR.SKODA_FABIA_MK4: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8705E906018CF\xf1\x891905', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300051M \xf1\x891936', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100110200--1111120749', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x872Q1909144S \xf1\x896042', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - ], - }, - CAR.SKODA_KAMIQ_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8705C906032M \xf1\x891333', - b'\xf1\x8705E906013CK\xf1\x892540', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300020 \xf1\x891906', - b'\xf1\x870CW300020T \xf1\x892204', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\0222221042111042121040404042E2711152H14', - b'\xf1\x872Q0959655BJ\xf1\x890412\xf1\x82\x132223042111042121040404042B251215391423', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x872Q1909144M \xf1\x896041', - b'\xf1\x872Q1909144AB\xf1\x896050', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572T \xf1\x890383', - b'\xf1\x872Q0907572AA\xf1\x890396', - ], - }, - CAR.SKODA_KAROQ_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8705E906013H \xf1\x892407', - b'\xf1\x8705E906018P \xf1\x895472', - b'\xf1\x8705E906018P \xf1\x896020', - b'\xf1\x8705L906022BS\xf1\x890913', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300020T \xf1\x892202', - b'\xf1\x870CW300041S \xf1\x891615', - b'\xf1\x870GC300014L \xf1\x892802', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1312110012120011111100010200--2521210749', - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001101131112012100', - b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\0161213001211001101131122012100', - b'\xf1\x873Q0959655DE\xf1\x890731\xf1\x82\x0e1213001211001101131121012J00', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563T6090500', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100500', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T6100700', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572AB\xf1\x890397', - b'\xf1\x872Q0907572M \xf1\x890233', - b'\xf1\x872Q0907572T \xf1\x890383', - ], - }, - CAR.SKODA_KODIAQ_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704E906027DD\xf1\x893123', - b'\xf1\x8704E906027NB\xf1\x899504', - b'\xf1\x8704E906027NB\xf1\x896517', - b'\xf1\x8704L906026DE\xf1\x895418', - b'\xf1\x8704L906026EJ\xf1\x893661', - b'\xf1\x8704L906026HT\xf1\x893617', - b'\xf1\x8783A907115E \xf1\x890001', - b'\xf1\x8705E906018DJ\xf1\x890915', - b'\xf1\x8705E906018DJ\xf1\x891903', - b'\xf1\x875NA907115E \xf1\x890003', - b'\xf1\x875NA907115E \xf1\x890005', - b'\xf1\x875NA906259E \xf1\x890003', - b'\xf1\x8704E906027LD\xf1\x893433', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870D9300043 \xf1\x895202', - b'\xf1\x870DL300011N \xf1\x892014', - b'\xf1\x870DL300012M \xf1\x892107', - b'\xf1\x870DL300012N \xf1\x892110', - b'\xf1\x870DL300013G \xf1\x892119', - b'\xf1\x870GC300014N \xf1\x892801', - b'\xf1\x870GC300019H \xf1\x892806', - b'\xf1\x870GC300046Q \xf1\x892802', - b'\xf1\x870D9300014S \xf1\x895201', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211', - b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', - b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1213001211001244212111442100', - b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e1213001211001205212112052100', - b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', - b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', - b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121240749', - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6060405', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6070405', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G500', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G600', - b'\xf1\x875TA907145F \xf1\x891063\xf1\x82\x002LT61A2LOM', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572Q \xf1\x890342', - b'\xf1\x872Q0907572R \xf1\x890372', - b'\xf1\x872Q0907572T \xf1\x890383', - b'\xf1\x872Q0907572AA\xf1\x890396', - b'\xf1\x872Q0907572AB\xf1\x890397', - b'\xf1\x872Q0907572M \xf1\x890233', - ], - }, - CAR.SKODA_OCTAVIA_MK3: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704C906025L \xf1\x896198', - b'\xf1\x8704E906016ER\xf1\x895823', - b'\xf1\x8704E906027HD\xf1\x893742', - b'\xf1\x8704E906027MH\xf1\x894786', - b'\xf1\x8704L906021DT\xf1\x898127', - b'\xf1\x8704L906026BP\xf1\x897608', - b'\xf1\x8704L906026BS\xf1\x891541', - b'\xf1\x875G0906259C \xf1\x890002', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300041L \xf1\x891601', - b'\xf1\x870CW300041N \xf1\x891605', - b'\xf1\x870CW300043B \xf1\x891601', - b'\xf1\x870CW300043P \xf1\x891605', - b'\xf1\x870D9300041C \xf1\x894936', - b'\xf1\x870D9300041H \xf1\x895220', - b'\xf1\x870D9300041J \xf1\x894902', - b'\xf1\x870D9300041P \xf1\x894507', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655AC\xf1\x890200\xf1\x82\r11120011100010022212110200', - b'\xf1\x873Q0959655AP\xf1\x890305\xf1\x82\r11110011110011213331312131', - b'\xf1\x873Q0959655AQ\xf1\x890200\xf1\x82\r11120011100010312212113100', - b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200', - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0163221003221002105755331052100', - b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', - b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', - b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111101000011110006110411111111119111', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A01513A1', - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\00521T00403A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521T00403A1', - b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521T00601A1', - b'\xf1\x875QD909144E \xf1\x891081\xf1\x82\x0521T00503A1', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', - b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\00101', - b'\xf1\x875Q0907572J \xf1\x890654', - b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', - b'\xf1\x875Q0907572P \xf1\x890682', - b'\xf1\x875Q0907572R \xf1\x890771', - ], - }, - CAR.SKODA_SCALA_MK1: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704C906025AK\xf1\x897053', - b'\xf1\x8705C906032M \xf1\x892365', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300020 \xf1\x891907', - b'\xf1\x870CW300050 \xf1\x891709', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14', - b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14', - b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1311150411110411210404040417151215391413', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x872Q1909144M \xf1\x896041', - b'\xf1\x872Q1909144AB\xf1\x896050', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572R \xf1\x890372', - b'\xf1\x872Q0907572AA\xf1\x890396', - ], - }, - CAR.SKODA_SUPERB_MK3: { - (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8704E906027BS\xf1\x892887', - b'\xf1\x8704E906027BT\xf1\x899042', - b'\xf1\x8704L906026ET\xf1\x891343', - b'\xf1\x8704L906026ET\xf1\x891990', - b'\xf1\x8704L906026FP\xf1\x891196', - b'\xf1\x8704L906026KA\xf1\x896014', - b'\xf1\x8704L906026KB\xf1\x894071', - b'\xf1\x8704L906026KD\xf1\x894798', - b'\xf1\x8704L906026MT\xf1\x893076', - b'\xf1\x8705L906022BK\xf1\x899971', - b'\xf1\x873G0906259 \xf1\x890004', - b'\xf1\x873G0906259B \xf1\x890002', - b'\xf1\x873G0906259L \xf1\x890003', - b'\xf1\x873G0906264A \xf1\x890002', - ], - (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870CW300042H \xf1\x891601', - b'\xf1\x870CW300043B \xf1\x891603', - b'\xf1\x870CW300049Q \xf1\x890906', - b'\xf1\x870D9300011T \xf1\x894801', - b'\xf1\x870D9300012 \xf1\x894940', - b'\xf1\x870D9300013A \xf1\x894905', - b'\xf1\x870D9300014K \xf1\x895006', - b'\xf1\x870D9300041H \xf1\x894905', - b'\xf1\x870D9300042M \xf1\x895013', - b'\xf1\x870D9300043F \xf1\x895202', - b'\xf1\x870GC300013K \xf1\x892403', - b'\xf1\x870GC300014M \xf1\x892801', - b'\xf1\x870GC300019G \xf1\x892803', - b'\xf1\x870GC300043 \xf1\x892301', - b'\xf1\x870GC300046D \xf1\x892402', - ], - (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111', - b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111', - b'\xf1\x875Q0959655AK\xf1\x890130\xf1\x82\022111200111121001121110012211292221111', - b'\xf1\x875Q0959655AS\xf1\x890317\xf1\x82\x1331310031313100313131823133319331313100', - b'\xf1\x875Q0959655AT\xf1\x890317\xf1\x82\x1331310031313100313131013131319331313100', - b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100', - b'\xf1\x875Q0959655BK\xf1\x890336\xf1\x82\x1331310031313100313131013141319331413100', - b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1333310031313100313152015351539331423100', - b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151013141319331423100', - b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151823143319331423100', - b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1333310031313100313152025350539331463100', - b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1333310031313100313152855372539331463100', - ], - (Ecu.eps, 0x712, None): [ - b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514UZ070203', - b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ050303', - b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ070303', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ070505', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ060505', - b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\00563UZ060700', - b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060600', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070500', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070600', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070700', - ], - (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x873Q0907572B \xf1\x890192', - b'\xf1\x873Q0907572B \xf1\x890194', - b'\xf1\x873Q0907572C \xf1\x890195', - b'\xf1\x875Q0907572R \xf1\x890771', - b'\xf1\x875Q0907572S \xf1\x890780', - ], - }, -} diff --git a/selfdrive/debug/auto_fingerprint.py b/selfdrive/debug/auto_fingerprint.py index 10e438e546..689d3573aa 100755 --- a/selfdrive/debug/auto_fingerprint.py +++ b/selfdrive/debug/auto_fingerprint.py @@ -17,7 +17,7 @@ BRAND_TO_PLATFORMS = {brand: [car.value for car in ALL_CARS[brand]] for brand in PLATFORM_TO_BRAND = dict(sum([[(platform, brand) for platform in BRAND_TO_PLATFORMS[brand]] for brand in BRAND_TO_PLATFORMS], [])) def add_fw_versions(brand, platform, new_fw_versions): - filename = f"{BASEDIR}/selfdrive/car/{brand}/values.py" + filename = f"{BASEDIR}/selfdrive/car/{brand}/fingerprints.py" with open(filename, "r") as f: values_py = f.read() From b0b4ae9c15e5e4651a83bc9a0a857baf27513d4b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 18 Dec 2023 14:38:57 -0800 Subject: [PATCH 299/724] run test_fuzzy.py with pytest --- .github/workflows/selfdrive_tests.yaml | 3 +-- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 708f16368f..d037011701 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -181,8 +181,7 @@ jobs: ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py && \ - ./system/camerad/test/ae_gray_test && \ - ./selfdrive/test/process_replay/test_fuzzy.py" + ./system/camerad/test/ae_gray_test" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v3 with: diff --git a/pyproject.toml b/pyproject.toml index 93dd9456a1..a2f62f9e43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ testpaths = [ "selfdrive/navd/tests", "selfdrive/thermald", "selfdrive/test/longitudinal_maneuvers", + "selfdrive/test/process_replay/test_fuzzy.py", "system/hardware/tici", "system/loggerd", "system/proclogd", From cee84381f31f1f71b83df989a1b4b9e4592e4b6c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 18 Dec 2023 17:05:24 -0800 Subject: [PATCH 300/724] Update README.md (#30793) * Update README.md * Update README.md * Update README.md --- README.md | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 31f88158a1..db74ee6a1a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ What is openpilot? -Running on a dedicated device in a car +To start using openpilot in a car ------ To use openpilot in a car, you need four things: @@ -22,30 +22,21 @@ To use openpilot in a car, you need four things: 3. **Supported Car:** Ensure that you have one of [the 250+ supported cars](docs/CARS.md). 4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3/3X to your car. -We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). +We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play. -Running on PC ------- - -All openpilot services can run as usual on a PC without requiring special hardware or a car. You can also run openpilot on recorded or simulated data to develop or experiment with openpilot. - -With openpilot's tools, you can plot logs, replay drives, and watch the full-res camera streams. See [the tools README](tools/README.md) for more information. - -You can also run openpilot in simulation [with the MetaDrive simulator](tools/sim/README.md). This allows openpilot to drive around a virtual car on your Ubuntu machine. - -A PC running openpilot can also control your vehicle if it is connected to a [webcam](https://github.com/commaai/openpilot/tree/master/tools/webcam), a [black panda](https://comma.ai/shop/products/panda), and a [harness](https://comma.ai/shop/products/car-harness). - -Community and Contributing +To start developing openpilot ------ openpilot is developed by [comma](https://comma.ai/) and by users like you. We welcome both pull requests and issues on [GitHub](http://github.com/commaai/openpilot). * Join the [community Discord](https://discord.comma.ai) * Check out [the contributing docs](docs/CONTRIBUTING.md) +* Check out the [openpilot tools](tools/) +* Read about the [development workflow](docs/WORKFLOW.md) * Code documentation lives at https://docs.comma.ai * Information about running openpilot lives on the [community wiki](https://github.com/commaai/openpilot/wiki) -Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions) and offers lots of [bounties](docs/BOUNTIES.md). +Want to get paid to work on openpilot? [comma is hiring](https://comma.ai/jobs#open-positions) and offers lots of [bounties](docs/BOUNTIES.md) for external contributors. Safety and Testing ---- From 10b3a22897aa63f23b7cfb90691a8594e574cf6a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 18 Dec 2023 17:12:21 -0800 Subject: [PATCH 301/724] Add GH project to bounties.md (#30791) * update bounties * just replace the link --- docs/BOUNTIES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/BOUNTIES.md b/docs/BOUNTIES.md index 7d6af8540f..9d6f8bc97b 100644 --- a/docs/BOUNTIES.md +++ b/docs/BOUNTIES.md @@ -1,4 +1,4 @@ -# Bounties +# [Bounties](https://github.com/orgs/commaai/projects/26/views/1) Get paid to improve openpilot! @@ -14,7 +14,7 @@ New bounties can be proposed in the **#contributing** channel in Discord. ## Issue bounties -We've tagged bounty eligible issues across openpilot and the rest of our repos; check out all the open ones [here](https://github.com/search?q=org%3Acommaai+is%3Aissue+label%3Abounty++&type=issues&state=open). These bounties roughly work out like this: +We've tagged bounty eligible issues across openpilot and the rest of our repos; check out all the open ones [here](https://github.com/orgs/commaai/projects/26/views/1). These bounties roughly work out like this: * **$100** - a few hours of work for an experienced openpilot developer; a good intro for someone new to openpilot * **$300** - a day of work for an experienced openpilot developer * **$500** - a few days of work for an experienced openpilot developer From 5052b55c440f3a778f9db9c854fa385077dd7ab9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 19 Dec 2023 01:18:54 -0800 Subject: [PATCH 302/724] test_models: fuzz test panda and CarState (#30443) * pre-hypothesis * some hypothesis junk * this kinda works but is really slow due to counter check * choose addrs from fingerprint * stash * honda nidec brake pressed mismatches fixed * bump panda * stash * tesla: use DI_torque2 (panda msg) * run * run * ah this honda mismatch too * no more multi can msgs * clean up, remove old file * add todo * prob can remove urandom * stash, huge examples * fix pq standstill mismatch * yuge * yup there's a leak somewhere * try to find leak * skip dashcam (pq and tesla) * PR comments * bump * draft stash * fix alt brake hondas * bump * bump * bump * some clean up * minor clean up * more clean up * stash * fix honda bug * more * 100 examples * revert tesla * no memory leak any more? * bring back tests with skips * parameterize max_examples * skip interceptor * is jenkins on my branch? * ooh that's fast * 50 is not bad for GH CI * 300 might be better with rest of test_models * no more detection * bump * need CS_prev to catch bugs where openpilot changes and panda doesn't (eg. not setting interceptor safety mode) * need to simplify all this * need a warm up first, since some signals are 1 by default (toyota's gas_released!=1) * changes * set honda safety param * set toyota safety param * bump panda * clean up honda * rm interceptor * thought interleaving addrs might help, but we can fine tune later * Revert "thought interleaving addrs might help, but we can fine tune later" This reverts commit 153301384b48c9f33f9e2af3c224241eaeec41c1. * get size from dict * what * add nocapture marker * clean up * try to raise logging level * need to run last as pytest_runtest_call, since it starts capturing * get capman conditionally * mark * type fingerprint * should use gen_empty_fingerprint * no longer needed * draft * no longer need gc * clean that up * test everything! * more clean up * more * no point * fix that * fix errors * bump * nice even 300 examples for 300 segs * final bump :fingers_crossed: * better import order * remove debugging prints * warm up kinda works * Revert "warm up kinda works" This reverts commit 7fc77b07d592edb13eadca77deb49540954a7d69. * random seed * revert * strat strat * add expl comment * cmt * check controls allowed * Revert "check controls allowed" This reverts commit e82a0e5396810dd4670e6847aa555194a709e10f. * not unittests * run tests! * run tests 2! * run tests 3! * seed unused * revert * add shrink phase, and remove health check suppression * hello * oncemore * Update selfdrive/car/tests/test_models.py --- Jenkinsfile | 2 +- conftest.py | 18 +++++-- selfdrive/car/tests/test_models.py | 79 ++++++++++++++++++++++++++++-- 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a7f272cd80..9868777dfb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -268,7 +268,7 @@ node { 'car tests': { pcStage("car tests") { sh label: "build", script: "selfdrive/manager/build.py" - sh label: "run car tests", script: "cd selfdrive/car/tests && MAX_EXAMPLES=100 INTERNAL_SEG_CNT=250 FILEREADER_CACHE=1 \ + sh label: "run car tests", script: "cd selfdrive/car/tests && MAX_EXAMPLES=300 INTERNAL_SEG_CNT=300 FILEREADER_CACHE=1 \ INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt pytest test_models.py test_car_interfaces.py" } }, diff --git a/conftest.py b/conftest.py index 3c566e3672..6792bd0c3d 100644 --- a/conftest.py +++ b/conftest.py @@ -12,6 +12,17 @@ def pytest_sessionstart(session): session.config.option.randomly_reorganize = False +@pytest.hookimpl(hookwrapper=True, trylast=True) +def pytest_runtest_call(item): + # ensure we run as a hook after capturemanager's + if item.get_closest_marker("nocapture") is not None: + capmanager = item.config.pluginmanager.getplugin("capturemanager") + with capmanager.global_and_fixture_disabled(): + yield + else: + yield + + @pytest.fixture(scope="function", autouse=True) def openpilot_function_fixture(): starting_env = dict(os.environ) @@ -58,7 +69,8 @@ def pytest_collection_modifyitems(config, items): @pytest.hookimpl(trylast=True) def pytest_configure(config): - config_line = ( - "xdist_group_class_property: group tests by a property of the class that contains them" - ) + config_line = "xdist_group_class_property: group tests by a property of the class that contains them" + config.addinivalue_line("markers", config_line) + + config_line = "nocapture: don't capture test output" config.addinivalue_line("markers", config_line) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 2103b6ccce..e9c2a4ecd5 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -6,10 +6,12 @@ import pytest import random import unittest from collections import defaultdict, Counter +import hypothesis.strategies as st +from hypothesis import Phase, given, settings from typing import List, Optional, Tuple from parameterized import parameterized_class -from cereal import log, car +from cereal import messaging, log, car from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.realtime import DT_CTRL @@ -33,6 +35,7 @@ NUM_JOBS = int(os.environ.get("NUM_JOBS", "1")) JOB_ID = int(os.environ.get("JOB_ID", "0")) INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) +MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "50")) def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: @@ -67,6 +70,7 @@ class TestCarModelBase(unittest.TestCase): ci: bool = True can_msgs: List[capnp.lib.capnp._DynamicStructReader] + fingerprint: dict[int, dict[int, int]] elm_frame: Optional[int] car_safety_mode_frame: Optional[int] @@ -105,7 +109,7 @@ class TestCarModelBase(unittest.TestCase): can_msgs = [] cls.elm_frame = None cls.car_safety_mode_frame = None - fingerprint = gen_empty_fingerprint() + cls.fingerprint = gen_empty_fingerprint() experimental_long = False for msg in lr: if msg.which() == "can": @@ -113,7 +117,7 @@ class TestCarModelBase(unittest.TestCase): if len(can_msgs) <= FRAME_FINGERPRINT: for m in msg.can: if m.src < 64: - fingerprint[m.src][m.address] = len(m.dat) + cls.fingerprint[m.src][m.address] = len(m.dat) elif msg.which() == "carParams": car_fw = msg.carParams.carFw @@ -149,7 +153,7 @@ class TestCarModelBase(unittest.TestCase): cls.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime) cls.CarInterface, cls.CarController, cls.CarState = interfaces[cls.car_model] - cls.CP = cls.CarInterface.get_params(cls.car_model, fingerprint, car_fw, experimental_long, docs=False) + cls.CP = cls.CarInterface.get_params(cls.car_model, cls.fingerprint, car_fw, experimental_long, docs=False) assert cls.CP assert cls.CP.carFingerprint == cls.car_model @@ -297,6 +301,73 @@ class TestCarModelBase(unittest.TestCase): CC = car.CarControl.new_message(cruiseControl={'resume': True}) test_car_controller(CC) + # Skip stdout/stderr capture with pytest, causes elevated memory usage + @pytest.mark.nocapture + @settings(max_examples=MAX_EXAMPLES, deadline=None, + phases=(Phase.reuse, Phase.generate, Phase.shrink)) + @given(data=st.data()) + def test_panda_safety_carstate_fuzzy(self, data): + """ + For each example, pick a random CAN message on the bus and fuzz its data, + checking for panda state mismatches. + """ + + if self.CP.dashcamOnly: + self.skipTest("no need to check panda safety for dashcamOnly") + + valid_addrs = [(addr, bus, size) for bus, addrs in self.fingerprint.items() for addr, size in addrs.items()] + address, bus, size = data.draw(st.sampled_from(valid_addrs)) + + msg_strategy = st.binary(min_size=size, max_size=size) + msgs = data.draw(st.lists(msg_strategy, min_size=20)) + + CC = car.CarControl.new_message() + + for dat in msgs: + # due to panda updating state selectively, only edges are expected to match + # TODO: warm up CarState with real CAN messages to check edge of both sources + # (eg. toyota's gasPressed is the inverse of a signal being set) + prev_panda_gas = self.safety.get_gas_pressed_prev() + prev_panda_brake = self.safety.get_brake_pressed_prev() + prev_panda_regen_braking = self.safety.get_regen_braking_prev() + prev_panda_vehicle_moving = self.safety.get_vehicle_moving() + prev_panda_cruise_engaged = self.safety.get_cruise_engaged_prev() + prev_panda_acc_main_on = self.safety.get_acc_main_on() + + to_send = libpanda_py.make_CANPacket(address, bus, dat) + self.safety.safety_rx_hook(to_send) + + can = messaging.new_message('can', 1) + can.can = [log.CanData(address=address, dat=dat, src=bus)] + + CS = self.CI.update(CC, (can.to_bytes(),)) + + if self.safety.get_gas_pressed_prev() != prev_panda_gas: + self.assertEqual(CS.gasPressed, self.safety.get_gas_pressed_prev()) + + if self.safety.get_brake_pressed_prev() != prev_panda_brake: + # TODO: remove this exception once this mismatch is resolved + brake_pressed = CS.brakePressed + if CS.brakePressed and not self.safety.get_brake_pressed_prev(): + if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05: + brake_pressed = False + + self.assertEqual(brake_pressed, self.safety.get_brake_pressed_prev()) + + if self.safety.get_regen_braking_prev() != prev_panda_regen_braking: + self.assertEqual(CS.regenBraking, self.safety.get_regen_braking_prev()) + + if self.safety.get_vehicle_moving() != prev_panda_vehicle_moving: + self.assertEqual(not CS.standstill, self.safety.get_vehicle_moving()) + + if not (self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH): + if self.safety.get_cruise_engaged_prev() != prev_panda_cruise_engaged: + self.assertEqual(CS.cruiseState.enabled, self.safety.get_cruise_engaged_prev()) + + if self.CP.carName == "honda": + if self.safety.get_acc_main_on() != prev_panda_acc_main_on: + self.assertEqual(CS.cruiseState.available, self.safety.get_acc_main_on()) + def test_panda_safety_carstate(self): """ Assert that panda safety matches openpilot's carState From adca970ca9dc574f740e8523536bf8d4b0d7bfa8 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 19 Dec 2023 09:41:09 -0800 Subject: [PATCH 303/724] cleanup fingerprinting typing (#30801) * wip * undo + add can fingerprints * add can fingerpints * those should go there --- selfdrive/car/fingerprints.py | 4 +++- selfdrive/car/fw_query_definitions.py | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/selfdrive/car/fingerprints.py b/selfdrive/car/fingerprints.py index 1d627e4b37..7ff700f4c5 100644 --- a/selfdrive/car/fingerprints.py +++ b/selfdrive/car/fingerprints.py @@ -1,5 +1,7 @@ +from typing import Dict, List from openpilot.selfdrive.car.interfaces import get_interface_attr +OfflineCanFingerprints = Dict[str, List[Dict[int, int]]] FW_VERSIONS = get_interface_attr('FW_VERSIONS', combine_brands=True, ignore_none=True) _FINGERPRINTS = get_interface_attr('FINGERPRINTS', combine_brands=True, ignore_none=True) @@ -7,7 +9,7 @@ _FINGERPRINTS = get_interface_attr('FINGERPRINTS', combine_brands=True, ignore_n _DEBUG_ADDRESS = {1880: 8} # reserved for debug purposes -def is_valid_for_fingerprint(msg, car_fingerprint): +def is_valid_for_fingerprint(msg, car_fingerprint: OfflineCanFingerprints): adr = msg.address # ignore addresses that are more than 11 bits return (adr in car_fingerprint and car_fingerprint[adr] == len(msg.dat)) or adr >= 0x800 diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index 4ffb087bdc..410040b10e 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -11,9 +11,8 @@ AddrType = Tuple[int, Optional[int]] EcuAddrBusType = Tuple[int, Optional[int], int] EcuAddrSubAddr = Tuple[int, int, Optional[int]] -LIVE_FW_VERSIONS = Dict[AddrType, Set[bytes]] -OFFLINE_FW_VERSIONS = Dict[str, Dict[EcuAddrSubAddr, List[bytes]]] - +LiveFwVersions = Dict[AddrType, Set[bytes]] +OfflineFwVersions = Dict[str, Dict[EcuAddrSubAddr, List[bytes]]] def p16(val): return struct.pack("!H", val) @@ -83,7 +82,7 @@ class FwQueryConfig: extra_ecus: List[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]]] = field(default_factory=list) # Function a brand can implement to provide better fuzzy matching. Takes in FW versions, # returns set of candidates. Only will match if one candidate is returned - match_fw_to_car_fuzzy: Optional[Callable[[LIVE_FW_VERSIONS, OFFLINE_FW_VERSIONS], Set[str]]] = None + match_fw_to_car_fuzzy: Optional[Callable[[LiveFwVersions, OfflineFwVersions], Set[str]]] = None def __post_init__(self): for i in range(len(self.requests)): From 3646ca0a14b9ed80bd0ba0ba3b74ee6f7df6d56a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 20 Dec 2023 02:36:35 +0800 Subject: [PATCH 304/724] refactor swaglog (#30795) --- common/swaglog.cc | 65 +++++++++++++++++++++++++---------------------- common/util.h | 34 ------------------------- 2 files changed, 34 insertions(+), 65 deletions(-) diff --git a/common/swaglog.cc b/common/swaglog.cc index bcd597a257..c22571dc9f 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -5,30 +5,28 @@ #include "common/swaglog.h" #include -#include -#include #include #include #include #include #include "third_party/json11/json11.hpp" - -#include "common/util.h" #include "common/version.h" #include "system/hardware/hw.h" -class SwaglogState : public LogState { - public: - SwaglogState() : LogState(Path::swaglog_ipc().c_str()) {} +class SwaglogState { +public: + SwaglogState() { + zctx = zmq_ctx_new(); + sock = zmq_socket(zctx, ZMQ_PUSH); - json11::Json::object ctx_j; + // Timeout on shutdown for messages to be received by the logging process + int timeout = 100; + zmq_setsockopt(sock, ZMQ_LINGER, &timeout, sizeof(timeout)); + zmq_connect(sock, Path::swaglog_ipc().c_str()); - inline void initialize() { - ctx_j = json11::Json::object {}; print_level = CLOUDLOG_WARNING; - const char* print_lvl = getenv("LOGPRINT"); - if (print_lvl) { + if (const char* print_lvl = getenv("LOGPRINT")) { if (strcmp(print_lvl, "debug") == 0) { print_level = CLOUDLOG_DEBUG; } else if (strcmp(print_lvl, "info") == 0) { @@ -38,39 +36,44 @@ class SwaglogState : public LogState { } } - // openpilot bindings - char* dongle_id = getenv("DONGLE_ID"); - if (dongle_id) { + ctx_j = json11::Json::object{}; + if (char* dongle_id = getenv("DONGLE_ID")) { ctx_j["dongle_id"] = dongle_id; } - char* daemon_name = getenv("MANAGER_DAEMON"); - if (daemon_name) { + if (char* daemon_name = getenv("MANAGER_DAEMON")) { ctx_j["daemon"] = daemon_name; } ctx_j["version"] = COMMA_VERSION; ctx_j["dirty"] = !getenv("CLEAN"); - - // device type ctx_j["device"] = Hardware::get_name(); - LogState::initialize(); } + + ~SwaglogState() { + zmq_close(sock); + zmq_ctx_destroy(zctx); + } + + void log(int levelnum, const char* filename, int lineno, const char* func, const char* msg, const std::string& log_s) { + std::lock_guard lk(lock); + if (levelnum >= print_level) { + printf("%s: %s\n", filename, msg); + } + zmq_send(sock, log_s.data(), log_s.length(), ZMQ_NOBLOCK); + } + + std::mutex lock; + void* zctx = nullptr; + void* sock = nullptr; + int print_level; + json11::Json::object ctx_j; }; -static SwaglogState s = {}; bool LOG_TIMESTAMPS = getenv("LOG_TIMESTAMPS"); uint32_t NO_FRAME_ID = std::numeric_limits::max(); -static void log(int levelnum, const char* filename, int lineno, const char* func, const char* msg, const std::string& log_s) { - if (levelnum >= s.print_level) { - printf("%s: %s\n", filename, msg); - } - zmq_send(s.sock, log_s.data(), log_s.length(), ZMQ_NOBLOCK); -} - static void cloudlog_common(int levelnum, const char* filename, int lineno, const char* func, char* msg_buf, const json11::Json::object &msg_j={}) { - std::lock_guard lk(s.lock); - if (!s.initialized) s.initialize(); + static SwaglogState s; json11::Json::object log_j = json11::Json::object { {"ctx", s.ctx_j}, @@ -89,7 +92,7 @@ static void cloudlog_common(int levelnum, const char* filename, int lineno, cons std::string log_s; log_s += (char)levelnum; ((json11::Json)log_j).dump(log_s); - log(levelnum, filename, lineno, func, msg_buf, log_s); + s.log(levelnum, filename, lineno, func, msg_buf, log_s); free(msg_buf); } diff --git a/common/util.h b/common/util.h index 5c0ef7fec5..0cbad5bd31 100644 --- a/common/util.h +++ b/common/util.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -180,36 +179,3 @@ void update_max_atomic(std::atomic& max, T const& value) { T prev = max; while (prev < value && !max.compare_exchange_weak(prev, value)) {} } - -class LogState { - public: - bool initialized = false; - std::mutex lock; - void *zctx = nullptr; - void *sock = nullptr; - int print_level; - std::string endpoint; - - LogState(std::string _endpoint) { - endpoint = _endpoint; - } - - inline void initialize() { - zctx = zmq_ctx_new(); - sock = zmq_socket(zctx, ZMQ_PUSH); - - // Timeout on shutdown for messages to be received by the logging process - int timeout = 100; - zmq_setsockopt(sock, ZMQ_LINGER, &timeout, sizeof(timeout)); - - zmq_connect(sock, endpoint.c_str()); - initialized = true; - } - - ~LogState() { - if (initialized) { - zmq_close(sock); - zmq_ctx_destroy(zctx); - } - } -}; From 8017c25f0b2960db13df22295c077fbbd6aa6a07 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 19 Dec 2023 10:45:07 -0800 Subject: [PATCH 305/724] Simulator: only send camera frames at rate that they are generated (#30802) only send frames at rate that they are generated --- tools/sim/bridge/metadrive/metadrive_process.py | 4 +++- tools/sim/bridge/metadrive/metadrive_world.py | 4 +++- tools/sim/lib/common.py | 4 ++-- tools/sim/lib/simulated_sensors.py | 12 ++++++------ 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_process.py b/tools/sim/bridge/metadrive/metadrive_process.py index d55ecd7ae5..3fb4186bf9 100644 --- a/tools/sim/bridge/metadrive/metadrive_process.py +++ b/tools/sim/bridge/metadrive/metadrive_process.py @@ -39,7 +39,8 @@ def apply_metadrive_patches(): MetaDriveEnv._is_arrive_destination = arrive_destination_patch -def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera_array, controls_recv: Connection, state_send: Connection, exit_event): +def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera_array, image_lock, + controls_recv: Connection, state_send: Connection, exit_event): apply_metadrive_patches() road_image = np.frombuffer(camera_array.get_obj(), dtype=np.uint8).reshape((H, W, 3)) @@ -98,5 +99,6 @@ def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera if dual_camera: wide_road_image[...] = get_cam_as_rgb("rgb_wide") road_image[...] = get_cam_as_rgb("rgb_road") + image_lock.release() rk.keep_time() diff --git a/tools/sim/bridge/metadrive/metadrive_world.py b/tools/sim/bridge/metadrive/metadrive_world.py index 570c9cb4f5..4cbe64393e 100644 --- a/tools/sim/bridge/metadrive/metadrive_world.py +++ b/tools/sim/bridge/metadrive/metadrive_world.py @@ -27,7 +27,9 @@ class MetaDriveWorld(World): self.metadrive_process = multiprocessing.Process(name="metadrive process", target= functools.partial(metadrive_process, dual_camera, config, - self.camera_array, self.wide_camera_array, self.controls_recv, self.state_send, self.exit_event)) + self.camera_array, self.wide_camera_array, self.image_lock, + self.controls_recv, self.state_send, self.exit_event)) + self.metadrive_process.start() print("----------------------------------------------------------") diff --git a/tools/sim/lib/common.py b/tools/sim/lib/common.py index 29c5858d33..795d7505c7 100644 --- a/tools/sim/lib/common.py +++ b/tools/sim/lib/common.py @@ -1,5 +1,5 @@ import math -import threading +import multiprocessing import numpy as np from abc import ABC, abstractmethod @@ -65,7 +65,7 @@ class World(ABC): def __init__(self, dual_camera): self.dual_camera = dual_camera - self.image_lock = threading.Lock() + self.image_lock = multiprocessing.Semaphore(value=0) self.road_image = np.zeros((H, W, 3), dtype=np.uint8) self.wide_road_image = np.zeros((H, W, 3), dtype=np.uint8) diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index e85665f903..ebbcc6c9e8 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -102,13 +102,13 @@ class SimulatedSensors: self.pm.send('driverMonitoringState', dat) def send_camera_images(self, world: 'World'): - with world.image_lock: - yuv = self.camerad.rgb_to_yuv(world.road_image) - self.camerad.cam_send_yuv_road(yuv) + world.image_lock.acquire() + yuv = self.camerad.rgb_to_yuv(world.road_image) + self.camerad.cam_send_yuv_road(yuv) - if world.dual_camera: - yuv = self.camerad.rgb_to_yuv(world.wide_road_image) - self.camerad.cam_send_yuv_wide_road(yuv) + if world.dual_camera: + yuv = self.camerad.rgb_to_yuv(world.wide_road_image) + self.camerad.cam_send_yuv_wide_road(yuv) def update(self, simulator_state: 'SimulatorState', world: 'World'): now = time.time() From 5f045176775c8c2cdd7f85e7229f78cf26f0c013 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 20 Dec 2023 03:50:16 +0800 Subject: [PATCH 306/724] cabana: add csv export (#30800) export csv --- tools/cabana/SConscript | 3 +- tools/cabana/dbc/dbc.cc | 2 +- tools/cabana/historylog.cc | 16 ++++++++- tools/cabana/historylog.h | 3 +- tools/cabana/mainwin.cc | 12 +++++++ tools/cabana/mainwin.h | 2 ++ tools/cabana/settings.cc | 2 +- tools/cabana/streams/abstractstream.h | 2 +- tools/cabana/utils/export.cc | 47 +++++++++++++++++++++++++++ tools/cabana/utils/export.h | 10 ++++++ tools/cabana/{ => utils}/util.cc | 2 +- tools/cabana/{ => utils}/util.h | 0 tools/cabana/videowidget.h | 2 +- 13 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 tools/cabana/utils/export.cc create mode 100644 tools/cabana/utils/export.h rename tools/cabana/{ => utils}/util.cc (99%) rename tools/cabana/{ => utils}/util.h (100%) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d557b3af80..6af4ca08f5 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -27,8 +27,9 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', + 'utils/export.cc', 'utils/util.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', - 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index a0e523d666..b1256098eb 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -2,7 +2,7 @@ #include -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" uint qHash(const MessageId &item) { return qHash(item.source) ^ qHash(item.address); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index b3440b557b..90bd9a8f76 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -2,11 +2,12 @@ #include +#include #include -#include #include #include "tools/cabana/commands.h" +#include "tools/cabana/utils/export.h" QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { const bool show_signals = display_signals_mode && sigs.size() > 0; @@ -220,6 +221,8 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { h->addWidget(filters_widget); h->addStretch(0); h->addWidget(dynamic_mode = new QCheckBox(tr("Dynamic")), 0, Qt::AlignRight); + ToolButton *export_btn = new ToolButton("filetype-csv", tr("Export to CSV file...")); + h->addWidget(export_btn, 0, Qt::AlignRight); display_type_cb->addItems({"Signal", "Hex"}); display_type_cb->setToolTip(tr("Display signal value or raw hex value")); @@ -252,6 +255,7 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { QObject::connect(signals_cb, SIGNAL(activated(int)), this, SLOT(setFilter())); QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(setFilter())); QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter); + QObject::connect(export_btn, &QToolButton::clicked, this, &LogsWidget::exportToCSV); QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::refresh); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &LogsWidget::refresh); QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &LogsWidget::refresh); @@ -304,3 +308,13 @@ void LogsWidget::showEvent(QShowEvent *event) { model->refresh(); } } + +void LogsWidget::exportToCSV() { + QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(can->routeName()).arg(msgName(model->msg_id)); + QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(msgName(model->msg_id)), + dir, tr("csv (*.csv)")); + if (!fn.isEmpty()) { + const bool export_signals = model->display_signals_mode && model->sigs.size() > 0; + export_signals ? utils::exportSignalsToCSV(fn, model->msg_id) : utils::exportToCSV(fn, model->msg_id); + } +} diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index 154b139fb0..21df6a622e 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -11,7 +11,7 @@ #include "tools/cabana/dbc/dbcmanager.h" #include "tools/cabana/streams/abstractstream.h" -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" class HeaderView : public QHeaderView { public: @@ -80,6 +80,7 @@ public: private slots: void setFilter(); + void exportToCSV(); private: void refresh(); diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 410f3a65f4..bcb65e2e3e 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -22,6 +22,7 @@ #include "tools/cabana/commands.h" #include "tools/cabana/streamselector.h" #include "tools/cabana/tools/findsignal.h" +#include "tools/cabana/utils/export.h" #include "tools/replay/replay.h" MainWindow::MainWindow() : QMainWindow() { @@ -86,7 +87,9 @@ void MainWindow::createActions() { QMenu *file_menu = menuBar()->addMenu(tr("&File")); file_menu->addAction(tr("Open Stream..."), this, &MainWindow::openStream); close_stream_act = file_menu->addAction(tr("Close stream"), this, &MainWindow::closeStream); + export_to_csv_act = file_menu->addAction(tr("Export to CSV..."), this, &MainWindow::exportToCSV); close_stream_act->setEnabled(false); + export_to_csv_act->setEnabled(false); file_menu->addSeparator(); file_menu->addAction(tr("New DBC File"), [this]() { newFile(); }, QKeySequence::New); @@ -270,6 +273,14 @@ void MainWindow::closeStream() { statusBar()->showMessage(tr("stream closed")); } +void MainWindow::exportToCSV() { + QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(can->routeName()); + QString fn = QFileDialog::getSaveFileName(this, "Export stream to CSV file", dir, tr("csv (*.csv)")); + if (!fn.isEmpty()) { + utils::exportToCSV(fn); + } +} + void MainWindow::newFile(SourceSet s) { closeFile(s); dbc()->open(s, "", ""); @@ -344,6 +355,7 @@ void MainWindow::changingStream() { void MainWindow::streamStarted() { bool has_stream = dynamic_cast(can) == nullptr; close_stream_act->setEnabled(has_stream); + export_to_csv_act->setEnabled(has_stream); tools_menu->setEnabled(has_stream); createDockWidgets(); diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index ea0bad5ea4..0b869f153d 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -29,6 +29,7 @@ public: public slots: void openStream(); void closeStream(); + void exportToCSV(); void changingStream(); void streamStarted(); @@ -94,6 +95,7 @@ protected: QMenu *manage_dbcs_menu = nullptr; QMenu *tools_menu = nullptr; QAction *close_stream_act = nullptr; + QAction *export_to_csv_act = nullptr; QAction *save_dbc = nullptr; QAction *save_dbc_as = nullptr; QAction *copy_dbc_to_clipboard = nullptr; diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index 17de0a1c0a..b63789a026 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -10,7 +10,7 @@ #include #include -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" Settings settings; diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index 6c9c025129..10ffcb184a 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -12,7 +12,7 @@ #include "cereal/messaging/messaging.h" #include "tools/cabana/dbc/dbcmanager.h" -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" struct CanData { void compute(const MessageId &msg_id, const uint8_t *dat, const int size, double current_sec, diff --git a/tools/cabana/utils/export.cc b/tools/cabana/utils/export.cc new file mode 100644 index 0000000000..10f69ad548 --- /dev/null +++ b/tools/cabana/utils/export.cc @@ -0,0 +1,47 @@ +#include "tools/cabana/utils/export.h" + +#include +#include + +#include "tools/cabana/streams/abstractstream.h" + +namespace utils { + +void exportToCSV(const QString &file_name, std::optional msg_id) { + QFile file(file_name); + if (file.open(QIODevice::ReadWrite | QIODevice::Truncate)) { + const uint64_t start_time = can->routeStartTime(); + QTextStream stream(&file); + stream << "time,addr,bus,data\n"; + for (auto e : msg_id ? can->events(*msg_id) : can->allEvents()) { + stream << QString::number((e->mono_time / 1e9) - start_time, 'f', 2) << "," + << "0x" << QString::number(e->address, 16) << "," << e->src << "," + << "0x" << QByteArray::fromRawData((const char *)e->dat, e->size).toHex().toUpper() << "\n"; + } + } +} + +void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id) { + QFile file(file_name); + if (auto msg = dbc()->msg(msg_id); msg && msg->sigs.size() && file.open(QIODevice::ReadWrite | QIODevice::Truncate)) { + QTextStream stream(&file); + stream << "time,addr,bus"; + for (auto s : msg->sigs) + stream << "," << s->name; + stream << "\n"; + + const uint64_t start_time = can->routeStartTime(); + for (auto e : can->events(msg_id)) { + stream << QString::number((e->mono_time / 1e9) - start_time, 'f', 2) << "," + << "0x" << QString::number(e->address, 16) << "," << e->src; + for (auto s : msg->sigs) { + double value = 0; + s->getValue(e->dat, e->size, &value); + stream << "," << QString::number(value, 'f', s->precision); + } + stream << "\n"; + } + } +} + +} // namespace utils diff --git a/tools/cabana/utils/export.h b/tools/cabana/utils/export.h new file mode 100644 index 0000000000..270906b163 --- /dev/null +++ b/tools/cabana/utils/export.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "tools/cabana/dbc/dbcmanager.h" + +namespace utils { +void exportToCSV(const QString &file_name, std::optional msg_id = std::nullopt); +void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id); +} // namespace utils diff --git a/tools/cabana/util.cc b/tools/cabana/utils/util.cc similarity index 99% rename from tools/cabana/util.cc rename to tools/cabana/utils/util.cc index a17961243f..c89e39993b 100644 --- a/tools/cabana/util.cc +++ b/tools/cabana/utils/util.cc @@ -1,4 +1,4 @@ -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" #include #include diff --git a/tools/cabana/util.h b/tools/cabana/utils/util.h similarity index 100% rename from tools/cabana/util.h rename to tools/cabana/utils/util.h diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 67c2c8a29f..b2039e09a4 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -10,7 +10,7 @@ #include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "tools/cabana/util.h" +#include "tools/cabana/utils/util.h" #include "tools/replay/logreader.h" struct AlertInfo { From 426072bd8717753afec89c4291f329bdedbc31b8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 19 Dec 2023 20:30:42 -0800 Subject: [PATCH 307/724] fix bad CAN fingerprint type (#30809) fix bad wrong type --- selfdrive/car/fingerprints.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/selfdrive/car/fingerprints.py b/selfdrive/car/fingerprints.py index 7ff700f4c5..6a7c3c75be 100644 --- a/selfdrive/car/fingerprints.py +++ b/selfdrive/car/fingerprints.py @@ -1,15 +1,12 @@ -from typing import Dict, List from openpilot.selfdrive.car.interfaces import get_interface_attr -OfflineCanFingerprints = Dict[str, List[Dict[int, int]]] - FW_VERSIONS = get_interface_attr('FW_VERSIONS', combine_brands=True, ignore_none=True) _FINGERPRINTS = get_interface_attr('FINGERPRINTS', combine_brands=True, ignore_none=True) _DEBUG_ADDRESS = {1880: 8} # reserved for debug purposes -def is_valid_for_fingerprint(msg, car_fingerprint: OfflineCanFingerprints): +def is_valid_for_fingerprint(msg, car_fingerprint: dict[int, int]): adr = msg.address # ignore addresses that are more than 11 bits return (adr in car_fingerprint and car_fingerprint[adr] == len(msg.dat)) or adr >= 0x800 From 1a165c658035233b4fca774d886c7ecb97a43364 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 19 Dec 2023 23:35:31 -0800 Subject: [PATCH 308/724] interfaces: fix get_interface_attr typing (#30813) * fix * Update selfdrive/car/interfaces.py --- selfdrive/car/interfaces.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 0b787897e7..44b198a421 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -3,6 +3,7 @@ import time import numpy as np import tomllib from abc import abstractmethod, ABC +from enum import StrEnum from typing import Any, Dict, Optional, Tuple, List, Callable from cereal import car @@ -440,7 +441,7 @@ INTERFACE_ATTR_FILE = { # interface-specific helpers -def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> Dict[str, Any]: +def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> Dict[str | StrEnum, Any]: # read all the folders in selfdrive/car and return a dict where: # - keys are all the car models or brand names # - values are attr values from all car folders From adb04b5dc52a8063bf16a91a159fdbb82c83d514 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 19 Dec 2023 23:47:34 -0800 Subject: [PATCH 309/724] fingerprints: fix formatting (#30814) formatting --- selfdrive/car/chrysler/fingerprints.py | 1 - selfdrive/car/gm/fingerprints.py | 1 - selfdrive/car/honda/fingerprints.py | 18 +++++------ selfdrive/car/hyundai/fingerprints.py | 3 +- selfdrive/car/mazda/fingerprints.py | 6 ++-- selfdrive/car/nissan/fingerprints.py | 3 +- selfdrive/car/subaru/fingerprints.py | 2 +- selfdrive/car/toyota/fingerprints.py | 40 ++++++++++++------------ selfdrive/car/volkswagen/fingerprints.py | 1 - 9 files changed, 35 insertions(+), 40 deletions(-) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index fb11936f77..3420658ec9 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -67,7 +67,6 @@ FINGERPRINTS = { }], } - FW_VERSIONS = { CAR.JEEP_GRAND_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ diff --git a/selfdrive/car/gm/fingerprints.py b/selfdrive/car/gm/fingerprints.py index 8fabb71003..a760f4b3cc 100644 --- a/selfdrive/car/gm/fingerprints.py +++ b/selfdrive/car/gm/fingerprints.py @@ -1,7 +1,6 @@ # ruff: noqa: E501 from openpilot.selfdrive.car.gm.values import CAR - FINGERPRINTS = { CAR.HOLDEN_ASTRA: [ # Astra BK MY17, ASCM unplugged diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 467ff30197..f0fdd79391 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -1280,29 +1280,29 @@ FW_VERSIONS = { b'78109-TV9-A510\x00\x00', ], }, - CAR.HONDA_E:{ - (Ecu.eps, 0x18DA30F1, None):[ + CAR.HONDA_E: { + (Ecu.eps, 0x18DA30F1, None): [ b'39990-TYF-N030\x00\x00' ], - (Ecu.gateway, 0x18DAEFF1, None):[ + (Ecu.gateway, 0x18DAEFF1, None): [ b'38897-TYF-E140\x00\x00' ], - (Ecu.shiftByWire, 0x18DA0BF1, None):[ + (Ecu.shiftByWire, 0x18DA0BF1, None): [ b'54008-TYF-E010\x00\x00' ], - (Ecu.srs, 0x18DA53F1, None):[ + (Ecu.srs, 0x18DA53F1, None): [ b'77959-TYF-G430\x00\x00' ], - (Ecu.combinationMeter, 0x18DA60F1, None):[ + (Ecu.combinationMeter, 0x18DA60F1, None): [ b'78108-TYF-G610\x00\x00' ], - (Ecu.fwdRadar, 0x18DAB0F1, None):[ + (Ecu.fwdRadar, 0x18DAB0F1, None): [ b'36802-TYF-E030\x00\x00' ], - (Ecu.fwdCamera, 0x18DAB5F1, None):[ + (Ecu.fwdCamera, 0x18DAB5F1, None): [ b'36161-TYF-E020\x00\x00' ], - (Ecu.vsa, 0x18DA28F1, None):[ + (Ecu.vsa, 0x18DA28F1, None): [ b'57114-TYF-E030\x00\x00' ], }, diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 269c3845b9..f61ac16e22 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -79,7 +79,6 @@ FINGERPRINTS = { }], } - FW_VERSIONS = { CAR.AZERA_6TH_GEN: { (Ecu.fwdRadar, 0x7d0, None): [ @@ -924,7 +923,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9300 g21', ], (Ecu.transmission, 0x7e1, None): [b'\xf1\x816U2VE051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VE051\x00\x00DOS4T16NS3\x00\x00\x00\x00', ], }, - CAR.KIA_CEED: { + CAR.KIA_CEED: { (Ecu.fwdRadar, 0x7D0, None): [b'\xf1\000CD__ SCC F-CUP 1.00 1.02 99110-J7000 ', ], (Ecu.eps, 0x7D4, None): [b'\xf1\000CD MDPS C 1.00 1.06 56310-XX000 4CDEC106', ], (Ecu.fwdCamera, 0x7C4, None): [b'\xf1\000CD LKAS AT EUR LHD 1.00 1.01 99211-J7000 B40', ], diff --git a/selfdrive/car/mazda/fingerprints.py b/selfdrive/car/mazda/fingerprints.py index 7c2d321c28..7f7964190d 100644 --- a/selfdrive/car/mazda/fingerprints.py +++ b/selfdrive/car/mazda/fingerprints.py @@ -109,7 +109,7 @@ FW_VERSIONS = { ], }, - CAR.CX9 : { + CAR.CX9: { (Ecu.eps, 0x730, None): [ b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -146,7 +146,7 @@ FW_VERSIONS = { b'GSH7-67XK2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-K\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'TK80-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], + ], (Ecu.transmission, 0x7e1, None): [ b'PXM4-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM7-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -230,7 +230,7 @@ FW_VERSIONS = { ], }, - CAR.CX9_2021 : { + CAR.CX9_2021: { (Ecu.eps, 0x730, None): [ b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], diff --git a/selfdrive/car/nissan/fingerprints.py b/selfdrive/car/nissan/fingerprints.py index 888ae8184a..87a7be07ad 100644 --- a/selfdrive/car/nissan/fingerprints.py +++ b/selfdrive/car/nissan/fingerprints.py @@ -39,7 +39,6 @@ FINGERPRINTS = { ] } - FW_VERSIONS = { CAR.ALTIMA: { (Ecu.fwdCamera, 0x707, None): [ @@ -55,7 +54,7 @@ FW_VERSIONS = { b'284U29HE0A', ], }, - CAR.LEAF: { + CAR.LEAF: { (Ecu.abs, 0x740, None): [ b'476606WK9B', ], diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py index 215e32e538..ac3b6d60c7 100644 --- a/selfdrive/car/subaru/fingerprints.py +++ b/selfdrive/car/subaru/fingerprints.py @@ -292,7 +292,7 @@ FW_VERSIONS = { b'\xd2\xa1`r\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\x1b\xa7@a\x00', + b'\x1b\xa7@a\x00', ], }, CAR.FORESTER_PREGLOBAL: { diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 7e89dfc7ac..18935a2be3 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -1269,26 +1269,26 @@ FW_VERSIONS = { ], }, CAR.LEXUS_ESH: { - (Ecu.engine, 0x7e0, None): [ - b'\x02333M4200\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152633171\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881513310400\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B33512\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4701100\x00\x00\x00\x00', - b'8821F4701300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F3302001\x00\x00\x00\x00', - b'8646F3302200\x00\x00\x00\x00', - ], + (Ecu.engine, 0x7e0, None): [ + b'\x02333M4200\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152633171\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881513310400\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B33512\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'8821F4701100\x00\x00\x00\x00', + b'8821F4701300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F3302001\x00\x00\x00\x00', + b'8646F3302200\x00\x00\x00\x00', + ], }, CAR.LEXUS_GS_F: { (Ecu.engine, 0x7E0, None): [ diff --git a/selfdrive/car/volkswagen/fingerprints.py b/selfdrive/car/volkswagen/fingerprints.py index a9c1ccef4b..a0134870b9 100644 --- a/selfdrive/car/volkswagen/fingerprints.py +++ b/selfdrive/car/volkswagen/fingerprints.py @@ -3,7 +3,6 @@ from openpilot.selfdrive.car.volkswagen.values import CAR Ecu = car.CarParams.Ecu - FW_VERSIONS = { CAR.ARTEON_MK1: { (Ecu.engine, 0x7e0, None): [ From 3713e4d5eaed783738ea8ecdbd3b7e6abaa974c0 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 20 Dec 2023 11:18:29 -0800 Subject: [PATCH 310/724] add script to automatically format fingerprints file (#30792) * Add script * fix script * fix script * fix for subaddr * run the script * old style can fingerprints * cleanup * sort imports, make executable, fix path * newline w/o newline * match og can formatting * match og formatting * generate template once * standard name * less nested * can fingerprints comments * fix spacing * no need for PLATFORM_TO_ENUM_NAME! * prep for PRs * comments for all, add honda comments * Auto-generated fingerprint PR from fuzzy fingerprinting cars * Revert "Auto-generated fingerprint PR from fuzzy fingerprinting cars" This reverts commit 97bc9e3bdb9d819dcbe684ceba92ea702d40eaf0. * even closer to original * readd this comment * and run script * add to precommit * add comments * add comments * add to release * use for auto fingerprint * disable precommit for now --------- Co-authored-by: Shane Smiskol --- .pre-commit-config.yaml | 7 + release/files_common | 1 + selfdrive/car/body/fingerprints.py | 7 +- selfdrive/car/chrysler/fingerprints.py | 16 +- selfdrive/car/ford/fingerprints.py | 16 +- selfdrive/car/gm/fingerprints.py | 62 ++---- selfdrive/car/honda/fingerprints.py | 94 +++++---- selfdrive/car/hyundai/fingerprints.py | 238 +++++++++++++---------- selfdrive/car/mazda/fingerprints.py | 19 +- selfdrive/car/nissan/fingerprints.py | 53 ++--- selfdrive/car/subaru/fingerprints.py | 222 ++++++++++----------- selfdrive/car/tesla/fingerprints.py | 8 +- selfdrive/car/toyota/fingerprints.py | 32 +-- selfdrive/car/volkswagen/fingerprints.py | 121 ++++++------ selfdrive/debug/auto_fingerprint.py | 52 ++--- selfdrive/debug/format_fingerprints.py | 86 ++++++++ 16 files changed, 553 insertions(+), 481 deletions(-) create mode 100755 selfdrive/debug/format_fingerprints.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2109c18b25..342b05bfc0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -89,3 +89,10 @@ repos: rev: 0.27.2 hooks: - id: check-github-workflows +# - repo: local +# hooks: +# - id: format-fingerprints +# name: format-fingerprints +# entry: selfdrive/debug/format_fingerprints.py +# language: system +# types: [python] \ No newline at end of file diff --git a/release/files_common b/release/files_common index 2039347048..0facb5cc5f 100644 --- a/release/files_common +++ b/release/files_common @@ -115,6 +115,7 @@ selfdrive/debug/can_printer.py selfdrive/debug/check_freq.py selfdrive/debug/dump.py selfdrive/debug/filter_log_message.py +selfdrive/debug/format_fingerprints.py selfdrive/debug/get_fingerprint.py selfdrive/debug/uiview.py diff --git a/selfdrive/car/body/fingerprints.py b/selfdrive/car/body/fingerprints.py index 620967acac..c671c1dde2 100644 --- a/selfdrive/car/body/fingerprints.py +++ b/selfdrive/car/body/fingerprints.py @@ -1,11 +1,15 @@ +# ruff: noqa: E501 from cereal import car from openpilot.selfdrive.car.body.values import CAR Ecu = car.CarParams.Ecu +# debug ecu fw version is the git hash of the firmware + + FINGERPRINTS = { CAR.BODY: [{ - 513: 8, 516: 8, 514: 3, 515: 4, + 513: 8, 516: 8, 514: 3, 515: 4 }], } @@ -16,7 +20,6 @@ FW_VERSIONS = { b'02/27/2022', b'0.3.00a', ], - # git hash of the firmware used (Ecu.debug, 0x721, None): [ b'166bd860', b'dc780f85', diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 3420658ec9..cb68c3911e 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -10,7 +10,7 @@ Ecu = car.CarParams.Ecu # For 564, All 2017 have length 4, whereas 2018-19 have length 8. # For 924, Pacifica 2017 has length 3, whereas all 2018-19 have length 8. # For 560, All 2019 have length 8, whereas all 2017-18 have length 4. - +# # Jeep Grand Cherokee unique messages: # 2017 Trailhawk: 618: 8 # For 924, Trailhawk 2017 has length 3, whereas 2018 V6 has length 8. @@ -27,27 +27,23 @@ FINGERPRINTS = { 55: 8, 58: 6, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 926: 3, 937: 8, 947: 8, 948: 8, 956: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 }], CAR.PACIFICA_2020: [{ - 55: 8, 179: 8, 181: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 536: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 776: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 886: 8, 897: 8, 906: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 7, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1543: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 2015: 8, 2016: 8, 2017:8, 2024: 8, 2025: 8 + 55: 8, 179: 8, 181: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 536: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 776: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 886: 8, 897: 8, 906: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 7, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1543: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], CAR.PACIFICA_2018_HYBRID: [{ 68: 8, 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8 }, - # based on 9ae7821dc4e92455|2019-07-01--16-42-55 { 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2016: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 }], CAR.PACIFICA_2019_HYBRID: [{ 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1538: 8 }, - # Based on 0607d2516fc2148f|2019-02-13--23-03-16 { 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1537: 8 }, - # Based on 3c7ce223e3571b54|2019-05-11--20-16-14 { 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1562: 8, 1570: 8 }, - # Based on "8190c7275a24557b|2020-02-24--09-57-23" { 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1536: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2015: 8, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 }, @@ -57,12 +53,10 @@ FINGERPRINTS = { CAR.JEEP_GRAND_CHEROKEE: [{ 55: 8, 168: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 975: 8, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1543: 8, 1562: 8, 1576: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }, - # Based on c88f65eeaee4003a|2022-08-04--15-37-16 { 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 658: 6, 660: 8, 671: 8, 672: 8, 678: 8, 680: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 783: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 844: 5, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1235: 8, 1242: 8, 1252: 8, 1792: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1892: 8, 1927: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], CAR.JEEP_GRAND_CHEROKEE_2019: [{ - # Jeep Grand Cherokee 2019, including most 2020 models 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1538: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], } @@ -81,7 +75,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x753, None): [ b'68456722AC', ], - (Ecu.eps, 0x75A, None): [ + (Ecu.eps, 0x75a, None): [ b'68453431AA', ], (Ecu.engine, 0x7e0, None): [ @@ -91,7 +85,6 @@ FW_VERSIONS = { b'05035707AA', ], }, - CAR.RAM_1500: { (Ecu.combinationMeter, 0x742, None): [ b'68294051AG', @@ -153,7 +146,7 @@ FW_VERSIONS = { b'68475160AF', b'68475160AG', ], - (Ecu.eps, 0x75A, None): [ + (Ecu.eps, 0x75a, None): [ b'21590101AA', b'21590101AB', b'68273275AF', @@ -204,7 +197,6 @@ FW_VERSIONS = { b'68540431AB', ], }, - CAR.RAM_HD: { (Ecu.combinationMeter, 0x742, None): [ b'68361606AH', diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py index d26c3eac0a..b3150d8277 100644 --- a/selfdrive/car/ford/fingerprints.py +++ b/selfdrive/car/ford/fingerprints.py @@ -19,7 +19,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -47,7 +47,7 @@ FW_VERSIONS = { b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LV4T-14F397-GG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6A-14C204-BJX\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6A-14C204-CNG\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -85,7 +85,7 @@ FW_VERSIONS = { b'LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LC5T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'LB5A-14C204-ATJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-AUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-AZL\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -112,7 +112,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'PL3A-14C204-BRB\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, @@ -126,7 +126,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x764, None): [ b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'NL3A-14C204-BAR\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, @@ -144,7 +144,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MJ98-14C204-BBS\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -162,7 +162,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'JX7T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, @@ -181,7 +181,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/gm/fingerprints.py b/selfdrive/car/gm/fingerprints.py index a760f4b3cc..9159ddefbf 100644 --- a/selfdrive/car/gm/fingerprints.py +++ b/selfdrive/car/gm/fingerprints.py @@ -1,82 +1,56 @@ # ruff: noqa: E501 from openpilot.selfdrive.car.gm.values import CAR +# Trailblazer also matches as a SILVERADO, TODO: split with fw verisions + + FINGERPRINTS = { - CAR.HOLDEN_ASTRA: [ - # Astra BK MY17, ASCM unplugged - { - 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 401: 8, 413: 8, 417: 8, 419: 8, 422: 1, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 458: 5, 479: 8, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 647: 5, 707: 8, 715: 8, 723: 8, 753: 5, 761: 7, 806: 1, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1009: 8, 1011: 6, 1017: 8, 1019: 3, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 8, 1280: 4, 1300: 8, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1908: 7, 1912: 7, 1919: 7, + CAR.HOLDEN_ASTRA: [{ + 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 401: 8, 413: 8, 417: 8, 419: 8, 422: 1, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 458: 5, 479: 8, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 647: 5, 707: 8, 715: 8, 723: 8, 753: 5, 761: 7, 806: 1, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1009: 8, 1011: 6, 1017: 8, 1019: 3, 1020: 8, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 8, 1280: 4, 1300: 8, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1908: 7, 1912: 7, 1919: 7 }], - CAR.VOLT: [ - # Volt Premier w/ ACC 2017 - { + CAR.VOLT: [{ 170: 8, 171: 8, 189: 7, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 289: 8, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 577: 8, 647: 3, 707: 8, 711: 6, 715: 8, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 961: 8, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1928: 7, 2016: 8, 2020: 8, 2024: 8, 2028: 8 }, - # Volt Premier w/ ACC 2018 { 170: 8, 171: 8, 189: 7, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 577: 8, 578: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 715: 8, 717: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1516: 8, 1601: 8, 1618: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1930: 7, 2016: 8, 2018: 8, 2020: 8, 2024: 8, 2028: 8 }, - # Volt Premier 2018 w/ flashed firmware, no radar { 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 368: 8, 381: 2, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 5, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 4, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 761: 7, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 5, 844: 8, 853: 8, 866: 4, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7 }], - CAR.BUICK_LACROSSE: [ - # LaCrosse Premium AWD 2017 - { + CAR.BUICK_LACROSSE: [{ 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 }], - CAR.BUICK_REGAL : [ - # Regal TourX Essence w/ ACC 2018 - { + CAR.BUICK_REGAL: [{ 190: 8, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 8, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 8, 419: 8, 422: 4, 426: 8, 431: 8, 442: 8, 451: 8, 452: 8, 453: 8, 455: 7, 456: 8, 463: 3, 479: 8, 481: 7, 485: 8, 487: 8, 489: 8, 495: 8, 497: 8, 499: 3, 500: 8, 501: 8, 508: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 569: 3, 573: 1, 577: 8, 578: 8, 579: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 884: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 8, 969: 8, 977: 8, 979: 8, 985: 8, 1001: 8, 1005: 6, 1009: 8, 1011: 8, 1013: 3, 1017: 8, 1020: 8, 1024: 8, 1025: 8, 1026: 8, 1027: 8, 1028: 8, 1029: 8, 1030: 8, 1031: 8, 1032: 2, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 8, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1259: 8, 1261: 8, 1263: 8, 1265: 8, 1267: 8, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1602: 8, 1603: 7, 1611: 8, 1618: 8, 1906: 8, 1907: 7, 1912: 7, 1914: 7, 1916: 7, 1919: 7, 1930: 7, 2016: 8, 2018: 8, 2019: 8, 2024: 8, 2026: 8 }], - CAR.CADILLAC_ATS: [ - # Cadillac ATS Coupe Premium Performance 3.6L RWD w/ ACC 2018 - { + CAR.CADILLAC_ATS: [{ 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 368: 3, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 401: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 462: 4, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 491: 2, 493: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 723: 2, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 882: 8, 890: 1, 892: 2, 893: 2, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1233: 8, 1241: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1916: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 }], - CAR.MALIBU: [ - # Malibu Premier w/ ACC 2017 - { - 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7, 1930: 7, 2016: 8, 2024: 8, + CAR.MALIBU: [{ + 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 528: 5, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1919: 7, 1930: 7, 2016: 8, 2024: 8 }], - CAR.ACADIA: [ - # Acadia Denali w/ACC 2018 - { + CAR.ACADIA: [{ 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 1, 290: 1, 298: 8, 304: 8, 309: 8, 313: 8, 320: 8, 322: 7, 328: 1, 352: 7, 368: 8, 381: 8, 384: 8, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 458: 8, 460: 4, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 5, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 512: 3, 530: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 568: 2, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 801: 8, 803: 8, 804: 3, 805: 8, 832: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7 }, - # Acadia Denali w/ /ACC 2018 { 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 338: 6, 340: 6, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 }], - CAR.ESCALADE: [ - { + CAR.ESCALADE: [{ 170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 4, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 }], - CAR.ESCALADE_ESV: [ - { + CAR.ESCALADE_ESV: [{ 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 }], - CAR.ESCALADE_ESV_2019: [ - { + CAR.ESCALADE_ESV_2019: [{ 715: 8, 840: 5, 717: 5, 869: 4, 880: 6, 289: 8, 454: 8, 842: 5, 460: 5, 463: 3, 801: 8, 170: 8, 190: 6, 241: 6, 201: 8, 417: 7, 211: 2, 419: 1, 398: 8, 426: 7, 487: 8, 442: 8, 451: 8, 452: 8, 453: 6, 479: 3, 311: 8, 500: 6, 647: 6, 193: 8, 707: 8, 197: 8, 209: 7, 199: 4, 455: 7, 313: 8, 481: 7, 485: 8, 489: 8, 249: 8, 393: 7, 407: 7, 413: 8, 422: 4, 431: 8, 501: 8, 499: 3, 810: 8, 508: 8, 381: 8, 462: 4, 532: 6, 562: 8, 386: 8, 761: 7, 573: 1, 554: 3, 719: 5, 560: 8, 1279: 4, 388: 8, 288: 5, 1005: 6, 497: 8, 844: 8, 961: 8, 967: 4, 977: 8, 979: 8, 985: 5, 1001: 8, 1017: 8, 1019: 2, 1020: 8, 1217: 8, 510: 8, 866: 4, 304: 1, 969: 8, 384: 4, 1033: 7, 1009: 8, 1034: 7, 1296: 4, 1930: 7, 1105: 5, 1013: 5, 1225: 7, 1919: 7, 320: 3, 534: 2, 352: 5, 298: 8, 1223: 2, 1233: 8, 608: 8, 1265: 8, 609: 6, 1267: 1, 1417: 8, 610: 6, 1906: 7, 611: 6, 612: 8, 613: 8, 208: 8, 564: 5, 309: 8, 1221: 5, 1280: 4, 1249: 8, 1907: 7, 1257: 6, 1300: 8, 1920: 7, 563: 5, 1322: 6, 1323: 4, 1328: 4, 1917: 7, 328: 1, 1912: 7, 1914: 7, 804: 3, 1918: 7 }], - CAR.BOLT_EUV: [ - { + CAR.BOLT_EUV: [{ 189: 7, 190: 7, 193: 8, 197: 8, 201: 8, 209: 7, 211: 3, 241: 6, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 451: 8, 452: 8, 453: 6, 458: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 566: 8, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1265: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 }], - CAR.SILVERADO: [ - { + CAR.SILVERADO: [{ 190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 }], - CAR.EQUINOX: [ - { + CAR.EQUINOX: [{ 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 }], - # Trailblazer also matches as a Silverado, so comment out to avoid conflicts. - # TODO: split with FW versions - # CAR.TRAILBLAZER: [ - # { - # 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1609: 8, 1611: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8 - # }], } diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index f0fdd79391..fe9e9273b7 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -1,9 +1,14 @@ - from cereal import car from openpilot.selfdrive.car.honda.values import CAR Ecu = car.CarParams.Ecu +# Modified FW can be identified by the second dash being replaced by a comma +# For example: `b'39990-TVA,A150\x00\x00'` +# +# TODO: vsa is "essential" for fpv2 but doesn't appear on some CAR.FREED models + + FW_VERSIONS = { CAR.ACCORD: { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ @@ -89,7 +94,7 @@ FW_VERSIONS = { b'39990-TVA-A340\x00\x00', b'39990-TVA-X030\x00\x00', b'39990-TVA-X040\x00\x00', - b'39990-TVA,A150\x00\x00', # modified firmware + b'39990-TVA,A150\x00\x00', b'39990-TVE-H130\x00\x00', ], (Ecu.unknown, 0x18da3af1, None): [ @@ -237,7 +242,7 @@ FW_VERSIONS = { b'37805-5AA-L660\x00\x00', b'37805-5AA-L680\x00\x00', b'37805-5AA-L690\x00\x00', - b'37805-5AA-L810\000\000', + b'37805-5AA-L810\x00\x00', b'37805-5AG-Q710\x00\x00', b'37805-5AJ-A610\x00\x00', b'37805-5AJ-A620\x00\x00', @@ -275,7 +280,7 @@ FW_VERSIONS = { b'57114-TEA-Q220\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ - b'39990-TBA,A030\x00\x00', # modified firmware + b'39990-TBA,A030\x00\x00', b'39990-TBA-A030\x00\x00', b'39990-TBG-A030\x00\x00', b'39990-TEA-T020\x00\x00', @@ -617,7 +622,7 @@ FW_VERSIONS = { b'39990-TLA-A040\x00\x00', b'39990-TLA-A110\x00\x00', b'39990-TLA-A220\x00\x00', - b'39990-TLA,A040\x00\x00', # modified firmware + b'39990-TLA,A040\x00\x00', b'39990-TME-T030\x00\x00', b'39990-TME-T120\x00\x00', b'39990-TMT-T010\x00\x00', @@ -686,9 +691,15 @@ FW_VERSIONS = { b'37805-R5Z-G740\x00\x00', b'37805-R5Z-G780\x00\x00', ], - (Ecu.vsa, 0x18da28f1, None): [b'57114-T1V-G920\x00\x00'], - (Ecu.fwdRadar, 0x18dab0f1, None): [b'36161-T1V-G520\x00\x00'], - (Ecu.shiftByWire, 0x18da0bf1, None): [b'54008-T1V-G010\x00\x00'], + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-T1V-G920\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T1V-G520\x00\x00', + ], + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-T1V-G010\x00\x00', + ], (Ecu.transmission, 0x18da1ef1, None): [ b'28101-5LH-E120\x00\x00', b'28103-5LH-E100\x00\x00', @@ -697,7 +708,9 @@ FW_VERSIONS = { b'78109-T1V-G020\x00\x00', b'78109-T1B-3050\x00\x00', ], - (Ecu.srs, 0x18da53f1, None): [b'77959-T1G-G940\x00\x00'], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-T1G-G940\x00\x00', + ], }, CAR.CRV_HYBRID: { (Ecu.vsa, 0x18da28f1, None): [ @@ -782,7 +795,6 @@ FW_VERSIONS = { b'39990-TDK-J050\x00\x00', b'39990-TDK-N020\x00\x00', ], - # TODO: vsa is "essential" for fpv2 but doesn't appear on some models (Ecu.vsa, 0x18da28f1, None): [ b'57114-TDK-J120\x00\x00', b'57114-TDK-J330\x00\x00', @@ -1065,7 +1077,7 @@ FW_VERSIONS = { b'37805-5YF-AD20\x00\x00', b'37805-5YF-C210\x00\x00', b'37805-5YF-C220\x00\x00', - b'37805-5YF-C410\000\000', + b'37805-5YF-C410\x00\x00', b'37805-5YF-C420\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ @@ -1106,7 +1118,7 @@ FW_VERSIONS = { b'78109-TJB-AF10\x00\x00', b'78109-TJB-AQ20\x00\x00', b'78109-TJB-AR10\x00\x00', - b'78109-TJB-AS10\000\000', + b'78109-TJB-AS10\x00\x00', b'78109-TJB-AU10\x00\x00', b'78109-TJB-AW10\x00\x00', b'78109-TJC-A420\x00\x00', @@ -1237,28 +1249,28 @@ FW_VERSIONS = { ], }, CAR.HRV_3G: { - (Ecu.eps, 0x18DA30F1, None): [ + (Ecu.eps, 0x18da30f1, None): [ b'39990-3W0-A030\x00\x00', ], - (Ecu.gateway, 0x18DAEFF1, None): [ + (Ecu.gateway, 0x18daeff1, None): [ b'38897-3W1-A010\x00\x00', ], - (Ecu.srs, 0x18DA53F1, None): [ + (Ecu.srs, 0x18da53f1, None): [ b'77959-3V0-A820\x00\x00', ], - (Ecu.combinationMeter, 0x18DA60F1, None): [ + (Ecu.combinationMeter, 0x18da60f1, None): [ b'78108-3V1-A220\x00\x00', ], - (Ecu.vsa, 0x18DA28F1, None): [ + (Ecu.vsa, 0x18da28f1, None): [ b'57114-3W0-A040\x00\x00', ], - (Ecu.transmission, 0x18DA1EF1, None): [ + (Ecu.transmission, 0x18da1ef1, None): [ b'28101-6EH-A010\x00\x00', ], - (Ecu.programmedFuelInjection, 0x18DA10F1, None): [ + (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-6CT-A710\x00\x00', ], - (Ecu.electricBrakeBooster, 0x18DA2BF1, None): [ + (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ b'46114-3W0-A020\x00\x00', ], }, @@ -1281,38 +1293,38 @@ FW_VERSIONS = { ], }, CAR.HONDA_E: { - (Ecu.eps, 0x18DA30F1, None): [ - b'39990-TYF-N030\x00\x00' + (Ecu.eps, 0x18da30f1, None): [ + b'39990-TYF-N030\x00\x00', ], - (Ecu.gateway, 0x18DAEFF1, None): [ - b'38897-TYF-E140\x00\x00' + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-TYF-E140\x00\x00', ], - (Ecu.shiftByWire, 0x18DA0BF1, None): [ - b'54008-TYF-E010\x00\x00' + (Ecu.shiftByWire, 0x18da0bf1, None): [ + b'54008-TYF-E010\x00\x00', ], - (Ecu.srs, 0x18DA53F1, None): [ - b'77959-TYF-G430\x00\x00' + (Ecu.srs, 0x18da53f1, None): [ + b'77959-TYF-G430\x00\x00', ], - (Ecu.combinationMeter, 0x18DA60F1, None): [ - b'78108-TYF-G610\x00\x00' + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78108-TYF-G610\x00\x00', ], - (Ecu.fwdRadar, 0x18DAB0F1, None): [ - b'36802-TYF-E030\x00\x00' + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TYF-E030\x00\x00', ], - (Ecu.fwdCamera, 0x18DAB5F1, None): [ - b'36161-TYF-E020\x00\x00' + (Ecu.fwdCamera, 0x18dab5f1, None): [ + b'36161-TYF-E020\x00\x00', ], - (Ecu.vsa, 0x18DA28F1, None): [ - b'57114-TYF-E030\x00\x00' + (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TYF-E030\x00\x00', ], }, CAR.CIVIC_2022: { - (Ecu.eps, 0x18DA30F1, None): [ + (Ecu.eps, 0x18da30f1, None): [ b'39990-T39-A130\x00\x00', b'39990-T43-J020\x00\x00', b'39990-T24-T120\x00\x00', ], - (Ecu.gateway, 0x18DAEFF1, None): [ + (Ecu.gateway, 0x18daeff1, None): [ b'38897-T20-A020\x00\x00', b'38897-T20-A510\x00\x00', b'38897-T21-A010\x00\x00', @@ -1320,14 +1332,14 @@ FW_VERSIONS = { b'38897-T20-A310\x00\x00', b'38897-T24-Z120\x00\x00', ], - (Ecu.srs, 0x18DA53F1, None): [ + (Ecu.srs, 0x18da53f1, None): [ b'77959-T20-A970\x00\x00', b'77959-T47-A940\x00\x00', b'77959-T47-A950\x00\x00', b'77959-T20-M820\x00\x00', b'77959-T20-A980\x00\x00', ], - (Ecu.combinationMeter, 0x18DA60F1, None): [ + (Ecu.combinationMeter, 0x18da60f1, None): [ b'78108-T21-A220\x00\x00', b'78108-T21-A620\x00\x00', b'78108-T23-A110\x00\x00', @@ -1343,7 +1355,7 @@ FW_VERSIONS = { b'36161-T47-A070\x00\x00', b'36161-T24-T070\x00\x00', ], - (Ecu.vsa, 0x18DA28F1, None): [ + (Ecu.vsa, 0x18da28f1, None): [ b'57114-T20-AB40\x00\x00', b'57114-T43-JB30\x00\x00', b'57114-T24-TB30\x00\x00', diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index f61ac16e22..59f71560d7 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -29,12 +29,12 @@ FINGERPRINTS = { { 67: 8, 68: 8, 80: 4, 160: 8, 161: 8, 272: 8, 288: 4, 339: 8, 356: 8, 357: 8, 399: 8, 544: 8, 608: 8, 672: 8, 688: 5, 704: 1, 790: 8, 809: 8, 848: 8, 880: 8, 898: 8, 900: 8, 901: 8, 904: 8, 1056: 8, 1064: 8, 1065: 8, 1072: 8, 1075: 8, 1087: 8, 1088: 8, 1151: 8, 1200: 8, 1201: 8, 1232: 4, 1264: 8, 1265: 8, 1266: 8, 1296: 8, 1306: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1348: 8, 1349: 8, 1369: 8, 1370: 8, 1371: 8, 1407: 8, 1415: 8, 1419: 8, 1440: 8, 1442: 4, 1461: 8, 1470: 8 }], - CAR.SONATA: [ - {67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 549: 8, 550: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 8, 865: 8, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 908: 8, 909: 8, 912: 7, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1089: 5, 1096: 8, 1107: 5, 1108: 8, 1114: 8, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1170: 8, 1173: 8, 1180: 8, 1183: 8, 1184: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1268: 8, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1330: 8, 1339: 8, 1342: 6, 1343: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1379: 8, 1384: 8, 1394: 8, 1407: 8, 1419: 8, 1427: 6, 1446: 8, 1456: 4, 1460: 8, 1470: 8, 1485: 8, 1504: 3, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8}, - ], - CAR.SONATA_LF: [ - {66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8}, - ], + CAR.SONATA: [{ + 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 546: 8, 549: 8, 550: 8, 576: 8, 593: 8, 608: 8, 688: 6, 809: 8, 832: 8, 854: 8, 865: 8, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 905: 8, 908: 8, 909: 8, 912: 7, 913: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1089: 5, 1096: 8, 1107: 5, 1108: 8, 1114: 8, 1136: 8, 1145: 8, 1151: 8, 1155: 8, 1156: 8, 1157: 4, 1162: 8, 1164: 8, 1168: 8, 1170: 8, 1173: 8, 1180: 8, 1183: 8, 1184: 8, 1186: 2, 1191: 2, 1193: 8, 1210: 8, 1225: 8, 1227: 8, 1265: 4, 1268: 8, 1280: 8, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1330: 8, 1339: 8, 1342: 6, 1343: 8, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1371: 8, 1378: 8, 1379: 8, 1384: 8, 1394: 8, 1407: 8, 1419: 8, 1427: 6, 1446: 8, 1456: 4, 1460: 8, 1470: 8, 1485: 8, 1504: 3, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 2015: 8 + }], + CAR.SONATA_LF: [{ + 66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }], CAR.KIA_SORENTO: [{ 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1384: 8, 1407: 8, 1411: 8, 1419: 8, 1425: 2, 1427: 6, 1444: 8, 1456: 4, 1470: 8, 1489: 1 }], @@ -57,7 +57,7 @@ FINGERPRINTS = { 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 7, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 }], CAR.IONIQ: [{ - 68:8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 8, 576:8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1473: 8, 1476: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 + 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 524: 8, 544: 8, 576: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 6, 1151: 6, 1155: 8, 1156: 8, 1157: 4, 1164: 8, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1379: 8, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1448: 8, 1456: 4, 1470: 8, 1473: 8, 1476: 8, 1507: 8, 1535: 8, 1988: 8, 1996: 8, 2000: 8, 2004: 8, 2005: 8, 2008: 8, 2012: 8, 2013: 8 }], CAR.KONA_EV: [{ 127: 8, 304: 8, 320: 8, 339: 8, 352: 8, 356: 4, 544: 8, 549: 8, 593: 8, 688: 5, 832: 8, 881: 8, 882: 8, 897: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1078: 4, 1136: 8, 1151: 6, 1168: 7, 1173: 8, 1183: 8, 1186: 2, 1191: 2, 1225: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1291: 8, 1292: 8, 1294: 8, 1307: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1355: 8, 1363: 8, 1369: 8, 1378: 4, 1407: 8, 1419: 8, 1426: 8, 1427: 6, 1429: 8, 1430: 8, 1456: 4, 1470: 8, 1473: 8, 1507: 8, 1535: 8, 2000: 8, 2004: 8, 2008: 8, 2012: 8, 1157: 4, 1193: 8, 1379: 8, 1988: 8, 1996: 8 @@ -98,7 +98,7 @@ FW_VERSIONS = { ], }, CAR.AZERA_HEV_6TH_GEN: { - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029', b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.00 99211-G8000 180903', ], @@ -187,7 +187,7 @@ FW_VERSIONS = { b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\000\000\xf1\0006U3H1_C2\000\0006U3J9051\000\000PAE0G16NL0\x82zT\xd2', + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x82zT\xd2', b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', @@ -296,9 +296,9 @@ FW_VERSIONS = { b'\xf1\x81HM6M1_0a0_G20', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware + b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC102', - b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware + b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', @@ -397,7 +397,7 @@ FW_VERSIONS = { b'\xf1\x00LF__ SCC F-CUP 1.00 1.00 96401-C2200 ', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00LF ESC \f 11 \x17\x01\x13 58920-C2610', + b'\xf1\x00LF ESC \x0c 11 \x17\x01\x13 58920-C2610', b'\xf1\x00LF ESC \t 11 \x17\x01\x13 58920-C2610', ], (Ecu.engine, 0x7e0, None): [ @@ -824,7 +824,9 @@ FW_VERSIONS = { b'\x01TJS-JDK06F200H0A', b'391282BJF5 ', ], - (Ecu.eps, 0x7d4, None): [b'\xf1\x00JSL MDPS C 1.00 1.03 56340-J3000 8308', ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00JSL MDPS C 1.00 1.03 56340-J3000 8308', + ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00JS LKAS AT USA LHD 1.00 1.02 95740-J3000 K32', b'\xf1\x00JS LKAS AT KOR LHD 1.00 1.03 95740-J3000 K33', @@ -832,7 +834,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\xba\x02\xb8\x80', b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\x00\x00\x00\x00', - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16KS2\016\xba\036\xa2', + b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16KS2\x0e\xba\x1e\xa2', ], }, CAR.GENESIS_G70: { @@ -851,7 +853,7 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', - b'\xf1\x87VDJLT17895112DN4\x88fVf\x99\x88\x88\x88\x87fVe\x88vhwwUFU\x97eFex\x99\xff\xb7\x82\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', + b'\xf1\x87VDJLT17895112DN4\x88fVf\x99\x88\x88\x88\x87fVe\x88vhwwUFU\x97eFex\x99\x7f\xff\xb7\x82\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB2\x11\x1am\xda', ], }, CAR.GENESIS_G70_2020: { @@ -911,41 +913,69 @@ FW_VERSIONS = { b'\xf1\x87VDGMD15352242DD3w\x87gxwvgv\x87wvw\x88wXwffVfffUfw\x88o\xff\x06J\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7', b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7', ], - (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 '], - (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2020 160302'], - (Ecu.engine, 0x7e0, None): [b'\xf1\x810000000000\x00'], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2020 160302', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x810000000000\x00', + ], }, CAR.KONA: { - (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00OS__ SCC F-CUP 1.00 1.00 95655-J9200 ', ], - (Ecu.abs, 0x7d1, None): [b'\xf1\x816V5RAK00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [b'"\x01TOS-0NU06F301J02', ], - (Ecu.eps, 0x7d4, None): [b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105', ], - (Ecu.fwdCamera, 0x7c4, None): [b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9300 g21', ], - (Ecu.transmission, 0x7e1, None): [b'\xf1\x816U2VE051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VE051\x00\x00DOS4T16NS3\x00\x00\x00\x00', ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00OS__ SCC F-CUP 1.00 1.00 95655-J9200 ', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x816V5RAK00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'"\x01TOS-0NU06F301J02', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00OS MDPS C 1.00 1.05 56310J9030\x00 4OSDC105', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00OS9 LKAS AT USA LHD 1.00 1.00 95740-J9300 g21', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U2VE051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VE051\x00\x00DOS4T16NS3\x00\x00\x00\x00', + ], }, CAR.KIA_CEED: { - (Ecu.fwdRadar, 0x7D0, None): [b'\xf1\000CD__ SCC F-CUP 1.00 1.02 99110-J7000 ', ], - (Ecu.eps, 0x7D4, None): [b'\xf1\000CD MDPS C 1.00 1.06 56310-XX000 4CDEC106', ], - (Ecu.fwdCamera, 0x7C4, None): [b'\xf1\000CD LKAS AT EUR LHD 1.00 1.01 99211-J7000 B40', ], - (Ecu.engine, 0x7E0, None): [b'\001TCD-JECU4F202H0K', ], - (Ecu.transmission, 0x7E1, None): [ - b'\xf1\x816U2V7051\000\000\xf1\0006U2V0_C2\000\0006U2V7051\000\000DCD0T14US1\000\000\000\000', + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CD__ SCC F-CUP 1.00 1.02 99110-J7000 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CD MDPS C 1.00 1.06 56310-XX000 4CDEC106', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CD LKAS AT EUR LHD 1.00 1.01 99211-J7000 B40', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x01TCD-JECU4F202H0K', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x816U2V7051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V7051\x00\x00DCD0T14US1\x00\x00\x00\x00', b'\xf1\x816U2V7051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V7051\x00\x00DCD0T14US1U\x867Z', ], - (Ecu.abs, 0x7D1, None): [b'\xf1\000CD ESC \003 102\030\b\005 58920-J7350', ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00CD ESC \x03 102\x18\x08\x05 58920-J7350', + ], }, CAR.KIA_FORTE: { - (Ecu.eps, 0x7D4, None): [ + (Ecu.eps, 0x7d4, None): [ b'\xf1\x00BD MDPS C 1.00 1.02 56310-XX000 4BD2C102', b'\xf1\x00BD MDPS C 1.00 1.08 56310/M6300 4BDDC108', b'\xf1\x00BD MDPS C 1.00 1.08 56310M6300\x00 4BDDC108', b'\xf1\x00BDm MDPS C A.01 1.03 56310M7800\x00 4BPMC103', ], - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00BD LKAS AT USA LHD 1.00 1.04 95740-M6000 J33', b'\xf1\x00BDP LKAS AT USA LHD 1.00 1.05 99211-M6500 744', ], - (Ecu.fwdRadar, 0x7D0, None): [ + (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00BD__ SCC H-CUP 1.00 1.02 99110-M6000 ', b'\xf1\x00BDPE_SCC FHCUPC 1.00 1.04 99110-M6500\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -965,48 +995,48 @@ FW_VERSIONS = { ], }, CAR.KIA_K5_2021: { - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', - b'\xf1\x8799110L2000\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', + b'\xf1\x8799110L2000\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', b'\xf1\x00DL3_ SCC FHCUP 1.00 1.04 99110-L2100 ', ], - (Ecu.eps, 0x7D4, None): [ - b'\xf1\x8756310-L3110\xf1\000DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x8756310-L3110\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L2220 4DLDC102', b'\xf1\x00DL3 MDPS C 1.00 1.02 56310L3220\x00 4DLAC102', ], - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915', b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.04 99210-L3000 210208', b'\xf1\x00DL3 MFC AT KOR LHD 1.00 1.04 99210-L2000 210527', b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.05 99210-L3000 211222', ], - (Ecu.abs, 0x7D1, None): [ - b'\xf1\000DL ESC \006 101 \004\002 58910-L3200', - b'\xf1\x8758910-L3200\xf1\000DL ESC \006 101 \004\002 58910-L3200', + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00DL ESC \x06 101 \x04\x02 58910-L3200', + b'\xf1\x8758910-L3200\xf1\x00DL ESC \x06 101 \x04\x02 58910-L3200', b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', b'\xf1\x00DL ESC \x01 104 \x07\x12 58910-L2200', b'\xf1\x00DL ESC \x06 103"\x08\x06 58910-L3200', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'\xf1\x87391212MKT0', b'\xf1\x87391212MKV0', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DLDWN5TMDCXXXJ1B', b'\xf1\x81HM6M2_0a0_DQ0', b'\xf1\x87391212MKT3', ], - (Ecu.transmission, 0x7E1, None): [ - b'\xf1\000bcsh8p54 U913\000\000\000\000\000\000TDL2T16NB1ia\v\xb8', - b'\xf1\x87SALFEA5652514GK2UUeV\x88\x87\x88xxwg\x87ww\x87wwfwvd/\xfb\xffvU_\xff\x93\xd3\xf1\x81U913\000\000\000\000\000\000\xf1\000bcsh8p54 U913\000\000\000\000\000\000TDL2T16NB1ia\v\xb8', + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', + b'\xf1\x87SALFEA5652514GK2UUeV\x88\x87\x88xxwg\x87ww\x87wwfwvd/\xfb\xffvU_\xff\x93\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', @@ -1016,46 +1046,46 @@ FW_VERSIONS = { ], }, CAR.KIA_K5_HEV_2020: { - (Ecu.fwdRadar, 0x7D0, None): [ + (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DLhe SCC FHCUP 1.00 1.02 99110-L7000 ', ], - (Ecu.eps, 0x7D4, None): [ + (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7000 4DLHC102', b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7220 4DLHC102', ], - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.02 99210-L2000 200309', b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.04 99210-L2000 210527', ], - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'\xf1\x87391162JLA0', ], - (Ecu.transmission, 0x7E1, None): [ + (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00PSBG2323 E08\x00\x00\x00\x00\x00\x00\x00TDL2H20KA2\xe3\xc6cz', b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDL2H20KA5T\xf2\xc9\xc2', ], }, CAR.KONA_EV: { - (Ecu.abs, 0x7D1, None): [ + (Ecu.abs, 0x7d1, None): [ b'\xf1\x00OS IEB \r 105\x18\t\x18 58520-K4000', b'\xf1\x00OS IEB \x01 212 \x11\x13 58520-K4000', b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000', b'\xf1\x00OS IEB \x03 210 \x02\x14 58520-K4000', b'\xf1\x00OS IEB \x03 212 \x11\x13 58520-K4000', ], - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00OE2 LKAS AT EUR LHD 1.00 1.00 95740-K4200 200', b'\xf1\x00OSE LKAS AT EUR LHD 1.00 1.00 95740-K4100 W40', b'\xf1\x00OSE LKAS AT EUR RHD 1.00 1.00 95740-K4100 W40', b'\xf1\x00OSE LKAS AT KOR LHD 1.00 1.00 95740-K4100 W40', b'\xf1\x00OSE LKAS AT USA LHD 1.00 1.00 95740-K4300 W50', ], - (Ecu.eps, 0x7D4, None): [ + (Ecu.eps, 0x7d4, None): [ b'\xf1\x00OS MDPS C 1.00 1.03 56310/K4550 4OEDC103', b'\xf1\x00OS MDPS C 1.00 1.04 56310K4000\x00 4OEDC104', b'\xf1\x00OS MDPS C 1.00 1.04 56310K4050\x00 4OEDC104', ], - (Ecu.fwdRadar, 0x7D0, None): [ + (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00OSev SCC F-CUP 1.00 1.00 99110-K4000 ', b'\xf1\x00OSev SCC F-CUP 1.00 1.00 99110-K4100 ', b'\xf1\x00OSev SCC F-CUP 1.00 1.01 99110-K4000 ', @@ -1063,7 +1093,7 @@ FW_VERSIONS = { ], }, CAR.KONA_EV_2022: { - (Ecu.abs, 0x7D1, None): [ + (Ecu.abs, 0x7d1, None): [ b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', @@ -1071,7 +1101,7 @@ FW_VERSIONS = { b'\xf1\x00OS IEB \x02 102"\x05\x16 58520-K4010', b'\xf1\x00OS IEB \x03 102"\x05\x16 58520-K4010', ], - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', @@ -1079,7 +1109,7 @@ FW_VERSIONS = { b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.04 99211-J9200 904', b'\xf1\x00OSP LKA AT USA LHD 1.00 1.04 99211-J9200 904', ], - (Ecu.eps, 0x7D4, None): [ + (Ecu.eps, 0x7d4, None): [ b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102', @@ -1087,7 +1117,7 @@ FW_VERSIONS = { b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4971\x00 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4261\x00 4OEPC102', ], - (Ecu.fwdRadar, 0x7D0, None): [ + (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', ], }, @@ -1100,7 +1130,7 @@ FW_VERSIONS = { ], }, CAR.KIA_NIRO_EV: { - (Ecu.fwdRadar, 0x7D0, None): [ + (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4000 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ', @@ -1113,12 +1143,12 @@ FW_VERSIONS = { b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', b'\xf1\x8799110Q4600\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ', ], - (Ecu.eps, 0x7D4, None): [ + (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105', b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105', b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104', ], - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706', b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4000 191211', b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211', @@ -1150,16 +1180,16 @@ FW_VERSIONS = { b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92', b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01', ], - (Ecu.eps, 0x7D4, None): [ + (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', ], - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826', ], - (Ecu.fwdRadar, 0x7D0, None): [ + (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ', ], @@ -1171,13 +1201,13 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL3\x00\x00\x00\x00', ], - (Ecu.eps, 0x7D4, None): [ + (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', ], - (Ecu.fwdCamera, 0x7C4, None): [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 99211-G5500 210428', ], - (Ecu.fwdRadar, 0x7D0, None): [ + (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DEhe SCC F-CUP 1.00 1.00 99110-G5600 ', ], }, @@ -1201,29 +1231,31 @@ FW_VERSIONS = { ], }, CAR.KIA_SELTOS: { - (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x8799110Q5100\xf1\000SP2_ SCC FHCUP 1.01 1.05 99110-Q5100 ',], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x8799110Q5100\xf1\x00SP2_ SCC FHCUP 1.01 1.05 99110-Q5100 ', + ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x8758910-Q5450\xf1\000SP ESC \a 101\031\t\005 58910-Q5450', - b'\xf1\x8758910-Q5450\xf1\000SP ESC \t 101\031\t\005 58910-Q5450', + b'\xf1\x8758910-Q5450\xf1\x00SP ESC \x07 101\x19\t\x05 58910-Q5450', + b'\xf1\x8758910-Q5450\xf1\x00SP ESC \t 101\x19\t\x05 58910-Q5450', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81616D2051\000\000\000\000\000\000\000\000', - b'\xf1\x81616D5051\000\000\000\000\000\000\000\000', - b'\001TSP2KNL06F100J0K', - b'\001TSP2KNL06F200J0K', + b'\xf1\x81616D2051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81616D5051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x01TSP2KNL06F100J0K', + b'\x01TSP2KNL06F200J0K', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\000SP2 MDPS C 1.00 1.04 56300Q5200 ', - b'\xf1\000SP2 MDPS C 1.01 1.05 56300Q5200 ', + b'\xf1\x00SP2 MDPS C 1.00 1.04 56300Q5200 ', + b'\xf1\x00SP2 MDPS C 1.01 1.05 56300Q5200 ', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\000SP2 MFC AT USA LHD 1.00 1.04 99210-Q5000 191114', - b'\xf1\000SP2 MFC AT USA LHD 1.00 1.05 99210-Q5000 201012', + b'\xf1\x00SP2 MFC AT USA LHD 1.00 1.04 99210-Q5000 191114', + b'\xf1\x00SP2 MFC AT USA LHD 1.00 1.05 99210-Q5000 201012', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87CZLUB49370612JF7h\xa8y\x87\x99\xa7hv\x99\x97fv\x88\x87x\x89x\x96O\xff\x88\xff\xff\xff.@\xf1\x816V2C2051\000\000\xf1\0006V2B0_C2\000\0006V2C2051\000\000CSP4N20NS3\000\000\000\000', - b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS6\xd30\xa5\xb9', - b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b', + b'\xf1\x87CZLUB49370612JF7h\xa8y\x87\x99\xa7hv\x99\x97fv\x88\x87x\x89x\x96O\xff\x88\xff\xff\xff.@\xf1\x816V2C2051\x00\x00\xf1\x006V2B0_C2\x00\x006V2C2051\x00\x00CSP4N20NS3\x00\x00\x00\x00', + b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\x00T0190XBL T01950A1 DSP2T16X4X950NS6\xd30\xa5\xb9', + b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\x00T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b', ], }, CAR.KIA_OPTIMA_G4: { @@ -1360,7 +1392,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\xe8\xba\xce\xfa', - b'\xf1\x87CXMQFM2135005JB2E\xb9\x89\x98W\xa9y\x97h\xa9\x98\x99wxvwh\x87\177\xffx\xff\xff\xff,,\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', + b'\xf1\x87CXMQFM2135005JB2E\xb9\x89\x98W\xa9y\x97h\xa9\x98\x99wxvwh\x87\x7f\xffx\xff\xff\xff,,\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', b'\xf1\x87CXMQFM1916035JB2\x88vvgg\x87Wuwgev\xa9\x98\x88\x98h\x99\x9f\xffh\xff\xff\xff\xa5\xee\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', b'\xf1\x87CXLQF40189012JL2f\x88\x86\x88\x88vUex\xb8\x88\x88\x88\x87\x88\x89fh?\xffz\xff\xff\xff\x08z\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', b'\xf1\x87CXMQFM2728305JB2E\x97\x87xw\x87vwgw\x84x\x88\x88w\x89EI\xbf\xff{\xff\xff\xff\xe6\x0e\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', @@ -1377,7 +1409,7 @@ FW_VERSIONS = { CAR.ELANTRA_HEV_2021: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', - b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', + b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.08 99210-AA000 220728', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.09 99210-AA000 221108', @@ -1389,13 +1421,13 @@ FW_VERSIONS = { (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103', b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', - b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', + b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', b'\xf1\x00CN7 MDPS C 1.00 1.04 56310BY050\x00 4CNHC104', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', - b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000', - b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', + b'\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\xb9?A\xaa', + b'\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', + b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\xb9?A\xaa', b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', b'\xf1\x006U3L0_C2\x00\x006U3K9051\x00\x00HCN0G16NS1\x00\x00\x00\x00', ], @@ -1403,7 +1435,7 @@ FW_VERSIONS = { b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G8051\x00\x00\x00\x00\x00\x00\x00\x00', - ] + ], }, CAR.KONA_HEV: { (Ecu.abs, 0x7d1, None): [ @@ -1423,32 +1455,32 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - ] + ], }, CAR.SONATA_HYBRID: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', b'\xf1\x8799110L5000\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', - b'\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', - b'\xf1\x8799110L5000\xf1\000DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', + b'\xf1\x00DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', + b'\xf1\x8799110L5000\xf1\x00DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102', - b'\xf1\x8756310-L5450\xf1\000DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103', + b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103', b'\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', b'\xf1\x8756310L5450\x00\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.04 99211-L1000 191016', b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.05 99211-L1000 201109', - b'\xf1\000DN8HMFC AT USA LHD 1.00 1.06 99211-L1000 210325', + b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.06 99211-L1000 210325', b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.07 99211-L1000 211223', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\000PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', + b'\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E09\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', - b'\xf1\000PSBG2323 E09\000\000\000\000\000\000\000TDN2H20SA5\x97R\x88\x9e', + b'\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', b'\xf1\x87PCU\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', @@ -1462,19 +1494,19 @@ FW_VERSIONS = { }, CAR.KIA_SORENTO: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00UMP LKAS AT USA LHD 1.01 1.01 95740-C6550 d01' + b'\xf1\x00UMP LKAS AT USA LHD 1.01 1.01 95740-C6550 d01', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00UM ESC \x0c 12 \x18\x05\x06 58910-C6330' + b'\xf1\x00UM ESC \x0c 12 \x18\x05\x06 58910-C6330', ], - (Ecu.fwdRadar, 0x7D0, None): [ - b'\xf1\x00UM__ SCC F-CUP 1.00 1.00 96400-C6500 ' + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00UM__ SCC F-CUP 1.00 1.00 96400-C6500 ', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87LDKUAA0348164HE3\x87www\x87www\x88\x88\xa8\x88w\x88\x97xw\x88\x97x\x86o\xf8\xff\x87f\x7f\xff\x15\xe0\xf1\x81U811\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U811\x00\x00\x00\x00\x00\x00TUM4G33NL3V|DG' + b'\xf1\x87LDKUAA0348164HE3\x87www\x87www\x88\x88\xa8\x88w\x88\x97xw\x88\x97x\x86o\xf8\xff\x87f\x7f\xff\x15\xe0\xf1\x81U811\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U811\x00\x00\x00\x00\x00\x00TUM4G33NL3V|DG', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00' + b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.KIA_SORENTO_PHEV_4TH_GEN: { diff --git a/selfdrive/car/mazda/fingerprints.py b/selfdrive/car/mazda/fingerprints.py index 7f7964190d..ee1f4be38b 100644 --- a/selfdrive/car/mazda/fingerprints.py +++ b/selfdrive/car/mazda/fingerprints.py @@ -108,7 +108,6 @@ FW_VERSIONS = { b'SH9T-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.CX9: { (Ecu.eps, 0x730, None): [ b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -159,13 +158,11 @@ FW_VERSIONS = { b'PYD6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.MAZDA3: { (Ecu.eps, 0x730, None): [ b'BHN1-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KR11-3210X-K-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], (Ecu.engine, 0x7e0, None): [ b'P5JD-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -196,40 +193,38 @@ FW_VERSIONS = { b'PYKE-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.MAZDA6: { (Ecu.eps, 0x730, None): [ b'GBEF-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GBEF-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GFBC-3210X-A-00\000\000\000\000\000\000\000\000\000', + b'GFBC-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'PA34-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PX4F-188K2-D\000\000\000\000\000\000\000\000\000\000\000\000', + b'PX4F-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYH7-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYH7-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'K131-67XK2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K131-67XK2-E\000\000\000\000\000\000\000\000\000\000\000\000', + b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x760, None): [ b'GBVH-437K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GBVH-437K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GDDM-437K2-A\000\000\000\000\000\000\000\000\000\000\000\000', + b'GDDM-437K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x706, None): [ b'B61L-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'GSH7-67XK2-P\000\000\000\000\000\000\000\000\000\000\000\000', + b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PA28-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYH3-21PS1-D\000\000\000\000\000\000\000\000\000\000\000\000', + b'PYH3-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYH7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.CX9_2021: { (Ecu.eps, 0x730, None): [ b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -259,5 +254,5 @@ FW_VERSIONS = { b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - } + }, } diff --git a/selfdrive/car/nissan/fingerprints.py b/selfdrive/car/nissan/fingerprints.py index 87a7be07ad..3059bf5b0c 100644 --- a/selfdrive/car/nissan/fingerprints.py +++ b/selfdrive/car/nissan/fingerprints.py @@ -5,38 +5,27 @@ from openpilot.selfdrive.car.nissan.values import CAR Ecu = car.CarParams.Ecu FINGERPRINTS = { - CAR.XTRAIL: [ - { - 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 2, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1821: 8, 1823: 8, 1837: 8, 2015: 8, 2016: 8, 2024: 8 - }, - { - 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 527: 1, 548: 8, 637: 4, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 8, 1497: 3, 1534: 6, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 2015: 8, 2016: 8, 2024: 8 - }, - ], - CAR.LEAF: [ - { - 2: 5, 42: 6, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 944: 1, 976: 6, 1008: 7, 1011: 7, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8, 1856: 8, 1859: 8, 1861: 8, 1864: 8, 1874: 8, 1888: 8, 1891: 8, 1893: 8, 1906: 8, 1947: 8, 1949: 8, 1979: 8, 1981: 8, 2016: 8, 2017: 8, 2021: 8, 643: 5, 1792: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8 - }, - # 2020 Leaf SV Plus - { - 2: 5, 42: 8, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 976: 6, 1008: 7, 1009: 8, 1010: 8, 1011: 7, 1012: 8, 1013: 8, 1019: 8, 1020: 8, 1021: 8, 1022: 8, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1402: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8 - }, - ], - CAR.LEAF_IC: [ - { - 2: 5, 42: 6, 264: 3, 282: 8, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 756: 5, 758: 3, 761: 2, 783: 3, 830: 2, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 1001: 6, 1057: 3, 1227: 8, 1228: 8, 1229: 8, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1514: 6, 1549: 8, 1573: 6, 1792: 8, 1821: 8, 1822: 8, 1837: 8, 1838: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8 - }, - ], - CAR.ROGUE: [ - { - 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 634: 7, 643: 5, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1042: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1534: 7, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1839: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }, - ], - CAR.ALTIMA: [ - { - 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 438: 8, 451: 8, 517: 8, 520: 2, 522: 8, 523: 6, 539: 8, 541: 7, 542: 8, 543: 8, 544: 8, 545: 8, 546: 8, 547: 8, 548: 8, 570: 8, 576: 8, 577: 8, 582: 8, 583: 8, 584: 8, 586: 8, 587: 8, 588: 8, 589: 8, 590: 8, 591: 8, 592: 8, 600: 8, 601: 8, 610: 8, 611: 8, 612: 8, 614: 8, 615: 8, 616: 8, 617: 8, 622: 8, 623: 8, 634: 7, 638: 8, 645: 8, 648: 5, 654: 6, 658: 8, 659: 8, 660: 8, 661: 8, 665: 8, 666: 8, 674: 2, 675: 8, 676: 8, 682: 8, 683: 8, 684: 8, 685: 8, 686: 8, 687: 8, 689: 8, 690: 8, 703: 8, 708: 7, 709: 7, 711: 7, 712: 7, 713: 7, 714: 8, 715: 8, 716: 8, 717: 7, 718: 7, 719: 7, 720: 7, 723: 8, 726: 7, 727: 7, 728: 7, 735: 8, 746: 8, 748: 6, 749: 6, 750: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 779: 7, 781: 7, 782: 7, 783: 3, 851: 8, 855: 5, 1001: 6, 1041: 8, 1042: 8, 1055: 3, 1100: 7, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1144: 7, 1145: 7, 1227: 8, 1228: 8, 1229: 8, 1232: 8, 1247: 4, 1258: 8, 1259: 8, 1266: 8, 1273: 7, 1306: 1, 1314: 8, 1323: 8, 1324: 8, 1342: 1, 1376: 8, 1401: 8, 1454: 8, 1497: 3, 1514: 6, 1526: 8, 1527: 5, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }, - ] + CAR.XTRAIL: [{ + 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 2, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1821: 8, 1823: 8, 1837: 8, 2015: 8, 2016: 8, 2024: 8 + }, + { + 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 527: 1, 548: 8, 637: 4, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 768: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1111: 4, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 8, 1497: 3, 1534: 6, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 2015: 8, 2016: 8, 2024: 8 + }], + CAR.LEAF: [{ + 2: 5, 42: 6, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 944: 1, 976: 6, 1008: 7, 1011: 7, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8, 1856: 8, 1859: 8, 1861: 8, 1864: 8, 1874: 8, 1888: 8, 1891: 8, 1893: 8, 1906: 8, 1947: 8, 1949: 8, 1979: 8, 1981: 8, 2016: 8, 2017: 8, 2021: 8, 643: 5, 1792: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8 + }, + { + 2: 5, 42: 8, 264: 3, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 724: 6, 758: 3, 761: 2, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 976: 6, 1008: 7, 1009: 8, 1010: 8, 1011: 7, 1012: 8, 1013: 8, 1019: 8, 1020: 8, 1021: 8, 1022: 8, 1057: 3, 1227: 8, 1228: 8, 1261: 5, 1342: 1, 1354: 8, 1361: 8, 1402: 8, 1459: 8, 1477: 8, 1497: 3, 1549: 8, 1573: 6, 1821: 8, 1837: 8 + }], + CAR.LEAF_IC: [{ + 2: 5, 42: 6, 264: 3, 282: 8, 361: 8, 372: 8, 384: 8, 389: 8, 403: 8, 459: 7, 460: 4, 470: 8, 520: 1, 569: 8, 581: 8, 634: 7, 640: 8, 643: 5, 644: 8, 645: 8, 646: 5, 658: 8, 682: 8, 683: 8, 689: 8, 756: 5, 758: 3, 761: 2, 783: 3, 830: 2, 852: 8, 853: 8, 856: 8, 861: 8, 943: 8, 944: 1, 1001: 6, 1057: 3, 1227: 8, 1228: 8, 1229: 8, 1342: 1, 1354: 8, 1361: 8, 1459: 8, 1477: 8, 1497: 3, 1514: 6, 1549: 8, 1573: 6, 1792: 8, 1821: 8, 1822: 8, 1837: 8, 1838: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8 + }], + CAR.ROGUE: [{ + 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 520: 2, 523: 6, 548: 8, 634: 7, 643: 5, 645: 8, 658: 8, 665: 8, 666: 8, 674: 2, 682: 8, 683: 8, 689: 8, 723: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 783: 3, 851: 8, 855: 8, 1041: 8, 1042: 8, 1055: 2, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1227: 8, 1228: 8, 1247: 4, 1266: 8, 1273: 7, 1342: 1, 1376: 6, 1401: 8, 1474: 2, 1497: 3, 1534: 7, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1839: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }], + CAR.ALTIMA: [{ + 2: 5, 42: 6, 346: 6, 347: 5, 348: 8, 349: 7, 361: 8, 386: 8, 389: 8, 397: 8, 398: 8, 403: 8, 438: 8, 451: 8, 517: 8, 520: 2, 522: 8, 523: 6, 539: 8, 541: 7, 542: 8, 543: 8, 544: 8, 545: 8, 546: 8, 547: 8, 548: 8, 570: 8, 576: 8, 577: 8, 582: 8, 583: 8, 584: 8, 586: 8, 587: 8, 588: 8, 589: 8, 590: 8, 591: 8, 592: 8, 600: 8, 601: 8, 610: 8, 611: 8, 612: 8, 614: 8, 615: 8, 616: 8, 617: 8, 622: 8, 623: 8, 634: 7, 638: 8, 645: 8, 648: 5, 654: 6, 658: 8, 659: 8, 660: 8, 661: 8, 665: 8, 666: 8, 674: 2, 675: 8, 676: 8, 682: 8, 683: 8, 684: 8, 685: 8, 686: 8, 687: 8, 689: 8, 690: 8, 703: 8, 708: 7, 709: 7, 711: 7, 712: 7, 713: 7, 714: 8, 715: 8, 716: 8, 717: 7, 718: 7, 719: 7, 720: 7, 723: 8, 726: 7, 727: 7, 728: 7, 735: 8, 746: 8, 748: 6, 749: 6, 750: 8, 758: 3, 772: 8, 773: 6, 774: 7, 775: 8, 776: 6, 777: 7, 778: 6, 779: 7, 781: 7, 782: 7, 783: 3, 851: 8, 855: 5, 1001: 6, 1041: 8, 1042: 8, 1055: 3, 1100: 7, 1104: 4, 1105: 6, 1107: 4, 1108: 8, 1110: 7, 1111: 7, 1144: 7, 1145: 7, 1227: 8, 1228: 8, 1229: 8, 1232: 8, 1247: 4, 1258: 8, 1259: 8, 1266: 8, 1273: 7, 1306: 1, 1314: 8, 1323: 8, 1324: 8, 1342: 1, 1376: 8, 1401: 8, 1454: 8, 1497: 3, 1514: 6, 1526: 8, 1527: 5, 1792: 8, 1821: 8, 1823: 8, 1837: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + }], } FW_VERSIONS = { diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py index ac3b6d60c7..6625d14e5c 100644 --- a/selfdrive/car/subaru/fingerprints.py +++ b/selfdrive/car/subaru/fingerprints.py @@ -7,32 +7,32 @@ FW_VERSIONS = { CAR.ASCENT: { (Ecu.abs, 0x7b0, None): [ b'\xa5 \x19\x02\x00', - b'\xa5 !\002\000', + b'\xa5 !\x02\x00', b'\xf1\x82\xa5 \x19\x02\x00', ], (Ecu.eps, 0x746, None): [ b'\x85\xc0\xd0\x00', - b'\005\xc0\xd0\000', + b'\x05\xc0\xd0\x00', b'\x95\xc0\xd0\x00', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00d\xb9\x1f@ \x10', - b'\000\000e~\037@ \'', + b"\x00\x00e~\x1f@ '", b'\x00\x00e@\x1f@ $', b'\x00\x00d\xb9\x00\x00\x00\x00', b'\x00\x00e@\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\xbb,\xa0t\a', + b'\xbb,\xa0t\x07', b'\xf1\x82\xbb,\xa0t\x87', - b'\xf1\x82\xbb,\xa0t\a', - b'\xf1\x82\xd9,\xa0@\a', + b'\xf1\x82\xbb,\xa0t\x07', + b'\xf1\x82\xd9,\xa0@\x07', b'\xf1\x82\xd1,\xa0q\x07', b'\xd1,\xa0q\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\x00\xfe\xf7\x00\x00', - b'\001\xfe\xf9\000\000', + b'\x01\xfe\xf9\x00\x00', b'\x01\xfe\xf7\x00\x00', b'\x01\xfe\xfa\x00\x00', ], @@ -70,7 +70,7 @@ FW_VERSIONS = { b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\xde\"a0\x07', + b'\xde"a0\x07', b'\xe2"aq\x07', b'\xde,\xa0@\x07', b'\xe2,\xa0@\x07', @@ -84,14 +84,14 @@ FW_VERSIONS = { }, CAR.IMPREZA: { (Ecu.abs, 0x7b0, None): [ - b'\x7a\x94\x3f\x90\x00', + b'z\x94?\x90\x00', b'\xa2 \x185\x00', b'\xa2 \x193\x00', b'\xa2 \x194\x00', b'z\x94.\x90\x00', - b'z\x94\b\x90\x01', + b'z\x94\x08\x90\x01', b'\xa2 \x19`\x00', - b'z\x94\f\x90\001', + b'z\x94\x0c\x90\x01', b'z\x9c\x19\x80\x01', b'z\x94\x08\x90\x00', b'z\x84\x19\x90\x00', @@ -99,7 +99,7 @@ FW_VERSIONS = { b'z\x94\x0c\x90\x00', ], (Ecu.eps, 0x746, None): [ - b'\x7a\xc0\x0c\x00', + b'z\xc0\x0c\x00', b'z\xc0\x08\x00', b'\x8a\xc0\x00\x00', b'z\xc0\x04\x00', @@ -108,14 +108,14 @@ FW_VERSIONS = { b'z\xc0\n\x00', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00\x64\xb5\x1f\x40\x20\x0e', + b'\x00\x00d\xb5\x1f@ \x0e', b'\x00\x00d\xdc\x1f@ \x0e', b'\x00\x00e\x1c\x1f@ \x14', - b'\x00\x00d)\x1f@ \a', + b'\x00\x00d)\x1f@ \x07', b'\x00\x00e+\x1f@ \x14', - b'\000\000e+\000\000\000\000', - b'\000\000dd\037@ \016', - b'\000\000e\002\037@ \024', + b'\x00\x00e+\x00\x00\x00\x00', + b'\x00\x00dd\x1f@ \x0e', + b'\x00\x00e\x02\x1f@ \x14', b'\x00\x00d)\x00\x00\x00\x00', b'\x00\x00c\xf4\x00\x00\x00\x00', b'\x00\x00d\xdc\x00\x00\x00\x00', @@ -124,20 +124,20 @@ FW_VERSIONS = { b'\x00\x00e\x1c\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\xaa\x61\x66\x73\x07', - b'\xbeacr\a', - b'\xc5!`r\a', - b'\xaa!ds\a', - b'\xaa!`u\a', - b'\xaa!dq\a', - b'\xaa!dt\a', - b'\xc5!ar\a', - b'\xbe!as\a', + b'\xaaafs\x07', + b'\xbeacr\x07', + b'\xc5!`r\x07', + b'\xaa!ds\x07', + b'\xaa!`u\x07', + b'\xaa!dq\x07', + b'\xaa!dt\x07', + b'\xc5!ar\x07', + b'\xbe!as\x07', b'\xc5!as\x07', - b'\xc5!ds\a', - b'\xc5!`s\a', - b'\xaa!au\a', - b'\xbe!at\a', + b'\xc5!ds\x07', + b'\xc5!`s\x07', + b'\xaa!au\x07', + b'\xbe!at\x07', b'\xaa\x00Bu\x07', b'\xc5!dr\x07', b'\xaa!aw\x07', @@ -146,17 +146,17 @@ FW_VERSIONS = { b'\xc5!ap\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\xe3\xe5\x46\x31\x00', + b'\xe3\xe5F1\x00', b'\xe4\xe5\x061\x00', b'\xe5\xf5\x04\x00\x00', b'\xe3\xf5G\x00\x00', - b'\xe3\xf5\a\x00\x00', + b'\xe3\xf5\x07\x00\x00', b'\xe3\xf5C\x00\x00', b'\xe5\xf5B\x00\x00', - b'\xe5\xf5$\000\000', - b'\xe4\xf5\a\000\000', - b'\xe3\xf5F\000\000', - b'\xe4\xf5\002\000\000', + b'\xe5\xf5$\x00\x00', + b'\xe4\xf5\x07\x00\x00', + b'\xe3\xf5F\x00\x00', + b'\xe4\xf5\x02\x00\x00', b'\xe3\xd0\x081\x00', b'\xe3\xf5\x06\x00\x00', b'\xe3\xd5\x161\x00', @@ -164,22 +164,22 @@ FW_VERSIONS = { }, CAR.IMPREZA_2020: { (Ecu.abs, 0x7b0, None): [ - b'\xa2 \0314\000', - b'\xa2 \0313\000', - b'\xa2 !i\000', - b'\xa2 !`\000', + b'\xa2 \x194\x00', + b'\xa2 \x193\x00', + b'\xa2 !i\x00', + b'\xa2 !`\x00', b'\xf1\x00\xb2\x06\x04', b'\xa2 `\x00', b'\xa2 !3\x00', ], (Ecu.eps, 0x746, None): [ - b'\x9a\xc0\000\000', - b'\n\xc0\004\000', + b'\x9a\xc0\x00\x00', + b'\n\xc0\x04\x00', b'\x9a\xc0\x04\x00', b'\n\xc0\x04\x01', ], (Ecu.fwdCamera, 0x787, None): [ - b'\000\000eb\037@ \"', + b'\x00\x00eb\x1f@ "', b'\x00\x00e\x8f\x1f@ )', b'\x00\x00eq\x1f@ "', b'\x00\x00eq\x00\x00\x00\x00', @@ -188,11 +188,11 @@ FW_VERSIONS = { b'\x00\x00e\xa4\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\xca!ap\a', - b'\xca!`p\a', - b'\xca!`0\a', - b'\xcc\"f0\a', - b'\xcc!fp\a', + b'\xca!ap\x07', + b'\xca!`p\x07', + b'\xca!`0\x07', + b'\xcc"f0\x07', + b'\xcc!fp\x07', b'\xcc!`p\x07', b'\xca!f@\x07', b'\xca!fp\x07', @@ -204,11 +204,11 @@ FW_VERSIONS = { b'\xe6!`@\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\xe6\xf5\004\000\000', - b'\xe6\xf5$\000\000', + b'\xe6\xf5\x04\x00\x00', + b'\xe6\xf5$\x00\x00', b'\xe7\xf5\x04\x00\x00', - b'\xe7\xf6B0\000', - b'\xe7\xf5D0\000', + b'\xe7\xf6B0\x00', + b'\xe7\xf5D0\x00', b'\xf1\x00\xd7\x10@', b'\xe6\xf5D0\x00', b'\xe9\xf6F0\x00', @@ -231,15 +231,15 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'\xd7!`@\x07', - b'\xd7!`p\a', + b'\xd7!`p\x07', b'\xf4!`0\x07', ], }, CAR.FORESTER: { (Ecu.abs, 0x7b0, None): [ b'\xa3 \x18\x14\x00', - b'\xa3 \024\000', - b'\xa3 \031\024\000', + b'\xa3 \x14\x00', + b'\xa3 \x19\x14\x00', b'\xa3 \x14\x01', b'\xf1\x00\xbb\r\x05', b'\xa3 \x18&\x00', @@ -252,7 +252,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00e!\x1f@ \x11', b'\x00\x00e\x97\x1f@ 0', - b'\000\000e`\037@ ', + b'\x00\x00e`\x1f@ ', b'\xf1\x00\xac\x02\x00', b'\x00\x00e!\x00\x00\x00\x00', b'\x00\x00e\x97\x00\x00\x00\x00', @@ -262,16 +262,16 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xb6"`A\x07', b'\xcf"`0\x07', - b'\xcb\"`@\a', - b'\xcb\"`p\a', + b'\xcb"`@\x07', + b'\xcb"`p\x07', b'\xf1\x00\xa2\x10\n', b'\xcf"`p\x07', b'\xb6\xa2`A\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\032\xf6B0\000', + b'\x1a\xf6B0\x00', b'\x1a\xf6F`\x00', - b'\032\xf6b`\000', + b'\x1a\xf6b`\x00', b'\x1a\xf6B`\x00', b'\x1a\xf6b0\x00', b'\x1a\xe6B1\x00', @@ -297,7 +297,7 @@ FW_VERSIONS = { }, CAR.FORESTER_PREGLOBAL: { (Ecu.abs, 0x7b0, None): [ - b'\x7d\x97\x14\x40', + b'}\x97\x14@', b'\xf1\x00\xbb\x0c\x04', b'm\x97\x14@', ], @@ -306,23 +306,23 @@ FW_VERSIONS = { b'm\xc0\x10\x00', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00\x64\x35\x1f\x40\x20\x09', + b'\x00\x00d5\x1f@ \t', b'\x00\x00c\xe9\x1f@ \x03', b'\x00\x00d\xd3\x1f@ \t', b'\x00\x00c\xe9\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\xba"@p\a', - b'\xa7)\xa0q\a', - b'\xf1\x82\xa7)\xa0q\a', - b'\xba"@@\a', + b'\xba"@p\x07', + b'\xa7)\xa0q\x07', + b'\xf1\x82\xa7)\xa0q\x07', + b'\xba"@@\x07', b'\xa7"@p\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\xdc\xf2\x60\x60\x00', + b'\xdc\xf2``\x00', b'\xdc\xf2@`\x00', b'\xda\xfd\xe0\x80\x00', - b'\xdc\xf2`\x81\000', + b'\xdc\xf2`\x81\x00', b'\xdc\xf2`\x80\x00', b'\x1a\xf6F`\x00', b'\xda\xf2`\x80\x00', @@ -333,7 +333,7 @@ FW_VERSIONS = { b'k\x97D\x00', b'[\xba\xc4\x03', b'{\x97D\x00', - b'[\x97D\000', + b'[\x97D\x00', b'k\x9aD\x00', ], (Ecu.eps, 0x746, None): [ @@ -347,17 +347,17 @@ FW_VERSIONS = { b'\x00\x00c\xec\x1f@ \x04', ], (Ecu.engine, 0x7e0, None): [ - b'\xab*@r\a', + b'\xab*@r\x07', b'\xa0+@p\x07', b'\xb4"@0\x07', - b'\xa0"@q\a', + b'\xa0"@q\x07', b'\xab+@p\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xbe\xf2\x00p\x00', b'\xbf\xfb\xc0\x80\x00', b'\xbd\xf2\x00`\x00', - b'\xbf\xf2\000\x80\000', + b'\xbf\xf2\x00\x80\x00', b'\xbe\xfb\xc0p\x00', ], }, @@ -365,17 +365,17 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'{\x9a\xac\x00', b'k\x97\xac\x00', - b'\x5b\xf7\xbc\x03', + b'[\xf7\xbc\x03', b'[\xf7\xac\x03', b'{\x97\xac\x00', - b'k\x9a\xac\000', + b'k\x9a\xac\x00', b'[\xba\xac\x03', - b'[\xf7\xac\000', + b'[\xf7\xac\x00', ], (Ecu.eps, 0x746, None): [ b'k\xb0\x00\x00', b'[\xb0\x00\x00', - b'\x4b\xb0\x00\x02', + b'K\xb0\x00\x02', b'K\xb0\x00\x00', b'{\xb0\x00\x01', ], @@ -384,35 +384,35 @@ FW_VERSIONS = { b'\x00\x00c\xd1\x1f@\x10\x17', b'\xf1\x00\xf0\xe0\x0e', b'\x00\x00c\x94\x00\x00\x00\x00', - b'\x00\x00c\x94\x1f@\x10\b', + b'\x00\x00c\x94\x1f@\x10\x08', b'\x00\x00c\xb7\x1f@\x10\x16', - b'\000\000c\x90\037@\020\016', - b'\x00\x00c\xec\x37@\x04', + b'\x00\x00c\x90\x1f@\x10\x0e', + b'\x00\x00c\xec7@\x04', ], (Ecu.engine, 0x7e0, None): [ - b'\xb4+@p\a', - b'\xab\"@@\a', - b'\xa0\x62\x41\x71\x07', - b'\xa0*@q\a', - b'\xab*@@\a', - b'\xb4"@0\a', - b'\xb4"@p\a', - b'\xab"@s\a', - b'\xab+@@\a', - b'\xb4"@r\a', + b'\xb4+@p\x07', + b'\xab"@@\x07', + b'\xa0bAq\x07', + b'\xa0*@q\x07', + b'\xab*@@\x07', + b'\xb4"@0\x07', + b'\xb4"@p\x07', + b'\xab"@s\x07', + b'\xab+@@\x07', + b'\xb4"@r\x07', b'\xa0+@@\x07', - b'\xa0\"@\x80\a', + b'\xa0"@\x80\x07', b'\xa0*@u\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xbd\xfb\xe0\x80\x00', b'\xbe\xf2@\x80\x00', - b'\xbf\xe2\x40\x80\x00', + b'\xbf\xe2@\x80\x00', b'\xbf\xf2@\x80\x00', b'\xbe\xf2@p\x00', b'\xbd\xf2@`\x00', - b'\xbd\xf2@\x81\000', - b'\xbe\xfb\xe0p\000', + b'\xbd\xf2@\x81\x00', + b'\xbe\xfb\xe0p\x00', b'\xbf\xfb\xe0b\x00', ], }, @@ -423,7 +423,7 @@ FW_VERSIONS = { b'\x9b\x97\xac\x00', b'\x8b\x97\xbc\x00', b'\x8b\x99\xac\x00', - b'\x9b\x9a\xac\000', + b'\x9b\x9a\xac\x00', b'\x9b\x97\xbe\x10', ], (Ecu.eps, 0x746, None): [ @@ -439,14 +439,14 @@ FW_VERSIONS = { b'\x00\x00e\x19\x1f@ \x15', ], (Ecu.engine, 0x7e0, None): [ - b'\xb5"@p\a', - b'\xb5+@@\a', - b'\xb5"@P\a', - b'\xc4"@0\a', + b'\xb5"@p\x07', + b'\xb5+@@\x07', + b'\xb5"@P\x07', + b'\xc4"@0\x07', b'\xb5b@1\x07', - b'\xb5q\xe0@\a', - b'\xc4+@0\a', - b'\xc4b@p\a', + b'\xb5q\xe0@\x07', + b'\xc4+@0\x07', + b'\xc4b@p\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xbc\xf2@\x81\x00', @@ -456,14 +456,14 @@ FW_VERSIONS = { b'\xbc\xe2@\x80\x00', b'\xbc\xfb\xe0`\x00', b'\xbc\xaf\xe0`\x00', - b'\xbb\xfb\xe0`\000', + b'\xbb\xfb\xe0`\x00', ], }, CAR.OUTBACK: { (Ecu.abs, 0x7b0, None): [ b'\xa1 \x06\x01', - b'\xa1 \a\x00', - b'\xa1 \b\001', + b'\xa1 \x07\x00', + b'\xa1 \x08\x01', b'\xa1 \x06\x00', b'\xa1 "\t\x01', b'\xa1 \x08\x02', @@ -474,21 +474,21 @@ FW_VERSIONS = { ], (Ecu.eps, 0x746, None): [ b'\x9b\xc0\x10\x00', - b'\x9b\xc0\x20\x00', + b'\x9b\xc0 \x00', b'\x1b\xc0\x10\x00', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00eJ\x00\x1f@ \x19\x00', - b'\000\000e\x80\000\037@ \031\000', + b'\x00\x00e\x80\x00\x1f@ \x19\x00', b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00', b'\x00\x00e\x9a\x00\x1f@ 1\x00', b'\x00\x00eJ\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xbc,\xa0q\x07', - b'\xbc\"`@\a', - b'\xde"`0\a', - b'\xf1\x82\xbc,\xa0q\a', + b'\xbc"`@\x07', + b'\xde"`0\x07', + b'\xf1\x82\xbc,\xa0q\x07', b'\xf1\x82\xe3,\xa0@\x07', b'\xe2"`0\x07', b'\xe2"`p\x07', @@ -558,6 +558,6 @@ FW_VERSIONS = { (Ecu.transmission, 0x7a3, None): [ b'\xa8\x8e\xf41\x00', b'\xa8\xfe\xf41\x00', - ] - } + ], + }, } diff --git a/selfdrive/car/tesla/fingerprints.py b/selfdrive/car/tesla/fingerprints.py index 1d41ddbb3a..772ca59efa 100644 --- a/selfdrive/car/tesla/fingerprints.py +++ b/selfdrive/car/tesla/fingerprints.py @@ -5,11 +5,9 @@ from openpilot.selfdrive.car.tesla.values import CAR Ecu = car.CarParams.Ecu FINGERPRINTS = { - CAR.AP1_MODELS: [ - { - 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4 - }, - ], + CAR.AP1_MODELS: [{ + 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4 + }], } FW_VERSIONS = { diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 18935a2be3..8332f242b2 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -182,7 +182,7 @@ FW_VERSIONS = { b'8965B33551\x00\x00\x00\x00\x00\x00', b'8965B33611\x00\x00\x00\x00\x00\x00', ], - (Ecu.fwdRadar, 0x750, 0xf): [ # Same as 0x791 + (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F0601200 ', b'8821F0601300 ', b'8821F0602000 ', @@ -609,7 +609,9 @@ FW_VERSIONS = { b'8965B48150\x00\x00\x00\x00\x00\x00', b'8965B48210\x00\x00\x00\x00\x00\x00', ], - (Ecu.abs, 0x7b0, None): [b'F15260E011\x00\x00\x00\x00\x00\x00'], + (Ecu.abs, 0x7b0, None): [ + b'F15260E011\x00\x00\x00\x00\x00\x00', + ], (Ecu.dsu, 0x791, None): [ b'881510E01100\x00\x00\x00\x00', b'881510E01200\x00\x00\x00\x00', @@ -835,7 +837,7 @@ FW_VERSIONS = { b'8965B47022\x00\x00\x00\x00\x00\x00', b'8965B47023\x00\x00\x00\x00\x00\x00', b'8965B47050\x00\x00\x00\x00\x00\x00', - b'8965B47060\x00\x00\x00\x00\x00\x00', # This is the EPS with good angle sensor + b'8965B47060\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'F152647290\x00\x00\x00\x00\x00\x00', @@ -1291,7 +1293,7 @@ FW_VERSIONS = { ], }, CAR.LEXUS_GS_F: { - (Ecu.engine, 0x7E0, None): [ + (Ecu.engine, 0x7e0, None): [ b'\x0233075200\x00\x00\x00\x00\x00\x00\x00\x00530B9000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ @@ -1376,7 +1378,7 @@ FW_VERSIONS = { b'8965B78120\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ - b"\x018821F3301400\x00\x00\x00\x00", + b'\x018821F3301400\x00\x00\x00\x00', b'\x018821F3301200\x00\x00\x00\x00', b'\x018821F3301300\x00\x00\x00\x00', ], @@ -1606,14 +1608,22 @@ FW_VERSIONS = { ], }, CAR.MIRAI: { - (Ecu.abs, 0x7D1, None): [b'\x01898A36203000\x00\x00\x00\x00',], - (Ecu.abs, 0x7B0, None): [ # a second ABS ECU + (Ecu.abs, 0x7d1, None): [ + b'\x01898A36203000\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ b'\x01F15266203200\x00\x00\x00\x00', b'\x01F15266203500\x00\x00\x00\x00', ], - (Ecu.eps, 0x7A1, None): [b'\x028965B6204100\x00\x00\x00\x008965B6203100\x00\x00\x00\x00',], - (Ecu.fwdRadar, 0x750, 0xf): [b'\x018821F6201200\x00\x00\x00\x00',], - (Ecu.fwdCamera, 0x750, 0x6d): [b'\x028646F6201400\x00\x00\x00\x008646G5301200\x00\x00\x00\x00',], + (Ecu.eps, 0x7a1, None): [ + b'\x028965B6204100\x00\x00\x00\x008965B6203100\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F6201200\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F6201400\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + ], }, CAR.ALPHARD_TSS2: { (Ecu.engine, 0x7e0, None): [ @@ -1626,7 +1636,7 @@ FW_VERSIONS = { b'8965B58052\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ - b'F152658341\x00\x00\x00\x00\x00\x00' + b'F152658341\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301200\x00\x00\x00\x00', diff --git a/selfdrive/car/volkswagen/fingerprints.py b/selfdrive/car/volkswagen/fingerprints.py index a0134870b9..bd58e12ebc 100644 --- a/selfdrive/car/volkswagen/fingerprints.py +++ b/selfdrive/car/volkswagen/fingerprints.py @@ -3,6 +3,9 @@ from openpilot.selfdrive.car.volkswagen.values import CAR Ecu = car.CarParams.Ecu +# TODO: Sharan Mk2 EPS and DQ250 auto trans both require KWP2000 support for fingerprinting + + FW_VERSIONS = { CAR.ARTEON_MK1: { (Ecu.engine, 0x7e0, None): [ @@ -73,15 +76,15 @@ FW_VERSIONS = { b'\xf1\x8709G927158GL\xf1\x893939', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655BC\xf1\x890503\xf1\x82\0161914151912001103111122031200', - b'\xf1\x873Q0959655BN\xf1\x890713\xf1\x82\0162214152212001105141122052900', + b'\xf1\x873Q0959655BC\xf1\x890503\xf1\x82\x0e1914151912001103111122031200', + b'\xf1\x873Q0959655BN\xf1\x890713\xf1\x82\x0e2214152212001105141122052900', b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\x0e1114151112001105111122052900', - b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\0162214152212001105141122052900', + b'\xf1\x873Q0959655DB\xf1\x890720\xf1\x82\x0e2214152212001105141122052900', b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1114151112001105161122052J00', b'\xf1\x873Q0959655DM\xf1\x890732\xf1\x82\x0e1115151112001105171122052J00', ], (Ecu.eps, 0x712, None): [ - b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\00571B60924A1', + b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B60924A1', b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6G920A1', b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6M921A1', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6080105', @@ -102,9 +105,6 @@ FW_VERSIONS = { b'\xf1\x8704L906056EK\xf1\x896391', b'\xf1\x8705L906023BC\xf1\x892688', ], - # Only current upstreamed vehicle has a manual transmission - #(Ecu.transmission, 0x7e1, None): [ - #], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AL\xf1\x890505\xf1\x82\x0e1411001413001203151311031100', b'\xf1\x873Q0959655BG\xf1\x890703\xf1\x82\x0e16120016130012051G1313052900', @@ -332,22 +332,22 @@ FW_VERSIONS = { b'\xf1\x870GC300020N \xf1\x892803', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AG\xf1\x890336\xf1\x82\02314171231313500314611011630169333463100', + b'\xf1\x875Q0959655AG\xf1\x890336\xf1\x82\x1314171231313500314611011630169333463100', b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1314171231313500314611011630169333463100', - b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314642011650169333463100', - b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314643011650169333463100', - b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02311170031313300314240011150119333433100', - b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02319170031313300314240011550159333463100', + b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\x1314171231313500314642011650169333463100', + b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\x1314171231313500314643011650169333463100', + b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\x1311170031313300314240011150119333433100', + b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\x1319170031313300314240011550159333463100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314643021650169333613100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314642021650169333613100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A10A11A1', - b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\00521A10A01A1', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A10A01A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521B00404A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A00642A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A10A01A1', - b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\00571A10A11A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A00642A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A10A01A1', + b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\x0571A10A11A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572N \xf1\x890681', @@ -394,11 +394,11 @@ FW_VERSIONS = { b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e5915005914001305701311052900', b'\xf1\x873Q0959655BG\xf1\x890712\xf1\x82\x0e5915005914001305701311052900', - b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900', + b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e5915005914001344701311442900', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e5915005914001354701311542900', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900', b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012416612124111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011111200631145171716121691132111', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1', @@ -407,9 +407,9 @@ FW_VERSIONS = { b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526B0060905', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1', - b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516B00501A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521B00703A1', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521B00606A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516B00501A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00703A1', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563B0000600', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00603A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600', @@ -439,12 +439,12 @@ FW_VERSIONS = { b'\xf1\x8709G927158FQ\xf1\x893745', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x87561959655 \xf1\x890210\xf1\x82\02212121111113000102011--121012--101312', - b'\xf1\x87561959655C \xf1\x890508\xf1\x82\02215141111121100314919--153015--304831', + b'\xf1\x87561959655 \xf1\x890210\xf1\x82\x1212121111113000102011--121012--101312', + b'\xf1\x87561959655C \xf1\x890508\xf1\x82\x1215141111121100314919--153015--304831', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x87561907567A \xf1\x890132', - b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\00152', + b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\x0152', ], }, CAR.POLO_MK6: { @@ -472,7 +472,6 @@ FW_VERSIONS = { ], }, CAR.SHARAN_MK2: { - # TODO: Sharan Mk2 EPS and DQ250 auto trans both require KWP2000 support for fingerprinting (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704L906016HE\xf1\x894635', ], @@ -519,7 +518,7 @@ FW_VERSIONS = { b'\xf1\x870CW300050E \xf1\x891903', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\02212130411110411--04041104141311152H14', + b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1212130411110411--04041104141311152H14', ], (Ecu.eps, 0x712, None): [ b'\xf1\x872Q1909144M \xf1\x896041', @@ -571,13 +570,13 @@ FW_VERSIONS = { b'\xf1\x870GC300046Q \xf1\x892802', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100', + b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\x1331310031333334313132573732379333313100', b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1331310031333334313132013730379333423100', - b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100', + b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\x1316143231313500314641011750179333423100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1312110031333300314240013750379333423100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1312110031333300314240583752379333423100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333336313140013950399333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100', @@ -591,11 +590,11 @@ FW_VERSIONS = { b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', b'\xf1\x875QF909144A \xf1\x895581\xf1\x82\x0571A60834A1', - b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\00571A60634A1', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571A60634A1', b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571A62A32A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60804A1', b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002RA60A2ROM', b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002SA6092SOM', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', @@ -621,7 +620,7 @@ FW_VERSIONS = { b'\xf1\x870CW300051M \xf1\x891926', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AS\xf1\x890318\xf1\x82\023363500213533353141324C4732479333313100', + b'\xf1\x875Q0959655AS\xf1\x890318\xf1\x82\x13363500213533353141324C4732479333313100', b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1336350021353336314740025250529333613100', ], (Ecu.eps, 0x712, None): [ @@ -736,34 +735,34 @@ FW_VERSIONS = { b'\xf1\x870GC300042J \xf1\x891402', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655AB\xf1\x890388\xf1\x82\0211111001111111206110412111321139114', + b'\xf1\x875Q0959655AB\xf1\x890388\xf1\x82\x111111001111111206110412111321139114', b'\xf1\x875Q0959655AM\xf1\x890315\xf1\x82\x1311111111111111311411011231129321212100', b'\xf1\x875Q0959655AM\xf1\x890318\xf1\x82\x1311111111111112311411011531159321212100', b'\xf1\x875Q0959655AR\xf1\x890315\xf1\x82\x1311110011131115311211012331239321212100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1311110011131100311111011731179321342100', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', - b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221', + b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--171115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111211--261117141112231291163221', - b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112110004110411111421149114', - b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114', + b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\x111212001112110004110411111421149114', + b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\x111212001112111104110411111521159114', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561G01A13A0', - b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566G0HA14A1', + b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\x0566G0HA14A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566G0HA14A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0JA13A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521G0G809A1', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00303A0', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00803A0', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521G0G809A1', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0503G00303A0', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0503G00803A0', b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0503G0G803A0', - b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\00516G00804A1', + b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516G00804A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516G00804A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521G00807A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521G00807A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907567M \xf1\x890398\xf1\x82\x0101', @@ -838,10 +837,10 @@ FW_VERSIONS = { b'\xf1\x870D9300014S \xf1\x895202', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0161212001211001305121211052900', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1212001211001305121211052900', ], (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\00571N60511A1', + b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571N60511A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572M \xf1\x890233', @@ -871,17 +870,17 @@ FW_VERSIONS = { b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r12110012120012021612110200', b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1312001313001305171311052900', b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1312001313001305171311052900', - b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\0161312001313001305171311052900', + b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\x0e1312001313001305171311052900', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521N01842A1', - b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521N01342A1', - b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00511N01805A0', + b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521N01342A1', + b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511N01805A0', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N01309A1', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521N05808A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N05808A1', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101', + b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\x0101', b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', b'\xf1\x875Q0907572P \xf1\x890682', @@ -915,7 +914,7 @@ FW_VERSIONS = { b'\xf1\x870CW300020T \xf1\x892204', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\0222221042111042121040404042E2711152H14', + b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\x122221042111042121040404042E2711152H14', b'\xf1\x872Q0959655BJ\xf1\x890412\xf1\x82\x132223042111042121040404042B251215391423', ], (Ecu.eps, 0x712, None): [ @@ -942,7 +941,7 @@ FW_VERSIONS = { (Ecu.srs, 0x715, None): [ b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1312110012120011111100010200--2521210749', b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001101131112012100', - b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\0161213001211001101131122012100', + b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1213001211001101131122012100', b'\xf1\x873Q0959655DE\xf1\x890731\xf1\x82\x0e1213001211001101131121012J00', ], (Ecu.eps, 0x712, None): [ @@ -1037,14 +1036,14 @@ FW_VERSIONS = { b'\xf1\x873Q0959655AP\xf1\x890305\xf1\x82\r11110011110011213331312131', b'\xf1\x873Q0959655AQ\xf1\x890200\xf1\x82\r11120011100010312212113100', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200', - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0163221003221002105755331052100', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e3221003221002105755331052100', b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111101000011110006110411111111119111', ], (Ecu.eps, 0x712, None): [ - b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\00566A01513A1', - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\00521T00403A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01513A1', + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521T00403A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521T00403A1', b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521T00601A1', @@ -1052,7 +1051,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', - b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\00101', + b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', b'\xf1\x875Q0907572J \xf1\x890654', b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', b'\xf1\x875Q0907572P \xf1\x890682', @@ -1070,7 +1069,7 @@ FW_VERSIONS = { ], (Ecu.srs, 0x715, None): [ b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14', - b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14', + b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\x12111104111104112104040404111111112H14', b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1311150411110411210404040417151215391413', ], (Ecu.eps, 0x712, None): [ @@ -1118,11 +1117,11 @@ FW_VERSIONS = { ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111', - b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111', - b'\xf1\x875Q0959655AK\xf1\x890130\xf1\x82\022111200111121001121110012211292221111', + b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121118112231292221111', + b'\xf1\x875Q0959655AK\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111', b'\xf1\x875Q0959655AS\xf1\x890317\xf1\x82\x1331310031313100313131823133319331313100', b'\xf1\x875Q0959655AT\xf1\x890317\xf1\x82\x1331310031313100313131013131319331313100', - b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100', + b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1331310031313100313131013141319331413100', b'\xf1\x875Q0959655BK\xf1\x890336\xf1\x82\x1331310031313100313131013141319331413100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1333310031313100313152015351539331423100', b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151013141319331423100', @@ -1136,7 +1135,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ070303', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ070505', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ060505', - b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\00563UZ060700', + b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060700', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060600', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070500', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070600', diff --git a/selfdrive/debug/auto_fingerprint.py b/selfdrive/debug/auto_fingerprint.py index 689d3573aa..1870dcba80 100755 --- a/selfdrive/debug/auto_fingerprint.py +++ b/selfdrive/debug/auto_fingerprint.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 import argparse -from openpilot.common.basedir import BASEDIR +from collections import defaultdict +from typing import Optional +from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versions from openpilot.tools.lib.logreader import MultiLogIterator from openpilot.tools.lib.route import Route @@ -16,31 +18,6 @@ PLATFORM_TO_PYTHON_CAR_NAME = {brand: {car.value: car.name for car in ALL_CARS[b BRAND_TO_PLATFORMS = {brand: [car.value for car in ALL_CARS[brand]] for brand in ALL_CARS} PLATFORM_TO_BRAND = dict(sum([[(platform, brand) for platform in BRAND_TO_PLATFORMS[brand]] for brand in BRAND_TO_PLATFORMS], [])) -def add_fw_versions(brand, platform, new_fw_versions): - filename = f"{BASEDIR}/selfdrive/car/{brand}/fingerprints.py" - with open(filename, "r") as f: - values_py = f.read() - - for key in new_fw_versions.keys(): - ecu, addr, subAddr = key - fw_version = new_fw_versions[key] - - platform_start = values_py.index(f"CAR.{PLATFORM_TO_PYTHON_CAR_NAME[brand][platform]}: {{") - - start = values_py.index(f"(Ecu.{ecu}, {hex(addr)}, {subAddr}): [", platform_start) - - try: - end_str = "],\n" - end = values_py.index(end_str, start) - except ValueError: - end_str = "]\n" - end = values_py.index(end_str, start) - - values_py = values_py[:end] + f" {fw_version},\n " + values_py[end:] - - with open(filename, "w") as f: - f.write(values_py) - if __name__ == "__main__": parser = argparse.ArgumentParser(description="Auto fingerprint from a route") @@ -55,6 +32,8 @@ if __name__ == "__main__": carVin = None carPlatform = None + platform: Optional[str] = None + for msg in lr: if msg.which() == "carParams": carFw = msg.carParams.carFw @@ -75,31 +54,26 @@ if __name__ == "__main__": print("Using platform from route") platform = carPlatform else: - raise Exception("unable to determine platform, try manually specifying the fingerprint.") + platform = None else: platform = list(possible_platforms)[0] - else: platform = args.platform + if platform is None: + raise Exception("unable to determine platform, try manually specifying the fingerprint.") + print("Attempting to add fw version for: ", platform) + fw_versions: dict[str, dict[tuple, list[bytes]]] = defaultdict(lambda: defaultdict(list)) brand = PLATFORM_TO_BRAND[platform] - new_fw_versions = {} - for fw in carFw: - if fw.brand == brand: + if fw.brand == brand and not fw.logging: addr = fw.address subAddr = None if fw.subAddress == 0 else fw.subAddress key = (fw.ecu.raw, addr, subAddr) - if key in ALL_FW_VERSIONS[brand][platform]: - fw_versions = set(ALL_FW_VERSIONS[brand][platform][key]) - if fw.fwVersion not in fw_versions: - new_fw_versions[(fw.ecu, addr, subAddr)] = fw.fwVersion + fw_versions[platform][key].append(fw.fwVersion) - if not new_fw_versions: - print("No new fw versions found...") - - add_fw_versions(brand, platform, new_fw_versions) + format_brand_fw_versions(brand, fw_versions) diff --git a/selfdrive/debug/format_fingerprints.py b/selfdrive/debug/format_fingerprints.py new file mode 100755 index 0000000000..7c8819cc47 --- /dev/null +++ b/selfdrive/debug/format_fingerprints.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +import jinja2 +import os + +from cereal import car +from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.car.interfaces import get_interface_attr + +Ecu = car.CarParams.Ecu + +CARS = get_interface_attr('CAR') +FW_VERSIONS = get_interface_attr('FW_VERSIONS') +FINGERPRINTS = get_interface_attr('FINGERPRINTS') +ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} + +FINGERPRINTS_PY_TEMPLATE = jinja2.Template(""" +{%- if FINGERPRINTS[brand] %} +# ruff: noqa: E501 +{% endif %} +{% if FW_VERSIONS[brand] %} +from cereal import car +{% endif %} +from openpilot.selfdrive.car.{{brand}}.values import CAR +{% if FW_VERSIONS[brand] %} + +Ecu = car.CarParams.Ecu +{% endif %} +{% if comments +%} +{{ comments | join() }} +{% endif %} +{% if FINGERPRINTS[brand] %} + +FINGERPRINTS = { +{% for car, fingerprints in FINGERPRINTS[brand].items() %} + CAR.{{car.name}}: [{ +{% for fingerprint in fingerprints %} +{% if not loop.first %} + {{ "{" }} +{% endif %} + {% for key, value in fingerprint.items() %}{{key}}: {{value}}{% if not loop.last %}, {% endif %}{% endfor %} + + }{% if loop.last %}]{% endif %}, +{% endfor %} +{% endfor %} +} +{% endif %} +{% if FW_VERSIONS[brand] %} + +FW_VERSIONS = { +{% for car, _ in FW_VERSIONS[brand].items() %} + CAR.{{car.name}}: { +{% for key, fw_versions in FW_VERSIONS[brand][car].items() %} + (Ecu.{{ECU_NAME[key[0]]}}, 0x{{"%0x" | format(key[1] | int)}}, \ +{% if key[2] %}0x{{"%0x" | format(key[2] | int)}}{% else %}{{key[2]}}{% endif %}): [ + {% for fw_version in fw_versions %} + {{fw_version}}, + {% endfor %} + ], +{% endfor %} + }, +{% endfor %} +} +{% endif %} +""", trim_blocks=True) + + +def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tuple, list[bytes]]] = None): + fw_versions = FW_VERSIONS.copy() + # TODO: this modifies FW_VERSIONS in place + if extra_fw_versions is not None: + for platform, ecus in extra_fw_versions.items(): + for ecu, fws in ecus.items(): + fw_versions[brand][platform][ecu] += set(fws) - set(fw_versions[brand][platform][ecu]) + + fingerprints_file = os.path.join(BASEDIR, f"selfdrive/car/{brand}/fingerprints.py") + with open(fingerprints_file, "r") as f: + comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line] + + with open(fingerprints_file, "w") as f: + f.write(FINGERPRINTS_PY_TEMPLATE.render(brand=brand, comments=comments, ECU_NAME=ECU_NAME, + FINGERPRINTS=FINGERPRINTS, FW_VERSIONS=fw_versions)) + + +if __name__ == "__main__": + for brand in FW_VERSIONS.keys(): + format_brand_fw_versions(brand) From 05dc0b51e6597fb83aed14f5a390fa4254751c98 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 20 Dec 2023 11:29:19 -0800 Subject: [PATCH 311/724] test_models: fallback to public route when route isn't uploaded to CI bucket (#30794) * wip * add public comment * fix that * wip * retrigger --- selfdrive/car/tests/test_models.py | 38 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index e9c2a4ecd5..7306962fa6 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -37,6 +37,7 @@ INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "50")) +CI = os.environ.get("CI", None) is not None def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: # build list of test cases @@ -67,13 +68,33 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: class TestCarModelBase(unittest.TestCase): car_model: Optional[str] = None test_route: Optional[CarTestRoute] = None - ci: bool = True + test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket can_msgs: List[capnp.lib.capnp._DynamicStructReader] fingerprint: dict[int, dict[int, int]] elm_frame: Optional[int] car_safety_mode_frame: Optional[int] + @classmethod + def get_logreader(cls, seg): + if len(INTERNAL_SEG_LIST): + route_name = RouteName(cls.test_route.route) + return LogReader(f"cd:/{route_name.dongle_id}/{route_name.time_str}/{seg}/rlog.bz2") + else: + # Attempt to use CI bucket first + try: + return LogReader(get_url(cls.test_route.route, seg)) + except Exception: + cls.test_route_on_bucket = False + + # Fallback to public route, which will fail the test_route_on_ci_bucket when running in CI + try: + return LogReader(Route(cls.test_route.route).log_paths()[seg]) + except Exception: + pass + + raise Exception("Unable to get route. Check that the route is valid, and either public or uploaded to the CI bucket.") + @classmethod def setUpClass(cls): if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'): @@ -95,13 +116,7 @@ class TestCarModelBase(unittest.TestCase): for seg in test_segs: try: - if len(INTERNAL_SEG_LIST): - route_name = RouteName(cls.test_route.route) - lr = LogReader(f"cd:/{route_name.dongle_id}/{route_name.time_str}/{seg}/rlog.bz2") - elif cls.ci: - lr = LogReader(get_url(cls.test_route.route, seg)) - else: - lr = LogReader(Route(cls.test_route.route).log_paths()[seg]) + lr = cls.get_logreader(seg) except Exception: continue @@ -145,7 +160,7 @@ class TestCarModelBase(unittest.TestCase): if len(can_msgs) > int(50 / DT_CTRL): break else: - raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded?") + raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded and public?") # if relay is expected to be open in the route cls.openpilot_enabled = cls.car_safety_mode_frame is not None @@ -451,6 +466,11 @@ class TestCarModelBase(unittest.TestCase): failed_checks = {k: v for k, v in checks.items() if v > 0} self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") + @pytest.mark.skipif(not CI, reason="When running in CI we want to make sure all the routes are uploaded to the preserved CI bucket.") + def test_route_on_ci_bucket(self): + assert self.test_route_on_bucket, "Route not on CI bucket. \ + This is fine to fail for WIP car ports, just let us know and we can upload your routes to the CI bucket." + @parameterized_class(('car_model', 'test_route'), get_test_cases()) @pytest.mark.xdist_group_class_property('test_route') From 3d8c78d6b3a74196b09576f97ac02b4baa67dfe0 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 20 Dec 2023 11:54:31 -0800 Subject: [PATCH 312/724] format_fingerprints: automatically sort fw fingerprints (#30819) * Auto sort fingerprints * add comment --- selfdrive/car/body/fingerprints.py | 2 +- selfdrive/car/chrysler/fingerprints.py | 2 +- selfdrive/car/ford/fingerprints.py | 8 +- selfdrive/car/honda/fingerprints.py | 92 ++--- selfdrive/car/hyundai/fingerprints.py | 468 +++++++++++------------ selfdrive/car/mazda/fingerprints.py | 28 +- selfdrive/car/nissan/fingerprints.py | 12 +- selfdrive/car/subaru/fingerprints.py | 370 +++++++++--------- selfdrive/car/toyota/fingerprints.py | 274 ++++++------- selfdrive/car/volkswagen/fingerprints.py | 118 +++--- selfdrive/debug/format_fingerprints.py | 6 + 11 files changed, 693 insertions(+), 687 deletions(-) diff --git a/selfdrive/car/body/fingerprints.py b/selfdrive/car/body/fingerprints.py index c671c1dde2..6efaabc137 100644 --- a/selfdrive/car/body/fingerprints.py +++ b/selfdrive/car/body/fingerprints.py @@ -17,8 +17,8 @@ FW_VERSIONS = { CAR.BODY: { (Ecu.engine, 0x720, None): [ b'0.0.01', - b'02/27/2022', b'0.3.00a', + b'02/27/2022', ], (Ecu.debug, 0x721, None): [ b'166bd860', diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index cb68c3911e..4a1299d763 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -169,8 +169,8 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'05036065AE ', b'05036066AE ', - b'05149592AE ', b'05149591AD ', + b'05149592AE ', b'05149846AA ', b'05149848AA ', b'68378701AI ', diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py index b3150d8277..8594ab95df 100644 --- a/selfdrive/car/ford/fingerprints.py +++ b/selfdrive/car/ford/fingerprints.py @@ -21,9 +21,9 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'M1PA-14C204-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'M1PA-14C204-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.ESCAPE_MK4: { @@ -72,9 +72,9 @@ FW_VERSIONS = { b'L1MC-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -87,6 +87,7 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'LB5A-14C204-ATJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LB5A-14C204-ATS\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-AUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-AZL\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-BUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -96,7 +97,6 @@ FW_VERSIONS = { b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PB5A-14C204-DA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-ATS\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.F_150_MK14: { @@ -145,8 +145,8 @@ FW_VERSIONS = { b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MJ98-14C204-BBS\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.FOCUS_MK4: { diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index fe9e9273b7..1667b46188 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -27,7 +27,6 @@ FW_VERSIONS = { b'37805-6A0-A930\x00\x00', b'37805-6A0-AF30\x00\x00', b'37805-6A0-AG30\x00\x00', - b'37805-6B2-C520\x00\x00', b'37805-6A0-C540\x00\x00', b'37805-6A1-H650\x00\x00', b'37805-6B2-A550\x00\x00', @@ -38,6 +37,7 @@ FW_VERSIONS = { b'37805-6B2-A810\x00\x00', b'37805-6B2-A820\x00\x00', b'37805-6B2-A920\x00\x00', + b'37805-6B2-C520\x00\x00', b'37805-6B2-M520\x00\x00', b'37805-6B2-Y810\x00\x00', b'37805-6M4-B730\x00\x00', @@ -66,11 +66,11 @@ FW_VERSIONS = { b'28102-6B8-R700\x00\x00', ], (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ + b'46114-TVA-A050\x00\x00', b'46114-TVA-A060\x00\x00', b'46114-TVA-A080\x00\x00', b'46114-TVA-A120\x00\x00', b'46114-TVA-A320\x00\x00', - b'46114-TVA-A050\x00\x00', b'46114-TVE-H550\x00\x00', b'46114-TVE-H560\x00\x00', ], @@ -88,13 +88,13 @@ FW_VERSIONS = { ], (Ecu.eps, 0x18da30f1, None): [ b'39990-TBX-H120\x00\x00', + b'39990-TVA,A150\x00\x00', b'39990-TVA-A140\x00\x00', b'39990-TVA-A150\x00\x00', b'39990-TVA-A160\x00\x00', b'39990-TVA-A340\x00\x00', b'39990-TVA-X030\x00\x00', b'39990-TVA-X040\x00\x00', - b'39990-TVA,A150\x00\x00', b'39990-TVE-H130\x00\x00', ], (Ecu.unknown, 0x18da3af1, None): [ @@ -199,9 +199,9 @@ FW_VERSIONS = { b'78109-TWA-A210\x00\x00', b'78109-TWA-A220\x00\x00', b'78109-TWA-A230\x00\x00', + b'78109-TWA-H210\x00\x00', b'78109-TWA-L010\x00\x00', b'78109-TWA-L210\x00\x00', - b'78109-TWA-H210\x00\x00', ], (Ecu.shiftByWire, 0x18da0bf1, None): [ b'54008-TWA-A910\x00\x00', @@ -222,8 +222,8 @@ FW_VERSIONS = { b'36802-TWB-H060\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ - b'39990-TVA-A160\x00\x00', b'39990-TVA-A150\x00\x00', + b'39990-TVA-A160\x00\x00', b'39990-TVA-A340\x00\x00', b'39990-TWB-H120\x00\x00', ], @@ -489,16 +489,16 @@ FW_VERSIONS = { b'36161-TGG-A080\x00\x00', b'36161-TGG-A120\x00\x00', b'36161-TGG-G050\x00\x00', + b'36161-TGG-G070\x00\x00', b'36161-TGG-G130\x00\x00', b'36161-TGG-G140\x00\x00', b'36161-TGK-Q120\x00\x00', b'36161-TGL-G050\x00\x00', b'36161-TGL-G070\x00\x00', - b'36161-TGG-G070\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TBA-A110\x00\x00', b'38897-TBA-A020\x00\x00', + b'38897-TBA-A110\x00\x00', ], (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ b'39494-TGL-G030\x00\x00', @@ -564,7 +564,6 @@ FW_VERSIONS = { }, CAR.CRV_5G: { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-5PA-AH20\x00\x00', b'37805-5PA-3060\x00\x00', b'37805-5PA-3080\x00\x00', b'37805-5PA-3180\x00\x00', @@ -590,6 +589,7 @@ FW_VERSIONS = { b'37805-5PA-AB10\x00\x00', b'37805-5PA-AD10\x00\x00', b'37805-5PA-AF20\x00\x00', + b'37805-5PA-AH20\x00\x00', b'37805-5PA-C680\x00\x00', b'37805-5PD-Q630\x00\x00', b'37805-5PF-F730\x00\x00', @@ -619,10 +619,10 @@ FW_VERSIONS = { b'57114-TMC-Z050\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ + b'39990-TLA,A040\x00\x00', b'39990-TLA-A040\x00\x00', b'39990-TLA-A110\x00\x00', b'39990-TLA-A220\x00\x00', - b'39990-TLA,A040\x00\x00', b'39990-TME-T030\x00\x00', b'39990-TME-T120\x00\x00', b'39990-TMT-T010\x00\x00', @@ -705,8 +705,8 @@ FW_VERSIONS = { b'28103-5LH-E100\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-T1V-G020\x00\x00', b'78109-T1B-3050\x00\x00', + b'78109-T1V-G020\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-T1G-G940\x00\x00', @@ -714,14 +714,14 @@ FW_VERSIONS = { }, CAR.CRV_HYBRID: { (Ecu.vsa, 0x18da28f1, None): [ + b'57114-TMB-H030\x00\x00', b'57114-TPA-G020\x00\x00', b'57114-TPG-A020\x00\x00', - b'57114-TMB-H030\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ + b'39990-TMA-H020\x00\x00', b'39990-TPA-G030\x00\x00', b'39990-TPG-A020\x00\x00', - b'39990-TMA-H020\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TMA-H110\x00\x00', @@ -748,9 +748,9 @@ FW_VERSIONS = { b'78209-TLA-X010\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36802-TMB-H040\x00\x00', b'36802-TPA-E040\x00\x00', b'36802-TPG-A020\x00\x00', - b'36802-TMB-H040\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TLA-C320\x00\x00', @@ -931,8 +931,8 @@ FW_VERSIONS = { b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A100\x00\x00', b'28101-5EZ-A210\x00\x00', - b'28101-5EZ-A600\x00\x00', b'28101-5EZ-A430\x00\x00', + b'28101-5EZ-A600\x00\x00', b'28101-5EZ-A700\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ @@ -940,16 +940,16 @@ FW_VERSIONS = { b'37805-RLV-4070\x00\x00', b'37805-RLV-A830\x00\x00', b'37805-RLV-A840\x00\x00', + b'37805-RLV-B210\x00\x00', + b'37805-RLV-B220\x00\x00', + b'37805-RLV-B420\x00\x00', b'37805-RLV-C430\x00\x00', b'37805-RLV-C510\x00\x00', b'37805-RLV-C520\x00\x00', b'37805-RLV-C530\x00\x00', b'37805-RLV-C910\x00\x00', - b'37805-RLV-B220\x00\x00', - b'37805-RLV-B210\x00\x00', - b'37805-RLV-L160\x00\x00', - b'37805-RLV-B420\x00\x00', b'37805-RLV-F120\x00\x00', + b'37805-RLV-L160\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TG7-A030\x00\x00', @@ -980,11 +980,11 @@ FW_VERSIONS = { b'36161-TG8-A630\x00\x00', b'36161-TG8-A720\x00\x00', b'36161-TG8-A830\x00\x00', + b'36161-TGS-A030\x00\x00', b'36161-TGS-A130\x00\x00', + b'36161-TGS-A220\x00\x00', b'36161-TGT-A030\x00\x00', b'36161-TGT-A130\x00\x00', - b'36161-TGS-A030\x00\x00', - b'36161-TGS-A220\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TG7-A020\x00\x00', @@ -1019,14 +1019,14 @@ FW_VERSIONS = { b'78109-TG8-AJ10\x00\x00', b'78109-TG8-AJ20\x00\x00', b'78109-TG8-AK20\x00\x00', + b'78109-TGS-AC10\x00\x00', + b'78109-TGS-AJ20\x00\x00', b'78109-TGS-AK20\x00\x00', b'78109-TGS-AP20\x00\x00', - b'78109-TGT-AJ20\x00\x00', - b'78109-TGT-AK30\x00\x00', b'78109-TGS-AT20\x00\x00', b'78109-TGS-AX20\x00\x00', - b'78109-TGS-AJ20\x00\x00', - b'78109-TGS-AC10\x00\x00', + b'78109-TGT-AJ20\x00\x00', + b'78109-TGT-AK30\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TG7-A130\x00\x00', @@ -1045,22 +1045,22 @@ FW_VERSIONS = { }, CAR.ACURA_RDX: { (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TX5-A220\x00\x00', b'57114-TX4-A220\x00\x00', + b'57114-TX5-A220\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-TX5-A030\x00\x00', b'36161-TX4-A030\x00\x00', + b'36161-TX5-A030\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ - b'77959-TX4-C010\x00\x00', b'77959-TX4-B010\x00\x00', + b'77959-TX4-C010\x00\x00', b'77959-TX4-C020\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TX5-A310\x00\x00', b'78109-TX4-A210\x00\x00', b'78109-TX4-A310\x00\x00', + b'78109-TX5-A310\x00\x00', ], }, CAR.ACURA_RDX_3G: { @@ -1217,9 +1217,9 @@ FW_VERSIONS = { (Ecu.combinationMeter, 0x18da60f1, None): [ b'78109-TXM-A010\x00\x00', b'78109-TXM-A020\x00\x00', + b'78109-TXM-A030\x00\x00', b'78109-TXM-A110\x00\x00', b'78109-TXM-C010\x00\x00', - b'78109-TXM-A030\x00\x00', ], }, CAR.HRV: { @@ -1231,21 +1231,21 @@ FW_VERSIONS = { b'39990-THX-A020\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T7A-A040\x00\x00', b'36161-T7A-A140\x00\x00', b'36161-T7A-A240\x00\x00', b'36161-T7A-C440\x00\x00', - b'36161-T7A-A040\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-T7A-A230\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-THW-A110\x00\x00', b'78109-THX-A110\x00\x00', b'78109-THX-A120\x00\x00', b'78109-THX-A210\x00\x00', b'78109-THX-A220\x00\x00', b'78109-THX-C220\x00\x00', - b'78109-THW-A110\x00\x00', ], }, CAR.HRV_3G: { @@ -1320,45 +1320,45 @@ FW_VERSIONS = { }, CAR.CIVIC_2022: { (Ecu.eps, 0x18da30f1, None): [ + b'39990-T24-T120\x00\x00', b'39990-T39-A130\x00\x00', b'39990-T43-J020\x00\x00', - b'39990-T24-T120\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-T20-A020\x00\x00', - b'38897-T20-A510\x00\x00', - b'38897-T21-A010\x00\x00', b'38897-T20-A210\x00\x00', b'38897-T20-A310\x00\x00', + b'38897-T20-A510\x00\x00', + b'38897-T21-A010\x00\x00', b'38897-T24-Z120\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-T20-A970\x00\x00', + b'77959-T20-A980\x00\x00', + b'77959-T20-M820\x00\x00', b'77959-T47-A940\x00\x00', b'77959-T47-A950\x00\x00', - b'77959-T20-M820\x00\x00', - b'77959-T20-A980\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ b'78108-T21-A220\x00\x00', - b'78108-T21-A620\x00\x00', - b'78108-T23-A110\x00\x00', b'78108-T21-A230\x00\x00', - b'78108-T22-A020\x00\x00', - b'78108-T21-MB10\x00\x00', + b'78108-T21-A620\x00\x00', b'78108-T21-A740\x00\x00', + b'78108-T21-MB10\x00\x00', + b'78108-T22-A020\x00\x00', + b'78108-T23-A110\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T20-A060\x00\x00', b'36161-T20-A070\x00\x00', b'36161-T20-A080\x00\x00', - b'36161-T20-A060\x00\x00', - b'36161-T47-A070\x00\x00', b'36161-T24-T070\x00\x00', + b'36161-T47-A070\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-T20-AB40\x00\x00', - b'57114-T43-JB30\x00\x00', b'57114-T24-TB30\x00\x00', + b'57114-T43-JB30\x00\x00', ], (Ecu.transmission, 0x18da1ef1, None): [ b'28101-65D-A020\x00\x00', @@ -1368,12 +1368,12 @@ FW_VERSIONS = { b'28101-65J-N010\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-64L-A540\x00\x00', - b'37805-64S-A540\x00\x00', - b'37805-64S-A720\x00\x00', b'37805-64A-A540\x00\x00', b'37805-64A-A620\x00\x00', b'37805-64D-P510\x00\x00', + b'37805-64L-A540\x00\x00', + b'37805-64S-A540\x00\x00', + b'37805-64S-A720\x00\x00', b'37805-64S-AA10\x00\x00', ], }, diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 59f71560d7..fa6b56507c 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -99,8 +99,8 @@ FW_VERSIONS = { }, CAR.AZERA_HEV_6TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029', b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.00 99211-G8000 180903', + b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00IG MDPS C 1.00 1.00 56310M9600\x00 4IHSC100', @@ -111,12 +111,12 @@ FW_VERSIONS = { b'\xf1\x00IGhe SCC FHCUP 1.00 1.01 99110-M9000 ', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006T7N0_C2\x00\x006T7VA051\x00\x00TIGSH24KA1\xc7\x85\xe2`', b'\xf1\x006T7N0_C2\x00\x006T7Q2051\x00\x00TIG2H24KA2\x12@\x11\xb7', + b'\xf1\x006T7N0_C2\x00\x006T7VA051\x00\x00TIGSH24KA1\xc7\x85\xe2`', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H590051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H570051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H590051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.HYUNDAI_GENESIS: { @@ -157,58 +157,58 @@ FW_VERSIONS = { b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\xdbD\r\x81', b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\x00\x00\x00\x00', + b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PAE0G16NS1\xdbD\r\x81', ], }, CAR.IONIQ_PHEV: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2200 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.02 99110-G2100 ', b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', + b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', ], (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2310 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', - b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2310 4APHC101', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEP MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEP MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027', - b'\xf1\x00AEP MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x82zT\xd2', - b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', - b'\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', + b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x82zT\xd2', + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', ], }, CAR.IONIQ_EV_2020: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7000 ', b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7200 ', + b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7000 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7310 4APEC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7560 4APEC101', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2600 190730', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.03 95740-G2500 190516', b'\xf1\x00AEE MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2600 190730', ], }, CAR.IONIQ_EV_LTD: { @@ -218,16 +218,16 @@ FW_VERSIONS = { ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.02 56310G7300\x00 4AEEC102', - b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7501 4AEEC104', b'\xf1\x00AE MDPS C 1.00 1.03 56310/G7300 4AEEC103', b'\xf1\x00AE MDPS C 1.00 1.03 56310G7300\x00 4AEEC103', b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7301 4AEEC104', + b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7501 4AEEC104', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G7200 160418', - b'\xf1\x00AEE MFC AT USA LHD 1.00 1.00 95740-G2400 180222', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2300 170703', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G7200 160418', + b'\xf1\x00AEE MFC AT USA LHD 1.00 1.00 95740-G2400 180222', ], }, CAR.IONIQ_HEV_2022: { @@ -245,8 +245,8 @@ FW_VERSIONS = { b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00', b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x96\xda\xd4\xee', + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00', ], }, CAR.SONATA: { @@ -259,22 +259,26 @@ FW_VERSIONS = { b'\xf1\x00DN8_ SCC FHCUP 1.00 1.02 99110-L1000 ', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', b'\xf1\x00DN ESC \x01 102\x19\x04\x13 58910-L1300', b'\xf1\x00DN ESC \x03 100 \x08\x01 58910-L0300', b'\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', + b'\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', + b'\xf1\x00DN ESC \x06 107 \x07\x03 58910-L1300', b'\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', - b'\xf1\x00DN ESC \x08 103\x19\x06\x01 58910-L1300', + b'\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', b'\xf1\x00DN ESC \x07 107"\x08\x07 58910-L0100', - b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', + b'\xf1\x00DN ESC \x08 103\x19\x06\x01 58910-L1300', b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', - b'\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', - b'\xf1\x00DN ESC \x06 107 \x07\x03 58910-L1300', + b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', ], (Ecu.engine, 0x7e0, None): [ + b'HM6M1_0a0_F00', + b'HM6M1_0a0_G20', + b'HM6M2_0a0_BD0', b'\xf1\x81HM6M1_0a0_F00', + b'\xf1\x81HM6M1_0a0_G20', b'\xf1\x82DNBVN5GMCCXXXDCA', b'\xf1\x82DNBVN5GMCCXXXG2F', b'\xf1\x82DNBWN5TMDCXXXG2E', @@ -282,37 +286,33 @@ FW_VERSIONS = { b'\xf1\x82DNCVN5GMCCXXXG2B', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A', + b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M00', b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M10', + b'\xf1\x8739110-2S042\xf1\x81HM6M1_0a0_M00', + b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B', b'\xf1\x87391162M003', + b'\xf1\x87391162M010', b'\xf1\x87391162M013', b'\xf1\x87391162M023', - b'\xf1\x87391162M010', - b'HM6M1_0a0_F00', - b'HM6M1_0a0_G20', - b'HM6M2_0a0_BD0', - b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B', - b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M00', - b'\xf1\x8739110-2S042\xf1\x81HM6M1_0a0_M00', - b'\xf1\x81HM6M1_0a0_G20', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', - b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC102', - b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC102', b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0200\x00 4DNAC102', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC102', b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', + b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101', + b'\xf1\x00DN8 MDPS R 1.00 1.02 57700-L1000 4DNDP105', b'\xf1\x8756310-L0010\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0010 4DNAC101', b'\xf1\x8756310-L0210\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC101', b'\xf1\x8756310-L1010\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1010 4DNDC103', b'\xf1\x8756310-L1030\xf1\x00DN8 MDPS C 1.00 1.03 56310-L1030 4DNDC103', + b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', - b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101', - b'\xf1\x00DN8 MDPS R 1.00 1.02 57700-L1000 4DNDP105', - b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC102', - b'\xf1\x00DN8 MDPS C 1.00 1.01 56310L0200\x00 4DNAC102', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', @@ -326,10 +326,6 @@ FW_VERSIONS = { b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.07 99211-L1000 211223', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16KB05\x95h%', b'\xf1\x00HT6TA260BLHT6TA800A1TDN8C20KS4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x00HT6TA260BLHT6TA810A1TDN8M25GS0\x00\x00\x00\x00\x00\x00\xaa\x8c\xd9p', b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -339,7 +335,12 @@ FW_VERSIONS = { b'\xf1\x00HT6WA280BLHT6WAE10A1SDN8G25NB5\x00\x00\x00\x00\x00\x00\xe0t\xa9\xba', b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE', + b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSA\xb9\x13\xf9p', b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSCF\xe4!Y', + b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16KB05\x95h%', + b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', @@ -376,6 +377,7 @@ FW_VERSIONS = { b'\xf1\x87SALFBA6892284GJ233S5\x87w\x87xx\x88\x87\x88vwwgww\x87w\x84?\xfb\xff\x98\x88\x8f\xff*\x9e\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SALFBA7005534GJ2eUuWfg\x86xxww\x87x\x88\x87\x88\x88w\x88\x87\x87O\xfc\xffuUO\xff\xa3k\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', b'\xf1\x87SALFBA7152454GJ2gvwgFf\x86hx\x88\x87\x88vfWfffffd?\xfa\xff\xba\x88o\xff,\xcf\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB1\xe3\xc10\xa1', + b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SALFBA7485034GJ2ww\x87xww\x87xfwvgwwwwvfgf\xa5/\xfc\xff\xa9w_\xff40\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMDBA7743924GJ3wwwwww\x87xgwvw\x88\x88\x88\x88wwww\x85_\xfa\xff\x86f\x7f\xff0\x9d\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', b'\xf1\x87SAMDBA7817334GJ3Vgvwvfvgww\x87wwwwwwfgv\x97O\xfd\xff\x88\x88o\xff\x8e\xeb\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB2\x00\x00\x00\x00\x00\x00', @@ -383,13 +385,11 @@ FW_VERSIONS = { b'\xf1\x87SAMFB41553621GC7ww\x87xUU\x85Xvwwg\x88\x88\x88\x88wwgw\x86\xaf\xfb\xffuDo\xff\xaa\x8f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMFB42555421GC7\x88\x88\x88\x88wvwgx\x88\x87\x88wwgw\x87wxw3\x8f\xfc\xff\x98f\x8f\xffga\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMFBA7978674GJ2gw\x87xgw\x97ywwwwvUGeUUeU\x87O\xfb\xff\x98w\x8f\xfffF\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMFBA9283024GJ2wwwwEUuWwwgwwwwwwwww\x87/\xfb\xff\x98w\x8f\xff<\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMFBA9708354GJ2wwwwVf\x86h\x88wx\x87xww\x87\x88\x88\x88\x88w/\xfa\xff\x97w\x8f\xff\x86\xa0\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SANDB45316691GC6\x99\x99\x99\x99\x88\x88\xa8\x8avfwfwwww\x87wxwT\x9f\xfd\xff\x88wo\xff\x1c\xfa\xf1\x89HT6WAD10A1\xf1\x82SDN8G25NB3\x00\x00\x00\x00\x00\x00', - b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SANFB45889451GC7wx\x87\x88gw\x87x\x88\x88x\x88\x87wxw\x87wxw\x87\x8f\xfc\xffeU\x8f\xff+Q\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', - b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSA\xb9\x13\xf9p', ], }, CAR.SONATA_LF: { @@ -397,8 +397,8 @@ FW_VERSIONS = { b'\xf1\x00LF__ SCC F-CUP 1.00 1.00 96401-C2200 ', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00LF ESC \x0c 11 \x17\x01\x13 58920-C2610', b'\xf1\x00LF ESC \t 11 \x17\x01\x13 58920-C2610', + b'\xf1\x00LF ESC \x0c 11 \x17\x01\x13 58920-C2610', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81606D5051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -411,12 +411,12 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\x00\x00\x00\x00', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', - b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24SL2n\x8d\xbe\xd8', b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2\x00\x00\x00\x00', b'\xf1\x87LAHSGN012918KF10\x98\x88x\x87\x88\x88x\x87\x88\x88\x98\x88\x87w\x88w\x88\x88\x98\x886o\xf6\xff\x98w\x7f\xff3\x00\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm', b'\xf1\x87LAJSG49645724HF0\x87x\x87\x88\x87www\x88\x99\xa8\x89\x88\x99\xa8\x89\x88\x99\xa8\x89S_\xfb\xff\x87f\x7f\xff^2\xf1\x816W3B1051\x00\x00\xf1\x006W351_C2\x00\x006W3B1051\x00\x00TLF0T20NL2H\r\xbdm', + b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\x00\x00\x00\x00', + b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24NL1\xb0\x9f\xee\xf5', + b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B4051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B4051\x00\x00TLF0G24SL2n\x8d\xbe\xd8', ], }, CAR.TUCSON: { @@ -425,16 +425,16 @@ FW_VERSIONS = { b'\xf1\x00TL__ FCA F-CUP 1.00 1.02 99110-D3510 ', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8971TLC2NAIDDIR002\xf1\x8271TLC2NAIDDIR002', b'\xf1\x81606G3051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x8971TLC2NAIDDIR002\xf1\x8271TLC2NAIDDIR002', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00TL MFC AT KOR LHD 1.00 1.02 95895-D3800 180719', b'\xf1\x00TL MFC AT USA LHD 1.00 1.06 95895-D3800 190107', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87LBJXAN202299KF22\x87x\x87\x88ww\x87xx\x88\x97\x88\x87\x88\x98x\x88\x99\x98\x89\x87o\xf6\xff\x87w\x7f\xff\x12\x9a\xf1\x81U083\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U083\x00\x00\x00\x00\x00\x00TTL2V20KL1\x8fRn\x8a', b'\xf1\x87KMLDCU585233TJ20wx\x87\x88x\x88\x98\x89vfwfwwww\x87f\x9f\xff\x98\xff\x7f\xf9\xf7s\xf1\x816T6G4051\x00\x00\xf1\x006T6J0_C2\x00\x006T6G4051\x00\x00TTL4G24NH2\x00\x00\x00\x00', + b'\xf1\x87LBJXAN202299KF22\x87x\x87\x88ww\x87xx\x88\x97\x88\x87\x88\x98x\x88\x99\x98\x89\x87o\xf6\xff\x87w\x7f\xff\x12\x9a\xf1\x81U083\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U083\x00\x00\x00\x00\x00\x00TTL2V20KL1\x8fRn\x8a', ], }, CAR.SANTA_FE: { @@ -445,16 +445,16 @@ FW_VERSIONS = { b'\xf1\x00TM__ SCC F-CUP 1.00 1.03 99110-S2000 ', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650', - b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500', - b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650', - b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650', b'\xf1\x00TM ESC \x02 100\x18\x030 58910-S2600', b'\xf1\x00TM ESC \x02 102\x18\x07\x01 58910-S2600', b'\xf1\x00TM ESC \x02 103\x18\x11\x07 58910-S2600', b'\xf1\x00TM ESC \x02 104\x19\x07\x07 58910-S2600', b'\xf1\x00TM ESC \x03 103\x18\x11\x07 58910-S2600', b'\xf1\x00TM ESC \x0c 103\x18\x11\x08 58910-S2650', + b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650', + b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650', + b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650', + b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81606EA051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -472,8 +472,8 @@ FW_VERSIONS = { b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1', b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', + b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1', b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91', b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00', b'\xf1\x87LDJUEA6010814HG1\x87w\x87x\x86gvw\x88\x88\x98\x88gw\x86wx\x88\x97\x88\x85o\xf8\xff\x86f_\xff\xd37\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', @@ -508,58 +508,58 @@ FW_VERSIONS = { (Ecu.abs, 0x7d1, None): [ b'\xf1\x00TM ESC \x01 102!\x04\x03 58910-S2DA0', b'\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0', + b'\xf1\x00TM ESC \x02 103"\x07\x08 58910-S2GA0', b'\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0', + b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0', + b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', + b'\xf1\x00TM ESC 103!\x030 58910-S1MA0', + b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', b'\xf1\x8758910-S2DA0\xf1\x00TM ESC \x03 101 \x08\x02 58910-S2DA0', b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x02 101 \x08\x04 58910-S2GA0', - b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', - b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', - b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0', - b'\xf1\x00TM ESC \x02 103"\x07\x08 58910-S2GA0', - b'\xf1\x00TM ESC 103!\x030 58910-S1MA0', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_L50', - b'\xf1\x81HM6M1_0a0_H00', - b'\xf1\x82TACVN5GMI3XXXH0A', - b'\xf1\x82TMBZN5TMD3XXXG2E', - b'\xf1\x82TACVN5GSI3XXXH0A', - b'\xf1\x82TMCFD5MMCXXXXG0A', b'\xf1\x81HM6M1_0a0_G20', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', + b'\xf1\x81HM6M1_0a0_H00', b'\xf1\x81HM6M2_0a0_G00', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', - b'\xf1\x8739101-2STN8\xf1\x81HM6M1_0a0_M00', + b'\xf1\x82TACVN5GMI3XXXH0A', + b'\xf1\x82TACVN5GSI3XXXH0A', + b'\xf1\x82TMBZN5TMD3XXXG2E', + b'\xf1\x82TMCFD5MMCXXXXG0A', b'\xf1\x87 \xf1\x81 ', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_L50', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', + b'\xf1\x8739101-2STN8\xf1\x81HM6M1_0a0_M00', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00TM MDPS C 1.00 1.02 56370-S2AA0 0B19', b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1AB0 4TSDC101', b'\xf1\x00TM MDPS C 1.00 1.01 56310-S1EB0 4TSDC101', + b'\xf1\x00TM MDPS C 1.00 1.02 56370-S2AA0 0B19', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TM MFC AT EUR LHD 1.00 1.03 99211-S1500 210224', b'\xf1\x00TM MFC AT MES LHD 1.00 1.05 99211-S1500 220126', b'\xf1\x00TMA MFC AT MEX LHD 1.00 1.01 99211-S2500 210205', b'\xf1\x00TMA MFC AT USA LHD 1.00 1.00 99211-S2500 200720', - b'\xf1\x00TM MFC AT EUR LHD 1.00 1.03 99211-S1500 210224', b'\xf1\x00TMA MFC AT USA LHD 1.00 1.01 99211-S2500 210205', b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', b'\xf1\x00HT6WA280BLHT6WAD00A1STM2G25NH2\x00\x00\x00\x00\x00\x00\xf8\xc0\xc3\xaa', b'\xf1\x00HT6WA280BLHT6WAD00A1STM4G25NH1\x00\x00\x00\x00\x00\x00\x9cl\x04\xbc', - b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NSA\xf3\xf4Uj', - b'\xf1\x87SDMXCA9087684GN1VfvgUUeVwwgwwwwwffffU?\xfb\xff\x97\x88\x7f\xff+\xa4\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', b'\xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', - b'\xf1\x87SDMXCA8653204GN1EVugEUuWwwwwww\x87wwwwwv/\xfb\xff\xa8\x88\x9f\xff\xa5\x9c\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', - b'\xf1\x87KMMYBU034207SB72x\x89\x88\x98h\x88\x98\x89\x87fhvvfWf33_\xff\x87\xff\x8f\xfa\x81\xe5\xf1\x89HT6TAF00A1\xf1\x82STM0M25GS1\x00\x00\x00\x00\x00\x00', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6', - b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', - b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', - b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', b'\xf1\x00T02601BL T02800A1 VTMPT25XXX800NS4\xed\xaf\xed\xf5', b'\xf1\x00T02601BL T02900A1 VTMPT25XXW900NS1c\x918\xc5', + b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', + b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NSA\xf3\xf4Uj', + b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6', + b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', + b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', + b'\xf1\x87KMMYBU034207SB72x\x89\x88\x98h\x88\x98\x89\x87fhvvfWf33_\xff\x87\xff\x8f\xfa\x81\xe5\xf1\x89HT6TAF00A1\xf1\x82STM0M25GS1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SDMXCA8653204GN1EVugEUuWwwwwww\x87wwwwwv/\xfb\xff\xa8\x88\x9f\xff\xa5\x9c\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', + b'\xf1\x87SDMXCA9087684GN1VfvgUUeVwwgwwwwwffffU?\xfb\xff\x97\x88\x7f\xff+\xa4\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', ], }, CAR.SANTA_FE_HEV_2022: { @@ -569,14 +569,14 @@ FW_VERSIONS = { (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLEC0 4TSHC102', - b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105', b'\xf1\x00TM MDPS C 1.00 1.02 56310-GA000 4TSHA100', + b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', b'\xf1\x00TMH MFC AT EUR LHD 1.00 1.06 99211-S1500 220727', b'\xf1\x00TMH MFC AT USA LHD 1.00 1.03 99211-S1500 210224', b'\xf1\x00TMH MFC AT USA LHD 1.00 1.06 99211-S1500 220727', - b'\xf1\x00TMA MFC AT USA LHD 1.00 1.03 99211-S2500 220414', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16SA3\xa3\x1b\xe14', @@ -591,8 +591,8 @@ FW_VERSIONS = { }, CAR.SANTA_FE_PHEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', b'\xf1\x00TMhe SCC FHCUP 1.00 1.01 99110-CL500 ', + b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', @@ -604,9 +604,9 @@ FW_VERSIONS = { b'\xf1\x00TMP MFC AT USA LHD 1.00 1.06 99211-S1500 220727', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe', b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', - b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA1\x0b\xc5\x0f\xea', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x87391312MTF0', @@ -635,45 +635,45 @@ FW_VERSIONS = { }, CAR.KIA_STINGER: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ', - b'\xf1\x00CK__ SCC F_CUP 1.00 1.03 96400-J5100 ', b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5000 ', + b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ', b'\xf1\x00CK__ SCC F_CUP 1.00 1.02 96400-J5100 ', + b'\xf1\x00CK__ SCC F_CUP 1.00 1.03 96400-J5100 ', ], (Ecu.engine, 0x7e0, None): [ + b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00', b'\xf1\x81606DE051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x81640E0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00', b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00', - b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00', - b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104', b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5220 4C2VL104', b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5420 4C4VL104', + b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5220 4C2VL106', b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5420 4C4VL106', b'\xf1\x00CK MDPS R 1.00 1.07 57700-J5220 4C2VL107', - b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5220 4C2VL106', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CK MFC AT EUR LHD 1.00 1.03 95740-J5000 170822', b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 95740-J5000 170822', b'\xf1\x00CK MFC AT USA LHD 1.00 1.04 95740-J5000 180504', - b'\xf1\x00CK MFC AT EUR LHD 1.00 1.03 95740-J5000 170822', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SCK0T33NB2\xb3\xee\xba\xdc', b'\xf1\x87VCJLE17622572DK0vd6D\x99\x98y\x97vwVffUfvfC%CuT&Dx\x87o\xff{\x1c\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', + b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x89E21\x00\x00\x00\x00\x00\x00\x00\xf1\x82SCK0T33NB0', b'\xf1\x87VDHLG17034412DK2vD6DfVvVTD$D\x99w\x88\x98EDEDeT6DgfO\xff\xc3=\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', b'\xf1\x87VDHLG17118862DK2\x8awWwgu\x96wVfUVwv\x97xWvfvUTGTx\x87o\xff\xc9\xed\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', + b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S', b'\xf1\x87VDKLJ18675252DK6\x89vhgwwwwveVU\x88w\x87w\x99vgf\x97vXfgw_\xff\xc2\xfb\xf1\x89E25\x00\x00\x00\x00\x00\x00\x00\xf1\x82TCK0T33NB2', b'\xf1\x87WAJTE17552812CH4vfFffvfVeT5DwvvVVdFeegeg\x88\x88o\xff\x1a]\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00TCK2T20NB1\x19\xd2\x00\x94', - b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S', - b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', ], }, CAR.KIA_STINGER_2022: { @@ -684,14 +684,14 @@ FW_VERSIONS = { b'\xf1\x00CK__ SCC FHCUP 1.00 1.01 99110-J5100 ', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x81640N2051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x81HM6M1_0a0_H00', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5300 4C2CL503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5320 4C2VL503', + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504', ], (Ecu.fwdCamera, 0x7c4, None): [ @@ -701,37 +701,37 @@ FW_VERSIONS = { b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 99211-J5000 201209', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', - b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1', b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T25KH2B\xfbI\xe2', + b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1', b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00TCK0T33NH0%g~\xd3', + b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', ], }, CAR.PALISADE: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ', - b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.00 99110-S8110 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ', - b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ', b'\xf1\x00ON__ FCA FHCUP 1.00 1.01 99110-S9110 ', + b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360', b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360', b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330', b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330', - b'\xf1\x00LX ESC \x0b 103\x19\t\t 58910-S8350', b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330', + b'\xf1\x00LX ESC \x0b 103\x19\t\t 58910-S8350', b'\xf1\x00LX ESC \x0b 103\x19\t\x10 58910-S8360', b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360', - b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360', - b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', - b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', b'\xf1\x00ON ESC \x01 101\x19\t\x08 58910-S9360', + b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360', + b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', + b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -747,13 +747,13 @@ FW_VERSIONS = { b'\xf1\x00ON MDPS C 1.00 1.01 56340-S9000 9201', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.03 99211-S8100 190125', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.05 99211-S8100 190909', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 200422', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 200903', b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 181105', b'\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 200720', - b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226', b'\xf1\x00ON MFC AT USA LHD 1.00 1.04 99211-S9100 211227', ], (Ecu.transmission, 0x7e1, None): [ @@ -762,6 +762,7 @@ FW_VERSIONS = { b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', b'\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00TON2G38NB5j\x94.\xde', b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1', + b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8", b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', b'\xf1\x87LBLUFN655162KF36\x98\x88\x88\x88\x98\x88\x88\x88x\x99\xa7\x89x\x99\xa7\x89x\x99\x97\x89g\x7f\xf7\xffwU_\xff\xe9!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', b'\xf1\x87LBLUFN731381KF36\xb9\x99\x89\x98\x98\x88\x88\x88\x89\x99\xa8\x99\x88\x99\xa8\x89\x88\x88\x98\x88V\x7f\xf6\xff\x99w\x8f\xff\xad\xd8\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', @@ -771,6 +772,7 @@ FW_VERSIONS = { b'\xf1\x87LDKVBN424201KF26\xba\xaa\x9a\xa9\x99\x99\x89\x98\x89\x99\xa8\x99\x88\x99\x98\x89\x88\x99\xa8\x89v\x7f\xf7\xffwf_\xffq\xa6\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', b'\xf1\x87LDKVBN540766KF37\x87wgv\x87w\x87xx\x99\x97\x89v\x88\x97h\x88\x88\x88\x88x\x7f\xf6\xffvUo\xff\xd3\x01\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB2\xafL]\xe7', b'\xf1\x87LDLVAA4225634HH1\x98\x88\x88\x88eUeVx\x88\x87\x88g\x88\x86xx\x88\x87\x88\x86o\xf9\xff\x87w\x7f\xff\xf2\xf7\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x87LDLVAA4478824HH1\x87wwwvfvg\x89\x99\xa8\x99w\x88\x87x\x89\x99\xa8\x99\xa6o\xfa\xfffU/\xffu\x92\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', b'\xf1\x87LDLVAA4777834HH1\x98\x88x\x87\x87wwwx\x88\x87\x88x\x99\x97\x89x\x88\x97\x88\x86o\xfa\xff\x86fO\xff\x1d9\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', b'\xf1\x87LDLVAA5194534HH1ffvguUUUx\x88\xa7\x88h\x99\x96\x89x\x88\x97\x88ro\xf9\xff\x98wo\xff\xaaM\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', b'\xf1\x87LDLVAA5949924HH1\xa9\x99y\x97\x87wwwx\x99\x97\x89x\x99\xa7\x89x\x99\xa7\x89\x87_\xfa\xffeD?\xff\xf1\xfd\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', @@ -797,6 +799,7 @@ FW_VERSIONS = { b'\xf1\x87LDMVBN830601KF37\xa7www\xa8\x87xwx\x99\xa7\x89Uw\x85Ww\x88\x97x\x88o\xf6\xff\x8a\xaa\x7f\xff\xe2:\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB4\xd6\xe8\xd7\xa6', b'\xf1\x87LDMVBN848789KF37\x87w\x87x\x87w\x87xy\x99\xb7\x99\x87\x88\x98x\x88\x99\xa8\x89\x87\x7f\xf6\xfffUo\xff\xe3!\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN851595KF37\x97wgvvfffx\x99\xb7\x89\x88\x99\x98\x89\x87\x88\x98x\x99\x7f\xf7\xff\x97w\x7f\xff@\xf3\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', + b'\xf1\x87LDMVBN871852KF37\xb9\x99\x99\x99\xa8\x88\x88\x88y\x99\xa7\x99x\x99\xa7\x89\x88\x88\x98\x88\x89o\xf7\xff\xaa\x88o\xff\x0e\xed\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN873175KF26\xa8\x88\x88\x88vfVex\x99\xb7\x89\x88\x99\x98\x89x\x88\x97\x88f\x7f\xf7\xff\xbb\xaa\x8f\xff,\x04\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN879401KF26veVU\xa8\x88\x88\x88g\x88\xa6xVw\x95gx\x88\xa7\x88v\x8f\xf9\xff\xdd\xbb\xbf\xff\xb3\x99\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN881314KF37\xa8\x88h\x86\x97www\x89\x99\xa8\x99w\x88\x97xx\x99\xa7\x89\xca\x7f\xf8\xff\xba\x99\x8f\xff\xd8v\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', @@ -804,10 +807,7 @@ FW_VERSIONS = { b'\xf1\x87LDMVBN889419KF37\xa9\x99y\x97\x87w\x87xx\x88\x97\x88w\x88\x97x\x88\x99\x98\x89e\x9f\xf9\xffeUo\xff\x901\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN895969KF37vefV\x87vgfx\x99\xa7\x89\x99\x99\xb9\x99f\x88\x96he_\xf7\xffxwo\xff\x14\xf9\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', b'\xf1\x87LDMVBN899222KF37\xa8\x88x\x87\x97www\x98\x99\x99\x89\x88\x99\x98\x89f\x88\x96hdo\xf7\xff\xbb\xaa\x9f\xff\xe2U\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b"\xf1\x87LBLUFN622950KF36\xa8\x88\x88\x88\x87w\x87xh\x99\x96\x89\x88\x99\x98\x89\x88\x99\x98\x89\x87o\xf6\xff\x98\x88o\xffx'\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8", b'\xf1\x87LDMVBN950669KF37\x97www\x96fffy\x99\xa7\x99\xa9\x99\xaa\x99g\x88\x96x\xb8\x8f\xf9\xffTD/\xff\xa7\xcb\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', - b'\xf1\x87LDLVAA4478824HH1\x87wwwvfvg\x89\x99\xa8\x99w\x88\x87x\x89\x99\xa8\x99\xa6o\xfa\xfffU/\xffu\x92\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', - b'\xf1\x87LDMVBN871852KF37\xb9\x99\x99\x99\xa8\x88\x88\x88y\x99\xa7\x99x\x99\xa7\x89\x88\x88\x98\x88\x89o\xf7\xff\xaa\x88o\xff\x0e\xed\xf1\x81U922\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00SLX4G38NB5\xb9\x94\xe8\x89', ], }, CAR.VELOSTER: { @@ -820,27 +820,27 @@ FW_VERSIONS = { b'\xf1\x816V8RAC00121.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\x01TJS-JNU06F200H0A', b'\x01TJS-JDK06F200H0A', + b'\x01TJS-JNU06F200H0A', b'391282BJF5 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00JSL MDPS C 1.00 1.03 56340-J3000 8308', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JS LKAS AT USA LHD 1.00 1.02 95740-J3000 K32', b'\xf1\x00JS LKAS AT KOR LHD 1.00 1.03 95740-J3000 K33', + b'\xf1\x00JS LKAS AT USA LHD 1.00 1.02 95740-J3000 K32', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\xba\x02\xb8\x80', - b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\x00\x00\x00\x00', b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16KS2\x0e\xba\x1e\xa2', + b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\x00\x00\x00\x00', + b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJS0T16NS1\xba\x02\xb8\x80', ], }, CAR.GENESIS_G70: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', b'\xf1\x00IK__ SCC F-CUP 1.00 1.01 96400-G9100 ', + b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -860,15 +860,15 @@ FW_VERSIONS = { (Ecu.eps, 0x7d4, None): [ b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9220 4I2VL107', b'\xf1\x00IK MDPS R 1.00 1.07 57700-G9420 4I4VL107', - b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9420 4I4VL108', b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9200 4I2CL108', + b'\xf1\x00IK MDPS R 1.00 1.08 57700-G9420 4I4VL108', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87VCJLP18407832DN3\x88vXfvUVT\x97eFU\x87d7v\x88eVeveFU\x89\x98\x7f\xff\xb2\xb0\xf1\x81E25\x00\x00\x00', b'\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', - b'\xf1\x87VDKLT18912362DN4wfVfwefeveVUwfvw\x88vWfvUFU\x89\xa9\x8f\xff\x87w\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', - b'\xf1\x87VDJLC18480772DK9\x88eHfwfff\x87eFUeDEU\x98eFe\x86T5DVyo\xff\x87s\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33KB5\x9f\xa5&\x81', b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T20KB3Wuvz', + b'\xf1\x87VCJLP18407832DN3\x88vXfvUVT\x97eFU\x87d7v\x88eVeveFU\x89\x98\x7f\xff\xb2\xb0\xf1\x81E25\x00\x00\x00', + b'\xf1\x87VDJLC18480772DK9\x88eHfwfff\x87eFUeDEU\x98eFe\x86T5DVyo\xff\x87s\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33KB5\x9f\xa5&\x81', + b'\xf1\x87VDKLT18912362DN4wfVfwefeveVUwfvw\x88vWfvUFU\x89\xa9\x8f\xff\x87w\xf1\x81E25\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SIK0T33NB4\xecE\xefL', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00IK__ SCC F-CUP 1.00 1.02 96400-G9100 ', @@ -876,13 +876,13 @@ FW_VERSIONS = { b'\xf1\x00IK__ SCC FHCUP 1.00 1.02 96400-G9000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', b'\xf1\x00IK MFC AT KOR LHD 1.00 1.01 95740-G9000 170920', + b'\xf1\x00IK MFC AT USA LHD 1.00 1.01 95740-G9000 170920', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x81606G2051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.GENESIS_G80: { @@ -891,21 +891,21 @@ FW_VERSIONS = { b'\xf1\x00DH__ SCC FHCUP 1.00 1.01 96400-B1110 ', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713', - b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810', - b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014', b'\xf1\x00DH LKAS AT KOR LHD 1.01 1.02 95895-B1500 170810', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G33KH2\xae\xde\xd5!', b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c', b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8', - b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G33KH2\xae\xde\xd5!', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n', + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x81640A4051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.GENESIS_G90: { @@ -976,8 +976,8 @@ FW_VERSIONS = { b'\xf1\x00BDP LKAS AT USA LHD 1.00 1.05 99211-M6500 744', ], (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00BD__ SCC H-CUP 1.00 1.02 99110-M6000 ', b'\xf1\x00BDPE_SCC FHCUPC 1.00 1.04 99110-M6500\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x00BD__ SCC H-CUP 1.00 1.02 99110-M6000 ', ], (Ecu.engine, 0x7e0, None): [ b'\x01TBDM1NU06F200H01', @@ -996,53 +996,53 @@ FW_VERSIONS = { }, CAR.KIA_K5_2021: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', + b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', + b'\xf1\x00DL3_ SCC FHCUP 1.00 1.04 99110-L2100 ', b'\xf1\x8799110L2000\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', - b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', - b'\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', - b'\xf1\x00DL3_ SCC FHCUP 1.00 1.04 99110-L2100 ', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x8756310-L3110\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', - b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', - b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L2220 4DLDC102', b'\xf1\x00DL3 MDPS C 1.00 1.02 56310L3220\x00 4DLAC102', + b'\xf1\x8756310-L3110\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', + b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', + b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DL3 MFC AT KOR LHD 1.00 1.04 99210-L2000 210527', b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915', b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.04 99210-L3000 210208', - b'\xf1\x00DL3 MFC AT KOR LHD 1.00 1.04 99210-L2000 210527', b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.05 99210-L3000 211222', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00DL ESC \x06 101 \x04\x02 58910-L3200', - b'\xf1\x8758910-L3200\xf1\x00DL ESC \x06 101 \x04\x02 58910-L3200', - b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', - b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', - b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', b'\xf1\x00DL ESC \x01 104 \x07\x12 58910-L2200', + b'\xf1\x00DL ESC \x06 101 \x04\x02 58910-L3200', b'\xf1\x00DL ESC \x06 103"\x08\x06 58910-L3200', + b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', + b'\xf1\x8758910-L3200\xf1\x00DL ESC \x06 101 \x04\x02 58910-L3200', + b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', + b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x87391212MKT0', - b'\xf1\x87391212MKV0', - b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DLDWN5TMDCXXXJ1B', b'\xf1\x81HM6M2_0a0_DQ0', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DLDWN5TMDCXXXJ1B', + b'\xf1\x87391212MKT0', b'\xf1\x87391212MKT3', + b'\xf1\x87391212MKV0', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00HT6TA261BLHT6TAB00A1SDL0C20KS0\x00\x00\x00\x00\x00\x00\\\x9f\xa5\x15', b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB2.\x13\xf6\xed', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', + b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', b'\xf1\x87SALFEA5652514GK2UUeV\x88\x87\x88xxwg\x87ww\x87wwfwvd/\xfb\xffvU_\xff\x93\xd3\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', - b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', - b'\xf1\x00HT6TA261BLHT6TAB00A1SDL0C20KS0\x00\x00\x00\x00\x00\x00\\\x9f\xa5\x15', - b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB2.\x13\xf6\xed', ], }, CAR.KIA_K5_HEV_2020: { @@ -1067,11 +1067,11 @@ FW_VERSIONS = { }, CAR.KONA_EV: { (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00OS IEB \r 105\x18\t\x18 58520-K4000', b'\xf1\x00OS IEB \x01 212 \x11\x13 58520-K4000', b'\xf1\x00OS IEB \x02 212 \x11\x13 58520-K4000', b'\xf1\x00OS IEB \x03 210 \x02\x14 58520-K4000', b'\xf1\x00OS IEB \x03 212 \x11\x13 58520-K4000', + b'\xf1\x00OS IEB \r 105\x18\t\x18 58520-K4000', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00OE2 LKAS AT EUR LHD 1.00 1.00 95740-K4200 200', @@ -1094,28 +1094,28 @@ FW_VERSIONS = { }, CAR.KONA_EV_2022: { (Ecu.abs, 0x7d1, None): [ - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', - b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', - b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010', b'\xf1\x00OS IEB \x02 102"\x05\x16 58520-K4010', b'\xf1\x00OS IEB \x03 102"\x05\x16 58520-K4010', + b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010', + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', + b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', - b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT EUR LHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.04 99211-J9200 904', b'\xf1\x00OSP LKA AT USA LHD 1.00 1.04 99211-J9200 904', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', - b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', - b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310-K4271 4OEPC102', - b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4971\x00 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4261\x00 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4971\x00 4OEPC102', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', @@ -1140,22 +1140,22 @@ FW_VERSIONS = { b'\xf1\x8799110Q4100\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4100 ', b'\xf1\x8799110Q4500\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4500 ', b'\xf1\x8799110Q4600\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4600 ', - b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', b'\xf1\x8799110Q4600\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ', + b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', ], (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104', b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105', b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105', - b'\xf1\x00DE MDPS C 1.00 1.04 56310Q4100\x00 4DEEC104', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706', b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4000 191211', - b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211', - b'\xf1\x00DEE MFC AT USA LHD 1.00 1.03 95740-Q4000 180821', - b'\xf1\x00DEE MFC AT USA LHD 1.00 1.01 99211-Q4500 210428', + b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706', b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.03 95740-Q4000 180821', b'\xf1\x00DEE MFC AT KOR LHD 1.00 1.03 95740-Q4000 180821', + b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211', + b'\xf1\x00DEE MFC AT USA LHD 1.00 1.01 99211-Q4500 210428', + b'\xf1\x00DEE MFC AT USA LHD 1.00 1.03 95740-Q4000 180821', ], }, CAR.KIA_NIRO_EV_2ND_GEN: { @@ -1169,29 +1169,29 @@ FW_VERSIONS = { }, CAR.KIA_NIRO_PHEV: { (Ecu.engine, 0x7e0, None): [ - b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ - b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91", - b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01', b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00', b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92', - b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01', + b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00', + b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91", ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', + b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117', + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826', ], (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ', + b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', ], }, CAR.KIA_NIRO_PHEV_2022: { @@ -1223,8 +1223,8 @@ FW_VERSIONS = { b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00DEH MFC AT USA LHD 1.00 1.07 99211-G5000 201221', b'\xf1\x00DEH MFC AT USA LHD 1.00 1.00 99211-G5500 210428', + b'\xf1\x00DEH MFC AT USA LHD 1.00 1.07 99211-G5000 201221', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DEhe SCC FHCUP 1.00 1.00 99110-G5600 ', @@ -1239,10 +1239,10 @@ FW_VERSIONS = { b'\xf1\x8758910-Q5450\xf1\x00SP ESC \t 101\x19\t\x05 58910-Q5450', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x81616D2051\x00\x00\x00\x00\x00\x00\x00\x00', - b'\xf1\x81616D5051\x00\x00\x00\x00\x00\x00\x00\x00', b'\x01TSP2KNL06F100J0K', b'\x01TSP2KNL06F200J0K', + b'\xf1\x81616D2051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x81616D5051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00SP2 MDPS C 1.00 1.04 56300Q5200 ', @@ -1253,9 +1253,9 @@ FW_VERSIONS = { b'\xf1\x00SP2 MFC AT USA LHD 1.00 1.05 99210-Q5000 201012', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x87CZLUB49370612JF7h\xa8y\x87\x99\xa7hv\x99\x97fv\x88\x87x\x89x\x96O\xff\x88\xff\xff\xff.@\xf1\x816V2C2051\x00\x00\xf1\x006V2B0_C2\x00\x006V2C2051\x00\x00CSP4N20NS3\x00\x00\x00\x00', b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\x00T0190XBL T01950A1 DSP2T16X4X950NS6\xd30\xa5\xb9', b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\x00T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b', + b'\xf1\x87CZLUB49370612JF7h\xa8y\x87\x99\xa7hv\x99\x97fv\x88\x87x\x89x\x96O\xff\x88\xff\xff\xff.@\xf1\x816V2C2051\x00\x00\xf1\x006V2B0_C2\x00\x006V2C2051\x00\x00CSP4N20NS3\x00\x00\x00\x00', ], }, CAR.KIA_OPTIMA_G4: { @@ -1277,8 +1277,8 @@ FW_VERSIONS = { b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 ', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00JF ESC \x0b 11 \x18\x030 58920-D5180', b"\xf1\x00JF ESC \t 11 \x18\x03' 58920-D5260", + b'\xf1\x00JF ESC \x0b 11 \x18\x030 58920-D5180', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32', @@ -1286,13 +1286,13 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', + b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', b'\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01', - b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', + b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01', - b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B8051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B8051\x00\x00TJFSG24NH27\xa7\xc2\xb4', ], }, @@ -1317,21 +1317,21 @@ FW_VERSIONS = { }, CAR.ELANTRA: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00ADP LKAS AT USA LHD 1.00 1.03 99211-F2000 X31', b'\xf1\x00AD LKAS AT USA LHD 1.01 1.01 95895-F2000 251', + b'\xf1\x00ADP LKAS AT USA LHD 1.00 1.03 99211-F2000 X31', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD(\xfcA\x9d', - b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD\x00\x00\x00\x00', b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\x00\x00\x00\x00', b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20NS2\xc5\x92\x9e\x8a', b'\xf1\x006T6J0_C2\x00\x006T6F0051\x00\x00TAD0N20SS2.~\x90\x87', + b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD\x00\x00\x00\x00', + b'\xf1\x006T6K0_C2\x00\x006T6S2051\x00\x00TAD0N20NSD(\xfcA\x9d', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x8161698051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x8161657051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816165D051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816165E051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x8161698051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00AD ESC \x11 11 \x18\x05\x06 58910-F2840', @@ -1344,20 +1344,20 @@ FW_VERSIONS = { }, CAR.ELANTRA_GT_I30: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51', + b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e', b'\xf1\x006U2U0_C2\x00\x006U2T0051\x00\x00DPD0D16KS0u\xce\x1fk', + b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104', b'\xf1\x00PD MDPS C 1.00 1.00 56310G3300\x00 4PDDC100', + b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104', ], (Ecu.abs, 0x7d1, None): [ - b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350', b'\xf1\x00PD ESC \t 104\x18\t\x03 58920-G3350', + b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ', @@ -1369,19 +1369,19 @@ FW_VERSIONS = { b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', b'\xf1\x00CN7_ SCC FNCUP 1.00 1.01 99110-AA000 ', - b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', + b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', b'\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', + b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', - b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205', - b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AB000 220426', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', @@ -1392,24 +1392,24 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\xe8\xba\xce\xfa', - b'\xf1\x87CXMQFM2135005JB2E\xb9\x89\x98W\xa9y\x97h\xa9\x98\x99wxvwh\x87\x7f\xffx\xff\xff\xff,,\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', - b'\xf1\x87CXMQFM1916035JB2\x88vvgg\x87Wuwgev\xa9\x98\x88\x98h\x99\x9f\xffh\xff\xff\xff\xa5\xee\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', b'\xf1\x87CXLQF40189012JL2f\x88\x86\x88\x88vUex\xb8\x88\x88\x88\x87\x88\x89fh?\xffz\xff\xff\xff\x08z\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', + b'\xf1\x87CXMQFM1916035JB2\x88vvgg\x87Wuwgev\xa9\x98\x88\x98h\x99\x9f\xffh\xff\xff\xff\xa5\xee\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', + b'\xf1\x87CXMQFM2135005JB2E\xb9\x89\x98W\xa9y\x97h\xa9\x98\x99wxvwh\x87\x7f\xffx\xff\xff\xff,,\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', b'\xf1\x87CXMQFM2728305JB2E\x97\x87xw\x87vwgw\x84x\x88\x88w\x89EI\xbf\xff{\xff\xff\xff\xe6\x0e\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', b'\xf1\x87CXMQFM3806705JB2\x89\x87wwx\x88g\x86\x99\x87\x86xwwv\x88yv\x7f\xffz\xff\xff\xffV\x15\xf1\x89HT6VA640A1\xf1\x82CCN0N20NS5\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\xf1\x82CNCWD0AMFCXCSFFA', b'\xf1\x81HM6M2_0a0_FF0', b'\xf1\x82CNCVD0AMFCXCSFFB', + b'\xf1\x82CNCWD0AMFCXCSFFA', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_HC0', ], }, CAR.ELANTRA_HEV_2021: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', + b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.08 99210-AA000 220728', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.09 99210-AA000 221108', @@ -1420,16 +1420,16 @@ FW_VERSIONS = { ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103', - b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', - b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', b'\xf1\x00CN7 MDPS C 1.00 1.04 56310BY050\x00 4CNHC104', + b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', + b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\xb9?A\xaa', b'\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', - b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\xb9?A\xaa', - b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', + b'\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\xb9?A\xaa', b'\xf1\x006U3L0_C2\x00\x006U3K9051\x00\x00HCN0G16NS1\x00\x00\x00\x00', + b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', + b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\xb9?A\xaa', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1459,16 +1459,16 @@ FW_VERSIONS = { }, CAR.SONATA_HYBRID: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', - b'\xf1\x8799110L5000\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', b'\xf1\x00DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', + b'\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', b'\xf1\x8799110L5000\xf1\x00DNhe SCC F-CUP 1.00 1.02 99110-L5000 ', + b'\xf1\x8799110L5000\xf1\x00DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', + b'\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5450 4DNHC102', b'\xf1\x8756310-L5450\xf1\x00DN8 MDPS C 1.00 1.03 56310-L5450 4DNHC103', - b'\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', + b'\xf1\x8756310-L5500\xf1\x00DN8 MDPS C 1.00 1.02 56310-L5500 4DNHC102', b'\xf1\x8756310L5450\x00\xf1\x00DN8 MDPS C 1.00 1.03 56310L5450\x00 4DNHC104', ], (Ecu.fwdCamera, 0x7c4, None): [ @@ -1478,18 +1478,18 @@ FW_VERSIONS = { b'\xf1\x00DN8HMFC AT USA LHD 1.00 1.07 99211-L1000 211223', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', - b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E09\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', b'\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', + b'\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', - b'\xf1\x87PCU\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', + b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E09\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2323 E09\x00\x00\x00\x00\x00\x00\x00TDN2H20SA5\x97R\x88\x9e', b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TDN2H20SA6N\xc2\xeeW', + b'\xf1\x87PCU\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TDN2H20SA7\x1a3\xf9\xab', ], (Ecu.engine, 0x7e0, None): [ + b'\xf1\x87391062J002', b'\xf1\x87391162J012', b'\xf1\x87391162J013', b'\xf1\x87391162J014', - b'\xf1\x87391062J002', ], }, CAR.KIA_SORENTO: { @@ -1514,8 +1514,8 @@ FW_VERSIONS = { b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406', + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', ], }, CAR.KIA_EV6: { @@ -1523,15 +1523,15 @@ FW_VERSIONS = { b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', - b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328', b'\xf1\x00CV1 MFC AT EUR RHD 1.00 1.00 99210-CV100 220630', - b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630', b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.04 99210-CV000 210823', b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.06 99210-CV000 220328', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', ], }, CAR.IONIQ_5: { @@ -1539,14 +1539,14 @@ FW_VERSIONS = { b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', - b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.05 99211-GI010 220614', b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007', - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.02 99211-GI010 211206', + b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.06 99211-GI010 230110', ], }, @@ -1571,17 +1571,17 @@ FW_VERSIONS = { }, CAR.TUCSON_HYBRID_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9250 14W', b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9250 14W', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9260 14Y', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', - b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', b'\xf1\x00NX4__ 1.00 1.01 99110-N9000 ', + b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', ], }, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: { @@ -1605,9 +1605,9 @@ FW_VERSIONS = { }, CAR.KIA_SPORTAGE_5TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NQ5 FR_CMR AT AUS RHD 1.00 1.00 99211-P1040 663', b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662', b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663', - b'\xf1\x00NQ5 FR_CMR AT AUS RHD 1.00 1.00 99211-P1040 663', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ', @@ -1617,20 +1617,20 @@ FW_VERSIONS = { }, CAR.GENESIS_GV70_1ST_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204', b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR200 220125', b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR300 220125', + b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204', ], (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ', b'\xf1\x00JK1_ SCC FHCUP 1.00 1.00 99110-AR200 ', b'\xf1\x00JK1_ SCC FHCUP 1.00 1.00 99110-AR300 ', + b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ', ], }, CAR.GENESIS_GV60_EV_1ST_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215', b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU000 211215', + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215', b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.03 99211-CU000 221118', ], (Ecu.fwdRadar, 0x7d0, None): [ @@ -1639,13 +1639,13 @@ FW_VERSIONS = { }, CAR.KIA_SORENTO_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.05 99210-R5000 210623', - b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.03 99210-R5000 200903', b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.00 99210-R5100 221019', + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.03 99210-R5000 200903', + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.05 99210-R5000 210623', ], (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', b'\xf1\x00MQ4_ SCC F-CUP 1.00 1.06 99110-P2000 ', + b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.08 99110-P2000 ', ], }, @@ -1668,27 +1668,27 @@ FW_VERSIONS = { }, CAR.KIA_CARNIVAL_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', + b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221', b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.06 99210-R0000 220221', b'\xf1\x00KA4CMFC AT CHN LHD 1.00 1.01 99211-I4000 210525', - b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.00 99210-R0100 230105', - b'\xf1\x00KA4 MFC AT KOR LHD 1.00 1.06 99210-R0000 220221', - b'\xf1\x00KA4 MFC AT USA LHD 1.00 1.05 99210-R0000 201221', ], (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00KA4_ SCC FHCUP 1.00 1.00 99110-R0100 ', b'\xf1\x00KA4_ SCC FHCUP 1.00 1.03 99110-R0000 ', b'\xf1\x00KA4c SCC FHCUP 1.00 1.01 99110-I4000 ', - b'\xf1\x00KA4_ SCC FHCUP 1.00 1.00 99110-R0100 ', ], }, CAR.KIA_SORENTO_HEV_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.04 99210-P2000 200330', b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331', b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', - b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.04 99210-P2000 200330', ], (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', b'\xf1\x00MQhe SCC FHCUP 1.00 1.04 99110-P4000 ', + b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', ], }, CAR.KIA_K8_HEV_1ST_GEN: { diff --git a/selfdrive/car/mazda/fingerprints.py b/selfdrive/car/mazda/fingerprints.py index ee1f4be38b..aa91f0fb31 100644 --- a/selfdrive/car/mazda/fingerprints.py +++ b/selfdrive/car/mazda/fingerprints.py @@ -9,13 +9,13 @@ FW_VERSIONS = { b'KSD5-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ + b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX85-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXFG-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PEW5-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -30,13 +30,13 @@ FW_VERSIONS = { b'GSH7-67XK2-U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ - b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYB2-21PS1-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PG69-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXDL-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXFG-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXFG-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PG69-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'SH51-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.CX5: { @@ -50,10 +50,6 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'PA53-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PAR4-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFA-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2E-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2F-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -64,6 +60,10 @@ FW_VERSIONS = { b'PX38-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX42-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX68-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFA-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'SHKT-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ @@ -150,12 +150,12 @@ FW_VERSIONS = { b'PXM4-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM7-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFM-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PYFM-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYD5-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYD5-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYD6-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYD6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFM-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYFM-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.MAZDA3: { @@ -186,8 +186,8 @@ FW_VERSIONS = { b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ - b'PY2S-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'P52G-21PS1-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PY2S-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKA-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKE-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKE-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -230,11 +230,11 @@ FW_VERSIONS = { b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ + b'PXGW-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXGW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXGW-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/nissan/fingerprints.py b/selfdrive/car/nissan/fingerprints.py index 3059bf5b0c..69e88be898 100644 --- a/selfdrive/car/nissan/fingerprints.py +++ b/selfdrive/car/nissan/fingerprints.py @@ -60,23 +60,23 @@ FW_VERSIONS = { CAR.LEAF_IC: { (Ecu.fwdCamera, 0x707, None): [ b'5SH1BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', - b'5SK0ADB\x04\x18\x00\x00\x00\x00\x00_(5\x07\x9aQ\x00\x00\x00\x80', b'5SH4BDB\x04\x18\x00\x00\x00\x00\x00_-?\x04\x91\xf2\x00\x00\x00\x80', + b'5SK0ADB\x04\x18\x00\x00\x00\x00\x00_(5\x07\x9aQ\x00\x00\x00\x80', ], (Ecu.abs, 0x740, None): [ + b'476605SD2E', b'476605SH1D', b'476605SK2A', - b'476605SD2E', ], (Ecu.eps, 0x742, None): [ b'5SH2A\x99A\x05\x02N123F\x15\x81\x00\x00\x00\x00\x00\x00\x00\x80', - b'5SK3A\x99A\x05\x02N123F\x15u\x00\x00\x00\x00\x00\x00\x00\x80', b'5SH2C\xb7A\x05\x02N123F\x15\xa3\x00\x00\x00\x00\x00\x00\x00\x80', + b'5SK3A\x99A\x05\x02N123F\x15u\x00\x00\x00\x00\x00\x00\x00\x80', ], (Ecu.gateway, 0x18dad0f1, None): [ + b'284U25SF0C', b'284U25SH3A', b'284U25SK2D', - b'284U25SF0C', ], }, CAR.XTRAIL: { @@ -84,8 +84,8 @@ FW_VERSIONS = { b'284N86FR2A', ], (Ecu.abs, 0x740, None): [ - b'6FU1BD\x11\x02\x00\x02e\x95e\x80iX#\x01\x00\x00\x00\x00\x00\x80', b'6FU0AD\x11\x02\x00\x02e\x95e\x80iQ#\x01\x00\x00\x00\x00\x00\x80', + b'6FU1BD\x11\x02\x00\x02e\x95e\x80iX#\x01\x00\x00\x00\x00\x00\x80', ], (Ecu.eps, 0x742, None): [ b'6FP2A\x99A\x05\x02N123F\x18\x02\x00\x00\x00\x00\x00\x00\x00\x80', @@ -94,8 +94,8 @@ FW_VERSIONS = { b'6FR2A\x18B\x05\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', ], (Ecu.engine, 0x7e0, None): [ - b'6FU9B\xa0A\x06\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', b'6FR9A\xa0A\x06\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', + b'6FU9B\xa0A\x06\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80', ], (Ecu.gateway, 0x18dad0f1, None): [ b'284U26FR0E', diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py index 6625d14e5c..73bab9ecac 100644 --- a/selfdrive/car/subaru/fingerprints.py +++ b/selfdrive/car/subaru/fingerprints.py @@ -11,29 +11,29 @@ FW_VERSIONS = { b'\xf1\x82\xa5 \x19\x02\x00', ], (Ecu.eps, 0x746, None): [ - b'\x85\xc0\xd0\x00', b'\x05\xc0\xd0\x00', + b'\x85\xc0\xd0\x00', b'\x95\xc0\xd0\x00', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00d\xb9\x1f@ \x10', - b"\x00\x00e~\x1f@ '", - b'\x00\x00e@\x1f@ $', b'\x00\x00d\xb9\x00\x00\x00\x00', + b'\x00\x00d\xb9\x1f@ \x10', b'\x00\x00e@\x00\x00\x00\x00', + b'\x00\x00e@\x1f@ $', + b"\x00\x00e~\x1f@ '", ], (Ecu.engine, 0x7e0, None): [ b'\xbb,\xa0t\x07', - b'\xf1\x82\xbb,\xa0t\x87', - b'\xf1\x82\xbb,\xa0t\x07', - b'\xf1\x82\xd9,\xa0@\x07', - b'\xf1\x82\xd1,\xa0q\x07', b'\xd1,\xa0q\x07', + b'\xf1\x82\xbb,\xa0t\x07', + b'\xf1\x82\xbb,\xa0t\x87', + b'\xf1\x82\xd1,\xa0q\x07', + b'\xf1\x82\xd9,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\x00\xfe\xf7\x00\x00', - b'\x01\xfe\xf9\x00\x00', b'\x01\xfe\xf7\x00\x00', + b'\x01\xfe\xf9\x00\x00', b'\x01\xfe\xfa\x00\x00', ], }, @@ -56,10 +56,10 @@ FW_VERSIONS = { }, CAR.LEGACY: { (Ecu.abs, 0x7b0, None): [ - b'\xa1\\ x04\x01', - b'\xa1 \x03\x03', b'\xa1 \x02\x01', b'\xa1 \x02\x02', + b'\xa1 \x03\x03', + b'\xa1\\ x04\x01', ], (Ecu.eps, 0x746, None): [ b'\x9b\xc0\x11\x00', @@ -71,150 +71,150 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'\xde"a0\x07', - b'\xe2"aq\x07', b'\xde,\xa0@\x07', + b'\xe2"aq\x07', b'\xe2,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xa5\xf6\x05@\x00', - b'\xa7\xf6\x04@\x00', b'\xa5\xfe\xc7@\x00', + b'\xa7\xf6\x04@\x00', b'\xa7\xfe\xc4@\x00', ], }, CAR.IMPREZA: { (Ecu.abs, 0x7b0, None): [ + b'z\x84\x19\x90\x00', + b'z\x94\x08\x90\x00', + b'z\x94\x08\x90\x01', + b'z\x94\x0c\x90\x00', + b'z\x94\x0c\x90\x01', + b'z\x94.\x90\x00', b'z\x94?\x90\x00', + b'z\x9c\x19\x80\x01', b'\xa2 \x185\x00', b'\xa2 \x193\x00', b'\xa2 \x194\x00', - b'z\x94.\x90\x00', - b'z\x94\x08\x90\x01', b'\xa2 \x19`\x00', - b'z\x94\x0c\x90\x01', - b'z\x9c\x19\x80\x01', - b'z\x94\x08\x90\x00', - b'z\x84\x19\x90\x00', b'\xf1\x00\xb2\x06\x04', - b'z\x94\x0c\x90\x00', ], (Ecu.eps, 0x746, None): [ - b'z\xc0\x0c\x00', - b'z\xc0\x08\x00', - b'\x8a\xc0\x00\x00', - b'z\xc0\x04\x00', b'z\xc0\x00\x00', - b'\x8a\xc0\x10\x00', + b'z\xc0\x04\x00', + b'z\xc0\x08\x00', b'z\xc0\n\x00', + b'z\xc0\x0c\x00', + b'\x8a\xc0\x00\x00', + b'\x8a\xc0\x10\x00', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00d\xb5\x1f@ \x0e', - b'\x00\x00d\xdc\x1f@ \x0e', - b'\x00\x00e\x1c\x1f@ \x14', - b'\x00\x00d)\x1f@ \x07', - b'\x00\x00e+\x1f@ \x14', - b'\x00\x00e+\x00\x00\x00\x00', - b'\x00\x00dd\x1f@ \x0e', - b'\x00\x00e\x02\x1f@ \x14', - b'\x00\x00d)\x00\x00\x00\x00', b'\x00\x00c\xf4\x00\x00\x00\x00', - b'\x00\x00d\xdc\x00\x00\x00\x00', - b'\x00\x00dd\x00\x00\x00\x00', b'\x00\x00c\xf4\x1f@ \x07', + b'\x00\x00d)\x00\x00\x00\x00', + b'\x00\x00d)\x1f@ \x07', + b'\x00\x00dd\x00\x00\x00\x00', + b'\x00\x00dd\x1f@ \x0e', + b'\x00\x00d\xb5\x1f@ \x0e', + b'\x00\x00d\xdc\x00\x00\x00\x00', + b'\x00\x00d\xdc\x1f@ \x0e', + b'\x00\x00e\x02\x1f@ \x14', b'\x00\x00e\x1c\x00\x00\x00\x00', + b'\x00\x00e\x1c\x1f@ \x14', + b'\x00\x00e+\x00\x00\x00\x00', + b'\x00\x00e+\x1f@ \x14', ], (Ecu.engine, 0x7e0, None): [ + b'\xaa\x00Bu\x07', + b'\xaa\x01bt\x07', + b'\xaa!`u\x07', + b'\xaa!au\x07', + b'\xaa!av\x07', + b'\xaa!aw\x07', + b'\xaa!dq\x07', + b'\xaa!ds\x07', + b'\xaa!dt\x07', b'\xaaafs\x07', + b'\xbe!as\x07', + b'\xbe!at\x07', b'\xbeacr\x07', b'\xc5!`r\x07', - b'\xaa!ds\x07', - b'\xaa!`u\x07', - b'\xaa!dq\x07', - b'\xaa!dt\x07', - b'\xc5!ar\x07', - b'\xbe!as\x07', - b'\xc5!as\x07', - b'\xc5!ds\x07', b'\xc5!`s\x07', - b'\xaa!au\x07', - b'\xbe!at\x07', - b'\xaa\x00Bu\x07', - b'\xc5!dr\x07', - b'\xaa!aw\x07', - b'\xaa!av\x07', - b'\xaa\x01bt\x07', b'\xc5!ap\x07', + b'\xc5!ar\x07', + b'\xc5!as\x07', + b'\xc5!dr\x07', + b'\xc5!ds\x07', ], (Ecu.transmission, 0x7e1, None): [ + b'\xe3\xd0\x081\x00', + b'\xe3\xd5\x161\x00', b'\xe3\xe5F1\x00', - b'\xe4\xe5\x061\x00', - b'\xe5\xf5\x04\x00\x00', - b'\xe3\xf5G\x00\x00', + b'\xe3\xf5\x06\x00\x00', b'\xe3\xf5\x07\x00\x00', b'\xe3\xf5C\x00\x00', - b'\xe5\xf5B\x00\x00', - b'\xe5\xf5$\x00\x00', - b'\xe4\xf5\x07\x00\x00', b'\xe3\xf5F\x00\x00', + b'\xe3\xf5G\x00\x00', + b'\xe4\xe5\x061\x00', b'\xe4\xf5\x02\x00\x00', - b'\xe3\xd0\x081\x00', - b'\xe3\xf5\x06\x00\x00', - b'\xe3\xd5\x161\x00', + b'\xe4\xf5\x07\x00\x00', + b'\xe5\xf5\x04\x00\x00', + b'\xe5\xf5$\x00\x00', + b'\xe5\xf5B\x00\x00', ], }, CAR.IMPREZA_2020: { (Ecu.abs, 0x7b0, None): [ - b'\xa2 \x194\x00', b'\xa2 \x193\x00', - b'\xa2 !i\x00', - b'\xa2 !`\x00', - b'\xf1\x00\xb2\x06\x04', + b'\xa2 \x194\x00', b'\xa2 `\x00', b'\xa2 !3\x00', + b'\xa2 !`\x00', + b'\xa2 !i\x00', + b'\xf1\x00\xb2\x06\x04', ], (Ecu.eps, 0x746, None): [ - b'\x9a\xc0\x00\x00', b'\n\xc0\x04\x00', - b'\x9a\xc0\x04\x00', b'\n\xc0\x04\x01', + b'\x9a\xc0\x00\x00', + b'\x9a\xc0\x04\x00', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00eb\x1f@ "', - b'\x00\x00e\x8f\x1f@ )', - b'\x00\x00eq\x1f@ "', b'\x00\x00eq\x00\x00\x00\x00', + b'\x00\x00eq\x1f@ "', b'\x00\x00e\x8f\x00\x00\x00\x00', + b'\x00\x00e\x8f\x1f@ )', b'\x00\x00e\x92\x00\x00\x00\x00', b'\x00\x00e\xa4\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\xca!ap\x07', - b'\xca!`p\x07', b'\xca!`0\x07', - b'\xcc"f0\x07', - b'\xcc!fp\x07', - b'\xcc!`p\x07', + b'\xca!`p\x07', + b'\xca!ap\x07', b'\xca!f@\x07', b'\xca!fp\x07', - b'\xf3"f@\x07', + b'\xcc!`p\x07', + b'\xcc!fp\x07', + b'\xcc"f0\x07', + b'\xe6!`@\x07', b'\xe6!fp\x07', - b'\xf3"fp\x07', b'\xe6"f0\x07', b'\xe6"fp\x07', - b'\xe6!`@\x07', + b'\xf3"f@\x07', + b'\xf3"fp\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xe6\xf5\x04\x00\x00', b'\xe6\xf5$\x00\x00', - b'\xe7\xf5\x04\x00\x00', - b'\xe7\xf6B0\x00', - b'\xe7\xf5D0\x00', - b'\xf1\x00\xd7\x10@', b'\xe6\xf5D0\x00', - b'\xe9\xf6F0\x00', + b'\xe7\xf5\x04\x00\x00', + b'\xe7\xf5D0\x00', + b'\xe7\xf6B0\x00', + b'\xe9\xf5"\x00\x00', b'\xe9\xf5B0\x00', b'\xe9\xf6B0\x00', - b'\xe9\xf5"\x00\x00', + b'\xe9\xf6F0\x00', + b'\xf1\x00\xd7\x10@', ], }, CAR.CROSSTREK_HYBRID: { @@ -223,8 +223,8 @@ FW_VERSIONS = { b'\xa2 !e\x01', ], (Ecu.eps, 0x746, None): [ - b'\x9a\xc2\x01\x00', b'\n\xc2\x01\x00', + b'\x9a\xc2\x01\x00', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00el\x1f@ #', @@ -238,44 +238,44 @@ FW_VERSIONS = { CAR.FORESTER: { (Ecu.abs, 0x7b0, None): [ b'\xa3 \x18\x14\x00', - b'\xa3 \x14\x00', + b'\xa3 \x18&\x00', b'\xa3 \x19\x14\x00', + b'\xa3 \x19&\x00', + b'\xa3 \x14\x00', b'\xa3 \x14\x01', b'\xf1\x00\xbb\r\x05', - b'\xa3 \x18&\x00', - b'\xa3 \x19&\x00', ], (Ecu.eps, 0x746, None): [ - b'\x8d\xc0\x04\x00', b'\x8d\xc0\x00\x00', + b'\x8d\xc0\x04\x00', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00e!\x1f@ \x11', - b'\x00\x00e\x97\x1f@ 0', - b'\x00\x00e`\x1f@ ', - b'\xf1\x00\xac\x02\x00', b'\x00\x00e!\x00\x00\x00\x00', - b'\x00\x00e\x97\x00\x00\x00\x00', - b'\x00\x00e^\x1f@ !', + b'\x00\x00e!\x1f@ \x11', b'\x00\x00e^\x00\x00\x00\x00', + b'\x00\x00e^\x1f@ !', + b'\x00\x00e`\x1f@ ', + b'\x00\x00e\x97\x00\x00\x00\x00', + b'\x00\x00e\x97\x1f@ 0', + b'\xf1\x00\xac\x02\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xb6"`A\x07', - b'\xcf"`0\x07', + b'\xb6\xa2`A\x07', b'\xcb"`@\x07', b'\xcb"`p\x07', - b'\xf1\x00\xa2\x10\n', + b'\xcf"`0\x07', b'\xcf"`p\x07', - b'\xb6\xa2`A\x07', + b'\xf1\x00\xa2\x10\n', ], (Ecu.transmission, 0x7e1, None): [ - b'\x1a\xf6B0\x00', - b'\x1a\xf6F`\x00', - b'\x1a\xf6b`\x00', - b'\x1a\xf6B`\x00', - b'\x1a\xf6b0\x00', b'\x1a\xe6B1\x00', b'\x1a\xe6F1\x00', + b'\x1a\xf6B0\x00', + b'\x1a\xf6B`\x00', + b'\x1a\xf6F`\x00', + b'\x1a\xf6b0\x00', + b'\x1a\xf6b`\x00', ], }, CAR.FORESTER_HYBRID: { @@ -297,134 +297,134 @@ FW_VERSIONS = { }, CAR.FORESTER_PREGLOBAL: { (Ecu.abs, 0x7b0, None): [ + b'm\x97\x14@', b'}\x97\x14@', b'\xf1\x00\xbb\x0c\x04', - b'm\x97\x14@', ], (Ecu.eps, 0x746, None): [ - b'}\xc0\x10\x00', b'm\xc0\x10\x00', + b'}\xc0\x10\x00', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00d5\x1f@ \t', - b'\x00\x00c\xe9\x1f@ \x03', - b'\x00\x00d\xd3\x1f@ \t', b'\x00\x00c\xe9\x00\x00\x00\x00', + b'\x00\x00c\xe9\x1f@ \x03', + b'\x00\x00d5\x1f@ \t', + b'\x00\x00d\xd3\x1f@ \t', ], (Ecu.engine, 0x7e0, None): [ - b'\xba"@p\x07', - b'\xa7)\xa0q\x07', - b'\xf1\x82\xa7)\xa0q\x07', - b'\xba"@@\x07', b'\xa7"@p\x07', + b'\xa7)\xa0q\x07', + b'\xba"@@\x07', + b'\xba"@p\x07', + b'\xf1\x82\xa7)\xa0q\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\xdc\xf2``\x00', - b'\xdc\xf2@`\x00', - b'\xda\xfd\xe0\x80\x00', - b'\xdc\xf2`\x81\x00', - b'\xdc\xf2`\x80\x00', b'\x1a\xf6F`\x00', b'\xda\xf2`\x80\x00', + b'\xda\xfd\xe0\x80\x00', + b'\xdc\xf2@`\x00', + b'\xdc\xf2``\x00', + b'\xdc\xf2`\x80\x00', + b'\xdc\xf2`\x81\x00', ], }, CAR.LEGACY_PREGLOBAL: { (Ecu.abs, 0x7b0, None): [ - b'k\x97D\x00', - b'[\xba\xc4\x03', - b'{\x97D\x00', b'[\x97D\x00', + b'[\xba\xc4\x03', + b'k\x97D\x00', b'k\x9aD\x00', + b'{\x97D\x00', ], (Ecu.eps, 0x746, None): [ - b'[\xb0\x00\x01', b'K\xb0\x00\x01', + b'[\xb0\x00\x01', b'k\xb0\x00\x00', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00c\xb7\x1f@\x10\x16', b'\x00\x00c\x94\x1f@\x10\x08', + b'\x00\x00c\xb7\x1f@\x10\x16', b'\x00\x00c\xec\x1f@ \x04', ], (Ecu.engine, 0x7e0, None): [ - b'\xab*@r\x07', - b'\xa0+@p\x07', - b'\xb4"@0\x07', b'\xa0"@q\x07', + b'\xa0+@p\x07', + b'\xab*@r\x07', b'\xab+@p\x07', + b'\xb4"@0\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\xbe\xf2\x00p\x00', - b'\xbf\xfb\xc0\x80\x00', b'\xbd\xf2\x00`\x00', - b'\xbf\xf2\x00\x80\x00', + b'\xbe\xf2\x00p\x00', b'\xbe\xfb\xc0p\x00', + b'\xbf\xf2\x00\x80\x00', + b'\xbf\xfb\xc0\x80\x00', ], }, CAR.OUTBACK_PREGLOBAL: { (Ecu.abs, 0x7b0, None): [ - b'{\x9a\xac\x00', - b'k\x97\xac\x00', - b'[\xf7\xbc\x03', - b'[\xf7\xac\x03', - b'{\x97\xac\x00', - b'k\x9a\xac\x00', b'[\xba\xac\x03', b'[\xf7\xac\x00', + b'[\xf7\xac\x03', + b'[\xf7\xbc\x03', + b'k\x97\xac\x00', + b'k\x9a\xac\x00', + b'{\x97\xac\x00', + b'{\x9a\xac\x00', ], (Ecu.eps, 0x746, None): [ - b'k\xb0\x00\x00', - b'[\xb0\x00\x00', - b'K\xb0\x00\x02', b'K\xb0\x00\x00', + b'K\xb0\x00\x02', + b'[\xb0\x00\x00', + b'k\xb0\x00\x00', b'{\xb0\x00\x01', ], (Ecu.fwdCamera, 0x787, None): [ - b'\x00\x00c\xec\x1f@ \x04', - b'\x00\x00c\xd1\x1f@\x10\x17', - b'\xf1\x00\xf0\xe0\x0e', + b'\x00\x00c\x90\x1f@\x10\x0e', b'\x00\x00c\x94\x00\x00\x00\x00', b'\x00\x00c\x94\x1f@\x10\x08', b'\x00\x00c\xb7\x1f@\x10\x16', - b'\x00\x00c\x90\x1f@\x10\x0e', + b'\x00\x00c\xd1\x1f@\x10\x17', + b'\x00\x00c\xec\x1f@ \x04', b'\x00\x00c\xec7@\x04', + b'\xf1\x00\xf0\xe0\x0e', ], (Ecu.engine, 0x7e0, None): [ - b'\xb4+@p\x07', - b'\xab"@@\x07', - b'\xa0bAq\x07', + b'\xa0"@\x80\x07', b'\xa0*@q\x07', + b'\xa0*@u\x07', + b'\xa0+@@\x07', + b'\xa0bAq\x07', + b'\xab"@@\x07', + b'\xab"@s\x07', b'\xab*@@\x07', + b'\xab+@@\x07', b'\xb4"@0\x07', b'\xb4"@p\x07', - b'\xab"@s\x07', - b'\xab+@@\x07', b'\xb4"@r\x07', - b'\xa0+@@\x07', - b'\xa0"@\x80\x07', - b'\xa0*@u\x07', + b'\xb4+@p\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\xbd\xfb\xe0\x80\x00', - b'\xbe\xf2@\x80\x00', - b'\xbf\xe2@\x80\x00', - b'\xbf\xf2@\x80\x00', - b'\xbe\xf2@p\x00', b'\xbd\xf2@`\x00', b'\xbd\xf2@\x81\x00', + b'\xbd\xfb\xe0\x80\x00', + b'\xbe\xf2@p\x00', + b'\xbe\xf2@\x80\x00', b'\xbe\xfb\xe0p\x00', + b'\xbf\xe2@\x80\x00', + b'\xbf\xf2@\x80\x00', b'\xbf\xfb\xe0b\x00', ], }, CAR.OUTBACK_PREGLOBAL_2018: { (Ecu.abs, 0x7b0, None): [ b'\x8b\x97\xac\x00', - b'\x8b\x9a\xac\x00', - b'\x9b\x97\xac\x00', b'\x8b\x97\xbc\x00', b'\x8b\x99\xac\x00', - b'\x9b\x9a\xac\x00', + b'\x8b\x9a\xac\x00', + b'\x9b\x97\xac\x00', b'\x9b\x97\xbe\x10', + b'\x9b\x9a\xac\x00', ], (Ecu.eps, 0x746, None): [ b'{\xb0\x00\x00', @@ -432,93 +432,93 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00df\x1f@ \n', - b'\x00\x00d\xfe\x1f@ \x15', b'\x00\x00d\x95\x00\x00\x00\x00', b'\x00\x00d\x95\x1f@ \x0f', b'\x00\x00d\xfe\x00\x00\x00\x00', + b'\x00\x00d\xfe\x1f@ \x15', b'\x00\x00e\x19\x1f@ \x15', ], (Ecu.engine, 0x7e0, None): [ + b'\xb5"@P\x07', b'\xb5"@p\x07', b'\xb5+@@\x07', - b'\xb5"@P\x07', - b'\xc4"@0\x07', b'\xb5b@1\x07', b'\xb5q\xe0@\x07', + b'\xc4"@0\x07', b'\xc4+@0\x07', b'\xc4b@p\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\xbc\xf2@\x81\x00', - b'\xbc\xfb\xe0\x80\x00', - b'\xbc\xf2@\x80\x00', b'\xbb\xf2@`\x00', - b'\xbc\xe2@\x80\x00', - b'\xbc\xfb\xe0`\x00', - b'\xbc\xaf\xe0`\x00', b'\xbb\xfb\xe0`\x00', + b'\xbc\xaf\xe0`\x00', + b'\xbc\xe2@\x80\x00', + b'\xbc\xf2@\x80\x00', + b'\xbc\xf2@\x81\x00', + b'\xbc\xfb\xe0`\x00', + b'\xbc\xfb\xe0\x80\x00', ], }, CAR.OUTBACK: { (Ecu.abs, 0x7b0, None): [ + b'\xa1 \x06\x02', + b'\xa1 \x06\x00', b'\xa1 \x06\x01', b'\xa1 \x07\x00', - b'\xa1 \x08\x01', - b'\xa1 \x06\x00', - b'\xa1 "\t\x01', - b'\xa1 \x08\x02', - b'\xa1 \x06\x02', b'\xa1 \x07\x02', b'\xa1 \x08\x00', + b'\xa1 \x08\x01', + b'\xa1 \x08\x02', b'\xa1 "\t\x00', + b'\xa1 "\t\x01', ], (Ecu.eps, 0x746, None): [ + b'\x1b\xc0\x10\x00', b'\x9b\xc0\x10\x00', b'\x9b\xc0 \x00', - b'\x1b\xc0\x10\x00', ], (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00eJ\x00\x00\x00\x00\x00\x00', b'\x00\x00eJ\x00\x1f@ \x19\x00', b'\x00\x00e\x80\x00\x1f@ \x19\x00', b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00', b'\x00\x00e\x9a\x00\x1f@ 1\x00', - b'\x00\x00eJ\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ - b'\xbc,\xa0q\x07', b'\xbc"`@\x07', + b'\xbc"`q\x07', + b'\xbc,\xa0q\x07', + b'\xbc,\xa0u\x07', b'\xde"`0\x07', - b'\xf1\x82\xbc,\xa0q\x07', - b'\xf1\x82\xe3,\xa0@\x07', + b'\xde,\xa0@\x07', b'\xe2"`0\x07', b'\xe2"`p\x07', - b'\xf1\x82\xe2,\xa0@\x07', - b'\xbc"`q\x07', b'\xe3,\xa0@\x07', - b'\xbc,\xa0u\x07', - b'\xde,\xa0@\x07', + b'\xf1\x82\xbc,\xa0q\x07', + b'\xf1\x82\xe2,\xa0@\x07', + b'\xf1\x82\xe3,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ - b'\xa5\xfe\xf7@\x00', b'\xa5\xf6D@\x00', b'\xa5\xfe\xf6@\x00', + b'\xa5\xfe\xf7@\x00', + b'\xa5\xfe\xf8@\x00', b'\xa7\x8e\xf40\x00', - b'\xf1\x82\xa7\xf6D@\x00', b'\xa7\xf6D@\x00', b'\xa7\xfe\xf4@\x00', - b'\xa5\xfe\xf8@\x00', + b'\xf1\x82\xa7\xf6D@\x00', ], }, CAR.FORESTER_2022: { (Ecu.abs, 0x7b0, None): [ - b'\xa3 !x\x00', b'\xa3 !v\x00', + b'\xa3 !x\x00', b'\xa3 "v\x00', b'\xa3 "x\x00', ], (Ecu.eps, 0x746, None): [ - b'-\xc0%0', b'-\xc0\x040', + b'-\xc0%0', b'=\xc0%\x02', b'=\xc04\x02', ], @@ -527,10 +527,10 @@ FW_VERSIONS = { b'\x04!\x08\x01.\x07!\x08\x022', ], (Ecu.engine, 0x7e0, None): [ - b'\xd5"a0\x07', b'\xd5"`0\x07', - b'\xf1"aq\x07', + b'\xd5"a0\x07', b'\xf1"`q\x07', + b'\xf1"aq\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\x1d\x86B0\x00', diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 8332f242b2..a04f7597b5 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -31,13 +31,13 @@ FW_VERSIONS = { }, CAR.AVALON_2019: { (Ecu.abs, 0x7b0, None): [ + b'F152607110\x00\x00\x00\x00\x00\x00', b'F152607140\x00\x00\x00\x00\x00\x00', b'F152607171\x00\x00\x00\x00\x00\x00', - b'F152607110\x00\x00\x00\x00\x00\x00', b'F152607180\x00\x00\x00\x00\x00\x00', b'F152641040\x00\x00\x00\x00\x00\x00', - b'F152641061\x00\x00\x00\x00\x00\x00', b'F152641050\x00\x00\x00\x00\x00\x00', + b'F152641061\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ b'881510703200\x00\x00\x00\x00', @@ -45,10 +45,10 @@ FW_VERSIONS = { b'881514107100\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ - b'8965B41080\x00\x00\x00\x00\x00\x00', b'8965B07010\x00\x00\x00\x00\x00\x00', - b'8965B41090\x00\x00\x00\x00\x00\x00', b'8965B41070\x00\x00\x00\x00\x00\x00', + b'8965B41080\x00\x00\x00\x00\x00\x00', + b'8965B41090\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x01896630725200\x00\x00\x00\x00', @@ -56,8 +56,8 @@ FW_VERSIONS = { b'\x01896630735100\x00\x00\x00\x00', b'\x01896630738000\x00\x00\x00\x00', b'\x02896630724000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', - b'\x02896630737000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x02896630728000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x02896630737000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F4702300\x00\x00\x00\x00', @@ -77,17 +77,17 @@ FW_VERSIONS = { b'8965B41110\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x018966306Q6000\x00\x00\x00\x00', b'\x01896630742000\x00\x00\x00\x00', b'\x01896630743000\x00\x00\x00\x00', - b'\x018966306Q6000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F6201200\x00\x00\x00\x00', b'\x018821F6201300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ - b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4104100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', ], }, CAR.CAMRY: { @@ -95,12 +95,15 @@ FW_VERSIONS = { b'\x018966306L3100\x00\x00\x00\x00', b'\x018966306L4200\x00\x00\x00\x00', b'\x018966306L5200\x00\x00\x00\x00', + b'\x018966306L9000\x00\x00\x00\x00', b'\x018966306P8000\x00\x00\x00\x00', b'\x018966306Q3100\x00\x00\x00\x00', b'\x018966306Q4000\x00\x00\x00\x00', b'\x018966306Q4100\x00\x00\x00\x00', b'\x018966306Q4200\x00\x00\x00\x00', - b'\x018966333Q9200\x00\x00\x00\x00', + b'\x018966306Q6000\x00\x00\x00\x00', + b'\x018966333N1100\x00\x00\x00\x00', + b'\x018966333N4300\x00\x00\x00\x00', b'\x018966333P3100\x00\x00\x00\x00', b'\x018966333P3200\x00\x00\x00\x00', b'\x018966333P4200\x00\x00\x00\x00', @@ -113,14 +116,11 @@ FW_VERSIONS = { b'\x018966333Q6200\x00\x00\x00\x00', b'\x018966333Q6300\x00\x00\x00\x00', b'\x018966333Q6500\x00\x00\x00\x00', + b'\x018966333Q9200\x00\x00\x00\x00', b'\x018966333W6000\x00\x00\x00\x00', - b'\x018966306Q6000\x00\x00\x00\x00', - b'\x018966333N1100\x00\x00\x00\x00', - b'\x018966333N4300\x00\x00\x00\x00', b'\x018966333X0000\x00\x00\x00\x00', b'\x018966333X4000\x00\x00\x00\x00', b'\x01896633T16000\x00\x00\x00\x00', - b'\x018966306L9000\x00\x00\x00\x00', b'\x028966306B2100\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', b'\x028966306B2300\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', b'\x028966306B2500\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', @@ -144,17 +144,17 @@ FW_VERSIONS = { b'8821F0601300 ', b'8821F0602000 ', b'8821F0603300 ', + b'8821F0603400 ', + b'8821F0604000 ', b'8821F0604100 ', + b'8821F0604200 ', b'8821F0605200 ', + b'8821F0606200 ', b'8821F0607200 ', b'8821F0608000 ', b'8821F0608200 ', - b'8821F0609100 ', - b'8821F0603400 ', - b'8821F0604000 ', - b'8821F0604200 ', - b'8821F0606200 ', b'8821F0609000 ', + b'8821F0609100 ', ], (Ecu.abs, 0x7b0, None): [ b'F152606210\x00\x00\x00\x00\x00\x00', @@ -162,55 +162,55 @@ FW_VERSIONS = { b'F152606270\x00\x00\x00\x00\x00\x00', b'F152606290\x00\x00\x00\x00\x00\x00', b'F152606410\x00\x00\x00\x00\x00\x00', - b'F152633540\x00\x00\x00\x00\x00\x00', - b'F152633A10\x00\x00\x00\x00\x00\x00', - b'F152633A20\x00\x00\x00\x00\x00\x00', b'F152633214\x00\x00\x00\x00\x00\x00', + b'F152633540\x00\x00\x00\x00\x00\x00', b'F152633660\x00\x00\x00\x00\x00\x00', b'F152633712\x00\x00\x00\x00\x00\x00', b'F152633713\x00\x00\x00\x00\x00\x00', + b'F152633A10\x00\x00\x00\x00\x00\x00', + b'F152633A20\x00\x00\x00\x00\x00\x00', b'F152633B51\x00\x00\x00\x00\x00\x00', b'F152633B60\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B33540\x00\x00\x00\x00\x00\x00', b'8965B33542\x00\x00\x00\x00\x00\x00', - b'8965B33580\x00\x00\x00\x00\x00\x00', - b'8965B33581\x00\x00\x00\x00\x00\x00', - b'8965B33621\x00\x00\x00\x00\x00\x00', b'8965B33550\x00\x00\x00\x00\x00\x00', b'8965B33551\x00\x00\x00\x00\x00\x00', + b'8965B33580\x00\x00\x00\x00\x00\x00', + b'8965B33581\x00\x00\x00\x00\x00\x00', b'8965B33611\x00\x00\x00\x00\x00\x00', + b'8965B33621\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F0601200 ', b'8821F0601300 ', b'8821F0602000 ', b'8821F0603300 ', + b'8821F0603400 ', + b'8821F0604000 ', b'8821F0604100 ', + b'8821F0604200 ', b'8821F0605200 ', + b'8821F0606200 ', b'8821F0607200 ', b'8821F0608000 ', b'8821F0608200 ', - b'8821F0609100 ', - b'8821F0603400 ', - b'8821F0604000 ', - b'8821F0604200 ', - b'8821F0606200 ', b'8821F0609000 ', + b'8821F0609100 ', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'8646F0601200 ', b'8646F0601300 ', b'8646F0601400 ', b'8646F0603400 ', + b'8646F0603500 ', b'8646F0604100 ', b'8646F0605000 ', b'8646F0606000 ', b'8646F0606100 ', - b'8646F0607100 ', - b'8646F0603500 ', b'8646F0607000 ', + b'8646F0607100 ', ], }, CAR.CAMRY_TSS2: { @@ -224,22 +224,22 @@ FW_VERSIONS = { b'\x01F152606390\x00\x00\x00\x00\x00\x00', b'\x01F152606400\x00\x00\x00\x00\x00\x00', b'\x01F152606431\x00\x00\x00\x00\x00\x00', + b'F152633310\x00\x00\x00\x00\x00\x00', b'F152633D00\x00\x00\x00\x00\x00\x00', b'F152633D60\x00\x00\x00\x00\x00\x00', - b'F152633310\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q5000\x00\x00\x00\x00', + b'\x018966306Q6000\x00\x00\x00\x00', + b'\x018966306Q7000\x00\x00\x00\x00', b'\x018966306Q9000\x00\x00\x00\x00', b'\x018966306R3000\x00\x00\x00\x00', b'\x018966306R8000\x00\x00\x00\x00', + b'\x018966306T0000\x00\x00\x00\x00', b'\x018966306T3100\x00\x00\x00\x00', b'\x018966306T3200\x00\x00\x00\x00', b'\x018966306T4000\x00\x00\x00\x00', b'\x018966306T4100\x00\x00\x00\x00', - b'\x018966306Q6000\x00\x00\x00\x00', - b'\x018966306Q7000\x00\x00\x00\x00', - b'\x018966306T0000\x00\x00\x00\x00', b'\x018966306V1000\x00\x00\x00\x00', b'\x01896633T20000\x00\x00\x00\x00', ], @@ -252,65 +252,65 @@ FW_VERSIONS = { b'\x028646F0602100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F0602200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F0602300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.CHR: { (Ecu.engine, 0x700, None): [ - b'\x01896631021100\x00\x00\x00\x00', b'\x01896631017100\x00\x00\x00\x00', b'\x01896631017200\x00\x00\x00\x00', + b'\x01896631021100\x00\x00\x00\x00', b'\x0189663F413100\x00\x00\x00\x00', b'\x0189663F414100\x00\x00\x00\x00', - b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0189663F438000\x00\x00\x00\x00', b'\x02896631013200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F405000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F418000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F423000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F431000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', - b'\x0189663F438000\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ b'8821F0W01000 ', b'8821F0W01100 ', b'8821FF401600 ', + b'8821FF402300 ', + b'8821FF402400 ', b'8821FF404000 ', b'8821FF404100 ', + b'8821FF405000 ', b'8821FF405100 ', b'8821FF406000 ', b'8821FF407100 ', - b'8821FF402300 ', - b'8821FF402400 ', - b'8821FF405000 ', ], (Ecu.abs, 0x7b0, None): [ + b'F152610012\x00\x00\x00\x00\x00\x00', + b'F152610013\x00\x00\x00\x00\x00\x00', + b'F152610014\x00\x00\x00\x00\x00\x00', b'F152610020\x00\x00\x00\x00\x00\x00', + b'F152610040\x00\x00\x00\x00\x00\x00', b'F152610153\x00\x00\x00\x00\x00\x00', + b'F152610190\x00\x00\x00\x00\x00\x00', + b'F152610200\x00\x00\x00\x00\x00\x00', b'F152610210\x00\x00\x00\x00\x00\x00', + b'F152610220\x00\x00\x00\x00\x00\x00', + b'F152610230\x00\x00\x00\x00\x00\x00', b'F1526F4034\x00\x00\x00\x00\x00\x00', b'F1526F4044\x00\x00\x00\x00\x00\x00', b'F1526F4073\x00\x00\x00\x00\x00\x00', b'F1526F4121\x00\x00\x00\x00\x00\x00', b'F1526F4122\x00\x00\x00\x00\x00\x00', - b'F152610012\x00\x00\x00\x00\x00\x00', - b'F152610013\x00\x00\x00\x00\x00\x00', - b'F152610014\x00\x00\x00\x00\x00\x00', - b'F152610040\x00\x00\x00\x00\x00\x00', - b'F152610190\x00\x00\x00\x00\x00\x00', - b'F152610200\x00\x00\x00\x00\x00\x00', - b'F152610220\x00\x00\x00\x00\x00\x00', - b'F152610230\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B10011\x00\x00\x00\x00\x00\x00', - b'8965B10040\x00\x00\x00\x00\x00\x00', - b'8965B10070\x00\x00\x00\x00\x00\x00', b'8965B10020\x00\x00\x00\x00\x00\x00', + b'8965B10040\x00\x00\x00\x00\x00\x00', b'8965B10050\x00\x00\x00\x00\x00\x00', + b'8965B10070\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0331024000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', @@ -323,42 +323,42 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F0W01000 ', + b'8821F0W01100 ', b'8821FF401600 ', + b'8821FF402300 ', + b'8821FF402400 ', b'8821FF404000 ', b'8821FF404100 ', + b'8821FF405000 ', b'8821FF405100 ', b'8821FF406000 ', b'8821FF407100 ', - b'8821F0W01100 ', - b'8821FF402300 ', - b'8821FF402400 ', - b'8821FF405000 ', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'8646FF401700 ', b'8646FF401800 ', + b'8646FF402100 ', b'8646FF404000 ', b'8646FF406000 ', b'8646FF407000 ', - b'8646FF402100 ', b'8646FF407100 ', ], }, CAR.CHR_TSS2: { (Ecu.abs, 0x7b0, None): [ + b'F152610041\x00\x00\x00\x00\x00\x00', b'F152610260\x00\x00\x00\x00\x00\x00', b'F1526F4270\x00\x00\x00\x00\x00\x00', - b'F152610041\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B10091\x00\x00\x00\x00\x00\x00', - b'8965B10110\x00\x00\x00\x00\x00\x00', b'8965B10092\x00\x00\x00\x00\x00\x00', + b'8965B10110\x00\x00\x00\x00\x00\x00', b'8965B10111\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ - b'\x0189663F459000\x00\x00\x00\x00', b'\x0189663F438000\x00\x00\x00\x00', + b'\x0189663F459000\x00\x00\x00\x00', b'\x02896631025000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F453000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', ], @@ -418,9 +418,11 @@ FW_VERSIONS = { b'\x01896630ZG5100\x00\x00\x00\x00', b'\x01896630ZG5200\x00\x00\x00\x00', b'\x01896630ZG5300\x00\x00\x00\x00', + b'\x01896630ZJ1000\x00\x00\x00\x00', b'\x01896630ZP1000\x00\x00\x00\x00', b'\x01896630ZP2000\x00\x00\x00\x00', b'\x01896630ZQ5000\x00\x00\x00\x00', + b'\x01896630ZU8000\x00\x00\x00\x00', b'\x01896630ZU9000\x00\x00\x00\x00', b'\x01896630ZX4000\x00\x00\x00\x00', b'\x018966312L8000\x00\x00\x00\x00', @@ -442,16 +444,14 @@ FW_VERSIONS = { b'\x018966312S7000\x00\x00\x00\x00', b'\x018966312W3000\x00\x00\x00\x00', b'\x018966312W9000\x00\x00\x00\x00', - b'\x01896637644000\x00\x00\x00\x00', - b'\x01896630ZJ1000\x00\x00\x00\x00', - b'\x01896630ZU8000\x00\x00\x00\x00', b'\x01896637621000\x00\x00\x00\x00', b'\x01896637623000\x00\x00\x00\x00', b'\x01896637624000\x00\x00\x00\x00', b'\x01896637626000\x00\x00\x00\x00', b'\x01896637639000\x00\x00\x00\x00', - b'\x01896637648000\x00\x00\x00\x00', b'\x01896637643000\x00\x00\x00\x00', + b'\x01896637644000\x00\x00\x00\x00', + b'\x01896637648000\x00\x00\x00\x00', b'\x02896630A07000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630A21000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZJ5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -476,6 +476,8 @@ FW_VERSIONS = { b'\x0230A11000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230ZN4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230ZN5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02312K4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02312U5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x03312K7000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', b'\x03312M3000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', @@ -486,37 +488,38 @@ FW_VERSIONS = { b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', - b'\x02312K4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02312U5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'\x018965B12350\x00\x00\x00\x00\x00\x00', b'\x018965B12470\x00\x00\x00\x00\x00\x00', b'\x018965B12490\x00\x00\x00\x00\x00\x00', b'\x018965B12500\x00\x00\x00\x00\x00\x00', + b'\x018965B12510\x00\x00\x00\x00\x00\x00', b'\x018965B12520\x00\x00\x00\x00\x00\x00', b'\x018965B12530\x00\x00\x00\x00\x00\x00', b'\x018965B1255000\x00\x00\x00\x00', - b'8965B12361\x00\x00\x00\x00\x00\x00', - b'8965B16011\x00\x00\x00\x00\x00\x00', - b'8965B76012\x00\x00\x00\x00\x00\x00', - b'\x018965B12510\x00\x00\x00\x00\x00\x00', b'\x018965B1256000\x00\x00\x00\x00', + b'8965B12361\x00\x00\x00\x00\x00\x00', b'8965B12451\x00\x00\x00\x00\x00\x00', + b'8965B16011\x00\x00\x00\x00\x00\x00', b'8965B16101\x00\x00\x00\x00\x00\x00', b'8965B16170\x00\x00\x00\x00\x00\x00', + b'8965B76012\x00\x00\x00\x00\x00\x00', b'8965B76050\x00\x00\x00\x00\x00\x00', b'8965B76091\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F152602280\x00\x00\x00\x00\x00\x00', + b'\x01F152602470\x00\x00\x00\x00\x00\x00', b'\x01F152602560\x00\x00\x00\x00\x00\x00', b'\x01F152602590\x00\x00\x00\x00\x00\x00', b'\x01F152602650\x00\x00\x00\x00\x00\x00', b'\x01F15260A010\x00\x00\x00\x00\x00\x00', b'\x01F15260A050\x00\x00\x00\x00\x00\x00', + b'\x01F15260A070\x00\x00\x00\x00\x00\x00', b'\x01F152612641\x00\x00\x00\x00\x00\x00', b'\x01F152612651\x00\x00\x00\x00\x00\x00', + b'\x01F152612862\x00\x00\x00\x00\x00\x00', b'\x01F152612B10\x00\x00\x00\x00\x00\x00', b'\x01F152612B51\x00\x00\x00\x00\x00\x00', b'\x01F152612B60\x00\x00\x00\x00\x00\x00', @@ -526,12 +529,9 @@ FW_VERSIONS = { b'\x01F152612B71\x00\x00\x00\x00\x00\x00', b'\x01F152612B81\x00\x00\x00\x00\x00\x00', b'\x01F152612B90\x00\x00\x00\x00\x00\x00', - b'\x01F152612C00\x00\x00\x00\x00\x00\x00', - b'\x01F152612862\x00\x00\x00\x00\x00\x00', b'\x01F152612B91\x00\x00\x00\x00\x00\x00', - b'\x01F15260A070\x00\x00\x00\x00\x00\x00', + b'\x01F152612C00\x00\x00\x00\x00\x00\x00', b'\x01F152676250\x00\x00\x00\x00\x00\x00', - b'\x01F152602470\x00\x00\x00\x00\x00\x00', b'F152612590\x00\x00\x00\x00\x00\x00', b'F152612691\x00\x00\x00\x00\x00\x00', b'F152612692\x00\x00\x00\x00\x00\x00', @@ -547,8 +547,8 @@ FW_VERSIONS = { b'F152612A10\x00\x00\x00\x00\x00\x00', b'F152612D00\x00\x00\x00\x00\x00\x00', b'F152616011\x00\x00\x00\x00\x00\x00', - b'F152616060\x00\x00\x00\x00\x00\x00', b'F152616030\x00\x00\x00\x00\x00\x00', + b'F152616060\x00\x00\x00\x00\x00\x00', b'F152642540\x00\x00\x00\x00\x00\x00', b'F152676293\x00\x00\x00\x00\x00\x00', b'F152676303\x00\x00\x00\x00\x00\x00', @@ -572,8 +572,8 @@ FW_VERSIONS = { b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', - b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1601200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F7603100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', @@ -657,10 +657,10 @@ FW_VERSIONS = { ], (Ecu.abs, 0x7b0, None): [ b'\x01F15260E051\x00\x00\x00\x00\x00\x00', + b'\x01F15260E05300\x00\x00\x00\x00', b'\x01F15260E061\x00\x00\x00\x00\x00\x00', b'\x01F15260E110\x00\x00\x00\x00\x00\x00', b'\x01F15260E170\x00\x00\x00\x00\x00\x00', - b'\x01F15260E05300\x00\x00\x00\x00', b'\x01F15264872300\x00\x00\x00\x00', b'\x01F15264872400\x00\x00\x00\x00', b'\x01F15264872500\x00\x00\x00\x00', @@ -680,6 +680,8 @@ FW_VERSIONS = { b'\x01896630E64100\x00\x00\x00\x00', b'\x01896630E64200\x00\x00\x00\x00', b'\x01896630E64400\x00\x00\x00\x00', + b'\x01896630E67000\x00\x00\x00\x00', + b'\x01896630EA1000\x00\x00\x00\x00', b'\x01896630EB1000\x00\x00\x00\x00', b'\x01896630EB1100\x00\x00\x00\x00', b'\x01896630EB1200\x00\x00\x00\x00', @@ -691,16 +693,14 @@ FW_VERSIONS = { b'\x01896630ED9100\x00\x00\x00\x00', b'\x01896630EE1000\x00\x00\x00\x00', b'\x01896630EE1100\x00\x00\x00\x00', - b'\x01896630EG3000\x00\x00\x00\x00', - b'\x01896630EG5000\x00\x00\x00\x00', - b'\x01896630E67000\x00\x00\x00\x00', - b'\x01896630EA1000\x00\x00\x00\x00', b'\x01896630EE4000\x00\x00\x00\x00', b'\x01896630EE4100\x00\x00\x00\x00', b'\x01896630EE5000\x00\x00\x00\x00', b'\x01896630EE6000\x00\x00\x00\x00', b'\x01896630EE7000\x00\x00\x00\x00', b'\x01896630EF8000\x00\x00\x00\x00', + b'\x01896630EG3000\x00\x00\x00\x00', + b'\x01896630EG5000\x00\x00\x00\x00', b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630EB3000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', @@ -713,8 +713,8 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', - b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, @@ -754,8 +754,8 @@ FW_VERSIONS = { b'8965B53311\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702300\x00\x00\x00\x00', b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'8646F5301101\x00\x00\x00\x00', @@ -916,8 +916,8 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'F15260R102\x00\x00\x00\x00\x00\x00', b'F15260R103\x00\x00\x00\x00\x00\x00', - b'F152642493\x00\x00\x00\x00\x00\x00', b'F152642492\x00\x00\x00\x00\x00\x00', + b'F152642493\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ b'881514201200\x00\x00\x00\x00', @@ -979,6 +979,7 @@ FW_VERSIONS = { b'\x01896630R58000\x00\x00\x00\x00', b'\x01896630R58100\x00\x00\x00\x00', b'\x018966342E2000\x00\x00\x00\x00', + b'\x018966342M5000\x00\x00\x00\x00', b'\x018966342M8000\x00\x00\x00\x00', b'\x018966342S9000\x00\x00\x00\x00', b'\x018966342T1000\x00\x00\x00\x00', @@ -991,13 +992,20 @@ FW_VERSIONS = { b'\x018966342V3000\x00\x00\x00\x00', b'\x018966342V3100\x00\x00\x00\x00', b'\x018966342V3200\x00\x00\x00\x00', + b'\x018966342W5000\x00\x00\x00\x00', + b'\x018966342W7000\x00\x00\x00\x00', + b'\x018966342W8000\x00\x00\x00\x00', + b'\x018966342X5000\x00\x00\x00\x00', + b'\x018966342X6000\x00\x00\x00\x00', b'\x01896634A05000\x00\x00\x00\x00', + b'\x01896634A15000\x00\x00\x00\x00', b'\x01896634A19000\x00\x00\x00\x00', b'\x01896634A19100\x00\x00\x00\x00', b'\x01896634A20000\x00\x00\x00\x00', b'\x01896634A20100\x00\x00\x00\x00', b'\x01896634A22000\x00\x00\x00\x00', b'\x01896634A22100\x00\x00\x00\x00', + b'\x01896634A25000\x00\x00\x00\x00', b'\x01896634A30000\x00\x00\x00\x00', b'\x01896634A44000\x00\x00\x00\x00', b'\x01896634A45000\x00\x00\x00\x00', @@ -1005,35 +1013,28 @@ FW_VERSIONS = { b'\x028966342M7000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', b'\x028966342T0000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', b'\x028966342V1000\x00\x00\x00\x00897CF1202001\x00\x00\x00\x00', - b'\x028966342Y8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x02896634A18000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x02896634A18100\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x02896634A43000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00', - b'\x02896634A47000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00', - b'\x028966342Z8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', - b'\x01896634A15000\x00\x00\x00\x00', - b'\x018966342M5000\x00\x00\x00\x00', - b'\x018966342W8000\x00\x00\x00\x00', - b'\x018966342X5000\x00\x00\x00\x00', - b'\x018966342X6000\x00\x00\x00\x00', - b'\x01896634A25000\x00\x00\x00\x00', - b'\x018966342W5000\x00\x00\x00\x00', - b'\x018966342W7000\x00\x00\x00\x00', b'\x028966342W4001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x028966342Y8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', + b'\x028966342Z8000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', b'\x02896634A13000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02896634A13001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896634A13101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896634A18000\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', + b'\x02896634A18100\x00\x00\x00\x00897CF1201001\x00\x00\x00\x00', b'\x02896634A23000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02896634A23001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', b'\x02896634A23101\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', - b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', - b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896634A43000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00', + b'\x02896634A47000\x00\x00\x00\x00897CF4201001\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F15260R210\x00\x00\x00\x00\x00\x00', b'\x01F15260R220\x00\x00\x00\x00\x00\x00', b'\x01F15260R290\x00\x00\x00\x00\x00\x00', + b'\x01F15260R292\x00\x00\x00\x00\x00\x00', b'\x01F15260R300\x00\x00\x00\x00\x00\x00', b'\x01F15260R302\x00\x00\x00\x00\x00\x00', b'\x01F152642551\x00\x00\x00\x00\x00\x00', @@ -1045,28 +1046,27 @@ FW_VERSIONS = { b'\x01F152642711\x00\x00\x00\x00\x00\x00', b'\x01F152642750\x00\x00\x00\x00\x00\x00', b'\x01F152642751\x00\x00\x00\x00\x00\x00', - b'\x01F15260R292\x00\x00\x00\x00\x00\x00', - b'F152642291\x00\x00\x00\x00\x00\x00', b'F152642290\x00\x00\x00\x00\x00\x00', + b'F152642291\x00\x00\x00\x00\x00\x00', b'F152642322\x00\x00\x00\x00\x00\x00', b'F152642330\x00\x00\x00\x00\x00\x00', b'F152642331\x00\x00\x00\x00\x00\x00', - b'F152642531\x00\x00\x00\x00\x00\x00', - b'F152642532\x00\x00\x00\x00\x00\x00', b'F152642520\x00\x00\x00\x00\x00\x00', b'F152642521\x00\x00\x00\x00\x00\x00', + b'F152642531\x00\x00\x00\x00\x00\x00', + b'F152642532\x00\x00\x00\x00\x00\x00', b'F152642540\x00\x00\x00\x00\x00\x00', b'F152642541\x00\x00\x00\x00\x00\x00', b'F152642542\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ + b'\x028965B0R01200\x00\x00\x00\x008965B0R02200\x00\x00\x00\x00', + b'\x028965B0R01300\x00\x00\x00\x008965B0R02300\x00\x00\x00\x00', + b'\x028965B0R01400\x00\x00\x00\x008965B0R02400\x00\x00\x00\x00', b'8965B42170\x00\x00\x00\x00\x00\x00', b'8965B42171\x00\x00\x00\x00\x00\x00', b'8965B42180\x00\x00\x00\x00\x00\x00', b'8965B42181\x00\x00\x00\x00\x00\x00', - b'\x028965B0R01200\x00\x00\x00\x008965B0R02200\x00\x00\x00\x00', - b'\x028965B0R01300\x00\x00\x00\x008965B0R02300\x00\x00\x00\x00', - b'\x028965B0R01400\x00\x00\x00\x008965B0R02400\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', @@ -1088,22 +1088,16 @@ FW_VERSIONS = { b'\x01F15260R350\x00\x00\x00\x00\x00\x00', b'\x01F15260R361\x00\x00\x00\x00\x00\x00', b'\x01F15264283100\x00\x00\x00\x00', - b'\x01F15264286200\x00\x00\x00\x00', - b'\x01F15264286100\x00\x00\x00\x00', b'\x01F15264283200\x00\x00\x00\x00', + b'\x01F15264286100\x00\x00\x00\x00', + b'\x01F15264286200\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'\x028965B0R01500\x00\x00\x00\x008965B0R02500\x00\x00\x00\x00', - b'8965B42182\x00\x00\x00\x00\x00\x00', b'8965B42172\x00\x00\x00\x00\x00\x00', + b'8965B42182\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ - b'\x01896634AA0000\x00\x00\x00\x00', - b'\x01896634AA0100\x00\x00\x00\x00', - b'\x01896634AA1000\x00\x00\x00\x00', - b'\x01896634A88000\x00\x00\x00\x00', - b'\x01896634A89000\x00\x00\x00\x00', - b'\x01896634A89100\x00\x00\x00\x00', b'\x01896634A02001\x00\x00\x00\x00', b'\x01896634A03000\x00\x00\x00\x00', b'\x01896634A08000\x00\x00\x00\x00', @@ -1111,6 +1105,12 @@ FW_VERSIONS = { b'\x01896634A62000\x00\x00\x00\x00', b'\x01896634A62100\x00\x00\x00\x00', b'\x01896634A63000\x00\x00\x00\x00', + b'\x01896634A88000\x00\x00\x00\x00', + b'\x01896634A89000\x00\x00\x00\x00', + b'\x01896634A89100\x00\x00\x00\x00', + b'\x01896634AA0000\x00\x00\x00\x00', + b'\x01896634AA0100\x00\x00\x00\x00', + b'\x01896634AA1000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R01100\x00\x00\x00\x00', @@ -1132,10 +1132,10 @@ FW_VERSIONS = { ], (Ecu.engine, 0x700, None): [ b'\x01896634A88100\x00\x00\x00\x00', - b'\x01896634AJ2000\x00\x00\x00\x00', b'\x01896634A89100\x00\x00\x00\x00', b'\x01896634AE1001\x00\x00\x00\x00', b'\x01896634AF0000\x00\x00\x00\x00', + b'\x01896634AJ2000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R03100\x00\x00\x00\x00', @@ -1205,14 +1205,14 @@ FW_VERSIONS = { b'\x018966333T5100\x00\x00\x00\x00', b'\x018966333X6000\x00\x00\x00\x00', b'\x01896633T07000\x00\x00\x00\x00', + b'\x01896633T38000\x00\x00\x00\x00', + b'\x01896633T58000\x00\x00\x00\x00', b'\x028966333S8000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', b'\x028966333S8000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x028966333T0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x028966333V4000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x028966333W1000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x02896633T09000\x00\x00\x00\x00897CF3307001\x00\x00\x00\x00', - b'\x01896633T38000\x00\x00\x00\x00', - b'\x01896633T58000\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F152606281\x00\x00\x00\x00\x00\x00', @@ -1234,20 +1234,20 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', b'\x018821F3301200\x00\x00\x00\x00', + b'\x018821F3301300\x00\x00\x00\x00', b'\x018821F3301400\x00\x00\x00\x00', b'\x018821F6201300\x00\x00\x00\x00', - b'\x018821F3301300\x00\x00\x00\x00', b'\x018821F6201400\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0610000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', + b'\x028646F3303100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3304100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', + b'\x028646F3304200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F0610000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', - b'\x028646F3303100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', - b'\x028646F3304200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, CAR.LEXUS_ES: { @@ -1337,8 +1337,8 @@ FW_VERSIONS = { (Ecu.dsu, 0x791, None): [ b'881517803100\x00\x00\x00\x00', b'881517803300\x00\x00\x00\x00', - b'881517804300\x00\x00\x00\x00', b'881517804100\x00\x00\x00\x00', + b'881517804300\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B78060\x00\x00\x00\x00\x00\x00', @@ -1356,13 +1356,13 @@ FW_VERSIONS = { }, CAR.LEXUS_NX_TSS2: { (Ecu.engine, 0x700, None): [ + b'\x018966378B2000\x00\x00\x00\x00', b'\x018966378B2100\x00\x00\x00\x00', b'\x018966378B3000\x00\x00\x00\x00', + b'\x018966378B3100\x00\x00\x00\x00', b'\x018966378B4100\x00\x00\x00\x00', b'\x018966378G2000\x00\x00\x00\x00', b'\x018966378G3000\x00\x00\x00\x00', - b'\x018966378B2000\x00\x00\x00\x00', - b'\x018966378B3100\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0237887000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1378,9 +1378,9 @@ FW_VERSIONS = { b'8965B78120\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ - b'\x018821F3301400\x00\x00\x00\x00', b'\x018821F3301200\x00\x00\x00\x00', b'\x018821F3301300\x00\x00\x00\x00', + b'\x018821F3301400\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F78030A0\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', @@ -1441,11 +1441,11 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'F152648472\x00\x00\x00\x00\x00\x00', b'F152648473\x00\x00\x00\x00\x00\x00', + b'F152648474\x00\x00\x00\x00\x00\x00', b'F152648492\x00\x00\x00\x00\x00\x00', b'F152648493\x00\x00\x00\x00\x00\x00', - b'F152648474\x00\x00\x00\x00\x00\x00', - b'F152648630\x00\x00\x00\x00\x00\x00', b'F152648494\x00\x00\x00\x00\x00\x00', + b'F152648630\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ b'881514810300\x00\x00\x00\x00', @@ -1526,39 +1526,39 @@ FW_VERSIONS = { b'\x01896630EC9100\x00\x00\x00\x00', b'\x01896630ED0000\x00\x00\x00\x00', b'\x01896630ED0100\x00\x00\x00\x00', + b'\x01896630ED5000\x00\x00\x00\x00', b'\x01896630ED6000\x00\x00\x00\x00', + b'\x018966348R9200\x00\x00\x00\x00', b'\x018966348T8000\x00\x00\x00\x00', b'\x018966348W5100\x00\x00\x00\x00', b'\x018966348W9000\x00\x00\x00\x00', + b'\x018966348X0000\x00\x00\x00\x00', b'\x01896634D12000\x00\x00\x00\x00', b'\x01896634D12100\x00\x00\x00\x00', b'\x01896634D43000\x00\x00\x00\x00', b'\x01896634D44000\x00\x00\x00\x00', - b'\x018966348X0000\x00\x00\x00\x00', - b'\x01896630ED5000\x00\x00\x00\x00', - b'\x018966348R9200\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ + b'\x02348U2000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348X4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348X5000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348X8000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348Y3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0234D14000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0234D16000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x02348U2000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F15260E031\x00\x00\x00\x00\x00\x00', b'\x01F15260E041\x00\x00\x00\x00\x00\x00', b'\x01F152648781\x00\x00\x00\x00\x00\x00', b'\x01F152648801\x00\x00\x00\x00\x00\x00', + b'F152648493\x00\x00\x00\x00\x00\x00', + b'F152648811\x00\x00\x00\x00\x00\x00', b'F152648831\x00\x00\x00\x00\x00\x00', b'F152648891\x00\x00\x00\x00\x00\x00', + b'F152648C80\x00\x00\x00\x00\x00\x00', b'F152648D00\x00\x00\x00\x00\x00\x00', b'F152648D60\x00\x00\x00\x00\x00\x00', - b'F152648811\x00\x00\x00\x00\x00\x00', - b'F152648C80\x00\x00\x00\x00\x00\x00', - b'F152648493\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B48261\x00\x00\x00\x00\x00\x00', @@ -1628,8 +1628,8 @@ FW_VERSIONS = { CAR.ALPHARD_TSS2: { (Ecu.engine, 0x7e0, None): [ b'\x0235870000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', - b'\x0235883000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0235879000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0235883000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B58040\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/volkswagen/fingerprints.py b/selfdrive/car/volkswagen/fingerprints.py index bd58e12ebc..fa9061e582 100644 --- a/selfdrive/car/volkswagen/fingerprints.py +++ b/selfdrive/car/volkswagen/fingerprints.py @@ -124,13 +124,14 @@ FW_VERSIONS = { CAR.GOLF_MK7: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906016A \xf1\x897697', - b'\xf1\x8704E906016N \xf1\x899105', b'\xf1\x8704E906016AD\xf1\x895758', b'\xf1\x8704E906016CE\xf1\x899096', b'\xf1\x8704E906016CH\xf1\x899226', + b'\xf1\x8704E906016N \xf1\x899105', b'\xf1\x8704E906023AG\xf1\x891726', b'\xf1\x8704E906023BN\xf1\x894518', b'\xf1\x8704E906024K \xf1\x896811', + b'\xf1\x8704E906024K \xf1\x899970', b'\xf1\x8704E906027GR\xf1\x892394', b'\xf1\x8704E906027HD\xf1\x892603', b'\xf1\x8704E906027HD\xf1\x893742', @@ -172,7 +173,6 @@ FW_VERSIONS = { b'\xf1\x878V0906264L \xf1\x890002', b'\xf1\x878V0906264M \xf1\x890001', b'\xf1\x878V09C0BB01 \xf1\x890001', - b'\xf1\x8704E906024K \xf1\x899970', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927749AP\xf1\x892943', @@ -232,18 +232,18 @@ FW_VERSIONS = { b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2251229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', - b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', + b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', b'\xf1\x875Q0959655D \xf1\x890388\xf1\x82\x111413001113120006110417121A101A9113', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271112111312--071104171825102591131211', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13271212111312--071104171838103891131211', - b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13341512112212--071104172328102891131211', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13272512111312--07110417182C102C91131211', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13341512112212--071104172328102891131211', b'\xf1\x875Q0959655M \xf1\x890361\xf1\x82\x111413001112120041114115121611169112', + b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200061104171717101791132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200621143171717111791132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200061104171724102491132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200621143171724112491132111', - b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011211200061104171717101791132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1324230011211200631143171724122491132111', b'\xf1\x875Q0959655T \xf1\x890825\xf1\x82\x13271200111312--071104171837103791132111', b'\xf1\x875Q0959655T \xf1\x890830\xf1\x82\x13271100111312--071104171826102691131211', @@ -268,9 +268,9 @@ FW_VERSIONS = { b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00442A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A00642A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A07B05A1', + b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00502A0', b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00602A0', b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0522A00402A0', - b'\xf1\x875Q0909144L \xf1\x891021\xf1\x82\x0521A00502A0', b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511A00403A0', b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00404A1', @@ -281,14 +281,14 @@ FW_VERSIONS = { b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00507A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A07B04A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A20B03A1', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000400', b'\xf1\x875QD909144B \xf1\x891072\xf1\x82\x0521A00507A1', b'\xf1\x875QM909144A \xf1\x891072\xf1\x82\x0521A20B03A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A00442A1', b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A16A1', - b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A18A1', b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A17A1', + b'\xf1\x875QN909144A \xf1\x895081\xf1\x82\x0571A01A18A1', b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\x0571A01A18A1', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A2000400', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907567G \xf1\x890390\xf1\x82\x0101', @@ -313,9 +313,9 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906024AK\xf1\x899937', b'\xf1\x8704E906024AS\xf1\x899912', + b'\xf1\x8704E906024B \xf1\x895594', b'\xf1\x8704E906024BC\xf1\x899971', b'\xf1\x8704E906024BG\xf1\x891057', - b'\xf1\x8704E906024B \xf1\x895594', b'\xf1\x8704E906024C \xf1\x899970', b'\xf1\x8704E906024L \xf1\x895595', b'\xf1\x8704E906024L \xf1\x899970', @@ -338,8 +338,8 @@ FW_VERSIONS = { b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\x1314171231313500314643011650169333463100', b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\x1311170031313300314240011150119333433100', b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\x1319170031313300314240011550159333463100', - b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314643021650169333613100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314642021650169333613100', + b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314643021650169333613100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A10A11A1', @@ -365,24 +365,24 @@ FW_VERSIONS = { b'\xf1\x8704L906026FP\xf1\x892012', b'\xf1\x8704L906026GA\xf1\x892013', b'\xf1\x8704L906026KD\xf1\x894798', - b'\xf1\x873G0906259B \xf1\x890002', b'\xf1\x873G0906259 \xf1\x890004', + b'\xf1\x873G0906259B \xf1\x890002', b'\xf1\x873G0906264 \xf1\x890004', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300042H \xf1\x891601', + b'\xf1\x870CW300042H \xf1\x891607', b'\xf1\x870CW300043H \xf1\x891601', b'\xf1\x870CW300048R \xf1\x890610', b'\xf1\x870D9300013A \xf1\x894905', b'\xf1\x870D9300014L \xf1\x895002', + b'\xf1\x870D9300018C \xf1\x895297', b'\xf1\x870D9300041A \xf1\x894801', + b'\xf1\x870D9300042H \xf1\x894901', b'\xf1\x870DD300045T \xf1\x891601', b'\xf1\x870DD300046H \xf1\x891601', b'\xf1\x870DL300011H \xf1\x895201', - b'\xf1\x870CW300042H \xf1\x891601', - b'\xf1\x870CW300042H \xf1\x891607', b'\xf1\x870GC300042H \xf1\x891404', - b'\xf1\x870D9300018C \xf1\x895297', - b'\xf1\x870D9300042H \xf1\x894901', b'\xf1\x870GC300043 \xf1\x892301', ], (Ecu.srs, 0x715, None): [ @@ -390,14 +390,14 @@ FW_VERSIONS = { b'\xf1\x873Q0959655AF\xf1\x890195\xf1\x82\r56140056130012026612120211', b'\xf1\x873Q0959655AN\xf1\x890305\xf1\x82\r58160058140013036914110311', b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311', + b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012416612124111', b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111', b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', - b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e5915005914001305701311052900', b'\xf1\x873Q0959655BG\xf1\x890712\xf1\x82\x0e5915005914001305701311052900', + b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e5915005914001305701311052900', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e5915005914001344701311442900', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e5915005914001354701311542900', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900', - b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012416612124111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\x1315120011111200631145171716121691132111', ], (Ecu.eps, 0x712, None): [ @@ -409,19 +409,19 @@ FW_VERSIONS = { b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526B0060905', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521B00606A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516B00501A1', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00603A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00703A1', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563B0000600', - b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521B00603A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x873Q0907572A \xf1\x890126', b'\xf1\x873Q0907572A \xf1\x890130', b'\xf1\x873Q0907572B \xf1\x890192', + b'\xf1\x873Q0907572B \xf1\x890194', b'\xf1\x873Q0907572C \xf1\x890195', b'\xf1\x873Q0907572C \xf1\x890196', b'\xf1\x875Q0907572P \xf1\x890682', - b'\xf1\x873Q0907572B \xf1\x890194', b'\xf1\x875Q0907572R \xf1\x890771', ], }, @@ -530,6 +530,7 @@ FW_VERSIONS = { CAR.TIGUAN_MK2: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8703N906026D \xf1\x893680', + b'\xf1\x8704E906024AP\xf1\x891461', b'\xf1\x8704E906027NB\xf1\x899504', b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906027G \xf1\x899893', @@ -539,13 +540,12 @@ FW_VERSIONS = { b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890005', b'\xf1\x875NA907115J \xf1\x890002', + b'\xf1\x8783A907115 \xf1\x890007', b'\xf1\x8783A907115B \xf1\x890005', b'\xf1\x8783A907115F \xf1\x890002', b'\xf1\x8783A907115G \xf1\x890001', b'\xf1\x8783A907115K \xf1\x890001', b'\xf1\x8783A907115K \xf1\x890002', - b'\xf1\x8704E906024AP\xf1\x891461', - b'\xf1\x8783A907115 \xf1\x890007', b'\xf1\x8783A907115Q \xf1\x890001', ], (Ecu.transmission, 0x7e1, None): [ @@ -557,6 +557,7 @@ FW_VERSIONS = { b'\xf1\x8709G927158GN\xf1\x893938', b'\xf1\x8709G927158HB\xf1\x894069', b'\xf1\x870D9300043 \xf1\x895202', + b'\xf1\x870DD300046K \xf1\x892302', b'\xf1\x870DL300011N \xf1\x892001', b'\xf1\x870DL300011N \xf1\x892012', b'\xf1\x870DL300012M \xf1\x892107', @@ -565,40 +566,39 @@ FW_VERSIONS = { b'\xf1\x870DL300013G \xf1\x892119', b'\xf1\x870DL300013G \xf1\x892120', b'\xf1\x870DL300014C \xf1\x893703', - b'\xf1\x870DD300046K \xf1\x892302', b'\xf1\x870GC300013P \xf1\x892401', b'\xf1\x870GC300046Q \xf1\x892802', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1316143231313500314617011730179333423100', b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\x1331310031333334313132573732379333313100', b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1331310031333334313132013730379333423100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\x1316143231313500314641011750179333423100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1312110031333300314240013750379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1312110031333300314240583752379333423100', - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333336313140013950399333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333336313140013950399333423100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100', - b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100', b'\xf1\x875Q0959655CD\xf1\x890421\xf1\x82\x13123112313333003145406F6154619333613100', - b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1316143231313500314617011730179333423100', + b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6050705', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', b'\xf1\x875QF909144A \xf1\x895581\xf1\x82\x0571A60834A1', b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571A60634A1', b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571A62A32A1', - b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', - b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60804A1', b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002RA60A2ROM', b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002SA6092SOM', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', + b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60804A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', + b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60804A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', @@ -628,8 +628,8 @@ FW_VERSIONS = { b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A8090400', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x873Q0907572C \xf1\x890195', b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x873Q0907572C \xf1\x890195', ], }, CAR.TRANSPORTER_T61: { @@ -642,8 +642,8 @@ FW_VERSIONS = { b'\xf1\x8705L906023MR\xf1\x892582', ], (Ecu.transmission, 0x7e1, None): [ - b'\xf1\x870BT300012G \xf1\x893102', b'\xf1\x870BT300012E \xf1\x893105', + b'\xf1\x870BT300012G \xf1\x893102', b'\xf1\x870BT300046R \xf1\x893102', b'\xf1\x870DV300012B \xf1\x893701', b'\xf1\x870DV300012B \xf1\x893702', @@ -656,12 +656,12 @@ FW_VERSIONS = { ], (Ecu.eps, 0x712, None): [ b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\x0532380518A2', - b'\xf1\x877LA909144G \xf1\x897160\xf1\x82\x05333A5519A2', b'\xf1\x877LA909144F \xf1\x897150\xf1\x82\x05323A5519A2', + b'\xf1\x877LA909144G \xf1\x897160\xf1\x82\x05333A5519A2', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572R \xf1\x890372', ], }, CAR.TROC_MK1: { @@ -671,25 +671,25 @@ FW_VERSIONS = { b'\xf1\x8705E906018P \xf1\x896020', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300041S \xf1\x891615', b'\xf1\x870CW300050J \xf1\x891911', b'\xf1\x870CW300051M \xf1\x891925', b'\xf1\x870CW300051M \xf1\x891928', - b'\xf1\x870CW300041S \xf1\x891615', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1311110012333300314240681152119333463100', - b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x13111100123333003142404M1152119333613100', - b'\xf1\x875Q0959655CF\xf1\x890421\xf1\x82\x1311110012333300314240021150119333613100', b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1111001111001105111111052900', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1311110012333300314240681152119333463100', + b'\xf1\x875Q0959655CF\xf1\x890421\xf1\x82\x1311110012333300314240021150119333613100', + b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x13111100123333003142404M1152119333613100', ], (Ecu.eps, 0x712, None): [ + b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521060403A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521060405A1', b'\xf1\x875WA907144M \xf1\x891051\xf1\x82\x001T06081T7N', - b'\xf1\x875Q0909144AA\xf1\x891081\xf1\x82\x0521060403A1', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x872Q0907572M \xf1\x890233', + b'\xf1\x872Q0907572T \xf1\x890383', ], }, CAR.AUDI_A3_MK3: { @@ -718,8 +718,8 @@ FW_VERSIONS = { b'\xf1\x870CW300048 \xf1\x895201', b'\xf1\x870D9300012 \xf1\x894912', b'\xf1\x870D9300012 \xf1\x894931', - b'\xf1\x870D9300012L \xf1\x894521', b'\xf1\x870D9300012K \xf1\x894513', + b'\xf1\x870D9300012L \xf1\x894521', b'\xf1\x870D9300013B \xf1\x894902', b'\xf1\x870D9300013B \xf1\x894931', b'\xf1\x870D9300041N \xf1\x894512', @@ -740,8 +740,8 @@ FW_VERSIONS = { b'\xf1\x875Q0959655AM\xf1\x890318\xf1\x82\x1311111111111112311411011531159321212100', b'\xf1\x875Q0959655AR\xf1\x890315\xf1\x82\x1311110011131115311211012331239321212100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1311110011131100311111011731179321342100', - b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--171115141112221291163221', + b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111211--261117141112231291163221', @@ -767,8 +767,8 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907567M \xf1\x890398\xf1\x82\x0101', b'\xf1\x875Q0907567N \xf1\x890400\xf1\x82\x0101', - b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', + b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572P \xf1\x890682', @@ -811,10 +811,10 @@ FW_VERSIONS = { ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655BF\xf1\x890403\xf1\x82\x1321211111211200311121232152219321422111', + b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218A219321532111', b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218C219321532111', b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111224118A119321532111', b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111', - b'\xf1\x875Q0959655BQ\xf1\x890421\xf1\x82\x132121111121120031112124218A219321532111', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300', @@ -918,12 +918,12 @@ FW_VERSIONS = { b'\xf1\x872Q0959655BJ\xf1\x890412\xf1\x82\x132223042111042121040404042B251215391423', ], (Ecu.eps, 0x712, None): [ - b'\xf1\x872Q1909144M \xf1\x896041', b'\xf1\x872Q1909144AB\xf1\x896050', + b'\xf1\x872Q1909144M \xf1\x896041', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572T \xf1\x890383', ], }, CAR.SKODA_KAROQ_MK1: { @@ -939,10 +939,10 @@ FW_VERSIONS = { b'\xf1\x870GC300014L \xf1\x892802', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1312110012120011111100010200--2521210749', b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001101131112012100', b'\xf1\x873Q0959655BH\xf1\x890712\xf1\x82\x0e1213001211001101131122012100', b'\xf1\x873Q0959655DE\xf1\x890731\xf1\x82\x0e1213001211001101131121012J00', + b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1312110012120011111100010200--2521210749', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563T6090500', @@ -959,20 +959,21 @@ FW_VERSIONS = { CAR.SKODA_KODIAQ_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027DD\xf1\x893123', - b'\xf1\x8704E906027NB\xf1\x899504', + b'\xf1\x8704E906027LD\xf1\x893433', b'\xf1\x8704E906027NB\xf1\x896517', + b'\xf1\x8704E906027NB\xf1\x899504', b'\xf1\x8704L906026DE\xf1\x895418', b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906026HT\xf1\x893617', - b'\xf1\x8783A907115E \xf1\x890001', b'\xf1\x8705E906018DJ\xf1\x890915', b'\xf1\x8705E906018DJ\xf1\x891903', + b'\xf1\x875NA906259E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890005', - b'\xf1\x875NA906259E \xf1\x890003', - b'\xf1\x8704E906027LD\xf1\x893433', + b'\xf1\x8783A907115E \xf1\x890001', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870D9300014S \xf1\x895201', b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870DL300011N \xf1\x892014', b'\xf1\x870DL300012M \xf1\x892107', @@ -981,17 +982,16 @@ FW_VERSIONS = { b'\xf1\x870GC300014N \xf1\x892801', b'\xf1\x870GC300019H \xf1\x892806', b'\xf1\x870GC300046Q \xf1\x892802', - b'\xf1\x870D9300014S \xf1\x895201', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1213001211001244212111442100', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e1213001211001205212112052100', b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121240749', - b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', @@ -1002,12 +1002,12 @@ FW_VERSIONS = { b'\xf1\x875TA907145F \xf1\x891063\xf1\x82\x002LT61A2LOM', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572Q \xf1\x890342', - b'\xf1\x872Q0907572R \xf1\x890372', - b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572AB\xf1\x890397', b'\xf1\x872Q0907572M \xf1\x890233', + b'\xf1\x872Q0907572Q \xf1\x890342', + b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572T \xf1\x890383', ], }, CAR.SKODA_OCTAVIA_MK3: { @@ -1073,12 +1073,12 @@ FW_VERSIONS = { b'\xf1\x872Q0959655AS\xf1\x890411\xf1\x82\x1311150411110411210404040417151215391413', ], (Ecu.eps, 0x712, None): [ - b'\xf1\x872Q1909144M \xf1\x896041', b'\xf1\x872Q1909144AB\xf1\x896050', + b'\xf1\x872Q1909144M \xf1\x896041', ], (Ecu.fwdRadar, 0x757, None): [ - b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572R \xf1\x890372', ], }, CAR.SKODA_SUPERB_MK3: { @@ -1133,10 +1133,10 @@ FW_VERSIONS = { b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514UZ070203', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ050303', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ070303', - b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ070505', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ060505', - b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060700', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ070505', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060600', + b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060700', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070500', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070600', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070700', diff --git a/selfdrive/debug/format_fingerprints.py b/selfdrive/debug/format_fingerprints.py index 7c8819cc47..b3e61b1eeb 100755 --- a/selfdrive/debug/format_fingerprints.py +++ b/selfdrive/debug/format_fingerprints.py @@ -76,6 +76,12 @@ def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tup with open(fingerprints_file, "r") as f: comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line] + # sort fw versions + if fw_versions[brand] is not None: + for platform in fw_versions[brand]: + for ecu in fw_versions[brand][platform]: + fw_versions[brand][platform][ecu] = sorted(fw_versions[brand][platform][ecu]) + with open(fingerprints_file, "w") as f: f.write(FINGERPRINTS_PY_TEMPLATE.render(brand=brand, comments=comments, ECU_NAME=ECU_NAME, FINGERPRINTS=FINGERPRINTS, FW_VERSIONS=fw_versions)) From 33e7baee401d8a8a2dd4210e6f1712c05ff200d4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 20 Dec 2023 13:27:27 -0800 Subject: [PATCH 313/724] Docs: fix push and running locally (#30823) fix push --- .github/workflows/docs.yaml | 2 +- docs/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6ad273a14d..bef5f96ea4 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -60,4 +60,4 @@ jobs: git commit -m "build docs" # docs live in different repo to not bloat openpilot's full clone size - git push -f origin gh-pages + git push -f origin tmp:gh-pages diff --git a/docs/Makefile b/docs/Makefile index 1eaae656bb..09103e206e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -47,7 +47,7 @@ clean: ../scripts \ ../selfdrive/modeld \ ../selfdrive/debug \ - $(shell find .. -type d -name "*test*") + $(shell find .. -type d -name "*test* -not -path "**.venv**" \") @echo "Building html files..." @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(DOCSBUILDDIR)" $(SPHINXOPTS) $(O) From abe4780fdb179ef29fc503894dcb382396aaff07 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 20 Dec 2023 13:56:34 -0800 Subject: [PATCH 314/724] Docs: fix old docs still being populated (#30825) * fix push * also remove cached files * fix readme * add readme * remove that wip submodule --- .github/workflows/docs.yaml | 3 ++- docs/README.md | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 docs/README.md diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index bef5f96ea4..c555eafc73 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -50,9 +50,10 @@ jobs: cd openpilot-docs git checkout --orphan tmp - git rm -rf --cached . + git rm -rf . cp -r ../build/docs/html/ docs/ + cp -r ../docs/README.md . touch docs/.nojekyll echo -n docs.comma.ai > docs/CNAME git add -f . diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..838b00e7ae --- /dev/null +++ b/docs/README.md @@ -0,0 +1,3 @@ +# openpilot-docs + +These docs are autogenerated from [this folder](https://github.com/commaai/openpilot/tree/master/docs) in the main openpilot repository. \ No newline at end of file From a6b17fb1f6063835f6e0065d9bf670fbf069de48 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 20 Dec 2023 15:10:11 -0800 Subject: [PATCH 315/724] Simulator: fix keyboard ctrl messing with pytest (#30827) fix --- tools/sim/lib/keyboard_ctrl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/sim/lib/keyboard_ctrl.py b/tools/sim/lib/keyboard_ctrl.py index f03d79c312..339f4ea6bb 100644 --- a/tools/sim/lib/keyboard_ctrl.py +++ b/tools/sim/lib/keyboard_ctrl.py @@ -15,8 +15,6 @@ ISPEED = 4 OSPEED = 5 CC = 6 -STDIN_FD = sys.stdin.fileno() - KEYBOARD_HELP = """ | key | functionality | @@ -32,6 +30,7 @@ KEYBOARD_HELP = """ def getch() -> str: + STDIN_FD = sys.stdin.fileno() old_settings = termios.tcgetattr(STDIN_FD) try: # set From cfc85f016b401017ce6003a9a36760418945f0b2 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 20 Dec 2023 15:22:48 -0800 Subject: [PATCH 316/724] Pytest: exclude teleoprtc (#30829) exclude --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a2f62f9e43..474c11e705 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" +addopts = "--ignore=openpilot/ --ignore=cereal/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" cpp_files = "test_*" cpp_harness = "selfdrive/test/cpp_harness.py" python_files = "test_*.py" From b38c580c2e687401fc819520ea038653873e9996 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 20 Dec 2023 15:51:56 -0800 Subject: [PATCH 317/724] format fingerprints: simplify sorting (#30826) * unique + sort + don't modify FW_VERSIONS * clean up * not here * self expl --- selfdrive/debug/format_fingerprints.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/selfdrive/debug/format_fingerprints.py b/selfdrive/debug/format_fingerprints.py index b3e61b1eeb..1752aa1f06 100755 --- a/selfdrive/debug/format_fingerprints.py +++ b/selfdrive/debug/format_fingerprints.py @@ -52,7 +52,7 @@ FW_VERSIONS = { {% for key, fw_versions in FW_VERSIONS[brand][car].items() %} (Ecu.{{ECU_NAME[key[0]]}}, 0x{{"%0x" | format(key[1] | int)}}, \ {% if key[2] %}0x{{"%0x" | format(key[2] | int)}}{% else %}{{key[2]}}{% endif %}): [ - {% for fw_version in fw_versions %} + {% for fw_version in (fw_versions + extra_fw_versions.get(car, {}).get(key, [])) | unique | sort %} {{fw_version}}, {% endfor %} ], @@ -65,26 +65,16 @@ FW_VERSIONS = { def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tuple, list[bytes]]] = None): - fw_versions = FW_VERSIONS.copy() - # TODO: this modifies FW_VERSIONS in place - if extra_fw_versions is not None: - for platform, ecus in extra_fw_versions.items(): - for ecu, fws in ecus.items(): - fw_versions[brand][platform][ecu] += set(fws) - set(fw_versions[brand][platform][ecu]) + extra_fw_versions = extra_fw_versions or {} fingerprints_file = os.path.join(BASEDIR, f"selfdrive/car/{brand}/fingerprints.py") with open(fingerprints_file, "r") as f: comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line] - # sort fw versions - if fw_versions[brand] is not None: - for platform in fw_versions[brand]: - for ecu in fw_versions[brand][platform]: - fw_versions[brand][platform][ecu] = sorted(fw_versions[brand][platform][ecu]) - with open(fingerprints_file, "w") as f: f.write(FINGERPRINTS_PY_TEMPLATE.render(brand=brand, comments=comments, ECU_NAME=ECU_NAME, - FINGERPRINTS=FINGERPRINTS, FW_VERSIONS=fw_versions)) + FINGERPRINTS=FINGERPRINTS, FW_VERSIONS=FW_VERSIONS, + extra_fw_versions=extra_fw_versions)) if __name__ == "__main__": From 5e6290c4a24e31428b1c1d3d4580128b77e3efeb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 21 Dec 2023 03:55:15 -0800 Subject: [PATCH 318/724] Fingerprints: add missing FW versions from users (#30833) Export fingerprints from Fingerprint stage --- selfdrive/car/toyota/fingerprints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index a04f7597b5..e7f3fe27ad 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -710,6 +710,7 @@ FW_VERSIONS = { b'\x018821F3301400\x00\x00\x00\x00', b'\x018821F6201200\x00\x00\x00\x00', b'\x018821F6201300\x00\x00\x00\x00', + b'\x018821F6201400\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', @@ -1422,6 +1423,7 @@ FW_VERSIONS = { (Ecu.engine, 0x700, None): [ b'\x01896630E36200\x00\x00\x00\x00', b'\x01896630E36300\x00\x00\x00\x00', + b'\x01896630E37100\x00\x00\x00\x00', b'\x01896630E37200\x00\x00\x00\x00', b'\x01896630E37300\x00\x00\x00\x00', b'\x01896630E41000\x00\x00\x00\x00', From 3de6b4365ba09e63c8e0453072ae25b0e05096b1 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 22 Dec 2023 14:41:26 -0500 Subject: [PATCH 319/724] Manager: Gate features behind non-release-c3 branches --- common/params.cc | 1 + selfdrive/manager/manager.py | 3 ++- system/version.py | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/common/params.cc b/common/params.cc index 871266b7cb..5318eb0c14 100644 --- a/common/params.cc +++ b/common/params.cc @@ -140,6 +140,7 @@ std::unordered_map keys = { {"IsOnroad", PERSISTENT}, {"IsRhdDetected", PERSISTENT}, {"IsReleaseBranch", CLEAR_ON_MANAGER_START}, + {"IsReleaseSPBranch", CLEAR_ON_MANAGER_START}, {"IsTakingSnapshot", CLEAR_ON_MANAGER_START}, {"IsTestedBranch", CLEAR_ON_MANAGER_START}, {"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index a84feda34b..0bcd686d70 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -23,7 +23,7 @@ from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGL from openpilot.system.swaglog import cloudlog, add_file_handler from openpilot.system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ get_normalized_origin, terms_version, training_version, \ - is_tested_branch, is_release_branch + is_tested_branch, is_release_branch, is_release_sp_branch sys.path.append(os.path.join(BASEDIR, "third_party/mapd")) @@ -146,6 +146,7 @@ def manager_init() -> None: params.put("GitRemote", get_origin(default="")) params.put_bool("IsTestedBranch", is_tested_branch()) params.put_bool("IsReleaseBranch", is_release_branch()) + params.put_bool("IsReleaseSPBranch", is_release_sp_branch()) # set dongle id reg_res = register(show_spinner=True) diff --git a/system/version.py b/system/version.py index 331895fd42..5a7ba4c790 100755 --- a/system/version.py +++ b/system/version.py @@ -8,7 +8,8 @@ from openpilot.common.basedir import BASEDIR from openpilot.system.swaglog import cloudlog RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3', 'nightly'] -TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging', 'staging-c3', 'release-c3'] +RELEASE_SP_BRANCHES = ['release-c3'] +TESTED_BRANCHES = RELEASE_BRANCHES + RELEASE_SP_BRANCHES + ['devel', 'devel-staging', 'staging-c3'] training_version: bytes = b"0.2.0" terms_version: bytes = b"2" @@ -101,6 +102,10 @@ def is_tested_branch() -> bool: def is_release_branch() -> bool: return get_short_branch() in RELEASE_BRANCHES +@cache +def is_release_sp_branch() -> bool: + return get_short_branch() in RELEASE_SP_BRANCHES + @cache def is_dirty() -> bool: origin = get_origin() From 70fd796e6d1c71edb160b31d35264ae691799da0 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 15:56:27 +0000 Subject: [PATCH 320/724] M-TSC: New implementation --- CHANGELOGS.md | 7 +- common/params.cc | 2 + .../controls/lib/longitudinal_planner.py | 8 +- .../controls/lib/turn_speed_controller.py | 351 +++++++++--------- .../offroad/sunnypilot/sunnypilot_settings.cc | 11 +- selfdrive/ui/qt/onroad.cc | 6 +- 6 files changed, 203 insertions(+), 182 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 981a98717c..fdce578946 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -15,8 +15,11 @@ sunnypilot - 0.9.6.1 (2023-xx-xx) * C3X-specific changes * Altitude (ALT.) display on Developer UI * Current street name on top of driving screen when "OSM Debug UI" is enabled - * DISABLED: Map-based Turn Speed Control (M-TSC) - * Reimplementation in near future updates +* UPDATED: Map-based Turn Speed Control (M-TSC) implementation + * Only available in "staging-c3" and "dev-c3" branches. If you are using "release-c3" branch, navigate to "Software" panel, select the desired target branch, and check for update + * Refactored implementation thanks to pfeiferj! + * Based on the new OpenStreetMap implementation + * Improved predicted curvature calculations from OpenStreetMap data * UI updates * RE-ENABLED: Navigation: Full screen support * Display the map view in full screen diff --git a/common/params.cc b/common/params.cc index 5318eb0c14..06749a9987 100644 --- a/common/params.cc +++ b/common/params.cc @@ -270,6 +270,7 @@ std::unordered_map keys = { {"MadsCruiseMain", PERSISTENT}, {"MadsIconToggle", PERSISTENT}, {"MapboxFullScreen", PERSISTENT}, + {"MapTargetVelocities", PERSISTENT}, {"Map3DBuildings", PERSISTENT}, {"MaxTimeOffroad", PERSISTENT}, {"NNFF", PERSISTENT}, @@ -282,6 +283,7 @@ std::unordered_map keys = { {"OsmLocationTitle", PERSISTENT}, {"OsmLocationUrl", PERSISTENT}, {"OsmWayTest", PERSISTENT}, + {"OsmDownloadedDate", PERSISTENT}, {"PathOffset", PERSISTENT}, {"QuietDrive", PERSISTENT}, {"RoadEdge", PERSISTENT}, diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index c030bf90b4..f8eb86325e 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -229,9 +229,7 @@ class LongitudinalPlanner: longitudinalPlanSP.events = self.events.to_msg() longitudinalPlanSP.turnSpeedControlState = self.turn_speed_controller.state - longitudinalPlanSP.turnSpeed = float(self.turn_speed_controller.speed_limit) - longitudinalPlanSP.distToTurn = float(self.turn_speed_controller.distance) - longitudinalPlanSP.turnSign = int(self.turn_speed_controller.turn_sign) + longitudinalPlanSP.turnSpeed = float(self.turn_speed_controller.v_target) longitudinalPlanSP.personality = self.personality @@ -244,7 +242,7 @@ class LongitudinalPlanner: self.vision_turn_controller.update(enabled, v_ego, v_cruise, sm) self.events = Events() self.speed_limit_controller.update(enabled, v_ego, a_ego, sm, v_cruise, self.CP, self.events) - self.turn_speed_controller.update(enabled, v_ego, a_ego, sm) + self.turn_speed_controller.update(enabled, v_ego, sm, v_cruise) # Pick solution with the lowest velocity target. v_solutions = {'cruise': v_cruise} @@ -256,7 +254,7 @@ class LongitudinalPlanner: v_solutions['limit'] = self.speed_limit_controller.speed_limit_offseted if self.turn_speed_controller.is_active: - v_solutions['turnlimit'] = self.turn_speed_controller.speed_limit + v_solutions['turnlimit'] = self.turn_speed_controller.v_target source = min(v_solutions, key=v_solutions.get) diff --git a/selfdrive/controls/lib/turn_speed_controller.py b/selfdrive/controls/lib/turn_speed_controller.py index 7d1051d314..b2818c6c96 100644 --- a/selfdrive/controls/lib/turn_speed_controller.py +++ b/selfdrive/controls/lib/turn_speed_controller.py @@ -1,56 +1,63 @@ -import numpy as np +import json +import math import time -from common.params import Params from cereal import custom -from selfdrive.controls.lib.drive_helpers import LIMIT_ADAPT_ACC, LIMIT_MIN_SPEED, LIMIT_MAX_MAP_DATA_AGE, \ - LIMIT_SPEED_OFFSET_TH, CONTROL_N, LIMIT_MIN_ACC, LIMIT_MAX_ACC -from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.common.params import Params +from openpilot.system.version import is_release_sp_branch + +R = 6373000.0 # approximate radius of earth in meters +TO_RADIANS = math.pi / 180 +TO_DEGREES = 180 / math.pi +TARGET_JERK = -0.6 # m/s^3 There's some jounce limits that are not consistent so we're fudging this some +TARGET_ACCEL = -1.2 # m/s^2 should match up with the long planner limit +TARGET_OFFSET = 1.0 # seconds - This controls how soon before the curve you reach the target velocity. It also helps + # reach the target velocity when innacuracies in the distance modeling logic would cause overshoot. + # The value is multiplied against the target velocity to determine the additional distance. This is + # done to keep the distance calculations consistent but results in the offset actually being less + # time than specified depending on how much of a speed diffrential there is between v_ego and the + # target velocity. -_ACTIVE_LIMIT_MIN_ACC = -0.5 # m/s^2 Maximum deceleration allowed while active. -_ACTIVE_LIMIT_MAX_ACC = 0.5 # m/s^2 Maximum acelration allowed while active. +def calculate_accel(t, target_jerk, a_ego): + return a_ego + target_jerk * t -_DEBUG = False +def calculate_velocity(t, target_jerk, a_ego, v_ego): + return v_ego + a_ego * t + target_jerk/2 * (t ** 2) + + +def calculate_distance(t, target_jerk, a_ego, v_ego): + return t * v_ego + a_ego/2 * (t ** 2) + target_jerk/6 * (t ** 3) + + +PARAMS_UPDATE_PERIOD = 5. TurnSpeedControlState = custom.LongitudinalPlanSP.SpeedLimitControlState -def _debug(msg): - if not _DEBUG: - return - print(msg) +# points should be in radians +# output is meters +def distance_to_point(ax, ay, bx, by): + a = math.sin((bx-ax)/2)*math.sin((bx-ax)/2) + math.cos(ax) * math.cos(bx)*math.sin((by-ay)/2)*math.sin((by-ay)/2) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)) + + return R * c # in meters -def _description_for_state(turn_speed_control_state): - if turn_speed_control_state == TurnSpeedControlState.inactive: - return 'INACTIVE' - if turn_speed_control_state == TurnSpeedControlState.tempInactive: - return 'TEMP INACTIVE' - if turn_speed_control_state == TurnSpeedControlState.adapting: - return 'ADAPTING' - if turn_speed_control_state == TurnSpeedControlState.active: - return 'ACTIVE' - - -class TurnSpeedController(): +class TurnSpeedController: def __init__(self): - self._params = Params() - self._last_params_update = 0. - self._is_enabled = self._params.get_bool("TurnSpeedControl") + self.params = Params() + self.mem_params = Params("/dev/shm/params") + self.enabled = self.params.get_bool("TurnSpeedControl") and not is_release_sp_branch() + self.last_params_update = 0 self._op_enabled = False - self._v_ego = 0. - self._a_ego = 0. - self._v_cruise_setpoint = 0. - - self._v_offset = 0. - self._speed_limit = 0. - self._speed_limit_temp_inactive = 0. - self._distance = 0. - self._turn_sign = 0 + self._gas_pressed = False self._state = TurnSpeedControlState.inactive - - self._next_speed_limit_prev = 0. + self._v_cruise = 0 + self._min_v = 0 + self.target_lat = 0.0 + self.target_lon = 0.0 + self.target_v = 0.0 @property def state(self): @@ -58,18 +65,6 @@ class TurnSpeedController(): @state.setter def state(self, value): - if value != self._state: - _debug(f'Turn Speed Controller state: {_description_for_state(value)}') - - if value == TurnSpeedControlState.adapting: - _debug('TSC: Enteriing Adapting as speed offset is below threshold') - _debug(f'_v_offset: {self._v_offset * 3.6}\nspeed_limit: {self.speed_limit * 3.6}') - _debug(f'_v_ego: {self._v_ego * 3.6}\ndistance: {self.distance}') - - if value == TurnSpeedControlState.tempInactive: - # Track the speed limit value when controller was set to temp inactive. - self._speed_limit_temp_inactive = self._speed_limit - self._state = value @property @@ -77,140 +72,156 @@ class TurnSpeedController(): return self.state > TurnSpeedControlState.tempInactive @property - def speed_limit(self): - return max(self._speed_limit, LIMIT_MIN_SPEED) if self._speed_limit > 0. else 0. + def v_target(self): + return self._min_v - @property - def distance(self): - return max(self._distance, 0.) - - @property - def turn_sign(self): - return self._turn_sign - - def _get_limit_from_map_data(self, sm): - """Provides the speed limit, distance and turn sign to it for turns based on map data. - """ - # Ignore if no live map data - sock = 'liveMapDataSP' - if sm.logMonoTime[sock] is None: - _debug('TS: No map data for turn speed limit') - return 0., 0., 0 - - # Load map_data and initialize - map_data = sm[sock] - speed_limit = 0. - - # Calculate the age of the gps fix. Ignore if too old. - gps_fix_age = time.time() - map_data.lastGpsTimestamp * 1e-3 - if gps_fix_age > LIMIT_MAX_MAP_DATA_AGE: - _debug(f'TS: Ignoring map data as is too old. Age: {gps_fix_age}') - return 0., 0., 0 - - # Load turn ahead sections info from map_data with distances corrected by gps_fix_age - distance_since_fix = self._v_ego * gps_fix_age - distances_to_sections_ahead = np.maximum(0., np.array(map_data.turnSpeedLimitsAheadDistances) - distance_since_fix) - speed_limit_in_sections_ahead = map_data.turnSpeedLimitsAhead - turn_signs_in_sections_ahead = map_data.turnSpeedLimitsAheadSigns - - # Ensure current speed limit is considered only if we are inside the section. - if map_data.turnSpeedLimitValid and self._v_ego > 0.: - speed_limit_end_time = (map_data.turnSpeedLimitEndDistance / self._v_ego) - gps_fix_age - if speed_limit_end_time > 0.: - speed_limit = map_data.turnSpeedLimit - - # When we have no ahead speed limit to consider or all are greater than current speed limit - # or car has stopped, then provide current value and reset tracking. - turn_sign = map_data.turnSpeedLimitSign if map_data.turnSpeedLimitValid else 0 - if len(speed_limit_in_sections_ahead) == 0 or self._v_ego <= 0. or \ - (speed_limit > 0 and np.amin(speed_limit_in_sections_ahead) > speed_limit): - self._next_speed_limit_prev = 0. - return speed_limit, 0., turn_sign - - # Calculated the time needed to adapt to the limits ahead and the corresponding distances. - adapt_times = (np.maximum(speed_limit_in_sections_ahead, LIMIT_MIN_SPEED) - self._v_ego) / LIMIT_ADAPT_ACC - adapt_distances = self._v_ego * adapt_times + 0.5 * LIMIT_ADAPT_ACC * adapt_times**2 - distance_gaps = distances_to_sections_ahead - adapt_distances - - # We select as next speed limit, the one that have the lowest distance gap. - next_idx = np.argmin(distance_gaps) - next_speed_limit = speed_limit_in_sections_ahead[next_idx] - distance_to_section_ahead = distances_to_sections_ahead[next_idx] - next_turn_sign = turn_signs_in_sections_ahead[next_idx] - distance_gap = distance_gaps[next_idx] - - # When we have a next_speed_limit value that has not changed from a provided next speed limit value - # in previous resolutions, we keep providing it along with the updated distance to it. - if next_speed_limit == self._next_speed_limit_prev: - return next_speed_limit, distance_to_section_ahead, next_turn_sign - - # Reset tracking - self._next_speed_limit_prev = 0. - - # When we detect we are close enough, we provide the next limit value and track it. - if distance_gap <= 0.: - self._next_speed_limit_prev = next_speed_limit - return next_speed_limit, distance_to_section_ahead, next_turn_sign - - # Otherwise we just provide the calculated speed_limit - return speed_limit, 0., turn_sign - - def _update_params(self): + def update_params(self): t = time.monotonic() - if t > self._last_params_update + 5.0: - self._is_enabled = self._params.get_bool("TurnSpeedControl") - self._last_params_update = t + if t > self.last_params_update + PARAMS_UPDATE_PERIOD: + self.enabled = self.params.get_bool("TurnSpeedControl") and not is_release_sp_branch() + self.last_params_update = t - def _update_calculations(self): - # Update current velocity offset (error) - self._v_offset = self.speed_limit - self._v_ego + def target_speed(self, v_ego, a_ego) -> float: + if not self.enabled: + return 0.0 - def _state_transition(self, sm): - # In any case, if op is disabled, or turn speed limit control is disabled - # or the reported speed limit is 0, deactivate. - if not self._op_enabled or not self._is_enabled or self.speed_limit == 0.: + lat = 0.0 + lon = 0.0 + try: + position = json.loads(self.mem_params.get("LastGPSPosition")) + lat = position["latitude"] + lon = position["longitude"] + except: return 0.0 + + try: + target_velocities = json.loads(self.mem_params.get("MapTargetVelocities")) + except: return 0.0 + + min_dist = 1000 + min_idx = 0 + distances = [] + + # find our location in the path + for i in range(len(target_velocities)): + target_velocity = target_velocities[i] + tlat = target_velocity["latitude"] + tlon = target_velocity["longitude"] + d = distance_to_point(lat * TO_RADIANS, lon * TO_RADIANS, tlat * TO_RADIANS, tlon * TO_RADIANS) + distances.append(d) + if d < min_dist: + min_dist = d + min_idx = i + + # only look at values from our current position forward + forward_points = target_velocities[min_idx:] + forward_distances = distances[min_idx:] + + # find velocities that we are within the distance we need to adjust for + valid_velocities = [] + for i in range(len(forward_points)): + target_velocity = forward_points[i] + tlat = target_velocity["latitude"] + tlon = target_velocity["longitude"] + tv = target_velocity["velocity"] + if tv > v_ego: + continue + + d = forward_distances[i] + + a_diff = (a_ego - TARGET_ACCEL) + accel_t = abs(a_diff / TARGET_JERK) + min_accel_v = calculate_velocity(accel_t, TARGET_JERK, a_ego, v_ego) + + max_d = 0 + if tv > min_accel_v: + # calculate time needed based on target jerk + a = 0.5 * TARGET_JERK + b = a_ego + c = v_ego - tv + t_a = -1 * ((b**2 - 4 * a * c) ** 0.5 + b) / 2 * a + t_b = ((b**2 - 4 * a * c) ** 0.5 - b) / 2 * a + if not isinstance(t_a, complex) and t_a > 0: + t = t_a + else: + t = t_b + if isinstance(t, complex): + continue + + max_d = max_d + calculate_distance(t, TARGET_JERK, a_ego, v_ego) + else: + t = accel_t + max_d = calculate_distance(t, TARGET_JERK, a_ego, v_ego) + + # calculate additional time needed based on target accel + t = abs((min_accel_v - tv) / TARGET_ACCEL) + max_d += calculate_distance(t, 0, TARGET_ACCEL, min_accel_v) + + if d < max_d + tv * TARGET_OFFSET: + valid_velocities.append((float(tv), tlat, tlon)) + + # Find the smallest velocity we need to adjust for + min_v = 100.0 + target_lat = 0.0 + target_lon = 0.0 + for tv, lat, lon in valid_velocities: + if tv < min_v: + min_v = tv + target_lat = lat + target_lon = lon + + if self.target_v < min_v and not (self.target_lat == 0 and self.target_lon == 0): + for i in range(len(forward_points)): + target_velocity = forward_points[i] + tlat = target_velocity["latitude"] + tlon = target_velocity["longitude"] + tv = target_velocity["velocity"] + if tv > v_ego: + continue + + if tlat == self.target_lat and tlon == self.target_lon and tv == self.target_v: + return float(self.target_v) + # not found so lets reset + self.target_v = 0.0 + self.target_lat = 0.0 + self.target_lon = 0.0 + + self.target_v = min_v + self.target_lat = target_lat + self.target_lon = target_lon + + return min_v + + def _state_transition(self): + if not self._op_enabled or not self.enabled: self.state = TurnSpeedControlState.inactive return - # In any case, we deactivate the speed limit controller temporarily - # if gas is pressed (to support gas override implementations). - if sm['carState'].gasPressed: + if self._gas_pressed: self.state = TurnSpeedControlState.tempInactive return - # inactive + # INACTIVE if self.state == TurnSpeedControlState.inactive: - # If the limit speed offset is negative (i.e. reduce speed) and lower than threshold and distanct to turn limit - # is positive (not in turn yet) we go to adapting state to reduce speed, otherwise we go directly to active - if self._v_offset < LIMIT_SPEED_OFFSET_TH and self.distance > 0.: - self.state = TurnSpeedControlState.adapting - else: + if self._v_cruise > self._min_v != 0: self.state = TurnSpeedControlState.active - # tempInactive + + # TEMP INACTIVE elif self.state == TurnSpeedControlState.tempInactive: - # if the speed limit recorded when going to temp Inactive changes - # then set to inactive, activation will happen on next cycle - if self._speed_limit != self._speed_limit_temp_inactive: - self.state = TurnSpeedControlState.inactive - # adapting - elif self.state == TurnSpeedControlState.adapting: - # Go to active once the speed offset is over threshold or the distance to turn is now 0. - if self._v_offset >= LIMIT_SPEED_OFFSET_TH or self.distance == 0.: + if self._v_cruise > self._min_v != 0: self.state = TurnSpeedControlState.active - # active + else: + self.state = TurnSpeedControlState.inactive + + # ACTIVE elif self.state == TurnSpeedControlState.active: - # Go to adapting if the speed offset goes below threshold as long as the distance to turn is still positive. - if self._v_offset < LIMIT_SPEED_OFFSET_TH and self.distance > 0.: - self.state = TurnSpeedControlState.adapting + if not (self._v_cruise > self._min_v != 0): + self.state = TurnSpeedControlState.inactive - def update(self, enabled, v_ego, a_ego, sm): - self._op_enabled = enabled - self._v_ego = v_ego - self._a_ego = a_ego + def update(self, op_enabled, v_ego, sm, v_cruise): + self.update_params() + self._op_enabled = op_enabled + self._gas_pressed = sm['carState'].gasPressed + self._v_cruise = v_cruise + self._min_v = self.target_speed(v_ego, sm['carState'].aEgo) - # Get the speed limit from Map Data - self._speed_limit, self._distance, self._turn_sign = self._get_limit_from_map_data(sm) - - self._update_params() - self._update_calculations() - self._state_transition(sm) + self._state_transition() diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc index 447c1e19bb..18a8541b2f 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc @@ -32,7 +32,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { }, { "TurnSpeedControl", - tr("Enable Map Data Turn Speed Control (M-TSC)"), + tr("Enable Map Data Turn Speed Control (M-TSC) (Beta)"), tr("Use curvature information from map data to define speed limits to take turns ahead."), "../assets/offroad/icon_blank.png", }, @@ -413,6 +413,7 @@ void SunnypilotPanel::updateToggles() { QString nnff_loaded = tr("✅ NNLC Loaded"); auto _car_model = QString::fromStdString(params.get("NNFFCarModel")); + const bool is_release_sp = params.getBool("IsReleaseSPBranch"); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { AlignedBuffer aligned_buf; @@ -445,6 +446,11 @@ void SunnypilotPanel::updateToggles() { } } + if (is_release_sp) { + params.remove("TurnSpeedControl"); + } + m_tsc->setVisible(!is_release_sp); + if (hasLongitudinalControl(CP) || custom_stock_long_param) { v_tsc->setEnabled(true); m_tsc->setEnabled(true); @@ -462,9 +468,10 @@ void SunnypilotPanel::updateToggles() { enforce_torque_lateral->refresh(); slc_toggle->refresh(); nnff_toggle->refresh(); + m_tsc->refresh(); } else { v_tsc->setEnabled(false); - m_tsc->setEnabled(false); + m_tsc->setVisible(false); // TODO: temporarily disable M-TSC until the reimplementation is in place. Remove this line to re-enable the toggle. reverse_acc->setEnabled(false); slc_toggle->setEnabled(false); slcSettings->setEnabled(false); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index b2999d050a..f648469149 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -722,7 +722,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { const int t_distance = int(lp_sp.getDistToTurn() * (s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH) / 10.0) * 10; const QString t_distance_str(QString::number(t_distance) + (s.scene.is_metric ? "m" : "f")); - showTurnSpeedLimit = tsc_speed > 0.0 && (tsc_speed < speed || s.scene.show_debug_ui); + showTurnSpeedLimit = tsc_speed > 0.0 && std::round(tsc_speed) < 224 && (tsc_speed < speed || s.scene.show_debug_ui); turnSpeedLimit = QString::number(std::nearbyint(tsc_speed)); tscSubText = t_distance > 0 ? t_distance_str : QString(""); tscActive = tscState > cereal::LongitudinalPlanSP::SpeedLimitControlState::TEMP_INACTIVE; @@ -1097,8 +1097,8 @@ void AnnotatedCameraWidget::drawTrunSpeedSign(QPainter &p, QRect rc, const QStri const QColor text_color = QColor(0, 0, 0, is_active ? 255 : 85); const int x = rc.center().x(); - const int y = rc.center().y(); - const int width = rc.width(); + const int y = 184 * 2 + UI_BORDER_SIZE + 202; + const int width = 184; const float stroke_w = 15.0; const float cS = stroke_w / 2.0 + 4.5; // half width of the stroke on the corners of the triangle From 880d0c2af186e72c748fd1470f675bc2ec3d791e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 15:59:11 +0000 Subject: [PATCH 321/724] MADS: Add delay to CRUISE MAIN and non-forward gear --- selfdrive/car/chrysler/interface.py | 1 + selfdrive/car/interfaces.py | 20 ++++++++++++++------ selfdrive/car/volkswagen/interface.py | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 0c759b6801..5c90c1f528 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -127,6 +127,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: self.CS.madsEnabled = False + self.CS.madsEnabled = self.get_sp_started_mads(ret, self.CS) if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed: if any(b.type == ButtonType.cancel for b in buttonEvents): diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index d37f3a8db0..eac1836fa9 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -40,6 +40,8 @@ TORQUE_NN_MODEL_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/lat_mode ACTIVATION_FUNCTION_NAMES = {'σ': 'sigmoid'} GAC_DICT = {1: 1, 2: 2, 3: 3} +FORWARD_GEARS = [GearShifter.drive, GearShifter.low, GearShifter.eco, + GearShifter.sport, GearShifter.manumatic, GearShifter.brake] def similarity(s1: str, s2: str) -> float: @@ -559,19 +561,25 @@ class CarInterfaceBase(ABC): return mads_enabled - def get_sp_started_mads(self, mads_enabled, cruiseState_available): + def get_sp_started_mads(self, cs_out, CS): + if not cs_out.cruiseState.available and CS.out.cruiseState.available: + self.madsEnabledInit = False + self.madsEnabledInitPrev = False + return False if not self.mads_main_toggle or self.prev_acc_mads_combo: - return mads_enabled - if not self.madsEnabledInit and mads_enabled: + return CS.mads_enabled + if not self.madsEnabledInit and CS.mads_enabled: self.madsEnabledInit = True self.last_mads_init = time.monotonic() - if self.madsEnabledInit and not self.madsEnabledInitPrev: + if cs_out.gearShifter not in FORWARD_GEARS: + self.last_mads_init = time.monotonic() + if self.madsEnabledInit and (not self.madsEnabledInitPrev or cs_out.gearShifter not in FORWARD_GEARS): if time.monotonic() < self.last_mads_init + 1.: return False self.madsEnabledInitPrev = True - return cruiseState_available + return cs_out.cruiseState.available else: - return mads_enabled + return CS.mads_enabled def get_sp_common_state(self, cs_out, CS, min_enable_speed_pcm=False, gear_allowed=True, gap_button=False): cs_out.cruiseState.enabled = CS.accEnabled if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed or min_enable_speed_pcm else \ diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 70a3caa393..240ad22b69 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -263,9 +263,9 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = True self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) ret, self.CS = self.toggle_gac(ret, self.CS, bool(self.CS.gap_dist_button), 1, 3, 3, "-") - self.CS.madsEnabled = self.get_sp_started_mads(self.CS.madsEnabled, ret.cruiseState.available) else: self.CS.madsEnabled = False + self.CS.madsEnabled = self.get_sp_started_mads(ret, self.CS) if not self.CP.pcmCruise or (self.CP.pcmCruise and self.CP.minEnableSpeed > 0) or not self.CP.pcmCruiseSpeed: if any(b.type == ButtonType.cancel for b in buttonEvents): From 81a42d9d29a72cc4df5ffbf9371521f1bc26a714 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 11:02:50 -0500 Subject: [PATCH 322/724] FCA: Fix heartbit forward logic --- selfdrive/car/chrysler/carcontroller.py | 2 +- selfdrive/car/chrysler/carstate.py | 3 +++ selfdrive/car/chrysler/chryslercan.py | 4 ++-- selfdrive/car/chrysler/interface.py | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index 8a362d6ea0..11d94309e5 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -93,7 +93,7 @@ class CarController: lkas_active = CC.latActive and CS.madsEnabled if self.frame % 10 == 0 and self.CP.carFingerprint not in RAM_CARS: - can_sends.append(chryslercan.create_lkas_heartbit(self.packer, CS.madsEnabled, CS.lkas_heartbit)) + can_sends.append(chryslercan.create_lkas_heartbit(self.packer, CS.lkas_disabled, CS.lkas_heartbit)) ram_cars = self.CP.carFingerprint in RAM_CARS diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index 905b46d813..836aea5b51 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -24,6 +24,7 @@ class CarState(CarStateBase): self.lkas_enabled = False self.prev_lkas_enabled = False self.lkas_heartbit = None + self.lkas_disabled = False self.buttonStates = BUTTON_STATES.copy() self.buttonStatesPrev = BUTTON_STATES.copy() @@ -99,6 +100,8 @@ class CarState(CarStateBase): else: self.lkas_enabled = cp.vl["TRACTION_BUTTON"]["TOGGLE_LKAS"] == 1 self.lkas_heartbit = cp_cam.vl["LKAS_HEARTBIT"] + if not self.control_initialized: + self.lkas_disabled = bool(cp_cam.vl["LKAS_HEARTBIT"]["LKAS_DISABLED"]) ret.steerFaultTemporary = cp.vl["EPS_2"]["LKAS_TEMPORARY_FAULT"] == 1 ret.steerFaultPermanent = cp.vl["EPS_2"]["LKAS_STATE"] == 4 diff --git a/selfdrive/car/chrysler/chryslercan.py b/selfdrive/car/chrysler/chryslercan.py index 80bf2f3591..4967506155 100644 --- a/selfdrive/car/chrysler/chryslercan.py +++ b/selfdrive/car/chrysler/chryslercan.py @@ -81,8 +81,8 @@ def create_cruise_buttons(packer, frame, bus, CP, cruise_buttons_msg=None, butto return packer.make_can_msg("CRUISE_BUTTONS", bus, values) -def create_lkas_heartbit(packer, mads_enabled, lkas_heartbit): +def create_lkas_heartbit(packer, lkas_disabled, lkas_heartbit): # LKAS_HEARTBIT (697) LKAS heartbeat values = lkas_heartbit.copy() # forward what we parsed - values["LKAS_DISABLED"] = 0 if mads_enabled else 1 + values["LKAS_DISABLED"] = 1 if lkas_disabled else 0 return packer.make_can_msg("LKAS_HEARTBIT", 0, values) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 5c90c1f528..67c0f56a29 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -124,6 +124,7 @@ class CarInterface(CarInterfaceBase): self.CS.madsEnabled = True if self.CS.prev_lkas_enabled != 1 and self.CS.lkas_enabled == 1: self.CS.madsEnabled = not self.CS.madsEnabled + self.CS.lkas_disabled = not self.CS.lkas_disabled self.CS.madsEnabled = self.get_acc_mads(ret.cruiseState.enabled, self.CS.accEnabled, self.CS.madsEnabled) else: self.CS.madsEnabled = False From d1efa4489c13818ad4d26a98d33b0ece84b86866 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 16:09:55 +0000 Subject: [PATCH 323/724] FCA: support steer to 0 with White Panda Mod on certain models --- selfdrive/car/chrysler/carcontroller.py | 5 ++++- selfdrive/car/chrysler/interface.py | 4 ++++ selfdrive/car/chrysler/values.py | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index 11d94309e5..b3efe0f419 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -5,7 +5,8 @@ from openpilot.common.params import Params, put_bool_nonblocking from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car import apply_meas_steer_torque_limits from openpilot.selfdrive.car.chrysler import chryslercan -from openpilot.selfdrive.car.chrysler.values import RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags +from openpilot.selfdrive.car.chrysler.values import RAM_CARS, RAM_DT, CarControllerParams, ChryslerFlags, ChryslerFlagsSP +from openpilot.selfdrive.car.interfaces import FORWARD_GEARS from openpilot.selfdrive.controls.lib.drive_helpers import FCA_V_CRUISE_MIN BUTTONS_STATES = ["accelCruise", "decelCruise", "cancel", "resumeCruise"] @@ -151,6 +152,8 @@ class CarController: lkas_control_bit = True if (self.CP.minEnableSpeed >= 14.5) and (CS.out.gearShifter != 2): lkas_control_bit = False + elif self.CP.spFlags & ChryslerFlagsSP.SP_WP_S20: + lkas_control_bit = CC.latActive and CS.out.gearShifter in FORWARD_GEARS elif CS.out.vEgo > self.CP.minSteerSpeed: lkas_control_bit = True elif self.CP.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED: diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 67c0f56a29..58bf65495f 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -94,6 +94,10 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.44 ret.enableBsm = 720 in fingerprint[0] + if 0x4FF in fingerprint[0]: + ret.spFlags |= ChryslerFlagsSP.SP_WP_S20.value + ret.minSteerSpeed = 0.0 + return ret def _update(self, c): diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index e14cae4ebe..097a0126e3 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -18,6 +18,7 @@ class ChryslerFlags(IntFlag): class ChryslerFlagsSP(IntFlag): SP_RAM_HD_PARAMSD_IGNORE = 1 + SP_WP_S20 = 2 class CAR(StrEnum): From 488f3647d9340f345466ec52559c401d4947f960 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Sun, 24 Dec 2023 16:14:33 +0000 Subject: [PATCH 324/724] UI: only display waring if user chose a model --- selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc b/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc index 6a79cd363f..87db0e00d0 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc @@ -69,8 +69,8 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { const QString selectedModelName = MultiOptionDialog::getSelection(tr("Select a Driving Model"), modelNames, currentModelName, this); - // Bail if the user doesn't want to continue while on metered - if (!canContinueOnMeteredDialog()) { + // Bail if no selected model or the user doesn't want to continue while on metered + if (selectedModelName.isEmpty() || !canContinueOnMeteredDialog()) { return; } From 1c662ff310f6d3f0707afd1c1df934a72e5433db Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 16:41:39 +0000 Subject: [PATCH 325/724] Sentry: Handle unofficial device dongle id --- selfdrive/manager/manager.py | 6 ++++++ selfdrive/sentry.py | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 0bcd686d70..b8ed6db814 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -155,6 +155,12 @@ def manager_init() -> None: else: serial = params.get("HardwareSerial") raise Exception(f"Registration failed for device {serial}") + if params.get("HardwareSerial") is None: + try: + serial = HARDWARE.get_serial() + params.put("HardwareSerial", serial) + except Exception: + cloudlog.exception("Error getting serial for device") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog if not is_dirty(): diff --git a/selfdrive/sentry.py b/selfdrive/sentry.py index 43bd9101cc..7dfdf81b53 100644 --- a/selfdrive/sentry.py +++ b/selfdrive/sentry.py @@ -97,7 +97,9 @@ def get_properties() -> Tuple[str, str]: params = Params() dongle_id = params.get("DongleId", encoding='utf-8') if dongle_id in (None, UNREGISTERED_DONGLE_ID): - dongle_id = UNREGISTERED_DONGLE_ID + hardware_serial = params.get("HardwareSerial", encoding='utf-8') + hardware_serial = "" if hardware_serial is None else hardware_serial + dongle_id = UNREGISTERED_DONGLE_ID + hardware_serial gitname = params.get("GithubUsername", encoding='utf-8') if gitname is None: gitname = "" From 4778efe4f6f96248eb269ec2d65e49dc044d3960 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 16:42:23 +0000 Subject: [PATCH 326/724] ui: Feature Status: Revert "parse from cereal" --- selfdrive/ui/qt/onroad.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index f648469149..d979949584 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -729,8 +729,9 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { curveSign = lp_sp.getTurnSign(); } - longitudinalPersonality = int(lp_sp.getPersonality()); - dynamicLaneProfile = int(lateral_plan_sp.getDynamicLaneProfile()); + // TODO: Add toggle variables to cereal, and parse from cereal + longitudinalPersonality = s.scene.longitudinal_personality; + dynamicLaneProfile = s.scene.dynamic_lane_profile; mpcMode = QString::fromStdString(lp_sp.getE2eBlended()); mpcMode = (mpcMode == "blended") ? mpcMode.replace(0, 1, mpcMode[0].toUpper()) : mpcMode.toUpper(); From d54fd16ae35296b71d686c22ba025803c04a0729 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 16:43:31 +0000 Subject: [PATCH 327/724] DEC: Update logic from dragonpilot-community/dragonpilot:lp-dp-beta2 --- CHANGELOGS.md | 2 +- .../lib/dynamic_experimental_controller.py | 102 +++++++++++------- 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index fdce578946..9c387e097b 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,7 +1,7 @@ sunnypilot - 0.9.6.1 (2023-xx-xx) ======================== * UPDATED: Dynamic Experimental Control (DEC) - * Synced with dragonpilot-community/dragonpilot:beta3 commit dd4c663 + * Synced with dragonpilot-community/dragonpilot:lp-dp-beta2 commit 578d38b * UPDATED: Vision-based Turn Speed Control (V-TSC) implementation * Refactored implementation thanks to pfeiferj! * More accurate and consistent velocity calculation to achieve smoother longitudinal control in curves diff --git a/selfdrive/controls/lib/dynamic_experimental_controller.py b/selfdrive/controls/lib/dynamic_experimental_controller.py index c3e1dd31a7..aa2e239b38 100644 --- a/selfdrive/controls/lib/dynamic_experimental_controller.py +++ b/selfdrive/controls/lib/dynamic_experimental_controller.py @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # -# Version = 0.1.2 +# Version = 2023-12-13 from common.numpy_fast import interp from openpilot.selfdrive.controls.lib.lateral_planner import TRAJECTORY_SIZE @@ -38,13 +38,16 @@ SLOWNESS_PROB = 0.6 SLOWNESS_CRUISE_OFFSET = 1.05 DANGEROUS_TTC_WINDOW_SIZE = 5 -DANGEROUS_TTC = 1.55 +DANGEROUS_TTC = 2.0 HIGHWAY_CRUISE_KPH = 75 -STOP_AND_GO_FRAME = 500 +STOP_AND_GO_FRAME = 60 -MODE_SWITCH_DELAY_FRAME = 500 +SET_MODE_TIMEOUT = 10 + +MPC_FCW_WINDOW_SIZE = 5 +MPC_FCW_PROB = 0.6 class SNG_State: @@ -80,8 +83,6 @@ class DynamicExperimentalController: self._is_enabled = False self._mode = 'acc' self._mode_prev = 'acc' - self._mode_switch_allowed = True - self._mode_switch_frame = 0 self._frame = 0 self._lead_gmac = GenericMovingAverageCalculator(window_size=LEAD_WINDOW_SIZE) @@ -90,41 +91,45 @@ class DynamicExperimentalController: self._slow_down_gmac = GenericMovingAverageCalculator(window_size=SLOW_DOWN_WINDOW_SIZE) self._has_slow_down = False - self._has_slow_down_prev = False self._has_blinkers = False - self._has_blinkers_prev = False self._slowness_gmac = GenericMovingAverageCalculator(window_size=SLOWNESS_WINDOW_SIZE) self._has_slowness = False - self._has_slowness_prev = False self._has_nav_enabled = False - self._has_nav_enabled_prev = False self._dangerous_ttc_gmac = GenericMovingAverageCalculator(window_size=DANGEROUS_TTC_WINDOW_SIZE) self._has_dangerous_ttc = False - self._has_dangerous_ttc_prev = False self._v_ego_kph = 0. self._v_cruise_kph = 0. self._has_lead = False - self._has_lead_prev = False self._has_standstill = False self._has_standstill_prev = False self._sng_transit_frame = 0 self._sng_state = SNG_State.off + + self._mpc_fcw_gmac = GenericMovingAverageCalculator(window_size=MPC_FCW_WINDOW_SIZE) + self._has_mpc_fcw = False + self._mpc_fcw_crash_cnt = 0 + + self._set_mode_timeout = 0 pass - def _update(self, car_state, lead_one, md, controls_state, radar_unavailable): + def _update(self, car_state, lead_one, md, controls_state): self._v_ego_kph = car_state.vEgo * 3.6 self._v_cruise_kph = controls_state.vCruise self._has_lead = lead_one.status self._has_standstill = car_state.standstill + # fcw detection + self._mpc_fcw_gmac.add_data(self._mpc_fcw_crash_cnt > 0) + self._has_mpc_fcw = self._mpc_fcw_gmac.get_moving_average() >= MPC_FCW_PROB + # nav enable detection self._has_nav_enabled = md.navEnabled @@ -154,8 +159,9 @@ class DynamicExperimentalController: self._sng_transit_frame -= 1 # slowness detection - self._slowness_gmac.add_data(self._v_ego_kph <= (self._v_cruise_kph*SLOWNESS_CRUISE_OFFSET)) - self._has_slowness = self._slowness_gmac.get_moving_average() >= SLOWNESS_PROB + if not self._has_standstill: + self._slowness_gmac.add_data(self._v_ego_kph <= (self._v_cruise_kph*SLOWNESS_CRUISE_OFFSET)) + self._has_slowness = self._slowness_gmac.get_moving_average() >= SLOWNESS_PROB # dangerous TTC detection if not self._has_lead_filtered and self._has_lead_filtered_prev: @@ -169,86 +175,94 @@ class DynamicExperimentalController: # keep prev values self._has_standstill_prev = self._has_standstill - self._has_slowness_prev = self._has_slowness - self._has_slow_down_prev = self._has_slow_down self._has_lead_filtered_prev = self._has_lead_filtered self._frame += 1 def _blended_priority_mode(self): + # when mpc fcw crash prob is high + # use blended to slow down quickly + if self._has_mpc_fcw: + self._set_mode('blended') + return + # when blinker is on and speed is driving below highway cruise speed: blended - # we don't want it to switch mode at higher speed, blended may trigger hard brake + # we dont want it to switch mode at higher speed, blended may trigger hard brake if self._has_blinkers and self._v_ego_kph < HIGHWAY_CRUISE_KPH: - self._mode = 'blended' + self._set_mode('blended') return # when at highway cruise and SNG: blended # ensuring blended mode is used because acc is bad at catching SNG lead car - # especially those that accel very fast and then brake very hard. + # especially those who accel very fast and then brake very hard. if self._sng_state == SNG_State.going and self._v_cruise_kph >= HIGHWAY_CRUISE_KPH: - self._mode = 'blended' + self._set_mode('blended') return # when standstill: blended - # in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light. + # in case of lead car suddenly move away under traffic light, acc mode wont brake at traffic light. if self._has_standstill: - self._mode = 'blended' + self._set_mode('blended') return # when detecting slow down scenario: blended # e.g. traffic light, curve, stop sign etc. if self._has_slow_down: - self._mode = 'blended' + self._set_mode('blended') return # when detecting lead slow down: blended # use blended for higher braking capability if self._has_dangerous_ttc: - self._mode = 'blended' + self._set_mode('blended') return # car driving at speed lower than set speed: acc if self._has_slowness: - self._mode = 'acc' + self._set_mode('acc') return - self._mode = 'blended' + self._set_mode('blended') def _acc_priority_mode(self): + # when mpc fcw crash prob is high + # use blended to slow down quickly + if self._has_mpc_fcw: + self._set_mode('blended') + return + # If there is a filtered lead, the vehicle is not in standstill, and the lead vehicle's yRel meets the condition, #if self._has_lead_filtered and not self._has_standstill: - # self._mode = 'acc' + # self._set_mode('acc') # return # when blinker is on and speed is driving below highway cruise speed: blended - # we don't want it to switch mode at higher speed, blended may trigger hard brake + # we dont want it to switch mode at higher speed, blended may trigger hard brake if self._has_blinkers and self._v_ego_kph < HIGHWAY_CRUISE_KPH: - self._mode = 'blended' + self._set_mode('blended') return # when standstill: blended - # in case of lead car suddenly move away under traffic light, acc mode won't brake at traffic light. + # in case of lead car suddenly move away under traffic light, acc mode wont brake at traffic light. if self._has_standstill: - self._mode = 'blended' + self._set_mode('blended') return # when detecting slow down scenario: blended # e.g. traffic light, curve, stop sign etc. if self._has_slow_down: - self._mode = 'blended' + self._set_mode('blended') return # car driving at speed lower than set speed: acc if self._has_slowness: - self._mode = 'acc' + self._set_mode('acc') return - self._mode = 'acc' + self._set_mode('acc') def get_mpc_mode(self, radar_unavailable, car_state, lead_one, md, controls_state): if self._is_enabled: - self._update(car_state, lead_one, md, controls_state, radar_unavailable) - if self._frame > self._mode_switch_frame: - self._mode_switch_allowed = True + self._update(car_state, lead_one, md, controls_state) if radar_unavailable: self._blended_priority_mode() else: @@ -262,3 +276,15 @@ class DynamicExperimentalController: def is_enabled(self): return self._is_enabled + + def set_mpc_fcw_crash_cnt(self, crash_cnt): + self._mpc_fcw_crash_cnt = crash_cnt + + def _set_mode(self, mode): + if self._set_mode_timeout == 0: + self._mode = mode + if mode == "blended": + self._set_mode_timeout = SET_MODE_TIMEOUT + + if self._set_mode_timeout > 0: + self._set_mode_timeout -= 1 From ace9b3e4519c11d2a9f9ce131f3a30563e1567cc Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 16:44:23 +0000 Subject: [PATCH 328/724] Toyota: Fix LKAS fault at max steering angle --- selfdrive/car/toyota/carcontroller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index f94847be0e..836824939f 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -50,7 +50,7 @@ class CarController: actuators = CC.actuators hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel - lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE + lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE and abs(CS.out.steeringAngleDeg) < MAX_USER_TORQUE # *** control msgs *** can_sends = [] @@ -88,7 +88,7 @@ class CarController: # toyota can trace shows this message at 42Hz, with counter adding alternatively 1 and 2; # sending it at 100Hz seem to allow a higher rate limit, as the rate limit seems imposed # on consecutive messages - can_sends.append(toyotacan.create_steer_command(self.packer, apply_steer, apply_steer_req)) + can_sends.append(toyotacan.create_steer_command(self.packer, apply_steer, apply_steer_req and lat_active)) if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR: lta_active = lat_active and self.CP.steerControlType == SteerControlType.angle full_torque_condition = (abs(CS.out.steeringTorqueEps) < self.params.STEER_MAX and @@ -173,7 +173,7 @@ class CarController: if self.frame % 20 == 0 or send_ui: can_sends.append(toyotacan.create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, - hud_control.rightLaneDepart, CC.latActive, CS.lkas_hud, CS.madsEnabled)) + hud_control.rightLaneDepart, CC.latActive and lat_active, CS.lkas_hud, CS.madsEnabled)) if (self.frame % 100 == 0 or send_ui) and (self.CP.enableDsu or self.CP.flags & ToyotaFlags.DISABLE_RADAR.value): can_sends.append(toyotacan.create_fcw_command(self.packer, fcw_alert)) From a4edd8d68d3313b6aced6f1a6dd3da31f7e71d9e Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 17:43:47 +0000 Subject: [PATCH 329/724] Driving Model Selector v2 --- CHANGELOGS.md | 4 ++++ .../qt/offroad/sunnypilot/models_fetcher.cc | 2 +- .../ui/qt/offroad/sunnypilot/models_fetcher.h | 6 +++++ .../sunnypilot/software_settings_sp.cc | 22 +++++++++++++++---- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 9c387e097b..b1d3bf8c14 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -2,6 +2,10 @@ sunnypilot - 0.9.6.1 (2023-xx-xx) ======================== * UPDATED: Dynamic Experimental Control (DEC) * Synced with dragonpilot-community/dragonpilot:lp-dp-beta2 commit 578d38b +* UPDATED: Driving Model Selector v2 + * Driving models sort in descending order based on availability date + * Experimental/unmerged driving models are only available in "dev-c3" branch + * To select and use experimental driving models, navigate to "Software" panel, select the "dev-c3" branch, and check for update * UPDATED: Vision-based Turn Speed Control (V-TSC) implementation * Refactored implementation thanks to pfeiferj! * More accurate and consistent velocity calculation to achieve smoother longitudinal control in curves diff --git a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc b/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc index 4d3504bf96..8b323748b9 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.cc @@ -78,5 +78,5 @@ std::vector ModelsFetcher::getModelsFromURL(const QString&url) { } std::vector ModelsFetcher::getModelsFromURL() { - return getModelsFromURL("https://docs.sunnypilot.ai/models.json"); + return getModelsFromURL("https://docs.sunnypilot.ai/models_v2.json"); } diff --git a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h b/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h index 07d76169c8..1759d3195e 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h +++ b/selfdrive/ui/qt/offroad/sunnypilot/models_fetcher.h @@ -26,6 +26,8 @@ public: fullName = json["full_name"].toString(); fileName = json["file_name"].toString(); downloadUri = json["download_uri"].toString(); + index = json["index"].toString(); + environment = json["environment"].toString(); } QJsonObject toJson() const { @@ -34,6 +36,8 @@ public: json["full_name"] = fullName; json["file_name"] = fileName; json["download_uri"] = downloadUri; + json["index"] = index; + json["environment"] = environment; return json; } @@ -41,6 +45,8 @@ public: QString fullName; QString fileName; QString downloadUri; + QString index; + QString environment; }; class ModelsFetcher : public QObject { diff --git a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc b/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc index 87db0e00d0..17875dbc2e 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/software_settings_sp.cc @@ -56,12 +56,26 @@ void SoftwarePanelSP::handleCurrentModelLblBtnClicked() { checkNetwork(); const auto currentModelName = QString::fromStdString(params.get("DrivingModelName")); + const bool is_release_sp = params.getBool("IsReleaseSPBranch"); const auto models = models_fetcher.getModelsFromURL(); - QStringList modelNames; - // Collecting model names - for (const auto &model: models) { - modelNames.push_back(model.displayName); + QMap index_to_model; + + // Collecting indices with display names + for (const auto &model : models) { + if ((is_release_sp && model.environment == "release") || !is_release_sp) { + index_to_model.insert(model.index, model.displayName); + } + } + + QStringList modelNames; + QStringList indices = index_to_model.keys(); + std::sort(indices.begin(), indices.end(), [&](const QString &index1, const QString &index2) { + return index1.toInt() > index2.toInt(); + }); + + for (const QString &index : indices) { + modelNames.push_back(index_to_model[index]); } currentModelLblBtn->setEnabled(!is_onroad); From 3ac37a2f202e57b493de75650999c83234ff227a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 18:05:26 +0000 Subject: [PATCH 330/724] Speed Limit Assist --- CHANGELOGS.md | 10 +- common/params.cc | 4 + .../controls/lib/longitudinal_planner.py | 4 +- selfdrive/controls/lib/sunnypilot/common.py | 11 ++ .../lib/sunnypilot/speed_limit_controller.py | 81 +++++++----- selfdrive/manager/manager.py | 3 + selfdrive/ui/SConscript | 3 +- .../speed_limit_control_settings.cc | 31 ++--- .../sunnypilot/speed_limit_control_settings.h | 24 ---- .../sunnypilot/speed_limit_policy_settings.cc | 50 ++++++++ .../sunnypilot/speed_limit_policy_settings.h | 73 +++++++++++ .../speed_limit_warning_settings.cc | 117 ++++++++++++++++++ .../sunnypilot/speed_limit_warning_settings.h | 69 +++++++++++ .../offroad/sunnypilot/sunnypilot_settings.cc | 48 ++++++- .../offroad/sunnypilot/sunnypilot_settings.h | 6 + selfdrive/ui/qt/onroad.cc | 25 +--- selfdrive/ui/qt/onroad.h | 1 + selfdrive/ui/qt/onroad_settings.cc | 10 +- selfdrive/ui/soundd/sound.h | 2 +- selfdrive/ui/ui.cc | 3 + selfdrive/ui/ui.h | 4 + 21 files changed, 470 insertions(+), 109 deletions(-) create mode 100644 selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc create mode 100644 selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h create mode 100644 selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc create mode 100644 selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h diff --git a/CHANGELOGS.md b/CHANGELOGS.md index b1d3bf8c14..b8d330a515 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -9,6 +9,14 @@ sunnypilot - 0.9.6.1 (2023-xx-xx) * UPDATED: Vision-based Turn Speed Control (V-TSC) implementation * Refactored implementation thanks to pfeiferj! * More accurate and consistent velocity calculation to achieve smoother longitudinal control in curves +* NEW❗: Speed Limit Warning + * Display alert and/or chime to warn the driver when the cruising speed is faster than the speed limit plus the Warning Offset + * Customizable Warning Offset, independent of Speed Limit Control (SLC)'s Limit Offset +* UPDATED: Speed Limit Source Policy + * Selectable speed limit source for Speed Limit Control and Speed Limit Warning + * Applicable to: Speed Limit Control, Speed Limit Warning +* UPDATED: Speed Limit Control (SLC) + * Engage Mode: Removed "Warning Only" mode - this has been replaced by the new Speed Limit Warning sub-menu * UPDATED: OpenStreetMap (OSM) implementation * Refactored implementation thanks to pfeiferj! * Less resource impact @@ -27,7 +35,7 @@ sunnypilot - 0.9.6.1 (2023-xx-xx) * UI updates * RE-ENABLED: Navigation: Full screen support * Display the map view in full screen - * To switch back to driving view, tap on the border edge\ + * To switch back to driving view, tap on the border edge * Hyundai Bayon Non-SCC 2019 support thanks to polein78! sunnypilot - 0.9.5.2 (2023-12-07) diff --git a/common/params.cc b/common/params.cc index 06749a9987..4dc3f805dd 100644 --- a/common/params.cc +++ b/common/params.cc @@ -296,6 +296,10 @@ std::unordered_map keys = { {"SpeedLimitEngageType", PERSISTENT}, {"SpeedLimitValueOffset", PERSISTENT}, {"SpeedLimitOffsetType", PERSISTENT}, + {"SpeedLimitWarningFlash", PERSISTENT}, + {"SpeedLimitWarningType", PERSISTENT}, + {"SpeedLimitWarningValueOffset", PERSISTENT}, + {"SpeedLimitWarningOffsetType", PERSISTENT}, {"StandStillTimer", PERSISTENT}, {"StockLongToyota", PERSISTENT}, {"SubaruManualParkingBrakeSng", PERSISTENT}, diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index f8eb86325e..830a551ac2 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -78,7 +78,7 @@ class LongitudinalPlanner: self.cruise_source = 'cruise' self.vision_turn_controller = VisionTurnController(CP) - self.speed_limit_controller = SpeedLimitController() + self.speed_limit_controller = SpeedLimitController(CP) self.events = Events() self.turn_speed_controller = TurnSpeedController() self.dynamic_experimental_controller = DynamicExperimentalController() @@ -241,7 +241,7 @@ class LongitudinalPlanner: # Update controllers self.vision_turn_controller.update(enabled, v_ego, v_cruise, sm) self.events = Events() - self.speed_limit_controller.update(enabled, v_ego, a_ego, sm, v_cruise, self.CP, self.events) + self.speed_limit_controller.update(enabled, v_ego, a_ego, sm, v_cruise, self.events) self.turn_speed_controller.update(enabled, v_ego, sm, v_cruise) # Pick solution with the lowest velocity target. diff --git a/selfdrive/controls/lib/sunnypilot/common.py b/selfdrive/controls/lib/sunnypilot/common.py index 9c72d23d56..731cc413c6 100644 --- a/selfdrive/controls/lib/sunnypilot/common.py +++ b/selfdrive/controls/lib/sunnypilot/common.py @@ -16,3 +16,14 @@ class Policy(IntEnum): map_data_priority = 4 car_state_priority = 5 combined = 6 + + +class Engage(IntEnum): + auto = 0 + user_confirm = 1 + + +class OffsetType(IntEnum): + default = 0 + fixed = 1 + percentage = 2 diff --git a/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py b/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py index 47366a3c2f..4b9391f547 100644 --- a/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py +++ b/selfdrive/controls/lib/sunnypilot/speed_limit_controller.py @@ -1,13 +1,12 @@ import time -from common.numpy_fast import clip, interp -from common.conversions import Conversions as CV -from common.params import Params +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import clip, interp +from openpilot.common.params import Params from openpilot.selfdrive.controls.lib.sunnypilot import LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V, \ PARAMS_UPDATE_PERIOD, TEMP_INACTIVE_GUARD_PERIOD, EventName, SpeedLimitControlState -from openpilot.selfdrive.controls.lib.drive_helpers import LIMIT_MIN_ACC, LIMIT_MAX_ACC, LIMIT_SPEED_OFFSET_TH, \ - CONTROL_N +from openpilot.selfdrive.controls.lib.drive_helpers import LIMIT_SPEED_OFFSET_TH, CONTROL_N from openpilot.selfdrive.controls.lib.events import Events, ET -from openpilot.selfdrive.controls.lib.sunnypilot.common import Source, Policy +from openpilot.selfdrive.controls.lib.sunnypilot.common import Source, Policy, Engage, OffsetType from openpilot.selfdrive.controls.lib.sunnypilot.helpers import description_for_state, debug from openpilot.selfdrive.controls.lib.sunnypilot.speed_limit_resolver import SpeedLimitResolver from openpilot.selfdrive.modeld.constants import ModelConstants @@ -16,8 +15,9 @@ ACTIVE_STATES = (SpeedLimitControlState.active, SpeedLimitControlState.adapting) class SpeedLimitController: - def __init__(self): + def __init__(self, CP): self._params = Params() + self._CP = CP self._policy = Policy(int(self._params.get("SpeedLimitControlPolicy", encoding='utf8'))) self._resolver = SpeedLimitResolver(self._policy) self._last_params_update = 0.0 @@ -41,16 +41,21 @@ class SpeedLimitController: self._state = SpeedLimitControlState.inactive self._state_prev = SpeedLimitControlState.inactive self._gas_pressed = False + self._pcm_cruise_op_long = CP.openpilotLongitudinalControl and CP.pcmCruise self._offset_type = int(self._params.get("SpeedLimitOffsetType", encoding='utf8')) self._offset_value = float(self._params.get("SpeedLimitValueOffset", encoding='utf8')) - self._engage_type = int(self._params.get("SpeedLimitEngageType", encoding='utf8')) + self._warning_type_type = int(self._params.get("SpeedLimitWarningType", encoding='utf8')) + self._warning_offset_type = int(self._params.get("SpeedLimitWarningOffsetType", encoding='utf8')) + self._warning_offset_value = float(self._params.get("SpeedLimitWarningValueOffset", encoding='utf8')) + self._engage_type = clip(int(self._params.get("SpeedLimitEngageType", encoding='utf8')), Engage.auto, Engage.user_confirm) self._brake_pressed = False self._brake_pressed_prev = False self._current_time = 0. self._v_cruise_rounded = 0. self._v_cruise_prev_rounded = 0. self._speed_limit_offsetted_rounded = 0. + self._speed_limit_warning_offsetted_rounded = 0. self._ms_to_local = CV.MS_TO_KPH if self._is_metric else CV.MS_TO_MPH # Mapping functions to state transitions @@ -99,14 +104,24 @@ class SpeedLimitController: @property def speed_limit_offset(self): - if self._offset_type == 0: + if self._offset_type == OffsetType.default: return interp(self._speed_limit, LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V) * self._speed_limit - elif self._offset_type == 1: + elif self._offset_type == OffsetType.fixed: return self._offset_value * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) - elif self._offset_type == 2: + elif self._offset_type == OffsetType.percentage: return self._offset_value * 0.01 * self._speed_limit return 0. + @property + def speed_limit_warning_offset(self): + if self._warning_offset_type == OffsetType.default: + return interp(self._speed_limit, LIMIT_PERC_OFFSET_BP, LIMIT_PERC_OFFSET_V) * self._speed_limit + elif self._warning_offset_type == OffsetType.fixed: + return self._warning_offset_value * (CV.KPH_TO_MS if self._is_metric else CV.MPH_TO_MS) + elif self._warning_offset_type == OffsetType.percentage: + return self._warning_offset_value * 0.01 * self._speed_limit + return 0. + @property def speed_limit(self): return self._speed_limit @@ -122,18 +137,19 @@ class SpeedLimitController: def _update_v_cruise_setpoint_prev(self): self._v_cruise_setpoint_prev = self._v_cruise_setpoint - def _update_params(self, CP): + def _update_params(self): if self._current_time > self._last_params_update + PARAMS_UPDATE_PERIOD: self._is_enabled = self._params.get_bool("EnableSlc") self._offset_type = int(self._params.get("SpeedLimitOffsetType", encoding='utf8')) self._offset_value = float(self._params.get("SpeedLimitValueOffset", encoding='utf8')) + self._warning_type_type = int(self._params.get("SpeedLimitWarningType", encoding='utf8')) + self._warning_offset_type = int(self._params.get("SpeedLimitWarningOffsetType", encoding='utf8')) + self._warning_offset_value = float(self._params.get("SpeedLimitWarningValueOffset", encoding='utf8')) self._policy = Policy(int(self._params.get("SpeedLimitControlPolicy", encoding='utf8'))) self._is_metric = self._params.get_bool("IsMetric") self._ms_to_local = CV.MS_TO_KPH if self._is_metric else CV.MS_TO_MPH self._resolver.change_policy(self._policy) - pcm_cruise_op_long = CP.openpilotLongitudinalControl and CP.pcmCruise - self._engage_type = clip(int(self._params.get("SpeedLimitEngageType", encoding='utf8')), 0, 1) if pcm_cruise_op_long else \ - int(self._params.get("SpeedLimitEngageType", encoding='utf8')) + self._engage_type = Engage.auto if self._pcm_cruise_op_long else clip(int(self._params.get("SpeedLimitEngageType", encoding='utf8')), Engage.auto, Engage.user_confirm) debug(f'Updated Speed limit params. enabled: {self._is_enabled}, with offset: {self._offset_type}') self._last_params_update = self._current_time @@ -152,7 +168,7 @@ class SpeedLimitController: self._speed_limit_changed = self._speed_limit != self._speed_limit_prev self._v_cruise_setpoint_changed = self._v_cruise_setpoint != self._v_cruise_setpoint_prev self._speed_limit_prev = self._speed_limit - if self._engage_type != 2: + if self._engage_type != Engage.user_confirm: self._update_v_cruise_setpoint_prev() self._op_enabled_prev = self._op_enabled self._brake_pressed_prev = self._brake_pressed @@ -160,16 +176,17 @@ class SpeedLimitController: self._v_cruise_rounded = int(round(self._v_cruise_setpoint * self._ms_to_local)) self._v_cruise_prev_rounded = int(round(self._v_cruise_setpoint_prev * self._ms_to_local)) self._speed_limit_offsetted_rounded = int(0) if self._speed_limit == 0 else int(round((self._speed_limit + self.speed_limit_offset) * self._ms_to_local)) + self._speed_limit_warning_offsetted_rounded = int(0) if self._speed_limit == 0 else int(round((self._speed_limit + self.speed_limit_warning_offset) * self._ms_to_local)) def transition_state_from_inactive(self): """ Make state transition from inactive state """ - if self._engage_type == 2: + if self._engage_type == Engage.user_confirm: if (((self._last_op_enabled_time + 7.) >= self._current_time >= (self._last_op_enabled_time + 2.)) or self._speed_limit_changed): if self._speed_limit_changed: self._last_op_enabled_time = self._current_time - 2. # immediately prompt confirmation self.state = SpeedLimitControlState.preActive - elif self._engage_type == 1: + elif self._engage_type == Engage.auto: if self._v_offset < LIMIT_SPEED_OFFSET_TH: self.state = SpeedLimitControlState.adapting else: @@ -178,10 +195,10 @@ class SpeedLimitController: def transition_state_from_temp_inactive(self): """ Make state transition from temporary inactive state """ if self._speed_limit_changed: - if self._engage_type == 2: + if self._engage_type == Engage.user_confirm: self._last_op_enabled_time = self._current_time - 2. # immediately prompt confirmation self.state = SpeedLimitControlState.preActive - elif self._engage_type == 1: + elif self._engage_type == Engage.auto: self.state = SpeedLimitControlState.inactive def transition_state_from_pre_active(self): @@ -207,14 +224,14 @@ class SpeedLimitController: def transition_state_from_active(self): """ Make state transition from active state """ - if self._engage_type == 2: + if self._engage_type == Engage.user_confirm: if self._state_prev == SpeedLimitControlState.active: if self._v_cruise_setpoint_changed and self._v_cruise_rounded != self._speed_limit_offsetted_rounded: self.state = SpeedLimitControlState.tempInactive elif self._speed_limit_changed: self._last_op_enabled_time = self._current_time - 2. # immediately prompt confirmation self.state = SpeedLimitControlState.preActive - elif self._engage_type == 1: + elif self._engage_type == Engage.auto: if self._v_offset < LIMIT_SPEED_OFFSET_TH: self.state = SpeedLimitControlState.adapting @@ -223,22 +240,22 @@ class SpeedLimitController: # In any case, if op is disabled, or speed limit control is disabled # or the reported speed limit is 0 or gas is pressed, deactivate. - if (not self._op_enabled or not self._is_enabled or self._speed_limit == 0 or - (self._gas_pressed and self._disengage_on_accelerator)) or self._engage_type == 0: + if not self._op_enabled or not self._is_enabled or self._speed_limit == 0 or \ + (self._gas_pressed and self._disengage_on_accelerator): self.state = SpeedLimitControlState.inactive return # In any case, we deactivate the speed limit controller temporarily if the user changes the cruise speed. # Ignore if a minimum amount of time has not passed since activation. This is to prevent temp inactivations # due to controlsd logic changing cruise setpoint when going active. - if self._engage_type == 1 and self._v_cruise_setpoint_changed and \ + if self._engage_type == Engage.auto and self._v_cruise_setpoint_changed and \ self._current_time > (self._last_op_enabled_time + TEMP_INACTIVE_GUARD_PERIOD): self.state = SpeedLimitControlState.tempInactive return self.state_transition_strategy[self.state]() - if self._engage_type == 2: + if self._engage_type == Engage.user_confirm: self._update_v_cruise_setpoint_prev() def get_current_acceleration_as_target(self): @@ -257,22 +274,26 @@ class SpeedLimitController: return self._v_offset / ModelConstants.T_IDXS[CONTROL_N] def _update_events(self, events): + if self._speed_limit > 0 and self._warning_type_type == 2 and \ + self._speed_limit_warning_offsetted_rounded < int(round(self._v_ego * self._ms_to_local)): + events.add(EventName.speedLimitPreActive) + if not self.is_active: if self._state == SpeedLimitControlState.preActive and self._state_prev != SpeedLimitControlState.preActive and \ self._v_cruise_rounded != self._speed_limit_offsetted_rounded: events.add(EventName.speedLimitPreActive) else: - if self._engage_type == 2: + if self._engage_type == Engage.user_confirm: if self._state_prev == SpeedLimitControlState.preActive: events.add(EventName.speedLimitConfirmed) events.add(EventName.speedLimitActive) - elif self._engage_type == 1: + elif self._engage_type == Engage.auto: if self._state_prev not in ACTIVE_STATES: events.add(EventName.speedLimitActive) elif self._speed_limit_changed != 0: events.add(EventName.speedLimitValueChange) - def update(self, enabled, v_ego, a_ego, sm, v_cruise_setpoint, CP, events=Events()): + def update(self, enabled, v_ego, a_ego, sm, v_cruise_setpoint, events=Events()): _car_state = sm['carState'] self._op_enabled = sm['controlsState'].enabled and _car_state.cruiseState.enabled and \ not (_car_state.brakePressed and (not self._brake_pressed_prev or not _car_state.standstill)) and \ @@ -286,7 +307,7 @@ class SpeedLimitController: self._speed_limit, self._distance, self._source = self._resolver.resolve(v_ego, self.speed_limit, sm) - self._update_params(CP) + self._update_params() self._update_calculations() self._state_transition() self._update_events(events) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index b8ed6db814..f4fdb1bcfa 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -92,6 +92,9 @@ def manager_init() -> None: ("SpeedLimitEngageType", "0"), ("SpeedLimitValueOffset", "0"), ("SpeedLimitOffsetType", "0"), + ("SpeedLimitWarningType", "0"), + ("SpeedLimitWarningValueOffset", "0"), + ("SpeedLimitWarningOffsetType", "0"), ("StandStillTimer", "0"), ("StockLongToyota", "0"), ("TorqueDeadzoneDeg", "0"), diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 1c2f2235c0..d02f30d194 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -33,7 +33,8 @@ widgets_src += ["qt/offroad/sunnypilot/display_settings.cc", "qt/offroad/sunnypi "qt/offroad/sunnypilot/lane_change_settings.cc", "qt/offroad/sunnypilot/speed_limit_control_settings.cc", "qt/offroad/sunnypilot/monitoring_settings.cc", "qt/offroad/sunnypilot/osm_settings.cc", "qt/offroad/sunnypilot/custom_offsets_settings.cc", "qt/widgets/sunnypilot/drive_stats.cc", - "qt/offroad/sunnypilot/software_settings_sp.cc", "qt/offroad/sunnypilot/models_fetcher.cc"] + "qt/offroad/sunnypilot/software_settings_sp.cc", "qt/offroad/sunnypilot/models_fetcher.cc", + "qt/offroad/sunnypilot/speed_limit_warning_settings.cc", "qt/offroad/sunnypilot/speed_limit_policy_settings.cc"] qt_env['CPPDEFINES'] = [] if maps: diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc index fd56b8871d..ca69c49bf8 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.cc @@ -12,7 +12,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { ListWidget *list = new ListWidget(this, false); - std::vector speed_limit_engage_texts{tr("Warning Only"), tr("Auto"), tr("User Confirm")}; + std::vector speed_limit_engage_texts{tr("Auto"), tr("User Confirm")}; speed_limit_engage_settings = new ButtonParamControl( "SpeedLimitEngageType", tr("Engage Mode"), "", @@ -25,7 +25,7 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { std::vector speed_limit_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; speed_limit_offset_settings = new ButtonParamControl( - "SpeedLimitOffsetType", tr("Offset"), tr("Set speed limit slightly higher than actual speed limit for a more natural drive."), + "SpeedLimitOffsetType", tr("Limit Offset"), tr("Set speed limit slightly higher than actual speed limit for a more natural drive."), "../assets/offroad/icon_blank.png", speed_limit_offset_settings_texts, 380 @@ -39,18 +39,6 @@ SlcSettings::SlcSettings(QWidget* parent) : QWidget(parent) { connect(speed_limit_offset_settings, &ButtonParamControl::buttonToggled, this, &SlcSettings::updateToggles); connect(speed_limit_offset_settings, &ButtonParamControl::buttonToggled, slvo, &SpeedLimitValueOffset::refresh); - speed_limit_control_policy = new ButtonParamControl( - "SpeedLimitControlPolicy", - tr("Source Policy"), - "", - "../assets/offroad/icon_blank.png", - speed_limit_control_policy_texts, - 250 - ); - speed_limit_control_policy->showDescription(); - connect(speed_limit_control_policy, &ButtonParamControl::buttonToggled, this, &SlcSettings::updateToggles); - list->addItem(speed_limit_control_policy); - param_watcher = new ParamWatcher(this); QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { @@ -65,22 +53,22 @@ void SlcSettings::showEvent(QShowEvent *event) { } void SlcSettings::updateToggles() { - param_watcher->addParam("SpeedLimitControlPolicy"); param_watcher->addParam("SpeedLimitEngageType"); if (!isVisible()) { return; } - speed_limit_control_policy->setButton("SpeedLimitControlPolicy"); + if (QString::fromStdString(params.get("SpeedLimitEngageType")) == "2") { + params.put("SpeedLimitEngageType", "1"); + } + speed_limit_engage_settings->setButton("SpeedLimitEngageType"); auto cp_bytes = params.get("CarParamsPersistent"); auto custom_stock_long_param = params.getBool("CustomStockLong"); auto speed_limit_control = params.getBool("EnableSlc"); - speed_limit_control_policy->setDescription(slcDescriptionBuilder("SpeedLimitControlPolicy", speed_limit_control_policy_descriptions)); - if (!cp_bytes.empty()) { AlignedBuffer aligned_buf; capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); @@ -90,24 +78,21 @@ void SlcSettings::updateToggles() { if (hasLongitudinalControl(CP) || custom_stock_long_param) { speed_limit_offset_settings->setEnabled(speed_limit_control); - speed_limit_control_policy->setEnabled(true); slvo->setEnabled(speed_limit_control && QString::fromStdString(params.get("SpeedLimitOffsetType")) != "0"); QString speed_limit_engage_condition_text = pcm_cruise_op_long ? "This platform defaults to Auto mode. User Confirm mode is not supported on this platform.

" : ""; - QString speed_limit_engage_condition_param = pcm_cruise_op_long ? "1" : "SpeedLimitEngageType"; + QString speed_limit_engage_condition_param = pcm_cruise_op_long ? "0" : "SpeedLimitEngageType"; if (pcm_cruise_op_long) { - speed_limit_engage_settings->setDisabledSelectedButton("2"); // "User Confirm" disabled + speed_limit_engage_settings->setDisabledSelectedButton("1"); // "User Confirm" disabled } speed_limit_engage_settings->setDescription(speed_limit_engage_condition_text + slcDescriptionBuilder(speed_limit_engage_condition_param, speed_limit_engage_descriptions)); } else { slvo->setEnabled(false); - speed_limit_control_policy->setEnabled(false); speed_limit_offset_settings->setEnabled(false); speed_limit_engage_settings->setEnabled(false); } } else { slvo->setEnabled(false); - speed_limit_control_policy->setEnabled(false); speed_limit_offset_settings->setEnabled(false); speed_limit_engage_settings->setEnabled(false); speed_limit_engage_settings->setDescription(slcDescriptionBuilder("SpeedLimitEngageType", speed_limit_engage_descriptions)); diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h index aad0db0fce..877064e577 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h +++ b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h @@ -23,8 +23,6 @@ class SlcSettings : public QWidget { public: explicit SlcSettings(QWidget* parent = nullptr); void showEvent(QShowEvent *event) override; - static const std::vector speed_limit_control_policy_texts; - static const std::vector speed_limit_control_policy_descriptions; static const std::vector speed_limit_engage_descriptions; signals: @@ -38,7 +36,6 @@ private: std::map toggles; SpeedLimitValueOffset *slvo; - ButtonParamControl *speed_limit_control_policy; ButtonParamControl *speed_limit_offset_settings; ButtonParamControl *speed_limit_engage_settings; ParamWatcher *param_watcher; @@ -69,29 +66,8 @@ private: } }; -// Add this line outside the class definition -inline const std::vector SlcSettings::speed_limit_control_policy_texts{ - tr("Nav\nOnly"), - tr("Map\nOnly"), - tr("Car\nOnly"), - tr("Nav\nFirst"), - tr("Map\nFirst"), - tr("Car\nFirst"), -}; - -inline const std::vector SlcSettings::speed_limit_control_policy_descriptions{ - tr("Select the precedence order of sources used to adapt cruise speed to road limits:"), - tr("Nav Only: Data from Mapbox active navigation only."), - tr("Map Only: Data from OpenStreetMap only."), - tr("Car Only: Data from the car's built-in sources (if available)."), - tr("Nav First: Nav -> Map -> Car"), - tr("Map First: Map -> Nav -> Car"), - tr("Car First: Car -> Nav -> Map"), -}; - inline const std::vector SlcSettings::speed_limit_engage_descriptions{ tr("Select the desired mode to set the cruising speed to the speed limit:"), - tr("Warning Only: Warn the driver when the vehicle is driven faster than the speed limit."), tr("Auto: Automatic speed adjustment on motorways based on speed limit data."), tr("User Confirm: Inform the driver to change set speed of Adaptive Cruise Control to help the driver stay within the speed limit."), }; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc new file mode 100644 index 0000000000..63840923c9 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.cc @@ -0,0 +1,50 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h" + +SpeedLimitPolicySettings::SpeedLimitPolicySettings(QWidget* parent) : QWidget(parent) { + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 20, 50, 20); + main_layout->setSpacing(20); + + // Back button + PanelBackButton* back = new PanelBackButton(); + connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); + main_layout->addWidget(back, 0, Qt::AlignLeft); + + ListWidget *list = new ListWidget(this, false); + + speed_limit_policy = new ButtonParamControl( + "SpeedLimitControlPolicy", + tr("Speed Limit Source Policy"), + "", + "../assets/offroad/icon_blank.png", + speed_limit_policy_texts, + 250 + ); + speed_limit_policy->showDescription(); + connect(speed_limit_policy, &ButtonParamControl::buttonToggled, this, &SpeedLimitPolicySettings::updateToggles); + list->addItem(speed_limit_policy); + + param_watcher = new ParamWatcher(this); + + QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { + updateToggles(); + }); + + main_layout->addWidget(new ScrollView(list, this)); +} + +void SpeedLimitPolicySettings::showEvent(QShowEvent *event) { + updateToggles(); +} + +void SpeedLimitPolicySettings::updateToggles() { + param_watcher->addParam("SpeedLimitControlPolicy"); + + if (!isVisible()) { + return; + } + + speed_limit_policy->setButton("SpeedLimitControlPolicy"); + + speed_limit_policy->setDescription(speedLimitPolicyDescriptionBuilder("SpeedLimitControlPolicy", speed_limit_policy_descriptions)); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h new file mode 100644 index 0000000000..4544d2e8ef --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h @@ -0,0 +1,73 @@ +#pragma once + +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/qt/util.h" + +class SpeedLimitPolicySettings : public QWidget { + Q_OBJECT + +public: + explicit SpeedLimitPolicySettings(QWidget* parent = nullptr); + void showEvent(QShowEvent *event) override; + static const std::vector speed_limit_policy_texts; + static const std::vector speed_limit_policy_descriptions; + +signals: + void backPress(); + +public slots: + void updateToggles(); + +private: + Params params; + + ButtonParamControl *speed_limit_policy; + ParamWatcher *param_watcher; + + QString speedLimitPolicyDescriptionBuilder(QString param, std::vector descriptions) { + std::string key = param.toStdString(); + int value = atoi(params.get(key).c_str()); + value += 1; // always bump one due to first line being generic description + QString description = ""; + + for (int i = 0; i < descriptions.size(); i++) { + if (i == 1) { + description += "

"; + } + + if (i == value) { + description += "" + descriptions[i] + ""; + } else { + description += descriptions[i]; + } + + if ((i > 0) && (i < descriptions.size() - 1)) { + description += "
"; + } + } + + return description; + } +}; + +// Add this line outside the class definition +inline const std::vector SpeedLimitPolicySettings::speed_limit_policy_texts{ + tr("Nav\nOnly"), + tr("Map\nOnly"), + tr("Car\nOnly"), + tr("Nav\nFirst"), + tr("Map\nFirst"), + tr("Car\nFirst"), +}; + +inline const std::vector SpeedLimitPolicySettings::speed_limit_policy_descriptions{ + tr("Select the precedence order of sources. Utilized by Speed Limit Control and Speed Limit Warning"), + tr("Nav Only: Data from Mapbox active navigation only."), + tr("Map Only: Data from OpenStreetMap only."), + tr("Car Only: Data from the car's built-in sources (if available)."), + tr("Nav First: Nav -> Map -> Car"), + tr("Map First: Map -> Nav -> Car"), + tr("Car First: Car -> Nav -> Map"), +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc new file mode 100644 index 0000000000..5116bf38c7 --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.cc @@ -0,0 +1,117 @@ +#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h" + +SpeedLimitWarningSettings::SpeedLimitWarningSettings(QWidget* parent) : QWidget(parent) { + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 20, 50, 20); + main_layout->setSpacing(20); + + // Back button + PanelBackButton* back = new PanelBackButton(); + connect(back, &QPushButton::clicked, [=]() { emit backPress(); }); + main_layout->addWidget(back, 0, Qt::AlignLeft); + + ListWidget *list = new ListWidget(this, false); + + std::vector speed_limit_warning_texts{tr("Off"), tr("Display"), tr("Chime")}; + speed_limit_warning_settings = new ButtonParamControl( + "SpeedLimitWarningType", tr("Speed Limit Warning"), + "", + "../assets/offroad/icon_blank.png", + speed_limit_warning_texts, + 380 + ); + speed_limit_warning_settings->showDescription(); + list->addItem(speed_limit_warning_settings); + + speed_limit_warning_flash = new ParamControl( + "SpeedLimitWarningFlash", + tr("Warning with speed limit flash"), + tr("When Speed Limit Warning is enabled, the speed limit sign will alert the driver when the cruising speed is faster than then speed limit plus the offset."), + "../assets/offroad/icon_blank.png" + ); + list->addItem(speed_limit_warning_flash); + + std::vector speed_limit_warning_offset_settings_texts{tr("Default"), tr("Fixed"), tr("Percentage")}; + speed_limit_warning_offset_settings = new ButtonParamControl( + "SpeedLimitWarningOffsetType", tr("Warning Offset"), + tr("Select the desired offset to warn the driver when the vehicle is driving faster than the speed limit."), + "../assets/offroad/icon_blank.png", + speed_limit_warning_offset_settings_texts, + 380 + ); + list->addItem(speed_limit_warning_offset_settings); + + slwvo = new SpeedLimitWarningValueOffset(); + connect(slwvo, &SPOptionControl::updateLabels, slwvo, &SpeedLimitWarningValueOffset::refresh); + list->addItem(slwvo); + + connect(speed_limit_warning_settings, &ButtonParamControl::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); + + connect(speed_limit_warning_offset_settings, &ButtonParamControl::buttonToggled, this, &SpeedLimitWarningSettings::updateToggles); + connect(speed_limit_warning_offset_settings, &ButtonParamControl::buttonToggled, slwvo, &SpeedLimitWarningValueOffset::refresh); + + param_watcher = new ParamWatcher(this); + + QObject::connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) { + updateToggles(); + }); + + main_layout->addWidget(new ScrollView(list, this)); +} + +void SpeedLimitWarningSettings::showEvent(QShowEvent *event) { + updateToggles(); +} + +void SpeedLimitWarningSettings::updateToggles() { + param_watcher->addParam("SpeedLimitWarningType"); + + if (!isVisible()) { + return; + } + + auto speed_limit_warning_type_param = QString::fromStdString(params.get("SpeedLimitWarningType")); + + speed_limit_warning_settings->setDescription(speedLimitWarningDescriptionBuilder("SpeedLimitWarningType", speed_limit_warning_descriptions)); + speed_limit_warning_flash->setEnabled(speed_limit_warning_type_param != "0"); + if (speed_limit_warning_type_param == "0") { + params.remove("SpeedLimitWarningFlash"); + speed_limit_warning_flash->refresh(); + } + slwvo->setEnabled(QString::fromStdString(params.get("SpeedLimitWarningOffsetType")) != "0"); + + speed_limit_warning_offset_settings->setEnabled(speed_limit_warning_type_param != "0"); + slwvo->setEnabled(speed_limit_warning_type_param != "0"); +} + +// Speed Limit Control Custom Offset +SpeedLimitWarningValueOffset::SpeedLimitWarningValueOffset() : SPOptionControl ( + "SpeedLimitWarningValueOffset", + "", + "", + "../assets/offroad/icon_blank.png", + {-30, 30}) { + + refresh(); +} + +void SpeedLimitWarningValueOffset::refresh() { + QString option = QString::fromStdString(params.get("SpeedLimitWarningValueOffset")); + QString offset_type = QString::fromStdString(params.get("SpeedLimitWarningOffsetType")); + bool is_metric = params.getBool("IsMetric"); + QString unit = ""; + + if (option.toInt() >= 0) { + option = "+" + option; + } + + if (offset_type == "0") { + option = ""; + unit = "N/A"; + } else if (offset_type == "1") { + unit = is_metric ? " km/h" : " mph"; + } else if (offset_type == "2") { + unit = " %"; + } + setLabel(option + unit); +} diff --git a/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h new file mode 100644 index 0000000000..ce3c48ac3f --- /dev/null +++ b/selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h @@ -0,0 +1,69 @@ +#pragma once + +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/scrollview.h" +#include "selfdrive/ui/qt/util.h" + +class SpeedLimitWarningValueOffset : public SPOptionControl { + Q_OBJECT + +public: + SpeedLimitWarningValueOffset(); + + void refresh(); + +private: + Params params; +}; + +class SpeedLimitWarningSettings : public QWidget { + Q_OBJECT + +public: + explicit SpeedLimitWarningSettings(QWidget* parent = nullptr); + void showEvent(QShowEvent *event) override; + static const std::vector speed_limit_warning_descriptions; + +signals: + void backPress(); + +public slots: + void updateToggles(); + +private: + Params params; + std::map toggles; + + SpeedLimitWarningValueOffset *slwvo; + ButtonParamControl *speed_limit_warning_offset_settings; + ParamControl *speed_limit_warning_flash; + ButtonParamControl *speed_limit_warning_settings; + ParamWatcher *param_watcher; + + QString speedLimitWarningDescriptionBuilder(QString param, std::vector descriptions) { + std::string key = param.toStdString(); + int value = atoi(params.get(key).c_str()); + QString description = ""; + + for (int i = 0; i < descriptions.size(); i++) { + if (i == value) { + description += "" + descriptions[i] + ""; + } else { + description += descriptions[i]; + } + + if ((i >= 0) && (i < descriptions.size() - 1)) { + description += "
"; + } + } + + return description; + } +}; + +inline const std::vector SpeedLimitWarningSettings::speed_limit_warning_descriptions{ + tr("Off: When the cruising speed is faster than the speed limit plus the offset, there will be no warning."), + tr("Display: The speed on the speed limit sign turns red to alert the driver when the cruising speed is faster than the speed limit plus the offset."), + tr("Chime: The speed on the speed limit sign turns red and chimes to alert the driver when the cruising speed is faster than the speed limit plus the offset."), +}; diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc index 18a8541b2f..7a2b5970b6 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.cc @@ -20,7 +20,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { }, { "EnableSlc", - tr("Enable Speed Limit Control (SLC)"), + tr("Speed Limit Control (SLC)"), tr("When you engage ACC, you will be prompted to set the cruising speed to the speed limit of the road adjusted by the Offset and Source Policy specified, or the current driving speed. The maximum cruising speed will always be the MAX set speed."), "../assets/offroad/icon_blank.png", }, @@ -170,7 +170,7 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { }); // SLC. Settings - slcSettings = new SubPanelButton(tr("Customize Speed Limit Control"), 900, this); + slcSettings = new SubPanelButton(tr("Customize Speed Limit Control"), 980, this); slcSettings->setObjectName("slc_btn"); // Set margin on the outside of the button QVBoxLayout* slcSettingsLayout = new QVBoxLayout; @@ -187,6 +187,42 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { main_layout->setCurrentWidget(sunnypilotScreen); }); + // Speed Limit Warning Settings + slwSettings = new SubPanelButton(tr("Customize Warning"), 720, this); + slwSettings->setObjectName("slw_btn"); + connect(slwSettings, &QPushButton::clicked, [=]() { + scrollView->setLastScrollPosition(); + main_layout->setCurrentWidget(slw_settings); + }); + + slw_settings = new SpeedLimitWarningSettings(this); + connect(slw_settings, &SpeedLimitWarningSettings::backPress, [=]() { + scrollView->restoreScrollPosition(); + main_layout->setCurrentWidget(sunnypilotScreen); + }); + + // Speed Limit Warning Settings + slpSettings = new SubPanelButton(tr("Customize Source"), 720, this); + slpSettings->setObjectName("slp_btn"); + connect(slpSettings, &QPushButton::clicked, [=]() { + scrollView->setLastScrollPosition(); + main_layout->setCurrentWidget(slp_settings); + }); + + slp_settings = new SpeedLimitPolicySettings(this); + connect(slp_settings, &SpeedLimitPolicySettings::backPress, [=]() { + scrollView->restoreScrollPosition(); + main_layout->setCurrentWidget(sunnypilotScreen); + }); + + // Speed Limit Warning and Speed Limit Policy in the same horizontal space + QHBoxLayout *warning_policy_layout = new QHBoxLayout; + warning_policy_layout->setContentsMargins(0, 0, 0, 30); + warning_policy_layout->addWidget(slwSettings); + warning_policy_layout->addSpacing(10); + warning_policy_layout->addWidget(slpSettings); + warning_policy_layout->setAlignment(Qt::AlignLeft); + // toggle names to trigger updateToggles() when toggleFlipped std::vector updateTogglesNames{ "EnforceTorqueLateral", "CustomTorqueLateral", "LiveTorque", "TorquedOverride", "NNFF" @@ -232,10 +268,14 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { if (param == "VisionCurveLaneless") { list->addItem(laneChangeSettingsLayout); list->addItem(horizontal_line()); + + list->addItem(new LabelControl(tr("Speed Limit Assist"))); } if (param == "EnableSlc") { list->addItem(slcSettingsLayout); + + list->addItem(warning_policy_layout); list->addItem(horizontal_line()); } @@ -302,8 +342,10 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { connect(toggles["EnableSlc"], &ToggleControl::toggleFlipped, slc_settings, &SlcSettings::updateToggles); connect(toggles["EnableSlc"], &ToggleControl::toggleFlipped, [=](bool state) { slcSettings->setEnabled(state); + slcSettings->setVisible(state); }); slcSettings->setEnabled(toggles["EnableSlc"]->isToggled()); + slcSettings->setVisible(toggles["EnableSlc"]->isToggled()); connect(toggles["CustomOffsets"], &ToggleControl::toggleFlipped, [=](bool state) { customOffsetsSettings->setEnabled(state); @@ -348,6 +390,8 @@ SunnypilotPanel::SunnypilotPanel(QWidget *parent) : QFrame(parent) { main_layout->addWidget(lane_change_settings); main_layout->addWidget(custom_offsets_settings); main_layout->addWidget(slc_settings); + main_layout->addWidget(slw_settings); + main_layout->addWidget(slp_settings); setStyleSheet(R"( #back_btn { diff --git a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h index 2a0b1b4734..1966eb2961 100644 --- a/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h +++ b/selfdrive/ui/qt/offroad/sunnypilot/sunnypilot_settings.h @@ -5,6 +5,8 @@ #include "selfdrive/ui/qt/offroad/sunnypilot/lane_change_settings.h" #include "selfdrive/ui/qt/offroad/sunnypilot/mads_settings.h" #include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_warning_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" @@ -50,6 +52,10 @@ private: MadsSettings* mads_settings = nullptr; SubPanelButton *slcSettings = nullptr; SlcSettings* slc_settings = nullptr; + SubPanelButton *slwSettings = nullptr; + SpeedLimitWarningSettings* slw_settings = nullptr; + SubPanelButton *slpSettings = nullptr; + SpeedLimitPolicySettings* slp_settings = nullptr; LaneChangeSettings* lane_change_settings = nullptr; CustomOffsetsSettings* custom_offsets_settings = nullptr; Params params; diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index d979949584..7be867aa68 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -702,7 +702,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { const QString sl_offset_str(speed_limit_offset > 0.0 ? speed_limit_offset < 0.0 ? "-" + QString::number(std::nearbyint(std::abs(speed_limit_offset))) : "+" + QString::number(std::nearbyint(speed_limit_offset)) : ""); - const QString sl_inactive_str(sl_temp_inactive && s.scene.speed_limit_control_engage_type == 1 ? "TEMP" : ""); + const QString sl_inactive_str(sl_temp_inactive && s.scene.speed_limit_control_engage_type == 0 ? "TEMP" : ""); const QString sl_substring(sl_inactive || sl_temp_inactive || sl_pre_active ? sl_inactive_str : sl_distance > 0 ? sl_distance_str : sl_offset_str); @@ -713,7 +713,8 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { slcSubTextSize = sl_inactive || sl_temp_inactive || sl_distance > 0 ? 25.0 : 27.0; mapSourcedSpeedLimit = lp_sp.getIsMapSpeedLimit(); slcActive = !sl_inactive && !sl_temp_inactive; - overSpeedLimit = showSpeedLimit && (std::nearbyint(speed_limit_slc + speed_limit_offset) < std::nearbyint(speed)); + overSpeedLimit = showSpeedLimit && s.scene.speed_limit_warning_type != 0 && + (std::nearbyint(speed_limit_slc + s.scene.speed_limit_warning_value_offset) < std::nearbyint(speed)); plus_arrow_up_img = loadPixmap("../assets/img_plus_arrow_up", {105, 105}); minus_arrow_down_img = loadPixmap("../assets/img_minus_arrow_down", {105, 105}); @@ -1677,23 +1678,7 @@ void AnnotatedCameraWidget::drawFeatureStatusText(QPainter &p, int x, int y) { // TODO: Add toggle variables to cereal, and parse from cereal // Speed Limit Control - if (uiState()->scene.speed_limit_control_engage_type == 0) { - QColor slc_color("#ffffff"); - QRect slc_btn(x - eclipse_x_offset, y - eclipse_y_offset, w, h); - QRect slc_btn_shadow(x - eclipse_x_offset + drop_shadow_size, y - eclipse_y_offset + drop_shadow_size, w, h); - p.setPen(Qt::NoPen); - p.setBrush(shadow_color); - p.drawEllipse(slc_btn_shadow); - p.setBrush(slc_color); - p.drawEllipse(slc_btn); - QString slc_status_text; - slc_status_text.sprintf("SLC: %s\n", QString("Warn Only").toStdString().c_str()); - p.setPen(QPen(shadow_color, 2)); - p.drawText(x + drop_shadow_size, y + drop_shadow_size, slc_status_text); - p.setPen(QPen(text_color, 2)); - p.drawText(x, y, slc_status_text); - y += text_height; - } else if (longitudinal || !cp.getPcmCruiseSpeed()) { + if (longitudinal || !cp.getPcmCruiseSpeed()) { drawFeatureStatusElement(int(slcState), feature_text.slc_list_text, feature_color.slc_list_color, uiState()->scene.speed_limit_control_enabled, "OFF", "SLC"); } } @@ -1721,7 +1706,7 @@ void AnnotatedCameraWidget::speedLimitWarning(QPainter &p, QRect sign_rect, cons } // current speed over speed limit - else if (overSpeedLimit && uiState()->scene.speed_limit_control_engage_type == 0) { + else if (overSpeedLimit && uiState()->scene.speed_limit_warning_flash) { speed_limit_frame++; speedLimitSignPulse(speed_limit_frame); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index a64700fd6d..091a47da88 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -181,6 +181,7 @@ private: bool showSpeedLimit = false; float speedLimitSLC; float speedLimitSLCOffset; + float speedLimitWarningOffset; QString slcSubText; float slcSubTextSize = 0.0; bool overSpeedLimit; diff --git a/selfdrive/ui/qt/onroad_settings.cc b/selfdrive/ui/qt/onroad_settings.cc index 4efd5d6c48..2a1b1a8ec6 100644 --- a/selfdrive/ui/qt/onroad_settings.cc +++ b/selfdrive/ui/qt/onroad_settings.cc @@ -7,7 +7,7 @@ #include "common/util.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_control_settings.h" +#include "selfdrive/ui/qt/offroad/sunnypilot/speed_limit_policy_settings.h" OnroadSettings::OnroadSettings(bool closeable, QWidget *parent) : QFrame(parent) { setContentsMargins(0, 0, 0, 0); @@ -147,7 +147,7 @@ void OnroadSettings::changeSpeedLimitControl() { UIScene &scene = uiState()->scene; const auto cp = (*uiState()->sm)["carParams"].getCarParams(); bool can_change = hasLongitudinalControl(cp) || !cp.getPcmCruiseSpeed(); - int max_policy = SlcSettings::speed_limit_control_policy_texts.size() - 1; + int max_policy = SpeedLimitPolicySettings::speed_limit_policy_texts.size() - 1; if (can_change) { if (!scene.speed_limit_control_enabled) { @@ -336,12 +336,12 @@ void OptionWidget::updateSpeedLimitControl(QString param) { auto subtitle_text = "Speed Limit Control"; std::string icon_color; // Declare icon_color here - // Define the speed_limit_control_policy_texts - const auto& speed_limit_control_policy_texts = SlcSettings::speed_limit_control_policy_texts; + // Define the speed_limit_policy_texts + const auto& speed_limit_policy_texts = SpeedLimitPolicySettings::speed_limit_policy_texts; auto enable_slc_status = atoi(params.get(param.toStdString()).c_str()); auto speed_limit_control_policy_status = atoi(params.get("SpeedLimitControlPolicy").c_str()); - auto title_text = QString(speed_limit_control_policy_texts.at(speed_limit_control_policy_status)).replace("\n", " "); + auto title_text = QString(speed_limit_policy_texts.at(speed_limit_control_policy_status)).replace("\n", " "); std::map color_map = { {0, "#1f77b4"}, // Muted blue for "Nav Only" diff --git a/selfdrive/ui/soundd/sound.h b/selfdrive/ui/soundd/sound.h index 82f2c2253e..3b4fb8db52 100644 --- a/selfdrive/ui/soundd/sound.h +++ b/selfdrive/ui/soundd/sound.h @@ -19,7 +19,7 @@ const std::tuple sound_list[] = { {AudibleAlert::REFUSE, "refuse.wav", 0, MAX_VOLUME}, {AudibleAlert::PROMPT, "prompt.wav", 0, MAX_VOLUME}, - {AudibleAlert::PROMPT_SINGLE_LOW, "prompt_single_low.wav", 0, MAX_VOLUME}, + {AudibleAlert::PROMPT_SINGLE_LOW, "prompt_single_low.wav", QSoundEffect::Infinite, MAX_VOLUME}, {AudibleAlert::PROMPT_SINGLE_HIGH, "prompt_single_high.wav", 0, MAX_VOLUME}, {AudibleAlert::PROMPT_REPEAT, "prompt.wav", QSoundEffect::Infinite, MAX_VOLUME}, {AudibleAlert::PROMPT_STARTING, "prompt.wav", 0, MAX_VOLUME}, diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index d688954d34..ed08f6ee7e 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -254,6 +254,9 @@ void ui_update_params(UIState *s) { s->scene.torqued_override = params.getBool("TorquedOverride"); s->scene.speed_limit_control_engage_type = std::atoi(params.get("SpeedLimitEngageType").c_str()); s->scene.mapbox_fullscreen = params.getBool("MapboxFullScreen"); + s->scene.speed_limit_warning_flash = params.getBool("SpeedLimitWarningFlash"); + s->scene.speed_limit_warning_type = std::atoi(params.get("SpeedLimitWarningType").c_str()); + s->scene.speed_limit_warning_value_offset = std::atoi(params.get("SpeedLimitWarningValueOffset").c_str()); // Handle Onroad Screen Off params if (s->scene.onroadScreenOff > 0) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 3f7bbcb5f8..f32a9741cb 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -245,6 +245,10 @@ typedef struct UIScene { int speed_limit_control_engage_type; bool mapbox_fullscreen; + + bool speed_limit_warning_flash; + int speed_limit_warning_type; + int speed_limit_warning_value_offset; } UIScene; class UIState : public QObject { From a6cf11ef39c2bbac7f4913247313fc9965044c5c Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 13:16:35 -0500 Subject: [PATCH 331/724] lower to 0.9.5.3 --- CHANGELOGS.md | 2 +- common/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 25516c4b6d..8018db8ecb 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,4 +1,4 @@ -sunnypilot - 0.9.6.1 (2023-xx-xx) +sunnypilot - 0.9.5.3 (2023-xx-xx) ======================== sunnypilot - 0.9.5.2 (2023-12-07) diff --git a/common/version.h b/common/version.h index dce92e0801..e2d845c5e8 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.6.1" +#define COMMA_VERSION "0.9.5.3" From ce4235f8ec1f98c61c257aefae18a3db5574737d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 13:29:37 -0500 Subject: [PATCH 332/724] Update CHANGELOGS.md --- CHANGELOGS.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOGS.md b/CHANGELOGS.md index 8018db8ecb..1199879492 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,5 +1,42 @@ -sunnypilot - 0.9.5.3 (2023-xx-xx) +sunnypilot - 0.9.5.3 (2023-12-24) ======================== +* UPDATED: Dynamic Experimental Control (DEC) + * Synced with dragonpilot-community/dragonpilot:lp-dp-beta2 commit 578d38b +* UPDATED: Driving Model Selector v2 + * Driving models sort in descending order based on availability date + * Experimental/unmerged driving models are only available in "dev-c3" branch + * To select and use experimental driving models, navigate to "Software" panel, select the "dev-c3" branch, and check for update +* UPDATED: Vision-based Turn Speed Control (V-TSC) implementation + * Refactored implementation thanks to pfeiferj! + * More accurate and consistent velocity calculation to achieve smoother longitudinal control in curves +* NEW❗: Speed Limit Warning + * Display alert and/or chime to warn the driver when the cruising speed is faster than the speed limit plus the Warning Offset + * Customizable Warning Offset, independent of Speed Limit Control (SLC)'s Limit Offset +* UPDATED: Speed Limit Source Policy + * Selectable speed limit source for Speed Limit Control and Speed Limit Warning + * Applicable to: Speed Limit Control, Speed Limit Warning +* UPDATED: Speed Limit Control (SLC) + * Engage Mode: Removed "Warning Only" mode - this has been replaced by the new Speed Limit Warning sub-menu +* UPDATED: OpenStreetMap (OSM) implementation + * Refactored implementation thanks to pfeiferj! + * Less resource impact + * Significantly smaller sizes with databases + * All regions are available to download + * Weekly map updates thanks to pfeiferj! + * Increased the font size of the road name + * C3X-specific changes + * Altitude (ALT.) display on Developer UI + * Current street name on top of driving screen when "OSM Debug UI" is enabled +* UPDATED: Map-based Turn Speed Control (M-TSC) implementation + * Only available in "staging-c3" and "dev-c3" branches. If you are using "release-c3" branch, navigate to "Software" panel, select the desired target branch, and check for update + * Refactored implementation thanks to pfeiferj! + * Based on the new OpenStreetMap implementation + * Improved predicted curvature calculations from OpenStreetMap data +* UI updates + * RE-ENABLED: Navigation: Full screen support + * Display the map view in full screen + * To switch back to driving view, tap on the border edge +* Hyundai Bayon Non-SCC 2019 support thanks to polein78! sunnypilot - 0.9.5.2 (2023-12-07) ======================== From 1b49d24db04b73252f0a96b485be3161372b8000 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 24 Dec 2023 14:35:32 -0500 Subject: [PATCH 333/724] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 467e0e2ca0..3f428c0a04 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 467e0e2ca06ee746103e6de6f26ab18b01f928e3 +Subproject commit 3f428c0a046562d4a360e6a26baad193de7cd446 From 519a3bf52c0928dace9db25fe15f6e4b5533c0da Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 25 Dec 2023 20:08:53 -0500 Subject: [PATCH 334/724] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 3f428c0a04..a8ce2099cf 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 3f428c0a046562d4a360e6a26baad193de7cd446 +Subproject commit a8ce2099cf5da700ef6139345525319a6f6426cc From 71adace06fc9fa6ea9bcfe3637929efef14399ed Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 26 Dec 2023 02:00:59 +0000 Subject: [PATCH 335/724] MADS: Only allow altButton1 trigger if enabled --- selfdrive/car/interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index eac1836fa9..bbf3ac5bf5 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -690,7 +690,7 @@ class CarInterfaceBase(ABC): self.cruise_cancelled_btn = True events.add(EventName.manualLongitudinalRequired) # do disable on MADS button if ACC is disabled - if b.type == ButtonType.altButton1 and b.pressed: + if b.type == ButtonType.altButton1 and b.pressed and self.enable_mads: if not cs_out.madsEnabled: # disabled MADS if not cs_out.cruiseState.enabled: events.add(EventName.buttonCancel) From 7638572e38773cdc5ecbf804ffc733fa9dd7893f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 26 Dec 2023 01:58:40 -0800 Subject: [PATCH 336/724] Bump submodules (#30849) bump submodules Co-authored-by: jnewb1 --- body | 2 +- cereal | 2 +- opendbc | 2 +- panda | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/body b/body index cad7ef43ad..3aa61382b7 160000 --- a/body +++ b/body @@ -1 +1 @@ -Subproject commit cad7ef43ad5daf40b44d4792d47671c397cca376 +Subproject commit 3aa61382b7ea9328cab7f1a2fe1ec701dffd018f diff --git a/cereal b/cereal index 8ae5c51a65..bceb8b942d 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 8ae5c51a6515c171f3a82c9511a18d79a9e55c07 +Subproject commit bceb8b942d3e622c2476e197102950efc4fe0bfd diff --git a/opendbc b/opendbc index 93b983d49a..0cff7a8aa2 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 93b983d49a2d6d5c67e15ce7650f55e4f121485d +Subproject commit 0cff7a8aa2df3be711cab4570bb422cd8661cb8b diff --git a/panda b/panda index 6e645d2d33..114b85a649 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 6e645d2d337f0c6b245ebae9bd8a54467634f13b +Subproject commit 114b85a649341d55d6beb36d7414eda5e6d324a2 From c09b651bd735e9ba92cd509a412ba600ae73dcee Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 26 Dec 2023 12:14:34 -0500 Subject: [PATCH 337/724] Update CARS.md --- docs/CARS.md | 223 +++++++++++++++++++++++++++------------------------ 1 file changed, 120 insertions(+), 103 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index b7f083b8e8..4c2eba8fdd 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,19 +4,19 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 267 Supported Cars +# 282 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Buick|LaCrosse 2017-19[4](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade 2017[4](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade ESV 2016[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -41,16 +41,18 @@ A supported vehicle is one that just works when you install a comma device. All |Ford|Maverick 2023|Co-Pilot360 Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G70 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|G70 Non-SCC 2021|No Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G90 2017-18|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV60 (Advanced Trim) 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV60 (Performance Trim) 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (2.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (3.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV80 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV60 (Advanced Trim) 2023[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV60 (Performance Trim) 2023[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV70 (2.5T Trim) 2022-23[8](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV70 (3.5T Trim) 2022-23[8](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Genesis|GV80 2023[8](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |GMC|Acadia 2018[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Accord 4-Cylinder 2016-17|Honda Sensing|openpilot|25 mph[6](#footnotes)|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[7](#footnotes)|| |Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -58,6 +60,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Civic 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic Hatchback 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Clarity 2018-22|Honda Sensing|openpilot|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -74,18 +77,20 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Bayon Non-SCC 2021|No Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Elantra Non-SCC 2022|No Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (with HDA II) 2022-23[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (without HDA II) 2022-23[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 6 (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq 5 (with HDA II) 2022-23[8](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq 5 (without HDA II) 2022-23[8](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Ioniq 6 (with HDA II) 2023[8](#footnotes)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Electric 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -94,11 +99,12 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Kona Electric (with HDA II, Korea only) 2023[8](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Kona Non-SCC 2019|No Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Santa Cruz 2022-23[8](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -107,44 +113,47 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2022[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson 2022[8](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson 2023[8](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson Hybrid 2022-24[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival 2023-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival 2023-24[8](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival (China only) 2023[8](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (with HDA II) 2022-23[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (without HDA II) 2022-23[6](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|EV6 (Southeast Asia only) 2022-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|EV6 (with HDA II) 2022-23[8](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|EV6 (without HDA II) 2022-23[8](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Forte 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Forte Non-SCC 2019|No Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Forte Non-SCC 2021|No Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|K8 Hybrid (with HDA II) 2023[8](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro EV 2023[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro Hybrid 2023[8](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2020|All|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Seltos Non-SCC 2023-24|No Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento 2021-23[8](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento Hybrid 2021-23[8](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento Plug-in Hybrid 2022-23[8](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sportage 2023[8](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sportage Hybrid 2023[8](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -168,35 +177,41 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|UX Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lincoln|Aviator 2020-21|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-5 2022-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ram|1500 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Ascent 2019-21|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Forester 2019-21|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Škoda|Fabia 2022-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Škoda|Kamiq 2021[9,11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Škoda|Karoq 2019-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Kodiaq 2017-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Octavia 2015-19[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Octavia RS 2016[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Scala 2020-23[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Škoda|Superb 2015-22[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ram|1500 2019-23|Adaptive Cruise Control (ACC)|Stock|32 mph|1 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ram|2500 2020-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ram|3500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|36 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Subaru|Ascent 2019-21|All[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Forester 2017-18|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Forester 2019-21|All[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Legacy 2015-18|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Legacy 2020-22|All[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Outback 2015-17|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Outback 2018-19|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Outback 2020-22|All[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|XV 2018-19|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|XV 2020-21|EyeSight Driver Assistance[9](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Škoda|Fabia 2022-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[15](#footnotes)|| +|Škoda|Kamiq 2021[11,13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[15](#footnotes)|| +|Škoda|Karoq 2019-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Kodiaq 2017-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Octavia 2015-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Octavia RS 2016[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Scala 2020-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[15](#footnotes)|| +|Škoda|Superb 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -209,8 +224,8 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2021-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2018-20|All|Stock|0 mph[8](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2021-23|All|openpilot|0 mph[8](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry 2018-20|All|Stock|0 mph[10](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry 2021-23|All|openpilot|0 mph[10](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -240,41 +255,41 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat 2015-22[10](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[13](#footnotes)|| -|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat 2015-22[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[15](#footnotes)|| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[15](#footnotes)|| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[15](#footnotes)|| +|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[15](#footnotes)|| +|Volkswagen|Taos 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| ### Footnotes 1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
@@ -282,14 +297,16 @@ A supported vehicle is one that just works when you install a comma device. All 3Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
4Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
52019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-6Requires a CAN FD panda kit if not using comma 3X for this CAN FD car.
-7In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
-8openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-9Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-10Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-11Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
-12Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-13Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+6openpilot operates above 25mph for 9th Generation Accord 4CYL, 6CYL and Hybrid which don't have Low-Speed Follow.
+79th Generation model-years have ALC data over serial lines and require third party hardware to interface with openpilot. For more information, see mlocoteta's GitHub.
+8Requires a CAN FD panda kit if not using comma 3X for this CAN FD car.
+9In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.
+10openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+11Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+12Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+13Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma 3X functionality.
+14Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+15Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). From 1f9970ba3db588301f97716fa5f1e891e2324f38 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 26 Dec 2023 15:22:59 -0800 Subject: [PATCH 338/724] stricter file size limits (#30839) * move all pics to lfs * wav and rest of so * more to lfs * ignore a few * push that up --- .gitattributes | 12 ++- .pre-commit-config.yaml | 5 +- docs/_static/logo.png | Bin 1122 -> 129 bytes docs/assets/icon-star-empty.svg | 59 +-------------- docs/assets/icon-star-full.svg | 59 +-------------- docs/assets/icon-star-half.svg | 69 +----------------- docs/assets/icon-youtube.svg | 15 +--- selfdrive/assets/body/awake.gif | Bin 69319 -> 130 bytes selfdrive/assets/body/sleep.gif | Bin 400908 -> 131 bytes selfdrive/assets/icons/close.svg | 7 +- selfdrive/assets/images/button_flag.png | Bin 1611 -> 129 bytes selfdrive/assets/images/button_home.png | Bin 1501 -> 129 bytes selfdrive/assets/images/button_settings.png | Bin 957 -> 128 bytes selfdrive/assets/images/triangle.svg | 59 +-------------- selfdrive/assets/img_chffr_wheel.png | Bin 16884 -> 130 bytes selfdrive/assets/img_circled_check.svg | 7 +- selfdrive/assets/img_circled_slash.svg | 7 +- selfdrive/assets/img_continue_triangle.svg | 6 +- selfdrive/assets/img_couch.svg | 7 +- selfdrive/assets/img_driver_face.png | Bin 3769 -> 129 bytes selfdrive/assets/img_driver_face_static.png | Bin 4908 -> 129 bytes selfdrive/assets/img_experimental.svg | 13 +--- selfdrive/assets/img_experimental_grey.svg | 7 +- selfdrive/assets/img_experimental_white.svg | 7 +- selfdrive/assets/img_eye_closed.svg | 8 +- selfdrive/assets/img_eye_open.svg | 7 +- selfdrive/assets/img_map.png | Bin 31990 -> 130 bytes selfdrive/assets/img_spinner_comma.png | Bin 15713 -> 130 bytes selfdrive/assets/img_spinner_track.png | Bin 41523 -> 130 bytes .../assets/navigation/default_marker.svg | 8 +- .../assets/navigation/direction_arrive.png | Bin 1246 -> 129 bytes .../navigation/direction_arrive_left.png | Bin 1581 -> 129 bytes .../navigation/direction_arrive_right.png | Bin 1528 -> 129 bytes .../navigation/direction_arrive_straight.png | Bin 1246 -> 129 bytes .../assets/navigation/direction_close.png | Bin 865 -> 128 bytes .../assets/navigation/direction_continue.png | Bin 1063 -> 129 bytes .../navigation/direction_continue_left.png | Bin 1400 -> 129 bytes .../navigation/direction_continue_right.png | Bin 1371 -> 129 bytes .../direction_continue_slight_left.png | Bin 1334 -> 129 bytes .../direction_continue_slight_right.png | Bin 1347 -> 129 bytes .../direction_continue_straight.png | Bin 1063 -> 129 bytes .../navigation/direction_continue_uturn.png | Bin 1780 -> 129 bytes .../assets/navigation/direction_depart.png | Bin 1234 -> 129 bytes .../navigation/direction_depart_left.png | Bin 1613 -> 129 bytes .../navigation/direction_depart_right.png | Bin 1538 -> 129 bytes .../navigation/direction_depart_straight.png | Bin 1234 -> 129 bytes .../navigation/direction_end_of_road_left.png | Bin 1439 -> 129 bytes .../direction_end_of_road_right.png | Bin 1456 -> 129 bytes .../assets/navigation/direction_flag.png | Bin 658 -> 128 bytes .../assets/navigation/direction_fork.png | Bin 2015 -> 129 bytes .../assets/navigation/direction_fork_left.png | Bin 1863 -> 129 bytes .../navigation/direction_fork_right.png | Bin 1841 -> 129 bytes .../navigation/direction_fork_slight_left.png | Bin 1800 -> 129 bytes .../direction_fork_slight_right.png | Bin 1805 -> 129 bytes .../navigation/direction_fork_straight.png | Bin 2473 -> 129 bytes .../assets/navigation/direction_invalid.png | Bin 1063 -> 129 bytes .../navigation/direction_invalid_left.png | Bin 1400 -> 129 bytes .../navigation/direction_invalid_right.png | Bin 1371 -> 129 bytes .../direction_invalid_slight_left.png | Bin 1334 -> 129 bytes .../direction_invalid_slight_right.png | Bin 1347 -> 129 bytes .../navigation/direction_invalid_straight.png | Bin 1063 -> 129 bytes .../navigation/direction_invalid_uturn.png | Bin 1780 -> 129 bytes .../navigation/direction_merge_left.png | Bin 1535 -> 129 bytes .../navigation/direction_merge_right.png | Bin 1484 -> 129 bytes .../direction_merge_slight_left.png | Bin 1765 -> 129 bytes .../direction_merge_slight_right.png | Bin 1699 -> 129 bytes .../navigation/direction_merge_straight.png | Bin 113 -> 128 bytes .../navigation/direction_new_name_left.png | Bin 1400 -> 129 bytes .../navigation/direction_new_name_right.png | Bin 1371 -> 129 bytes .../direction_new_name_sharp_left.png | Bin 1549 -> 129 bytes .../direction_new_name_sharp_right.png | Bin 1552 -> 129 bytes .../direction_new_name_slight_left.png | Bin 1334 -> 129 bytes .../direction_new_name_slight_right.png | Bin 1347 -> 129 bytes .../direction_new_name_straight.png | Bin 1063 -> 129 bytes .../direction_notification_left.png | Bin 1400 -> 129 bytes .../direction_notification_right.png | Bin 1371 -> 129 bytes .../direction_notification_sharp_left.png | Bin 1546 -> 129 bytes .../direction_notification_sharp_right.png | Bin 1551 -> 129 bytes .../direction_notification_slight_left.png | Bin 1334 -> 129 bytes .../direction_notification_slight_right.png | Bin 1347 -> 129 bytes .../direction_notification_straight.png | Bin 1063 -> 129 bytes .../navigation/direction_off_ramp_left.png | Bin 1524 -> 129 bytes .../navigation/direction_off_ramp_right.png | Bin 1580 -> 129 bytes .../direction_off_ramp_slight_left.png | Bin 1478 -> 129 bytes .../direction_off_ramp_slight_right.png | Bin 1519 -> 129 bytes .../navigation/direction_on_ramp_left.png | Bin 1400 -> 129 bytes .../navigation/direction_on_ramp_right.png | Bin 1371 -> 129 bytes .../direction_on_ramp_sharp_left.png | Bin 1549 -> 129 bytes .../direction_on_ramp_sharp_right.png | Bin 1551 -> 129 bytes .../direction_on_ramp_slight_left.png | Bin 1334 -> 129 bytes .../direction_on_ramp_slight_right.png | Bin 1346 -> 129 bytes .../navigation/direction_on_ramp_straight.png | Bin 1063 -> 129 bytes .../assets/navigation/direction_rotary.png | Bin 1906 -> 129 bytes .../navigation/direction_rotary_left.png | Bin 1543 -> 129 bytes .../navigation/direction_rotary_right.png | Bin 1743 -> 129 bytes .../direction_rotary_sharp_left.png | Bin 1532 -> 129 bytes .../direction_rotary_sharp_right.png | Bin 1855 -> 129 bytes .../direction_rotary_slight_left.png | Bin 1699 -> 129 bytes .../direction_rotary_slight_right.png | Bin 1821 -> 129 bytes .../navigation/direction_rotary_straight.png | Bin 1736 -> 129 bytes .../navigation/direction_roundabout.png | Bin 1906 -> 129 bytes .../navigation/direction_roundabout_left.png | Bin 1543 -> 129 bytes .../navigation/direction_roundabout_right.png | Bin 1743 -> 129 bytes .../direction_roundabout_sharp_left.png | Bin 1532 -> 129 bytes .../direction_roundabout_sharp_right.png | Bin 1855 -> 129 bytes .../direction_roundabout_slight_left.png | Bin 1702 -> 129 bytes .../direction_roundabout_slight_right.png | Bin 1821 -> 129 bytes .../direction_roundabout_straight.png | Bin 1736 -> 129 bytes .../assets/navigation/direction_turn_left.png | Bin 1400 -> 129 bytes .../direction_turn_left_inactive.png | Bin 7221 -> 129 bytes .../navigation/direction_turn_right.png | Bin 1371 -> 129 bytes .../direction_turn_right_inactive.png | Bin 7239 -> 129 bytes .../navigation/direction_turn_sharp_left.png | Bin 1546 -> 129 bytes .../navigation/direction_turn_sharp_right.png | Bin 1551 -> 129 bytes .../navigation/direction_turn_slight_left.png | Bin 1334 -> 129 bytes .../direction_turn_slight_left_inactive.png | Bin 3696 -> 129 bytes .../direction_turn_slight_right.png | Bin 1347 -> 129 bytes .../direction_turn_slight_right_inactive.png | Bin 3743 -> 129 bytes .../navigation/direction_turn_straight.png | Bin 1063 -> 129 bytes .../direction_turn_straight_inactive.png | Bin 5452 -> 129 bytes .../navigation/direction_turn_uturn.png | Bin 1780 -> 129 bytes .../assets/navigation/direction_updown.png | Bin 1327 -> 129 bytes selfdrive/assets/navigation/home.png | Bin 6128 -> 129 bytes selfdrive/assets/navigation/home.svg | 68 +---------------- selfdrive/assets/navigation/home_inactive.png | Bin 9165 -> 129 bytes .../assets/navigation/icon_directions.svg | 4 +- .../navigation/icon_directions_outlined.svg | 4 +- selfdrive/assets/navigation/icon_favorite.svg | 4 +- selfdrive/assets/navigation/icon_home.svg | 4 +- selfdrive/assets/navigation/icon_recent.svg | 4 +- selfdrive/assets/navigation/icon_settings.svg | 4 +- selfdrive/assets/navigation/icon_work.svg | 4 +- selfdrive/assets/navigation/work.png | Bin 5570 -> 129 bytes selfdrive/assets/navigation/work.svg | 69 +----------------- selfdrive/assets/navigation/work_inactive.png | Bin 8446 -> 129 bytes selfdrive/assets/offroad/icon_calibration.png | Bin 8910 -> 129 bytes selfdrive/assets/offroad/icon_checkmark.svg | 6 +- .../assets/offroad/icon_chevron_right.png | Bin 1420 -> 129 bytes selfdrive/assets/offroad/icon_close.svg | 7 +- .../offroad/icon_disengage_on_accelerator.svg | 10 +-- selfdrive/assets/offroad/icon_lock_closed.svg | 7 +- selfdrive/assets/offroad/icon_map.png | Bin 28235 -> 130 bytes selfdrive/assets/offroad/icon_map_speed.png | Bin 29822 -> 130 bytes selfdrive/assets/offroad/icon_menu.png | Bin 635 -> 128 bytes selfdrive/assets/offroad/icon_metric.png | Bin 604 -> 128 bytes selfdrive/assets/offroad/icon_minus.png | Bin 2577 -> 129 bytes selfdrive/assets/offroad/icon_monitoring.png | Bin 58679 -> 130 bytes selfdrive/assets/offroad/icon_network.png | Bin 39872 -> 130 bytes selfdrive/assets/offroad/icon_openpilot.png | Bin 42640 -> 130 bytes selfdrive/assets/offroad/icon_plus.png | Bin 2833 -> 129 bytes selfdrive/assets/offroad/icon_road.png | Bin 6674 -> 129 bytes selfdrive/assets/offroad/icon_settings.png | Bin 13369 -> 130 bytes selfdrive/assets/offroad/icon_shell.png | Bin 42462 -> 130 bytes selfdrive/assets/offroad/icon_speed_limit.png | Bin 3321 -> 129 bytes selfdrive/assets/offroad/icon_warning.png | Bin 8002 -> 129 bytes .../offroad/icon_wifi_strength_full.svg | 9 +-- .../offroad/icon_wifi_strength_high.svg | 9 +-- .../assets/offroad/icon_wifi_strength_low.svg | 9 +-- .../offroad/icon_wifi_strength_medium.svg | 9 +-- .../assets/offroad/icon_wifi_uploading.svg | 9 +-- selfdrive/assets/sounds/disengage.wav | Bin 68628 -> 130 bytes selfdrive/assets/sounds/engage.wav | Bin 63120 -> 130 bytes selfdrive/assets/sounds/prompt.wav | Bin 144642 -> 131 bytes selfdrive/assets/sounds/prompt_distracted.wav | Bin 85662 -> 130 bytes selfdrive/assets/sounds/refuse.wav | Bin 83228 -> 130 bytes selfdrive/assets/sounds/warning_immediate.wav | Bin 68306 -> 130 bytes selfdrive/assets/sounds/warning_soft.wav | Bin 153764 -> 131 bytes selfdrive/debug/profiling/clpeak/clpeak3 | Bin 163696 -> 0 bytes .../acados/larch64/lib/libqpOASES_e.so.3.1 | Bin 223152 -> 131 bytes .../acados/x86_64/lib/libqpOASES_e.so.3.1 | Bin 262824 -> 131 bytes tools/bodyteleop/static/poster.png | Bin 8297 -> 129 bytes tools/cabana/assets/cabana-icon.png | Bin 18008 -> 130 bytes 172 files changed, 114 insertions(+), 488 deletions(-) delete mode 100755 selfdrive/debug/profiling/clpeak/clpeak3 diff --git a/.gitattributes b/.gitattributes index d00208ba84..8781a7371f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,18 +1,24 @@ * text=auto +# to move existing files into LFS: +# git add --renormalize . *.dlc filter=lfs diff=lfs merge=lfs -text *.onnx filter=lfs diff=lfs merge=lfs -text +*.svg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text + selfdrive/car/tests/test_models_segs.txt filter=lfs diff=lfs merge=lfs -text -selfdrive/assets/fonts/*.ttf filter=lfs diff=lfs merge=lfs -text -selfdrive/assets/training/*.png filter=lfs diff=lfs merge=lfs -text system/hardware/tici/updater filter=lfs diff=lfs merge=lfs -text selfdrive/ui/qt/spinner_larch64 filter=lfs diff=lfs merge=lfs -text selfdrive/ui/qt/text_larch64 filter=lfs diff=lfs merge=lfs -text third_party/**/*.a filter=lfs diff=lfs merge=lfs -text third_party/**/*.so filter=lfs diff=lfs merge=lfs -text +third_party/**/*.so.* filter=lfs diff=lfs merge=lfs -text third_party/**/*.dylib filter=lfs diff=lfs merge=lfs -text third_party/acados/*/t_renderer filter=lfs diff=lfs merge=lfs -text -third_party/bootstrap/bootstrap-icons.svg filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lrelease filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lupdate filter=lfs diff=lfs merge=lfs -text third_party/catch2/include/catch2/catch.hpp filter=lfs diff=lfs merge=lfs -text diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 342b05bfc0..9f1ba1ab97 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,8 +19,9 @@ repos: - id: check-executables-have-shebangs - id: check-shebang-scripts-are-executable - id: check-added-large-files + exclude: '(docs/CARS.md)|(poetry.lock)|(third_party/acados/include/blasfeo/include/blasfeo_d_kernel.h)' args: - - --maxkb=500 + - --maxkb=120 - --enforce-all - repo: https://github.com/codespell-project/codespell rev: v2.2.6 @@ -95,4 +96,4 @@ repos: # name: format-fingerprints # entry: selfdrive/debug/format_fingerprints.py # language: system -# types: [python] \ No newline at end of file +# types: [python] diff --git a/docs/_static/logo.png b/docs/_static/logo.png index 269956508556e48b7302dabb601c0371ae7900e6..0e7577dae1d4c4129f554ea267110a6a0815fbd1 100644 GIT binary patch literal 129 zcmWN?NfN>!5CFh?Ucm>kmhl+_!1makH&IySuWK&$qoUj5TB2~+ronwxnntNef Lq<={nW9{+-=*K8` literal 1122 zcmV-o1fBbdP)Px(97#k$RA>dwnn`FBK@^6axCDb5a7Q$P1`Y9`B5rX7MGcC2^dd+=aJjg|c=9NM zA|j|45ixE=@F0kUs30c61qCG{=pmqS2_|t#&|nfBzkf^*opEHUW_oJ+!9P{q^Xk2C zUT@Xi6B%S_(P(riLsp@3Gz}G@sb~o5K<(%gYD9JDNhA{aO`BZaAsEt^bQ0^?#7Jgetap`MnD6^;YD#Eg-+PWZWWDZ!}A- zNY~h~*fOfvAQ)9}6ABZYxc2KqCcs6pUbsHW*|LQ~5@1QV*kUyKd~Q}xfWgf!6frp4 zqEI?WI%!7+7dNm-eMBl$1%Cw=GT1uL%RDnF+HZckG3P-kj0FCEwZ%}0}c`| zShAMLs`hfU1nD-R7-`Pdi@J~|9lEa9vjyJJ4lu@7u;Sc+m8lnYrFIF(1HkpR<-@-$ z={^_`Q(_p~TU$+Fnp^^oOC(OQ+{~`hf4}j*Luz z%|UucY+|=v!|8SILlZK5x{=%G!2zXN%0&yfGx!=mD9GrCkVub1 z8ZF=sWt7lnFi@I4y)%UC!Ec*Q{Iq>ZGy&BZg30YQ%Kp1(lIV?S1^SFkOi#B?p@Oul zVLFf`W#}<7wH6##o5oMuk0km_-eps&e!ptk*lYZ>6PpYq$p+NbpVXvzsxc<9Gxj0T zFrnwVxHdQ9xjNI21e>=r&t-BI~6k+}FjfI6S)6f@W7?!F7abRVEj7Y%3!ia!i@ oPvy%9ND - - - - - image/svg+xml - - - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:3142658fa2782d266028dcab491e02ab329a2cadcf9efb1846a5f9631d64cac1 +size 1947 diff --git a/docs/assets/icon-star-full.svg b/docs/assets/icon-star-full.svg index 294db2b7f2..0e0bf52cd8 100644 --- a/docs/assets/icon-star-full.svg +++ b/docs/assets/icon-star-full.svg @@ -1,56 +1,3 @@ - - - - - - image/svg+xml - - - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:5fdcc85f3d8c9d0c2fb76b98ddd9f118c0e4a12c859a4281a04a0870d5de12c8 +size 1950 diff --git a/docs/assets/icon-star-half.svg b/docs/assets/icon-star-half.svg index ab905fddcb..11398fa6f1 100644 --- a/docs/assets/icon-star-half.svg +++ b/docs/assets/icon-star-half.svg @@ -1,66 +1,3 @@ - - - - - - image/svg+xml - - - - - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:cdf5e7ae57626f4cda35973873a4696252babbbe473913fe0aafdd8ba39d7fc3 +size 2508 diff --git a/docs/assets/icon-youtube.svg b/docs/assets/icon-youtube.svg index 4e2c9fdfa9..f738dca10a 100644 --- a/docs/assets/icon-youtube.svg +++ b/docs/assets/icon-youtube.svg @@ -1,12 +1,3 @@ - - - - - - - - - - - - \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:6cb64f9da10b818c56763a7c48347f6043da20a2a77fb14f6d60d9457c575b6b +size 1278 diff --git a/selfdrive/assets/body/awake.gif b/selfdrive/assets/body/awake.gif index 7ec67055ddbc5e27087daf7a76e16927c327464e..cc22fd9b128e32df9c3df8daef633fa81cd162f2 100644 GIT binary patch literal 130 zcmWN?OA^8$3;@u5Pr(H&{}y_iBm`kbrDL!QPp@zDE`CcNFV*He<E^Y?|^mXTq6sek+$QJ+t=WEVn04fLX0%JhOrY0ivd(7yfgJg{@ Mi}Ww6Eqj}nA2Gowr2qf` literal 69319 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

I`>;7CMlw{lX{_h!?gH$E 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 diff --git a/selfdrive/assets/body/sleep.gif b/selfdrive/assets/body/sleep.gif index 469cc803389e7b73c11173f22c2fa4dc73532891..fcc0b4ed50b379e4ba3f48c23de5add68a5e9a03 100644 GIT binary patch literal 131 zcmWN?K@!3s3;@78uiyg~Xb6Pn2z!04$fSg?lnNsnQE37^+L&A)%?Tu5l+!}&KD$b@- OnUsug)!y1eGp_OPTuSDeYvf2 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

d>UnV9${dbL2}EZz1BsCtVW@!szE) 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 diff --git a/selfdrive/assets/icons/close.svg b/selfdrive/assets/icons/close.svg index b1e6d3b867..33f68f02bc 100644 --- a/selfdrive/assets/icons/close.svg +++ b/selfdrive/assets/icons/close.svg @@ -1,4 +1,3 @@ - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:84c6b23bd3245954b86f80278511186fca4ddfa70d87c8b25e8f9fb76f9af758 +size 379 diff --git a/selfdrive/assets/images/button_flag.png b/selfdrive/assets/images/button_flag.png index cac4db6d4cdb51c4ec2d93f3a68bdb89513ab2df..745c8d576bf10635d6d961e497ae46ed8da2593a 100644 GIT binary patch literal 129 zcmWN{ITFJl3;@91uiyhQNW*3ShC>R>B*7D57kPbLZL=4BrH_wla~`s5{p|DcVp(t7 znb$JjS`I4uf@Sooq$PBIFBKd{@PSRKh$U|cJR=)R8`zAQIYj1?&}WL^!W}A_m~C`5 MQd*?{NP!1f{@8;j>;M1& literal 1611 zcmV-R2DJH!P)Px*1xZ9fRCwC$oz1EoMHGNL37O$;1VI`^LkIVGtuT+! z_zwLFE_x;6LSjXiT8N9d2r;D4WoUA9KXF2G?IQPQ>Q?uh>N(#mXZO*k&Z*PYr!q;9 zQ&3UU(9mMASR5T44vWRmuK%m4D9Fi_|H~^`(O__Fio?*bqTFGrYk@VESXk;E6G}mi zVJHkW#l&^8pvEx^DBLp7mbr=X={TXeJ&2z5#f5-^m>%48plBrN-h z$z)HQ#1v&x2$lF8o4}e$PD2VbqO&;_j#O||B2}^!lAkxhuoNbf6{)fkqmrDKlxan* zmlO%Di^t(k%;Or;iG+CaA?;(f65=d`m)#-Q(OYjUC$KWU498JHqzNXG#6wBPl<7uk zxlyqTRH()TR8q1_lnj+(CcZZMbQEa8BeWP|v4a``T+|Yse1f}}vtlD|920EW#>Ai< z9zmTU3KY(rzsYwr$@sW)u9Oq&IG%?VT^+Z)QnEzpvqV_YbAaQo!G3@9R8S#AHK4}3 z+~$M9eqV5pLm@q(L9_J(t}`ba1}^X#-|$#S_NHMt{@@DPaBzvMJYXmqgcRWi{Kz|G zL&6pA2`J9l!wxF)2i{Ivq~**amG`>SQQ!JnPeYSWQ1oDt0E&jla!z$BfMk8 zsK^N0MG8=n5$Z`=6&ayJNlvFCH=rZuxPFg{jL-|hsK^KjhXts}jaiU+Py;G*Q))5~ z9H1gM#qrV#Qbh(oSO4~2TRb(ip-$GN7DssyrZGVHPB13T* z9Y?6hP&ySEhfYH~4o;Iz9IT3rgVlmO4o0_#acH$^q1_*8BDJv_gIZrT#G%rcUvVh( zb!;4RJs=PRr$<)eVDu1G92z~=7l)M|G>t<^3P+!r^Coj7i_DRJ7@ryM@;Tq~DX(%7 znIf(D&qJ8;5qJ29C;Z9Ryu~FXimZiyu*8gO{K5eMhdiR@4K5;2WQpWY=w4`i3TqB| z$XC1~PGm=p+>bh$@!?t{9Ks8g_qfdrSt5<#&y1OIb*<6okl(q*3`rtuq<;9m4(gvc z;AK3;a+5i7L>9;##jp8ZTQ42k1vwmLw`&(xe*rMv-3+aBq~jH~|Icxr%u!VG~Uy|y@Hj>}x@Y24(g zls05;dt(o|c(fVZSmiBd$QpaNtwEez;5|Q6ZxrA+m?3Kr=XOTJ@;cw~gh19$(A!f- zRVn3)fsrn~n4eyL2Ny#Zq+EJ2{533#e)%2wshkVBhGp=Jf$s8UPh)JApK*)(3`c>G ztEwLJu%|J$%17Mi2}98!=JK?ZapZ`(&M##uL|rhMDh9psGzv@f65g#|SI(YZ3;lu? zy{J4=w$`ijqZZ+MU3s+CQCG_Fq9+pMSaualP8JOoJDPMH3$m!N+(DI&C0TSR=-4GX z3bH|Qn<$dpq%(x*-?^>0>y+gxH~1qXfYH}@QeYA3GQ|*3$oE6 zpV%JUkdti%_9oOBI=RE6iAP|9nQVx+g73i%OJN$HL6y*Y>8M0X zd@i^b8=;jmZ??fSo(ib9sWe*pqjGd|#&<2V2S002ov JPDHLkV1i2U;%5K= diff --git a/selfdrive/assets/images/button_home.png b/selfdrive/assets/images/button_home.png index 9f52faf9e2da488ab9ba4b7e11cae9fed4f9c47c..dd3f97f44bbeeaafe5054bdaa1688c7efb66ee35 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@tQr{Dq>5(rFtoA8M+qtX%V!qe;9ysN%vA1~e3I_F``y|3Ft=JvmR z(w4{5%(G^5ff~K&*%&DtPBJ5dgh-i-7f)VXVhSbHD0a8(2pvKSAm~9disV#FjT~ur MtY-gK4P-(60OYqQ3jhEB literal 1501 zcmb7^`#%#30L2$AGe$xU*{~6=#A?>OvYFW;wvmQJk;Odcc}0ah@`yZoa2efsYg%(f zT*_6;s|S}n>L!mgEiy`EcHKYWp3mo;@8|q-emvJGE{+heCKvz!K*%HlZJV6`43gbW zQ)W!mHg+;_CvX4&FHd3fEO5K;h@v^-0o)h+h1O6?4~~um0>3RV5zWM+5Vs z1I|efU^sYN|M2VU}w_^GB)I*kNJIsx%Ph4|JA9dLKFUFNCV z@#Bh}k9$+mK9hQO9)fPZd)^y zxd)r+9U4T8wQ1=4I>JCz8EN1FHBM1O+J22hY;hh%T~M~JNSb*Obg}m4g)Qhag}zh| z8;%%vTwonw0FN3dE*Gyaj>nxNLv%)3E{ZLOzPXY? zoMl&tfJ5kERY{hQg_i#Am8Bk%By!4VfJ`V+@$5K3;hpd1Xc}K4w$PZb)O3k2*%ze@ z3w%~Q^+8fc_Feem<+Tf2Bn+x=`^Oq~%!DD+$7q|{`D>r=gsLu^Wj}$+nHvMZhsf|R zd!!n{wTKi1drXOEaS)}&wHuivd6%j7p7!>9nSGiqI%0i8Z?imqXG2`O!ToNyeQ%sN zrP7dz6b3FXqWP2`vJoE+J1&ucvNB!LH?EoemiU`tLc?`{Cdn03Ty!+c!*?Y5k|0n= z-umb05~U=W(_#E-Wd^BApuN6kQgFoIrJ&JyRhICOq+qP-iCpqYz7Kf>*5?J-H&ERO z%)2#gH@>EnH2b(+v|o+GP}5=|ZbhMyvmy(J7>n+UCZ28b3@_F)CgQjrSA`@n3W2-S zo8y8@+>nP8Idz5&a}gmjLo0S3jv;q;(Rg;F=^PYqyLoo-i;Sbv~uN<98Tz8;H<&qchuRB=e=CKjQ z0&P1b_c|DIlIhVS{E+>wK!QF@XgidJkY_a=Rrtoy8v_Q|g(lqiyjQvE3%&ej|G@-o zz6zp1WLUdUOSGwr_ju*4AqWz))kWDpxLm5&eaxT(WtjG32+D3)rY=@2$TIdMnFTGftg8E~) z-Dg{PyJI_CPv1{TRs7l@_nXC`RrYyegf&G6&e4j0MO9!GIr+bpGIIu~Tmc1KEXcIV zamLd2tT?wXwGtZ3uGC(a^Uslqsl38q%>wYq_(s$&(!M`4)j zVrKCRnXD61*vWfJ>EHRW{pnyax-l>;4tju&LQZQwfu|P9LoJ(H!wRkPvx(J3XX1KI zvN*{c$<+*1vNdGw{g=H{YR@NiSB(o7yH-7)^l!)|Od0pkBuvnb;TtXYEO|Y{ypLZ^ zZ^lIPzH{<`sHDbep?@6}{<(64-aI1cdtiIB>l=^Ql$UljsWG;LprvinyMKd@nGj5m zR%WZW+Qa*=W|)qml#@(aVUOtQmj;3C%R);OLLa13T)d|fG-X^m-$hHlvl$eF1={3} zu9oD5x3IUmi`eqjd|#UtLAUn7axOl&me*wEn B)3yKr diff --git a/selfdrive/assets/images/button_settings.png b/selfdrive/assets/images/button_settings.png index e04262b887cea54cce4fd822a35965e91a53b75e..4bbfe581e6740e67100068b23468028664549445 100644 GIT binary patch literal 128 zcmWN@K@!3s3;@78uiyg~Xb6V>1~3RSDjh?6@b&hzd-7NM_~Nn{1`8H1)YfonjlyblP8z0aBxd`=ez%?SE$v2oU=VGM-+OV zPrDc(Jtgv8uR`XFD-RCsENFi9tx`%Xiow`A=D1l!T;g%wd6W5c9sRRieqWIK^@CH^ zsais5dFZPPN?T$CQWyGeki6>G9=7m4{{ewpM=xx?!3Ngvct)D?-yxFEHCF)m94jXv|V?y!oDa+%i21A zl}CY>E>utac_xB6_ed7K1?}WXa{7e@Pk7ni>6-8E-sk&bomG@tzboy}qUX0P&;H(8&FZRcyfpA&MGdE8 z;m_(tQ*Hm7^4<=;pZux3a9ZR!6P<14F*@S0I@y)~{xb$oeN20)hff&mmj(EWqHm_o7Z1XRC}(DlwB5;B33$aN%RXY zpWNxvr;|6X+I-wN^yby)43mC7H=cU4a}Jvz|MH`l>*6@A+uyP)R<3w{{_FBkn|hO3&yK99x@a-T4)%Z>8@E=bimnqV(Y9y91XE zo0oI1bV?ULF{{euZc&Amz{iqOrB_pISN)#RR4)Jc<%?sNRr@nOF`PGHU&JZt66{`~ zwo}0OxZ*5}BgRKNGmNG^m}%x_a - - - - - image/svg+xml - - - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:aaf9a1967365f2c641bf7b54a409e32842cee0bfded7ebb01b1a95c4ac9f0154 +size 2163 diff --git a/selfdrive/assets/img_chffr_wheel.png b/selfdrive/assets/img_chffr_wheel.png index 3f09a35a79bf45992cf9ccd16f9b05c0d209f049..570ba4ee827622ba6bf003e0a167c73f9150caa6 100644 GIT binary patch literal 130 zcmWN?OA^8$3;@tQr{DsXCNv@RHvB5gsB{Fo@bvmN?<()n`b*b2&#@bGZ}avz#`3>@ z;*$E)jI$(jf$A;mQ3De1C0j%yktCkW5ge@KoCIP*&lo`>#)KK9i1pD*07rHsW(UG+ MR-=831{T@W53`vj2><{9 literal 16884 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 - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:c5687faf4cb22bece0405893671651eb13e8eda393987610f493ee5e05eac61d +size 439 diff --git a/selfdrive/assets/img_circled_slash.svg b/selfdrive/assets/img_circled_slash.svg index b10a3938d5..6c77030d15 100644 --- a/selfdrive/assets/img_circled_slash.svg +++ b/selfdrive/assets/img_circled_slash.svg @@ -1,4 +1,3 @@ - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:fbec38447732a443c304042c3c3d362c94e11e794e7cc7dc86aa1c7bba16c6b6 +size 328 diff --git a/selfdrive/assets/img_continue_triangle.svg b/selfdrive/assets/img_continue_triangle.svg index 20f9e45dcf..ee9db369a4 100644 --- a/selfdrive/assets/img_continue_triangle.svg +++ b/selfdrive/assets/img_continue_triangle.svg @@ -1,3 +1,3 @@ - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:1764c0c93703481b2ced63bc35c5a23a6c12388ca690ce4cf577b3b478a08b69 +size 197 diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg index 2e86012809..7c58515200 100644 --- a/selfdrive/assets/img_couch.svg +++ b/selfdrive/assets/img_couch.svg @@ -1,4 +1,3 @@ - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:a428d2561198ebdc853ba6fb25a8b9c5a58064d000413694e1535adc06633c0a +size 2649 diff --git a/selfdrive/assets/img_driver_face.png b/selfdrive/assets/img_driver_face.png index e2d943e537fb1cc489dbcfbb3dcfeb57258fb4b0..8b6f515b1f82878d548cc8ff37bc9f62305c4359 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@u5Pr(H&khFyKHhh9*Ms3Gn7oJ|<=3VmEe!R>+#;KdN_dafqrt|;( zljm|gt)5cs<)@=pqkDoiYylJ3)d3N4<>neOCNTg2 literal 3769 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 PNjdt3OOnnd7;OIrwou~J diff --git a/selfdrive/assets/img_driver_face_static.png b/selfdrive/assets/img_driver_face_static.png index d8bc5f137170889c54773375f30f2c8cf6ac4669..5d5ebf67955106ebdca0382ec1c2d9d28ef1ad84 100644 GIT binary patch literal 129 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 Us`>2_d - - - - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:c26afadff128244567a7cf98f1c998f97056b19209301ff7fc8851eb807fb748 +size 2193 diff --git a/selfdrive/assets/img_experimental_grey.svg b/selfdrive/assets/img_experimental_grey.svg index dc87105ac5..8ba6c87bd5 100644 --- a/selfdrive/assets/img_experimental_grey.svg +++ b/selfdrive/assets/img_experimental_grey.svg @@ -1,4 +1,3 @@ - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:5ae28a53171567c8a0d52eec75cc49004cb8dd19dcab9a360784718ab8ec7c02 +size 1931 diff --git a/selfdrive/assets/img_experimental_white.svg b/selfdrive/assets/img_experimental_white.svg index ae4f18fde2..9714fe0c01 100644 --- a/selfdrive/assets/img_experimental_white.svg +++ b/selfdrive/assets/img_experimental_white.svg @@ -1,4 +1,3 @@ - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:99be696be983700d7eb1768bd1c840198a4eb9525b71e28efea49f13c358e519 +size 1891 diff --git a/selfdrive/assets/img_eye_closed.svg b/selfdrive/assets/img_eye_closed.svg index 91b229e911..fcef6e8a3c 100644 --- a/selfdrive/assets/img_eye_closed.svg +++ b/selfdrive/assets/img_eye_closed.svg @@ -1,5 +1,3 @@ - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:8e0e5b451ed2e426fea99da24a9df6552be7048205b9aa7b89e46eeb89f6d08e +size 1490 diff --git a/selfdrive/assets/img_eye_open.svg b/selfdrive/assets/img_eye_open.svg index ea6e41ac54..7289c4a571 100644 --- a/selfdrive/assets/img_eye_open.svg +++ b/selfdrive/assets/img_eye_open.svg @@ -1,4 +1,3 @@ - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:23935f9e2ddba8dafd4a5d0217f29783260a0832c9b0d3e6a2ef66d4529b91d2 +size 775 diff --git a/selfdrive/assets/img_map.png b/selfdrive/assets/img_map.png index 8bdae4d7d81e0be341f1bd065808b498e37062c3..a8646491d721e809c646e3ad271fe5bcf1d6777e 100644 GIT binary patch literal 130 zcmWN|yAi`63;@ucDOex|8H6lv!#8l31Yf`|GJRaV>RtIeTK~v8=P`Dro^3u}Wh}S# z3`^{9HI5wB1){ekNA)YZ-Fjeh4t-udm8cOkvo@A&LNJDy015>vh!`!{EqdmJ8%M_A N3yINwSsJ*L_yeovCT9Qu literal 31990 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 Z{tt6|OISl&?mYki002ovPDHLkV1l?SC1?Nu diff --git a/selfdrive/assets/img_spinner_comma.png b/selfdrive/assets/img_spinner_comma.png index 16109557f85911820bcafc30b6af681e3d5a1bfb..81f2fb38c4da469a0cc7e58f8f8d0e2ae0768260 100644 GIT binary patch literal 130 zcmWN?K@!3s3;@78uiyg~LJ4X68v;R?QRxWw;OliSd&zsW{?c{Ma~#Ip+q^x>SpL@! zFR4GxI3<}2RBu_28Xbm%whZ1z79_{kQou-(#0Sp%Zc{1XHcC!5vKX--0+aUvBCnP> Muo~?v8jxUBKj1AVEdT%j literal 15713 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 N%QpMB2&>V+_5$! literal 41523 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 K|KhNPP8kxNoLo*=lCrFwa3mSzS1(7-C>N}Fz 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

_ zW)F_(3Z{g5Q^mKPSt_`5PR){;D6V4R5d=_DRRVEB_;2*AM^Ljw&XX6oXoUdf>y({# z$j(L_Eqy$+6n`tAU0~Sk)NiF{`vmZurp5z}Qbx0BQWJLUISaa~T49!dQU-rc{wDUy zzn-;hVv{EB6oW)H{0~~=pI8s-^b4AsvPbl;TjV}T5TZ*?G3Z{CQ}1pTKZ`$p25`3k z`i+mN8SH-ey{<@NFv^!yyqv<0`+h9bJ3th}%>@-mTU_}3w?A>PQF}X^Jo!wk)Oz-A z5=cDUh-q2-V0&##qi#cOAvnbg=%iA+R>ySJ2E+yOw57jp41skyCMs`9=t@K+CRWNf zz1OI_Sn2MKmmMbuX{rsFr3)Sgt%w++k?s0U095_zZbL)5x|Pk{aWp>_0VM4?0SOyo zYXLTPDZU$8`06E%_dqV@{f$CJKcaucD3gp5mv-mOhXS98k0k*&?MUayt;apd0m@15 z8c3MLJJY_atKP!&g__2Q$D!w?>NXB&T$Xl+qD&7|v_I?I>`1@4Bzmge2+cKEFg2@S zkKh#Fbk3NoG1xl$ZWx|JzPVqzjUjx_-Bt|%^JBhFQ@dZqw% z6;y*gv01Xx*llG(P5SNO(k(0piJx1Gtb6=kWw;{>xpiSH-zY60DTAvtm~wy4vGY>V zJ!oS%{y7UUZlQkbZMB^3AGy`1-vZYuK3BTIImBu9>dOWm9mqdGSJz2$T|=u@HpxN> z2>xsG60p;eXlC0k?U-=lqC61JJyCEPIb!C3_7;oV z_{NEP)wG44%3lG5>LYcMu6l{-PDIetrvEY>KiH-|ZXcT>BqTh>*hX(DdUB2^udtcR zma*JX_BZ7a((MSgq^tYzUVD!EP^%BsC?WZ@WYe9mDpRH}eP=J*!W-v+=d3dvQ-%Q? z5_5NQSIqr}%0UT(HZuwPl?Od8{cQu2oKz6eJnhN-^~eq4fkE<3p~TS`L!>HSw^W~a zA*HnMRPoo1NknmlcgFera7uYEoensv(-&1}1AFAt%^sx}CVT?O%F#s(9Knx?Q0H(?oIL_Sw}%*DW?|%?Y2ybb7ATy}4hSw?=Av^Q zCOb8z4NkUbjAsOO?9w*Kj+29CYii(TKcDj$&augUx<@~QIhsbltkQb?Bw8OZLJSzt zLvG;E57oLop7a3YqU7F5p0sBMzeLg{CV2vei(0q3ItC;8$djjqA7xx2C(w%nfw;U) z0KDO0??v^}wW8aH)m7x7z2oVlal}0kJ>f8kA=9+Li^DsHMPyij=a6kL4ds#&2gSvB z@8Pd(NeGr_2?RR{=f-hnz;rqZhYZ25#7P0y=SXAGA)Qef{T0ge9EErz^^7OCfs7nuy^JrUR*y7(77VJwk#g5|RS`H)btgc9epkw+Y z{!~ci&tI(SOcBSMq|W70%J?N_d?y;~_}kZwTP{r$H{Z5f1&wH`Jh)I8Dsy8%+4aWA z5mISMnsL_ohx}MJl~whWHU+H^{rf1#=ufeT691D8_}qLyR7@e05FU{+O%lO)tMN$F z`}Z0uJ~$G8xW>G`5+!XEuH&Kdr)NK5C?J8egxO9=4-q?#CR3S07hjX)q~3Px@0PAq zU{c~u)DCQ3*?SZFG5f|G7n{Q*oUm%+8hkUKIg4&DMagAM{wNt$qTh$t*G-xz=d9~O zr1zz%JpR03f_FXe2gtPPB6APNA7r1g4DBgP9!(`6Jz{K>g=3rq`xv z!6K(J;JUfx96Aa-^gYP}+F??OArqqSLK!NUn_;{Si9$1LW}~xrC|5`N{T#P82ITOX z@6M$8V9#!Dgz$-LD!cXdV{YUXUd>P-T%3zL%;j8ovO-EK1#BY&VS|AC8ISH2* z?HL^lO=I5+H&>-iWk*u{Y$v&1fYN|{}fzlzl;fald1EFH{s(g zJ=-u||2$7l>OrAi1uxSp7xKMRygV>!&e;#fCY2t2chqKV*SK9^O1}N3|8rwlrS3e{ zY~P{t<#L+%8`y-Gy_(Y9PIo@58O}Sl%&}47Gn%C$pqeh`|N8#>I2Ync>wCUO6_gMn z^MF?Nf)gJHXX$uR2P7JFA1JJ%JKr~aiL+2b%Yd5^fX}VgQNj~{8R=I*@AB4z%O8|g z6C*V{(^-W2)~B4G*#|k9I9+r;_(uf5QrWz%%rGrcVl0K13xFW4?rYB6t6k(e7(vDK zUpjk*OzQ%u(U{9VIrp2l&aK|7i8%<;`IEFu<04%>rQM8GN2L0<)wix9@rD4;+j$G+ z+}LGY)oJWLRoQmX(SiQQFD4j*TVA_@z2_B6b;`+qXTEgFU;$}hjupl-?2G2lOl8J| zn~!BsCNpVDl#03-DI2Jgx772qSFVr?tMd@>4`KAgp1iAbl)x&9!MS&N{bC|%IV!%SXpP|O*1I?*T)E5w0_GjZ9HQ6!^^ME>Lr{M zhuvA(a9xnMg_ekFz!72poRr;kQ02*I9B|5$~zCQLEfi$?K{9t0MYBG}hE1lKK zt=$0r;D27~(9=iOxI>$7vp>gOwKY;Pal3w-7j4Zb;V)D~j{IhuUy~_D zLzn$YPS)I(bpkF-3=k*tmC{Ar*=|W#TJ2U)52>0uU5bSXfvz|Z9=O2N(gux#BGShb zg>@G0vR>FL@0X6kSdbH3df|4?5Ve~oosleL%YoC4q1IXQSDSO;_Se0(t!4sb2*nvVgn<|J3 z|KK<8{3x;_0=qa1I^Uyj>Uw~1(PrDv36cW!Vb!*ZJ~>($NUt;+He|>D>WjviQ~%mF z_0rHT-3>;NcNiJBIn&&uV?DsyaQ^M1#YVPWvca@A8Z21=OslTq?1$x38K^ - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:a950b541c4e676475e3571aaa806060c3309c7f44b08a59f1ada19881b42493d +size 389 diff --git a/selfdrive/assets/navigation/direction_arrive.png b/selfdrive/assets/navigation/direction_arrive.png index 733c12909112694fdab3489ba6d21d042c96d4c7..f8c5bdc2d81b562235d819a191924364ee90259d 100644 GIT binary patch literal 129 zcmWN?K@!3s3;@78uiyg~LII(_2_XnGDjn1I;OliSd+|rceCvK3yKKfh#<#S^oF4 zEHqz6b|!Oynxksz=tA6(;odw!%8^pSSc9_-kSbx$Sln7YF@U!itS!+O#uZYq9$I3m MS;_da2Ir&t0od#(sQ>@~ literal 1246 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!Hk33m2Y0fBydc`_G?0 z|M>Ca*RNl{!Qc-n_)8-A|NsB>%j^As7RZ$Z`2{nuatny5S$GA;q+}P>G}+)MxW0pOe!peEGqvvov;Zn0R?jh*+NNv5H9_ zH}eQj`u$PFO>CNZw|3t3gJyO$D$z}Rn;-WHsXG}jQJMaq!%xGi$8*PHm&nU4IY7$t zQp+45wexbzk~@WqY%-j<-dO}{gf3~Z_M4K>!m@cwljpYtx%~%4jwEc;IG7{VeDhd) z@dmDEE2R5=3iQZ|^f`+6rAqa5%KAko3i&L4eo|+`wc9E)(lR`!tZ~lp*9LnN>{+GR zDQ9I5{ug*ISHhzi(|hty>6U*#bk6SNE*Ci*`R_*0{5yp_ssF5JZ+g&|B60b9#;N3U zvZocaCTnijEH_|Y!MjWb7()I*?q}J-kdn~+z#hgN7vnTvMD}( zg}vZk;%V8?@@EO**Z1#|xa%N$@66>r8~J$d{CU+`ccVmN^Ygy7+{M>L?y{Gcl%%b6 zZrA*C)6Q!7oOz`er@wuCZ0+H8!#DXH%4a@3-=Kbf8D}5+?c*=JE<9{r`^od-!^X9n zmb?_mUwx)iysGEJ!~2>a?fg44qFXEGK5yTBXP3vGv!Au7ZfKi4LTBCao=XuTBccQm5+IuE_}Zt zGx;T#jdFj+>NO4V^ViKPl@$Gy!uj-X#x|}1&h-cS`juGD{hpXJbz5c;(`yM|jw#=j zZ)){!4zWF~9ni{9X@5?0^|tc!<=3Ko#4nxtoiwFp{hw1MZ*NqV3mRoU`Kw_)^=~!f zj`Jn5ts$TP{aep?yF$_-J!ko?$P?D@kKbZB-d>${`U973i%tDEy|WkWFP``!q$DV+ z#7iN{aGGhoERJD21IxaE1_lE={a!5CFhCuiyiQC9L@kGYmqNO3J|;e7*LiFM9KsFWJ{R`B3UU*6q=F``eF+oIhmcT=#mP>|!I(keR MN$~iRG(H*e1LFrMJOBUy literal 1581 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 hSN!@~ literal 1246 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!Hk33m2Y0fBydc`_G?0 z|M>Ca*RNl{!Qc-n_)8-A|NsB>%j^As7RZ$Z`2{nuatny5S$GA;q+}P>G}+)MxW0pOe!peEGqvvov;Zn0R?jh*+NNv5H9_ zH}eQj`u$PFO>CNZw|3t3gJyO$D$z}Rn;-WHsXG}jQJMaq!%xGi$8*PHm&nU4IY7$t zQp+45wexbzk~@WqY%-j<-dO}{gf3~Z_M4K>!m@cwljpYtx%~%4jwEc;IG7{VeDhd) z@dmDEE2R5=3iQZ|^f`+6rAqa5%KAko3i&L4eo|+`wc9E)(lR`!tZ~lp*9LnN>{+GR zDQ9I5{ug*ISHhzi(|hty>6U*#bk6SNE*Ci*`R_*0{5yp_ssF5JZ+g&|B60b9#;N3U zvZocaCTnijEH_|Y!MjWb7()I*?q}J-kdn~+z#hgN7vnTvMD}( zg}vZk;%V8?@@EO**Z1#|xa%N$@66>r8~J$d{CU+`ccVmN^Ygy7+{M>L?y{Gcl%%b6 zZrA*C)6Q!7oOz`er@wuCZ0+H8!#DXH%4a@3-=Kbf8D}5+?c*=JE<9{r`^od-!^X9n zmb?_mUwx)iysGEJ!~2>a?fg44qFXEGK5yTBXP3vGv!Au7ZfKi4LTBCao=XuTBccQm5+IuE_}Zt zGx;T#jdFj+>NO4V^ViKPl@$Gy!uj-X#x|}1&h-cS`juGD{hpXJbz5c;(`yM|jw#=j zZ)){!4zWF~9ni{9X@5?0^|tc!<=3Ko#4nxtoiwFp{hw1MZ*NqV3mRoU`Kw_)^=~!f zj`Jn5ts$TP{aep?yF$_-J!ko?$P?D@kKbZB-d>${`U973i%tDEy|WkWFP``!q$DV+ z#7iN{aGGhoERJD21IxaE1_lE={auMmF2P> zxX^eT*_q5~)#yb_%g%=l*sYW)ArlmKP*Yf4u_@SyoDe*7L9jsKiU^n=Ir2&ro%1!^ LSxNt~j=`%xz)vUJ literal 865 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5m8A!&LZC(qcR04cLT!Hlc`}cqU{{06G{$hat z|Nmcn)o=``j=LntFPMSDEwZ+8{^Bi%AH^1i9%5i%dg$rm7*a9k?d|<}!j1xLAA&h% zbg(-t@^W-@fL9^0#I0sn?0!b_Gde9(VnUrdkTwY!@(@BaMuxj)yemFa2U z)2%nY&r6@B1OxvbS4_0)pR(Ue{)EjJ`r?ctL6jhblx6wy@XZ{x38L_NVQ`_TL$}`)D zrYEp_8fXA^T%j-#p7%h)#GecC7^ItjmKj%H|wR0!;L1tCMaBW)A86U%o~(> zv^k_oP%+I|?Z_LqR*APe1QyFn2IZV+6S2OeSb6&U+aAJZ|E3anIiwR7v{4dwr?m17pWA0sdRnedRq&K{#MYX=?zk{E{#6QhC4lhJbP7f1wTINybAt9jU`swkx z76Hvk@^?fzdX;u^L^=BvTdMCg&@EXKpX8kb2jVws0rTG>_pAQk=@)~nk!Cbj>&yOmkw zq$7{Js?S83zQU`GA}1SF(?wk-$|x0IXAn7g(2n8#z3ERjsD9F`Dp#{);C7r-Jz2(! zO>x~_PrfegzCGT2@4b%+s4$C8x+rjRLU&4xyHUjw$%T`6nlq2xQem!|73h5O!jiN< j!A2*f1fig?{xP3*%ZAgehh0j5d4R#w)z4*}Q$iB}+pvX1 diff --git a/selfdrive/assets/navigation/direction_continue.png b/selfdrive/assets/navigation/direction_continue.png index a01045ae6a2b586dfc374fb7e34e4adbf78a1180..ce2c42c43de9c52dbe143d6838fc738e017e245e 100644 GIT binary patch literal 129 zcmWN{I}*Ym5CG7gQ*Z&pF32ypSr!mxR5F4oJiV!2^Um^_eY|B`>zKPzkG^itI=9RA zO&gDwnnz)Eq8L5P*+^Tr0|c&MdXcI}p0e;B3q_ MkeK~fB!V;I52R!!;Q#;t literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HlU>(`$@fBydc`>$WW z{`m3Z=g*(Ne*O9l1AkD#U!uYP|NotiUB3skK&~XnFPMRiTR=+9$lA#}I667Iq`IkR z^6X`6Htsok`tt3kuYYqzLC#x{pH2;BU>XL&_Txqf=~ zdqcVH)8gOXE4Il}3s-*cDQEm^zH?vg`$)Nq_V=1jT&tepo};?q!@OmC`86v)&s)BB zdl8=|+dbi}#v1RMPON)v8!UbIZY|%$sow)~*Q~O1S#~ej)ck7Eo(CTbBO(?4%f!9; zal`tBrOUoO?%B_@r#Yv*vyB%1T){VS~I3&J9oKW$hadM~)R;kuS`^*`w+QK~w= zIS00$x;D>_C3+R3{)Y~?gHvm;Ti+P*pS?43FC(X(zOWKI2b=}249>Ik3_OTrAjg{58AP70WH zgCT3`luJjR?bqLSpY!yd?5S6PoS4-r)78wbM=f>COe$UzI%|sBw#m0YZj1!-R)G&n@|RrRHVM{Jmi-wSCQ|iwFIkx^st37A*nM9sFaS!-6;OQ^D1 zuCIPtGht=e$|-6`gKi%eYR-6V7reIU`}UB`R`yBF8n5k6=LOZ7-g3Ra^oD{*>RtKd z{_16QADFkBo`2@%cy8Ij{9hlwtTf#&zhcb?hsz2Ncb)&d?bgNj!cp-L8=pDmz5X}j zrGQP$`|^pRav8!;HofEDEqZ0YPZS&D5rqs17T)Fsi2^1(O>K@B3^=6N91qh<&^MlQ z(o;SY|K)df%KT%_4LSAp%L02=wRJ^@Z9klU+Npi_{F)VagnK(6;9rBh_jJ$Mde?Wn Q0cJ=BPgg&ebxsLQ01@OIpa1{> diff --git a/selfdrive/assets/navigation/direction_continue_left.png b/selfdrive/assets/navigation/direction_continue_left.png index 9a618026f0e00dd044f359e1f3fed8c668c63b6b..438532ee2995e37090ea2433fe7a7d6d27f97bf9 100644 GIT binary patch literal 129 zcmWN`$qmCG5J1s8RnR~T!#3F6unYky30A;9r226BPx?1~N9!M6=RC(D#Iw!EtBh^6 zUU5tPEyhVn+`xLveAJ+NtYng1f z$Vhj?PqO{GpSyY<9WJ+-cI5V|zNAOiO4gT>eUG0kcPX2)d0o;oYZdKjrIJTgKgmvJ zy}D*~#b?@H%CcawFGG@5CSOn4ED}Nm-$M(xS+4 z!7l5{%-w6(<gzHO_-&?!rMr~fF#8Gbac8Xw&^~)2xx)|pe8ySA^f8hHwySU!HJ^F6VZ{GJymbPt)dNOC~nO%?dy)Ea>PI-R*aNX`X{%sr0 zCHFhus>&%9^9mM*o_d(r*(&eJ59;+PBS?~~n)78E+}mr2)KbMy*iiOecNw()p`AQrc27| zTF;yM?K>2T&%JQy>S3KIRN~@stZmf=W#?$CZz27APVZVVWx`DTaKERsWoHVh-@Dj3 z$z^wYQL3#^P{Z?!r{Cl*o_>Mzy4si1HOK!LNS!sc6FsE3ByZ;aj2P{C+k9F?TWpuE ztGvy7c3S4Htp5o`hwQq;^yBKK>oz!iSNm7IC}&f~)XKl%OID>pF4Pq=ihnHa^W|M{7o z%+8%MZ*MG4y?1E0lKJ$-d!4qNi9TcbaQ5k&uXKZ+`PQDA$`O-QT+Se^_t{L8A#Ou~ z`QbKmE}gC-o;npi^P2~Z^4|onYgivCw(^eLiZjtYS-~If742$e&Y3T#tH(UcJ^Z#{ z*AB^M*T8uWPA_x|rn=4C9+A%?U;bSC)#Iv}+h2$|eV_K*^dyk^L*ro3qlXq=XH#eV zUXt$7aeH}eU~KT_51JR8opu*D2WWm4vt!L-+u;}8>?R=&`+Hs%ZYkRxeW7pr5Zqi`jRB%Z@ z;N_8X)vTfANp1TxCc#6zWTD=6u}77O2l!Am14Ta>EDLP;k@7*~JV!}`%7*%`Qf74d zR>o7!lZv{G7`@8b5PUe$X`@Rr0LZ&ZHP`Glr4(StnmO(qqqVF7az+T20z1cn8Oc&a MFJ}LeG&UOX1IK75y#N3J literal 1371 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvG6Q@n>Sy)c=7)I`%j-f z{rK_Y7a078fj_9=FInLK|Nm28tKA1$rc@H-7tFxUEg-69V(sJ|6qTG)T;1F~Y4)Nu zn|2>QefiFVr!U`r{AQ}bX~Mw3Qt#>F7*a9k?Oe{JM+Q8ujVdCiZf%&hw)1`ciJ32X z8dZvNEKh8*u44E2dT`=C-I;uBO$r<^^k;v{?nj%>?pXZ7zbttF&shb>FV61$b-K%X z!oE5GI0JvTSWl^&^WN!7>KR#YeLF41Rdb5jFSl>0(FmL^SZ-l^!-g|(zToqRZz4Oi zmfiWdWAl2=!0dMwb7o#?UEzF6cB1xtUe$}w9!5HQY^kiM$kYnFZSncTB2|~Ui_RQg zylAb;Ih#Fs+?~e!*K544R8}x^23|LRx8p*}0)bGy9n8*~+ov9x-agxIsnRZ+J6|+K zk6M>!SVY-9J$QVAwS`FT{Gh@fEq-RZrH{md<>YEhH3GfgeGq7{Tbd+(exvxCV6Af> zpCsN`9x}!F9YgNLA5+*Xbt)sC2WusrJe;ql_)5(4x8v(zxg!_rD$Kb%m;5c*8c@{t z@W$%|MU^g_8ne?soIh0VIDGm#=kC7-Q6;mdq*vTNaI<9JJ-Kt+7q#q&T>Ya>lVAPv z)qR(jKTbSx_~xlammYq+QzxYTW%Wa4{^dJs%}U$% z01adE|eB#b_zFE!>k9>dacty`Qqc>D~5!a(w_CVk7YnxA{s@ATyyp$C5^WMU%6R)Ih zpXa}+#Dqm+iFb?Grnw!K@9%vozq~s$w&lgQwq*icceDbd@{YXl^S!@I{Ag<8PJuPo z)D!Iu-`Y&JI=jZ-Vr`jWi|oC1O>qi)US4rxOFh5$^$D@i8eJoc)bF}i-!Cp;4o}|9 zcWI}{-$PDfYv+2hy`9)um#AoOms~xotc+X2{vZ3b%QLgIK5l#z^t)C;K1Jf;0=pN! zCDA2o`VOuNX0PN9F~8hww@|WFEc8=aN1koY1^-ti*j_CE?~Ys# z4crj_`{f6P8*?jO-#RpZ@nJ>#nBG5MFCKjOa<%g$#?=##pJX#gw>;C);Cw?a-B+j} z);dK0!R)VAt(2O@`$GxN9_G$b)c6VXjeCKa} z-+69`JJIULCOhqTMf|ktYcp#zgauEguCr27F>&$8=$LRpKyjg>laq5xOB)9#ub_|= zVTm7xPH`p%2l!16DLPsFH1zgH)B8t4aYAO#qvJ1UGi(=Ff5*_0@lI<0V|n>4H_S?9 zl7E{gl)tjy5c6c?D+!*q$Lf!!D#FVTj)@<+XC~|a;`*a`2Uu`1c)I$ztaD0e0ss*f ByXgP` diff --git a/selfdrive/assets/navigation/direction_continue_slight_left.png b/selfdrive/assets/navigation/direction_continue_slight_left.png index 08e964dbd6a68a9b82983499adda5a8bc78ac1a9..29f44404608bd362089b6aae0c45d5a1f6df3e13 100644 GIT binary patch literal 129 zcmWN`K@x)?3;@78uiyg}F$o5Lg8>RNZE*^E=0O!S98_ka)FR--p$H*(I&$YN^{laaWA^&#_v<-EeHR2-Khu)uSPdrqs z%R4_kOFgzOFZ@OC|B9V|JNmA*_TRq#A|ZB;P0O1L2X}oatX{Wli_nLn->#`#KM(3} ze;)s$;%&vVZ>m;t+ID&Rfv+pxd;MEuvio=VrUVZT~-`@|9kHuauHf8C3urD;jug$lQ7WE{R8R_ENsB1nK6-!NoyQusn?w$u(EV}!t(|sFPUhBzqmM7|6koqS zQY}vI^yd6|dEf8Y=$ake`Q&BU`Ow73m;*7N3U;qvE;R9TQrm17n`2@BEFI?EdH7Dm z<$IC#gPE^FM1=l3cF4|@*`)Krw&P8?sBwtB+O0QoXB>r?sv7?Yeez$Fxaw_pMA6H= z|1#WlLN=VOmY6u@-rnHVY)70GzWgz1nW>V}A-&`ScVXKhh14R>Bse^mVGt-SN)PFm6r%Y&UJxM@1)+;&yW56O@W<(c9|iO;O!eu1cJ!0T-+CuOctIiHwC5+gYjanl%dH3>VEM%0>FVdQ I&MBb@06s*zH~;_u diff --git a/selfdrive/assets/navigation/direction_continue_slight_right.png b/selfdrive/assets/navigation/direction_continue_slight_right.png index 3e21cae11ed5209a7f63f7352d7ca2c4ef5b0b14..755935b84c512c69b72569720de3be7f485cbfb4 100644 GIT binary patch literal 129 zcmWN{OA^8$3;@tQr{Dq>FnpTc20{>KR63@1;py$_ZQjWr8S}0CaqO}g^BBkN*<`uw z-?Gqr8QGc4>C_xmOV9TZH;(9P^?*JRda#XZ0*-|%qt7|Cx*`Tfu)rk;AhMaliuvlz N*{EdvSUd1Z{Q=xSC)@x4 literal 1347 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HkZOP5}}c=7)I`_G?0 z|M>Ca7a078fj_9=FVW!t|Nrx>eU1VxkShuD3ua*D77$l8ws!Ikj!w=lu5Rj@JbTIN z&ASetxpL?6>mzF(>N7B~lzX~3hE&XXJLl%K#Rfbsk(=NDcKi3=cy@`EtojKy>pAO^ z+`gA4^sQQ$<0;muSv3bA5XEv~P#B&%EZJ^m3=FqQ-iAN!e&YBlp)Dl5e5}nm+IH-1jen!;$a5 z@3uc@z9+1oy=3!gcBc<<<{if$Y8p0Q_ig*_pePe({xY|@WkbbX6_rxOPrE$LzQ-O~ zu_tem&z~h5D(^0MuG(_`wf`QoYPa?FGb&#weElUme`|I(-!;c)kIRzQKaUGN#UQ(G zYUBM^-mkxQPVadwDZ9RR>3qJh_3HPg>My@;{C`o)rPq5KCYigk>k3?2n*QaNjRX6_ z>ytMeGn;#Tv-1Q#tM$(p<|Y`e=XbU)Nr{`!c1q!MRZMVgPMUVMd{GaZxg>C8n@Zg7Hii(G;ZOfLawn{A6u=DZWcQ$4g&p-K2ZSVNL zYqOR8jj9DxugTtdeQr<6`9n4*S3Nd{S*$ zee>ZL_D9=$w}*fF;?5X7A*ffr&(vM^#hB+mTg^sD!a7x>n_{k_-pt7?8iRnZHtx9QDx4?ezPT|?aVIXC2NoT}swG4IXu6yu(Mv32=rps3A+!oPFb_UkTL z_S*S(gv_IxU;n)IeY8h!yZhd_H76?U6XVuTaV_6tFQ#`PZu)Y@Z}n~RPnfUo0pPfKqz{iF@ zEryLD-@W}TCX^j!x|6`|=u=Zz9cA(8(d+$^k0w473)s)_NpOi$F3PaPp!@`z0o4S^2||x zT^5DjkFpmXTNR$}i`7|mL^$(S{`Oc8^#yb9@GouJ+srFsdG*y2ftQ^npRP`_aWdw4 zci4V&mS^cyF2_YqHv$xMRtT7da7b&l@QNO4<4SZEdSrj!r2)uj-LMdwywt-NDQER+ zjqjdhw8~tzvDzKPzkG^itI=9RA zO&gDwnnz)Eq8L5P*+^Tr0|c&MdXcI}p0e;B3q_ MkeK~fB!V;I52R!!;Q#;t literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HlU>(`$@fBydc`>$WW z{`m3Z=g*(Ne*O9l1AkD#U!uYP|NotiUB3skK&~XnFPMRiTR=+9$lA#}I667Iq`IkR z^6X`6Htsok`tt3kuYYqzLC#x{pH2;BU>XL&_Txqf=~ zdqcVH)8gOXE4Il}3s-*cDQEm^zH?vg`$)Nq_V=1jT&tepo};?q!@OmC`86v)&s)BB zdl8=|+dbi}#v1RMPON)v8!UbIZY|%$sow)~*Q~O1S#~ej)ck7Eo(CTbBO(?4%f!9; zal`tBrOUoO?%B_@r#Yv*vyB%1T){VS~I3&J9oKW$hadM~)R;kuS`^*`w+QK~w= zIS00$x;D>_C3+R3{)Y~?gHvm;Ti+P*pS?43FC(X(zOWKI2b=}249>Ik3_OTrAjg{58AP70WH zgCT3`luJjR?bqLSpY!yd?5S6PoS4-r)78wbM=f>COe$UzI%|sBw#m0YZj1!-R)G&n@|RrRHVM{Jmi-wSCQ|iwFIkx^st37A*nM9sFaS!-6;OQ^D1 zuCIPtGht=e$|-6`gKi%eYR-6V7reIU`}UB`R`yBF8n5k6=LOZ7-g3Ra^oD{*>RtKd z{_16QADFkBo`2@%cy8Ij{9hlwtTf#&zhcb?hsz2Ncb)&d?bgNj!cp-L8=pDmz5X}j zrGQP$`|^pRav8!;HofEDEqZ0YPZS&D5rqs17T)Fsi2^1(O>K@B3^=6N91qh<&^MlQ z(o;SY|K)df%KT%_4LSAp%L02=wRJ^@Z9klU+Npi_{F)VagnK(6;9rBh_jJ$Mde?Wn Q0cJ=BPgg&ebxsLQ01@OIpa1{> diff --git a/selfdrive/assets/navigation/direction_continue_uturn.png b/selfdrive/assets/navigation/direction_continue_uturn.png index 0bd1b91777ed8d3c487e188c945383482b219c43..ff9d97893fb766220f44cfd48c7bb1501b47a85e 100644 GIT binary patch literal 129 zcmWN?NfN>!5CFhCuiyg)W*{v2&9ID6rIHfN!PjeF`l|2Q$4j=g&Uq+x@9Xy9x&3dS zwB_+s^Q>82AVx28Hp1N8es2Px>@rEjv;7SB$P7bxH5Gawh$wrfkV+#f2ZU`W! M9L4P4D!~UWe#d+#dH?_b literal 1780 zcmbW2X*Amj7l#vDB4TMe!Kqq8(GzNE6(!mcX@yBDme84?A#Erv(uP{PF_sQ#TD5dS zsM?Bu#?n$tLfT=dWsDk3YbjE-R5YE^O3)ZHALhL8$M=2C{oUuwJ?DPA_XpBRcXikv z7zhMX_rT+PcKrPBKoxg-L3%K6M>G<_@A!^KMn)DF7bOzO+S=Os`npsqmC0lq|FHQF zTmQ|M|Ap=C?GRf+?amq_rq4-Vkcyg?F3J>bhdJR(p+z#2Qdl{KWx!*8SO4hb%v*6P zhn5clsTg_ST>av?D@Dr%r8e+%2&SYIUL1?DS-+mQ%<#icG!n<(ug-npJD1rYt*-}YqocT$4L$Io!im0?sXPxcqnYyx z<0lcTG-SvJsSEqxV10iv_@cHjkv&FSy3l?mFxnGw@>tj4UZ?p6@|CN^Ed#)O0`rKS ziR(^t3v`!N4l_9VG&E{t7g@EMebe)1Y+!EJx07B~v4Q!)wy%Sf-?b-A^76A}p{?U( z@=YfJTi$C|wfdx35FL@V#K@7e)xjWc_BW6lcyn_#Ay+C|TAXFx{4JR~>FvbO6phu>l*_CVvM>m3I z74R>AIGgaX{ax;?y&mXv$8<-)uY*s?dYONs!C!`tWrH#Jv+5;SHCW1&qO|Mc`>8|E zjedzxNX&>C+&5$GzRTzP*A(t1&L)c3KuXIW;qomOT|6EMX#9GG@Cwh5N;j4dbaM+$ zscgDRx}b=0sR=xQGA@UfbS8GQtilgrbDBUz5{rV^Kj3jF+EsG8y9|Mc-a67@0kJp7 z`W*Uy;vV<*3Tvr*hHh1u+ucP+qnbxp$gEaDTmAq~9g>2>7FP=>ov7^gR{0jgDI6bf zj`--LHyNo}a{gCjbgRV%3u%m!n8){c1|`zNqP7@wRbKl{geCz6t$mUU19?BK65DgM zRF}%>mLWp-LVl9eIsIo$O|!p)&KX6S%kbp=456d0r)hQSnWt$Wp*zRohPbgiH^Rdn z_4&bh0ejkH;MOyE7|kC%RRFGqoqMM#%wT^yhBz>8Ag7+Q^J_o|t zk|pO=g~I*#jvvh4on$xUS!(gtYA*O&|P**T3DzPPegOX$7vnpiAFb3go52@ zp6HJ|rlbySV_vAv&IleKbu{V&uT(S7H4F|&uPvcYp-V!7*P_7sRu8@P{2Me1n9v`& z)vCZt2$2*JhA349v>LBMIaUB1ATcdq1?vD5Xi*|Mr4p$s&Py+t*EB4Y;J{F0h$`1d zBb6-B8=6wJEKi#Jdh|E@#yfG9IgvxS5m|_0M3BcA}Zn_hHs^S7rE@)kjnA z1ZbPTU8jUX6Vl07Qq1&Ap$TUgcgK^p)wfQYwR|cnzaUiS;n&MmsPwg<^ZXu~YsWYB z#@lm#W0@n|S1xlW20v_R)W{5DIg2{M%KvfRZqUx^eZ#oxcL|IDQgWi>0Rg E1z#s(%m4rY diff --git a/selfdrive/assets/navigation/direction_depart.png b/selfdrive/assets/navigation/direction_depart.png index 4bf32c870d5f26dcca6c5e80537cff2c2cc4461b..aa4e4f7115f30ed52768185e018142fa3888af12 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@u5Pr(H&hVboe5(vVK+K$jJJiWfnyW}(a{#I?yV{BSK`n)~sSpT<^ z*Sx>8X4!eSR)#+87g^VLd=8$RZJ>;PXUoka8dCa*RNl{Vc-uc_)9eS|Np=2bS4|1>9Qq3e!&c^+yY{1md@UR(aG6GHBH@w8Q4`}NA5#b!>YG+S=^2(UN`upDeJ^$;DGFY^- zToXm!PUhb+?aHjCiu!Mi7lN|69+tjgPVirQYW~yW#+r{#Yfn%A>H1oE@6+VP-x&*L z277-_O8&%nAt3wnLZjadHv(1{FW+;kN9BsR{TjPVFT1UG^EdEkwdSoclxMKc>eZ95 zdN{#!tJj?BjHbyB@isetcqoKb7Jp_eSP+(V{1#{XhQ81#7g*YhXU-}3x!<5KG%IHB zsm`eNoucb%IW9+}ip+Yj;ZB}JyPAdV@u!Q`Ob%xL{OX#U(R5fZtT0OJRf5>+8BW47 zX{V1Tui#JHc*^V!`|60J$tzbh`>ttfQL~6i5>+udm?*UR&GKIfLaX=qd_SlZ#`&(r z`9$*64}KOcd-SEY^Ve8j{^t4|=m?XrnKwAw_gwt>fy=kx#i=9OCI=O}Lv5UeWfnyo zwFYu_b{i`Isg6*t+ut0^jlP|Hmyx}YWn-7BN#^_O54Hc=zp3k6t8;zw#bnXqjG{mL zc3pUPpjMx6`ZIgyC%Y|ry^nsspSIxliO0)RbAPq1*x0pD_}kX1j@NdJbyhr+K0N)% zo96>`iVxp(P7Ft*ulFl-qXoi>=O>)}`)S_aNy>SnjO5mpwL~ zvM{mC;LkQ!-v0h}?<+gMHJ?gey(w=~dzR%A{QdcVjsMdR>esF-U@ceP9C+6~v-_0L z;~eXk)sj0y=T!v?q%Qq*Vq=Nux(AsqYkym`hy5(gd8r+9@b-zt?~=bBDY_r$Bf3r? z;Yr;0w~~K_UDZx-xF6v#rvz;>b6Ts(aaZ;fFu7f@Q9E4ru1)l8>{gxj&<8zooynhP zY|#%bek&g%t;g@l-K4+)MH6^kx8~~EI@~wCt#xw8m&u#tY|?AjR{CUoUV6o5#XXgb z)U~prH;iZh=nRrrzRuips#l`q!y>N}ooC&H{3c}n>uH;Q%Q3!uqQ_DF3m2E^xF_}( zR`|LIv4?m5?l75?q~tgGrBKD?yoV-#zMuLp^2VZh(too#Pv<6S`NXeY@W5i})c=*I z3v=?^*9spkty13VC*m%jq#|`wChDe0wb}CfIZ{bx-cxeKdT;7H{=9R;sb@jv^FJug zw$Pa0pf~@7$!r1Z`MIr;X41CiVl#90p51hlXK!%GY?*D#S%LZ4vE!_L!-tFyXRZr- Q0gDd?Pgg&ebxsLQ0MYho$N&HU diff --git a/selfdrive/assets/navigation/direction_depart_left.png b/selfdrive/assets/navigation/direction_depart_left.png index 1f8d726911fcb7ad726e80a4fd84d0c5cea33240..76937eb5d36d24412ad5953e20a1c07bd69a4041 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@tQr{DsXgg?CvEr~Fr(lNCQPp@zDE_%zDFWt}ckky#`IByS@<$wQd z%RZk*4%*FyYL2F*R~zE$Lt%;@fgob=HmArnH7bs{asg}@ic6deQU)a4isNh(Pzrz! Ll#DNFjOf%4naU;~ literal 1613 zcmb7^Yd8}M7{|B7thwLPvT7ma(uF!La$D1gGqaZ{lGSIZh=)TZJ)f?Yo42fn8PgZM9#~N%oZSLH+ul z$qtk}{2;uZ5Xf11@ijXz`JKci zlpl={TwzTR%X{Go=Lb}Qp z3#tY4vZ=W^(XL2-Oj``^PF#`yq+3wM6Qht-@ZNF$`n8Nq$U_h6wM~cz2bKLU2wnW8 zE1WIE<6_h7a$A%9GR7~NHJg1u(i=tZUV%ecIRCwdm!$R%&n1D;DQh}`jM zkDQaj6X*Ep{*MyM=yfWP)6rRds%g&0y+Ss@wX-<-%-5)!fBM-=E((hzILP_YM_w3z za@#@QkmXM!i^PoW>PdG9N|xxjBN?+d6YMIO-+gxs?_nrO<`rzIFwbel`%=1zwOHJa zmZ8?H?D{h-%c$k|{WWHD1&+cX0Zr}tP=DBxtk=lSG4RQpP zb+U{l7`qoOVhS7$d+ACp9S^bTG9{S8tsxW;Phq^-$@pC)xL?>!kAtdv6&VxQqTEZYKGYhRUG zQ(Sn@U6+)nXFklA-*4H02(}DsWXd6g*pi#OjTkgYOw0XxMbFs}j%Q~~`sKY)5GCe)ayrjJJI> z`K*bsSHm(JIQU{AQgmbAd#IiP^3;Ni6dvd*wbCFvOwwb|7MuIYO{Ly`_+fFC8$PoD zfJEMg8d-vy836D$kQNDn_57uR-RTGC@sP*^%+zyhz}m(JP&em_Fr7-6TZ=^(*b8k! zg`<82-UBrK{fz$939}eDk3ezMn-Po5zmS3OX7hE&;4!9B^ z_pG*8FGL8~_&@=mWr*qeeobocX`s2`9vngPt?$+*%k8ksc?q>LZ@MY9jyyf0sByRd zERWDxq&8cnM_xzVk*#<5JUtRs6us(gfeX-=iLQ`Xtllpt-6ljBEa_r4;om}*kFeOj zpP4NrcM5Dd$B6TceLk)T*eadTrSC=_um`o6%@DM3r-V*4E7m+uO{TstrlXRH>3zbRj;r z-84wIXdqF^U0APUd6|OPu+h*@lfSB^l48s*|l8osH=N> oV}MhJC0xD#4gztYwKFAof=78%C)3lY;RQ2+n{ diff --git a/selfdrive/assets/navigation/direction_depart_right.png b/selfdrive/assets/navigation/direction_depart_right.png index f359a685ffa5682a226ec488aa359cfb0fccb7f6..149626b17132ed86caec70c812c2c6707d3b6aa0 100644 GIT binary patch literal 129 zcmWN?!4bkB5CFhGRnUNe0LMYP;ouNvR5F5jSiSCNFMiKHUb3xq&O@ntU$;k{+yC~V zEsv*~Cueo-V)PVgt=cLx3nfwV>%{x zn$VhCYPB&^4h||-mda>FoSmNMd^jJ^@A<#)|I7P4@ALoi&&Fa<+g0>b006*tv>Vb> z>O=pG(l+U)W}K>*+BvK{&Q)QRS#bgl(oL83Qx=sw6|6>(ikKp$z8vw3wRO3U)8l1${6#2V?HH2SEAxA)Yh!U8Q4{Du+j_Yl`!LtBNLsjuO*f?tpiyJ4@`I7jjPC;*P$^IhbG)U?;A`U7lbb z(pgb%v~W%=Uozt6lLDRu)8WZ^O^|%bV~!-qCg8lm@T2~6Qyscd3sgT>x$NVlB_@YF zkl)ip!s4C?&<_sU_@rieUcaP!Rb?WB-qcok-FW>}jKI@pajUSLJn%8uz&cK>8d~tH z9!&98SNqNJN)Z;#c~;HwHOARfQbs{b&kq0DR&&w*azTp`KXEg&DukJLZvRf9Wz2Xcpx>Ks_*XV3R&638e^ z*yZTCSSzyJeKj$%)8=s0R9E_oQ;DU7(K9hK9*Q5vW){`lz!ja95lZaR^q7IjXlJV^ z+2<$97JE$>DQ*pf|_k|W-&QPuRv)0vMR$M4Aw~ClsJEHnl4$=9N|Ma0@Md2x#%njdcZu)z^ zvIq`hP`Wrt8|4tMnaLDCl26B`B(&%VK}OzeqI(l7xYIkSz}-pId9ry{=pZXUJK-W; zLwyP`(yb2)IL_~tdsG05V&q)4S&H|J{jx`^YMbSrtc3%oEz*uUGf45OpgH~BeYVJ~ zh%Dd?5Yhv2P9$16Cx0iP!$uMSqWtOQ39&4;in#IxRdI}bW8OaYWYH?e5&F^*hm>;y zN|kEmt2tO{MMr4o_7xbi?UBAD#3C(@-yU~EDJbKe8r_{8z-;*%;qL$nL9ye0oKN9J z4*GN4veAm>CN<^??@K?sp9#BPuy3!uV{^~(6vHpI{mqRNu!eWhW`N8}xz*jWl!kR)PQ1ZnplW&Ihq=UZ~`>gKN(i4m%m#f9d770^_y{@4|@Y(!O)A*+PF_ntom( z8^&aZt+5SyJ5e`(In>8aGovW$DS`)fm^SIT-?OL9ar}p!I8dJN^k@8mL-7&iC6OZ_ zwHhW+t%eU)t6kM5P;a+&e&}n}jfc3u>z^ID<)}#>5C#9tEsc(tJ+jxp)8X4!eSR)#+87g^VLd=8$RZJ>;PXUoka8dCa*RNl{Vc-uc_)9eS|Np=2bS4|1>9Qq3e!&c^+yY{1md@UR(aG6GHBH@w8Q4`}NA5#b!>YG+S=^2(UN`upDeJ^$;DGFY^- zToXm!PUhb+?aHjCiu!Mi7lN|69+tjgPVirQYW~yW#+r{#Yfn%A>H1oE@6+VP-x&*L z277-_O8&%nAt3wnLZjadHv(1{FW+;kN9BsR{TjPVFT1UG^EdEkwdSoclxMKc>eZ95 zdN{#!tJj?BjHbyB@isetcqoKb7Jp_eSP+(V{1#{XhQ81#7g*YhXU-}3x!<5KG%IHB zsm`eNoucb%IW9+}ip+Yj;ZB}JyPAdV@u!Q`Ob%xL{OX#U(R5fZtT0OJRf5>+8BW47 zX{V1Tui#JHc*^V!`|60J$tzbh`>ttfQL~6i5>+udm?*UR&GKIfLaX=qd_SlZ#`&(r z`9$*64}KOcd-SEY^Ve8j{^t4|=m?XrnKwAw_gwt>fy=kx#i=9OCI=O}Lv5UeWfnyo zwFYu_b{i`Isg6*t+ut0^jlP|Hmyx}YWn-7BN#^_O54Hc=zp3k6t8;zw#bnXqjG{mL zc3pUPpjMx6`ZIgyC%Y|ry^nsspSIxliO0)RbAPq1*x0pD_}kX1j@NdJbyhr+K0N)% zo96>`iVxp(P7Ft*ulFl-qXoi>=O>)}`)S_aNy>SnjO5mpwL~ zvM{mC;LkQ!-v0h}?<+gMHJ?gey(w=~dzR%A{QdcVjsMdR>esF-U@ceP9C+6~v-_0L z;~eXk)sj0y=T!v?q%Qq*Vq=Nux(AsqYkym`hy5(gd8r+9@b-zt?~=bBDY_r$Bf3r? z;Yr;0w~~K_UDZx-xF6v#rvz;>b6Ts(aaZ;fFu7f@Q9E4ru1)l8>{gxj&<8zooynhP zY|#%bek&g%t;g@l-K4+)MH6^kx8~~EI@~wCt#xw8m&u#tY|?AjR{CUoUV6o5#XXgb z)U~prH;iZh=nRrrzRuips#l`q!y>N}ooC&H{3c}n>uH;Q%Q3!uqQ_DF3m2E^xF_}( zR`|LIv4?m5?l75?q~tgGrBKD?yoV-#zMuLp^2VZh(too#Pv<6S`NXeY@W5i})c=*I z3v=?^*9spkty13VC*m%jq#|`wChDe0wb}CfIZ{bx-cxeKdT;7H{=9R;sb@jv^FJug zw$Pa0pf~@7$!r1Z`MIr;X41CiVl#90p51hlXK!%GY?*D#S%LZ4vE!_L!-tFyXRZr- Q0gDd?Pgg&ebxsLQ0MYho$N&HU diff --git a/selfdrive/assets/navigation/direction_end_of_road_left.png b/selfdrive/assets/navigation/direction_end_of_road_left.png index 5c0a24e7cb11c45ce7e676cc81c389229ccd785b..32a602bdefbb119603afac4cd6ab9afa703f644c 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUNeUBWNj0J{h?DjC5XtX|*cUHqPXykuMJoQG2PzHW~?xBu;v zw=$k;o?O%gVsw(TCA99yu>?m%-f{vaEPE`FsUZ6r0~1&RM0C*v?Trh8C_&aaQ*~%` M5VL<-!fvAY0qx=^$N&HU literal 1439 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3Pv(gJ)!T!Hk}t5;vXeEI(U`!8R< z{P^+X7a05ogFhJHFX`a_|NrerzghroQY;Db3ua*B7Ld>|vvu(fiB8EWu4(R`GJEN& z&AX1AzHO1TH*b%v@(TED)g5;j-F;o4fs;P$kmPt-0? z3@hC_daR!x57zEXMSs}!xXyG&Px zsK;kXIxQ(I`dd}NI?=5(Xgl{5mB6_=Ta1o$TDdWAnj7cNb+uFPwVlSw#aE0&Xa3Z8 ze`QemWh>C!Z`bTvJPxhxcCH`q z>Ah(CTa$6TLS1XR(Ym{OFSwpLS&&6Pxp~?E55i& z6=lwx`c`(+b=xmD+V5s4UsYY2w!8ma)GTIKew{xY7iB*FN?2S}#jg0uxm;dGg#E$J z9Sd8(6uC+t;kN(eUiYj_N#N9dX}?YPQ$nofnX<0jG0(Eu`p9m__j5`PC>yiHg!frh zmF=E%H^qWU!ar^G*?Vi=t#muUxADai!_0mDA<+l_KA)TOWv0M>;VVg5pWWP-9oD~_ zy!^M7jKP-5!z?p(O}cZQ_;=1cJNx~r<)=h~e&x7G-TZUq&*x>USf6eExO7%o#)ix< zJR!3K_P3wt`Z<|x^;KQz@4JdW-+p@S_MdyNK6CwSocw&gBBzdLQg2H9P)x-MpOApYp~*=BvD4t14Poa~Y&B*;4u2%X86@ zs;No=H*FSe)!rEOI<(6n^|M&1*XPU+Z?2f0C|PocQGi+V^SXUPJi;r_Dz7T|!Omym zskpUrx7AZe#^wDE$vHu@-T%~9v}xT~vi!(1!`*os>TJs8HuCJ??=NiNe0uBE$sD=I z3=hJEiq=R+-8FZ4$#rfI%UlL&Mzem4izg30*L`7QlJI$kposxH+rCe?w^bgw_)|dW zQQebl_tVb|n>Sx*kUkW1H{E{pid+xh$<5n|u!Bl}ebrqW{tM#QFoHX=) z?7wBR$ggo*wo3DAedY-rR;*Soyc>O4Dz>Hnz9ecT_36g9z)vZkzb)0?638>JUi#n7 zJ$nT-as}@_$;i3)Zh;NdDG z%q1oyt)pTV;gPdp!i@yQjR&2Qnp=;sad+|(mZ;e>p?-cvdMOlNLbQ@Yr2YWZ1j!*%RdHBS3j3^P6!5CFhCuiyg)hE2$CSZ0JOm6U@y_6VD8cz4{?7O(=>Px# literal 1456 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3Pv@&kNAT!HkpYu8@9c=7)I`_G?0 zfB*jd$B!RB!Qj`gU%z4C4=VUe7Wn`Fzw-|73qUJXN`m}?8Q8c5#1z%EjclF0gJP1i zORAfDCeK>3dc&T>r!QQ){p97_k6(+L1zs>Pu$=L9aSW-L^LB1;)hYv?HqpY*`LF)W zU$NrLzx5w0wsm%C<~HTtvaQr-_^i1kewIkIuA=}847G%5&wXFExy8Ec*^-^RHq<}w zTWsk4)%W8;S&!gvuco`sww{tEv)<{-w$rlS%F~t#l*$yaUv4QcJ+5AE;(Ozc_RW$V zhZkPnFTRLR@cF|xj-E#2$?a62IQYrCGQ?+uwwL`Rt-BUM{%8KkDA#b^Bzoty8RjG`49qiR*@oB@o<#%Qr56Ihk zUd(Ho;giJJ$WI3hn{4LX<_OF>CA*xn_|~sf*>BsfZ#Z@;e8Kj{$se;WPg(Cg_wZ52 zT>CG5&$6rAuCTqE7h)zCC@K9$LC{OOVx1Yw&-+Uz>#G}ytmK`e{V49(fwTREtas}) zM2?oLJ)RpZ$#uVO0f(pE+s&G?Dbh0(o*XWkn#ZNbzqplW&ixQ`gSYHUCX~GOZ*$rz zU*OcXFZ}#cVMWisY*U#9(j*j@q^}E)=gyjaBJRYC(tQ{B?i`!Db%OtSqYcq3(^I!L zZ3~~5x?pzC%uP4jJbSEj^x5uzlU;o$hgB%uES!0v;Qf6^c9>qRpO<qyE&Yw4K^ z{JdptUsD%6eLjEF*(9@8iSq_+2X5MV#d3dWPqH-(O@G!MQ@Cw&>MzHxuP2gJv!cTc z-c{M{{iXiGW^+v4+fUnf>%~MxyF3lfUYr)%WBI20+En?YN9=C%>!i$)_2ao}t01}T zc*;hRopT;~WnX&6v-!ujGgfo_uD;CIdD61c;D%By}sn|Th4}U+mvDy z)^9O*QDDQpCO6^MdyYRB40l)_&t2v6eMxeuM3?t-W|>|6@rn9Z)@|)M7rk@kvV}d@ z3Rm9EDzJZI(rRyUI6jj<{-wR1_-E~cQ(7OCm7VwY&24YYIeT1KsOR2yrEq1b4VFrG zBBIJ4J-@(Yvo~BoEMe~1k0m`UdcK^`yGq_2xqeV?!|ctZoYR=9%a9{c0!b9(l z+!qnZ*j(gvl;IY0e{p2OIVBs#1=ch7+r;R~dbm6OkYg-=RN5vJ!+UYrcGjK3^4&jq zKb%vuS#UnSWBSoQY$j<%N-@h{`R$qS&{B5!!?(XD=T_JY&vGgkdnmbfhw%U9B?ogI zYtF4&vxGnn7WV~hGb9h{GloAtSrNhh>ia|aYx9@MzJATJV6Ek$q-*@ zOj@x@`L2BIGvx$*;X}O~oVj6D|lSE>v`Ka&Bp9BP_9?kx}qaC$hRf zl8W`mvO6bS_+jj{pxw4@`el2@Gsm9aF`US7uJzbY^S1Nv&z(M0H+%QAl%GHLtXH~P osJORU|E@vX|2(9^sN*O5%PB2(IfvGY0?R=LPgg&ebxsLQ05LJsmH+?% diff --git a/selfdrive/assets/navigation/direction_flag.png b/selfdrive/assets/navigation/direction_flag.png index bad12ec6664679d0d42d7f270d2740a80db95d5e..26e8a220f27c5b3b4360e925dc6b6f60a16589f6 100644 GIT binary patch literal 128 zcmWN?NfN>!5CFhCuiyiQby)J738{ zEAy%NtVLZ$%t4a-VIk}ZbVdTgmP7TvMdz)dqH{S?i^UL8vsA2U4m}zOpnbIHst)LU M)Pl#i$^c3H0Ikd?D*ylh literal 658 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvECYN(T!HkXM~~jWfB)snm+#-d z|NQy$*RNl{fB*gi1AnoB|NsB5*SvHZs9C5a$S;_IQ$Rx5z|J?Zw61&p#@&Z5+@0|+ zFSZ||P=VonrbeOS@!)~ojZ|Ar=^J1=zJ%quy&FiV@oZqE~o&-^#v zo`Hd$+y5%; zNL0Am=-N1iU5q72K7z;N@Bt?WD>gBvONJXbCQNH&ZTP~orqbi`0jc&9-ZhyXw+~3Q zec@db>GAl0RBH*}8c&bc2c%lQ@U5}*__QIUomo3@ZiuwwU$gDbP`D{w@2c72{ z3FjW3ne%v)CHsuxzA4XTKAo?8qx9Ty#vJ#_NpfPRD{J(hH=g?S=8(gx&E3qJ=hmg3 zXcKbs=FnB#(sF@9MpvUweb0losDBB^b+`TqoIN}1 P(+7j6tDnm{r-UW|YGF%* diff --git a/selfdrive/assets/navigation/direction_fork.png b/selfdrive/assets/navigation/direction_fork.png index 3e0c262e2a2785e2795b9312f60d432e0171cb62..c3019a599104895d193eda4a3a8afe39808cfb79 100644 GIT binary patch literal 129 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 diff --git a/selfdrive/assets/navigation/direction_fork_right.png b/selfdrive/assets/navigation/direction_fork_right.png index aa3efaabca2cf7a040f0e7188da6503917c338e5..102ddf753c2e2cc803e6f00e5689d2ceaa211ee3 100644 GIT binary patch literal 129 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 diff --git a/selfdrive/assets/navigation/direction_fork_slight_right.png b/selfdrive/assets/navigation/direction_fork_slight_right.png index 3596a2fbf2945d487ee19e294413603a60a21fb6..65db839df9be088a655df450ee323ea5a6862b8d 100644 GIT binary patch literal 129 zcmWN^K@!3s3;@78uiyg~Qc9@(4GD=bqtcPugRi%zojv6MB;v&DjO3#R MZ$|rxh1Ey%2m3iFK>z>% literal 1805 zcmb7_c{Ce{7RF^^+C-BwN+UI7&}yjZVri>P6-6bs zSRPF>)d&^E)KZj6CU$DgjHRNqnx%+5@4fTR={slsncw;D{myspJ?H*;@A~;5RFrg; z004lBrw2S>&wKw}it>B?aXOZ^XP5nuDEB=d8yj0%T3TOU-`w03i^UR&n+S=Ow zA3Oh#^sgh6$%ZD4Z|~g$#|QWX0u+>i+8`rS>r?RW0z$qg#F1`iJ}jm_X=v%}ADWov z3qA;?Bh%pkfP%Rv+y#|LUL&tOuCqLlu9msoX7NwMW?7QMMSmdA%W6E^cLtXY`a9Rc zn`bTCwK~vg`GrtK>29n8#UiM9dOoo&RZPKW>dse2a5R|W4gqH0KeVf(yEaIjzrhPz zXOx(?F$c%i7*^KN@nIt8LPwkuQ>|uB7h=_tP3Ya~LmzlAiWqxdNbGFq7UC{=tRkXd z2k@?_>r>I{fpK-6K^W}Zh|oE(YkhR_Q(SONTIe4N&$ zGgwHJ^d+iO?K@UU{cvm-mY&&LmHTv@mOwY@#?sY<6&CfQ+u+h=q-Lv4TF`2)Gx;O@ z$LRR@66ZQ0)cGJB$Z>MYWe$CkZ`>z>6#{7FpBPU=h4V!Y(ujTKqZwATA)PRk0H#|n z)lL9WMw5;SHVa&9meg!uQU2_n<4(sDb?jgA4yDH0P71 z-6fS+bc)6-T*D1B96LaPs|G}nIcj=s$RianD?10QUc&_Nt+(Z_JV}@syP?sId2=1BI-g(|gmLY;JB-g&! zd`i#Dk=RhcS$eFA+>ljhg+1Hz2a!6%$e_mrmd$%iYplt|u0lPRLhq+6C{*>21^*Fj z!mqo@kf*~0`B#qaVAPdSn&r=8{MVShyfaFblnnK*&qyke{d zkxJA(?{@1n$7^&3n@1Wn?%-#~dB$lWoT%;VjctQY&rtdIX0&38RTskfO)pBv%VbjB z#+D&c_SIX8{VyH?NgsQs!reQPKA$c?bAV~5Q>CvDH}cwim=FSVZW^C;nwTByrjU#y zoqF7u*}wYZ5rU%|@`hXh!85Qsf&wmQ@~*N+N$Cb1<+}{p>De*0e7~rp2@K)=m#ry% z;l(L~Z^l-=3M!K5k#{60N{x~2?{)R)U{vrdm-NaDK2=~#O5`|>o*RrWS+e^L{bV8J z@b*K!il#~_jnPIG=B4ZeEU=%u+EABwKD1ik9WToH<0Tw_SeW-4Crr&OkMz2hJjB0l zww5+DH-+{+W`c35rp2|kCXOs%})B;mvyrjgN*qS zV9VJKN&X4NZ{^Bbx@18EUT5Xmo_&k^iLo0HeE6i<^$n@zPrjg$i&^@a23bQi|E2&WXBmys8&{vYfVj+$)ORUOgZwv34ri5peHTxi{55-A z;Z&HO;7;^YaO2A_!-tbvpg*7Nt-a#dS#N0}$T;G@DP_r6V`$`XOrK8a>hbQ{%Y;0c h*fKrz&=*SiSvEk{C&;49@q2#;;OXuIXSiOt^)FTRxGewx diff --git a/selfdrive/assets/navigation/direction_fork_straight.png b/selfdrive/assets/navigation/direction_fork_straight.png index 86f30ab9b6edec7d892cdf1d7bde9999a85b8d83..74528d230c415edcfb335db5806fe69191f5abf8 100644 GIT binary patch literal 129 zcmWN?K@!3s3;@78uiyigA&^k|8xla6QRxWw;OliSdzJU>bq-?A$ zo%p2xct9b(m!^5u^AYE0=Yr;w$z(1sFR!n!Z*FdGZ*TAH?Ckz6E|o~hCg!r_Jc#UtiMgDjlJ2`LYcbSI z(rbz1a|IK@CrnY6l(m;sB84PY)VRi^nh7hrMN?`FE#myJu?blmtqPJ^*V`%(C)fL&{73dx6K6D#yuis*w3A6_Q$LM3`L^hZ?lwZ zg?v0?XF`u`7y~q;DU7n1r*eWFZ8JexUGAT5vm5T`HDSfNmm=8xzu1eJxx|7o%WPY8 zmZ?vjaFCGd(b9;zdOHG702km_Q~l-TC5MKEbgLQduA0mW%^7XaF-wO#VPxxhuFgu^ z%;4F-DElR8)7o_XkuhuB2{ZmDt7+}jkgA{wD%HAFGybQbx-bo?@z`XkNfDXzT2n}<}(*{yjs(Zdrf z#zSG`E!1HGsL!j=h}&|XG&Z?BVa4zMXr!MTY2YOcthQIX6@+WusQ}O7^~NmocJzZw zsk_-5$I8F~BhOmqvr=xqK)NgQ_mT`HF9(UpgX>Sv3|yneoW}n(jlav@!ld0CY$r)q2u{W1I*@d?7X*QC%ag>7aZC7A}`-D`Es}Zh($mfUQ8=Gks;Cs z5H`vno(J+wEJ8{{s}!M8s!yDxee38tc#Y-&ATjp}5{P{ShcYE4#*TNgx(}6bBKpy= zcM(g-`JyBNuqF`b6ByAeYUX*X-|IW@ZpK>qrJufTH-YUYyH!`7@hYXX#%9G;jQVuu zb!Xq-8Dzt)od$n@ZA6uY>o;d+tAJv8=W%*sdv=^5$i+*6zorq=)vTr%2-z-rE(y8n z;qNDV0kMvm)M8#Mi4;9p+=9o%`O7m~hf$eHorI#YU}+rs)gVa8w*g}23bdl4D>9ev zhY;Nn@sJ^EmwQ6T{XrkfXkztgaKj1HqJd-%%mAaNH|v%`9ivgZFd$cq?{klW?8Z~@ zE&qOb`5K#Z5X5V{4C=UdFl#$}F{_S4ous3!edvX z)g}MEf0F%4J^%V3C?jy1-ageU97s|yu^TL z*i^(cOG|?`GjgXp&wEy>-);29KBGwkRfDf=yU?C?()uU}{K<>rv?@9%h-&+zf6VEA z@+*2gsh*mae;pg3OEWDfX-rxKMS_<$11Y3%{1m99C(UsBl8-Z^kZg3^vOrqoakk?P z$SDC@G*PI2a5}y@^_XmRO(`7oJBj8J!(mPbL6&w9*AeXxAK%OTJ>NlwX8Ibh^|P~k z+#^kzQmAXPKg{1|S8c2yc8;E!?G9wwC=*YS2#_9&$L`#i9$Ce(bi9j;R3Xa1^3x&4 zhlqtMPRv4#T?Axq&3H(NfsT5NQ)nJMsj! zQl#7sNe=GCT#AC|QC;TUsC)D6l?YbKv3*<=1VI>e(8wX%rXtn_2~_xDIaGnE{xcqF z5UIvEdvEqGN%D((ps6S8zr&dk&2oz7j1GH&;kTL|9P(@0N+zVd!;Z87Hf~BP_R5gGZXlnG zA?b{?>D?N0c*Imt?4XM6Px$8Wr=nOGb zuTXgDeKQq5qSorG%_in*Y9fw%iAGFreJrQdpzvD>wn*L%U|7A?Pg{8Z#l?pWgpB|i z47H)Enbn|urW3!2=$&1B-UAw2h+xR7RAU>^r&OvN-@1x;02w-PJ5t$YHJf?4TeeD( z+*lG73NeP*TcZvCF^oJB27|J}P&T?Oe@T1*_rabHx2IHIL1^)q*uXCiC8Hr&3<8fh99zKPzkG^itI=9RA zO&gDwnnz)Eq8L5P*+^Tr0|c&MdXcI}p0e;B3q_ MkeK~fB!V;I52R!!;Q#;t literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HlU>(`$@fBydc`>$WW z{`m3Z=g*(Ne*O9l1AkD#U!uYP|NotiUB3skK&~XnFPMRiTR=+9$lA#}I667Iq`IkR z^6X`6Htsok`tt3kuYYqzLC#x{pH2;BU>XL&_Txqf=~ zdqcVH)8gOXE4Il}3s-*cDQEm^zH?vg`$)Nq_V=1jT&tepo};?q!@OmC`86v)&s)BB zdl8=|+dbi}#v1RMPON)v8!UbIZY|%$sow)~*Q~O1S#~ej)ck7Eo(CTbBO(?4%f!9; zal`tBrOUoO?%B_@r#Yv*vyB%1T){VS~I3&J9oKW$hadM~)R;kuS`^*`w+QK~w= zIS00$x;D>_C3+R3{)Y~?gHvm;Ti+P*pS?43FC(X(zOWKI2b=}249>Ik3_OTrAjg{58AP70WH zgCT3`luJjR?bqLSpY!yd?5S6PoS4-r)78wbM=f>COe$UzI%|sBw#m0YZj1!-R)G&n@|RrRHVM{Jmi-wSCQ|iwFIkx^st37A*nM9sFaS!-6;OQ^D1 zuCIPtGht=e$|-6`gKi%eYR-6V7reIU`}UB`R`yBF8n5k6=LOZ7-g3Ra^oD{*>RtKd z{_16QADFkBo`2@%cy8Ij{9hlwtTf#&zhcb?hsz2Ncb)&d?bgNj!cp-L8=pDmz5X}j zrGQP$`|^pRav8!;HofEDEqZ0YPZS&D5rqs17T)Fsi2^1(O>K@B3^=6N91qh<&^MlQ z(o;SY|K)df%KT%_4LSAp%L02=wRJ^@Z9klU+Npi_{F)VagnK(6;9rBh_jJ$Mde?Wn Q0cJ=BPgg&ebxsLQ01@OIpa1{> diff --git a/selfdrive/assets/navigation/direction_invalid_left.png b/selfdrive/assets/navigation/direction_invalid_left.png index 9a618026f0e00dd044f359e1f3fed8c668c63b6b..438532ee2995e37090ea2433fe7a7d6d27f97bf9 100644 GIT binary patch literal 129 zcmWN`$qmCG5J1s8RnR~T!#3F6unYky30A;9r226BPx?1~N9!M6=RC(D#Iw!EtBh^6 zUU5tPEyhVn+`xLveAJ+NtYng1f z$Vhj?PqO{GpSyY<9WJ+-cI5V|zNAOiO4gT>eUG0kcPX2)d0o;oYZdKjrIJTgKgmvJ zy}D*~#b?@H%CcawFGG@5CSOn4ED}Nm-$M(xS+4 z!7l5{%-w6(<gzHO_-&?!rMr~fF#8Gbac8Xw&^~)2xx)|pe8ySA^f8hHwySU!HJ^F6VZ{GJymbPt)dNOC~nO%?dy)Ea>PI-R*aNX`X{%sr0 zCHFhus>&%9^9mM*o_d(r*(&eJ59;+PBS?~~n)78E+}mr2)KbMy*iiOecNw()p`AQrc27| zTF;yM?K>2T&%JQy>S3KIRN~@stZmf=W#?$CZz27APVZVVWx`DTaKERsWoHVh-@Dj3 z$z^wYQL3#^P{Z?!r{Cl*o_>Mzy4si1HOK!LNS!sc6FsE3ByZ;aj2P{C+k9F?TWpuE ztGvy7c3S4Htp5o`hwQq;^yBKK>oz!iSNm7IC}&f~)XKl%OID>pF4Pq=ihnHa^W|M{7o z%+8%MZ*MG4y?1E0lKJ$-d!4qNi9TcbaQ5k&uXKZ+`PQDA$`O-QT+Se^_t{L8A#Ou~ z`QbKmE}gC-o;npi^P2~Z^4|onYgivCw(^eLiZjtYS-~If742$e&Y3T#tH(UcJ^Z#{ z*AB^M*T8uWPA_x|rn=4C9+A%?U;bSC)#Iv}+h2$|eV_K*^dyk^L*ro3qlXq=XH#eV zUXt$7aeH}eU~KT_51JR8opu*D2WWm4vt!L-+u;}8>?R=&`+Hs%ZYkRxeW7pr5Zqi`jRB%Z@ z;N_8X)vTfANp1TxCc#6zWTD=6u}77O2l!Am14Ta>EDLP;k@7*~JV!}`%7*%`Qf74d zR>o7!lZv{G7`@8b5PUe$X`@Rr0LZ&ZHP`Glr4(StnmO(qqqVF7az+T20z1cn8Oc&a MFJ}LeG&UOX1IK75y#N3J literal 1371 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvG6Q@n>Sy)c=7)I`%j-f z{rK_Y7a078fj_9=FInLK|Nm28tKA1$rc@H-7tFxUEg-69V(sJ|6qTG)T;1F~Y4)Nu zn|2>QefiFVr!U`r{AQ}bX~Mw3Qt#>F7*a9k?Oe{JM+Q8ujVdCiZf%&hw)1`ciJ32X z8dZvNEKh8*u44E2dT`=C-I;uBO$r<^^k;v{?nj%>?pXZ7zbttF&shb>FV61$b-K%X z!oE5GI0JvTSWl^&^WN!7>KR#YeLF41Rdb5jFSl>0(FmL^SZ-l^!-g|(zToqRZz4Oi zmfiWdWAl2=!0dMwb7o#?UEzF6cB1xtUe$}w9!5HQY^kiM$kYnFZSncTB2|~Ui_RQg zylAb;Ih#Fs+?~e!*K544R8}x^23|LRx8p*}0)bGy9n8*~+ov9x-agxIsnRZ+J6|+K zk6M>!SVY-9J$QVAwS`FT{Gh@fEq-RZrH{md<>YEhH3GfgeGq7{Tbd+(exvxCV6Af> zpCsN`9x}!F9YgNLA5+*Xbt)sC2WusrJe;ql_)5(4x8v(zxg!_rD$Kb%m;5c*8c@{t z@W$%|MU^g_8ne?soIh0VIDGm#=kC7-Q6;mdq*vTNaI<9JJ-Kt+7q#q&T>Ya>lVAPv z)qR(jKTbSx_~xlammYq+QzxYTW%Wa4{^dJs%}U$% z01adE|eB#b_zFE!>k9>dacty`Qqc>D~5!a(w_CVk7YnxA{s@ATyyp$C5^WMU%6R)Ih zpXa}+#Dqm+iFb?Grnw!K@9%vozq~s$w&lgQwq*icceDbd@{YXl^S!@I{Ag<8PJuPo z)D!Iu-`Y&JI=jZ-Vr`jWi|oC1O>qi)US4rxOFh5$^$D@i8eJoc)bF}i-!Cp;4o}|9 zcWI}{-$PDfYv+2hy`9)um#AoOms~xotc+X2{vZ3b%QLgIK5l#z^t)C;K1Jf;0=pN! zCDA2o`VOuNX0PN9F~8hww@|WFEc8=aN1koY1^-ti*j_CE?~Ys# z4crj_`{f6P8*?jO-#RpZ@nJ>#nBG5MFCKjOa<%g$#?=##pJX#gw>;C);Cw?a-B+j} z);dK0!R)VAt(2O@`$GxN9_G$b)c6VXjeCKa} z-+69`JJIULCOhqTMf|ktYcp#zgauEguCr27F>&$8=$LRpKyjg>laq5xOB)9#ub_|= zVTm7xPH`p%2l!16DLPsFH1zgH)B8t4aYAO#qvJ1UGi(=Ff5*_0@lI<0V|n>4H_S?9 zl7E{gl)tjy5c6c?D+!*q$Lf!!D#FVTj)@<+XC~|a;`*a`2Uu`1c)I$ztaD0e0ss*f ByXgP` diff --git a/selfdrive/assets/navigation/direction_invalid_slight_left.png b/selfdrive/assets/navigation/direction_invalid_slight_left.png index 08e964dbd6a68a9b82983499adda5a8bc78ac1a9..29f44404608bd362089b6aae0c45d5a1f6df3e13 100644 GIT binary patch literal 129 zcmWN`K@x)?3;@78uiyg}F$o5Lg8>RNZE*^E=0O!S98_ka)FR--p$H*(I&$YN^{laaWA^&#_v<-EeHR2-Khu)uSPdrqs z%R4_kOFgzOFZ@OC|B9V|JNmA*_TRq#A|ZB;P0O1L2X}oatX{Wli_nLn->#`#KM(3} ze;)s$;%&vVZ>m;t+ID&Rfv+pxd;MEuvio=VrUVZT~-`@|9kHuauHf8C3urD;jug$lQ7WE{R8R_ENsB1nK6-!NoyQusn?w$u(EV}!t(|sFPUhBzqmM7|6koqS zQY}vI^yd6|dEf8Y=$ake`Q&BU`Ow73m;*7N3U;qvE;R9TQrm17n`2@BEFI?EdH7Dm z<$IC#gPE^FM1=l3cF4|@*`)Krw&P8?sBwtB+O0QoXB>r?sv7?Yeez$Fxaw_pMA6H= z|1#WlLN=VOmY6u@-rnHVY)70GzWgz1nW>V}A-&`ScVXKhh14R>Bse^mVGt-SN)PFm6r%Y&UJxM@1)+;&yW56O@W<(c9|iO;O!eu1cJ!0T-+CuOctIiHwC5+gYjanl%dH3>VEM%0>FVdQ I&MBb@06s*zH~;_u diff --git a/selfdrive/assets/navigation/direction_invalid_slight_right.png b/selfdrive/assets/navigation/direction_invalid_slight_right.png index 3e21cae11ed5209a7f63f7352d7ca2c4ef5b0b14..755935b84c512c69b72569720de3be7f485cbfb4 100644 GIT binary patch literal 129 zcmWN{OA^8$3;@tQr{Dq>FnpTc20{>KR63@1;py$_ZQjWr8S}0CaqO}g^BBkN*<`uw z-?Gqr8QGc4>C_xmOV9TZH;(9P^?*JRda#XZ0*-|%qt7|Cx*`Tfu)rk;AhMaliuvlz N*{EdvSUd1Z{Q=xSC)@x4 literal 1347 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HkZOP5}}c=7)I`_G?0 z|M>Ca7a078fj_9=FVW!t|Nrx>eU1VxkShuD3ua*D77$l8ws!Ikj!w=lu5Rj@JbTIN z&ASetxpL?6>mzF(>N7B~lzX~3hE&XXJLl%K#Rfbsk(=NDcKi3=cy@`EtojKy>pAO^ z+`gA4^sQQ$<0;muSv3bA5XEv~P#B&%EZJ^m3=FqQ-iAN!e&YBlp)Dl5e5}nm+IH-1jen!;$a5 z@3uc@z9+1oy=3!gcBc<<<{if$Y8p0Q_ig*_pePe({xY|@WkbbX6_rxOPrE$LzQ-O~ zu_tem&z~h5D(^0MuG(_`wf`QoYPa?FGb&#weElUme`|I(-!;c)kIRzQKaUGN#UQ(G zYUBM^-mkxQPVadwDZ9RR>3qJh_3HPg>My@;{C`o)rPq5KCYigk>k3?2n*QaNjRX6_ z>ytMeGn;#Tv-1Q#tM$(p<|Y`e=XbU)Nr{`!c1q!MRZMVgPMUVMd{GaZxg>C8n@Zg7Hii(G;ZOfLawn{A6u=DZWcQ$4g&p-K2ZSVNL zYqOR8jj9DxugTtdeQr<6`9n4*S3Nd{S*$ zee>ZL_D9=$w}*fF;?5X7A*ffr&(vM^#hB+mTg^sD!a7x>n_{k_-pt7?8iRnZHtx9QDx4?ezPT|?aVIXC2NoT}swG4IXu6yu(Mv32=rps3A+!oPFb_UkTL z_S*S(gv_IxU;n)IeY8h!yZhd_H76?U6XVuTaV_6tFQ#`PZu)Y@Z}n~RPnfUo0pPfKqz{iF@ zEryLD-@W}TCX^j!x|6`|=u=Zz9cA(8(d+$^k0w473)s)_NpOi$F3PaPp!@`z0o4S^2||x zT^5DjkFpmXTNR$}i`7|mL^$(S{`Oc8^#yb9@GouJ+srFsdG*y2ftQ^npRP`_aWdw4 zci4V&mS^cyF2_YqHv$xMRtT7da7b&l@QNO4<4SZEdSrj!r2)uj-LMdwywt-NDQER+ zjqjdhw8~tzvDzKPzkG^itI=9RA zO&gDwnnz)Eq8L5P*+^Tr0|c&MdXcI}p0e;B3q_ MkeK~fB!V;I52R!!;Q#;t literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HlU>(`$@fBydc`>$WW z{`m3Z=g*(Ne*O9l1AkD#U!uYP|NotiUB3skK&~XnFPMRiTR=+9$lA#}I667Iq`IkR z^6X`6Htsok`tt3kuYYqzLC#x{pH2;BU>XL&_Txqf=~ zdqcVH)8gOXE4Il}3s-*cDQEm^zH?vg`$)Nq_V=1jT&tepo};?q!@OmC`86v)&s)BB zdl8=|+dbi}#v1RMPON)v8!UbIZY|%$sow)~*Q~O1S#~ej)ck7Eo(CTbBO(?4%f!9; zal`tBrOUoO?%B_@r#Yv*vyB%1T){VS~I3&J9oKW$hadM~)R;kuS`^*`w+QK~w= zIS00$x;D>_C3+R3{)Y~?gHvm;Ti+P*pS?43FC(X(zOWKI2b=}249>Ik3_OTrAjg{58AP70WH zgCT3`luJjR?bqLSpY!yd?5S6PoS4-r)78wbM=f>COe$UzI%|sBw#m0YZj1!-R)G&n@|RrRHVM{Jmi-wSCQ|iwFIkx^st37A*nM9sFaS!-6;OQ^D1 zuCIPtGht=e$|-6`gKi%eYR-6V7reIU`}UB`R`yBF8n5k6=LOZ7-g3Ra^oD{*>RtKd z{_16QADFkBo`2@%cy8Ij{9hlwtTf#&zhcb?hsz2Ncb)&d?bgNj!cp-L8=pDmz5X}j zrGQP$`|^pRav8!;HofEDEqZ0YPZS&D5rqs17T)Fsi2^1(O>K@B3^=6N91qh<&^MlQ z(o;SY|K)df%KT%_4LSAp%L02=wRJ^@Z9klU+Npi_{F)VagnK(6;9rBh_jJ$Mde?Wn Q0cJ=BPgg&ebxsLQ01@OIpa1{> diff --git a/selfdrive/assets/navigation/direction_invalid_uturn.png b/selfdrive/assets/navigation/direction_invalid_uturn.png index 0bd1b91777ed8d3c487e188c945383482b219c43..ff9d97893fb766220f44cfd48c7bb1501b47a85e 100644 GIT binary patch literal 129 zcmWN?NfN>!5CFhCuiyg)W*{v2&9ID6rIHfN!PjeF`l|2Q$4j=g&Uq+x@9Xy9x&3dS zwB_+s^Q>82AVx28Hp1N8es2Px>@rEjv;7SB$P7bxH5Gawh$wrfkV+#f2ZU`W! M9L4P4D!~UWe#d+#dH?_b literal 1780 zcmbW2X*Amj7l#vDB4TMe!Kqq8(GzNE6(!mcX@yBDme84?A#Erv(uP{PF_sQ#TD5dS zsM?Bu#?n$tLfT=dWsDk3YbjE-R5YE^O3)ZHALhL8$M=2C{oUuwJ?DPA_XpBRcXikv z7zhMX_rT+PcKrPBKoxg-L3%K6M>G<_@A!^KMn)DF7bOzO+S=Os`npsqmC0lq|FHQF zTmQ|M|Ap=C?GRf+?amq_rq4-Vkcyg?F3J>bhdJR(p+z#2Qdl{KWx!*8SO4hb%v*6P zhn5clsTg_ST>av?D@Dr%r8e+%2&SYIUL1?DS-+mQ%<#icG!n<(ug-npJD1rYt*-}YqocT$4L$Io!im0?sXPxcqnYyx z<0lcTG-SvJsSEqxV10iv_@cHjkv&FSy3l?mFxnGw@>tj4UZ?p6@|CN^Ed#)O0`rKS ziR(^t3v`!N4l_9VG&E{t7g@EMebe)1Y+!EJx07B~v4Q!)wy%Sf-?b-A^76A}p{?U( z@=YfJTi$C|wfdx35FL@V#K@7e)xjWc_BW6lcyn_#Ay+C|TAXFx{4JR~>FvbO6phu>l*_CVvM>m3I z74R>AIGgaX{ax;?y&mXv$8<-)uY*s?dYONs!C!`tWrH#Jv+5;SHCW1&qO|Mc`>8|E zjedzxNX&>C+&5$GzRTzP*A(t1&L)c3KuXIW;qomOT|6EMX#9GG@Cwh5N;j4dbaM+$ zscgDRx}b=0sR=xQGA@UfbS8GQtilgrbDBUz5{rV^Kj3jF+EsG8y9|Mc-a67@0kJp7 z`W*Uy;vV<*3Tvr*hHh1u+ucP+qnbxp$gEaDTmAq~9g>2>7FP=>ov7^gR{0jgDI6bf zj`--LHyNo}a{gCjbgRV%3u%m!n8){c1|`zNqP7@wRbKl{geCz6t$mUU19?BK65DgM zRF}%>mLWp-LVl9eIsIo$O|!p)&KX6S%kbp=456d0r)hQSnWt$Wp*zRohPbgiH^Rdn z_4&bh0ejkH;MOyE7|kC%RRFGqoqMM#%wT^yhBz>8Ag7+Q^J_o|t zk|pO=g~I*#jvvh4on$xUS!(gtYA*O&|P**T3DzPPegOX$7vnpiAFb3go52@ zp6HJ|rlbySV_vAv&IleKbu{V&uT(S7H4F|&uPvcYp-V!7*P_7sRu8@P{2Me1n9v`& z)vCZt2$2*JhA349v>LBMIaUB1ATcdq1?vD5Xi*|Mr4p$s&Py+t*EB4Y;J{F0h$`1d zBb6-B8=6wJEKi#Jdh|E@#yfG9IgvxS5m|_0M3BcA}Zn_hHs^S7rE@)kjnA z1ZbPTU8jUX6Vl07Qq1&Ap$TUgcgK^p)wfQYwR|cnzaUiS;n&MmsPwg<^ZXu~YsWYB z#@lm#W0@n|S1xlW20v_R)W{5DIg2{M%KvfRZqUx^eZ#oxcL|IDQgWi>0Rg E1z#s(%m4rY diff --git a/selfdrive/assets/navigation/direction_merge_left.png b/selfdrive/assets/navigation/direction_merge_left.png index a713f52c56dddf61a60a7b2e09d9999eef265415..7812f9eb94a0bec9d785813e8b11be78fadf69de 100644 GIT binary patch literal 129 zcmWN^K@!3s3;@78uiyg~LO^JLQ$ixlsB{E-@b&hzvnPK>>u+7>JjQ0sqs`k>#&TJ| zd7=I?<6tr;sNRYmb$8*u+2njkE1(5Q;DzjUKxABtpk(mTS?dB3vdM-P6ys+IStz|Q)NE{M&D%}4ArubfEk<4{FKHUhlGoxUT$JSP zmaAXtdy(Gx!kd4rkZ)XI_GxJxqsg8d7kHU&hyXnJm);;n?oeHf|YcYKp+qp zkHdIv^X2zcl-sUl*#QmP2qk*>V7JNPaOUUdMIwsH+1-m25)q%AK`kh&e%$nYaQN-i>|a9hmqde!JP=6U z6puOKlXPELzEJegPTd8<62G^5ax$fo{(Q?_e=*87T?t`WZGJgo>t0RdNnt+`K5K&? zuu>WPCr0|*$&5yZT0v{lU}L|sf*}*W2%wr89Wd5|$Pg=u00}rYFX#-dm|x|FoVLj) zq5Oi2Z73hdobT=?Tg&+==16vQ?+ptLY@tw|9pID`FXZ{tjYMEI&sWXvals!~)OmI+ zQtsF; z-`@Eb>}f(>+(6@LS8}TcuwzVM3{1Cs&)6~asHymtVIb_U2cC(&4nWOWmG_CEW zGySi~-6PZ;-|zU@-+{tn*K{k?OT$0z@_or=>1ute!)=T~54l zJayay&TSd^ns4$}AzdGyxM5qCz89{LfQF4rOA0qJR{W4iAWfYjj*T((IYFf{Ffyxr zUR5!nu4Qm(X?&NjmLlL^zC`9Yt`^lc-VDzAe$vw^{GEmwdr9wR_!}`PRms7k{dMv3 zo_mad)i08Fq%GC`F{$H(>ba*k5la~;$C2rT_FE6co(Jgxdb9HiY0iS_F}-yfz6o$QdaF@B~It4%0qL)hgr(FS-tLk+6EBx0%0T_Gl0rX*$FjkrpEmQ zg>bQ*px&An$RYU{7b@4g0WuRms?}|-!07B$Ow=0$U_G$m9!6`8bfL5%9Sf{q>IYzX zU#N$}O?Z4|MkmNO>J9iCiLr31D#dMtrL>rX-Fx3}#nL7-n#8qmv#iG+ zN^Hb^ZFTCPhI6Kw3Cg`xM44rpXU%Ff-zn!j+l@62e}*mz#-i(j43^HR$)-(7>?q;!=J|p)+VQPoUC@xF|D@oIzfQjTI9Rw z%~UqhTy$)jIQzNfOOpS3-<~7d^`%HMJfLN`cB|9Mf*u`8ef2s2H|p{)yW=c|rwU}W zIiVs=_!YneK_Z$IZT+%z_oKl-ekAsyUT8cZyQaR931oclW-s>S>pwlHl diff --git a/selfdrive/assets/navigation/direction_merge_right.png b/selfdrive/assets/navigation/direction_merge_right.png index 3390b31a05b26bbf1e96eeb09662abcfdc076d1c..3c44c8fc75d3bea18fdf4f835c2c379524f319b4 100644 GIT binary patch literal 129 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 U=v}`@w(@=e{Lq0Yj#q5fpVI diff --git a/selfdrive/assets/navigation/direction_merge_slight_right.png b/selfdrive/assets/navigation/direction_merge_slight_right.png index 8f5289011d67b43bca6345d3b86f17a57d9e618c..b8d42b741fc9ad5bca6be307b64d346a1df72a6b 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@tQr{DsXUz0*_Lr4&2R60_-@bvmN?~?cIW-o5Ehn9hyYP?A_&EL ML^byNNed$7Q9iVO5EmilB8F zRkYd!W!1P_LYL6iZBk4t8_FVU)5(;s)YA8ycjxTiea`bdpU?C8e$M&)_dFaz;Ae)A z0}v1hWQfOMf;24su)*4zv+8oFKtpi^e-c*1GMQ{;Wksb@snzPOt*t-)hj;(S_TRDd zkFdMDD}!C0)|3G$L4ib&F2oq-@Gqp#DPl+zGdbg{E2Y(q{EoiiM?XAUQoQ_aV+(z} zQUL<(k>D|CQd-T+Zxk06j~V#Jfy*X3j>b4+RJ$cZ3pUbHw|+iUkYYF}nuq)}%WYkRT^DoW&-4^sQw-(jr&zR2d4^)c zkI+Y$Hr3U7%P2)iXyjg_H+NEwA3etUi0`gQ`meech7AYkpVOx-$`q%lBwkyg-l1~~ zFbUa+6#&plh8?j+tnL)@&nbJagAE#$5ZmAe%aXxU)Q_bGo1odbo}<}4-k^a-rha{O zFE-qXZc+E<>5^6nen#?zVuO|7xcQJP*A9n$tWg&tYujhRUW}ESfsspE+h>T{^%c^$4{xVA&HlRuk=Y5=lJbdfRJkWbbb*lVEslQF|LkO)^T8sruJmdcYR}Yt zc7y8R6>`Z$bZ+Kh4DfKb+;794;S_zaL!F zFUw|h4(aQ#y*IMpJE2{IPg2uzt+;-2_l}^8f`x1E9kXvp@2KoI=g<8+tVQ{(sX-II zQqu|_dX&FBOK2=Ds$fzJV9nM^i%U%GdzhO)3Yw3B#{adp&7s5aLUta9QRNk(recuS z*_TBu!bHmKO)zpU;-9&&S8bt^yPG|ZGbL>1bYj-q>0;cB+wVCR zZ;rv&U;Vh6KF?)hZIX9B*(?0@M>bCGS&>=C>a<>5Sn26b@VRGIn)blr#DBW>8z=gU zVP5?)=;&YOSOgD4bT3XK)Nbf9q6%>tYtQG1hvX#Zb%lH}1bKkk&fYw;M|M256G(`P z9Hhl8cnO1B$fZ>HTSF{isPuA@S-K|Oq0ojo=7hTR&gXflE zMvdp_K#|=ERKsoB++B=rO;q)*ReNLE2d~AXP3ia$7`gPVIwR|hYRznfW)Js&Zu9_& z33@65zpIPs4T4g5t30vG`Er2%G}kq(DUOy8^x4U&W1bWCrR~MTHRN&K2|F=!{fW93 z69b$ayMvTiC*9;AprO2@liXA+5dXBEm?61LM*&$B-sf~Z76Xb8F4(aS$o`B2{&obl zn+M^#C`)8bsjz%qZc(zm9IA(QG>)(c{^i`d7X-84p4Fo0S3@vT>_~3%`HqjE2>alB z5BwaAf6M8-@OX)8%5JQ^Ey@%-`{shvM<>mEdGblCsU)ZLU`<6nID6EsZA8^`Ei3?;!!mhmAl4RWntxY6h93{XK-_s3TLLt~Qkynkwfg!lG;U*@ z|2FgNW`PnKXWNga-K28{qLW$ z)%jF>QdO4`bD-p&!G{BlE{K$XV%h_ej(`LSvkT4udV&_}uA!k1&el*|%Bj`36FRWa L1dlICW32cAuv#Z* literal 113 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5y8Awi_W^)%vF$egBxB}_rRkpW)6k|z{UoeBi zvm0qZj*O>^V@O2n+k=fj4#VLEKZD~RGK4aMI5lh{0St_CsaKDKc%H6)F6*2UngGLH B9D4u& diff --git a/selfdrive/assets/navigation/direction_new_name_left.png b/selfdrive/assets/navigation/direction_new_name_left.png index 9a618026f0e00dd044f359e1f3fed8c668c63b6b..438532ee2995e37090ea2433fe7a7d6d27f97bf9 100644 GIT binary patch literal 129 zcmWN`$qmCG5J1s8RnR~T!#3F6unYky30A;9r226BPx?1~N9!M6=RC(D#Iw!EtBh^6 zUU5tPEyhVn+`xLveAJ+NtYng1f z$Vhj?PqO{GpSyY<9WJ+-cI5V|zNAOiO4gT>eUG0kcPX2)d0o;oYZdKjrIJTgKgmvJ zy}D*~#b?@H%CcawFGG@5CSOn4ED}Nm-$M(xS+4 z!7l5{%-w6(<gzHO_-&?!rMr~fF#8Gbac8Xw&^~)2xx)|pe8ySA^f8hHwySU!HJ^F6VZ{GJymbPt)dNOC~nO%?dy)Ea>PI-R*aNX`X{%sr0 zCHFhus>&%9^9mM*o_d(r*(&eJ59;+PBS?~~n)78E+}mr2)KbMy*iiOecNw()p`AQrc27| zTF;yM?K>2T&%JQy>S3KIRN~@stZmf=W#?$CZz27APVZVVWx`DTaKERsWoHVh-@Dj3 z$z^wYQL3#^P{Z?!r{Cl*o_>Mzy4si1HOK!LNS!sc6FsE3ByZ;aj2P{C+k9F?TWpuE ztGvy7c3S4Htp5o`hwQq;^yBKK>oz!iSNm7IC}&f~)XKl%OID>pF4Pq=ihnHa^W|M{7o z%+8%MZ*MG4y?1E0lKJ$-d!4qNi9TcbaQ5k&uXKZ+`PQDA$`O-QT+Se^_t{L8A#Ou~ z`QbKmE}gC-o;npi^P2~Z^4|onYgivCw(^eLiZjtYS-~If742$e&Y3T#tH(UcJ^Z#{ z*AB^M*T8uWPA_x|rn=4C9+A%?U;bSC)#Iv}+h2$|eV_K*^dyk^L*ro3qlXq=XH#eV zUXt$7aeH}eU~KT_51JR8opu*D2WWm4vt!L-+u;}8>?R=&`+Hs%ZYkRxeW7pr5Zqi`jRB%Z@ z;N_8X)vTfANp1TxCc#6zWTD=6u}77O2l!Am14Ta>EDLP;k@7*~JV!}`%7*%`Qf74d zR>o7!lZv{G7`@8b5PUe$X`@Rr0LZ&ZHP`Glr4(StnmO(qqqVF7az+T20z1cn8Oc&a MFJ}LeG&UOX1IK75y#N3J literal 1371 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvG6Q@n>Sy)c=7)I`%j-f z{rK_Y7a078fj_9=FInLK|Nm28tKA1$rc@H-7tFxUEg-69V(sJ|6qTG)T;1F~Y4)Nu zn|2>QefiFVr!U`r{AQ}bX~Mw3Qt#>F7*a9k?Oe{JM+Q8ujVdCiZf%&hw)1`ciJ32X z8dZvNEKh8*u44E2dT`=C-I;uBO$r<^^k;v{?nj%>?pXZ7zbttF&shb>FV61$b-K%X z!oE5GI0JvTSWl^&^WN!7>KR#YeLF41Rdb5jFSl>0(FmL^SZ-l^!-g|(zToqRZz4Oi zmfiWdWAl2=!0dMwb7o#?UEzF6cB1xtUe$}w9!5HQY^kiM$kYnFZSncTB2|~Ui_RQg zylAb;Ih#Fs+?~e!*K544R8}x^23|LRx8p*}0)bGy9n8*~+ov9x-agxIsnRZ+J6|+K zk6M>!SVY-9J$QVAwS`FT{Gh@fEq-RZrH{md<>YEhH3GfgeGq7{Tbd+(exvxCV6Af> zpCsN`9x}!F9YgNLA5+*Xbt)sC2WusrJe;ql_)5(4x8v(zxg!_rD$Kb%m;5c*8c@{t z@W$%|MU^g_8ne?soIh0VIDGm#=kC7-Q6;mdq*vTNaI<9JJ-Kt+7q#q&T>Ya>lVAPv z)qR(jKTbSx_~xlammYq+QzxYTW%Wa4{^dJs%}U$% z01adE|eB#b_zFE!>k9>dacty`Qqc>D~5!a(w_CVk7YnxA{s@ATyyp$C5^WMU%6R)Ih zpXa}+#Dqm+iFb?Grnw!K@9%vozq~s$w&lgQwq*icceDbd@{YXl^S!@I{Ag<8PJuPo z)D!Iu-`Y&JI=jZ-Vr`jWi|oC1O>qi)US4rxOFh5$^$D@i8eJoc)bF}i-!Cp;4o}|9 zcWI}{-$PDfYv+2hy`9)um#AoOms~xotc+X2{vZ3b%QLgIK5l#z^t)C;K1Jf;0=pN! zCDA2o`VOuNX0PN9F~8hww@|WFEc8=aN1koY1^-ti*j_CE?~Ys# z4crj_`{f6P8*?jO-#RpZ@nJ>#nBG5MFCKjOa<%g$#?=##pJX#gw>;C);Cw?a-B+j} z);dK0!R)VAt(2O@`$GxN9_G$b)c6VXjeCKa} z-+69`JJIULCOhqTMf|ktYcp#zgauEguCr27F>&$8=$LRpKyjg>laq5xOB)9#ub_|= zVTm7xPH`p%2l!16DLPsFH1zgH)B8t4aYAO#qvJ1UGi(=Ff5*_0@lI<0V|n>4H_S?9 zl7E{gl)tjy5c6c?D+!*q$Lf!!D#FVTj)@<+XC~|a;`*a`2Uu`1c)I$ztaD0e0ss*f ByXgP` diff --git a/selfdrive/assets/navigation/direction_new_name_sharp_left.png b/selfdrive/assets/navigation/direction_new_name_sharp_left.png index 77106b493ff6d51acd0e3f4612baa028c6937b5c..ae1f3741ef57d896fcb8326c69269e04f4792ae7 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUOpw+kWNuq+5ODjC5XtX|*cUFALdc*(ZbIS-}oecc{)ZvWed zwmhC{o+Ycxh|yWjmMr>10|wFck--Aw@t93uh| MSTXyzNP`f?53Il^2mk;8 literal 1549 zcmbW1S5y-Q6o!LD!;ldmMG%1m0!Y{e1qD$^WbYAH5KAZ<8DS_|Fh&fblqq2&$`Cam z$S~{;GKGT3UX_H6jF3d2^r1cNTTj39-}~MBbl+~QrMUqQmnati0N^n)L|Pxa>(Am~ zKkgYZu5XUzW@&1RI`-)3=+e^C#>U3Z&JLYU-{0S7Fc?fGll2$h|INXF=IH3C7kLYM zd_w|fZEgcN#m#>PDucM7XJX^x?t{CFk4w&cQS!d7rDu3zcIB+cBp(29QpX5+%{C%y zE&B_;MCJ5VgOnf;9rOLDQ7rVjh$JRSrixE%;Qd7dRYh+SYqvn|asEH@&E$%b{mNQE zQ`XHLQ8wt5Oy+v5j*^$SyR*4E;2@1$O{x95EdiW|F0 z*&ME^Fk|gyxE`pB%e-^1OxUQ`SXXd=E@KlW%L7TIPF_(r zLfzXO+jlS4)e0jf1Cqyrs?(Fz*IDGT`_&EtIsw(ctPUO}uXTwVO*ymcQSR7u#5ep* zZ9LCr=$Wf$idIf?6nsTnZiiPjNg%?DKerZU#TQlNM+Z-6R5c5RQ!e$FGrwuufzcF- z(ssu#9=3ooQXbG8va@>+BF16|I`c$=cG_~tyth3Gq^=p|P^75KSR=3310|Vn^hEM` z_chw`(7~mKMPy8A6hv3OLN_xPG%I&Lk?s_8SbuqFgmaHy@V4$x;X`rk29$A+m>_G| z(r0Ysq^1GINd&|di{0p!x9uCqA^YtDp@~5{XAAoqt9)#$UJ;NV?_AiP8Z5kJfkyjw zn;9JWrz?9zIqv=OFyZ>j-tx<0MnX2=nfoQeuy-3t5KuUr^a_k4feFS?xP8u zAGF+>0QUBKrfr<26B#?6K}MqDpE9y8&o&&`Zq8Tqfx$G5rI2ju!vcg+q7_1*b7T)! z8T#^g*}LBA^nk|i|EQd7E`gcg?@C>AG=XQBrvGUJw5frSW>L{*jS>{%RK zYJMy~{0l3!SXT*ur+wl2>IVjaBUr0#FZ`}4Fr)9Z1w{ZHB*d5L;xO}J4zlGH_;&7| zL@K)NP;7$0DB!S96jw1v!g2!d9F^P7k?3Rxq zZ(xH^Ied>oaY~Eaye2U?MDc{Uun1~(s?*h$JNS9LmGt7dekTtj7bymkBUO^5@rv9c ziyvo>KUbjNg~i)ziq#So#n?)H_1)4UuO#Ntv!H=8ey|RGOSPKt+%HJWR7qV+U#?7! z5`jgKPqwFEvn~F6pn9N5Ea5;A5Y0tzYKQA-12r^Vl#4WiU$?;ZE&<+qBAfF`q zzhhagM7ENLL)eWnLJ(;xA`@ME#c%5NjX@gZ)p6(^N?xTfJR(~f2x6c-F WuY1_pt2_Pp=K_pS=18)h8~zWbs|9ob diff --git a/selfdrive/assets/navigation/direction_new_name_sharp_right.png b/selfdrive/assets/navigation/direction_new_name_sharp_right.png index eb3a02f8b3adfe72430aa9f328f1bc009ef5ce4f..0d8cd3ed5303705969163847d7fb9c96a591bbe2 100644 GIT binary patch literal 129 zcmWN}OAf;z3;@wRr{Dq&;b)oL1`HIWwnR0_qNgvb(c8>jzTC%0wmDBelzR60cvaq3 z+m$vNZ^dUJb!#ztmfWK8aJULZdk=L_h!|@ETtKR}XaH-14?64>F=qgBIppNfnurDh NCGvv%ugG;U;t#afCjkHe literal 1552 zcmb7^do&XY9LI;SrePr^m$j6znnaVM5?fx4iHls8JmOMJRvy=LS!N~SMmF;pxg^#+ zLe^_WuIWtGEAX@CF#0NB^_M8iPOZj~M)YR0%!ouR>qDUlK`;+y*5dRy=|6y};v&Qw> zqpcg-g!2y0067Ka-LPY3R`$-GzCqzX5pQSGiXPQ8a(G>RLu0cM6;D|JfUJcr>WoWd z?i_tOdfada8e`+mQ!MP1Uf9f1$+}S$nZmsCILC1+R7RMH_qD&Je*j2?{$0}Xl>X?5 zDQs4<#Ps= zs&d;Ccv@5AzFPSGJT0~C3p1>N&*5H@^IyP|>1o#2V`Gc8o@{r+UGEc$i{UXRAAJD5 z;RWND)Em6slqC=OgCboHW_C}^cb<->wL2@Zo0S*qhkDAAhqpCWX=%EXdUvWf&`mKZ zqHXCPNKQNQ^(r&yn+NI9>zJUGt0E1G*pEQ&0*a(X!oG8#UpVmE3f4@-zFUU;G zS5nMSk43u({Y?_(s$-uQ8k2ONqg_IfdWnLyr+J-e8!-JkW{RY#d?hN7-1sP&-1d90 z`hsB1%Wt7{SDPcMES1kivfJ|I^HO$3h*5b_KeiaKgZM;1ri>%Rg!M+yGtr&-Qb_^t zyr||xisg($aJ91p+hu`z7^$}*BC99MQoc$aIu2q6cL>^o(;9RjVXn{DNpAa*pX*RI zTs7SDBdaazb_=fFu-^nTM+>i-%ft=?DW#kjeF%E$XxPL`hu^m!6X|2ML0XQu%vUh| z>TIs(7(eXk(D93Ic8ENl+wig?8Tv$5Tx&YkK%Caz#=d+g#P#(VjP5c z8%)2R#^_11B(EfASB_cd0^}h=MP|h(s1MEb4q0R1cGH~TebhQ!G?7wzW?iwjcn^*= ze@{gT5)jE}YD$*XJuruIZ35Q18M|<6=+jr@?JXc~k#q65YfY!Lw!C7p0sGWN|9RF% zM-3H=-ST2j=l5eB82ls-#k8rM%JgfoGc7CAaOXBOeDo=EX3$$rVU>a^ivd$L94htR zTT;Z`c9e_MeD^*~f-kwA!vj7%sES5`30*%Re=Z5sgx~|T9_ne>Zv!FhoQteE_;%m5TFFlI2;{wn5WpM3f4FBEabn}@OCyXuz8_aAcaKVOi^_YbCPjY0qIB& zalzi0cTm_UW)RS$3Wge4hK|cUDLLfD?JR13O=S=uF~@zRgD4qj!H9>UG(X9dVxi)_ z_E7d|KimT2^b9!2HIc?7$Z$hhCu6;c15lH~gzySD;_Sq)gk_d{1+r06C`QSqmwes} zB1F7?XN7MVzM@T>;_)w&jnN}eAI47jJn^gc>xpTxL>Wb8Y)7B-vzolyirr>~s^142 zREC6*fk;%+UKj@q8M{Ts|JPcKggujlns3bB)TLfDP!Q6_7IbIX2YTbbw9Rbj=<(v? qd(wPftbDvFo+$li{1Krwt1`RGTfKAJ>bP663$R5ypeilBiGKhAgAFJE diff --git a/selfdrive/assets/navigation/direction_new_name_slight_left.png b/selfdrive/assets/navigation/direction_new_name_slight_left.png index 08e964dbd6a68a9b82983499adda5a8bc78ac1a9..29f44404608bd362089b6aae0c45d5a1f6df3e13 100644 GIT binary patch literal 129 zcmWN`K@x)?3;@78uiyg}F$o5Lg8>RNZE*^E=0O!S98_ka)FR--p$H*(I&$YN^{laaWA^&#_v<-EeHR2-Khu)uSPdrqs z%R4_kOFgzOFZ@OC|B9V|JNmA*_TRq#A|ZB;P0O1L2X}oatX{Wli_nLn->#`#KM(3} ze;)s$;%&vVZ>m;t+ID&Rfv+pxd;MEuvio=VrUVZT~-`@|9kHuauHf8C3urD;jug$lQ7WE{R8R_ENsB1nK6-!NoyQusn?w$u(EV}!t(|sFPUhBzqmM7|6koqS zQY}vI^yd6|dEf8Y=$ake`Q&BU`Ow73m;*7N3U;qvE;R9TQrm17n`2@BEFI?EdH7Dm z<$IC#gPE^FM1=l3cF4|@*`)Krw&P8?sBwtB+O0QoXB>r?sv7?Yeez$Fxaw_pMA6H= z|1#WlLN=VOmY6u@-rnHVY)70GzWgz1nW>V}A-&`ScVXKhh14R>Bse^mVGt-SN)PFm6r%Y&UJxM@1)+;&yW56O@W<(c9|iO;O!eu1cJ!0T-+CuOctIiHwC5+gYjanl%dH3>VEM%0>FVdQ I&MBb@06s*zH~;_u diff --git a/selfdrive/assets/navigation/direction_new_name_slight_right.png b/selfdrive/assets/navigation/direction_new_name_slight_right.png index 3e21cae11ed5209a7f63f7352d7ca2c4ef5b0b14..755935b84c512c69b72569720de3be7f485cbfb4 100644 GIT binary patch literal 129 zcmWN{OA^8$3;@tQr{Dq>FnpTc20{>KR63@1;py$_ZQjWr8S}0CaqO}g^BBkN*<`uw z-?Gqr8QGc4>C_xmOV9TZH;(9P^?*JRda#XZ0*-|%qt7|Cx*`Tfu)rk;AhMaliuvlz N*{EdvSUd1Z{Q=xSC)@x4 literal 1347 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HkZOP5}}c=7)I`_G?0 z|M>Ca7a078fj_9=FVW!t|Nrx>eU1VxkShuD3ua*D77$l8ws!Ikj!w=lu5Rj@JbTIN z&ASetxpL?6>mzF(>N7B~lzX~3hE&XXJLl%K#Rfbsk(=NDcKi3=cy@`EtojKy>pAO^ z+`gA4^sQQ$<0;muSv3bA5XEv~P#B&%EZJ^m3=FqQ-iAN!e&YBlp)Dl5e5}nm+IH-1jen!;$a5 z@3uc@z9+1oy=3!gcBc<<<{if$Y8p0Q_ig*_pePe({xY|@WkbbX6_rxOPrE$LzQ-O~ zu_tem&z~h5D(^0MuG(_`wf`QoYPa?FGb&#weElUme`|I(-!;c)kIRzQKaUGN#UQ(G zYUBM^-mkxQPVadwDZ9RR>3qJh_3HPg>My@;{C`o)rPq5KCYigk>k3?2n*QaNjRX6_ z>ytMeGn;#Tv-1Q#tM$(p<|Y`e=XbU)Nr{`!c1q!MRZMVgPMUVMd{GaZxg>C8n@Zg7Hii(G;ZOfLawn{A6u=DZWcQ$4g&p-K2ZSVNL zYqOR8jj9DxugTtdeQr<6`9n4*S3Nd{S*$ zee>ZL_D9=$w}*fF;?5X7A*ffr&(vM^#hB+mTg^sD!a7x>n_{k_-pt7?8iRnZHtx9QDx4?ezPT|?aVIXC2NoT}swG4IXu6yu(Mv32=rps3A+!oPFb_UkTL z_S*S(gv_IxU;n)IeY8h!yZhd_H76?U6XVuTaV_6tFQ#`PZu)Y@Z}n~RPnfUo0pPfKqz{iF@ zEryLD-@W}TCX^j!x|6`|=u=Zz9cA(8(d+$^k0w473)s)_NpOi$F3PaPp!@`z0o4S^2||x zT^5DjkFpmXTNR$}i`7|mL^$(S{`Oc8^#yb9@GouJ+srFsdG*y2ftQ^npRP`_aWdw4 zci4V&mS^cyF2_YqHv$xMRtT7da7b&l@QNO4<4SZEdSrj!r2)uj-LMdwywt-NDQER+ zjqjdhw8~tzvDzKPzkG^itI=9RA zO&gDwnnz)Eq8L5P*+^Tr0|c&MdXcI}p0e;B3q_ MkeK~fB!V;I52R!!;Q#;t literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HlU>(`$@fBydc`>$WW z{`m3Z=g*(Ne*O9l1AkD#U!uYP|NotiUB3skK&~XnFPMRiTR=+9$lA#}I667Iq`IkR z^6X`6Htsok`tt3kuYYqzLC#x{pH2;BU>XL&_Txqf=~ zdqcVH)8gOXE4Il}3s-*cDQEm^zH?vg`$)Nq_V=1jT&tepo};?q!@OmC`86v)&s)BB zdl8=|+dbi}#v1RMPON)v8!UbIZY|%$sow)~*Q~O1S#~ej)ck7Eo(CTbBO(?4%f!9; zal`tBrOUoO?%B_@r#Yv*vyB%1T){VS~I3&J9oKW$hadM~)R;kuS`^*`w+QK~w= zIS00$x;D>_C3+R3{)Y~?gHvm;Ti+P*pS?43FC(X(zOWKI2b=}249>Ik3_OTrAjg{58AP70WH zgCT3`luJjR?bqLSpY!yd?5S6PoS4-r)78wbM=f>COe$UzI%|sBw#m0YZj1!-R)G&n@|RrRHVM{Jmi-wSCQ|iwFIkx^st37A*nM9sFaS!-6;OQ^D1 zuCIPtGht=e$|-6`gKi%eYR-6V7reIU`}UB`R`yBF8n5k6=LOZ7-g3Ra^oD{*>RtKd z{_16QADFkBo`2@%cy8Ij{9hlwtTf#&zhcb?hsz2Ncb)&d?bgNj!cp-L8=pDmz5X}j zrGQP$`|^pRav8!;HofEDEqZ0YPZS&D5rqs17T)Fsi2^1(O>K@B3^=6N91qh<&^MlQ z(o;SY|K)df%KT%_4LSAp%L02=wRJ^@Z9klU+Npi_{F)VagnK(6;9rBh_jJ$Mde?Wn Q0cJ=BPgg&ebxsLQ01@OIpa1{> diff --git a/selfdrive/assets/navigation/direction_notification_left.png b/selfdrive/assets/navigation/direction_notification_left.png index 9a618026f0e00dd044f359e1f3fed8c668c63b6b..438532ee2995e37090ea2433fe7a7d6d27f97bf9 100644 GIT binary patch literal 129 zcmWN`$qmCG5J1s8RnR~T!#3F6unYky30A;9r226BPx?1~N9!M6=RC(D#Iw!EtBh^6 zUU5tPEyhVn+`xLveAJ+NtYng1f z$Vhj?PqO{GpSyY<9WJ+-cI5V|zNAOiO4gT>eUG0kcPX2)d0o;oYZdKjrIJTgKgmvJ zy}D*~#b?@H%CcawFGG@5CSOn4ED}Nm-$M(xS+4 z!7l5{%-w6(<gzHO_-&?!rMr~fF#8Gbac8Xw&^~)2xx)|pe8ySA^f8hHwySU!HJ^F6VZ{GJymbPt)dNOC~nO%?dy)Ea>PI-R*aNX`X{%sr0 zCHFhus>&%9^9mM*o_d(r*(&eJ59;+PBS?~~n)78E+}mr2)KbMy*iiOecNw()p`AQrc27| zTF;yM?K>2T&%JQy>S3KIRN~@stZmf=W#?$CZz27APVZVVWx`DTaKERsWoHVh-@Dj3 z$z^wYQL3#^P{Z?!r{Cl*o_>Mzy4si1HOK!LNS!sc6FsE3ByZ;aj2P{C+k9F?TWpuE ztGvy7c3S4Htp5o`hwQq;^yBKK>oz!iSNm7IC}&f~)XKl%OID>pF4Pq=ihnHa^W|M{7o z%+8%MZ*MG4y?1E0lKJ$-d!4qNi9TcbaQ5k&uXKZ+`PQDA$`O-QT+Se^_t{L8A#Ou~ z`QbKmE}gC-o;npi^P2~Z^4|onYgivCw(^eLiZjtYS-~If742$e&Y3T#tH(UcJ^Z#{ z*AB^M*T8uWPA_x|rn=4C9+A%?U;bSC)#Iv}+h2$|eV_K*^dyk^L*ro3qlXq=XH#eV zUXt$7aeH}eU~KT_51JR8opu*D2WWm4vt!L-+u;}8>?R=&`+Hs%ZYkRxeW7pr5Zqi`jRB%Z@ z;N_8X)vTfANp1TxCc#6zWTD=6u}77O2l!Am14Ta>EDLP;k@7*~JV!}`%7*%`Qf74d zR>o7!lZv{G7`@8b5PUe$X`@Rr0LZ&ZHP`Glr4(StnmO(qqqVF7az+T20z1cn8Oc&a MFJ}LeG&UOX1IK75y#N3J literal 1371 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvG6Q@n>Sy)c=7)I`%j-f z{rK_Y7a078fj_9=FInLK|Nm28tKA1$rc@H-7tFxUEg-69V(sJ|6qTG)T;1F~Y4)Nu zn|2>QefiFVr!U`r{AQ}bX~Mw3Qt#>F7*a9k?Oe{JM+Q8ujVdCiZf%&hw)1`ciJ32X z8dZvNEKh8*u44E2dT`=C-I;uBO$r<^^k;v{?nj%>?pXZ7zbttF&shb>FV61$b-K%X z!oE5GI0JvTSWl^&^WN!7>KR#YeLF41Rdb5jFSl>0(FmL^SZ-l^!-g|(zToqRZz4Oi zmfiWdWAl2=!0dMwb7o#?UEzF6cB1xtUe$}w9!5HQY^kiM$kYnFZSncTB2|~Ui_RQg zylAb;Ih#Fs+?~e!*K544R8}x^23|LRx8p*}0)bGy9n8*~+ov9x-agxIsnRZ+J6|+K zk6M>!SVY-9J$QVAwS`FT{Gh@fEq-RZrH{md<>YEhH3GfgeGq7{Tbd+(exvxCV6Af> zpCsN`9x}!F9YgNLA5+*Xbt)sC2WusrJe;ql_)5(4x8v(zxg!_rD$Kb%m;5c*8c@{t z@W$%|MU^g_8ne?soIh0VIDGm#=kC7-Q6;mdq*vTNaI<9JJ-Kt+7q#q&T>Ya>lVAPv z)qR(jKTbSx_~xlammYq+QzxYTW%Wa4{^dJs%}U$% z01adE|eB#b_zFE!>k9>dacty`Qqc>D~5!a(w_CVk7YnxA{s@ATyyp$C5^WMU%6R)Ih zpXa}+#Dqm+iFb?Grnw!K@9%vozq~s$w&lgQwq*icceDbd@{YXl^S!@I{Ag<8PJuPo z)D!Iu-`Y&JI=jZ-Vr`jWi|oC1O>qi)US4rxOFh5$^$D@i8eJoc)bF}i-!Cp;4o}|9 zcWI}{-$PDfYv+2hy`9)um#AoOms~xotc+X2{vZ3b%QLgIK5l#z^t)C;K1Jf;0=pN! zCDA2o`VOuNX0PN9F~8hww@|WFEc8=aN1koY1^-ti*j_CE?~Ys# z4crj_`{f6P8*?jO-#RpZ@nJ>#nBG5MFCKjOa<%g$#?=##pJX#gw>;C);Cw?a-B+j} z);dK0!R)VAt(2O@`$GxN9_G$b)c6VXjeCKa} z-+69`JJIULCOhqTMf|ktYcp#zgauEguCr27F>&$8=$LRpKyjg>laq5xOB)9#ub_|= zVTm7xPH`p%2l!16DLPsFH1zgH)B8t4aYAO#qvJ1UGi(=Ff5*_0@lI<0V|n>4H_S?9 zl7E{gl)tjy5c6c?D+!*q$Lf!!D#FVTj)@<+XC~|a;`*a`2Uu`1c)I$ztaD0e0ss*f ByXgP` diff --git a/selfdrive/assets/navigation/direction_notification_sharp_left.png b/selfdrive/assets/navigation/direction_notification_sharp_left.png index dd8a4301db6dbee6bb7df86a7a1d04dc813886af..d2f1d491a0d59da07837839978aeec7afcdfa46c 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUN8m%r%-77%7sGA_x%>h)dTRX;rDTlTdM-j#Zcb$d46{`WI) zG+&C3O6o!}N0Ho=tD73urz95 L1dnf(=&Tn%trRA^ literal 1546 zcmbW1Yd8}M7{`}eBf?m@h8<2q%COusnOkn*n6QzJ%6-gzv80p9wGtss(S+7^D2z&_ zwv)s$h11_5OH{Tcy@M{&*!hKtOx`Gp-}k!`}g(r^^L#u`@gmMpS8Wc z{l+m)M|49A@8jtUklLl7qVuB>%;C5%3KNMZWM95kd8eVJ<9QEz{KJAfxl^<%Y31q& z_e-r>yvYo^a9Gw}9aJGVYW;OPYZTTiHluSg?_9t2g~l?_C@W!OwN5|p_CLJjWoPWa zuo(OFeau#X=TnSx4&~^{n~_A0MKV@JN>Be=UmfpdVP8>n^5LKf3posP4({*2yWr>^ z`V0IN^hfH)Y-zPa3aI5ZJrj2R=XX2%D)@G#@gcCfHrP(ira-Z2Mp&k&7 zv4`q57KB%nRP8biwuawq#b)j!rnr)9W}zzrP6^eMCdw^?JFcQNECz;*O(*v2C1lDz zVsd6(kcQI*4v^7An#@nj%@o5RTeu7McQrZR*^nS}gK=*M$fqc?Wp@AFI4>kJYEl7| zwu{Tl@XC}5OT(GfHZnO-2tVXx2Ea?>p&^C=0-w#FZ=I|NG@sX-cm9QBc6-U-P^rB} zz5Fq!T6(~eK59*DptPPvH>pmPoM`3Cpb6J4A&|$PQ>nIPG`!vcXe8oe+gLUAQK4uU zK%WVSqSbKSe6Ztc@saH?3zTbKaG-c>K5~s`9cjmP^RDIFYCAJP$~W*%W;_~W034BZ z%mMqGsv?!&F#w+TaB`^aP&;a0W;p;(V1_tI2+8N$$r~9_#Dj~{xdicJ$*^bXi^`l@ zhrMeOjuBnOi|@L(6x-$`gD-5f09&ra3~`@~n|fRoKV|K2FJVM8w+nM-jCohw7b2jc z-4QxF)7K`+i!YZh=MxpxACL<1;X&o+{3Otlb_;mb6b4AiBzlu0t}((MQoTj(c9ccT zZJ$1_jdCT)y2GiNrv>-y8CO8`CbDN?W4kv@=am`XYW z1X2~7PNT`kCQt9FF<)tV5j5J4=s))=D<03lT^{L?)&?J6&}nv|fM%u?%c)q$S6%VR z+3eE%TnuS4oR7S{)drl;b@t%3f6oCXSaq+Z5ZooydS$&BAVr+Ad}VMz-^ABE^7SX3 zvnSFJJU-o{TpmHoTOmHn7YgcApBacDvt_l_+k$5U?4s~{W?Z>?mTWe5ehnmSfJU6A9O6DZxVfJv2_R+hF@NRlz^ zqvzRIl7Ys}WFMJ3xtJeb_dW!sqOi*J*5$Ml2U!=boJxvKs}7aw;RL9h?rkw_YW!42OM>kw~?i1Mw(z2PQTrAH*nSmf;*qKEFr5iMH1OXUBy zJ%`cebk7gKI90!`pktgy!SGbt8*^T#G*URrMdZ7zGiaL|CYnN|GUCMFzJ?d>Fr)w4 UAJ#<^{aXOQ72)YfbqLM=0~U1z7ytkO diff --git a/selfdrive/assets/navigation/direction_notification_sharp_right.png b/selfdrive/assets/navigation/direction_notification_sharp_right.png index a7e3c4cee56a7de3cce450a83f07fcb55202ba71..f338742e5883c917dac520f01f1ff2f7dc50968a 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUOp7hp*@!0rk&DjC5XtX|*cUG$!PykuMJoQG2PzHSem+yC|{ zTOCg|PpaxNVswHq)$ literal 1551 zcmb7EeK^wz93Od^X=80txm3S0QVx~$(7WH{ZOdDmDT|#$Td};HNp5Zyn{vBRxk%bv zXob$pp-^U(RLon#B=bnxIl~&7xZOR^Jx}-7J@A2X%|UkK{<4R+ZMh_+)tpUa0txn;o-}cX+Qzp(vWt$)}GU%UIuWv<>Jf zx?e_bXfWub^9K`TbN3$j=lV*Nj@Tz?A^wPwR zuvKIPKa(=aILTn=9{+_BR>%u+S590Uv-Kx4xTW$ar+R#I6;rk$1Q7*0x(JE_28J)| z$Ind9Hv7$piJ;j@0xHU3u+PFPfFZXN#y;>gV)xa4WQWaPER{835GED!ae?C>A|1fC zsv?BdqDbU776q3&AaCs^nhV6u&hKkM z5AB_tdww+KR^m1XWF#ZWC(5GZV4*V$5Z$v^dq-|&$o4E%IU}zO_C0D>20QVrD?CGy zSNgpvqS!@N}5 z#%Ek-aQ3v^`F`wu?L#T`@HFVsU9!4H*=7Ifl;${)Stq6~&t_Ywr?r|R_Qc4?1=~2@ zM`m#^Td36(nuQnXv8&23Xrg2-x%a%)PJ-AI*V_uTzqB*=E?6}Q79-DI$kp$9a(f=2 zvQK~A5EqUS!ssQ4Q@Ym=YyEV$aT06+s5kT9+!yS^c5_l)XKiqufq*Z=FHiUkyr~3y zQoFeuN3f+vR%k!c?WEXk>^xsDSD}0>PV>itFS63oa}J89EE&ANm*z55n1G zL*r4-sZQ7;PO9_S6+r>JSgp`TuEnD~VLZ}}x7v`F*@ad1bBs#2daZ6|(wd=xD7lR; zE_EVz_3MZ#h!b`KpriVryL5guefU_dO5jq{de}gFO=ZKHEbH$6r<9sEzpFE5P-bAB zFvdnYq<_SudF)(O3iO6OF@IB+)&maL>t3C4J5!HWGCSvHdMZOwrRc(D9%DCRNZE*^E=0O!S98_ka)FR--p$H*(I&$YN^{laaWA^&#_v<-EeHR2-Khu)uSPdrqs z%R4_kOFgzOFZ@OC|B9V|JNmA*_TRq#A|ZB;P0O1L2X}oatX{Wli_nLn->#`#KM(3} ze;)s$;%&vVZ>m;t+ID&Rfv+pxd;MEuvio=VrUVZT~-`@|9kHuauHf8C3urD;jug$lQ7WE{R8R_ENsB1nK6-!NoyQusn?w$u(EV}!t(|sFPUhBzqmM7|6koqS zQY}vI^yd6|dEf8Y=$ake`Q&BU`Ow73m;*7N3U;qvE;R9TQrm17n`2@BEFI?EdH7Dm z<$IC#gPE^FM1=l3cF4|@*`)Krw&P8?sBwtB+O0QoXB>r?sv7?Yeez$Fxaw_pMA6H= z|1#WlLN=VOmY6u@-rnHVY)70GzWgz1nW>V}A-&`ScVXKhh14R>Bse^mVGt-SN)PFm6r%Y&UJxM@1)+;&yW56O@W<(c9|iO;O!eu1cJ!0T-+CuOctIiHwC5+gYjanl%dH3>VEM%0>FVdQ I&MBb@06s*zH~;_u diff --git a/selfdrive/assets/navigation/direction_notification_slight_right.png b/selfdrive/assets/navigation/direction_notification_slight_right.png index 3e21cae11ed5209a7f63f7352d7ca2c4ef5b0b14..755935b84c512c69b72569720de3be7f485cbfb4 100644 GIT binary patch literal 129 zcmWN{OA^8$3;@tQr{Dq>FnpTc20{>KR63@1;py$_ZQjWr8S}0CaqO}g^BBkN*<`uw z-?Gqr8QGc4>C_xmOV9TZH;(9P^?*JRda#XZ0*-|%qt7|Cx*`Tfu)rk;AhMaliuvlz N*{EdvSUd1Z{Q=xSC)@x4 literal 1347 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HkZOP5}}c=7)I`_G?0 z|M>Ca7a078fj_9=FVW!t|Nrx>eU1VxkShuD3ua*D77$l8ws!Ikj!w=lu5Rj@JbTIN z&ASetxpL?6>mzF(>N7B~lzX~3hE&XXJLl%K#Rfbsk(=NDcKi3=cy@`EtojKy>pAO^ z+`gA4^sQQ$<0;muSv3bA5XEv~P#B&%EZJ^m3=FqQ-iAN!e&YBlp)Dl5e5}nm+IH-1jen!;$a5 z@3uc@z9+1oy=3!gcBc<<<{if$Y8p0Q_ig*_pePe({xY|@WkbbX6_rxOPrE$LzQ-O~ zu_tem&z~h5D(^0MuG(_`wf`QoYPa?FGb&#weElUme`|I(-!;c)kIRzQKaUGN#UQ(G zYUBM^-mkxQPVadwDZ9RR>3qJh_3HPg>My@;{C`o)rPq5KCYigk>k3?2n*QaNjRX6_ z>ytMeGn;#Tv-1Q#tM$(p<|Y`e=XbU)Nr{`!c1q!MRZMVgPMUVMd{GaZxg>C8n@Zg7Hii(G;ZOfLawn{A6u=DZWcQ$4g&p-K2ZSVNL zYqOR8jj9DxugTtdeQr<6`9n4*S3Nd{S*$ zee>ZL_D9=$w}*fF;?5X7A*ffr&(vM^#hB+mTg^sD!a7x>n_{k_-pt7?8iRnZHtx9QDx4?ezPT|?aVIXC2NoT}swG4IXu6yu(Mv32=rps3A+!oPFb_UkTL z_S*S(gv_IxU;n)IeY8h!yZhd_H76?U6XVuTaV_6tFQ#`PZu)Y@Z}n~RPnfUo0pPfKqz{iF@ zEryLD-@W}TCX^j!x|6`|=u=Zz9cA(8(d+$^k0w473)s)_NpOi$F3PaPp!@`z0o4S^2||x zT^5DjkFpmXTNR$}i`7|mL^$(S{`Oc8^#yb9@GouJ+srFsdG*y2ftQ^npRP`_aWdw4 zci4V&mS^cyF2_YqHv$xMRtT7da7b&l@QNO4<4SZEdSrj!r2)uj-LMdwywt-NDQER+ zjqjdhw8~tzvDzKPzkG^itI=9RA zO&gDwnnz)Eq8L5P*+^Tr0|c&MdXcI}p0e;B3q_ MkeK~fB!V;I52R!!;Q#;t literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HlU>(`$@fBydc`>$WW z{`m3Z=g*(Ne*O9l1AkD#U!uYP|NotiUB3skK&~XnFPMRiTR=+9$lA#}I667Iq`IkR z^6X`6Htsok`tt3kuYYqzLC#x{pH2;BU>XL&_Txqf=~ zdqcVH)8gOXE4Il}3s-*cDQEm^zH?vg`$)Nq_V=1jT&tepo};?q!@OmC`86v)&s)BB zdl8=|+dbi}#v1RMPON)v8!UbIZY|%$sow)~*Q~O1S#~ej)ck7Eo(CTbBO(?4%f!9; zal`tBrOUoO?%B_@r#Yv*vyB%1T){VS~I3&J9oKW$hadM~)R;kuS`^*`w+QK~w= zIS00$x;D>_C3+R3{)Y~?gHvm;Ti+P*pS?43FC(X(zOWKI2b=}249>Ik3_OTrAjg{58AP70WH zgCT3`luJjR?bqLSpY!yd?5S6PoS4-r)78wbM=f>COe$UzI%|sBw#m0YZj1!-R)G&n@|RrRHVM{Jmi-wSCQ|iwFIkx^st37A*nM9sFaS!-6;OQ^D1 zuCIPtGht=e$|-6`gKi%eYR-6V7reIU`}UB`R`yBF8n5k6=LOZ7-g3Ra^oD{*>RtKd z{_16QADFkBo`2@%cy8Ij{9hlwtTf#&zhcb?hsz2Ncb)&d?bgNj!cp-L8=pDmz5X}j zrGQP$`|^pRav8!;HofEDEqZ0YPZS&D5rqs17T)Fsi2^1(O>K@B3^=6N91qh<&^MlQ z(o;SY|K)df%KT%_4LSAp%L02=wRJ^@Z9klU+Npi_{F)VagnK(6;9rBh_jJ$Mde?Wn Q0cJ=BPgg&ebxsLQ01@OIpa1{> diff --git a/selfdrive/assets/navigation/direction_off_ramp_left.png b/selfdrive/assets/navigation/direction_off_ramp_left.png index d3fd182893762edfe00c4812c46ca9e4a99beabb..d01bb4fce35c7dbf37b244923fe68626b639b631 100644 GIT binary patch literal 129 zcmWN?K@!3s3;@78uiyg~NFkK|CIk>>R63^i;OliSd)2p$`O^J757~{mkMs6uvi$F# zyzqP)IVzjWsX2<4P7rX%xMD^I2Pves14$(z>f4%z%-^XK&1 z*+;^ExIe${fA$mm=h)bXdO0jt-u=s#J8Zva{q?){Ij?=^WU@}4pS9=2mUUJ9u5t5~ ze03(Ie%-Ub`+@)JT_yiiIWMpORxBk~xT7Pbz3hOa&1)Z*XVd+jxU@*iDY=?6zY%aQ z5D5Gor&WEJ$7r8N-MdN|gT+1im-=?iVojT|(RJDVTHX{X=|~l8KATsfSxX9Cs{*$_ zY|`HEF8Wg@D`C~-HbGam!0n8Ib6;F^6P9#|^DAwt6&97G+=tLEz^KY5Bf4p+3cW0XT=E=1c=k@D6nFPbHTzwP& zN32M&9O#N&n$Eh5`106!vuZbfb={&Q7x>+8Yt_P(C1M{tcIBSpdK5J8Tl~?YJpVsa zl;Q%-j}&j8C;i&XY2qFAS8T-*hr`?aQg*}&bSE?J2Ioep?;>3ue)_oQAcxC?w@gxg{Z$J4Iuds`bnRUv^ifA>abcuT zXT`L)f;Tm#1ow-toH50C(Tb-L0V)ZeQw7#~?-8j9nyQhXnS6a~#zHNr;51=hSLI21 z`|V%sU45i_qgPPjR*M~{JWLjvept=b}YQttr2@721}qov-|D(#rH%-(!qIwST9sEHV;2 zZ}NVB$KDO^T;|F1+Z^216h3SI@BZx@?k4p2>UZ_ZecyN6w5+WsU0eE{ey>S%{A$$E5HwpEHrKnsg#! z>bzN@N{7Vw+-LFUJv4}$yzF$xrj^GwI_xj&zi>1q=+J3F!HxMdTee%yyf&}ycTb1( z`8ApMLM`|VZOZqTYnEy+Sli0Cacj26*4Hd&XKpi*4_JQZ!~=#Rze{@A=NJ@APxVfH zsqT64Ku+Et-c`XSA6V}#&(t(GV{N;tS*7#i*{Mq=Kcz3*Eb8UsYjItbEZ%0)Z~2iY ztnhdWd*TC={!G)@27AwWZ!(1DJZpYE<#jFBzD6-l{%*%|^JTKqZz?U9+3aIq`tyTT z>Y@kFb)PnRDV$A?y6`|^#WA+cXB)l|%#WGMJ@X{F2DL#|6^KNBqFitrOYbGf;!iqC!tOPWwqJ*cv`V%g6E!4JLD6Z-0Gt`#5dkKVgkXy@{u-W7k&&g}HrlpFe1;Qj6d zrA~X>DqyZxI>amZu&txO*=2{K(j5W8ybca?a)mcMju-nWSM8^9CE&@@|M%_WYA2tL re^uzJ8^+J}-y|Ad&I$ah{Ks%q@2k(WzcoU@(v`u})z4*}Q$iB}KUVM? diff --git a/selfdrive/assets/navigation/direction_off_ramp_right.png b/selfdrive/assets/navigation/direction_off_ramp_right.png index 722e3f808f632c84d319bd282aa2902d0811e4ba..013ec5a4c9f46e847f5718da856237f16a798d51 100644 GIT binary patch literal 129 zcmWN?K@!3s3;@7;U%>|~0g@E^8yZq!Mx`UP2Vbvy*-JmYwznx`9DTL+QOE7M_xXQ0 zw^`cD_Cv*9ZfZ4~UXmwXIdR||0&x_kxB)3LC{S{mgap;eE}A_cATlip(A^#QMGysL L>h-I}QH1FSqzxv8 literal 1580 zcmb`HSya*q6o;8XNgEeXK}*6UMR6+^+$)zqM_e;-O3PA{GNwsN(cDoqz|0IQTvDsC z#azNRv$94_L5}9Qp?J&|9ZLfRwQwsOr_(&lg1&3ZiQEB zhEZc|{+`wd;fd!XMbh`)yu7n}1pR%ox{!eNH+BcO+t|5Ayh+J0rk1-UU(>q3P&n)f zcTrkMR&H&pq+Yc&d?<<#S8FOQ1cKFg`>Pa0g;)M(eSURw#6-BFYh0`xvNAtRZ|{Dh zIQwk3!%y1z9gQd(uHLkl_m;xxkC-c47|bYRo#bAx#a-GbiwP!flf&j2$eTLJ{rG|R z*rwy=Rlq$NQ;3a2VsuMzz#&1D9mliRA=i*41nw1Z*~@1PSW^IVnx4Bni@ovns_yL} zEPTFM;o@y>#{>o(iAaoIujHbI)=t*L9+)3TKNx_^q5^5zM3WUp;DAL{ZtVFp4w8$7 zU^)^57Y1Q;mCgZ{ebpojgmE1$a}QY_?KnKkkJ<;T3c@Zaieutfp(@oRztHh*ch$mg zU94TY`K${6*7ZitRxMHR0d$=NHpA}Q#+BBcI31$##@eh)oc6;E2)^S+iaY)bd5xA zeSO=)tH{U|U~=C%*=BV%AK?t03-d-ayyTu|yC>`lwuOdj7y@wuV%gcZ@6@drRgo06 zD~(`0f`c2dXrko+jvO2T^T?Rf(POpJ06^|;XCub;n~uehwX1Cpd6Vl4YCN5Op|Tr) zHf6mONO5!&SR+0FV_N3Zi+{oe$)q2)azm$3P1u+Q2bkpSK1J9RKUs<{5r)8Vt+lo;WHfVo zxG1T99EKOs9*<^6?>&G$G!q*0svDIm6yE#eE>cWPPup@C(yyM$VxNrNm>Nu>%x*rQ zcl)HiZR~^AZZ-EXKKDUq2rd@QPv0|Hl0UPYa~v%2ZlkSiT23jS5N6G)yx_4ZMIuyr zy4#R=gOkfB{=GbZ*P`L6uDf>B)vs_{J)cEqiq@Fa@E#A%97(PHy|_kKVLi7O_M##& zO<=dz>>*3W0~x`-9DeZedJ$DG3(%UZIT}5Y@qAoWC#;f3cF;@0 gp(>u=qqJ0l#zhhKtDsHRYo8e8iw^?scttY)0wFN_Z2$lO diff --git a/selfdrive/assets/navigation/direction_off_ramp_slight_left.png b/selfdrive/assets/navigation/direction_off_ramp_slight_left.png index ddac4aad6645923290106c2a79b5c95043d5e445..39497c8d4181680a01dd492393f03174b8efd73e 100644 GIT binary patch literal 129 zcmWN{I}*Ym5CG7gQ*Z&ppM~6Jfkl{6$p}f|=}q;TchPtD@!@T)W9~vc`+B_U+-}>M zwmjZq9+btU*yx4NhT8cZH5BIxNLv9?Mw;0qb;Y&-j~5Br^a|NnFEJ?DP>XM1_NsVVCz0{{Ru zcND@~=JYQOl9y=-G4z4V0=>|_NSQO4OfHwZy1FV93fI=wHvVMuZ?^u0M61kbf0D!`_JL078l`>vA z`%bkr7#Rd?>wB-IB$bxJzde`M5T5_6exyNico$>J$Q4 zsl184lv}pX*}h)0#?J8t^Io(&j(F&|y>|j^8+yUT7Ev^#H!j@ZTzw%%5`#|Ee4?`E=a5?{Oy@dz1fN~Q#CcsUw~bkXP8d0EJZt$L>xI+Do> zZ~=xTDRM?QP&m)z;0zzufd;59x>nq3)F?Lk;*+GZ0mO!d}9&23+3V1Fq6m7=d zFvz?3!`sdSMN#Ue3^&%BgY8p;*(vu#gFTCByOR1ndZwnZ)n4RyMg^^rqv%_UXBAvX zZ3bz5ED;U&UbVW9>S#5hj`uYS1Gi)B@lhm${d1+bK`WOCLjzhT2~-!Q{HE-dh>8{K_-3Xs%dl98CEW~OV`3y41O6$Yb7AJHgGPtO;TDiLw*)4 zv)3tA$P`9J%Y0mhHlMceW&~moIJBP!90}EJr4I$|^8nuAtFLUEv;`#7rsQ+hD1G5$ zz3RcW>`uu$Gwui2c8pag6RxiFF<+F9Ut%nd;xXN|PE^09S)@ZH^Rpf*DMR#x&Bh+Zrb6Qg@KAR3A3;KmKpMT1 zSy8hTIhT0iC1>SF@Sp26H`{muo3N&{OIQ#& zmc<$F06H=kd_xJ~dWxh#zC5MRTTwOP>p1hf9qcimw%VVRsismn^VGcJ3jVs*4UMZ* z&f<=p8VASCNp3e##iG(lc9Cw=+`W)>d+2FxYqGmd@F~eXfUPmoveQ%*kvXp4b2OOv8m~W71BocWw;1ygp8o+YMDI^htxk8}Rl!NTWF?+|yu*)IF6TVQV@7$({G;b0P;t=8W2j#hbmz*g2^hF41GS)%-QdWkwq(=0D-oT4rf9l@}wvtWc_j- zQu}rmwlCEDHoabQNl=NfYnA(@*K)L+)ngCHsuu^WTe=D3VR=uA};T}$^SvRY*mTK~z mx4VkRrnHcl-G7x*&b9d7%8f&icSmHA32;YxB52Ox>3;wNTliW4 diff --git a/selfdrive/assets/navigation/direction_off_ramp_slight_right.png b/selfdrive/assets/navigation/direction_off_ramp_slight_right.png index ed57608864518713dc97d8af9fa88b6bd0693702..a68d28d674d8976c2753c87602459e5fb52c320b 100644 GIT binary patch literal 129 zcmWm5I}*Ym5CG7gQ*Z$T%PcJ9HhdOgMkOO8g{N1mSG|khvyYc-YaMe}>fYDwQRnuv zooUPCspi31T_8p;a<*u!+nE5eX9FG)b;VLR7VUhX95h*vq?wRxjl?=vpG-;C5o7o_ MOJep9ORx~c7q2EJ^8f$< literal 1519 zcmb7^c`zFY6vpGIrj80~^+3r|woz-hEmKEZBqU{BOY5k!sI8V3A?hqj(zxOnXRD0V zt(6pus3J5)sFqkqq6m$~F(fI8N;ch@&g`GN-+b@=-h6N7{rNCX4z>!i$7KNkfC2&z zbrF;Dt$dsmp4larGy7K_8-?C$Og{?Xodef^jA|Fc9Qk>Tgl;>!T_ zC>Mv{0WxxGr_P!|?OeQqBjZvZ<(1(Zh+X}|ALf?WeY|rW0KfqV0&3}sDqk;WN7oyH zY*cG$UfuJ_J4vHgT4e*3f7MPr)I2S6D0*|gYk4>bm}!%u1^n)K`PI*!uk?*QSSjj= zpM&xjAyJ4h^`YVFFJo&?D@%R@TrCLpV^DVW& zE!yCgEg75+_0==tKtwBJBd5mBr%{klq0tT72pVf3#AMX3xh%}y==LlIFL_PpZ}J0L z8LmOT7=oM2{tmz2U|TvA+G>e~cWkMgojVO_ zR}gq_y?I>LiKYaz!7OU#d!(gnQi$#7bdkxWo`ro6g(=Xw; z`gwdZVWG^Pub!|A@7p{_uu>fEOr_*4JotuE?UZ3tU!CP@jUr?ld~gQ;kua)SvTfsg zomK)K86@+%TVo3DfEJh}$zo(SypO?`B#wfd9pP32J8+d}T()CYWxO)^R#kf_lFly|=C$LNkLIYYZ5g%!l!$y=jpW__4pzJ5sUbkI4!bMz5nsVCQj^}=Hc9d*0P zu*vr9E6i9j(E%IP*nD{1*BH4bRgQFFhA`xn#{i(N*t;~1(JBK}+^AYXWTs&h~jS%o4@~y)jw=m+F%41#|dMmfCuH>DM zZxxZDjIdXsjBqX1FUpy+&nWf#Kb`xE3Sj4vNbR>$(t)iLsJj~>m}+WHKamk{Y}tcz zxvDEmrK_5=6_oae66pIds{e^6yiRl?6Mt3rPHDcGIr}^g8s~p3qd|`+p;*ByMMsB* zei~;bFA#;w2&AU*lp8F8(tmSbx2nv_AR@6Q@sCBK@W?oF`l!O(%!X_QuLix6y^e8y zANr$;7Fz@^9SG>{7}&bvk=!W4h6uGtF$(5ha0!ACG_I2to3V5u`5;ALo%Y1qU5!>7 zoghOIcn8`$eqQT;1N*aa9>)3ru>DeGu>xk-z0peoa}$0NVtoibqYPbgAy3cBmcHy0 z+(zY`;Wl&gucT!QUNf{^F}Y1UmEF6Q_uV&Dgj$8b={1XohMB{^rOnkVZouFsO1F#+ zQsgTW+zSFHGIp%=sPx>0ZeHdOy(9@TGiZdF<`{aTxjgMGp!(@&s zc}m~+n^LJp2eq4=rri81y=}WmE4(4YaOl9c_TzW3j>}Kj3f={B%t&A*HsZl;LArQD zz^iuZt5K|rzeRH=#nHnYJ~rOo=3?*!xXe?WF5EZ;Rl+FzA7|w#@r#N26J0IosA^n% y#V}y3IJ?#sWNm(UHT6rGW;*JrGf3LNtYng1f z$Vhj?PqO{GpSyY<9WJ+-cI5V|zNAOiO4gT>eUG0kcPX2)d0o;oYZdKjrIJTgKgmvJ zy}D*~#b?@H%CcawFGG@5CSOn4ED}Nm-$M(xS+4 z!7l5{%-w6(<gzHO_-&?!rMr~fF#8Gbac8Xw&^~)2xx)|pe8ySA^f8hHwySU!HJ^F6VZ{GJymbPt)dNOC~nO%?dy)Ea>PI-R*aNX`X{%sr0 zCHFhus>&%9^9mM*o_d(r*(&eJ59;+PBS?~~n)78E+}mr2)KbMy*iiOecNw()p`AQrc27| zTF;yM?K>2T&%JQy>S3KIRN~@stZmf=W#?$CZz27APVZVVWx`DTaKERsWoHVh-@Dj3 z$z^wYQL3#^P{Z?!r{Cl*o_>Mzy4si1HOK!LNS!sc6FsE3ByZ;aj2P{C+k9F?TWpuE ztGvy7c3S4Htp5o`hwQq;^yBKK>oz!iSNm7IC}&f~)XKl%OID>pF4Pq=ihnHa^W|M{7o z%+8%MZ*MG4y?1E0lKJ$-d!4qNi9TcbaQ5k&uXKZ+`PQDA$`O-QT+Se^_t{L8A#Ou~ z`QbKmE}gC-o;npi^P2~Z^4|onYgivCw(^eLiZjtYS-~If742$e&Y3T#tH(UcJ^Z#{ z*AB^M*T8uWPA_x|rn=4C9+A%?U;bSC)#Iv}+h2$|eV_K*^dyk^L*ro3qlXq=XH#eV zUXt$7aeH}eU~KT_51JR8opu*D2WWm4vt!L-+u;}8>?R=&`+Hs%ZYkRxeW7pr5Zqi`jRB%Z@ z;N_8X)vTfANp1TxCc#6zWTD=6u}77O2l!Am14Ta>EDLP;k@7*~JV!}`%7*%`Qf74d zR>o7!lZv{G7`@8b5PUe$X`@Rr0LZ&ZHP`Glr4(StnmO(qqqVF7az+T20z1cn8Oc&a MFJ}LeG&UOX1IK75y#N3J literal 1371 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvG6Q@n>Sy)c=7)I`%j-f z{rK_Y7a078fj_9=FInLK|Nm28tKA1$rc@H-7tFxUEg-69V(sJ|6qTG)T;1F~Y4)Nu zn|2>QefiFVr!U`r{AQ}bX~Mw3Qt#>F7*a9k?Oe{JM+Q8ujVdCiZf%&hw)1`ciJ32X z8dZvNEKh8*u44E2dT`=C-I;uBO$r<^^k;v{?nj%>?pXZ7zbttF&shb>FV61$b-K%X z!oE5GI0JvTSWl^&^WN!7>KR#YeLF41Rdb5jFSl>0(FmL^SZ-l^!-g|(zToqRZz4Oi zmfiWdWAl2=!0dMwb7o#?UEzF6cB1xtUe$}w9!5HQY^kiM$kYnFZSncTB2|~Ui_RQg zylAb;Ih#Fs+?~e!*K544R8}x^23|LRx8p*}0)bGy9n8*~+ov9x-agxIsnRZ+J6|+K zk6M>!SVY-9J$QVAwS`FT{Gh@fEq-RZrH{md<>YEhH3GfgeGq7{Tbd+(exvxCV6Af> zpCsN`9x}!F9YgNLA5+*Xbt)sC2WusrJe;ql_)5(4x8v(zxg!_rD$Kb%m;5c*8c@{t z@W$%|MU^g_8ne?soIh0VIDGm#=kC7-Q6;mdq*vTNaI<9JJ-Kt+7q#q&T>Ya>lVAPv z)qR(jKTbSx_~xlammYq+QzxYTW%Wa4{^dJs%}U$% z01adE|eB#b_zFE!>k9>dacty`Qqc>D~5!a(w_CVk7YnxA{s@ATyyp$C5^WMU%6R)Ih zpXa}+#Dqm+iFb?Grnw!K@9%vozq~s$w&lgQwq*icceDbd@{YXl^S!@I{Ag<8PJuPo z)D!Iu-`Y&JI=jZ-Vr`jWi|oC1O>qi)US4rxOFh5$^$D@i8eJoc)bF}i-!Cp;4o}|9 zcWI}{-$PDfYv+2hy`9)um#AoOms~xotc+X2{vZ3b%QLgIK5l#z^t)C;K1Jf;0=pN! zCDA2o`VOuNX0PN9F~8hww@|WFEc8=aN1koY1^-ti*j_CE?~Ys# z4crj_`{f6P8*?jO-#RpZ@nJ>#nBG5MFCKjOa<%g$#?=##pJX#gw>;C);Cw?a-B+j} z);dK0!R)VAt(2O@`$GxN9_G$b)c6VXjeCKa} z-+69`JJIULCOhqTMf|ktYcp#zgauEguCr27F>&$8=$LRpKyjg>laq5xOB)9#ub_|= zVTm7xPH`p%2l!16DLPsFH1zgH)B8t4aYAO#qvJ1UGi(=Ff5*_0@lI<0V|n>4H_S?9 zl7E{gl)tjy5c6c?D+!*q$Lf!!D#FVTj)@<+XC~|a;`*a`2Uu`1c)I$ztaD0e0ss*f ByXgP` diff --git a/selfdrive/assets/navigation/direction_on_ramp_sharp_left.png b/selfdrive/assets/navigation/direction_on_ramp_sharp_left.png index 77106b493ff6d51acd0e3f4612baa028c6937b5c..ae1f3741ef57d896fcb8326c69269e04f4792ae7 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUOpw+kWNuq+5ODjC5XtX|*cUFALdc*(ZbIS-}oecc{)ZvWed zwmhC{o+Ycxh|yWjmMr>10|wFck--Aw@t93uh| MSTXyzNP`f?53Il^2mk;8 literal 1549 zcmbW1S5y-Q6o!LD!;ldmMG%1m0!Y{e1qD$^WbYAH5KAZ<8DS_|Fh&fblqq2&$`Cam z$S~{;GKGT3UX_H6jF3d2^r1cNTTj39-}~MBbl+~QrMUqQmnati0N^n)L|Pxa>(Am~ zKkgYZu5XUzW@&1RI`-)3=+e^C#>U3Z&JLYU-{0S7Fc?fGll2$h|INXF=IH3C7kLYM zd_w|fZEgcN#m#>PDucM7XJX^x?t{CFk4w&cQS!d7rDu3zcIB+cBp(29QpX5+%{C%y zE&B_;MCJ5VgOnf;9rOLDQ7rVjh$JRSrixE%;Qd7dRYh+SYqvn|asEH@&E$%b{mNQE zQ`XHLQ8wt5Oy+v5j*^$SyR*4E;2@1$O{x95EdiW|F0 z*&ME^Fk|gyxE`pB%e-^1OxUQ`SXXd=E@KlW%L7TIPF_(r zLfzXO+jlS4)e0jf1Cqyrs?(Fz*IDGT`_&EtIsw(ctPUO}uXTwVO*ymcQSR7u#5ep* zZ9LCr=$Wf$idIf?6nsTnZiiPjNg%?DKerZU#TQlNM+Z-6R5c5RQ!e$FGrwuufzcF- z(ssu#9=3ooQXbG8va@>+BF16|I`c$=cG_~tyth3Gq^=p|P^75KSR=3310|Vn^hEM` z_chw`(7~mKMPy8A6hv3OLN_xPG%I&Lk?s_8SbuqFgmaHy@V4$x;X`rk29$A+m>_G| z(r0Ysq^1GINd&|di{0p!x9uCqA^YtDp@~5{XAAoqt9)#$UJ;NV?_AiP8Z5kJfkyjw zn;9JWrz?9zIqv=OFyZ>j-tx<0MnX2=nfoQeuy-3t5KuUr^a_k4feFS?xP8u zAGF+>0QUBKrfr<26B#?6K}MqDpE9y8&o&&`Zq8Tqfx$G5rI2ju!vcg+q7_1*b7T)! z8T#^g*}LBA^nk|i|EQd7E`gcg?@C>AG=XQBrvGUJw5frSW>L{*jS>{%RK zYJMy~{0l3!SXT*ur+wl2>IVjaBUr0#FZ`}4Fr)9Z1w{ZHB*d5L;xO}J4zlGH_;&7| zL@K)NP;7$0DB!S96jw1v!g2!d9F^P7k?3Rxq zZ(xH^Ied>oaY~Eaye2U?MDc{Uun1~(s?*h$JNS9LmGt7dekTtj7bymkBUO^5@rv9c ziyvo>KUbjNg~i)ziq#So#n?)H_1)4UuO#Ntv!H=8ey|RGOSPKt+%HJWR7qV+U#?7! z5`jgKPqwFEvn~F6pn9N5Ea5;A5Y0tzYKQA-12r^Vl#4WiU$?;ZE&<+qBAfF`q zzhhagM7ENLL)eWnLJ(;xA`@ME#c%5NjX@gZ)p6(^N?xTfJR(~f2x6c-F WuY1_pt2_Pp=K_pS=18)h8~zWbs|9ob diff --git a/selfdrive/assets/navigation/direction_on_ramp_sharp_right.png b/selfdrive/assets/navigation/direction_on_ramp_sharp_right.png index a7e3c4cee56a7de3cce450a83f07fcb55202ba71..f338742e5883c917dac520f01f1ff2f7dc50968a 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUOp7hp*@!0rk&DjC5XtX|*cUG$!PykuMJoQG2PzHSem+yC|{ zTOCg|PpaxNVswHq)$ literal 1551 zcmb7EeK^wz93Od^X=80txm3S0QVx~$(7WH{ZOdDmDT|#$Td};HNp5Zyn{vBRxk%bv zXob$pp-^U(RLon#B=bnxIl~&7xZOR^Jx}-7J@A2X%|UkK{<4R+ZMh_+)tpUa0txn;o-}cX+Qzp(vWt$)}GU%UIuWv<>Jf zx?e_bXfWub^9K`TbN3$j=lV*Nj@Tz?A^wPwR zuvKIPKa(=aILTn=9{+_BR>%u+S590Uv-Kx4xTW$ar+R#I6;rk$1Q7*0x(JE_28J)| z$Ind9Hv7$piJ;j@0xHU3u+PFPfFZXN#y;>gV)xa4WQWaPER{835GED!ae?C>A|1fC zsv?BdqDbU776q3&AaCs^nhV6u&hKkM z5AB_tdww+KR^m1XWF#ZWC(5GZV4*V$5Z$v^dq-|&$o4E%IU}zO_C0D>20QVrD?CGy zSNgpvqS!@N}5 z#%Ek-aQ3v^`F`wu?L#T`@HFVsU9!4H*=7Ifl;${)Stq6~&t_Ywr?r|R_Qc4?1=~2@ zM`m#^Td36(nuQnXv8&23Xrg2-x%a%)PJ-AI*V_uTzqB*=E?6}Q79-DI$kp$9a(f=2 zvQK~A5EqUS!ssQ4Q@Ym=YyEV$aT06+s5kT9+!yS^c5_l)XKiqufq*Z=FHiUkyr~3y zQoFeuN3f+vR%k!c?WEXk>^xsDSD}0>PV>itFS63oa}J89EE&ANm*z55n1G zL*r4-sZQ7;PO9_S6+r>JSgp`TuEnD~VLZ}}x7v`F*@ad1bBs#2daZ6|(wd=xD7lR; zE_EVz_3MZ#h!b`KpriVryL5guefU_dO5jq{de}gFO=ZKHEbH$6r<9sEzpFE5P-bAB zFvdnYq<_SudF)(O3iO6OF@IB+)&maL>t3C4J5!HWGCSvHdMZOwrRc(D9%DCR literal 1334 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!Hk}t5;vVc=7)I`_G?0 z|M>Ca7a078fj_9=FVW!t|NjSrta^YJ$dv^71v9X53y7;4TRV9LM<-0O!S98_ka)FR--p$H*(I&$YN^{laaWA^&#_v<-EeHR2-Khu)uSPdrqs z%R4_kOFgzOFZ@OC|B9V|JNmA*_TRq#A|ZB;P0O1L2X}oatX{Wli_nLn->#`#KM(3} ze;)s$;%&vVZ>m;t+ID&Rfv+pxd;MEuvio=VrUVZT~-`@|9kHuauHf8C3urD;jug$lQ7WE{R8R_ENsB1nK6-!NoyQusn?w$u(EV}!t(|sFPUhBzqmM7|6koqS zQY}vI^yd6|dEf8Y=$ake`Q&BU`Ow73m;*7N3U;qvE;R9TQrm17n`2@BEFI?EdH7Dm z<$IC#gPE^FM1=l3cF4|@*`)Krw&P8?sBwtB+O0QoXB>r?sv7?Yeez$Fxaw_pMA6H= z|1#WlLN=VOmY6u@-rnHVY)70GzWgz1nW>V}A-&`ScVXKhh14R>Bse^mVGt-SN)PFm6r%Y&UJxM@1)+;&yW56O@W<(c9|iO;O!eu1cJ!0T-+CuOctIiHwC5+gYjanl%dH3>VEM%0>FVdQ I&MBb@0Nzi!kpKVy diff --git a/selfdrive/assets/navigation/direction_on_ramp_slight_right.png b/selfdrive/assets/navigation/direction_on_ramp_slight_right.png index f8ea3800e880fcf640fbfec6f849308a49bb5ac3..d309f2d14e2546ff0035e462a722a6883932c02d 100644 GIT binary patch literal 129 zcmWN?$qmCG5CFhGRnS1py@0#nSOQWKtSt5;)yJC0T==CiKfJGX&@RMttjDX-cHhsu zmH8HQU=f#Mb5u?}X$QNk+Dlt+K{^N(GZ+^`vLSn`vedR&bgsE*01aY}r500D15hCt M&uILRD`SEE0j1q0=Kufz literal 1346 zcma)+eNfT|7{|HtElsdRk|qdPtNBt3O`D`5zTpy6Q}d+=Wy`kIA|>C_siEh~R))iF zDK^y7O8HTiDJd?>G^K8#35r^&nMy>8hzpRh-R-X3?T>x#`#hiL?)l^S?#M1S#3=sQw~_?vAxT2|QmCA+H=0tl)8Jy1KcSljM*lEvYo{7yv5sCD6}c(9F0?#y#Ide_mY14t^bS{677We~<($2Qasz{dt2zX1~@ z+hOxA&ZV=j-;l1D^dptohY#i^A9Ffkf4UevfuyP5lb zr4hJk)fkI;I9q+wSudPY@uf4pL3dF;pCNz?EDT3FO=Wk=2QzbD4=31NQ9rUEbk+_n zHTTZ+ObU8ltAyi}6%wbm#0zT|G2B)?Qa6Y~W#=hZx=l=C=Qu0zcz}Jm-BdICKXR$i9ce!drX9=#?L+#xsNKXdsjgx)9-jla? z*pAi!T%vTn-y0=;!FL2+Y#Np=3v*+CP|9vws(86?SZ?haT3S%H*Ij|bxYa+?f`}pL9Pqj4CrcEmDGZ=l{$`*^}ja%crrq4P^p_lfgj)sK7T;R^f9$C-X z!kIp&C!`Nd(O^f!@8QPCs`r6$dG5K4PQ6;6jQV&=J30t)ue)~jYYvy4%Z~SdD zZMp-93j0oUkwQVPW5b_iAc=hi5Mx0_;56^}hFjL`Es~VNJI8mgBBSA=&A~9&qgkpJ zaP6yOI>`a-9=_B1D>Paim*r5)_*!_!V*4cu5#4nSj^6tu`|ZO;+yzxp%N*HIH09 zc@6s&Zu?WWGt|d!WsLqX2PL4mPq;P%^Xj=)PFX0c+wn>6^uabSmE{zU5PmlNDo}7h Ks6Pi!F8mXYqQ4LT diff --git a/selfdrive/assets/navigation/direction_on_ramp_straight.png b/selfdrive/assets/navigation/direction_on_ramp_straight.png index a01045ae6a2b586dfc374fb7e34e4adbf78a1180..ce2c42c43de9c52dbe143d6838fc738e017e245e 100644 GIT binary patch literal 129 zcmWN{I}*Ym5CG7gQ*Z&pF32ypSr!mxR5F4oJiV!2^Um^_eY|B`>zKPzkG^itI=9RA zO&gDwnnz)Eq8L5P*+^Tr0|c&MdXcI}p0e;B3q_ MkeK~fB!V;I52R!!;Q#;t literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HlU>(`$@fBydc`>$WW z{`m3Z=g*(Ne*O9l1AkD#U!uYP|NotiUB3skK&~XnFPMRiTR=+9$lA#}I667Iq`IkR z^6X`6Htsok`tt3kuYYqzLC#x{pH2;BU>XL&_Txqf=~ zdqcVH)8gOXE4Il}3s-*cDQEm^zH?vg`$)Nq_V=1jT&tepo};?q!@OmC`86v)&s)BB zdl8=|+dbi}#v1RMPON)v8!UbIZY|%$sow)~*Q~O1S#~ej)ck7Eo(CTbBO(?4%f!9; zal`tBrOUoO?%B_@r#Yv*vyB%1T){VS~I3&J9oKW$hadM~)R;kuS`^*`w+QK~w= zIS00$x;D>_C3+R3{)Y~?gHvm;Ti+P*pS?43FC(X(zOWKI2b=}249>Ik3_OTrAjg{58AP70WH zgCT3`luJjR?bqLSpY!yd?5S6PoS4-r)78wbM=f>COe$UzI%|sBw#m0YZj1!-R)G&n@|RrRHVM{Jmi-wSCQ|iwFIkx^st37A*nM9sFaS!-6;OQ^D1 zuCIPtGht=e$|-6`gKi%eYR-6V7reIU`}UB`R`yBF8n5k6=LOZ7-g3Ra^oD{*>RtKd z{_16QADFkBo`2@%cy8Ij{9hlwtTf#&zhcb?hsz2Ncb)&d?bgNj!cp-L8=pDmz5X}j zrGQP$`|^pRav8!;HofEDEqZ0YPZS&D5rqs17T)Fsi2^1(O>K@B3^=6N91qh<&^MlQ z(o;SY|K)df%KT%_4LSAp%L02=wRJ^@Z9klU+Npi_{F)VagnK(6;9rBh_jJ$Mde?Wn Q0cJ=BPgg&ebxsLQ01@OIpa1{> diff --git a/selfdrive/assets/navigation/direction_rotary.png b/selfdrive/assets/navigation/direction_rotary.png index 2a5d264bd274263aa718d1c09a680aeeb6d5c053..20b405201559e2f76617ae89dd6e0b2cada485a7 100644 GIT binary patch literal 129 zcmWN?$q~aK3;@7CRnS1pEm`h{Lj-1$*b}fHsXqSfUi=+>|HwAyF*c>1eLh}wEcfk9 zOWxmV9Gul<#NLY>E!qH^Sz+aj7IQ%`G2Ye$IU(xcKy#srI+BeX39HX8kq=3GM+C*N L6{G)H8qy&C$Xh2z literal 1906 zcmbW2Su`667sq3!l(EzZqg097ibM@ctwHV7zL!uzt!0v0Qd{jok|EUEv3Ie?Qd>oJ z8d}?o+M1f$)h;EdB!Y~7=X?)y=52oG-1~dD=iGbW{u#E`ro2Ch{r~^~c+Jg>?9V*# ztvJ6w>&0oX`ZFPHtsITdOeT}p*VlJ;cJ}x8X*3$0PG>L}NB@KQe~$mj$$y2#Vnt<8 zzP%-L-`?5*z`@NgEGeU?s(sVK!2=$Qe2|=lFR7|+?RrHTo|v9p+MrOsDil|-0053m zb0Y)C=*N`D0qE)?8(?2QVmb1Q89FU5YG{!b?KA5T)tJ%VlW6J3sMv(}ay&0U*?Ye{OuyIPt z>wXsV@Ycst38OWyK0HW9mc2nF%`1`#i_S?8$0#Lv2473qJ-kbuP;;t^TJaw<8bej}_r=vVur|J-nW|D?i z;9amERP@RAFoB}CTQcK{j~y9{3NFB)p_U)O2Co#uNiQ5zk>>A^%k3>icg)0~{+Ve* zey2n4T9?|hJ|^`AGz2t>adJ~zP`tW0&@V(XC5$pSw)@AYRK-CjLZvODZ(yY@6isNH zp~)A;1uA<$nqiI(F(>LKc)VCa8vUyDU*f$9+A>JOE&RBapjum0##=jtpLws~Equx( z2nHcO1>P$~WhsU3jv`DwM)C25p&qhJ`7`0J2M3c2AG#;&14JUBnU}(2EtyK(Zp(gm z?2wg|@A~x@@>GsrY@8x!x#oqu4j_1ThA`jJhHn@Nj{Cw!o7;WRr@JjG+ zEd&oaf!X75eNeUhwpjQxj;5D?{((-(xzZYNr!}7*KU>)RS?-G3*$T|pCUKzUrVHV= zk$aoUes#En`gsAMV@IEW>Q6PC!fTzqcO@ELutIDpCDP7~Z?p9QMrw|#2JX8Ntc2i% zLZ3K77)Wqi>1(@3{D3Xw&7VWGB(6)~neZ-pLe#$S(L-LcC9%xH)qPq~ImfBMyB(cs z85yLV%^YcJpxg`UPV{<}=Z3kyE44fi4xsacY#w-T=#4sXq4!(S-*jeV%&$ea!0wwZ2r8&rYxb8)ip`M z!xV%>Jj|CzFKOlhk)}9Qf~2;hLdMC*V6G+LC*8^*Fxdd7Tw?ju1iDCLSTzM?S&_Ig zN}E-Op<{ZWQkE)b%`GDcvzem#4uTSOI`7gSSRXX1+@RL{hUk_C9jRG`w**R=-qfrT zd2yp3L3S&j6lKmypD*4NhigCOHLa>PK^O);k9BzYI}~%-hOb{nIJv1YL=%>F+fa0^ zU$8IQ(u|~&Q{_Xjxr#{!w;)*pH>L)u%GFxE+_|Psd}{g2$%3#crP{isABSqe>BU;y zTzQ*~VNxwAYU)m|wL~#TkYmkGA>UJH(73`0UN+ZLkY38RXqn@@QKU8~ySDX!(*@Z8 zbI*6)C{e4pfT>^xcDPx$fkgw|p9CBGv&FrZTtiKVofP?8ldP%OE>ojwrqky-;GM9m zw|O*3cv&UZz{mNmQ(e=<%PIaPuAHKI7trbT1x6BaP8WyWUno<7J(f82&0@tgT&P^;^T_7DjNMait_Df&! z7f=~L=8ztKd?C|a&ssa0oK>+H9`Fd7g~dTQmW)HPo-dRe0#RLzu-8~fy^vgYn30yq zuNOSqF={;>n$%gKOgf65rxINTynHx4hd_Go*nD2MVNSR8vzIY77vD}&1de*Y+DrTz zWxU96=D0Vp8m7B$6_&8Y^2L&v)qJ_RrY>J%dCcZctWfi(bu0PcP~S)B5%+n%&hB=F z6ZXq0J+d?^c3LY)IkND)da)5Z-mc91u<|W>0fWGn9Qy78$$uUs3H;dv(URdof;`GiO&+jkF4$`nB?o!;2Gz} z`MBhzhNrMfD0@j>-)y$Q+{9`|gWcrJo;fsDwMC#^3qN6o$BoqbedMS!(7~9Ma(ZJ= nvBG;(ce(yMSK(0mVKissI20 literal 1543 zcmb7^eLT|%9LIOnNiw8Up$AVlCM=ILy2P1nCTW{glBZ2&ldCfpm(`HR7SojHQyyQI znXHw0e#nrXG$ThBy0T%%Pnk(h_j=tQ_v)X!_v`!py#M&VUf=J3-&7xO6j)794Fm## zF=%((nnhn)b^TiAT|LEFQ<#qz0l8+eSUfc~C6~(q09agHR45cnOH0bXS^kGl|7PX? zu)4aslR3#=yJQ%J^TvZz)U~!7LM-f^-LQm{q2#DIMtXKZY4tr}*W-ba2^nyjHGlwt zzJ+1jT?w>;nSzlV#y)kV&KCLY@c`7DRr&>UgYwI_PGvR>4#R*Dtj3`c6UzbFH*5R9 z<{*~VM%%v|EOh8d3|8Dk?<`-biNKY+>`gW6{Pc1tV?0(zcO+2vCNewiM*3_1HT461 z=<}l;>x$Idcx1f(a2-_@65zkQEf9INBcTrWGRx5k&y+f`h%fnK-zWU5MSj^O5*e6r zme3-|5Z)I)=7&%4D5Y@jmWf*|Y**{?8M)F~&?F<+%#Q6#-|HOqten(#@U0}Db#_m5 z%6w)0_PC`AlcRr*mQW^Oh_bon3t%mQk3=Ri8R3djg z(e$daPZQdWzFXSe+Dh-ja?0Mhx@Q~#_bprYv!LI}28gWEA6 z(v#A=WZh?$(nse!^a|~YJPWH2^b$xW;sLjFw%6@&$1VnDO18`DVfQk%lblB&4UF&~ zXhC8@FJ_&KlXbpfbS%xFDX?W8bMK+4}!GMmoB^iZTo>&id0OeHfRIA)yfIFOWzP6Jw0y6qhviMjrbv!9|S z^EMC7WlxxMO=ZND!{PV0Ce==5ZdL_L$&+bUjvQ%jXalfjAvvUE&CnUz;aGN<{ZhDy zA0#M*(5GIVE#E^l8~P=}?(_ zRr&p%Ev?^+Z+9&KEUUj=)`h7!!S6SnpJ6po3Q={L)|jKAscf>d~i} zm(}Y(eMAGGV%@!*th_gNzPe&&=~ySzJymdc(O?i-frtAsXy6XGySGTYit>HaSlv%G$=XjC@N8;*jBkAOt160G_oT8O*_Z&Jl40!ICmL$X zuFx+*bH`W@5Qr=}GPiiwt3-3%6wsSGnoyHXs&{Z*_|-GkHf!uRIG-5^=(H|o5DlSo zIoCl8wKssg;2F|1*I&7*Wb(R2Q+BjZH8sB?N!g3HrG8`FwYsQ zq)FsS^|AcdYU!w~>02A0$`gkkT1gxL6ni>y4|8l2FRxh8Yv$=UrXZ{wgdkShPnRSH zQ8~jKG+oM#PCFW#*1Qu(rip0%TW&I_V1rKk|5zSfDBGi}bDO^Yav+f6z-r^0)`q~r QGr?cR7^Js*jav}oFK_w|p8x;= diff --git a/selfdrive/assets/navigation/direction_rotary_right.png b/selfdrive/assets/navigation/direction_rotary_right.png index 32a6b2504ba9d91a8acf885e358252574ad2f77f..9b8ea37ddfd432e6d2393563cb074d19746e48ff 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@tQr{DsX5(tvshEEV?R63^Z!qe;9yo*0F=3DpU*kv>3F^=1_$@0IS zc`5T{WanZotLCU$dIAg^c|e=NM{KsmT-OZ1*+HvF9w|TopyDF?%9Iki*cy1st`r+^ MMJ40Q+TOiWKck)|s{jB1 literal 1743 zcmbW2dpHw{8^>2FnUUKQKO`HAVhS-$iZ#d2TsCsc$RQNJ&->E<`KzVY>M z$o>^_xg5^8EZng)CU|?E2kZl>X+xl9)(9lp2Y2a*gzH)4VoF^Tvzs$CI{EH{aB1~R zMHfCC0N6Y4igNO~Mp>+yt$b*xmhI1MZa0T<8AJFbx$c2|$10(Q_w57~VqRwzEp-=r z=*0F{!~N<15VeYZ%KXteReZuZTJ-wE<_K;toD$jHf5 zhLy$Cop-5)0g)Sf5&j*G>U0U$b!t=2yvcRvc;b=ujDlseX@upNQ4GMP|WzKp?S!^2kt&V9&LNfCW)jQF}oi3(cPebyrr{-o!sO3sofPPObjM^J6{`? zUMsDd>m|rhla5}KO*#fh9ykO4{av%0^%5rfl8(nm&7s5C7y;_`OyZ&q8p+DT&_xMn zE8U3LQccQa8B6tIF0oNCd7?`2dUGXvx%B1jbScydaUUHZzYE|m223Ai(mAaOZ%52R z(0%8Xw!+T!XEw+RAKN!Ll|WnpCMizkgEuEhi=zcgV81}pf7ox6s2`~l$%%Qi=T|JWdGT+UsD+iEAyjj<~mcHugh zi~TqscJuCqaSZezTc+s(n`Qt$Ie$5y8&ZuNdk(9*5PUuJ3k+`5tOcC)gFfAy$ofs!gb*i96p~>B$HCp4g;Tao(q8|boTn4Rm7;DaW zP=;R%r9)6@YoV!#9P3_wZ`L*eTU{gaUEJ9S_o5mj9N6Y5hinS#$jB>F@}`Ezw!LWc zcMEHBU-#3%;e11hw3*XBeoAqqOg#@g?T!Ar1gPlH{h=zyw(?U=!>JUvI+MFEc*NN= z9)$IA{cJ?ig~N6N0tKb#LDUH}z*xgC8zrCS;>=gOm&5x;Bc1^giXlg#utRO}%mLs& zA2+)|1jl1M6RICk@A%6Cr>vcrv(>|^tyBk(x!4kI@~%{r{8|_XrSy9D`$Ty1o-+sU z6rzB<&Ror0m*!Z_gZInoZ${OlYP4GQ8gzYdOY!LJJZp9#*|O1Cwt~L$nD%>@wcTAh zh)E!n+J(ONf4Zdd%io9xW`)9FL6&qY{$$FW`BPl zSCjWl_y!z)274ADDF;%8=o+0sIJkQG23`-pO-ae3(kfp!xAl$Bd|7Lsht&fB5(X|P zdoLpOPbxFF#2Vx@qY{EvX89=3?HdA%XT2Qko+Ww@T&SB86u|l$w6(RRNwJ2>B?dnm z5_v>Nup`~YfF*#8UNpAf?A1uJ3vvGB_RAB=p1xNpHWeGiE2OQ?j`Ht)NVf4 z?C!A6fQOhZ{u;a+y!MRL;Z`7)Ila@&%3AvrQ9l;JxahMTDzTLo$x(>dXV8yo^-LeA zM0ohCaNAY5px~{Qb3}+&^IVO-k=jri0%srRY=iEzC zLmY|SEgaP*hoom6e7XD$xxy2xwg}Etv;_yFp-ub=qYOf5x_k% zumRdDVh6mlsUa`+h7w#V)@HVFQcWI2vD6p6-?kHQLO_b&v00m)fd=H)m1sJmX02nS zGERXu!r<*$rvA_kZP6x0!j`(*BV1c{cx-w~YtWMHwzA3!xUnGsqvr=6;ec4uo@ts!c(2$~nOkuR>9~bOaP1W{t??t0sV1uwHZq-C ze(0=eUIf?{)s}rMgx7Jf*c4iw?|PN_+c*ZV166=%V)fsYQxVoR)=mxxQOiSAzSaD= z>5vFXgpgi;oczQ2^T%>~f{?rBmbQQbbPDH>di0bWc61*99|<_ z)bB&r%+0CGTYaZelV%4Om}eJwOHHDZReTRXZdV;QaD7QC(pVBW-7tU*seKR~Tn%%^ z%j=%m<{&;rd)B62mkpLhpi0BF*7*}Ymugq}ywLs;6UaBYV-3zQb+=x6(**)uyr*wb;mAmu|D=}d|2Na1D@Vw(j$nw9kie(D@0Sq6f}@U zQiaJI?@zv?K`xPfVn^kMHg8x|%oq8rGdCn=q-JL83&IJu=fO5Qi8}aw#0SUt?!fGE zb#$>Xuz~BCCq!5Rr6CWb}2W`J#Wg8^|Z!xZ6P<)rv_zDgD>6%-MbuQJnbW^D) z|0vx34sOR}arsFXEqS9werFNoCr&#TDYGyp2aAw_(RsWTt?TD8-bg{ zI|}_q$UnU|yq2aAc*f(q^hca~%r0 diff --git a/selfdrive/assets/navigation/direction_rotary_sharp_right.png b/selfdrive/assets/navigation/direction_rotary_sharp_right.png index d15cbee00232cc8d0dbc67b827638d223a1eb7d1..c7d4ba3175196d474411b8e3f078ad6957665e0e 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUOpyBkP13oOEnN=7gTtJimV7r$p8FWJ^Q=b_ZSuiK-}?SK2E zEsv*~CuemTF?x}+kp(<-T0JgX%SpWb4 literal 1855 zcmbW2YdjN*8^=d3aa@Mj>EgCDEaaAZnQYiGx7_-KkYS>7%YA2&irjW`ztnLwtiy^B zb6JEUS`B3jHP>wCplvR*x%{2~+y8m>|9+n5`M!8wJg=V5FWcSKQC9kxGynjQMLNMR z9(d@VK78n4R@}SNc%UeElo#T_bUK~IVy&&M@pwEwpTE7mEf5HV|7GWY*!_Rm-`~GP z@CF~;I~jk`)e|5g0|e<9TVHVY^g~C*rex$6RuUT8ItPZSw9ky0Io1kKF<^WS0FVeo z!tK10s#dBPB{haXSfw{mOgzyeU9gX<0t8<#0Y*REoWP1GhqKL)r!rsvt}bQ={I}XO zuUNca>L{AI5F>?kR#H`ve4t8Uf6WdrC#S5e zd`#Dnq7*^NmtA<whgn(v8c*}AZYe%o#j&@gDs`t>zX}fR2%Z1ZhkZ|63bDT&KYp$%nS!gc$3v}R z9xVf_2h#d^5_2_iV$uHkI&wuv9z^=xZ;!CSd^glkX3SjgP_Z_DtoCrWgdr4_X*i>P zlCb?PGc&G}0ExfC^fggY=*EC#x9jI5p+UvT{$j^qF<|gsIbT9;+eaK|?$V3_Y1>6_ zdA03?Op8O9F9L3s$b=`Rm>o*zk?>25%MLS_9yp%_6QxLue-Ej5IYEVZ=b&?)PY*ifvc8U5lv571kDP`5BZqz&+8VlxZdXp&PT*V@Palc1 zRC~nLr_fsbOrUbDgcEqn8$E~(QahYK&m!(cks;7(a;YKkN* z?y>gXm1VcDmKe5|fC@puvESw@|wgK+i--9sJdU3;5@USYKP$?GAC zJ@daBmP4uQoyW#1a*IdU?P{{gE)vpZZ2_myX8sYP82(|R z>=T@aHS!%`gfR0C-~T-|)RcU?cX7yy()^?hmHT%AC;R8}Af;eqM!33^-`>?D!A8Tv z=?o!rBuKg3teGqS@|7*$)-VTEae}vXm*n&!2USL7CH*2aHG#izS^fx((!H5KM3f3h z$v&^NWZD9EK*IN=4>b)_(%dH4Y}9Xdh1cdFY~3+3_z=WrBcFI2*n$wau!rl3 ziZ$C{EF>N{4D?>^(;N$#n>d3gW^l6NQF0Gx{B!aiXW{-})xy>dw&!oIy+>hFguB)0 znOkQpqrk)2xtLXS8aAc~t(GiE)5>}vB7_fTTzq`P1X-#?y?4X|}mDape0fY&ze zpo6oszJ7Z-&}hcZAyRuuU47qJLY;1~PuXT<)-k6JcrJEL)B-UIGY*8`!WAdAD)1_i zafrtGphBpEpT_7wQX!P=vTuKtMwL;?ycA#UX$S%haZdX5^T(yduSzH;w2q@~&*1A~ zqko>67R+v2YHD=~(j)|LIQ=8;(uGTJx9BweL|iw;8jK6lI0`e;+luZS^n(A8wWNvY@Ab diff --git a/selfdrive/assets/navigation/direction_rotary_slight_left.png b/selfdrive/assets/navigation/direction_rotary_slight_left.png index 3838e720a3553a713f012ce6fcd1de134edeabd2..9b5912030bd2436336e6bdb699abfd20a7141652 100644 GIT binary patch literal 129 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 N0kw?wV*!41%OBisCtCmj literal 1736 zcmbW2X;9OL7RCRmvY40YGKO#lha=^3D0;@q&98=)W&~+Tf>h^CEwH2N9D8fIudfo53;j`eSo; z+6|^~&Zz{O^9Jc5e`z-9w}@P$_KVz(F7dTj@Z5tx8pS~mh?;*CNwj#!v6rp7uJJ6G zQy>Nco6mGLkp~A$hSWBG$r6FFT%>^-akX8`=@#cL=z5M3%ltTThJ4>8J4A~ytqk7$IF(OQi z`Y>$fGBksm88&N)L+(1$MCMdd7y$W|SL{`IujH54=_Qr&vV)AO>@$Rlau;?-teafE z!`#jYhEjA*DfJxduF@l@=Lr3^@P!=x2y4m^+V_G&tPg895p2&>62hZBrigtfM@jPQ;W$B%||8u;iUWsZBF~zaG z9W@dRi>Ot%{b!vgUelE3-|+k9c2R~#VjHOue<|HFA*}xvR&CJETXVc?-vRvj` zHW>C>q9-`cf0gF8;(X5$h)ew%dW&nnw|3w(N8tbzWS(i0VE06g1h5idfY?*iW)Pa= zhjT-bBYTa$^oRyu0G2CD&_vo}a05&UVtmAIkB{nw=Tig4p+6nhdd7l$h1^(>)O7+E zT6$s|_B~1{lO05y6<#%H>CbsTxA$3%rHNPmodkoD%$RclThndTe$F_!v;&(ZXko^O zw|ane-X5Nw=umgyMVR~8^c+fphG26^vmr(xaZ}KDa{y3y>Ke4Vz68RG{F$xmHJIu= zIA>F#fFoZQ@Q8~ydVhsf?-{iXC}1ocDa_+ejnP~%Y2#fyGXAA{3dA_E3v6B-)&#G5 z+;AE(`XIK<)A8wxq{C2CmOY(Q?bxlwcdB>O)~~khPfMjIWED4-3A_ZR#ZEe9hL}_%|d+!q6E3<|%j(e@Gzb~k$ z7s#x#r2@O zR_jyIhwoZ=g|YOHnzG*4J;a2T)igV8nW=(*z%kFc=W9deXHx1R8S$Z>#>4irCm4Vi zQhkgPARJQB1~agQKhdphTDYGceY)*QE9R1e!qwP!)UG$b1E`mL^$#_DRn=)8 zPW&u3TWReI8R*4{*5|jW0%vx=@ape43yS|qS~T+rUt(XHg3i^2uN-NcUUw9(oxQpB zwMnCct>Ic19AXwYp6vZ7s=G4)H)V|uq*(4hHz{!Gou(j)2*xKfq!Y1GJxKTD zq8#6Sx?6YE*%=8H6Lbv>cPlANTWoZMRR#2fQYh<2Xp8fNxEP{5$2b3@8U84YMO&0c ehvnaT^ZvjcA(u{%y1l;p9{?9ef|HwAyF*c>1eLh}wEcfk9 zOWxmV9Gul<#NLY>E!qH^Sz+aj7IQ%`G2Ye$IU(xcKy#srI+BeX39HX8kq=3GM+C*N L6{G)H8qy&C$Xh2z literal 1906 zcmbW2Su`667sq3!l(EzZqg097ibM@ctwHV7zL!uzt!0v0Qd{jok|EUEv3Ie?Qd>oJ z8d}?o+M1f$)h;EdB!Y~7=X?)y=52oG-1~dD=iGbW{u#E`ro2Ch{r~^~c+Jg>?9V*# ztvJ6w>&0oX`ZFPHtsITdOeT}p*VlJ;cJ}x8X*3$0PG>L}NB@KQe~$mj$$y2#Vnt<8 zzP%-L-`?5*z`@NgEGeU?s(sVK!2=$Qe2|=lFR7|+?RrHTo|v9p+MrOsDil|-0053m zb0Y)C=*N`D0qE)?8(?2QVmb1Q89FU5YG{!b?KA5T)tJ%VlW6J3sMv(}ay&0U*?Ye{OuyIPt z>wXsV@Ycst38OWyK0HW9mc2nF%`1`#i_S?8$0#Lv2473qJ-kbuP;;t^TJaw<8bej}_r=vVur|J-nW|D?i z;9amERP@RAFoB}CTQcK{j~y9{3NFB)p_U)O2Co#uNiQ5zk>>A^%k3>icg)0~{+Ve* zey2n4T9?|hJ|^`AGz2t>adJ~zP`tW0&@V(XC5$pSw)@AYRK-CjLZvODZ(yY@6isNH zp~)A;1uA<$nqiI(F(>LKc)VCa8vUyDU*f$9+A>JOE&RBapjum0##=jtpLws~Equx( z2nHcO1>P$~WhsU3jv`DwM)C25p&qhJ`7`0J2M3c2AG#;&14JUBnU}(2EtyK(Zp(gm z?2wg|@A~x@@>GsrY@8x!x#oqu4j_1ThA`jJhHn@Nj{Cw!o7;WRr@JjG+ zEd&oaf!X75eNeUhwpjQxj;5D?{((-(xzZYNr!}7*KU>)RS?-G3*$T|pCUKzUrVHV= zk$aoUes#En`gsAMV@IEW>Q6PC!fTzqcO@ELutIDpCDP7~Z?p9QMrw|#2JX8Ntc2i% zLZ3K77)Wqi>1(@3{D3Xw&7VWGB(6)~neZ-pLe#$S(L-LcC9%xH)qPq~ImfBMyB(cs z85yLV%^YcJpxg`UPV{<}=Z3kyE44fi4xsacY#w-T=#4sXq4!(S-*jeV%&$ea!0wwZ2r8&rYxb8)ip`M z!xV%>Jj|CzFKOlhk)}9Qf~2;hLdMC*V6G+LC*8^*Fxdd7Tw?ju1iDCLSTzM?S&_Ig zN}E-Op<{ZWQkE)b%`GDcvzem#4uTSOI`7gSSRXX1+@RL{hUk_C9jRG`w**R=-qfrT zd2yp3L3S&j6lKmypD*4NhigCOHLa>PK^O);k9BzYI}~%-hOb{nIJv1YL=%>F+fa0^ zU$8IQ(u|~&Q{_Xjxr#{!w;)*pH>L)u%GFxE+_|Psd}{g2$%3#crP{isABSqe>BU;y zTzQ*~VNxwAYU)m|wL~#TkYmkGA>UJH(73`0UN+ZLkY38RXqn@@QKU8~ySDX!(*@Z8 zbI*6)C{e4pfT>^xcDPx$fkgw|p9CBGv&FrZTtiKVofP?8ldP%OE>ojwrqky-;GM9m zw|O*3cv&UZz{mNmQ(e=<%PIaPuAHKI7trbT1x6BaP8WyWUno<7J(f82&0@tgT&P^;^T_7DjNMait_Df&! z7f=~L=8ztKd?C|a&ssa0oK>+H9`Fd7g~dTQmW)HPo-dRe0#RLzu-8~fy^vgYn30yq zuNOSqF={;>n$%gKOgf65rxINTynHx4hd_Go*nD2MVNSR8vzIY77vD}&1de*Y+DrTz zWxU96=D0Vp8m7B$6_&8Y^2L&v)qJ_RrY>J%dCcZctWfi(bu0PcP~S)B5%+n%&hB=F z6ZXq0J+d?^c3LY)IkND)da)5Z-mc91u<|W>0fWGn9Qy78$$uUs3H;dv(URdof;`GiO&+jkF4$`nB?o!;2Gz} z`MBhzhNrMfD0@j>-)y$Q+{9`|gWcrJo;fsDwMC#^3qN6o$BoqbedMS!(7~9Ma(ZJ= nvBG;(ce(yMSK(0mVKissI20 literal 1543 zcmb7^eLT|%9LIOnNiw8Up$AVlCM=ILy2P1nCTW{glBZ2&ldCfpm(`HR7SojHQyyQI znXHw0e#nrXG$ThBy0T%%Pnk(h_j=tQ_v)X!_v`!py#M&VUf=J3-&7xO6j)794Fm## zF=%((nnhn)b^TiAT|LEFQ<#qz0l8+eSUfc~C6~(q09agHR45cnOH0bXS^kGl|7PX? zu)4aslR3#=yJQ%J^TvZz)U~!7LM-f^-LQm{q2#DIMtXKZY4tr}*W-ba2^nyjHGlwt zzJ+1jT?w>;nSzlV#y)kV&KCLY@c`7DRr&>UgYwI_PGvR>4#R*Dtj3`c6UzbFH*5R9 z<{*~VM%%v|EOh8d3|8Dk?<`-biNKY+>`gW6{Pc1tV?0(zcO+2vCNewiM*3_1HT461 z=<}l;>x$Idcx1f(a2-_@65zkQEf9INBcTrWGRx5k&y+f`h%fnK-zWU5MSj^O5*e6r zme3-|5Z)I)=7&%4D5Y@jmWf*|Y**{?8M)F~&?F<+%#Q6#-|HOqten(#@U0}Db#_m5 z%6w)0_PC`AlcRr*mQW^Oh_bon3t%mQk3=Ri8R3djg z(e$daPZQdWzFXSe+Dh-ja?0Mhx@Q~#_bprYv!LI}28gWEA6 z(v#A=WZh?$(nse!^a|~YJPWH2^b$xW;sLjFw%6@&$1VnDO18`DVfQk%lblB&4UF&~ zXhC8@FJ_&KlXbpfbS%xFDX?W8bMK+4}!GMmoB^iZTo>&id0OeHfRIA)yfIFOWzP6Jw0y6qhviMjrbv!9|S z^EMC7WlxxMO=ZND!{PV0Ce==5ZdL_L$&+bUjvQ%jXalfjAvvUE&CnUz;aGN<{ZhDy zA0#M*(5GIVE#E^l8~P=}?(_ zRr&p%Ev?^+Z+9&KEUUj=)`h7!!S6SnpJ6po3Q={L)|jKAscf>d~i} zm(}Y(eMAGGV%@!*th_gNzPe&&=~ySzJymdc(O?i-frtAsXy6XGySGTYit>HaSlv%G$=XjC@N8;*jBkAOt160G_oT8O*_Z&Jl40!ICmL$X zuFx+*bH`W@5Qr=}GPiiwt3-3%6wsSGnoyHXs&{Z*_|-GkHf!uRIG-5^=(H|o5DlSo zIoCl8wKssg;2F|1*I&7*Wb(R2Q+BjZH8sB?N!g3HrG8`FwYsQ zq)FsS^|AcdYU!w~>02A0$`gkkT1gxL6ni>y4|8l2FRxh8Yv$=UrXZ{wgdkShPnRSH zQ8~jKG+oM#PCFW#*1Qu(rip0%TW&I_V1rKk|5zSfDBGi}bDO^Yav+f6z-r^0)`q~r QGr?cR7^Js*jav}oFK_w|p8x;= diff --git a/selfdrive/assets/navigation/direction_roundabout_right.png b/selfdrive/assets/navigation/direction_roundabout_right.png index 32a6b2504ba9d91a8acf885e358252574ad2f77f..9b8ea37ddfd432e6d2393563cb074d19746e48ff 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@tQr{DsX5(tvshEEV?R63^Z!qe;9yo*0F=3DpU*kv>3F^=1_$@0IS zc`5T{WanZotLCU$dIAg^c|e=NM{KsmT-OZ1*+HvF9w|TopyDF?%9Iki*cy1st`r+^ MMJ40Q+TOiWKck)|s{jB1 literal 1743 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 diff --git a/selfdrive/assets/navigation/direction_roundabout_sharp_right.png b/selfdrive/assets/navigation/direction_roundabout_sharp_right.png index d15cbee00232cc8d0dbc67b827638d223a1eb7d1..c7d4ba3175196d474411b8e3f078ad6957665e0e 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUOpyBkP13oOEnN=7gTtJimV7r$p8FWJ^Q=b_ZSuiK-}?SK2E zEsv*~CuemTF?x}+kp(<-T0JgX%SpWb4 literal 1855 zcmbW2YdjN*8^=d3aa@Mj>EgCDEaaAZnQYiGx7_-KkYS>7%YA2&irjW`ztnLwtiy^B zb6JEUS`B3jHP>wCplvR*x%{2~+y8m>|9+n5`M!8wJg=V5FWcSKQC9kxGynjQMLNMR z9(d@VK78n4R@}SNc%UeElo#T_bUK~IVy&&M@pwEwpTE7mEf5HV|7GWY*!_Rm-`~GP z@CF~;I~jk`)e|5g0|e<9TVHVY^g~C*rex$6RuUT8ItPZSw9ky0Io1kKF<^WS0FVeo z!tK10s#dBPB{haXSfw{mOgzyeU9gX<0t8<#0Y*REoWP1GhqKL)r!rsvt}bQ={I}XO zuUNca>L{AI5F>?kR#H`ve4t8Uf6WdrC#S5e zd`#Dnq7*^NmtA<whgn(v8c*}AZYe%o#j&@gDs`t>zX}fR2%Z1ZhkZ|63bDT&KYp$%nS!gc$3v}R z9xVf_2h#d^5_2_iV$uHkI&wuv9z^=xZ;!CSd^glkX3SjgP_Z_DtoCrWgdr4_X*i>P zlCb?PGc&G}0ExfC^fggY=*EC#x9jI5p+UvT{$j^qF<|gsIbT9;+eaK|?$V3_Y1>6_ zdA03?Op8O9F9L3s$b=`Rm>o*zk?>25%MLS_9yp%_6QxLue-Ej5IYEVZ=b&?)PY*ifvc8U5lv571kDP`5BZqz&+8VlxZdXp&PT*V@Palc1 zRC~nLr_fsbOrUbDgcEqn8$E~(QahYK&m!(cks;7(a;YKkN* z?y>gXm1VcDmKe5|fC@puvESw@|wgK+i--9sJdU3;5@USYKP$?GAC zJ@daBmP4uQoyW#1a*IdU?P{{gE)vpZZ2_myX8sYP82(|R z>=T@aHS!%`gfR0C-~T-|)RcU?cX7yy()^?hmHT%AC;R8}Af;eqM!33^-`>?D!A8Tv z=?o!rBuKg3teGqS@|7*$)-VTEae}vXm*n&!2USL7CH*2aHG#izS^fx((!H5KM3f3h z$v&^NWZD9EK*IN=4>b)_(%dH4Y}9Xdh1cdFY~3+3_z=WrBcFI2*n$wau!rl3 ziZ$C{EF>N{4D?>^(;N$#n>d3gW^l6NQF0Gx{B!aiXW{-})xy>dw&!oIy+>hFguB)0 znOkQpqrk)2xtLXS8aAc~t(GiE)5>}vB7_fTTzq`P1X-#?y?4X|}mDape0fY&ze zpo6oszJ7Z-&}hcZAyRuuU47qJLY;1~PuXT<)-k6JcrJEL)B-UIGY*8`!WAdAD)1_i zafrtGphBpEpT_7wQX!P=vTuKtMwL;?ycA#UX$S%haZdX5^T(yduSzH;w2q@~&*1A~ zqko>67R+v2YHD=~(j)|LIQ=8;(uGTJx9BweL|iw;8jK6lI0`e;+luZS^n(A8wWNvY@Ab diff --git a/selfdrive/assets/navigation/direction_roundabout_slight_left.png b/selfdrive/assets/navigation/direction_roundabout_slight_left.png index da1b1127051a19deaaf9ca1fd7faad1170bfe96b..d8b806137c574b1225f2964a9cd7d5038df8b51f 100644 GIT binary patch literal 129 zcmWN?K@!3s3;@78uiyg~NFdn$1`-fvR60_7@b&hxdzR1a~k~b(U04+&gHs& z^TOk0=3dyGPK_>lwgiDT$iyNEGXo|OL|cOav0V Ln*CR_ix$)$-8Lt5 literal 1702 zcmb7_dpHw{8^Xch_ z(2~?iZn=yQxmA*w%q2#1Fq0kUJkRg>{eI`a^LgI){XXCKpYMO~%XA{*LEl2Z1pojb zyOUVwZRP#dK-ukCknHo@wuCs@yWqBUWMpJ!W=1F!uB@!At*x!EuZzXvjlYrnht2=z z>%T-QmCk7@EN+LvBAtn6018Sf8Zd;RvBe3(nX~?37igC=*qoAzn);TP1EUku^CD%& z>K6b&zRM14<3hc;aC1JQ;*bhmemR-pu7XqlvIX``)cXcu4dC>2$0xiSn4SH61pby> zW5m%1GW7miJG3P_w_-2(&1MBy8W>PNyeO*koXMidp(jqBYd*{C*B$~?VX>>JsFbmo zDhqS@-s2G4nfmdnu(8vtq;aG(t6gy{F^ape_Y0-W;Z@6-8QGq0(=!SL^KZ ze9*5=IkHB~VszC2V3NQ%vb) zPVhnP>*hJw$=NJ|rY8*DJGCEz?rmi=^C%BlX&Mw55soN0l(>(R*^IE$UPB#xs~H+0 z!4^*M#9we=IQHF`9B(feWRktNnq1h9uxJ-6^x#yD(-6+g*x@G9XZkz;Qad+WEv}XE z>#5TUFL&b{Oqgj-1MRiBiHO|@=Apcx2of83`4@x*YTHmAPH!7OgveIa&&h~;vn_(# z=XuK>YL|59Hj^)Eyzb*>rSupifSxo=2dOo;4WafN&uTi=v4GHG;yQgCd8%Ia#U;gH znc_v2eCDPtW3PfqOA_KhV6g&yaYi1&g#V;}9%WE~2;B+V5i&eFR@b;d&tF-sw0Psz z)GbD_J375FHDdpotF4WI(o1*SriSa=qp)=!NTv1NWW&vHf#0xkbZ0fUk|4Sv^NKCJ zus40EKIChe*|U&Gm&VMcCyI3S*HbiT%|YyAg~&`=>`}qBgIVyi!$tQ6se(@lS|rC| zF=W};F-GvyIsdT)O_J~AB-Y+Ref9hI#xi}FPpn70khTUwh_JQf+PGX9J=di+g&lD& z0M{xO>AqzbZ&Gq0?#oh?{?VMITgcz2*5*JB)+&7C!eh8$#{kPxG0gO%WlribMMGB| z4@}tQE5qRr&IATJdI%SjAy0{#dC2%$Zkpob&Y$TSm8n(FLvxhhXBfkv;H4;AeALNa zS*e^TpTmz#ET7B?S{~}+eM}~)1k77Yh$FIPG0H%eVNbFIlKDtqj+HdcCt>@uF$a`8 zu>($(CRfp;OJ3v|1{#`D+~!UjeC?UuI*?RCH0?ODXWBgVZ9_?P+-T8xn4sL3j9MDSuIg8qj`zjqr?nas}hikVS`Nd_&8YIx**{=Redd%5lLQ9xJ=^1~F>yKS>E=^k8F!aH$Jme1- zW04Ou(2g=;wP^(>#66))o-O4^Uo-DQThg=U`5)i+X8cen^acjAuNptu0Qi$Xan!uh z!JQ#}x@G_f*Qmheuy2pxE6LL2dMzcIkMXARZ3@XWJ#g`p`v&_`2u6PprZIdp<=J8R zx{6iEclWdp>AWE1sx8N>28!RsBd3>Dt2e z1c&1x$#i(+`7u}f^(b|q!&y;~>A0)?6TzKokFJS$1@{EC4=1Gd2QluK4UgDK>Mg@M zXx6iLYINXtwbU(O@XydCw=UYuGh*<=Q&xzYI9s2Eee2Wd6HO+4JydpEVfOvahN<~l zY^OBojoL6gVgJ;GlH!(oxsk5N2$_u7qw(&yTeO~?uEQ)Iw zZWk2pQA@0OI@bb9kDCx7sW-;7m%iUUaVx+E%4~83L5AN2sm4SYIWwGJL1cY1c??*e zH6-{~poxSpL^f zTvC6UaY`~5s@}35l`Z*W7X~Ju9hBf*@q1)U1cU`hIH41d9FSPnyupe#q5uUb&Uz;{ LsL{TnakHp?xZ)QACYhqPCP!qE$svX=1OTG)yhEB!ehoP;2QT=+siG zI#q^RVvX9@LD0~|n#aT=LXG#~o%h~(pXPV|_x{hh=iKu@_tQ;vayC}ywlYis1u)L%X*oq@uiiCx(#QT-`m(`2V~tS&0Dx$; zwZ)AG#hWEFd8IlM=DDbISuWQaRCvd=#R>w%`coCn{rA^e2d)m;&-YzNtK5k+B`S#g zm%bY23BcN~43NI7;FW9(5|l4q3g$fa+)@eEd7(7w6WVMM{=JF7X_9#GrM$B=*6dKT zc_+-I)m@VX^_}V|Ft-wivE*3Bg6K0aw&F?5vbaN97+C&f-D-ilQPbkdUesg9 z6!S0dm+Fn~{W?hou74GU&4-&I$D3&2dj$IVo#_vo;8xWP(ddoHAKz({L>)K+71d=hK9Oo$0~|4PLTR5;{{g7ongyhVQnj z$i8Uq6Fm^sdxL0}F%UEUN+?Fgeev2$spCw+F`rbM61xP-x50bRa`(LQutium-H48e zG-K@@)zXZUmQ<$R+i{T~;Mj~Mc$*V`xPCVB&w`ITu2Id(OWzxneAcYpt3q+#bb4%T zv!(-m!!Vgg1!9K%RR&G}OnV=IEUqH6!emy32Fnj-pUcACkQ*Aqv6a}6u2T1YKYhwL z9-e*9{>z#iAAbQCml0DYA1S*i*OQ%Zb}RGjdQ&=WhpPN(R9Rg7NEEwL{Os&w`D76- z*CITpi|3r00||s@2&FT*jbRmfJ$G%>EFL;2X4!PGJr{U=xkUfu3FeI0&XjTtCOVZy z=ma^d`V}*&tT6S;-sDQ5o5}_d&FZ+c1w7m`wwroCA3j-U$Oj=RPp(4La-S9>uSJvZ z%trs5emqcuEeLg88Gd~K;b74brFUb7$YkOdmN|y!Np8N)Ih6b@RLxV7Y%1~hmIn>f z%z2@%n)HMpjLr2B42L*1J*|Q4lwA%bTA#x3x+)a$zOj%%a~d%<-YQI(s?eI0d;8@N z!?KAk_=7N$z-UyP{bsz&J9Gl)5LzxLUFypn${rU0$1WiQ!rP)IOsqCNkAKj7xTP!o z2lgCe^5+ikqy8&VyWc#(VGTIHg;d2f z>V^qu!;mu)-N||hrJ<;BvXQJd!}}6>IXnUqVnpSdN;cYyfup1%8TUr+Eu^;1GyKB? z5WmT7Mkr#zR#!jxK|Y2|M<_uzg`-}4u3N3~6M%p2M^5CSL0t_ieJ$kXz?VgMiN2Lu z4{PmzD2B``dITkj+@kT|_+=m*M|Y<)2(S$SIIb5_@9$wJTq5kLlGNuC`^L9$Jhjbb zg%ci_t>4uRDJT({E3bb$hdCw70F{_XHU{C<@0zEGQN5y;cb=8rN*oKuxSk_vNK~Kq z<~Ro>{<)^+*`Fe>@KGw8KAO@=&jm%5TPTFK2s?PacH=#2Gaq?-yQ}!X>4Mt_yS^+k z`IB|ah(Uh@(rP;aX~T_juXV)Tu7;|BHN~!bxz&QyyYXGL6%F}e%)L&(Ny5#!?3;)- zxESXzA0`u>D=Zq|UYrSoE1l*sSoUrxf%M z>DV!#sO->&u{EZ%x&ZFjeyGZ1oMR@T>jP+em+KN=;9bb=BXUnp=it7!f9T^M1`$kt zF4hN$vf5|n@^-mj64_R2f&RF%*rscYCxU%|EOk}WJr*;=HiiE>ag&!Hgu4m86W5U_ zt-P87Y@Hge>>CvA+9A!48zLh+%(R|Mb4X=J{8q-s`(+I$vo(rQ{I7|HA%wzhbNxY6 z+BiMutLK_Sv_jEOeCT=B-tEvrDR*zbBSFNaUB+QZuI`0z;$Zh>lr)XwuQ)uWD)!d* zNcipMrIgH10ico=^HnD4%=L}mB?5h%mY=lt_}FxMbNL06tpIxF!ZVCCdr~QF)u3Vj zV=oeIcx5;~D@I1D9hizGS=U)8PAf^iBGbp!Yu?4*(fUW!IghbR`E2v?Dr3E^ zXIgWA%W-g)7c9Mz7&U6^cJH&%6p1Ms3Wc&w$UcDGARwxskjO#I$vD;+7_lHL=kH~` N0kw?wV*!41%OBisCtCmj literal 1736 zcmbW2X;9OL7RCRmvY40YGKO#lha=^3D0;@q&98=)W&~+Tf>h^CEwH2N9D8fIudfo53;j`eSo; z+6|^~&Zz{O^9Jc5e`z-9w}@P$_KVz(F7dTj@Z5tx8pS~mh?;*CNwj#!v6rp7uJJ6G zQy>Nco6mGLkp~A$hSWBG$r6FFT%>^-akX8`=@#cL=z5M3%ltTThJ4>8J4A~ytqk7$IF(OQi z`Y>$fGBksm88&N)L+(1$MCMdd7y$W|SL{`IujH54=_Qr&vV)AO>@$Rlau;?-teafE z!`#jYhEjA*DfJxduF@l@=Lr3^@P!=x2y4m^+V_G&tPg895p2&>62hZBrigtfM@jPQ;W$B%||8u;iUWsZBF~zaG z9W@dRi>Ot%{b!vgUelE3-|+k9c2R~#VjHOue<|HFA*}xvR&CJETXVc?-vRvj` zHW>C>q9-`cf0gF8;(X5$h)ew%dW&nnw|3w(N8tbzWS(i0VE06g1h5idfY?*iW)Pa= zhjT-bBYTa$^oRyu0G2CD&_vo}a05&UVtmAIkB{nw=Tig4p+6nhdd7l$h1^(>)O7+E zT6$s|_B~1{lO05y6<#%H>CbsTxA$3%rHNPmodkoD%$RclThndTe$F_!v;&(ZXko^O zw|ane-X5Nw=umgyMVR~8^c+fphG26^vmr(xaZ}KDa{y3y>Ke4Vz68RG{F$xmHJIu= zIA>F#fFoZQ@Q8~ydVhsf?-{iXC}1ocDa_+ejnP~%Y2#fyGXAA{3dA_E3v6B-)&#G5 z+;AE(`XIK<)A8wxq{C2CmOY(Q?bxlwcdB>O)~~khPfMjIWED4-3A_ZR#ZEe9hL}_%|d+!q6E3<|%j(e@Gzb~k$ z7s#x#r2@O zR_jyIhwoZ=g|YOHnzG*4J;a2T)igV8nW=(*z%kFc=W9deXHx1R8S$Z>#>4irCm4Vi zQhkgPARJQB1~agQKhdphTDYGceY)*QE9R1e!qwP!)UG$b1E`mL^$#_DRn=)8 zPW&u3TWReI8R*4{*5|jW0%vx=@ape43yS|qS~T+rUt(XHg3i^2uN-NcUUw9(oxQpB zwMnCct>Ic19AXwYp6vZ7s=G4)H)V|uq*(4hHz{!Gou(j)2*xKfq!Y1GJxKTD zq8#6Sx?6YE*%=8H6Lbv>cPlANTWoZMRR#2fQYh<2Xp8fNxEP{5$2b3@8U84YMO&0c ehvnaT^ZvjcA(u{%y1l;p9{?9efNtYng1f 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 Zo~{oAPRia=wfrNXs-&q{q3|yFKLGp@*MI;3 diff --git a/selfdrive/assets/navigation/direction_turn_right.png b/selfdrive/assets/navigation/direction_turn_right.png index 0fbaa3f253edb8511e0125a5b4bcf8c6e7b4177d..4ee7ebc3b4fd2b7085bc4689acc598104d634621 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUL|mT%I{0=vSDN=7gTtJimV7rkd6FWJ^Q=b_ZSuiJy?_P>4d zR>o7!lZv{G7`@8b5PUe$X`@Rr0LZ&ZHP`Glr4(StnmO(qqqVF7az+T20z1cn8Oc&a MFJ}LeG&UOX1IK75y#N3J literal 1371 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvG6Q@n>Sy)c=7)I`%j-f z{rK_Y7a078fj_9=FInLK|Nm28tKA1$rc@H-7tFxUEg-69V(sJ|6qTG)T;1F~Y4)Nu zn|2>QefiFVr!U`r{AQ}bX~Mw3Qt#>F7*a9k?Oe{JM+Q8ujVdCiZf%&hw)1`ciJ32X z8dZvNEKh8*u44E2dT`=C-I;uBO$r<^^k;v{?nj%>?pXZ7zbttF&shb>FV61$b-K%X z!oE5GI0JvTSWl^&^WN!7>KR#YeLF41Rdb5jFSl>0(FmL^SZ-l^!-g|(zToqRZz4Oi zmfiWdWAl2=!0dMwb7o#?UEzF6cB1xtUe$}w9!5HQY^kiM$kYnFZSncTB2|~Ui_RQg zylAb;Ih#Fs+?~e!*K544R8}x^23|LRx8p*}0)bGy9n8*~+ov9x-agxIsnRZ+J6|+K zk6M>!SVY-9J$QVAwS`FT{Gh@fEq-RZrH{md<>YEhH3GfgeGq7{Tbd+(exvxCV6Af> zpCsN`9x}!F9YgNLA5+*Xbt)sC2WusrJe;ql_)5(4x8v(zxg!_rD$Kb%m;5c*8c@{t z@W$%|MU^g_8ne?soIh0VIDGm#=kC7-Q6;mdq*vTNaI<9JJ-Kt+7q#q&T>Ya>lVAPv z)qR(jKTbSx_~xlammYq+QzxYTW%Wa4{^dJs%}U$% z01adE|eB#b_zFE!>k9>dacty`Qqc>D~5!a(w_CVk7YnxA{s@ATyyp$C5^WMU%6R)Ih zpXa}+#Dqm+iFb?Grnw!K@9%vozq~s$w&lgQwq*icceDbd@{YXl^S!@I{Ag<8PJuPo z)D!Iu-`Y&JI=jZ-Vr`jWi|oC1O>qi)US4rxOFh5$^$D@i8eJoc)bF}i-!Cp;4o}|9 zcWI}{-$PDfYv+2hy`9)um#AoOms~xotc+X2{vZ3b%QLgIK5l#z^t)C;K1Jf;0=pN! zCDA2o`VOuNX0PN9F~8hww@|WFEc8=aN1koY1^-ti*j_CE?~Ys# z4crj_`{f6P8*?jO-#RpZ@nJ>#nBG5MFCKjOa<%g$#?=##pJX#gw>;C);Cw?a-B+j} z);dK0!R)VAt(2O@`$GxN9_G$b)c6VXjeCKa} z-+69`JJIULCOhqTMf|ktYcp#zgauEguCr27F>&$8=$LRpKyjg>laq5xOB)9#ub_|= zVTm7xPH`p%2l!16DLPsFH1zgH)B8t4aYAO#qvJ1UGi(=Ff5*_0@lI<0V|n>4H_S?9 zl7E{gl)tjy5c6c?D+!*q$Lf!!D#FVTj)@<+XC~|a;`*a`2Uu`1c)I$ztaD0e0ss*f ByXgP` diff --git a/selfdrive/assets/navigation/direction_turn_right_inactive.png b/selfdrive/assets/navigation/direction_turn_right_inactive.png index 7d327766af13be26a23159e354cce5df6453f920..bc0a98dccf08b3d95df3f1ee0b4815e537cd3a5d 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@u5Pr(H&1PB>=8$LmpQRx`$!qe;9yo=v5=1cXpPC2x`k9B)A+5h)X z-phPyIl1TywmGVjo-Br=l~8~nhGy7#WGWPMFfFB8qXRk4bFOE$}XAg M^CsiVimZ#<54QLw*Z=?k literal 7239 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 L1dnf(=&Tn%trRA^ literal 1546 zcmbW1Yd8}M7{`}eBf?m@h8<2q%COusnOkn*n6QzJ%6-gzv80p9wGtss(S+7^D2z&_ zwv)s$h11_5OH{Tcy@M{&*!hKtOx`Gp-}k!`}g(r^^L#u`@gmMpS8Wc z{l+m)M|49A@8jtUklLl7qVuB>%;C5%3KNMZWM95kd8eVJ<9QEz{KJAfxl^<%Y31q& z_e-r>yvYo^a9Gw}9aJGVYW;OPYZTTiHluSg?_9t2g~l?_C@W!OwN5|p_CLJjWoPWa zuo(OFeau#X=TnSx4&~^{n~_A0MKV@JN>Be=UmfpdVP8>n^5LKf3posP4({*2yWr>^ z`V0IN^hfH)Y-zPa3aI5ZJrj2R=XX2%D)@G#@gcCfHrP(ira-Z2Mp&k&7 zv4`q57KB%nRP8biwuawq#b)j!rnr)9W}zzrP6^eMCdw^?JFcQNECz;*O(*v2C1lDz zVsd6(kcQI*4v^7An#@nj%@o5RTeu7McQrZR*^nS}gK=*M$fqc?Wp@AFI4>kJYEl7| zwu{Tl@XC}5OT(GfHZnO-2tVXx2Ea?>p&^C=0-w#FZ=I|NG@sX-cm9QBc6-U-P^rB} zz5Fq!T6(~eK59*DptPPvH>pmPoM`3Cpb6J4A&|$PQ>nIPG`!vcXe8oe+gLUAQK4uU zK%WVSqSbKSe6Ztc@saH?3zTbKaG-c>K5~s`9cjmP^RDIFYCAJP$~W*%W;_~W034BZ z%mMqGsv?!&F#w+TaB`^aP&;a0W;p;(V1_tI2+8N$$r~9_#Dj~{xdicJ$*^bXi^`l@ zhrMeOjuBnOi|@L(6x-$`gD-5f09&ra3~`@~n|fRoKV|K2FJVM8w+nM-jCohw7b2jc z-4QxF)7K`+i!YZh=MxpxACL<1;X&o+{3Otlb_;mb6b4AiBzlu0t}((MQoTj(c9ccT zZJ$1_jdCT)y2GiNrv>-y8CO8`CbDN?W4kv@=am`XYW z1X2~7PNT`kCQt9FF<)tV5j5J4=s))=D<03lT^{L?)&?J6&}nv|fM%u?%c)q$S6%VR z+3eE%TnuS4oR7S{)drl;b@t%3f6oCXSaq+Z5ZooydS$&BAVr+Ad}VMz-^ABE^7SX3 zvnSFJJU-o{TpmHoTOmHn7YgcApBacDvt_l_+k$5U?4s~{W?Z>?mTWe5ehnmSfJU6A9O6DZxVfJv2_R+hF@NRlz^ zqvzRIl7Ys}WFMJ3xtJeb_dW!sqOi*J*5$Ml2U!=boJxvKs}7aw;RL9h?rkw_YW!42OM>kw~?i1Mw(z2PQTrAH*nSmf;*qKEFr5iMH1OXUBy zJ%`cebk7gKI90!`pktgy!SGbt8*^T#G*URrMdZ7zGiaL|CYnN|GUCMFzJ?d>Fr)w4 UAJ#<^{aXOQ72)YfbqLM=0~U1z7ytkO diff --git a/selfdrive/assets/navigation/direction_turn_sharp_right.png b/selfdrive/assets/navigation/direction_turn_sharp_right.png index a7e3c4cee56a7de3cce450a83f07fcb55202ba71..f338742e5883c917dac520f01f1ff2f7dc50968a 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUOp7hp*@!0rk&DjC5XtX|*cUG$!PykuMJoQG2PzHSem+yC|{ zTOCg|PpaxNVswHq)$ literal 1551 zcmb7EeK^wz93Od^X=80txm3S0QVx~$(7WH{ZOdDmDT|#$Td};HNp5Zyn{vBRxk%bv zXob$pp-^U(RLon#B=bnxIl~&7xZOR^Jx}-7J@A2X%|UkK{<4R+ZMh_+)tpUa0txn;o-}cX+Qzp(vWt$)}GU%UIuWv<>Jf zx?e_bXfWub^9K`TbN3$j=lV*Nj@Tz?A^wPwR zuvKIPKa(=aILTn=9{+_BR>%u+S590Uv-Kx4xTW$ar+R#I6;rk$1Q7*0x(JE_28J)| z$Ind9Hv7$piJ;j@0xHU3u+PFPfFZXN#y;>gV)xa4WQWaPER{835GED!ae?C>A|1fC zsv?BdqDbU776q3&AaCs^nhV6u&hKkM z5AB_tdww+KR^m1XWF#ZWC(5GZV4*V$5Z$v^dq-|&$o4E%IU}zO_C0D>20QVrD?CGy zSNgpvqS!@N}5 z#%Ek-aQ3v^`F`wu?L#T`@HFVsU9!4H*=7Ifl;${)Stq6~&t_Ywr?r|R_Qc4?1=~2@ zM`m#^Td36(nuQnXv8&23Xrg2-x%a%)PJ-AI*V_uTzqB*=E?6}Q79-DI$kp$9a(f=2 zvQK~A5EqUS!ssQ4Q@Ym=YyEV$aT06+s5kT9+!yS^c5_l)XKiqufq*Z=FHiUkyr~3y zQoFeuN3f+vR%k!c?WEXk>^xsDSD}0>PV>itFS63oa}J89EE&ANm*z55n1G zL*r4-sZQ7;PO9_S6+r>JSgp`TuEnD~VLZ}}x7v`F*@ad1bBs#2daZ6|(wd=xD7lR; zE_EVz_3MZ#h!b`KpriVryL5guefU_dO5jq{de}gFO=ZKHEbH$6r<9sEzpFE5P-bAB zFvdnYq<_SudF)(O3iO6OF@IB+)&maL>t3C4J5!HWGCSvHdMZOwrRc(D9%DCRNZE*^E=0O!S98_ka)FR--p$H*(I&$YN^{laaWA^&#_v<-EeHR2-Khu)uSPdrqs z%R4_kOFgzOFZ@OC|B9V|JNmA*_TRq#A|ZB;P0O1L2X}oatX{Wli_nLn->#`#KM(3} ze;)s$;%&vVZ>m;t+ID&Rfv+pxd;MEuvio=VrUVZT~-`@|9kHuauHf8C3urD;jug$lQ7WE{R8R_ENsB1nK6-!NoyQusn?w$u(EV}!t(|sFPUhBzqmM7|6koqS zQY}vI^yd6|dEf8Y=$ake`Q&BU`Ow73m;*7N3U;qvE;R9TQrm17n`2@BEFI?EdH7Dm z<$IC#gPE^FM1=l3cF4|@*`)Krw&P8?sBwtB+O0QoXB>r?sv7?Yeez$Fxaw_pMA6H= z|1#WlLN=VOmY6u@-rnHVY)70GzWgz1nW>V}A-&`ScVXKhh14R>Bse^mVGt-SN)PFm6r%Y&UJxM@1)+;&yW56O@W<(c9|iO;O!eu1cJ!0T-+CuOctIiHwC5+gYjanl%dH3>VEM%0>FVdQ I&MBb@06s*zH~;_u diff --git a/selfdrive/assets/navigation/direction_turn_slight_left_inactive.png b/selfdrive/assets/navigation/direction_turn_slight_left_inactive.png index 37f1f83627230de134165009353608a9605ac4e8..7c970ebb81faf98bb556cd430ba1266c7abaa5eb 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUOpw~!^>u&@X-DjC5XtX|*cUHqPXykuMJoQG2PzHW~?xBu-! zTOLm}PtNKxV)Ptp>~(C_2_mHDQH-u)D$#iK9)$YF)`g35ehz MlbHR>(uEkr57*cyqyPW_ literal 3696 zcmc&%i8qvg7k|b!wwO>V%E*K)QMSoCmWiNFg`xG|-07o!J z238>L`h9b9fY0al6%>#_Zde)W0TmxamjHlI24kR$i*#8oaK7tocdT>!^4^3-X09qf zmmq?_L|^C3x}~L1i5^H`SYLN)FQDg?mqLZscE02KIjdf{^ii4&Q;Mx-rzl&Iaq!^<7UfCKTJzTouXbP&EiXKtw1?m4(U4T^V8!eYQ ztH3RONxZ#EuBR~TQt!siy3JMQD)UeZQ*-%Dn&Biq;s#Rmn{VD$QrlM_JRUEE#`;N0 z_+f2GZ1AG~MWe=XEqogWjpc1fXkh;o{!z%Y?EP_{gjTm=H#Q$tO_;3+0&sA3ZT83G zS8zrH<|u0m`Aq}Hj;D1j=Db?1T+gshP;B1$>4t=@w<19SXLN2|&2AG*<0bdb!Tm*)`TpxlHLO-GSymG13(b1`FX&2LH?*cNs zK69O)Pq?E_u4q`UU;CCBz>*g)JI?Z4I^{5oG0C5vGbC1>b8Kv1h~0Bv3NPHtK^vYJ`BJ4Ds{WBG{i!6Y532t1Z4#P;3=`aZ`^xue+t=X8 z>t9|IWpncU2}+(oE`q6(G8-9Nt|ibi-8s89Gc+_b58Rm{aux%+RJ;@gKhd=2KPUd! z2g40RFgGFZHhx)UAS#D~lCSXm7I9IK-q1XBBM>(G>@{|9hE^mvR*hqB=Dvv9REPht z_of6prvQP78k%v(3athN1YA`QTl^Ti>Ax<6;R7u;@=sHxD>_1c*@?IB*{d`weV+p` zgFk&T5S=Rl>Xia$?9D8%&+MjOY&SG6qsX)`r+_HRp_17kkNw}}9B*xXLqUg!?e%{2 zaK#fZZ?}*0^45@o&4;9txA#OuM1myNDxe&`sHSsFKjDhJbVnyAMxqL@h8vk9(S%wN zK2_Gf_iJb4uw_Y~@tdbhy@lpnGZ#W1h6*0{$ySEr1UEJN6wL<84&$tyT-=G7)CF(`gH>td*LU?^Jub)GsuH`u$k3S z?iqgGfy;L97@EIvDj_es*5be1KFZkG129t{jOuvqyWQo2Cp8G#wg# zasO6xEFUAdjge%`Bh(Aj@_CL?w5owbJ?d-2^@F&q^)#cY$<`hga*f==ZP*urR8e!zhNkO_@a3_y z84+sQZV=8A17h0A3jh6Ehv)!{YJM&rl(ZyML*KndYo5Ppsr>>X04M~*JIWoKLS58i zxW8#sQ6(v-)5-}c$W)#8HP6J*{R^5D^oyptx}T>mWR~-*ilDAaF=ZTFoSn58`y3g) zy}dE?$(4v1B<1O9S-1RE2NxHYH}|Ep35*h!e`I5bCmX)3icu&OTe9*&lwZA8$(Pl` zGw&FeLGK~CwDMBJ421VqXoC8K&>z#Sf+3<}su9@^X4x;(I%gmJAi|XP?IANV*b#@} z3q3WN3p+)x*E7~4f31V12fYqLJ}!$IP$nWV8HNE2Mz^!AcJn-#68T_C5XQHx@oC54 z7X%0@H_nyZY%nJOaiu5;#Oq146QB%|7&K;{NVt0+`!hQfL4ESm5l{$j0ZkwDgV-22I{oBDmN| zTGX?Pw-ci;)Y=&nl}^Vr;VY#yPyv!m=CepJ&cE(_pP8XvwYRs-gt}IY+Yax_Br!Lm zOV{Z2jL`2u^&^_oj#m83yWsnb60&}#`$uPcQxNH zo}bqzJeuCsQ>)jSZ;4}NhVRBO|ETqIE^vm}j0fC^${~#!4S}b?$&tN$$#Lt~SmAGF zkd(mlk7eEpkm#RJ!?ZI<*%uGW|Yu{iP;8`-M^Uc%FK)h?S38Xo? z1g2YLcvkyqanFBdq29WE8>aJ=X&_v_VAEOk>ZU`^V`06U<>n&l1me82y2lNNJMwVJ zWP0A`bSv$J0ddJdwTo>fF7>vywv>(RnB2u{MST7!UV+VfSfN*b`jO*-$`Gzj3C1(X zMWdkvO26+lwI7{gvA=lCon{?at)hDz!54>5BbtRwDt~y;VMpU%re~ zi84_uG{ZFwWvTqG^1VFoH%=S9>+jUxVeB;GE}QMuKn>KeLF0^V&kpC-)j1cp3;8EM zvr;oh%lt!&vr3({yPfNn*AJenz6$-+pQ8m=PmaznRdfC(AsTvgi?oPf2Hq+N-YFaY zCdM~e${mAR$)c$dk;Wq-Hr+2A4RCfG1sV}6c3BG109@~Gja+lXNC6J_ZNH~Rb7>}& zz83issiH!7!Ce(7!-LX>6k7{*Y==*srQQ+d4ghuI>>SMkY0^!~7)B&!Uh_YN=nphlT_4OXK zA*SfjGgjDmzd&S+Lk`i}c!xc|Ei5XT(i@Uj$Br_^`G=ggvq*1PR{ls32l{PNEMLg= zP=c#0%2i^9%bEm8IP5(m2Vo~m8s3c!ZV>-E9Q6?W;R9RtzK0Z-e1!j3RX2r-!9#q*CNa1=EMJI4WMK;l7Gk^gljoQ|smp{8W zls-%X$j901mJ8`kVX0%$oIv$yg1nza!Li)@d=0OtE(HT@*l=k6R30mQs+hlMRn)zcC>7EdIMklAqjx#wc@X( zEYCJE$+=nU4w==7_E1vdVm3$ToKM86QlV>qne4^if&0AZ=s>@ys`_D_{|%eRWQVIt z6qN+dM;#1(*!Y`ZS{TMAOd3jNJ4@KmjJ=dP+t%>@Briv)x3{-W%`l}e9Jp(-$+|SY z1(7jkL_>_=y389Vz(TG1)iLp>jxrAI4_Kk)sa}CWj>SJ)EdV{`#qx*q(@z91EewL+ z(CDn=CkWhg%L4+V>biEwLHDYEusxjtvW_@G=HM}VPz!REM`L=w`*9wUvVc$Eayux^ zXZDqA;1jI@i3j4P?sf!IM}zm$k34ry9p9tRu z;Uu0eq!>a$7z;37Mhw7$dXs9|SHubj+HY{)nbZPR-3@6A{UG3(%WQ2cXGj3KN@8eZ z5#R*YMC08f_)&n-=uu`=7-%b!ueFK+g#(E1oST~JpsoK9MnmQPNxmfSt>y7XfrjDe zqBg0~pJ8X6Wy}VPFd3q??w(ko1hDHbd|OyZQXSivC0i>!hjXyrw0Hj5k+o5vD0Jz*FUl9SMbmKx% z6L7yVX>9Xx2KSo^E9p)vaK9N?R&=z30Mv4!|GWBh?4!^)iTJMFMgY7pXmf)KJ-7IO E0lb8=pa1{> diff --git a/selfdrive/assets/navigation/direction_turn_slight_right.png b/selfdrive/assets/navigation/direction_turn_slight_right.png index 3e21cae11ed5209a7f63f7352d7ca2c4ef5b0b14..755935b84c512c69b72569720de3be7f485cbfb4 100644 GIT binary patch literal 129 zcmWN{OA^8$3;@tQr{Dq>FnpTc20{>KR63@1;py$_ZQjWr8S}0CaqO}g^BBkN*<`uw z-?Gqr8QGc4>C_xmOV9TZH;(9P^?*JRda#XZ0*-|%qt7|Cx*`Tfu)rk;AhMaliuvlz N*{EdvSUd1Z{Q=xSC)@x4 literal 1347 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 diff --git a/selfdrive/assets/navigation/direction_turn_straight.png b/selfdrive/assets/navigation/direction_turn_straight.png index a01045ae6a2b586dfc374fb7e34e4adbf78a1180..ce2c42c43de9c52dbe143d6838fc738e017e245e 100644 GIT binary patch literal 129 zcmWN{I}*Ym5CG7gQ*Z&pF32ypSr!mxR5F4oJiV!2^Um^_eY|B`>zKPzkG^itI=9RA zO&gDwnnz)Eq8L5P*+^Tr0|c&MdXcI}p0e;B3q_ MkeK~fB!V;I52R!!;Q#;t literal 1063 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3PvVgr0aT!HlU>(`$@fBydc`>$WW z{`m3Z=g*(Ne*O9l1AkD#U!uYP|NotiUB3skK&~XnFPMRiTR=+9$lA#}I667Iq`IkR z^6X`6Htsok`tt3kuYYqzLC#x{pH2;BU>XL&_Txqf=~ zdqcVH)8gOXE4Il}3s-*cDQEm^zH?vg`$)Nq_V=1jT&tepo};?q!@OmC`86v)&s)BB zdl8=|+dbi}#v1RMPON)v8!UbIZY|%$sow)~*Q~O1S#~ej)ck7Eo(CTbBO(?4%f!9; zal`tBrOUoO?%B_@r#Yv*vyB%1T){VS~I3&J9oKW$hadM~)R;kuS`^*`w+QK~w= zIS00$x;D>_C3+R3{)Y~?gHvm;Ti+P*pS?43FC(X(zOWKI2b=}249>Ik3_OTrAjg{58AP70WH zgCT3`luJjR?bqLSpY!yd?5S6PoS4-r)78wbM=f>COe$UzI%|sBw#m0YZj1!-R)G&n@|RrRHVM{Jmi-wSCQ|iwFIkx^st37A*nM9sFaS!-6;OQ^D1 zuCIPtGht=e$|-6`gKi%eYR-6V7reIU`}UB`R`yBF8n5k6=LOZ7-g3Ra^oD{*>RtKd z{_16QADFkBo`2@%cy8Ij{9hlwtTf#&zhcb?hsz2Ncb)&d?bgNj!cp-L8=pDmz5X}j zrGQP$`|^pRav8!;HofEDEqZ0YPZS&D5rqs17T)Fsi2^1(O>K@B3^=6N91qh<&^MlQ z(o;SY|K)df%KT%_4LSAp%L02=wRJ^@Z9klU+Npi_{F)VagnK(6;9rBh_jJ$Mde?Wn Q0cJ=BPgg&ebxsLQ01@OIpa1{> diff --git a/selfdrive/assets/navigation/direction_turn_straight_inactive.png b/selfdrive/assets/navigation/direction_turn_straight_inactive.png index 4c567966ee8238f2a65cba3a12994e78aeec9b6f..d864e58bd97acfc72a4f8744a4db5c1b2fee341b 100644 GIT binary patch literal 129 zcmWN^%MrpL5CG6SRnUL|7RW-nS$@KdN=8TyRxhVF@6O-R`iIv!&v6LxZ1eFdV_ViM zY_Y$^ICB)YV!b6kDuLY(h)DwlDp{K73i?&!2|X=QD+wu4KtvgX3mJT}DgYYoc3%<^ L8||05H5S+((qAWx literal 5452 zcmbtXXH-+cwhl##(mNO+K&a9M1pxt*NCHTgF1-i=1p?Bf1VxHS3B3sjk=}c+0@Azm zB2Aj13W&gqcfEJlz3<(B@0>Mr_W9=9-|X3QW}TTZEe#dQn`}1$001Qd4%NXwd;b

52)T=qZliR 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 diff --git a/selfdrive/assets/navigation/direction_turn_uturn.png b/selfdrive/assets/navigation/direction_turn_uturn.png index 0bd1b91777ed8d3c487e188c945383482b219c43..ff9d97893fb766220f44cfd48c7bb1501b47a85e 100644 GIT binary patch literal 129 zcmWN?NfN>!5CFhCuiyg)W*{v2&9ID6rIHfN!PjeF`l|2Q$4j=g&Uq+x@9Xy9x&3dS zwB_+s^Q>82AVx28Hp1N8es2Px>@rEjv;7SB$P7bxH5Gawh$wrfkV+#f2ZU`W! M9L4P4D!~UWe#d+#dH?_b literal 1780 zcmbW2X*Amj7l#vDB4TMe!Kqq8(GzNE6(!mcX@yBDme84?A#Erv(uP{PF_sQ#TD5dS zsM?Bu#?n$tLfT=dWsDk3YbjE-R5YE^O3)ZHALhL8$M=2C{oUuwJ?DPA_XpBRcXikv z7zhMX_rT+PcKrPBKoxg-L3%K6M>G<_@A!^KMn)DF7bOzO+S=Os`npsqmC0lq|FHQF zTmQ|M|Ap=C?GRf+?amq_rq4-Vkcyg?F3J>bhdJR(p+z#2Qdl{KWx!*8SO4hb%v*6P zhn5clsTg_ST>av?D@Dr%r8e+%2&SYIUL1?DS-+mQ%<#icG!n<(ug-npJD1rYt*-}YqocT$4L$Io!im0?sXPxcqnYyx z<0lcTG-SvJsSEqxV10iv_@cHjkv&FSy3l?mFxnGw@>tj4UZ?p6@|CN^Ed#)O0`rKS ziR(^t3v`!N4l_9VG&E{t7g@EMebe)1Y+!EJx07B~v4Q!)wy%Sf-?b-A^76A}p{?U( z@=YfJTi$C|wfdx35FL@V#K@7e)xjWc_BW6lcyn_#Ay+C|TAXFx{4JR~>FvbO6phu>l*_CVvM>m3I z74R>AIGgaX{ax;?y&mXv$8<-)uY*s?dYONs!C!`tWrH#Jv+5;SHCW1&qO|Mc`>8|E zjedzxNX&>C+&5$GzRTzP*A(t1&L)c3KuXIW;qomOT|6EMX#9GG@Cwh5N;j4dbaM+$ zscgDRx}b=0sR=xQGA@UfbS8GQtilgrbDBUz5{rV^Kj3jF+EsG8y9|Mc-a67@0kJp7 z`W*Uy;vV<*3Tvr*hHh1u+ucP+qnbxp$gEaDTmAq~9g>2>7FP=>ov7^gR{0jgDI6bf zj`--LHyNo}a{gCjbgRV%3u%m!n8){c1|`zNqP7@wRbKl{geCz6t$mUU19?BK65DgM zRF}%>mLWp-LVl9eIsIo$O|!p)&KX6S%kbp=456d0r)hQSnWt$Wp*zRohPbgiH^Rdn z_4&bh0ejkH;MOyE7|kC%RRFGqoqMM#%wT^yhBz>8Ag7+Q^J_o|t zk|pO=g~I*#jvvh4on$xUS!(gtYA*O&|P**T3DzPPegOX$7vnpiAFb3go52@ zp6HJ|rlbySV_vAv&IleKbu{V&uT(S7H4F|&uPvcYp-V!7*P_7sRu8@P{2Me1n9v`& z)vCZt2$2*JhA349v>LBMIaUB1ATcdq1?vD5Xi*|Mr4p$s&Py+t*EB4Y;J{F0h$`1d zBb6-B8=6wJEKi#Jdh|E@#yfG9IgvxS5m|_0M3BcA}Zn_hHs^S7rE@)kjnA z1ZbPTU8jUX6Vl07Qq1&Ap$TUgcgK^p)wfQYwR|cnzaUiS;n&MmsPwg<^ZXu~YsWYB z#@lm#W0@n|S1xlW20v_R)W{5DIg2{M%KvfRZqUx^eZ#oxcL|IDQgWi>0Rg E1z#s(%m4rY diff --git a/selfdrive/assets/navigation/direction_updown.png b/selfdrive/assets/navigation/direction_updown.png index 16d0979f3ed040ea1036a8a737d609b400160faf..3c9e3fe22e019b625b3716f67c1d364c0d68b422 100644 GIT binary patch literal 129 zcmWN?%MHUI3;@tOQ?Nio!k=toj44QMiE5HVr*HHQ@1igF@sVxLgLkE#eLh~5m-}|+ z1;<go%Hf(S8cpW*qV-{z> z)DTUC=YPvkySpOj0bQA6$xgNq!Wg-R+P5+U>dbeV@Dc`Qy3wy?fuv6nxwg z=n5zV0$CEjJto!84?d-YQ;?}k(5n!;$}C0kC;<&AZkyv0@-F=vx691i|aO1)C3pxmWDwa!{a45mc zf4gY37Ktq*{r*)FZA4nNn`ddFcwa*69n`37${oj60*b%Lqn3E(91}-A-E8ffhm}n_7iFUU;p$V<`)S`aOGQhN`jr>J5O+?=TyU7t! zD_~dDr~uscIziDQhnBXDNc)uqHs>^^N6nzUm`1aoQ*1;fDwc=elNIA*%k?` zte-!ALw;S|{%JP%hocEcZwf37y=sOF+aZ+kd&l>CJMz&{ZXf0f3>p1D(2xHnWK&yBLYOW$K`-GaJi`VdtFf7t0x6Fhv$g2 zYG+)i`GhiuDI3XIu@HBc(UgaK(giQr2}6gtu2|-@#Ent05ZCh`?GYFG&po%u{ECyk z$x7|!-4Xb@xo*V|9Tz@yo2k35He@>LtQQ9xRd~1Q9j~2kB)|M?GK$-fqK38$BGm(- zW|B1zC#Wtge2QOvkdmuar@lz4FFjVCQ;ESezmuHaa8Gi&mKpXaBeSM-)Q|PF5M7B0 z8Qbhx6paZHjJ*c{M=rfuW{RYCj`_#{<_tX+@!B`LncT?cIjmoGcIf2!eVzHP96*wO zz_}ds)d|Sw0bsI%?u_s7b?6{}DFimi>CVfAz6cRnb{#luraO-a<(%lp2v55BjjI;* z-m319561TAZ>5V%RfVe?fMAu6Xz3MEr@g7|B#HJ>3cZHb_e!BMv|fI|n?|`arB11`pGPRE)kDQ1bEdcQ lEFttYNsD{J7Wb6t1GdBf+Nxa(#_j(I62A=}Bi@qB_!}`=u8IHv diff --git a/selfdrive/assets/navigation/home.png b/selfdrive/assets/navigation/home.png index 8a4f65c7d7a8367e4d4241e6318b67dd5005de95..321cef4163007d727100513f187ddfa0268edf7f 100644 GIT binary patch literal 129 zcmWN?OA^8$3;@tQr{DsX08LVQ8@|GfO2^bLJiWfnyXsrUeCd9khwR4O$9a1+S^oD= zTJn4vIcheSRdW?2`^=FXyM&yHu#()HED1}CC^x%^T>WxoS!eERrQBER zEMzN3>&{J%|Ms8PYi3^GdFGk<&OGzX%;z0@$IOU@QGgKu0I)y2 zJ(CKXup6jh-<9)=1(EzPVa%y`n^>^0wC7)m^Uq+oiMUq5_eYkCbxx!=3yrUF2gdEL z&uX+Zf<;t@y5Zk$eOEJ5DRs{+422A(1=OODF1Y0&YG2o|_CM9&o2jaCVFK!=-x;Ka zvU?W(f=zN!12Y9mM-Qe=+rn~hjn52bzwO@Gd;nK9X?7xM4L1?iN57V4M!-NJ3)!ab zFbz`@GSKXvtYIn1*RK9my7jSFaQHC8?W zhoCZG45)|3ndWSKBybvgzYOf6A~ zv))c3RL=ldX~VOh=%|qYzL;F=ry={b(n}5Hm)j?^`GK~268`S6Z*@-}8#7f2`^ks3 zNv=lhVAdaaE8QT7#F@b`%8?N_+- z9E&#dY5RC|`3Gm{yAQWIjV$xyMe-S&L?n;L7asslXsWIR0$;maN+AJ+;PdKgBd(Wq zPowe&ahLqwMALPpv!VxQp{1d1bZS@Y>pw&6ohqH zz7d1E-3w($HOO<-b^J$woFtJE#wGzir}H&VngkHi#4(ebDF5#fsP%$fo)lBi7s5^; zMmQq4l&OY&`g*;J80#VMM~{`;Pi?fo)CO=my1jOM38XopGb5=;9Oh>mrXw~^fAhaA zpJZgkOJ+q_pVlTK0r`ku!`A%4g}a8Jg#R8xHcY2s_*9V2tzF*Inr(!q1Am{DOZW=C zbMg?EoEIjUvHfzvHK&S6upFljnb*I7|D{FpCm4yV8d)MEi#QP_k67t!v3lF`Ct`0P z#=@}Y1VJ0|8(9XEughr_$AdER1L$o8Dh(bvU;V!mUh)vDR5DgVRv7HInby zp{h*bS3bz$y|@v&MK7+c$?4`dARYCkBeGFBeNr4@K$4!warmr_QCf59M|QybGxk^W zadejipkNVlqq3y?!IukpP_*qE{^qKYd_}y3 zM%LH{wcIoLFo1VAw?+$EF9Dyqk95%({W`fRw++r8MI-O9s?t=7tep#6Khi^M&(du( zO_`9Wo22G)28-q0yErBDtMKM8>J1X_k}hw1mv`8bJmAvebJr>1%RqYpwWCIRM&aH@C=c1{Oj!cH8mwz}uw`=AmqK-ljc0W*qYMeZ*6(+ znkF?E5G4yhtN29XU(~2Pb7RZRU+?9jBDMJb#2>vd=^7ezAeNTK2RK!LjA`Y7&&ner z8@Uhlvg-=9ZYKI0Y6h~oXPSSqw zDsUxk+m-DxXC(MHOTPAx8yObfPWC+~ae`WzI@%9)55=p(;`ee?bfMg=o<(`KfE+C8 z@axm(Kh*HNdVLMPcR72_NOLj=z?S+uW1pkOcOq;(J;kkiyxMee@ZWE8$Nn1Z9cMPc zyebVcm|F!8IN^6;0zQ(TIIv5gziduf5YH<9-5~GSxXB%UcqL>;)A$(e&`y`X1@$Qi z?|?d6TW%p|1GeLAM>$pH!b+|wndJP50RzH?exqY0GR8cL!RpQ=*1r8maA&09L)EDK zm(Sx1pEi00WdsOreOEcztnvG=(6nz~5>D6ygDU?ExwF**J&okN^;B=^X;u17+TRvG z1@y6)0UE+8Cg#nH#7WWo3~PS*Imy!6F~S0^-;2T{>^1}M6Rhkfp8V3!pRrD6^$o9g z`wY$Sp_e0sAz?SB1B*}_S;dQi{KOgYxyz+)dBxzPw8B--YQe^_C&OA$N=au0%0pDg zLCpM3>2}+5_%|ufke%&jCIr4vQ2ev~$>K$L&;xJH;-g|Te+GJfyq;+2uJo!b)d9nW0B#$VK?uThE-Q||8-cdDFN{IP{$kq7evZ|g}> zY_5k*tv!X3Yhs?4>I)rvi#qr9;hSrl3xb@3e?fa|5L8kQk#AMo* zidc2k=2w8LCe0>CEl=nNN+6CVyp$<_r|Aim#jQVN^g0T7e9<|s#c%W2IZXiLW`Uo;GR#^v`fMU(FCnpQ-ZM> zbeoE~d|}KGPp4dgwdUei*PON`p8RqudeB7e6#_p1Jbnd*JjN=7`BtqCmbc%tW!ZiN zZ=v6Ge<(C&18`mJGVJG*Z^=eG;{~lg$GC+C9aR``-ve`2EdIH=JbQ~^I{}9vXVv=~ zpNT(6L;E*Yd{l4HCH+K9g_3$6^7m>+t_#{j@i?Np+DWcPSE4s_LRCV3=2=z0E{4Wh z*xn!4e35(+UaNFi`rfem!j6;>U`;SQv*M;6lfBsa?wSV1ytH<%(eT$`>Iao)B8JJk z@+nopfn_Kps>PZ+m6)jM*k(a%82e<{7aQPSRff;18`8yA&0p(ucT6Xh6e8~z$Rk_ze zJCMbUn{$#v=Efn~&pL*9^k~#Irt@Zy1_ppPo8{+rN zsd#CJQ*d#~<*3H_$DPuK6d?h0Rbc|IfKzp2g8EwOO@-JM3S#PE>6ESny+~TcJ`6u_ z4?CPdDEf5Ki|`S+?N>D?48}G-S$_ZRg3E`z=VC7dFV5Vy+b?@#_e1J!w1b)V+e|^# zyD=um{86yCj)0FfI&f?V)?(pHC%p+OeOv-#<%XPXErpD>zjNyT7|EG&nv0Rw+UA<{ z79LN9An(U_C-zvwrvee6^?rV8QEgSeO zz8aBy5XsYe0+0_8b0LuEzAlaW;G&g2C&TJ#0}`d$K&dwl5$cnS-rr=DM)8apBjcgO z->-XInLJWP!QHGjfh?px@HX%W{V_eQ^wvaBbV=%p=1)y~mA3j^pR`?*q|1-a&d-ytu612O3CFppZN^GF!&;(|L?@a$@>5n1+$nhbJSmA zu`|gmOVx*O{mO@kMJ;R|-VJ;nwyoS~ zA{bNHp2fpb-~fAl>PFQWHdtGF&4WIR6qk#!qq?^0%eHqv&hgTT2;~%rc1d#5)6K*e zem|)T<6)IgkcHfj+j5~U$YU9y2YimO{OH0OuapZII;emnT~$G0Tco41H~ZBxgM^J= zcmAzwi)-@i%d;r#nGo7z^@xAY&~7Q;f0&Sh=grFE^%2R9hjdA@T1Bp_rzhHj6+Y{k zB_Joiz z@IiIeP;5ncAi|V7Hm!mc`PAeFy)2iDn5qFk9BQMoj&j74;CtScN z+N5iAUuI=O6uT=1zYdu(BXzLe&U4XUW?F&nNKcqAx9T}keQ1MFo<}*l14Ab?zRSfi z$T@uvwfUhr+_bkh>|F>>EqrY9EyaiJbwD_3n^{bQ_kB>-P9U~@tQXG0{|dEn&t~e1 z2%QX5>lY3H4wB2gc5H@as*t7LZm_%DaBtlDIwY(#7R4ea!HW+HIWob@`tE*;VS)51 z@!Fb*fShK2AH{IckmYQQBJNLmtY22X8wDe&PP9x9-+`Y`EF8t8s=kH`V0}8?N_erA z!m0ie(gG~1R%yu#kIszr&+ebIyRzS~IZr_Y&8z~$8$5@bcxO(Fx-eHMZt`-i1m|xc zOasUy*2tkmmZsy?Zi@;)hmLfu@EwSmo(HTULQ_AmI$&<>2toZ7hz4b5NEKY2)G4b2 zAx*UmSbJ?SF5>_-zG=R+5kc2i(`nq$VVYTZviD6gb z*t=i=?JOzWpDUvg{}E{-GAE`PS~xBsoDrsXF7X0xt>k}ovZ$n2p?|kzDVH2$5srPi zw;Ac*$gkFVmZc6jamU-Y$kj^}v;bs{(>s|Scz-i|(6(bW>7Df>@jjp!;1Id3su?mS z#ZqNXqA%T2sxt9$G;9Jsr8^T!YvGN!P#x$7%1OsBA+P6%3l?4WY?@7%LTx4!%A7IM zmXX(E34M}Ri4B%+>n+vF6LfsQ6K(c3Znd_vte7uq#J`r^@**8M_J4r;pgg8ej{!*) z&x>8g>F2M_v!_)q67#A9mFfII6TiepIJ|r3C0P#HOSo?~eWPtvude_Cb8_)uUY9(m zD+b_Nn&$<~z4V&(kI(5QZdqi2MA?Jv2nK>bg2^gwPgj<>h>A;B4ZHg_|Kk_=Fpvgm?m! zms9LsN;6<5BG=Vh@h3Jd@k4{x%_%-3It26gZ0;I{X`TOzX#NQEhr8-j&cYL2m>X=e z5?KyNN+b;R@{(crE?hYU>T9wUXtVOMoPPKfwV0mVX5C{8JV~f&@~3AEmy^%^%|s4e zNM{-86+S^HRsIlbzg29Nag%5ZLeq+%dHhwYynTEPy*q=t$D(?&Bc8_*2%~_<10M0( ziO~bE!RW?g#nRB$9J9p!Awh&W9;a)aC+l1*ad#Q3R9bA2ivYF?2BCezgz4IMv~$~M z;|qzZMxexe$fSL>j&0f#2(}mHT@Rr`Fs%Sw1EIM(Ub*rd&@7JcwyCC(LE1tR6iIjwOv9cutx=*SMzM!0 zLgrR;3J6y;0g5RX;fg=`s6cd=6`ojp*9y_blct*20Sj>=^)J*JlfdvY zao_Y0a8mO^js-RbzLKhBNP-=eJYQ5cYtP6Q_0-3OOv0K?NVnmNxhRwD8y#hGG@c-I z=Ke#t)k3zc6=Yr5#yTu7<1ynfPRW5Jl8dVRls>MW8NtknC`UVVkUpqelOXWmWT{H* z2RqWO?V(m9ajk_MhpgHpOBXOn0&BLdfAnaS7T#j~&0xZ`h-2Hd{=dF5652u9(`4IT vdj7Tpe{0_BT~)Yw-$7so_YnLJ&zZ - - - - - image/svg+xml - - - - - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:62f1548c61503d8fdb03592413c38204834d52b30b6fb18d1a9982229616a97e +size 2713 diff --git a/selfdrive/assets/navigation/home_inactive.png b/selfdrive/assets/navigation/home_inactive.png index a58fd3864fdf30a983878556fe17b9a85ee1ccc8..9ffaf2b87b1702f4b90d99603f8c8499f9e5e7ef 100644 GIT binary patch literal 129 zcmWN?%MrpL5CG6SRnUNeMV3#xfn^b9R5C(xuzG!$ckyTT@s@3^W9~{l`no;q-2S&S zZ!}(N9!%;2F?x}+C4+7+SO}{3I(nlELIhwSlZ%7oGM8BSuE~a=eU8<*iq^0Rn$Qv? MZ^i6imJu9^AG{JL0{{R3 literal 9165 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 diff --git a/selfdrive/assets/navigation/icon_directions.svg b/selfdrive/assets/navigation/icon_directions.svg index 66009ac43b..c36e27316e 100644 --- a/selfdrive/assets/navigation/icon_directions.svg +++ b/selfdrive/assets/navigation/icon_directions.svg @@ -1 +1,3 @@ - +version https://git-lfs.github.com/spec/v1 +oid sha256:935cd01600d56350cda1941cf382c7c86cd959fa7a0a574bdba8b3011a350578 +size 466 diff --git a/selfdrive/assets/navigation/icon_directions_outlined.svg b/selfdrive/assets/navigation/icon_directions_outlined.svg index 5c0c2fa3be..356a1e7456 100644 --- a/selfdrive/assets/navigation/icon_directions_outlined.svg +++ b/selfdrive/assets/navigation/icon_directions_outlined.svg @@ -1 +1,3 @@ - +version https://git-lfs.github.com/spec/v1 +oid sha256:0e5f0b26a56397872f8ac6085a18b666fba4cdf01299f73d704cb29b22c64606 +size 780 diff --git a/selfdrive/assets/navigation/icon_favorite.svg b/selfdrive/assets/navigation/icon_favorite.svg index ba64df4ab9..0eb0e43688 100644 --- a/selfdrive/assets/navigation/icon_favorite.svg +++ b/selfdrive/assets/navigation/icon_favorite.svg @@ -1 +1,3 @@ - \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:ff184bf0239c54060ffbaf465573f17cbc92bd8d3d38ce10e1bd02cdd2b21575 +size 315 diff --git a/selfdrive/assets/navigation/icon_home.svg b/selfdrive/assets/navigation/icon_home.svg index cb87011090..ca90cc7bf6 100644 --- a/selfdrive/assets/navigation/icon_home.svg +++ b/selfdrive/assets/navigation/icon_home.svg @@ -1 +1,3 @@ - \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:1673f8d46251a05787b60346193852991739345506dc7e9b106dfb370d3611ed +size 489 diff --git a/selfdrive/assets/navigation/icon_recent.svg b/selfdrive/assets/navigation/icon_recent.svg index 668aa38209..76b5a620b1 100644 --- a/selfdrive/assets/navigation/icon_recent.svg +++ b/selfdrive/assets/navigation/icon_recent.svg @@ -1 +1,3 @@ - \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:5fbce167d2005d08e8bc2113e0a9d5d3e2ed113db8e2a020df1ee14633cd3eed +size 1279 diff --git a/selfdrive/assets/navigation/icon_settings.svg b/selfdrive/assets/navigation/icon_settings.svg index 134cc0f31f..3fd31459c3 100644 --- a/selfdrive/assets/navigation/icon_settings.svg +++ b/selfdrive/assets/navigation/icon_settings.svg @@ -1 +1,3 @@ - +version https://git-lfs.github.com/spec/v1 +oid sha256:1fa72d94b5e05884129dd502babd5de78666c349bb5d12e19872fdbc9fe2100e +size 910 diff --git a/selfdrive/assets/navigation/icon_work.svg b/selfdrive/assets/navigation/icon_work.svg index c1ea6c5e3b..dc18914a39 100644 --- a/selfdrive/assets/navigation/icon_work.svg +++ b/selfdrive/assets/navigation/icon_work.svg @@ -1 +1,3 @@ - \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:7ea949d3b66896ac3404d20aea9445a5cd00fefd18623f1c7160293a1de5f807 +size 364 diff --git a/selfdrive/assets/navigation/work.png b/selfdrive/assets/navigation/work.png index 611f9b038dc9cd271b9ff79bbc70e32b1c342630..589087a8aa197af1373b5d309eacd9db55b3a2a9 100644 GIT binary patch literal 129 zcmWN?K@!3s3;@78uiyg~h)sb020{>KR65f3;OliSd&y_^@z(7)_PH7J=*R6@=kmXu zdExOga~C$3Q==C>TLPqQjY%pv0-LY^5pnR*V}`&a*)azpcjsUQ?OLL*2tIHTz~F&n LQL}$VBYLZTuX!f# literal 5570 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 N - - - - - image/svg+xml - - - - - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:d2495943fec4ab00b1df5e4e6227bbec45ec57b19b25be561d616292316212f8 +size 2188 diff --git a/selfdrive/assets/navigation/work_inactive.png b/selfdrive/assets/navigation/work_inactive.png index 679e6a54b2852e69248e8387ef0b251f05b4e23f..c1ce69b0042c4b4dd5fd4ea8f27d3ce609610b59 100644 GIT binary patch literal 129 zcmWN?NfN>!5CFhCuiyiQZ5W39hG7w^)T9J+@b%i4zUq69<0boA=iHULk9B*rx&7}S z-tzHO^Q>82MjWHa*^~9SM^`af2JM--G@#<4<&a%KW5MZ$NR%6SFtz%~k)U8I#W6K) MQxJ1}t0VvvKgxn9djJ3c literal 8446 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 diff --git a/selfdrive/assets/offroad/icon_calibration.png b/selfdrive/assets/offroad/icon_calibration.png index c4ee0d63d46210deafe0797f182e563c50e9f042..d2a098ba5a5fc2d34fb0e9b1570da29fdbdf0d61 100644 GIT binary patch literal 129 zcmWN?K@!3s3;@78uiyg~0xcx{O$kJpQRxWw;OliSdzJTS{iW-i=h%(8w|RTWSpL^f zTDU*WI18H#RBu_2dblm@HU|_JJ*5l@g^6sj86uGLydisZ5mt^F3kS}t7Z9{~Fh-%^ MS&jBB8VRiW0k)kdA^-pY literal 8910 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 - - +version https://git-lfs.github.com/spec/v1 +oid sha256:da1540859c4c42878a32a0a81a38ca38b04be4cb0fe46709df3e024b7f3b4036 +size 243 diff --git a/selfdrive/assets/offroad/icon_chevron_right.png b/selfdrive/assets/offroad/icon_chevron_right.png index a3aaa76486170d8659bb9acd948893ac6842ae40..46baa1324058c78ec23aa8997966ded0933d66ee 100644 GIT binary patch literal 129 zcmWN{yAi`63;@ucDOex|iNCioNDka3!56TLOdnUTc^7|2>mOO?JjSZjv(3kAAIoh$ z^HTa-jf0E2K=ej()D4|q$tImB1drY@qKVEh+e8U8Fhn9meUDP2_n}yqv$mQyAOu6S MTQS;?C0Nwr4~;M;XaE2J literal 1420 zcmV;71#|j|P)%Px)MoC0LRCod1n_GxgWf+Fto=@56h^JB+LPteYRG6qnGRgypNCQ%a2Z)rSyfBPN z>%uOIx+uD^@WP<9E-EMl3r}T{q6OMikcv@xD2udk@_^d-ytacuCUw1wf9-Gk!OP6- zz25nrcdfnmTI>J+-JwIdT3cId(DP_FI*GnPYf;^Q+f?08;O{}dqxPzW2bWZ1sKJ2$ zU#PZ0AFHZxkgsk_e}&)w;kkuX6AsvKuMp<)r_S@heN_<-&_6Gyzslcdd45{>a6s<7 zN|5%R{+kEyh#amXa2pK3o_IDAZld8W#sXXy^fhuypQfXWAvL-HSC5W4$?zOTaZlpF zjYr3kQwK2(Huh;T7T_9?E~lK*X`{9nQlkrSccD{GGCW_RK1SPOEWph~I$t@p9aE2D zNR2MQH6dMTIHglT7o%-47T^{jT_Za6E~ZY!kQ!Zpd&Egq_UL}DWV9{D0^E~1!o5-q zrP+mGt8#>U&TLzZjbJb52=_!Wlx7!#t;rGYVY6*|8=*R)x6N=D@ZF!D((C|SXY_#? z?i{{ZX6y7e;JTqrX1LS%rlzMfI{?=ceQt)+#dd?)I=v0JKIkhmoNklGrKdDI09T8$ zjCQr%55Mxq2(xv18*qcrfwsgMe*cMWho+}AI{-Hf3DT*3mfqrq4;n|C7 z(o>opfE$bcKu&3#vzOUAy$!g@=r1Q3o-L?bdP=hcaE(Z##!h{Rsk7NSy$!f&$Q?gf zk5{ABF$=gkNY{u?y&f}FnTkXYB3<1(^{Bz`5qzPii*EpMml;me#X};$=lTKYu434Ow`=5Z0|C@ffm5r(1``;GZbm;E$$kqR ztQ_tZf@ydzYuyz0Chp(eKAayn8b{!);VH(#A!ve&2d3Zk89M+q0J-O`pYaaM*fRa*K-C}(Z#$)z?a1_$ z89M;g3+-?c;n4@otr=UU-yA4S_ijc`=~QrAddiF)fXXvLe+K{E8C#~`94HM5yoH?7 zi1O_8ltu@@Ry&FCXe4=_(K5XSuouiwvc>%J5MWQ|2(>)@ZZ|p*=+PXZV%|IwXg<>L zv{SDd`!T(RB$^v@*A~BlH{J?WpzcLF=Q#BqrjS?aw*%JVB*ODCiZ|K>)C6<_IrSMP zecLHk0acHVIf?LSv01!tS^za19YRihhpB(8!vqabgU|sd5uP7Vyy0n}G%VeWoQgT2 zK$70bok-9W(3prXzwUr-Lr!VZZc@Zhx>4NVB*LR<*2ajTwD$fTGgRD(aRRL|L&aUn zKp=O0hOX`xg?sr42(V|&BPCnRonacV23cuUKH8jW;u>Xg0=&^z2GAW(Hkw-HWhHBH}$ zp@sl<&`E?xGm#;uNoxVqt+`X0GmiNcDZup6?3AWSV}7X(uqjR=JTa$9%Wra>KxY_l z_!c}fOQlk?;Vs0XU|Gi;&fu9%s9l8+noSh!Xa?_jww+6;PcnEavnlVD`PcX!yTEo2 zREE;%3akcYu4VXavGMOKYay5xJbsVbt5ZDqP-O{obO&B*-=9KXAkCC$xOyELmE+%5 a%z6?0(E|2_u(%}v0000 - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:4eff44a05132ed9f99ad821993be4bab9b1a1c880e07441f3b887214bee62afe +size 825 diff --git a/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg b/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg index 0175e672c6..d5a8c87e21 100644 --- a/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg +++ b/selfdrive/assets/offroad/icon_disengage_on_accelerator.svg @@ -1,7 +1,3 @@ - - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:5252412f2225c89ea7e78ad0fbd6544170aea157693ce0f9778f26a64f582ec2 +size 6821 diff --git a/selfdrive/assets/offroad/icon_lock_closed.svg b/selfdrive/assets/offroad/icon_lock_closed.svg index 7dc9283c81..501d978a8a 100644 --- a/selfdrive/assets/offroad/icon_lock_closed.svg +++ b/selfdrive/assets/offroad/icon_lock_closed.svg @@ -1,4 +1,3 @@ - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:ef09792cc1893f81c64abd6b72091d3762c07b849863ce90508afdd29b392c02 +size 732 diff --git a/selfdrive/assets/offroad/icon_map.png b/selfdrive/assets/offroad/icon_map.png index 21dd0bacc65f76c55875570d5169263f43ad0d32..82c0236a48d28d405dabfed88be303c11f037682 100644 GIT binary patch literal 130 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 z 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 literal 29822 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 literal 635 zcmeAS@N?(olHy`uVBq!ia0vp^DL@>;!3HEtW=#4Cq!^2X+?^QKos)UVz`$hc>Eakt z!T9#JuNSkUK-}xN zy3$3a*2|)|88P+VipiRrX)xc6<96RxzpWB&nN#kbx$`EMZSjKD$?n_zqh^;itetfr zWN!IeF3D`eo~+^-=G!ED-f~r5ICt-lfwd{CT&{wA6>E&5>G2IOY;*5l5pTb-S}TFM za^)|sjC-OL+Zykb^lyj{eK4(mZ|L9Zv-|D8y}JEyHJ^VKf8p}B14q&>?pt}=L2t{O zY@6svMq~5eH(f9OUg;v=cw1hazvNKZ&Ffps?%w+%RmR0S5*~Ld1{x zO!a@Sb57j$=d;SCY4y0Lw)n9(n<#KBhXDzot z=I5_I>)n><-o3DyX`-3GZ+n!>Eg6BmUO`u}u7n-=O=-S1(9a~(3 zr&><*h?ac*L(k>bqqSahJeRC(`P3lia*HkDsohkM=)8xGUpu$(vYalpnz$`y6Z7kT cQklP*^0qEYQEN`^2c{+lPgg&ebxsLQ05!`uO&L%iV LAZGuP40;zowNWQE literal 604 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 diff --git a/selfdrive/assets/offroad/icon_monitoring.png b/selfdrive/assets/offroad/icon_monitoring.png index 05f78811e274a59ea8cd114eeafb16a7294878da..39d52de13d6b4d98a70d2e6d9318a65445b789b7 100644 GIT binary patch literal 130 zcmWN@NfN>!5CFhCuiyiQ9ftgdWk#q{NeRip*PB!Kh2Po7hqtwkxeM{^>+zzw-L^At zWxT~au!u{s(JP-VY3FyDOLf^8pS4n&tVy1{i6$hhSZo3(Z6I~07_y*I61WI}D&>rr M*z7;#4t$905AeGu{{R30 literal 58679 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 diff --git a/selfdrive/assets/offroad/icon_network.png b/selfdrive/assets/offroad/icon_network.png index 3236924f4dd90b40018cad92538b397e246994c2..71ccc89aacd56dfffece5a53171aec71e8f500fb 100644 GIT binary patch literal 130 zcmWN?K@!3s3;@78uiyg~2nIrbLnswyR62(C;OliSd)1Hh@z!n5LsnxRecqnCEdSfd zOBpXCM=j=ZYV=J@%Yb1GwHEUJo?AHL=$S^xk5 literal 39872 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 diff --git a/selfdrive/assets/offroad/icon_plus.png b/selfdrive/assets/offroad/icon_plus.png index 92b448b0bdcfcb2b1f1abede5f5b751b314353d1..6418a37a654ffa7908bb1aa8c7db559cd1ccd7bd 100644 GIT binary patch literal 129 zcmWN?NfN>!5CFhCuiyiQWe7`tGYpJSrIHfN!PjeF`l|2Q$4j=g&Uq+x@9Xy9x&3dS zywP~7c~(-F5u+D58`|9;jKGE@Zk`0UAWfNWE& MNMiPHl{Ny1AN%Ggc>n+a literal 2833 zcmV+s3-0uZP)WN8vmn!XzrJg`6*c(%5Ay&O9c%;y) z_8(A-iN%{1Vxe9t=wzTMcEYNVPpYHB6@e%5`)+1c6M*|*>B zo}35f%-b{1eCNBfv$Hd^zq4&yM6O-Cc1rFak*sao{X{8U^WzrYF0t?q3sfnSPoCr^ z!dU@gL;`s)h<{4l)evMxqq2K&hyI+3=Ef6W{hHEud3ipJe|hQqES$BzrVUOpFL0z) z0qD&d?elC^%MYzT@8xA&-vqP@d9I-BM^xaTSLks+FB+EeopM*@ei16T{(;3X)8f{? zI(AGq^D|~-aIKK4h*|Q}rFUg}ta_AV>8&~%ea08R&sf-9sAzR{FrM;F*M(bA*Z!jT zRr{31`1W;OScTRom>|bop&;O3TRO%p+`&H}^k=M`Gv3pv*~@&Rr*5n(6d;lpvd&Y= zw*&*qFn6aV4(U??J@%@M?hFlE)57#vTsCf0`hUYbX!&KBNSvBO$~J&nxV1R?=fa%iH!A=!>HCD=@om{K*pz_)3v*li^Edn6eIeXh_Y!qO`Av>9M$P zFqU7Nriz}Bdq?h~+@Iy>9ko)2`eT*!pW0DFRfrweR!V3dl4D$HG)ftCC6}zc`31yM zVE(YjGTZt-dWTp>Pxmnj2%sAnXIKgAme|La?UyK@QQ_BAWz?FPs_P@Jve`Oam7j|~ zr(>4dAg25%h(|RXrNWvWqa>CVyP)C3DJz#N7M^{o?Av52kMY@;-G0QvQN)(%`__sW zI$zJiqh7qbt(GDE(eJ#6o6w5ekL3z`Lf)?0b)1#^iTc+P3@Nb4?N_U~a+61xl zd@ih{NBj-CRam|NTKPOT{}L*7h(F43f^;ZbDgtoHEtcVoDlutJZw2doAFS*qz2Mc^ z^YXqa+aG02%kVSGXfrT-c2*kvH??I<3v{X9^no%W)4)2(GRN=w0Gy!gPnl`FBbO?V zi~&-W-zvvEEJ*;(n|YFq0oZH`z~LkiQsQ;u1-V~Y;W{6qb@`Sza-ZJDt4efb%m8%p zM+W`L4ps2EfL-hYZlW@~vT<6#OFIS=0cxuDsIY$F9^?KcZ7SK6uP%41rxSgJ_VQDM zylMOqW%n4jlB$r^G54Ie^ojl31^6w);Fz`qW%4+0Tnsk-k^Q&n0TKt8`bwZll+pz> z6}*G^1Y%&n8>>4C+7yNRO~4~cd)d-Q^yvmj6l|}0)$e!9os;V;N4fwh-H2U`2ssa^ z2j%XUyGxG6IBsU<(*Kk@A$MF1o|QkH2q?SO$@7-rk}!W?ov89-0f0VHW_IH}*kpX8 z54wi3;hJv$tDp)mHB>rO_CqOXYi~p$*()Q374H3ttuT(e7APb6R6$ckIp9o9c9Y)Vn`|n7 zkL-ScUdFWS@=BC+41z)viCd?(?(w&YfV8RBEsz@M@3kpCm9Bb3HaldohFzvthXgWG zpUxm+8>as-B;ZxpO5VY517=L7h2sa-r`uqfTK9n^GW|*$rau$KY)UqYe<>Q3r6B#^ z_+=Wx-)X~i?JjCVMUB}S#jZ+eXQQG{EVg0#=LrV;sH6bK{^d4IQ-I@>9}=h&UGeM? z5;|hAFxjan*JkbTZ1gl#g<+CmqfG^rX{%3}I{AB>?onrGm3hbM9)nFu4LKu*g=K%J zsi~RV3Ddu~*b;p4@i|O$Ug&>A0v?g+i?Y4O?$#GW0v_4ceb~1f@#x5v4Pssdzve(I zlI~0wZy)@z_KjN4gPLlTY341eLHSE zB~ca4VGYm6NBFV`buYCfIHrjxlh^d;3C~4Bz~Cw{Z9v61n~nP|KA|6-4i}g3_NU{!j9+~abvl_DIc?|ni&I!>pM= zZ4ou%7C8pU)LT@llL;8XI{v_0%UWWlfb!UUNp9&v8+fr8Oll`fDYXa?Kilyf%u1hA z3yif(7ut}0s*<;j0}j2U^TSX8xZ*F$?eB$PIfZ$x_*vH(b8DhSFb9NqiWlCEm%~?Y zq`ZLME5a+^sbV!G6D@){GWLqx_0of5eEDhg@Owmf^sU=hB)lU`h{YMFl+L-lEt0WrT5<5E7FC5qdAwZtQ$I%G59e;SIYna z^Rzp8V3=`a1#x8MhhHV*E8#@azbcT<<2I&ABa} z`qvEPoS2#A_xJ+5wDsuM1^N5d(`@Ll_>UBr%Z8_6TA~k!aOtE#w5|Zlr5rI}X>Uuq zF#vb`L>^o^DG*Jn6!-;|y>c9wnT=>z`*jE=f0LWjHWr-i%{eTdN&=Hb|VL5)l@PHhZU2j60 z`9PK<1j!^rQmY;vvZ@}=?BoTx%|4_sMBFEGlg>hiJP~izqeBB!EXNh*g7MH~XJWGm zZQ&t@RDR|?X=ByX*PZA`Tz# jyzaSk8e2I(Jq!6i3v!liw|v~500000NkvXXu0mjfpO=4A diff --git a/selfdrive/assets/offroad/icon_road.png b/selfdrive/assets/offroad/icon_road.png index 5868ed1ccc3f733b4c42838cbeea9354d97c2120..7e460dc0d0732f4c6aeee4119a348514e0deb385 100644 GIT binary patch literal 129 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 diff --git a/selfdrive/assets/offroad/icon_settings.png b/selfdrive/assets/offroad/icon_settings.png index d0c90a620d780d68bc33098bfa3897bf1066da0c..3d8159acc66b1c6e49f047d588df5e49fa9992cd 100644 GIT binary patch literal 130 zcmWN`OA^8$3;@tQr{DsX5=bn)4WA&)sB{c=;py$_E#Ao=>Eo^2oQLejJo>ynt1Or8 zo0dFYMh?#Av}*LCrA0v6BLXD$l!@SuZY5VUI~!7gPywM-n}GAW0ViKlA+%e;t1CsU N(JSdc*48-+>JRHdC-MLQ literal 13369 zcmXAwV_+m*w}q2TY}=XGwr$(V#5N|jZQC|FnAo;4NhY>+`~B{Z?)p=8dRLt~`|Q1* z)sadHk_fQ4upl5H2+~qwD!^ke@Y4nj3H-L8e2056fn3!w_d&fw@f`rh;g@n}9#FQVpjFxOri#3z}8P+q8L{nKGV4{nY zD;HNZ0>(jmDV{9|8w`*KZ*_GSk*z8@O;=XuSi9=>e(p1T;ma3MimbIbN zX{nffy4=j?^Ss#`Ad`$k$LDpgx%oF=-2M1n&g*f7|9)DY|FrC-ytoTEg=Jwc$uKl3uC)4 zNmi)pbUj^&cE4D=H8D2c_aK$ebl&R^d~LRv6_8zB1d)Ow9my6m=k-doSc$=5ok^f~ znPAhRtgywSCNnFY>Jng0K-3!AbT`|VB}Im&kbl%P^lhyL>>rX2ttdsH`NUm;g|F6V zyL7`QlMUbF1|Fyjj7byzO})Qt-`%0XLxkAGnD|c7HuE>x>Tsf@rN06*U^g&3iq?e2 zK8==YHXaP)70>D^Oel$QiHrL1s3i8B^Gw>6f6F5M%xyZi#A!?Q(!#z#!~9lkgW@z` zx+f1saZJwB{Y}4pWk{QhVw^*Kho8gacIG96cZV!E%5z)}Un0H1eGRx*@V|o}9AZ(V zr=s%gF`v%rlp%__m^n%|G%BgeC@&Fl;dI#H>ypD1a~#;|`Q4}V=Cxo;O(1y}o0@=W zwU9qXRu3&lcb`PB+Xg6j2p+G`_mO4?-^yz`e!uP$)N1uFk%JWT9B8px{E`P*PD8yL znmE_Rk!t4f60|5 z@Mdcd{(7Vunp)8TdN(&YDK0%X?5*HvO+Ld^00P)SW*abjWyMO`DX~$L73$S1l?Lcd zexGkxQ`wvj5qG2#ZH|`RKOx`c6yL>;!79-laM|_{y39Gz9=jyc=4iWW*839` zG-43hod1+M%k;igy#`AcrBx_oSQJWdjfURVakrG%SAKz*p3%0AFJzg9DLIG$JE)YA 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 B>JR_` diff --git a/selfdrive/assets/offroad/icon_speed_limit.png b/selfdrive/assets/offroad/icon_speed_limit.png index 0aa7038f909985af264c1c4451bf9c143d724c03..d4c662cfc0ae30dc84796041e2c70e29448abbb8 100644 GIT binary patch literal 129 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 D0wQgt diff --git a/selfdrive/assets/offroad/icon_warning.png b/selfdrive/assets/offroad/icon_warning.png index 50fe8211277954c2f7fb8210bc48aa2c6b07dfec..583f9c2443ac6f1b6e11224c2d191a47e60ef850 100644 GIT binary patch literal 129 zcmWN?K@!3s3;@78uiyg~5}-hT10fY=R63^i;OliSd+}SweCd9khit~&$9a3~vi$F# zy3l+YIhf1^YL2F*7YyqL!3Ff8)#@26vW?tYiR6LZp0g_ffm4AL0cfR|BU3`cl0C;( MlaldeovcOm1GSGQEC2ui literal 8002 zcmV-IAHCp-P)lb37cpP3Sy8=APFcU1e7hTf`YOmM3^`1Fo5hkGybPv*L~I1r>ag@S8w-reP6w< zI(5!}{&Tus-@e_|edn2{!ZNP}d+yx13qtR8U3Xp-y&v2LIF-i~z<77=T-6WWra-?i z0aE!xro2#}Kx_uCF`Lam?lZ>tV?Lb$nBIWV`VvElj*gqOIU|x$(Us9%(3Gk1`BX#S z+LU!2)y9l`gW9l}s$^%w!9Ln@l6>tD-BDOz(T!oPh$ijd7s49m*MVhNcX5KQ?8# zz73q@X?Y=M1Obhi@(lA-;Q&*~=zq)T54xO#WDo-jSO=j&V`;G-GH86@2@Re38=m9E z^<)U041f-Xc5aptt2a!qhEdWD(vcW(&TKM;H(51oib}5QdD54dK>zZ=j#41DErF+n zor=1hQna4s>dMrXur+OHwmd>Y@@5W(^c4<~nT+~wB~aTCP{ZZ{qaUYB1TE#PI3C7P ztpCYHoe){6O~~AkVvF^_cdC}*rCo;5dtK?e^eP_%iPJ-fgxHwD1Wy%muE0Wey=FfN z_Hz`LbgO>|No_KRpb*mJNV9tfK^!V}9;!&0O3Mu{45dU{q24Ply;5TunJryFn%9)p7x{@#&I|ca8&aS4BhuNT4AF*mNcL6tY$R zLU0lr06b10REmI%TV(!TBDIIrD#}u7lhISgw9O0%I=XGz-dtdoQPD8uS2#4I!lcPN z?32@Qg_B`aprh=uz1eG~DZlAewkQDOU=1fIcm*xO(UeY8hHcntys}BazzW*nUS-Jw zOF!|-Itv8X_$7GBah!w-FUBEH$BTsvOu8EWs=}KOf^AhN*47CA6{8>OCUZ)V%Xo^q zJe)lh>LIorw+9D4XwXu=GEl0q0WNn~_}ob}a_1^g-V*X_Xs}Hs(sXN6ND;VTZ|!2) zy@R7x1UGRZY=jN@PO-e^=6DD&$kFM+{6gFLFkegW0q^;^kc>`x5&B2c3oy>ma0-7h z)@(UDzgM-(U6BmfmF}7<;Y+O3#nD|gcL{j4^I4V4E0;xXK-bQ

Ze=h#d{4~bitD_2ZtrYQKoAETS=!z*JX^+L7>i0NsJE?2wxvH?YX{}IDiH>q;v-y zSSm|ypF75FL-(ZvKeH(ml2f+R;>?-?h3#jNP3{`|^sVCnI7fk5m5c%C zPf<2%Yp7Ve&`++#lbuybGN2q|TWI%bc1cs>OX=vgO2%ym{}kpR0VtVpZY%ZN9fczH za7+Jp@1$V!!F+?<_A0Pe)eFH(|3F(d=TRfRdv35{fyOPH zb?ZPNVI2?cUj@Qs%rwk%lK}`;(uCQFGN!T)u#VO7s4cT}Yw4JYrY=DHGPk*Z)WA!+ zek;F*fxAZLJ0Aug_CvTefU&CKBzney|By*MmJ@xtWIik0VcSQIWV})dal8Al5s<#t zAbtoDzt9jy$$A(?-iqUwoU71)Y0`#|e9$6(`e|-Llenq*x1C8mjgnSoE^dxh-G@)L zoQfq){s(7=P~H#yA68AO48A@KRL1FS@U(P*{i;;PVFcS_b=>Vd#)SPk_SR9m4h z9lO-Srkt0-6fXx8-x>*|D}76rK%S1-HCDpD64TZtoThN(NM|7Nw?@Vch8r9UgApPb zBF~2llKow{qh+6Tf08E4@~DTPx~_Zw7P!tQmtj9z1qr!}lFgKDie>^B$#ZoKR_y=@k9PI?^)fscC_KP$x8p`F5Dl92nh5Gx?~`VYq6-m&mojf22f zW`cjLYSQpIxN@xdFLX1mYL3zI5$LD1U{&*i=#DIaaG1F~ni808QSY`G!Cj&S~UkRz{mnfqf){f49%B(Qyzyi6O1l4+e7s z=7S7wP894QH3Z-mIZ#}BD2rQ(J&4}NkWC(|FY6GvgO`XfJ_e$)e#jnROAP?EMhxs| z7~n#pEn9ER{Vhw=Dnhlz72C^;ao8c=gr_vkJRN>1)%5N|N_p#UcX z#6vkU-#vUPa&W2BFxDU(eV*Cl!ROdgUYt)u_Hijzh#q|X)bK~$KZRh4ZQ$USjMEpz z03{R|zouyA67V8ZIQ)X?l=>p$qDsK!z5?G=6em=l3sjed;uf`oPxb@6D2`KK4@_&T zwdYRTScCBE{PGjaj|2x?l2_+zxlqkTCH77og=B2Eb{bVM2><6A95USsTzVlh1SUFL zc?~%Q*e@}Gk!elg^y`piSY$ir*WsV@k)<){eTUPv%3$`l15>SGHK=(2lnz2Y5p|uK zU9;}M<4shvJiscOLRg(_T2kNC>-*UZAVq&*@}?ey%qpuM4A>7QHQt6TIO-27+>d@U zU$vQJfXm>;6KD?#qncc-;VwY6W@ZoA+Z$hI1FKmGdfee9n@1>dmsTmXQcn@aij{~+ zN-Te6aKLjN8&|?r*HUaRjd`ey^N6;x51@z+OF+%E*5Pz!UgP*6>Sajoa4+m1p`F@HsQAx16qUW!138uRP#0;2-4J<2 z67QsJ7AW)x;`AX(K|P@$X?qq(GN)SG&Fy+JQlS@Q&w1~X%dSN>_`#71>2@0$iQ|?# zTkarb61?g?8sG8Kpf1ID3C2k1AVHHXNqQ^4gkQNzcjrXTd*TZOOIVj^JfQR_{s zBFrQ_cPOjMcjLGSeqkm7bgZqcz#qMV!Ys#B=FkBg?E}i;BQu1kxev0%@Ih8}(sQ#q z<_B~pNJppE4^1SSUW;L#HT%NF)w^^Eq3LI`Vd7!^z6I?3uiGb+Rs#&(J z%%KO^Btm_UNq2@3%Ty-7M&D)+KHL?>AJvi7%0Dy_93^*>5zKPLY+$2@L>1^jxN`Ob)>X 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 - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:485dd0d4eb8968726003ae460bab4ff498127def52b8b1ed6d968f4629ab233a +size 1655 diff --git a/selfdrive/assets/offroad/icon_wifi_strength_high.svg b/selfdrive/assets/offroad/icon_wifi_strength_high.svg index a8db07f91e..ff001bd14a 100644 --- a/selfdrive/assets/offroad/icon_wifi_strength_high.svg +++ b/selfdrive/assets/offroad/icon_wifi_strength_high.svg @@ -1,6 +1,3 @@ - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:c92fd8bebbe630f991fe3f61f61407f8002d29881698634239be3a6fae2fb4bc +size 1657 diff --git a/selfdrive/assets/offroad/icon_wifi_strength_low.svg b/selfdrive/assets/offroad/icon_wifi_strength_low.svg index 8963c3dbc1..bcc6e83c63 100644 --- a/selfdrive/assets/offroad/icon_wifi_strength_low.svg +++ b/selfdrive/assets/offroad/icon_wifi_strength_low.svg @@ -1,6 +1,3 @@ - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:e629e3a84dc288278e455be4a8deff95730e24d9a0b894f8c417ac1c409670ab +size 1661 diff --git a/selfdrive/assets/offroad/icon_wifi_strength_medium.svg b/selfdrive/assets/offroad/icon_wifi_strength_medium.svg index 8f8d503260..e14ebd238f 100644 --- a/selfdrive/assets/offroad/icon_wifi_strength_medium.svg +++ b/selfdrive/assets/offroad/icon_wifi_strength_medium.svg @@ -1,6 +1,3 @@ - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:52113c14656483854a814c50c93770f5e6ab7d63b2b5048ddd6fd47fdfd4a7de +size 1659 diff --git a/selfdrive/assets/offroad/icon_wifi_uploading.svg b/selfdrive/assets/offroad/icon_wifi_uploading.svg index 95cb0e283e..b9392dfe7c 100644 --- a/selfdrive/assets/offroad/icon_wifi_uploading.svg +++ b/selfdrive/assets/offroad/icon_wifi_uploading.svg @@ -1,6 +1,3 @@ - - - - - - +version https://git-lfs.github.com/spec/v1 +oid sha256:bedd579f56c65fffe2ad571f92843b6d103826fe173f8f5b58fc2200d2ad8850 +size 1663 diff --git a/selfdrive/assets/sounds/disengage.wav b/selfdrive/assets/sounds/disengage.wav index ba583c41f362d35ddbba08a247ce2e3ae4dab3c5..f3b5f21a27c7aeb1b15c42a2c285dc90143ccf86 100644 GIT binary patch literal 130 zcmWN?%MrpL5CG6SRnUNeP25k?4X_~0sAMGOVD;u*-kCq6^|!6Zv5(EUM>}rMGN$YL zjZ^9`H};&|38pu-qlOu2gW?I8S&39skOGzzfftrLP+4*Ha>1<39t0LdSTQJ|W)X;H Lv|qL)G>iEItK%jf literal 68628 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 MiaEY4z0nBb2mAOaO#lD@ literal 63120 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%0DTDvgUC)xd@CDDdr zS7{PsxWNF=pP}#3o)DhpCvetNTbQw0d-+d!Ye*Ebht4538D)TwAUF#9nO~#V#r#%w z^X{7GCFJsJWtK(PGJJEVD(GdB+JdHwP20OerX(su*d<&Kz68+H8ZlZRtmQ1>{)x$W zax&PzC?2>JLICvj%vSlFGJT>)+%Qg(g-unAxlO*5Wokl*X{znYA(RoN9DI&7qFQ6m zKsF+DP$Q7ual)`qpI-aRE}5vZVzQc1`MY?m=uoY?#kuZ#!^yrQGpxZu$=$AdGqTrplV>CKK`WV75AZ?kwyM;VQL6KUhF8 z53tv>6+61RZt*g8`{=UI`IFT;+i+_w!#qK$!56(<>;SEGtbKGM^Eh#pX4H8BJ4oA% z%f#8SR-jR^RrBjqfz#1LtB0d%yF~X&^wTE`H~f6|TO57s)%Hk(@JpX+zZymzj?GWB zC@e4gS?@4_9Cn=xL?0kD?kd;`p!tN|7FiDQ11qen8(%eGm3gqL3D_pAhAU<;>PWOiQ(RxU#Y~ zzn-1*rR+oT`=YlMkE=QxLmJNXuIk@1)(MN1r=lBhp};S$KU=L0aD8+ZY9Hr>>-Pob=v=v%);E)!HR6%;GJ&)Q%TMWb_ z*U~nj%#hLQWeS0mK6+T9Y@Tlp68)RLuDmDx;cr%k!&gT1_gLG=_c6DAP9}U#-Bu7) z{-N4-@L~VfX$JZZl)^a1Y2to0ak0?0sk4}{PqfLmuX1)U6IlD0*b2K1R`7Ac<($`A z73>HqRBxDK$hgmoAa--Nu+7O(?q9Sl8Vp=b)WKsRCyp>Yu)?{65&LgjGcdu%@<2vA`U0aEB)R;K(~o7jz*%f5!;y#O6qrR?mYAlQ zYdcvw>RP4S!%YrauD7P^?KV{D?Bl!WJk>-23@JC6r^s*c0~|NZFXBa7Bw~>K9(NOF z3n!>C$`hj@6G!@K?XNl?78q7uE8Cj5CHF(DX>@1$o!`aDZejS;_xvNbDXSm;4b!0Ol5lkb6K+EB7ve?O)IZ?OGKxlVzO>F1+s_i9c(Yx(d>A#tW(>Jtkg--vi#`4q-1Kv1mi2O!`6@Dybd)A|bcE z6(@A;tmU_+S8gcxZU&o#)do!_z3`ql6N{#cr|fXIaJPuJ*|zNWMk~!%8J#eb**(7eqk*ov-uK^P#g)cT~em;RLn(X5^zXfDJ7dK}ILXF|J<`2a!*@z4cm0C7s$ zB;ib!XcFh#x>40H$}u&MvgFxcQx?UZPe;Tb{aKbAlG&W@T_CKkt0?M{480xRr+Tb@ zf<4ZNXXOa&3^p2Oo9wfzx4dW6V#_mKWt3#v$u$*N3jprlS~|3c>^H1_<}iId`HA*v z#!|*+t@WfDEvWWEGKJDgd`JvLh9O;5Ws+kuS^tIJ3w`TE%Ugkl#g#kiHdp4A9cWnA z>RofU6*+jemoj4~ji26(UWc8>w}7X>F_wTE#RRDxnt9wI(qr}kTn~CD=?C;DJQ3ok zUJctj|2IT3e*lsTn^DzcT+~~UcY%{gbKn@Z9=-{chUr2)lOg6$N!>=F6L&g|IGd5XD%$piZh z=4kyQD{s9_-F3pZOu80|vxh~e-6Y0SoydzR`Gg&q9x! zNxMF3-{zJNRU-|@i|q?S^2KQe*^wEmQx9cq&W+4TFIiRJUE$H*HOv{Fk~^zN5I*S! zGo9n3lfoYmT9|D#5$VmEtv9|dj59Un25bM;JHp9i-=_1Jq3ri`55`l{F!MX>3|)u) z6ht$Yv5lx+)CPcpcR>@;YoOsX#nLb1Uk2_DB(`p8e%C5!xLDU$E39!5Eo@oTaKGhO ze^I}&WQ9~ZQ3id7&BL@3uM^KxCs+?SDEdRj6q87CXK52xVrEGnQI`-22q*PTXgVZO zZK4cOcfjo6cTxYsjNy4WW1JuUHTEXF5kmD_pYR(qz5jA${_xcR;rtHQ+B?+?&TSa+IFJxKh#T&UAW*T2N^*1rJd~G%-G%)fP zI`YYSKAaoOC7kcHCh|w{Gq{WFM$RDYL!Sd&iLpQ%{t0Xd(TJRccF29EyCtC`>cMNB zx{a7dlWKYC$s)HLXbvTJGV@zzNgkn)SazY-qOPZTxW8faq_khLTG@tE)4G_Y+8?>8 zd_SW=Q)_*Q0n%`<-mLy5UV;{!57l1JdQHDTJHo(dGBZ@*BU(S*pIOArr^HbUnVF0P z3LnhHNim`5Td;NVx#{qUog=nG;ax{s(^^s*X6o{5ztkLVSk_$A=+~&#EgzU2Yn(bV zd47H?vK?y&(20jh5Y_{&HbyA(2lGAs93zAhOGqc2#naGn$OyPL>@j=_(yoew{DNj6 zw9qKT4_Gk93hzd!#;rq^A=aR=XixRrtg-a+RItQoP~G#O`(%4S3sJgmtna$sm-bJK<$Of z#8R($wxqNeg6FG1=5G@(#KsgH3~x-{Ss8 zokpKTeN|3Qn@>C#jTsv4dD-5t>EByLt+mUl=j&g#$QzN3PVHCwQbvm=R!lCC)aU7y?So6m~KO}Q;qjcHA)Hb(d1 z!TQlNlAP%^${b8N!GYXLeZ{cj?=%=N{4TsNfSPbjCk)B@8+6YL-tksyg)>~~h3qg* zc0ny522qs12s+sP*j2cE;u^dRA%-nS*dsdBs`-kUqvNYaR}5E+@3%-rIrZPG7!?U6 zu;Pd!Sh0C=S>gH0yA3{~#m$_Kuwl0ub&B^?o9SokqI7h019(4K3brSsjjo`4CJX~>i7uc3 z=){F!tk7y;F@cLYikgNB;B+`zy;HVC;yvLoN*i3+`J|=1>2_^w&98E4@$#~(B|Az^ z7YnLZi@NInZK-O%+MhM8n%OvCs(OKZs)@KrnMc^mwb8nN>GBPN`RzK}1Z?dVo#ona z)-6swJ%C1~)ld)8ku(SLesB-nhS@`Hrk(~3C@`ig)(%qQ|` z-E1;Lm)=Wr29iO40EVf>9!A@st5DbByJ7E;7T6Y)7rGpwLWl`rI7bW%Wew|td%(}C zEoQr?6_Pchjw7qZhr4DwlA4Y+=Zi8#4Ncgl_?Eq`VdCAxDI@YJ7pYqH0(XhfLQbH5 zWo7A-h5Pk81!ekt!yo#;jL!29@f`V?tlONmObc2yZ4~U(M5R`Mj>JJi2=)fDj36gS zG27A0p{=mDNDpZE+}u>F#Bc1zh(gS26Sb~tXsH{kxmNa}@>khNNo*OVa-Zm`D5>Re zdrcQ-^6O00+=wzo?FOK!YZ#S`UdA-{7(YVzgTI`6NC4x0;hO7YFpD^1mYVvV=1p&= z|4siz`9VyiPBF`=blNXs32>Tzi825fVYrAM_-c5GYVTBtq<(}kFgif(_}RX$?Ri6= zsIhLo@ng%Q=GoSe*1P?0MqS4g)61vbmCa~0_9`J5e-z{}^w{}q1kIa?WFDozX59qu zgS8|r+(~>7R*G4GJA!@>3qkzGb)r9DP9xVsm*PI--lG8|4e}2Z4nGTVnTwk_HQ73X z9N#~1x|=JWYJJjnzHPoKzRjoAzx|(%vF>FfnWHsRlIgFrvFIzfggf;?}fs5`(E{qSd{aJm?H`J{(KAB9YBp884gbw^#JPLUhdz+w-iN&6 z5oooNAlp7^JU%vla@eXfrnA3UOElcbsG`=C)-+elR@zmO8c#P@wEXB0cJCg`oN|=; zD`%BsXccHd(PvaM*08T>UF8?)!nIH6O!etI7qJMtf(Nc)zKI~S?7ihGQc!CJ!VrialWv=_A1wC%Jh(r(gf`Z}sB6-C`i z1j)vfWke#u7-NV~Ky@Hn<;P}DODID*gHC-B9m5^g&5%ZA({s^=rWY+Wt)^{TyXpt( zho4A#ru1dAYC0kqdlA2i@Q}PqGs=W9>@|IRIZe%YL)%L>p~wMO!VZEH9*NtA$wj?E zMqoSV}aC{4whMM_Ueye53d!}`qz8pY8aQQhV3!|hMH zpLeEqZWW&&vL2;Lh%;xT8`Yl3QVf+i0OV5kvN&2zTokun`)}<^-Ur?qP7|k%v4f_= z*i3OCEd&<;30R>fyPzKX1a}PyAPTS@sITZ_s8;n8*aJi*YO?>_I}uUD5$riH$Yt1y{Q)`PV5o)?HGDJ z+9ffVIUy}jSZSWK280VhCg}j3%5v4Zua&@A$vLiNp#6ef#jK~HC~;&@&>i@IUyrpx zJEMeX9%>_MA94y-jO3!{Q3)s}+znoe;J^Xs7u9unfD9@do4hs_H1eR&P~6kKy~DC2 ztX169(?V%m*lyn??%CRVb&xW?Zc2YnJpWWBMy$jVi9Mt#QYYmxU6<9yY-Vg?sA==G z7t}Bch2jD}05)p!UOoW=;0IwDo(cqmDq;!vkO(D4gXKg5v6xVTDMcC~6JRKnjy!9c zB3U_pY$&Gxn|Mj*_m1iI!|gvieswlZ*Yl2atP9cSe_3&y~Kimrui!ees!B-=npkh!#C^38` zELsyK<*BU|eY3qYjOlk1He<2FTl=r}c8aHaAYy`ere}R`@_^k)?)a%GTdCvxOGqX{ z3kL_@fmKu_b3f;#RtKk+)5G!8`iFg)$)mTCLr965H={n|Trtm3U7D!dC!{Vi8cu}o zKvW|csC}qjhts{{o3q{(l@@1#nbLvp%-&z8hDP5JGT=;0_mecXuxC?(Xic z7uVnp0YVbuvhFTN-~6w(po&V>GMU!t{<^=H_>}Ogz$Wi6XROs|H0v{~>sLmV4=;sE zUze^bpHyk6ey^`EB|6@DJ_YyS6!i&$;kJrrB$TdS%PDM!Z79cFP z0pyukI1uz4PY6c{B7(!h;i6IE_u`QFl=zo;qGXG>ROsP%;C^8BL;o;Oi8i5jzCNyU zTeJn&U#lvuSWuo;eyn^CSdlQfYK!5Z1)xMcLBB2{rTe0ZKxyX+wbHKgd&*y`i7Hr` zp%5z1%ZEy{#CHU}0k)$t*U3g$PtYRdF!~8IV^-9RtVFxA*0O7}+hQ3=52Ptl%B-N4 z;M;Zxf}_G0Qj6lH z=AWp8I-gdjd#hWkyQslbBDq5{1Kf5CK_#~yyA8;$H?bB23HL#tBDc_WEEnq@HVVze z80;I`73l&g$jgx)k*UGH-e{-Lw#tkd5yPD7lNC40TUH#a{8SaAe`RQBYGP~V?ih%Q zoF>LHPV^G@mQW-9M><1}Dz2%Hs)wmME8{?3>XE^+7m_}r%YsBp z5=DulMWaL=MIn(;tP(X8uI3t8U0IuusdOVeBUs1V-Sy0#Yo2DX09hATR8_91JX-l* z)eQX*^8wpom&o@um_|xqF-y*k6IP0MN`@*LswJw!$}Y-r3aMhNY@Xz-@DOhT?=|2!U~mO-?$4!sI;McfjU>m(@oY*i~6ZGY0qdR;8ih3 z(pIDup5z_n7_j*W4d)}Bk&{ph_%`Uj)?%H->R}7m>w&(XX8AA|x&``64I(3vCBduS zB-d2y8S{ISw&qji&dST>p7PFBORKAE-Wi=hfGiBezkA-LXNdi6p7H=4@2k@pdd5OYj!jNFE*e=>D+AjGaZY_!y z#PhCkE@K|(8dVrs?w{Zu;jCp%GA^rWU9q&>Q_;R0E#FbGsOq_4z9nE==$;W+7P(Eg zK#y@o315mINyjP+)TF9dU96TU&C2JBUec%H&4N3;-rT0_qpZVdAFy5GDsmq&KogP4 zXh-w{t37Kw_L8*`D@3ah5&VaNDRo2=4EdXQBaVJH-1NriH7?XA>8BfbhB2lMw(gFv z?rDL8VF67eSJ-mFcyWoWLZ#F0)1Hmm7j4oOXsz0iYKroyG$7n3jOK6R)Mp8h8*l`? zWxp{0K*!)-a68~nj$lvN4pt>g&H2cBk8MR?L-Dkh42BZ}I`?Yzxyko9U?z^jN3pNm^THGfE^DoFsrIR3 zHBXgFRUwF0UYS+2llO-Io;QTUVLd?#k)7x|l#S#dO0+F92wR68K}WG3qXMKAQVagh z^rB`*qCy>mGki+-RA;gErD=s(ZJuX5VC-x3m}=WEI!M<$9~BUgpWzI&HHR;pC+;q5 zrQE7c(mJ&~&1H4A_CM7Y<$Kw4;Rw+$K|e0c*@-qnt{_ElG8_lScQ--jkj0n^9g8(% zxiA6SjSa^Xm><+~wouz~UbtCsfM>Q{Yd2csO?&hws#VqA8m<0g%^yRuX@&KuE5nl+ z=!Fj?UxWMpkJA^tnfpstNw+9=DmE(aDxb-k%Z|wMB`u^?!Xx~j!h3>M{9XKQymVfD zLAt1uV7y?o=#MZ~v{slXY$BY)Tfq6mT81P+CDgO_ zmDj3#)q_l*EF;R%D#bQuiT^9r<8zfXtpA(yrQ@*|4+gZCX34W zSzMZP49iFOSZ{1C;)1)O=fTbr2}UCa(VplDBpJ;4jAho)7YHa)FLcDe+uPm~=lE_} zWR0~tOovPv=2qqbww+FetIRVw@IEq+hM@}V46lq|Anq=oqHrpAYr1GitRQ%o9kN;dKTGD-$K5i4>;H7#K_bT>%zT)nZ9oB?apP^`6i9YVQg*qP*cZn(=gA}*9zO-J4Buh{x0Fx z_;k8I@)l!rd+?Km$>L|yBIzN?L)m|_&C;dPhT@5$vqD_3LEz=6IAP8sb~I}?b`E=k zHpeDn50KA@3avp#!WO1EWTZz^bx1xwJG?&BIXKrh&qIKor@hC9POQF+(qua z9%W!iC=EYET4)PA06Wfk#@)#C335cwMJ2)+BD1JiI7t)|Jmn_>q~J9Ef82)L)!bCh zN%l5YC2B<)BQY?CX-n6m+tN`C#&m*qLRUc*=P9?B;5vw9tA#Z06hOITQyYT^oOz~E z)y7h~$nd*Me*OGfg_9fWTkbAD@0#Mc1a|%4QivpAu{oOWD{Hn5SY{Rb-!nQUiG!?Tkh|#l|FM3`5N08 zw>`C6OSsL5?pt~s>jrhX-c;KFjlQl-Cbs$7lwT6fA)w@Q!@wK3*4TR||)W&XM97X@`q zbA5M8TNOMb+VXk}KZ)+Nn%bhI)9C)+C%)`i{m-`vqdUE;bu9XAvwOsf@^uwwyq!Ox zH^`5de%|>!?D0l@{fdZjhix(|+PA=`10a`CuA<^+{4t&`Uq`%;1m_#1Z~FbQhPzXX zD!Pg5B{og*C7OG$?+7=(IppK`r)~1ms)t) zSWx|?#yGuN;}8>vIae^)Q@|1^a`B!8z1)XLd&$n+_2t~q`{H_~M*Oerhrk%e$vM#E zzYdW>z5AB5KG}Ink4?>HOFHuNbv-;se~tX{Z{^UBiYJHO9m#J~)ach0JLQVOb0bY8 zJk}WUuz0>u5F#pN3>0`&nwvK^XmjhzVYi&$O0|#kv3_aOQLSC==k_1c`*Ekc-S79@ z(mW?Bq4qs_J90|N5cAGLMQ-V{ACK0)nsxu!)6lb=ANM|17uTya>!;XShB}h@)<}@U z(6q#G*ZmTOB7^K++@NYF+#$J?+M&t%Ci_~4dQSZ3z(2$L4jDMHP50)#TkGod7QKfX zhsY^)o%+IdnMX}>!Z zRTRFh+FAb8IM><7waR+U9?h)9`*>VozF-{pAXJ-wPd6g^ns#79-^_{GqWVcqD;qy= zlF)iUizl^Tr#?)NPEfO2MWpyn_nxBJpG)(vd{w=Te|_|;;OE5OW6L*H-L%-lrwzzy z^EN&vrj@L>PgeZ9;9ya|m&woGylC~_p38dIB`@n|v*PpRosF`J2Ih0#4)!}WdCoWZ zMAsYpwqQ@zNU9(GlUJ&kplqlrNPLzks&z7JW!;0BtLpA)6x+a8t54d_jKXBU{0^A+ z$-p)QAGqS2>x{mN`+qN%bt*btOqGl%%P%=r+1pxHai+3Nzr}vCa&s|OGu-y5hNV7m`zDX-y;t@ME%%7+%fAyZBe~5#^nFG> zefCvf=q~+koEE$lq0se$ErK0z58*>$L*}Yqg*)I37sviBxCR~^?m!L|JeT)XCdZ6P z8JX5BYhpIHexPHqAN5>94n2GQU1TCW|c7|wxanzT7Dn;Wy#l>U%q@x z_!<4H*58LEXR22l8d`4HLm^k-X?O*-mUEW(iThNkitY-!JNXS(H=CQ&r{lk!b2=J2 zHtblhMO5bbxW);$(U+cM`gD`ySN%7lPshK7zr6eSxOhc*2is5Q1)K~HhtpUx%H?%= zCg78*6@Ral_A}-b=KpzO;CYLDUCHi}&(RN3`_vuW;sF4yDZAw4G;WsIvZ8S?v97v8 zy+pVu!g8x!gjHMeu%xOu_;*HO@n2o>vy$4?eQG|`3^iVMj5K9dbK*$ z@W~wSx!_IlOmqE;xP9|o3*2La|M+^jUBMW#icuhU#LX4QVs|Eu&CaTSqPZn!cFxe8 zVQq3-zpOVsvtH7A-8UA&ocA}e{!fY*3if}+z9)QJn-Be%Rs8$!psGF9b*(!r+s)~o z!OlId1*SKlZ?2u*AD%i)4ZIZEC!ypu35juc>o%!#ze#?R?B*vLHmcvQ@wVi|c!hkv zdKz6AUhHY>x^C!PcBlGUd3kl;isfe9Fx_Qz{q=lv=Le|3Hb)Ofz+T%PRkN=8hOxh? zt#O5YP#~FFhz^k5)~2R3&MIo`YnImfUfa5DYB%N7Kbdto_J+JI`w4T^Hbfs)9$%39 zW8v4;KhwUC|2?4aO;ujyzxJo@&Ou?Q1)UB1BlVGk^j}yTehODJ6Tk+*;T*LXtY}iD zYEqN#$Lz~MQr>1*)3eenDM>L%(rkIP{5`sjE5H*19>;RqQDg7&2UVwvPF7kA{;lv7 z&8bQQRl8F)<7z^d>Y9s|yXG;j@iv*)?ms}BhQyqUvNf9Ylw}#G8}(_vsBOzm+77Eb zNZQtFQIs_&^?B5D{x@=?_pfjtt0)?Ju6tj?V{cR~}@ zeU*syiuImI4JW!cTH4rJRo<$(R(zzYaS^BdNlEvLgz}x07mY5x-a5~EAh5`73g_Z8 zkzOc=(_Y$Lc2}1Z^)h8tN^b3Lb+p-cvYuu}*J+zg870ofBS7ouKw&FjfXLWCOYx-rPOeeayMQb;%rX`-6vZKfH}EHWT_l zknXq6QPq>GUR0_ooD~x*hMC&cw6oo?w)fn(XM3^&QtuU4wd-#<-*v-%+gm_jp_AbO za2fj}Z?fQ*rkl1bu5Hrvba%@B^ix@_(; z_Wo6tOe&sNwXkxFVXR}c)9-xgZA>SJa)Mv+(a2yT6gmRc2Q`x{BrIMdU#8BBawV@! z)2HXuyOeb}YfAcn+N1^cGbj}oGxEb_O{Gg-oCob)WZ1E{?#$Z zBX@lBeDKbsVo#+f)xKZ3Oby))+cb$} zRwk53MMq!c9wb7bR`J!at)yWYUCb`n_;VmYc+4sOTE0Zzu%^s>&bQYcac=iFV9dea z-fxlr;9EE+cn5yQQnTc&C$h;(R2zwHoV+&maIMvKb27fCE=b;z=81|1bx@07EY#e0 zF_7sFS;JMoja?1mn)Di#ha^32NgCeJ6=|&^mbX6 zk#Cq{jrXwpr#uNEDcp*X2YJK|)v~phgy^?wr4p6O|QaRc(&2-9i z+0xFx$GgULFqFuwhMrOh+^Hgu?73`U6dCt6F)bxOeOT>;v@4l=((k8CiHVODXnG25 z@Ofq$DRFi(H8Q<3_Ak3ywx#lB^|7ks2D>%W`pY@MpAzWk5(RkV6JLVklJ9(^!ac+; z2sWnjBQx;FoVjAFG(+=GqABS_T3PnMEMG=ZM&s-uNt@N(Rc}>F_R4@Xaz0pW^;TpX z@9MW!R+jw%(;}^_1(q_i+q}ji3oLN|YoF(9OR6GYeM_k?*mi6Pv{7(Ox>`0wwp!Ob zx?6Pbgtzgl;`_(uCf19sr_sopsuqd6pmI7w)8RXItJQ73XueqCs61TVv8I3ZVPmr8 zwrPMf!FR+Ham)?5BjJG0jYsM*#i0kG^YCLZLA6l8Q!S2C$EBv?>3wT=uM@5}CNnB& zTGHXfG=&*8qhFyr9#a3v^x0^s(EJ@-`MSDiWgC4r^A77lYq4*!2XS@uF2(x?VAn7I z3Tk&`tamIO0JZ$F$PMvNMMHorZV@*maZh4w8mE>%QJQc&^?q!bqDtCFv6nZS?0~-w z_xHsbI~g_@URFITIaxNStX$=vjR)6qm&`!{NcdLOSNHH@x>S39Mkg4y2ky{f!A&iKQ! z(%jeC&o$1S;C&Q&?*Hyu;@uF=^=`5z`QC;$2l{)Ck~mtQ^@KBBc3=A^YHPye)OD%X zQi;qBwaVkW$F7S1qB_aF!xFH1lMcra5nhTAXa5jHfiBEeMTxFwOv~6&iL4ZVLS-DB+#qp;_J~xf zw2NvZqr-1-O+asnF)lX3MtS9dsx{R-!(4rdCDn4wGR1MoJHi!XyY9Ok=;%A=>=7=d z29i5S1!pGM2XjevA@+4ryOf5ROKXqIIGBE@cH@)-+9~Q()l2?l0>#%w#=2JNXBi$D zCRR5q%dI$4J*%pZ{;BbdDcO9-eZYIkl0$n?xmtHY(T|l4T}4My z*IlQq16}`EcUEn#URg7}W?40E=w^USCH9%_lOQte4oxIl2O|FVU>SEBeTWWo-wNcs z4ABqyDNT*4N%V^tCgxP!zQoL!EbY6fE!x-8E}S9!MXX8GU|(FIzwejDVr*=@ZCqG= zwkBA;LBCWVZxLH&nD^PU32p-SU(opwpMnWX{ zRk9}jeQewKdNBi~5+Nck<4nNg0;3}~?+^38poY1_{0Q90PP5r8w#7Mb+7-?`U)|7e z_eakRqCRmqfCeW*ci}Xu0rZ)719X%JN-e6B+U%%far0u5<9zXC{36|bWpnL9MMHix zqGruwngkZO6z&Q38-{Py^6E|1-tu!*V=HCV(fTOMYRg(nXU~T~qtKsVGTjC4Y-w=U~6EBxdP#n=Ni5?g4 zNjRVUJ$_kSUVPTo|( zlb;gRVf{o;B7ez6f%l$tUzX##CDZiFY%%^dMw>dAKAPv+?mGfju5*KTRp7px^8HJE zAhU@IYAVKcAvGMceS>yH(k^3H(3E%{-CL!ak}}Xb(V9lb)Efz?@sWo z?_gj7=%jRm`RFlWQz=(U$ew9l>&&_)F`VeA=r7Snqi1QFDQuvZvs-ux%pc!pzon`0 zy2uf*izCjl#1^uZo1TMO?l?mi&=>1%-elTn|KkpO#(Ipw9u&khq~ehJyeYgZyrIIa za*aHo*rfWZ`Bx{6Ivm|xE7cv;Nwj4WspOHE;QfU7)KaP5uItFl~vl(AxTHs5dz<<@9>`ZXnu%EI$wob6Du(Y#1wMW}W*$=vT z{>T2a{`PoxCJVAaM$E?_C)gouE}5n1uNk)`-@y3Z@Z0*zQQ;Zty&CFH`)kQZWu@ z*lB?Dv!!{%JvKNTm=_mFNV&OcgcV8pTR1x#5dm4-gVZV zXCtgTtV-)>bG~_tb-4Y3?TWq3{oVg9&SM0O>=j?^PNpopmI>6S*3QNN;U zMX{o>sQ#M2^2hRf(mR4Ptas>pxC>zl?DHP?(6)1Cy$0DcRpLebU)+8i z0y_#P&|Rsv_|njR-(>GSm)Kd`w%<}=9&TQ5?rhp%_E|KJLWkeg(O&?jadRm)D&@8T zduslZo>rbxH_-IaZq%L84b*kjY*OVZWr`L6UA%#}h;ss&PwgR^;Hd$LC**WHHd_ao zR~tJUDZ_EYLjwisFFISIW2CdCe?a&@{07BEMck(R8KS+?T!5z?qzP))=*H?KI=5zo zdcV56GDF^1azWfwc!sxweG!|CU`#Di7Woov=)>JXd%$V~zQm%>t?90p>IWP88daA2 zw#6>CFD2BE=nehEPIBf57f3%U0?LD$v%1q!E2AoOny6@j&*4zD0^cwft{gBOQJ0qb%2rZ}r3UBlTkeR${$A(Tv*M_MgrN{;v@}^@rKP+AJs* zt(UG*%+L(gF3>g6J<#pYP0{vO2Nc^Bn`K|codkA%H=c%VLmt7~q4v~Nd`W0XaK7(| zd%WYWZJ_m&xrIq>yldKHeq*_BUG9{53j@{R`(!l2XRqen7rc?|k}p@TQm#>*RS#1) zRNYd{R1h*o`bMM?Y6RzbbJ+}{hwDSh)IZ@(K~`X=r>TRn{wP0Ob2>-dAy+f~W4UsnYh2&WSz{^+s#fG*oX@($WV2$5O)|&FO?$ z&@ISwrXIy5+YlGSzJSq}=GD7?JKk91E$7TbOsU3}#)IbNmW8%h=W6fQU{l;eZ$tGQ zx4Mq)lroZ;A_PnOKnxj0a*dXgJStEKan8xeOImN0EdaCKvT6}O&=xgRK zbZ|iDUTDnG2dm?%@6{aDZ!?au#Mzg+Px+@sI#7*~8|=%1n<7khM7dqf)-=*Q)-Knu zHB*(V74dSnq@!q#Aceo4yNNv*`-=_*G4DS8i@HF(iIj&9_yb<2yN7csSWfuZvc&wr zbiy2OD+ZF(`KXWqzrb9?R&o0Zzeu_$)+!CEkh-77t(u@5qD+y?B_YvX;VIr&P89nE zmWgnnWX4VE@U5YpL5H`iYml?RCbm2>eh0bc8N*a#wYkV@w|Dhm!KIO&)Ccq$JHme@ zu9Ck}!siFq$`+r8PGzV332Z9-Vy`rb)qV9<Y1toi*8p zf#ztd&;HO`7+gj4VfM1V@(P6gq@$HTRX}>$X4+<&Un-G$j)EhvlB7sbQ8xc7ZwEjD zeMJADFfy6pQ&mLY$duqA-*wL;m(9krO#$(DfvL#6(1KZqI*Q!G{PjYs$Ww4RYXY}Y zv`vKWNt_{!_{Mub zI&aystq(wF)T-ZV7-YC)6j_%$B5q0G7Lf?oXUX|{q{+%%>K3}{XePQXz>D3}Wi~+Y9#sUz^BC=qeWGmWdiG zx~gue0qQ@h3~aKps$VNpWD6yu#pn5X+yG}JXFO{ix*z+FHUa(dBB+#}L_jz{933e2 zh};_-BWxWk&8)pGU(MI7lL5|rwkHxAL2{sV$P8|_h!LNW_EV;*6{@|eA*wuuRyJAY z7G(-r@m6pX*c=2!+9PF5grLbZ$_wj=BavCYTD!&DWM&u%BmlqAc{N=B2V8LCOp!36c&Z;x2=nF zsP9lXj&Ra6t0Uhk{2^(hbg3SzjH;089?1GGOC6%7{8;X8&J*-ER0(y433?mZmNwA$ z$S6FEFyq|NG5;A~UysmP-`>>P%Y4ez%#v)enIG60kJrCH2-75naU*=Ubg6o+W}~(& zN*L2Rs-EtbMy^Vef0sTLwc&BN1OJCkt{#a`{ySUc)B=}i+9^8t3UARxWMvg02YxUZd+C=R=O;c3|#SWQ6)K>77 z_ny0m)e3!$#$j3TMCLl^qPL@R@l*KN@L7KyPrzAdTW*F;5`&=TW{t>r*tppwakTJF z3YsDv;3eF3f*#^eigFFCoekooL%T@Zu!IYuH*k|~NG$M;pTvaYt z-%wpwHdKs~MZ|njIlmr9kEt*vI+GbgH)3KLDJdixfoQfUTni8>9q}!3i=0#Jk1bEl zbIhwuBTQmzo`dB|^d1XeWL6_}*dIjC6rEMeHA|zzF}tESMV;3X>Uzp$z-zzfRdSYb z3}_is2=zrCGM~wrbR#H2l94kJLu6_I^;grk1)wNz0l^wu!$8XLDlN8-e!KhT1bM_&??{Q5@+` zw2O=gEcI!9zg=e>D{Vh5g(kh}g?Xka&Rl7`;%?@N@?R$M0kWk&=eKA$!1FazF4q8t z2lWSK0BpwjAl@%B@Y30H*l*eQ(57H7@@m$9U<1QQqzn~9U8#?x8&`*}`$l+rI)(N> zR>aCNJ1t!tI@b$V=fJ;YG?=K9fN$OmrtEtw7O2OnnksV@z2tu-d~va0D#yseF$}## z%jo4ybNVrU0)Gnj>U9cV3!Mq~4A%88^V&V%T=Se;9I^J-wqcH+PMLF&dq?0&9hEQ+G)_R?|-PM&4TTU9?wlk<$YUqm`(EsZZ;eZqO$(jrfn~ zMihmD{y)AUp2g1jHj#ChDc#i6G{iIxpd4Ge4|q!h0pcjS5wH*7;)lv-s)Oo@+IqU* znun@I03*I!(g+}f-m;&vF0x*s|3W*U|K$zcXgR%r(NK->X^ge6eT4n3>ux}SzaajFI&)SD0zyo>Tp3mg)sT9S`j_&iVvp>LI3#E)Sjk(= z8jA`sC)x;F&Af-&z(1%hBq#_HC_rj14}9{*cwRd^B`}9gm#VUT%12B#oMk zT;YXpTexzqNuBUFK;62FTN^##_i0R!IGjc0k2aUltGUm3&=Lidg?CmfVfUJ z1ek6l_}o9y``J~?X|bi-#@J@so>-0cPM#iqb?^qh1(vhxbGHkR$v3E`sI#=4qu5cW zwOs(tcE9|r^t-5-XJXf7_rVgNL-Zado;gc+a3#5x=ohvJ9|vvzy50)cU1xjyUR!f( zA4{%zj%~f8o>S*Z4(=z;QOU?#PCL<4@kdz$)nLsMP`~H}ki+$rGr-=x2ZBZXe1I3t z0jTiC=maR8fdE(71n^$ij8{d*g$Db7dZXP5&X=|+)&b`FW}W4mWxchjBgs1`Ff$w> zC!-BG6t7UkmUjb_=%-Y#R0=THa7)%wk|*pUNa8hOAHfb_i_sf!D|j6;8yOE7m@ZH+ zrW1JpZynhmg#0HwCtb4~^&HFXA8mE*_g$;KmwdudDTyIF(7T);LZh_3?3di5?4Xh< zfP1dg7et zjCO>bWuC*{Exz%guhc8h@hoKz6b%AfR^KQ*>iHVBda?Q+6`}YeJ1==5c*yGpGM=|c zYq$-15RL-71j?BI=u(`E@5c9r_xaCyL+*{v?)KB*wQ$~a*wW8B#=6pe!uvhgH0&bB zVQ+ZX`4`2xiudZqYC>(*_|*MWB85=4O>{w6&fCVGh2BI7WGLgIO6b?j7;+OJ&PxK- zvV$RQXjx#Nuc>FDyN)xNEI=COJD?eWNQgjsHq8llO z$@An~*)AzZk|P?)e+-aTt=Jy)FVYn2fQ*8iOeHjz`ANbgM$C`!gSY(iyf@rQF2BQL ze`%lO8tty}Jn~P$Q|KMgR;&d-T3jT)D%-3$sWi!($nQ%Ri8~0Tyisfp+7JmdJE$_E zk~~5F#4GSv@)vm#zku(;=i!p@{$NSK;``>Ui@(dJD5fb%RkXUf`Yf1Nm?bNe`~(PE7l*|zz@EVO88w|seohEQJw%X zvX!Kj=rF&OBVj23rgJf@gyP|Rs4dfpdB)skEToovj5m(-2sZM&Jms#w&dv6>w%^vJ z_R)^J&IRsy{@&rPIEP+~6tV5xqk?r}3&8Vn0BW^T)=uV@tPm#w4ERxA2TmteZ_I-p zfZIc#0lQp3x{5qaI*1+kn=ly~7Hk<9=G)@!;jz2Nd0Kgry(@jr;G}R5Vn6LgBCM(0 zLcwzJ3#n7uO1?wZMutd^hywf-++Xb5=u=q0l+fVrNH$?2E)x&&V*Ct#1^*si9DW^S z1&e(zyrV&u`NEa$`sqq^-}UbH&kwc6N6;QPfpv+S3eZ75X_UeN=0akXixeH@)1?OS zcj03&J^6zj%gRP`;Cq0dZy`O3a*@->OZbGykZ|+RuE1{JO|QpoaoL^yoO;JaO@`P=9o9IG zgg=sx3wjHkLbdR);17Qf&%k}ddCD%ucCb!k572UC3c3l+K(4|AfLGBl@2EVGLkx{n zho%PK_!Ir>eUp7nd|Ul1gLgyQB16coPyn8YJ>pE|y9Eb@UQwbrBx*05EaTOJp-@m7To96l8if~x zKLTsmO>^KV>>0b7vx6@ZHUQIsF<>9lPVsawd(uXb!7JsKuy3&XqTSE}cqdecQ8EMQ zIpi7Q0Un8*36%%=0l9yucdEzahCxT%%?U6r7d3c;O`RXi2%G zvGj;!CCEi43V6J9&Nkq)zrpnYf1#D0LJy)dnJW4Ty@6TG3}jl-t?2Wlm3R@U3D*TO zE%Gn+W&5UhpZm`GS_Jk4)sf#gpSsUC_TV5v|5c*aHu`tnck?3^*2 zC#+=FFti`~73RQAq5qgv#z5Vma!4sT99IDP%Ld_uP-SpauuiZd*e7%)yeN`OOs71| zGk7dOig)GWJb|EG@IU}iMErrg)tqA1J@h&JjafxYs1HP6q6d*dj3PwjaZ*4vr%I^P z)I4e`nLvKT*F=_v7l&2__Xj=)76%3dB_UO02!4l@GezLG;dFXgJ93GC3AzD$->BCTGq#Xvc|K;4_PUZFGHvj~x65dv> zjI)yU08K#VLdWO^6px%iOvXFniTEnuN3w}&L>|$KY)RH9FAz%z7v3B16iJHQ4xbF) z2~Uh{#18`u-(A`TJwlw=W_EAxExf7IU0#M7O3T05Lq6-bl})>(G4q3}poR zy%tn8RS|CZU_?msXAgG+cMA6iXDz3I z9bzT1%wT$FEt-UGMMOv@Tm@KQvq40jMU5lH#Av)G5(>8o4-9V%b0c#iFL56+i>jdu z0apAYI*+xCJ&fbytl&1`=7U`s>)HKTztE9LTR6;gr?*lU!O3!nbRv%E1x{!oazH)$ zAE4b!sIk;<@F$PVB+bM`;y#{8o8eOMy~=j_XlV{7KhUrOo7hlYXr3f zFZi&aJAV!@iaVVnX6IqUP(D%zb~9||EXAjA!1K}u?-&7Fk-|MgGlK(zx?r22DR4Kq zJv2YOHS!5ROlH!l&=j~I>cIN4>v94BukGVHxtUxWr!i+NTg3u%hv*gL8*GH^fai1= zf!|#xhyW*ep&@P74mkN#ty0E7)P|DHa9fxf02P*T7|fS#<(4n8~B3(>tjcibvfc zN0YY+7g0uFWH~W}WK%b(sq|O60iXn#G#Tdd+IhlCd1C4ne zvW8F+oA9BLpWzMR%<$*X#E>bJ9ljhU!)iR8s7H>c7SLR%2YdxgqFu#4v9_^o>^htu zoMW8zoWtzC?At68R*V)R+d=l42fb$=F?ARxkuJdaMB3k73wyP$wqQOmqnv zK>T2mpay;fYv6Mr56oeD(?;q6*_H#&Bg6Se^4n2)t%tS*n_$j;-aiYhucPt~&aUJIxCxz376UVvA*0RN{ zFm?s)jDCjK!Be4+%zgSg)t+ieUL)dh7M=vWpFUI&!b1(i%fsixBK$SpnEXvH1JqFs z;8-Mrq+$l_B5M^3X8Tz_Ruk3}Yy+q&E=PAEE09!B?MLA^(0ynQ;4{7q*n~}hEx8X< zo0-HkV=8F@-IOi`w!fHc4}9V)Vmk2#??rHk5rmGQi2ulaRBxJ~&qICT*1!wl5@<5?ox$mL%p&>}-HG~34Waf>zsN9ognCBx0A6f2 z^MWyfYM~dNiSW^Xz%1+wbSnBBJ&qhg%HiGc3}AH|7z^mif2UX=FZo6FrKVEbsOy09 zcL=bI>mX7tr#6Fc`vs!$9O@JGhq6Tb96ObOdkPU&#S>)%UZY>;z16fFc!n= z!;-U0fG#d$j{_XP6ImTu+1O*WFCs(k0Ji7*bZzj;xkN^ha^hd&7@miF!2Cf&{3w1L z|A^lsN(epqoy?-g(np!6Ock&k47rS)Moys9Q7twb^`S@5w`gND4D{zQatrao!;r_Y z9R{Q#up4?0jfP$VX6hbHYi20jk#JIJKwqK@0R{LL<|Y#hn6o!R+aW*n2jaqm z;fwH3xD+0QY(r3R%F*Z*)QM844Nb%9U{dTqbR60nc&%hGJ6R4Gum>?y=#7BqIR{i< z|KR!faC{EPkJsQqygP^(b3rulPOCxBQE)6uk&6spnEUr zD0&R_jx-TFfZxg?Xk18aAgYKSPwn;0tgCd<9NK3gK#aERcIF@GAqMr_4vD1yh$fM3>X2!EbnpzD9>= zC*6!Gpf$`RaP!%8BArj22e#abIt*?+j&2Wp%19<1Sn6lJYdx4Atb&#Tj`jD< zY^D(t2AX<@zCv#U)&!`O=?8!*{2l$0{!H(u7lPBMX*=-HXQ=JeN$|IuN}>I)dx z$Aj$SCiEHN0hw+F63B-&K!Qz?G$aa~VGsE3Drf`Xk=HYuK`es-e|iDXY%w#0S%2Bp0lYyghN4?D-I-$y&a8#ZfLHwv@Erp|Pv##a4&3-IxE=fn>I?;# zodA*YlFkJ_XA3=>J_cCFPtmu)zl(uA9RN4oA4Hcz>MXSk;54>UyQo`~nBGSR=~dvo zpP;VrHMk{+DY?i!6nU0sJCZ>>w$Ow6e>I5`wJfi{`mmVsH5@7`P14jDA zP;^ zl|uzB0dtcUz;JJc%AmvlZ`V=GcG>{q^?kA*$iy53hnzt2sM%CJkdU3Z2;~5;dkm4I z7+N1KMJ^!;$SDw!mqT@-eN0av%P~xIAhVy$Dsb2Tz&>~uh+Ji0&m#+^kcEgE83%5= z4K$mnrt5=PbsXdx&8SV(PLL-NR3D&IujvL%LuMqC23}@W^kaG}-G{DAd#HMJ4!sD- zV>DxA#(+N0VK@Vs0{T-{1o>b65U~QyHi3G@E2a&DFmC!k`aRu>$z=r4zu?SfxC^od zF@UptLi!*9AoJ_M`)NTY_!@Z6Qjpgl0J&xcux2fYHj|i#%zWqtlnq008`unPd=eyr zj)43wjyX$jrh5VJdxyq=?h1e&zW{#yDcl}Wg8!i@(i@!Q5}X3x2i8Y1cbWRkKJeSj z0v{bT4q63nyCZx6?hlWLrQp0RKn_zt$AEd@GO`8vh$tlr!AC?|$p=(dTEs{o6lkad z#Ec(EK8VyK5j*f{N$@sE2z>&6dLwh0A(-2sFVzM%!JCk3tPt z<$Un6O`*kfcd7vuBv*kfWFu8c&7(g8U)G%&3bL4gn5|3{h%|5L1$1LNNVTPHR0Hr* ztIKp{ikab1KhSmUhS-n>=u>nsIv4eV?H56KD5!%x2R3^Y$ngOsr9;5;%%nRoTxdF^ z1A4a{Fr1eo?~#*8L*z2Z9-^UL%pcGJm`@F(Mp9}j0c1e4fp<*=Z4^k1N_Oa?QX-a&=PC*)gl7&(@FNDifTQ)X%t4Kt&evCI?Z8uJ!Zclt9{;KOsl zJ9rwsf=&n8b%=?B65y@CW1m6lfG9WL?gFb>KwYHP zfL`86V8v$O0}J61AU{42@+lRv4z>baJIC0-ds{*u1KE2w>I;_9(XJ8mL=#7 zv>0UAhfw$bHFg&8R#e^JpE-x_?o_%v1Q7%QK@pKOkQ5P6B&C&5L8VkeN=jN3knT{V zxpWHBotL=hoSFB#WuP^#?REcgPSZiy9&Qo- zJ2WzsH#{x;LpTj+Z*%TCt=;=>U1EAI`lI{Z-R@UzdNRuxC*aO@);g`+hE{9qq_x^! z9*7GR53UFv4(<$A3Oa!@{LO^p)-Cs@(}aBE^~hCn<`I$3#Qb-izSvt0s~8qvJmA{3 z1J~@0_Dl9^VpS*lb(5XS*v|;EqCRBsN3orr*7NoZ@~DmW75fLfk-Y&;df%PE?@W)5 zB!~06YgH; zT%=269{cs1sHfb}<517=OH_#^SnXif!N)bWJ|dbvXPqS86{IFhhlEZ!-Ko?P;w#(O zCGpixU{LUEa4k7$kKnyP+rUrQ%~m>aU7RWS;+XKk@HgRk;d)dMo$!Gd-Hlc*vbei; zKJte%WTlQZ(JF3Dpl7g*Jm?Fj0Xb1;JfVeFcDFa<35)R;V^Yz5D`)pcld`%^ogR_P z;f>)A;bh@@;oIypEyW&3%5JCi6_V^17#Elr_%iS!S<*7Q0J-KS>l?JD4zF44gmzaX zI3UnFFqu4QTA&2}EU{gZO1!@_Dl#^lGTc9ODsE)l-*H((l|!3CGs08wSOK(eot57n zY2UVkfv|nXUT9abGuZ!H9q6K4b&KnR^17 zH-Oye&p?G>2W(~;RX~#9XZXU0tn6*KA71;F$fa;^`YnHuQ?;V%U*~YAs5{!qX}7n( zCp-Suu5RDqj)eTye6pCLaP*i=_$#u9^44}Lg!Sal?*@hinv>hDrPn#o+Tz~D0vfYh zP&>Rl^cTLhbht;jZsc|Jdp0}`CCh)C4Cy9W!DjsH9ebSJkvSTY6@0@!aU=dp+W=&^ zjflD{u%9k!mq4k&C}L%1E32Ev=@XeBo)bz98ew+vV&c|>I)x{Ody@J0c27|)mmq&$ zN5;9!9*k%C5U>A>mB21Z#u;lZu-_!_n;U2!d>A|${5KejFDXr=JY?^+5?Wc@WX^?f zmvEBsJE1diW!MAX8cNH4@}o#Qe93yZvo)RHc+N~NbpSnk-`YuKyV1)Uzj5=@W&6^q zZRcS{y94!tE9q=?3ziLT3nUA4BKx20Zg#3T-$XWr2Zg_-$JHf#GyHp`4moT)keqLY z@NyIEui&{h{&*01OnSOB-Q4N!E@rWbKe^DIKWy&o3pD00n&b=oj-MPx4sgIJ==6`Q zhwF26ffj^fLOt-`(<4otMs7{K$q=%K-hm&0mPmHzNDltnIX`c{`H^ithaN9P!;cE7q3Nl78&eEku{MIol@>k?sHTpN$F?2 zVZUf+$Iow}D$QjjwjQ}j$<<4sgIBQmZ;5#=0=)tw0yFWHH|#ce?h90IZ#y5Nfd#|6 zLvM#(3ylrg;rHk(xRDrVjN657;X(9vX5cIp;4-R#!hzLxH@hr5mQ$^z*y$N7u|0^Y z{IOjb4?HT+GEgpX(5_3JaM?}n7IfMn$t>Z|LbXF>LNCyD-W_Tajt&1vUoR7RWC?4A zMd)Ct19x^kip+Pq5L4z^iR=dC&>w?K8v790Yb`LjPrj3cN^B6; z)80P7+UAi5=B3hl$X_z58TggFu7)+hUFIZo21RT-=HtVY!fW7pNF;+Z#>qeiT*mqs zJqwCQv*M^XMw2=8v07S1iD^r%ORQ)(5vK@Q!)Uzg=UC_&yPf@$^?*#OoYN+9f=XmU z=>5=Vp~<0%q4MFhkqu~fb?!bmPiHI(x;>k$emCB`D7oGi^zW2)j(lVkYsx`R`GLI* zyJ=2Dc$M7$y8Q|LmHPPZrA|htRHSn_V|Z7nd8k0>3$pJl*k_YSV&^OJu|sZiYNCK$ z9)w>e0yMXakhk~8tBfWZ^Z@OD@FXk1dq4l>Ykn|Wa6V|4vMX2}+`i7|k&Sd$7ln$1 zvV_X9GqX64CMD6`@9bCUN?#-gy+SXeD3V)*4J2gms|eEA zj`oMxiC9hL)Yw@NNgNp!&H?=`p*^A7$Z}CQb7VoJ0@*`z>et$EdJG*Y4vH_*C1_`N zC)3|yry)O_%U`!?5F8m?NJg?e*pt5a?}0#|JinJd+Wp!2BjSc@g;#}|(F-pTdLy)f zyU`AYKc`O{ab^(ZW2|ym%|v>TL$J2&M3b3xkl)77zE9LyZ`nw834MV=Wbys+m(>Gy zV4hu&4D%Y*+X}MoIaDo~=yg7Z<3^GBk@QYC=a$ogZc$ol?(gV#tOe=6tQFpIjEXuf z7CM5AI9;GMzI85MVWL+Z_NM<*!g|-8OGiC#q)#{yo{4UE3r!CN=usADXK4l&`2w+K z0NvRS=qAtL+97D~BX$5ju}g#Kad5hd#TKN}A4LuH4>iMix=4Mwvv(Rh$vw%`ia6uh z!;c&~x1aouvG=s#l1WW02iHb_qIuivrgJ zZ_qKE6r39TG1!XTlkWoekx4IW0bR&Dk+NhxcS93GUD(Iy8`>PI0owiPs-DIRmL!|Z zh98+qMEDvVugzZ{`;*u?0DEtP?f+~YrpkGrZ_km@l_Yb>L6$q2nt82NiAWi6hqC)H zDe_jNNF;kCAGyT1$Ul)P&JrgFUH+|P!A;3Ae?V&Mtuz8t0G;T%Ji8=qupEVARr4^!#GeHF<@-ZPFn z4cgO>+!@Xh=@i))sYg$vh&$N5?!FA(zo3gL>pRPhj~o0Tj5DokgIQ{XLH8s>m+uTM26GZ>K5rq zT`ARnXJ@xljQ;Q{_pV!%ZuAgnErj=nXk7t1sy}1fH^`j3gVzZB$QSt6Yj!_!%U0~M z1>Bm>%t+4258<5bklqa4!ISh1C!s4`f@)wYnd@=7CI|3CEdn1C1z$r8VnOv2I)=~L zH;I#v*fUH(%&29zq*u~{j$T=4|6;vtogvGOP&M25lUD3H9$?SmI=f%|6^Y0$y0IOd zLrC-odIdSD?`B!2$^XjWhxnTi*y8*2!%tA}l_bx8J6I-|obGaa`efC}8_u#f_<{2q zS~ojfF?^8-`G8vW9XgI3BjLz*c>>W+mX*AndpMGqw_hQOllMrNG|l6zh_DB$qbak>g>*$re8>vW-pduZdG1yK5=RLZkVW+-36@OBMNI#3nlap*| z1imUQwZR~w#jQZDU=I2iNrM;JYbqDmN|is8?pPgn5yX0%-*+JP$e7_7%n|a(Mi`mV)MO4R4t+ebC^reG(ioKo6^aamGequ*u zF)?yM&Y-ctqUt53I7`nEB2 zu*xCc8WtuF5`FEc^4R=$R$7zMy_;Okdytnav||kNfzz3201vbon8^`?9r;`131~ zd5$&Nnq-Z`o3*8%mKtC3oBI*HuPgZI{&1X|-($TUIgAHP$(~keI&<@#qsX*5c%O00 zlA|ppgKvX9#?W2=h-ztifdB94^=8Eb-liJLU{9bs`z80^e(Ky~Po^GS*S+Dh;agbb zE66c5cX^$mH+8@*O%%II$E69?(r0+69#l}p@p=xnxXhY@FPY4~g+wnknIeoYgd;2sSSAV=gBSKLp$EX*6WdJrpABoCkq_P z_wS$?CE4rC0m9jMBt@1fEepH9M`p8<4%-y2Q(6ho=u=ok47R-2 z-N~-kIwZD?SlE+&l~?II-i6PV&J1S|J1awpI^*!(H=V+6FY1PL(AjSlLJwEjNzv}N z@FzW~70R>cu-oo~UjB)fN@$I9YqC@DJvl-F?kPGOImK}QFN7TteXZCl{N@o8Cf{&qI2hY8k2-ess z%N|%o?qVs3pX!7B)^R65IpXM8X3b8=bvRkbIri9JWC!3?^!rWnkMwYTm40kzd`MyL zdfW}7zp&pr0}ERPujgEvMQ$d#AGy%>g!tS|lM z;JYL6J(e6IaUc!eBsV(`JLwO+#s1!0ywwA6sn6c=9`>&LJMZ96Uvl1H@1i~4X)gBo z(8m|J0Vz^F09odvT_A}5HUs4hTF7ZuIyhu7S;Z^9tP~yR7Jf^_=->mr_ zu{J$+%KfZp&?>C_RXS~V=)OGUrHNi4hkS(R5 z>aEWG8uiIdDv|BovQ`lzo5I&cMoxA6;W2vQ8NP$-b?y~7DL|~Ri}u&C%2L6#S8Ff3CX3LqC3w{BC4954==%YZ**xdk}in;4k5R@LcWt1`*?#ozK&gpT4x7Y$VcpAT>!^#xS~GywbUW< zeL*g^53ZlbGTIO)`VjvcaR0+YI@p8I?^x!Yj*bk+3$DQnR$|SUs03#q(WYdZr|md$ zw=h-O8uHIF_8GFxvev&u)MoTu6S&8TvKyVP&R_6+7M)H&Ovq0T{|d;-+$+(^q+b7Q zBL3+fRY)E5x*2xa3}0&5d#D%QBsLzv8b9T_%B;8?KBG69z5`EG9KPGL=7H1>b+~)v zI`^Elai4Q9gVQLie;`ucz`V84v4lkBt?V`Qr3ZbE>@^i#v_nM5HrW3r^41*IZuIX> zt`4Fn|Kdq6u;Rzqbb5Bhinvv%yIYXk_H@VK(e{E`eyZ^=>DyhW1}g>DuX2>PGufB1 z$!_fX-NIWAbQ^JRL?N!P2qpvRhF_+3d4u`JlHX1TuMgPSc|;8A!fx7SYRa+jJ(S=1 z{0m!phx3`x?$dNS2HLC1-0qTjA0nIRMjn2Qm6zp?oGD1MEWYSIS<`vvhV#ftNXAoy zd!lMH`+L|(Cn}WT__NhSz5t!9w#>ejxRV15d4t;FH6mqdYOtATL00RqJArGe;3qO- zGiA~6p-Ad3z3i4$JX6UO=g@&`!wPR8mln)*l87_h=|{Aj>)^gK z|54}`STtwc0Wy=XozKyg#m*(CEV1$;e{+8kx$@gsqP`u}Nc+YL@cW#95kBC2K@xJxIYX|wsV7%@`{MjS+ z6=yI@2iBK|xG;sj!#=oPLR_vv_OpPDEhT6VL;p%aKP{S)g*!E>qZ=L3$PwuCIO5}I zG~{cK?`a@;n21-BUd;F06L!zaNPbcUvkCU%dp8x zvE(I#$XsLHMc8mTPQ{2kkD2Q>v7Nst#~$C;Wbjqc=$!B_ z_vEzXsvn6$i&^tVyz4?_`~#>9`m@0M6fv&?HQx-cUzZ1eQkEV-V)DQRNFk4Pl)a?J z_)d%a`(p7)MX~vx$gc{+r}QVKqk0X#$I~HC#vN5%>E2%?6CA_+p;9s2cdCKVHTO+8 zw8`#!!}l!e?=RRvN=GkjFmd)e*mT7vOCr@G9@cH}8)G@Uksj_YJnnj;?KH6ef%Sb) ztX_?tCI{6|JRTxeb~?hPK>H5pk0GZ_!u_B9(6mfs)i=@9q{Ng@@t!t4!J$~=IpSX! zd)@}$4Y1(l;FOWy9a;p>-(#D5sEC_W52e6ktfo6(o7`tSy`CXN<)Zd={7`drdmq}> z8Z9Y|pDu+5c^69*t~Zfwo+hFnAkSY1_EXXEZ}BFx$we=a%fE~lp3e?n(9Vj+6(A43 zKwi?rLt-X2S&HAjxQ;)4$o-!U(7D}i25fRN)!TJ)1BYJ9TC$ibc)?kqmWteDK2d%W zKKx&PKdGy`jkwy2Dm5!{p%?YvAo@`+Gt1xTc17~cWuV&}Eh&NZzd%&$26w+wWt>J% zw}=xb@p|jXNv0vWk)S>YDO{z4R0n^ynB9a3_a$Z{-aG{DiTLKM+*>n}2$tIo)0w>O zX-P{@ONvo#&ZmDH$66gS>Qz>MYUFdERMSmJr*JX;WF8UcA@{R<$^4b@@Ba{u8Ue-o=)x@4dWH-njMun}4?Tn>&UYt!Rym#(?4U!M zlc+b$T7rdztYmbF6H>#kCw|r>Pg;eiuSPa}A1%3u%&T(m-S6C+@-jYmBmK4jmJqQ{ zQ8RYHDi4vHRH9PZMU-Da<#57DgB8!h_q;|#mHWzHr;pTzTB;N|#9yHO0vXLZB-k2D ztcWM7PgMK@zq^Th33M=k7&>yIs^H!=;8R`f9XRcB;&q| z#{5Drprm!l^W35(x4En9v6CHC2BT}~(aOolB$kXlnf-u1?R3thvNodorQLhvLd&tt zzgTMlJn(wPzektjF7;V`cy2_dnU1P`HfU!j^PNK+d-QGby5u>H$SN)B0P!Wo!DSM8 zT@ke8cl!7iz3*i94SsjvTXvLPaA-qRy$Rl{i8n{^npIinVK<-EoqFM(m5UO{u>r)Z6MbAV> zE}$V9$>$4Gm1Ls+yMixVfj1e;s>BPfcmKr;Rw2@jgYRSH-&c6vNdD7`t1cj!j_7L! z*P)-92qfPk2jAmq$wWGDcdSI<9pblA=0hPrcVUgf+hoS`9D?)Hj>8=gACpNsRKue@ zzO&(F^VliLAXb5PZM^J$`0R@mn{c#7Uw#CuqjU+VCX@OY}7jdJ=^Dir_K>EDEFhzgP#5O=7I_9^P;q zeYf<)yunC2t$T}#>Lk7C3}l}}u!eNxA3xy*A5wK^gzH##TPA~c5$i5~_ZU1Xa9B7xYLU`e6c)iYE zcKsPVEvELnhkT0CKaiTWI=!a!c*>JtIM?I*d$eVRdj#DpL~Yp@Jy{CRoABZj(3)Cg zItN)}yW}nGDEtFdk|L_Is37E+J=ci}nVHQj3_QFFwCJ zsE_l!axCLZd%tn<7S~lH`%jJ+jKPadhvy&AyG3AeiF~I5RwI1Rp&FTkC+W#LbKrTF zlXbt1tc$>TNvfSL;CaAJgT=~kogD!?+4bH_|EU-B1KdeD9RFK}d&N_eg%zbs*bgna zN@miG*gFqd?gH!ONVXR=a-vt;@Mt3#)f>ARi7dC0WlBG$8u7as$Ln~ye029N;RTnX zALCiuRQ%I!;!t|5_C4hGJ^pkC+z&+ys<6i6*uelWt_sg3LA^PM{7#1R9G+t&vGp(> z^BNw1qnCFV!!s;rL=&ta5ArF%6|K?NE!a;9o)NHDiJX=UCxta z;R``}JnNjxU(z@RA2}J<3cLJ-~5ebm_`4ZcmNhLRy{!k0hPL8+Q z3J>3V6{FNIYe4%keT{OU{0=g24d!*Qv-D(DNAM=|k;nviUx@9V!c*pjZc9-90v|jG zEShp<3aZ-`Ncj`AqaL2H37%>ca=gka8&W0CfZrJ6&JKLgXgpe3WVs99+k=rc;JhZU z-RQn<^K#8si3UT^!|@<92<+>yBNRtWT!sG_Oa3#OE0+*SuF`)h04`0yx&xNm7|sg8 zi{wTS zj99rIp10#kX23}kZ09a0jE7fQXC3@sdu)0h-pj>n)`#!U(WVhRcVXS7kRO>Km5Cw=$Y3Nlky>Xe&&&CA6@4m*cYO!SJ@DEeg7yn& zR4m!oJS5l^X-ki&2iCrn+&?*#+A;rJY-}?Yxd^#`f|g{WSGx)hx>H{@LRVU$F+bvQ z&$`Kw^qb@^ec^ckV_V=Wa+3o}{1c5Ghg4?4%U+L{d?4I}89KsAOIA>kEZ2d`-^@On z(LbXtYp|Ica3Xv+L)M)c{T`N3g1qD!5}pO$-Kpa{pe;kVW(%V;F}4%7`wMrtu0ltq zA^+A;Pl9h)hP->yZ+MrreuA~lrmubzZ&e=L+Ji|CuxL*K<@RA(~ zHG=v_9IY8!8P8*3lYjI5Y&e;QTvy?{E>j~skN#6NvWAoR8sU2&ob)F${LCuPU^OL?VlQ%&pNS&B;OmE> zhc)p&_lN`wu-tyEvMagO2xi$#hHQgERbt3{*i9?w%kQA1@i^H`M4AoOzkuX65PgVb zN-%#5kp7sXEvuD&dJ_6UG00{i{7lC-mm)>!xu*s1s%TOpIBEdeg`gXY2CZV$X!K(+ z-fTRp+(rG79^G$22KFsJZ90$dh^*q%QWIPMg60_FP9L!T3cLRujlGE$=7(kjtfm=S zUYl$>8=gmU=`|qu3)lRLMeakl6SBg}tgszue}bO0=Bf&wY%VhQ8c>-D+6(b%JMgVx zGOvQHt1j}dk6bEXGl}utG1%{Ha)IyY28`p~8rB%%w~Sr{wa>82G5EAmk)V-?E31nrx| zhOB)50#d5Va|w8pJF)*^O>5EWU(uyCV0oOnJ+&wG#-45q-ya|$$*7Ve`@^jAcTky) z1^))+gJ2kkeDcFjMMhPIa!#UC9DMHvhsBJY0}uSoMb0?*z_L(l3)MdO;@)`8X4qtY zEay5HtV8NEh^_l_^7UAC|%K*x}3?0%eagE1d`JZOG>$_K+T43xP@n5XnoPk=WDi zJ#hO6eE-4vq>mO$?8}2sYl190p@ls`y#18qz)R`1y9@#uA8B^1*jIn-a0qpj~BzuN+h+1 z{NX;Bh%5{9CIPkM$&~rBP*2Cvc79@u^-Q~22NHYQGTnB7%Z8q zAo318clV-pJ7%c_KdI<<9*2jO*xJ1RdY@CQ^)dWphq}l!ADAQum3zqeI9jv|2_3+` z&VgN0?6n{^P#>Fb1k!KeA;(HX3};alIqkC%rpp?wIN>P_aOwadZfI9eO)^|JCnJvZyi zj8!_sqN^U_hv51+b6vy2lJk8*Xw`yZef)7@%WV)l!z?F|<5i+Z1gYeLdP%(5D@e2&{--?akp5T5 zTjdcD-^&~)x#}`pC&C6(@_kO^l#E#8q7NtWB-`OabYu-^izFX_w)~|4@vonPwZzH~ z@!eIhx%AXIXVJQ~So{LCdp55dke7O(B+$)_p5*2^1IP#`cfs%?^UHA?u9G5%>}W*= zd`?9y{{^f#AM!~Q3WyV8{`4f3==k+Kv#bNKc;r3NDyA7Ch#ctYxj#Pfxh&ea7=1=r> zk!SOwgB!W>5S)iVJsC30#gWzXTgiwHVeIEF$9;}CZ(V7TWkGl^&QTU!EWl^+5H2HR zrKdTLAzIhV-vGFZHh#WY2V(zPpv z?j~og*FZ$P;0ENm5v%))YfmGo8}JuE%M-G;r0^^EUp@4+=sHN>;#-HA#dZs#%Vj|3 zMRc$%?+Rh5slfCx<1WDO5$r-9$63R5j{960fM?;{Mw0j7{W`p!_oR6d+V>cd46O3t zg)0ylUiLKQCEgcCyE8)j5m%fC@ndis!-`L$CFkM!Avz#vCh!oBgJ-2J)GxEvTgW>B zx{w~)CE&LLT)&Lvln0N(Ad(7>9x?8mrvWi&$q|lITziMLKOj0J0CVAA{NFv!@=q7M z<0k8hpgl>^`{#%ZMWI=lXiy47io$hzv@1x=xd|s{;rBFnoWw$}amerNJJ62fdJ9DE z!1YaJ!2jmvA)0;HTUC0{&V!{?f@{fQs-iD?ex1lVnN(Og=NfC}>a4wlfL5^IUO$=?}`wD!Y1`Gb-EAOswuhjLA-DgC>8%?&JnngDIaqrN_|5^}g*Zw=w-k63K$2NNJ0bFy)e5I)K>ZBIIWQ6Z z7DVIV{xQ!&KNctl>g=RBDBTk z??d|ml;wEHJ6VaWCDxlWf?g)WlVxQ6d7&o~6v_pVV-_Tt91RRJ_7)m@5vu3CqOBB}5|>L+f&>1C*2M5aRdIx7)Wm6FgEbOdoZ z^T?BM5d9TNW`ghB(9Fm2yvKD`5D`Bmb{C86mG&j53+)^5eFp>t6Fro3ML!PO!nJBi z7JOAMtmiq7yq=fO3{uJQL2~D+MB?jEyy6|=yKi`W3%Y`>9=ZmhE?C=Kn-uQS!FM)K zQ}RGR7g%TY5Ru3#9$7T&W)$t~9&NE|LB-I1>`7H9N1z?#+T{PCofT9hF6Cqni8g6H zw1ZfV+@XC3+BZDjukkGJgtDR{IEy?*Uj&UXatwfo=yn?T&VW{AgQjG=LOBCxQ}S&P z%Msc_=cY$h=-z;`*tJ-;axQd*GJnJYuK5Fs9wI5>Ph^>yBMWr1@tP6Zsaa8CJe`m$`)58;M$~6NP|XXfS$-RGqYs!xK82aF}6ortU`Q{ z_)h-*1~c69xEAWFdB*M|;32dVLR-9R8jrfrl*&aYr-kd3%q#I*FcIxl|E81$5#e08 zR!l^WArEO6tP^Hk_Lo@zTljShggkhppAY@ z98?b~R0SKsMdlJ*)Gm$1i1vxqB=vHkRL~P@B2jr&Dav;uNy!c*2YCz)!B#NVs3O{} zTB24io=*@FIVR>1E#XhoLR0Aq&x(iKvEkDW!?$2A+9DVWCh{u08}B4`u9_{noy?;n zmNNoGUV_-D2yaBSJd>9>TTchT;Q-s^BgX6uP1xiT{hX z=perU?diB;E)*4OACWl5sLdqc$|N3bS-;Rzy2`QA6|N26V)ddcV%cITf{EI?yce$3 z(nb4(w(^}6-j#k*=8$#BtfJS7h>a{Iw-Mhax}th+=nCI*P7qP91rec;6dFQNY09%` zolsZGqT5PasEZ#`ZIK9|dM%XYp|&n^6z?a93T5F`v|Q=RIYB}Wp)A_1)P-;1Tx?q` zR5=#OK8CXJtvcfKEu0Hw;Z@!##%hgX0fuj}H{o0|9MKl_AZm}2nTTdf3=^(JOB7q- z+($y>knle%6UuVP4>x>z(vtc}I@<~qZjhMVxiB9itsMFBFGxfC4y-dD2Ny>QB0I$@zC;F zkWjk9r%;rK9I{ryBtGZDx9Gd@Emk9F%k@G_IX0BVM)Xk1g0g%z)VY+Oh_`yRA>mja zf`{t1p{vd~qo&FY1=Vk~D2bvdh;3j#t}e3v{6Ws#%Wq|jEo6S_iKeuSQ^ z%>2k_!9>2%5rVkrq0hUlL%pB!A>);%J{#`jN};Q-KIihi(AT+zx+0^miicXeT8^S1 zx}lVXrczbPGFEg-IF}JVWs#^*HP13$c~VNsxne2T8al$0`B7e#U!||_m6AOBW0kgy zGJ0h=G92qz;YY8R^YWuyD-U``SLF@kNzTa{%@Ivkzl%@VP*?7CwdUC%AlJz`{n1x5 zUS^Yr`7s>Jt2uA3H#{kQvj#mIJ%fxl^z>DDR?3FHoL7DfCQ8-FNO6^CLtm*XWuX$E zrhi6fRQfW9UhmTr`!HU@&=;zzLrTZjBtuuFuh<*jmAcR{R$(m9%%E7ywQ5OfQ9@m3 z@Ttq}<|i5#rLDe5eZ8Tr_#4_9SAF`5j-hB?%^ZS?P}Y21{F}r*Ih4Bat(1L#An$Y) zhKj6TDeF4qyzs8HeZMa;OFh5fq_j0EsfYA=7dnP&w4}|9N?W;Bzb{#bL@nW55fL6W z;)!QhKPhPIkHJ>uDbFek!?|#+{0e2IuBZq<5;IJ^@TnWF4Q-=I1}&LeG12T)sA|4s z_*Sfiw!}|~pb|r)J-cd>N=3EFP*-o^XQz^b8S0vgn(RU-`?Q5?m6mT|M&>GOg|)+>nkQBkRDl`Gk_RsvGRX!RLnW|5?3pjz8YKB}1e(o#>MzQW)yTpL|6JVbM% z2r4&AK{VUdj8rVdXq>!?cB@sYW$Gu@7vV^&7s-tjAz6#^FWf1vDlwDu zYqh2l6Rb?`plF&LO}#*T{3VyxLo-Lszr>c+b11KxbEr%NC6SRvYJ-oQ^YKwT68=p# zDs)9J6j#+du|4CNBQ|w+Ob%L9-=9#$Hr1*g!~8}{v5h;!C%)Qv*`?m zTh%O+bE#b_uQIFXi#+5Vl@=pfN#sR^1TV zsu^;PYM}D0^y6cp{0dFwTd-8hK5gM%=Xp|sLf_1wYlx<9coy1%hN12At-OmYO}r6Z zQJ#$Sgrd^+<)~;Y$3A6QwX9t^*H`^4cA|%&u6m-}8~Um_hPF!3=ULY-^vo=JMra$~ zW^hzWx&nC?no3tRfe^xm+D^ME#vvMjC!9VA}(~7|V zEbk1x_-EylPs4Cw&d7T7=gF0s&(SM?7A=Ed{8y!~QChB23Hvfv&5G7kx5UKgiKr&&v*K!2U}jM%`=5-Z`&|2IEAr-ZG~YVfe^x5;9G{*)Lf=JG^v?+8 z=ub~xW!5j}rjK1@w@#Hu9*`LFVm)V{?>woubD$hFW(@?qkpJlW;XR!05 zx8YdNJh{qe&E(UQcV-?#*+1it(=pGcZ8%X5_4&ywpTx)?<&Tti@?-eXPyTcKYy9)( zozI;aX-GSa^?`aAjF{P-ix8M(@g z{@=2eQSmtu8iqrkp1d>Ci_fKe<8xqUHDk{B!X?MUN5Agq}~+ z%wlN!-zW$AIaFJm3igI%oF{p6#eVsf9GH2U+weYpEHP=8I_8z z&!-VhJ9>rj&+8|ptrX1nPk(w+b~4g%rLX3bp`ou&pEbPtOj?r14r1&h3 z|HHz6UzDogWaulc{|@lh_-q993VHV5$#rHOK23v)9M1Phmh4=WnXh!&}(z&8%jONKdZ>}`*FrN)2`E1@l z>nbz;$yXh#S4O`VYUZ=P>a%~n|Jh*gpE08hNAg)l`Jer<(P!m*9i#7blz)}dm9zdi zL+Qz}{s@(v8K*SlS!Xum^wl3_&dW$6MgLnl`z(!!9-;63^$A6T>a(tmURCt_Xijuq zpJw!Y(bvehr?1ni|1Zz~>sm!4zNI{irqYmS!=+e-ypN_H|FanxpPr64-#vMC{IN1h zM@Em6G3NUC<9&%lpEuWniq zf3rI~J3BKwGrPIVCDX=^8U+R5Ftdlw8o6>qu!e)dV6aFz7=a)=u^1eNgIT^Lcu5|@ z10x?i#s=dsb{LBcqaPUQ*^nL#+hGF6`rkN8hY_kB>DyscB8s6Dlph6@1C@nR9CU|7 zw2$h?V9=SUep|N;!hbCWp*ZF{q@q1kGRlnt4w?H`J}L#(gh%WmB2-5Y`(Gcm*J0J) z7L|jp%^?ewgK8xr6o*y^9kmbTLdP64Q9IF@4l7U#Fc`-u>JNurhsXY|)UkHdHgv6j ztw24Ca--55BaRl;gN|VkPE@l4bIh}K+h#$mgu1!@`V^J%7ckwvLw${|1pPR02ghGO zI>t~t(NR<%DjS3Nj)2HU<)ePYV=Ucz92_V&IuErIjU#HI1JJ%BPf#lQacFbI6U7~R z35Yxr0%&|3v2#S<5oL#G9GUYs%749v@;QoQ9K~_n@%|s?h#wk#RFcD|=qNgafPf>S zwr&eZ-7EbYQwJ|vJ1{#U1GN%ey+andE=M%bmH)+1espz?taU`v;StoMs4uO^C=CCN z0P0^X0;ne~-M+IUxE+y*@>{wkp}b(X=TS*EWDKp*nP`j7L&s6+j!~4u*3E&A{14E$ z+YlayMi^;PJ^%k4RJTI|I)X+Mjky(RVc61b5o#5>S~L$)43&sx0ctObAtJj!C=CTi z{2iH#j-u4R*?}MkML8M>ao9z=bG<_Ahe+y8+hGaa*0WeyC@O(Nn6A~Fw+04m3k zkB$}n#WDZaZpWIS?v*$^fsXz4!CxPuTqvMkM!~^jL;QzQ(Um)D5a~nz^&K<_p@0a* ziqNqbGcptE#_g~f=^K$gh|IMiqYi$D1S`VhD3)$+$GC&XhRB1vX9L}`(HNom=jNxHwKU#~wm60L>3{7olTlPC2e6=m?q*j(zxdkD)yjM|sel z?VvkabVobRUbOExw;ioxAOF>YN^nq6TTot9&tFc|V#hd&k-BA~rv-X)pmbCNN+ovB zK~Eh=L8YSWL(di>!cF)OP#phXAt7VrZmB5m{|ccSM{5$oO+@D6kQ(J7AwcLZ_-+ar z8AoX-5dUi(eUyvT&580*y1_w1xk-pLl#h%6rMo3`<7l7UJx=bPk8+X_$^QeCj&h(a z6_Lv5mVox?h)lFc?H;Egv*<{VfwVM42U@8A6%vMtNT4GW8dB&8m4N^q!O@n1v`l1- zjo>Tu<`GiASGzU=}%}_Mk(3$_sB1bkkq)`!mG;U<@)?u^=I3eJQ)NV-YiE+kwAU$^k zL%|EFeY(L5v_4mDzx8nIJ_5hFH=kXq0ke<8AJ9JqpRE`kf-TsRN@1LwmTa1NY~VCmf@1*wza z6gUA+h2sz`9!^9`0)odQ1#J`H7&r-z?WX=lYLqJunGugrQxHf;AQOQ`5Ifrto7<2Tbix2qJFZ5KD>b=$CzFxbqPvWVc!h_= z5sjz{iJunnm@^VJm+p9>k@7~s7xA|Pe7j@if>2Q&4KhdG?NGOS=hmdWj0I!<}KzpV!tc$-QXvD9-ag1;AVS- z{iuDm-Q8}t71$ze4{Yachi&_9M{SpFFKj8c4x6WavHgj?(;kF)t{U!zIf2Q*D6n%8 zJsDU$t{-j{?lJBUj*p*!KaWqqV+p>5$%K`Jjf4$^6@;mTL4;le4S`0;!{5SB!iVBv zToLv@xZ8G&_>HKuCRvU_zbyiy!WwQWF#BQME!FL<#xs~6#4TMtx@L75Y;}~rh89yW zbPDK0JYc(TO|o&Z?+B5MN!Fzu!wJo_{$vxr){Y@AB=RZWNHx?ol5}>wd64BYQzh>d z+F@DkN$LUtQF54mr{32VqIk)`+v-6|=UzCL9}f>D>07q9I>DBl!AAFgKMhtx)O@Duz<;m*dL%w4PJzgasX!tJz&n!kcm{$En?~Z8-6U(nKRtx5yT= z_hFAwaindM)iMHgU1uibE6?)s;-1pGwZ*E}sE6tv;_6CX>cc!gYUDO?Q+DZ8>l5Xq zMzT@Rp|C?<@9WP5p73-v7#hdcylpQEoTh;~i zs_S3*B=6pjkJ&ehCgmQ-oy%1hW!Dpm=ey<0SJn*7W7?7fq#oat6DwCyA<<@m%=taQ zAw)?^nX$S$$xYHCQE#O;c+2Id+ij<#q9b6mG{Ga@htXpbrO6hh`8xEF|4_r0y2s@h z>K}`u^j%qE=D~&=Io}FirR{r{pPe5&rF2HlzR0PSUgc{ONxv`R&x+<^oy?2NFRNy% zw!3rzr@%+vJ9~~5$FQAoKAr(;Z*HaMCM`%BOL#?z5^wP}^nggO6n`j7<(Z>|t^}92 zVuKEDLs{JrM*(r{R>ytzosLcudW~&w~_5I*| zUE_>h@72W17{0IHMbUTFRIiskYJ8!f-Oi)QMe;E+T#xq+^=St?@)|Re(~5Z)54`A~ zQuX-Ytv)ZazK#mV+Ru6Q*FCM8~D9fX|Z(!MX0%{*Aq^V%{o}gF>BB`_A(Eq)nYPV8#x=!K|77$$gaG zeWc2g$s{fFZ0u^&kGG%hoQnkS#NPfEI_0+gQ0|38uiT%XdfD3MT(#;EjxdTlYtkxIYjZ5};;@3l!#K1X&2fIf1{W5Z{-%EloWTHImsIFV_V@^nq@AAjEn-i|>K04*(!wX60*Prq@ z>;Lq{t?SR&&kn`zCvaP}jm7X;HtgOp@cf`)6(w-!i0t7hvnoBt_Pjs&v`b~L&%Sq9 z-@JSHdh(|U+x?FEaa>%zW;mS~JAUF;PhM}m`_KWaXT}AEE1iY^s?FRx%%wI)Vs>0= zWZ>I1VL>-voNqZZ`P|^sM~|*O|KQ5DN5)(9^EF8)3+88hZopKL8HWQds)lj*>VOf! zBZrLM>NnQy&E)?5rv_CFTBn%n&(YSfj2>-1wF-BUr|T)rY~L}XPY2riZty+nUNh>( zs2fA_e98n>E-QP#Q~YV3S~s#|SzcAroY1IypI_4Mt-Dlmc+WZ2?cS$bPiig<3DbW& z@%-11p!nd{54`8tSa`1}!}Y2gZ@~Kz)4hv)z<@(T_e@ylf7J7*U!=#nfGL`6)@>ix zfS$tfyj}t02d(Y#)7#0_d5CUeY2O>pZPMf+FGns?z9l(Px6w-Ud)ht}&HkbI#(txH zboyb};{{i)9G-J&^=v5ruMV+BKGj^ zvBr5Uz7u~<-WxZul%BRHwcwXi_|}&xH+tMne!AyQ)G6H6U7^PwMO^Rod_e5ytcJ{u znf8*!#Ch6DLMy>fN9;Gpw>WTqUsE5f?{dF?gKh?H>|N@%O|i@6h<}Oa7k-FyuK-Wo zO3kYt=Z5eDdH(afH}%wyc|O=nyOXutqa` zcouSh_66=`_m_{~U3xk$a#M8QL{7D^DI9hp#`5lSGX*CHUKn)4vqXI|aKKovK6O5$ zJ?45m?j7zkM)p+m!XI+82nH!7ffIUv^>B0L`TXpcFl^KitgF`hL4TpoJlzs$ZwVXN zpkE41F5jIU7yjV$t+)QqOfQaKeSRtJUdm(ZP3JqZk3HYpLZ8RPWVkhS8YVZj=-W6) zyl;DABo{q;_N(bXxA*8C{6QDJussI%daW7SbArbw`9^iUdx_Ttwcg{7=NTVek8(fk zuzo!b^jhEhw0GRV^}WAqa)>W@!<1*i^2VzLbJB)q9{Ta{o&M#>=Vi|**DROJ&w9R& zeTcih^WE*}7eB6*T+EnM_R0PeUdf>`Uu*Acc4;d6g#;|=U7%g)^K#f?ua~}io%>2J zc$axSQjipWzL#7dDjHmvKA#5E^!)7fk9VE_=^=-POjY7lrS5iT5oZbBod{Q+Yxq>M zFSa__C+t~RTIkV`j(1ldKD&SN`IZ-wH_4&Y^5%rn8=KQKwN&39@qig(B0VJ`E0 z)&7@tzdVPiKT8%HAo~}>^78rBkAIK;))~+JSpVMbWzmD(ucMz#zZ>*?>BrWuLtd;+ z*qSxE&|;I-RCmp1?B;FJ91*VcNYd`{>>JqP<`jh0=6GQH_E9eK8sqdyOxG@SKco32 z3-Pa1ySoPUGC1cCOzbh-si)socYWagKxf4(j)JpUve>@JRNS^V@qF2X_?(aKvH!ll z7*_v0=RwKa4bN!r20lCg{z#1EQ%>c%Y(_EPHo;iHJji7VZV7nmegS*jNBL}bt=B#e z3i5Ds|KndIgmf6Usj|l!s{1wdF=@Q_MBP5W=0QIdnLU|)bKL1eYINSNr_@R{NtRBg zGq=~Ib*^j-`Ms==_4#R7#P^{uPKVun>i&rNV)64aUmm<+$KaFdzkjQ|S5?|7V2p=I z)G7Q^szmn!oyf;UhU>YaN4fTUKb;(K{^^$|N!4cSI@AQ|FxR`9JL<9hon#eWL;9`L z1rA~PgzAjG?_9sRja6=w4kt!g*An~G&T1|#a{fZiqJ6p^HYdFBw&8Vg$k^xo-uOTF z`qks@+TYXu98XybOsqfMIhlNylBMh=3DdYLaGsQ2YBf3Fjq6&qI8daX;gsai% zrc~t%!`p}Nr`|pErsYM$`|205p`65P5liy;g}iK&>7lU^vxD=D86%r5&v2RI>fvMb z3m4DoHNcau{obop|O)a zuXGazs&o+n5xs|NXZG#yk*})Io>EtHyZ{bmM{EDO+jS2j>N8oYX;tRZR& zeGHOV(FLaj_X%Q?(|Y&YQd?lCM(HXYx>;G(qn9sLySnd17f&(394y~VGjtup$x0>V zQ5A~sd$JOuPP~&v$R2(FFy(8X*XQ2ueQ!*vi#ndRyJA^(l|IMt$gqjggLzi;iStYq z=Q`W9)nl0`!zaXRs`9GOd+7pMp=+tKS-8dNmlvIL%W0?kHr3|=%akpeMSXv&T?4Lo z1hZZ0w{OvJ^F>*QYu2ah>eWuK{oORXe4U-Pi=3sNlD?Gw#zyk6 zws}R%>J_m`>8mr>h3$^o8rJsdVr2a5rC)~s*zkM&ubNbPnPX<_~ zW0g;|!HRh2T&1h?BbQY9V7F5mv#d;sl~jwpv|g?%R*)jZ7b|__yV32a0@KgOd4lUr zpGT_k9ucZH{A=ubUM#*>%K~ddr7-{BYI)SlxJ_}Z-oN@8@NxXd*l(>LbdepuFQqI< zSdz1*=0bIeX};wSbcmtigZy`beVW(03rc?{Z-u|hJ=d@De~`1uTRB`)EwA7=N(t_h zgk!aV-iJg)zkJVLTA#qZx`QrukK?L*%?D{Qr-c+uy955H-DG@Pd@Q4P{@(D$U$WmX z!f@X@!v=n7h`JmR9QiJ8?jJ(V@{0bAn;UQ0?%+G{Q7o0{vD7a4sHs+^svo)bQx$2( zs#%IU%_YS<*$2rmg}<~|>FK_OAEPn)tXE$3yXv-2^Sme4^_TNj=OTHf?4*!EUuxZj z8)WdRh-(;jkFIU`g?eH`zKKMTM9_TXO z$IYoxciHKw^a^JHGo3sXh&8XPH|Dddwxy;0vc|pqRP&wlz4yDKPnIup!bipUBrHhe zWM`HGHA_1dn_gNnDMf4yKVC#q{8CTUO?4Wq8l!b~DNwIe_K}I@?MkjHQubYxr@o+i zsM+dXAmg|V^82N8_hGy5a?bEJIxSZolx-8ZQ)NJ-6K zeZLC7d3==rERBizG3vJ_<3jGK^1|BC`gGG(j2CVj^$Tl-Fhmxk_#|DWsB%hBKT_XP zEtYZEx+cuLcPHKqeq3}zULp05bXn}_i`8UG;tnl2^?ul zfrd3cZFp8NAmx2wOXA?DsWFc}XGFb>x&Gy8WJuhlgn%r0{+0swx=kH>42!KJuzbpM z_H1E`=%GZTNK>5BPE$XZ$y8+ZRMmb-j?h`=ESsXS$QyY(wEK1Ly3Ou=K$Afny1d`&eIj^WkdKUXe${3gb`iEEz_DWmPHhvOV`B;)0hdi zh(kY^U+@14P6~*e6hAs^PVU|cN_Bl*f)Rr6;A8Lt8eLE#HcQ@1>J=yDB;_~VIa#eN zMlO+x6j*Vfc&DV7q)Owe@)K>=5ZyPrw7bNqzv#-mqMfg)4AL`_i;5KPN{q?IvR~>l zHk>Ux@n=BJugqhynK1*RPe%sFE{Y&TPfXnL>-e8h$c@)M)wt$4#=+KInC}EKEm*W% z(o1CIua$eaJd<-JW%4odBborkWL|<0BcH8`k|y!|m4}@VtM6!PwMDvB9s}IxYnE$A zYd@*iD*3!Hb_%84ZnQP%L+kQN=ak5EcBiH%9Qe@{YyGwI$InRc``Dzg%(Dq^vYr-S zt6A56u>F|+4}6C)iam{fhO<{XU7aW&D`BhrWY<;2n$f}vX{5|3@sx4JzA|@NsC=yk zuN|rUuAzF3ae3>s-3g;P(MD-EX6XdUgyE2x7C|f3-q!_5W7h|9p)47(c@ZJ_jQqdp9EvFRGVCfynXz>{3B6*(ZfatnZRHYuQX^Q0jzZNF{`+G<9@n4z0e`Tbl+A^=@-!86f`_W2j zuK^Na8e;(?i_uO`6&_O0lf04z35AlO&KIRk;)@c9&s6l3M+!4V;mWk?KNaLc4QGIb6C4I;J$eO|`pbIgh%%aA?7D2=9g2>##$zxOBrmg=)`jsB7O0Xnm zC-hFcnSQr)NR@BJ{bp+CDBBI<1Kc8FH2tk8RN^ApFY4ev(U8^AlEI2AymNBo-kb2K z=%o09qD1bb*ePA1Nb`86o~GWa%T)$>uGF0tua-;{xr$kwLqs|hW1av+Hw~ycSok*c zQJyumBmQyRrI>%>C&i78ca7hj*1tfO%P75GdAl(W8U?gLM=@`xZo(b>&Af2dT6sTh zsW?FKTkuZeqYD;a5)Y72Bv<5m=|1T^X+Ld&nynn+#8d8bF?%dmyi+yEn`Bp12l-Ky z0NlTXZT2$#p{Dras)|V^y)qVRgz8)7Hm5X=D*aI~J!4POoit4Rq*&drN58A%Fo^+a zGgF5a&?^_0k8Qlwvdff)--eq&BvLZC64^~DBVI@ ziVy`}zDofrZo2sEeyA=uHK@d{B)3%Aed%klpKvQLhIYtaWt?K>v~RBqE+u7!W-rND zobdGb_MbU1VZVR={x|kml25K8JGwBad|qvX$=`g&JP`St-IKS1U&DROIV%o8u9A(& zudT_#f!Z$FWNDY|ndrQ{SaD2&mqE%5ol;HFsg)vs~6FYL%STa8w#4R&i7D(qpWyTJEjF z$rR3FT?aRi8N!6=uB2tKw&PF3&epVQcGXS%Q=7v2dDR2M#cro0nDJ$qCP+6_7G_r3A*M zC-h4xLi7(!-IZUK^P=EmIi*UW=L5k;H;V^mJw28)j(e5;54%~qUA|T6E2VM=D1((Z zrM=~w1z_r=Ka_K%Vq44krYW5h4YRGeR0(|=6-ODv z6Uwt?TO~s70_g_zd0CjENZ2Lpr|KblBe^W2D(5IO6fw#oMWoXa=f0}1N^g0AYOHpt z*uehCSVL1$JqZe9Xj56smwHX*y29P*-_l=XtW9l9Fvjgmic0ZGzni)^8(VRptao)~ z!{OGww%yn*FcCL^Qp@ufH3;Lm9(NDb%{MEcTfhYGUelzd{C@~If5365PG(K-jzW<+oX_1NkiT4s0 zCnY8|C3|Ft=QQT;E*nw4vL({E)vyGdV#ATXv1f9tS;v_JM9K0!3ao4lKS|nKrIK$@ z+!arhd{u6iXUMiImZ;yWcd6=?(^OjL4^9J=50E=?(Xxp$A$Ke3H4ehew@oo$ZlqL_ zD$7fr=ci`gN`9C$C%G$$pYS>9XWFa`cFvca*Cjt1RE-N-V>@q|CBy;L9+V>#5sNNX z%d(_X1h9~!oU0h1yefSwx}$iaScH5Rtd_G>IQ1Q6k@B+kr<1!@uf!`JE2n8r%AI+$ z8G0I()`J)dMz$q4*ES?pPbk^@hn{gDeRJxc#4+)L#K(!>Q@*9~GJh1VD{ZPcSvR5S zqw$W-VqbAzQzu=mxGLuf(u8l6xr%qn1nE-o z7G;TYu|lR?rs}D2*90k_DN?kBPAv5m$sXY_iL<;|@Q}P8vlaQb`-f#mTW!^c%2O4~ zOZj;x({`moslC%)C-+E}X0&B|%ifwBR*+nKtg&zN(YCbCW$;e2f#gcuOJ?yz(g#u( z0f)zyg(z97-Euckmn=qgLorCXLXoH>Xs#+xCt0q?hHG+5@{&|ZIxbU|DJxi2R#Wa&bGSaZ!^0v& z{x`k_OUQ{F5&tvyINQXFmXa06fwoYAUPGmMII$ zbKCy7<^VZh)~K9Yc|Quq6o-@s*RE=ox2ihuhJP$McoXp=;Rf<0SjOKd_{3Ym!SfD_ z21ruGe8DDuoA9U5Dqbq#izbLKh`Yo?Wg}!4PvzY zB6Rt+|85U#aj9`B+g13zP*Hp=FFto}{;=G}{H~&;QmA@v?Sgt-%dUA4p>VTeb2ti2PMI1u~m`R*Hyk6X+>{&d85G#Bo*v@0~ zjtM3p9;XY}39pJaiK4`ym?)htwF;FYk{BmS70lv*v^&(5lpuVU?W|ef`Kv?L%ByEr zZ!gO#?o~3b@M3;-!Pfl81t&``l?GRis0*r3Y+Bwi(b&^+)D~>Jjy*$u!w6;#Vi4$l zoHxR=f@i!7oE6+`VYaZ7KcAl^FbQu7<_mL0LnX&0G9gR2N6eA5h?9Axu@o`w9rvr_l46r5aEJ$)_vx*$Msk1ylC;w%xw3nmI| z{GWo=!uf*xLY%0t7#1()PvxcY)A-qZi1n4yNVbu6#7~$KFh$>2FYkJb++vQcHk1>p zmREo!A*BmS9+Zr$I9ZWYWv_qIpl{mSezi*q5^b#(iQNZ3h%%G@lm49goxX`1$Xm%9 z#a+z~;@#t4tkpd z)M7l*dA|8r-I@Bf+EbO6D;AXPD-oAZEr-fls*Y99uNl>t)%>bsvJq#*09n@67&cKt z&LCbU2Q$K1acn@%Dj zYy&fgiXjwYA7cC1bx^Uvt=+wCTMMqCuy#q+kn);}dliLcw=1eEqAH%&80*F~T3TD$ zXq}zLB*1JNi(Q9#hU-J#MZ3p1$PA^IFqGWg+%WE3t{bP7o5$nuOZc1k68=CwMQ}zi zSr{x#;5Bg{@IG+oaEG%W(OSrR$^Q`VU`e*U=IO?f21$pxZ;EfjbuBY(f<8lDwqe*cgl2LM)k5LXtjvY%CiXSXAhr`n#GAGeN@P6{9^W*q~1^Wf3_`Ufz`9(Z0zKU1H;xTcIle7f#bV4>f!I}p}nVE+6_9e~Q zrWXy?+Srn#bcSc&oWOK2>l{&?=ZOXyo(xaXfE+0^gH=kKM{R$GFXCp+BY!!+%Ad zjncxgmKo+AT}L_|b}U7{C(LL_sa;x^Sa+)SUTsV5np#D@bK~(QQroHa#`b4jJB%*S zTiZOF%C5uu6OEMZv^L5iY9>>{naI7)?P5>nPT?~JX@X)TkIRwOzUCM6_w%oDm#~kp zf>;+>PZ&zdL_#ut1fc<2Y#Rz}H{LPzH{!aS+9o$SH9c*b-8iU0TmPvRtV?d#+2Gvl z(yDBE-B#F9q2B>K1w6rrmTG%Ao(Af9JITGzyUAO_ zuj4oIM(`}0o*X|;FlRKop596+A!{jdq?dSSSZdK(oD8}^vJpqX|N@|;>KR*xS?cB9qOf6+eC@|Z5{ zyBs&}Q?{AY!M)1c!e7A)_MF0oK?IA?hT}*auT@{dB?aC&KlNr=6Ixd(_<;eNfLsR zFaQVGMb_cK2J<)5aKrh|DQ&pcpDo8*4mH~w(;Fu@4sHr*Ue#i29o)9OJ-u_VAsG41 z-33T2Cfhx1AG`%0MwmbvOcl{LG5%phGMrgNwwg=kdT==0>zokoP@an0&JlB_vx`{2 zS<%dc^qbVd9WiKSD8-gcXod3xY&NAExaYRIlO5~Q+{(p^R$*f zEeBe_Hg)H(u3iQgW0NT#SY-w5#jpWWip?N|k=fKI)YsH4^g&DwdpG+I%L$2V3`dJ- zox}~{EN0iS)tsyB!_1%b`Lt|m4z-%pK{$$=fjr5WV@rZ6fjHw@!$0EE{GDVoq02863mR9>&OfEJY z-;Z#H^o}x_7EC`&-^cjO~-u_ ztcMHjay{LM|BziXTLEneUxP-;0uw#Sd+kbUfq1l>U=+XCdt+zxC2b|yZb^nh}O zDxvPCh0zHt7CVbsz~0Hyu^KqOY#E4;jWf~Y;IY-zLtQYJW#!aRt>o#KoeLV8Z`6(%g zoJX9EpNd}zN7)y{8f%%Q7+hupq&_j?0{4}31Ju(Cul8t!7DSelIb?1Kl zB!j&x$nZ*EsGnjyXKFT;o4vqD0JK!v*4j2;ZemVh{}9d-rQ`*aIaD+C3O#~Z%Lrk$ zG9NJBuxGLgm|E6O`ab%6`eNE%$}lpE^d5hPun;H2EWjjKH(Re-M?x~_A<$`hU;<6v zCb99RalHPXo?slVzijX~X!T~pUSpZj)pXBPXnF_qvCM@|SW|7gZ3D3;Y;XJxVg@mQ zoK0z<^`*~YjA7Q$zp(zW9xz?mR~Zu-LChR_AblCNiad=pp0tek2=^7&22Zz7glAb> zEn6%CumRW#6q-fm4pXpkkD<5enIYcbY*grJ#xUamW3KTq0$&il>w&4@Aj=VGtIZvG zPI?k<7w#FpgeWA3Qxd5}`ght+2Ah?`_{zG+T*Da6N?~kd+@n9C_M&d2oF^|O9wRv8 zi!fKQD)^bL*~YL`K_8*9U<)uDSY#ey(wdK&ei%2K<{GHR0F%VXGhH(3jN?oqa}VjUd6=pm$nMBqF?3}^wCxu1E6>6?*lt}`w%xtgjB zBTSo2=S*!TcSJV}*a6u*^)8Cuq&`$1RvtR;?Jymi1kbZgw9mI>Kw%aYv>jRi z`G8l<>A-TZ)+_-lOovS0fpKOh;FNi(X_xt}IRJpncHkeN8VItAEXmgA_DUNOtHQ0u zZzZ-7pOAwnBWOM80Ns-rON(SYXZkR%GEKB2R1!^1l~JA%A%X@k!AWqt;pcFXO>ZID zYi2Uz9c=^ypas%qQ|FSah*JsSgbz47 z^1eWZ?YLcHOR|i#j)G=GgP|qhB@hNv!58LOOdCSdZ91%WsHg`3zoyE<vDw7rdS4cU)y2ZG$1`jU9%DP*i{3?+ld=d#LVx^8Oeg#rIfr7bX^4bz5DEE3n+^z| zKnMm80S$l`G#wlOeFKi0(KAL5l$$F}jiys3H=xm6ZoF&Av z-!=diU;^-;3FDD(qgF}@{W^od@@IWzE@kuB7ujoB1V$n~kd{FWrff&9RA;aTtQb>m z!`pb4i^RuqU_$B3VXTDuG$x3ur&I*pdX4niGL!APDSft}%t1UYia8 z(dM5(0zd_4T2EW^?4cMg7E4eOmy?H3sdO$gnm(OLL;ep8MeZ0TFcKJ-7|R)(D1Au7 ziO&h!@GCKU;1JtW>k{iTs2Wm2Z^7N56}ShTfG$E~q2D0eg16``Vv9HU30d_c$O!&6 z`$Fu~P3t*8wxXR*VJ7^z`eSteoXdrDM_oM|e7BgE| z64pf46ZS05CH8FQNBVZ!BielG4dNZV0s9Ae59p~a8o5qoLrLIO@B+9Q909!nmxB_^ zFN@AfwGu1`E$!Aq>qbj_w?}FrE8uL_oBEqBn-xHfDZo4&SOr?38`dZr4)X%%fnQI2 zMG?_PGddZ&83n9soXMO`>}IB#8OAJPbWjhH9}sDTc>H!OY#U~qXw89`mK5+c5DuOL z+W<0D3hl7CT9;d#ErXD^W7M{H)_lkaby))}FmgA3jQP4b8ptxm7?VtN^J3s7cn`d5 z;n^GPKG-OH5Yde?n$}4j&RD`;%_ea!AbL-*&FpaIS$Y8VH~A|0EwKqZ2t$Q)tyoEg_CdA?#F95a3$Pnn09cGSOiN5B zOap-q({uAK=n<4`wb}WYefTd#5@|EVPETQsXGO6ym>=2ixGUKOEE}Vi=1ni5rX#O$ z4#kNu9q?WIXs8(41Wg7sP!li+$*(I=I!Ll;Y)frAd$7%F8H>EHblfJkTOfwTXq^KM z0bBusInX2qvQ0{3oq4`F3P`X>EqMD&%qFZa;T>rqC4+XE>A_geJiyIIzWsh-doakX zU(9zj56WlaKZFVRT5PE8qcz6r4}OAPf&#N2GzlCHEJS|2JhMEt#@Oarx7dqdoE?Wb zZS%4Yfg7#wEFF+HP-ET$_6I@@M@^SZR^tkAnK=V!vF^93VPC8sw}Nz+VxVYfcUTix zsVq0{DyAQMJ135Dn$eS%PX0k!M2y6}h6QkuMQio6&NsgUr+^F08=wi`d@$Y;XAQNS zu{Of5?Q=2r;WJhOHWx0i&9pCsTA*pxBCyi5!Mwq2GE4z1ra{L3&?R6dq_*#|$*?2v zD+nh^@2M7AEWMunfH9oO;;~p1b_%nYa+%hXHj}&q{|7q}BZYUvcR^?9tc78o0!hF* z<^&4>v7s-P5B53sMtcWln@x#bh>N!Wz>@7r)^qR@>q5&)Xb14#G#+Gt4SK*l&V1eE zW0isZEl=RN$P3KZ@Sdcnl$Z3=v_k4l&Rmv^HGtzrqcK~UL#RATCUF!2i|fLM*xy?A z*){_{P)`fXG#=Uq`T(((Pz%nw!L|_t*&f?P_&T@&w;kJMEyHRtXYEC{7SIhj;ew#& zMuIsAIArJpt~2*Gt+56}{jKZaC71{JsmN=cL&(SJGa3Ep0!}JDoqnB7r#H}DXqCkC zL{H*Xe2jgEy@$0H8flwpUJq=6)aJ9$D{v0jX)U*T+jO?C*k;T=>?b@Ic@QBK&&61= zr|su$bM4!$bx;7vF-A)8Q4_)hFinne6a{6Z2E z=|nN{6YeNs4PhX5AMOzL4(2Xy8Fnq~hFOZOfj?r35TCXp?+#wEEyO&w8LS)aV=a}? zY0C(x$XpE=%}iq(u+iOO<_}=nLn9pIsK7)BSC^M{72$L;v(@%UM%~AV1PK7FQZm5r^1!=p^W9WGyJgD zR6`tbkil-9+?n37q4lVKt@(1B8R%U(&rHK~HXMih)_m^rv@5C(VotS=YP@T_UD6JF zSw=R5G!JP1w`qoz$+%paV;ltAEf|lf&~d^NVLH1>#I z$c>Jhqh=`!riJsTl*67i;u!&LX4-C(L-m=qpE(-&kEL&KeMh>nW-Wg#ZPr3l#;JZBecKX!CrJsU=sw!`k(tLr{@ z%}rlecDYenT3=mTGo!Yt$-9P#sKQ%i@V&BA@SyKioQ9ul^=^krIEa{sa9s z{3l~+<|6kiev}cMv2UdFCZ#O*UN&pPxqq&#+_ZN4lHW70%&VH+d(piS=P?U)qX$e= z?4$QfaEsP=mVr}?y>EA5vAOl|w^x6LW+=;@+Owj1w?Qq^oVRrgqX#xUFu*B3 zg@vz@zszd#jy6Z``WW>frKTH17|ir~4B7RMBRZFK^qUpWR$vxZ`(f=-OCjL%+=7F^l6?MXxqA z)*p_mDHxlS)m~li9bHj#w6wicothjru4F^*g*e&!b9Y8WtLwAM9!9MDZ7MIa@l~F5 z68W;rjh<7cYv$<39$$K9+rtgv+a_d)IdJ9da4(B~r_=e+1~uKoLb}&az_oME*-Wu^2Hy#i@E|E2y zz-I;{Z6-#g$DQi-&mc}w-zgE(Lzg~Te0pl`(kCls&)79%YgfP?B{+jTo(JzOk3Qq=Mcd#kC9`iR4iV*{LI30 z)B7yHvnYPZ!om3Pra||!Kl@!^ckuoin#C5^1$`UelB@e(D=yW1>G0`##f7HIvSok6 zYW}TWRlT=%K}(+ox1WbALo3t2_Wd)eO852e%dyXUl-w*I`{&cgeNURo6KIWNi{y_= z>gSiFOrta6(h>(Oo4j!H=?Sx^_^$Y8cHqeQ6RIZ9FIwGyS?03X=P`rgdj{<$m+JSZ zqGbIP`{d>-w>MkQV)-EbY^*50cUj zO%KX)Uf+9?^+zqc*L=AqSgbS7K;ZT$#xMFw+tuB6^;QjEJt2Pdgt3;%UFI&EcxYJ4 zq_xBH^8!*oraVkP7y65L(4f%ga>m-%)J^}y=Fd>N*49@Z{!vjeU3i#}G_=$Ph<#*p zt3Ndu6kkMd$_D;@QycU9P5F|_mXES82R=Xlj@O*8-B*9@$G(;WM3dVrRur=>HZ<#K zw@bafx)qGrFzMW++oPk3#Pj|g?NM0VXHrjAzaeQMk>Nf=c;5ro;rAt;;v{jL(7RD5 zSW_MIMceqLDXoPN{rr2mX>8LL#UEvlib3$&U{Uk2pQ7>((yc!ql}vxT=+l#iDV4Xr zK7VJgP0{yny`@@iILKuJeupnl9+)~a=jPxagOf+yoG@X6_t<+QuaBQOSTN{T|0k(L z#@&dj$oS|;l!ttjyc3ri{WQZHQr@3zDiw@r3>V-1(W#+Pn%_*dRMl=2FH%aXmee3Z zjmWLy{m;(j2fi`oGs`mGE%_SqeR_2q|C69pwnuo=dDyKqtW)?CU$0K}UH=_WI_lw| z`f-9WtSKi(=8Y+z+*%;)6_8Qd?q!!{5$C)+;69c>meaCRaIltaSSt8k{k-LE<&$4t zs#&!IL=R+=iYm$A`XmusF{swuc)zx|e$bCu6#>nkKJP8feAoBuh5CK9ZEC3BpQXzz zqtL|L(lb4emIyyes^%dMb zuyx%Gj;0}Er%)k!+IZrVP&lMk(Nxzu;m=V)Pf6PbwN%?QUXUk@tvy}wPu=QfMrV#_YIq{$4{JQ_w#XY}%Wcpyo@WP>^#z&0L=))eip|icm zw}Px-dxub;FxPMXQ>=H4=hZ>Vfx^k68tLpZV{>-X!pdUZ5qMugs&7|+k>6Ilua0U7 zZPC;$se05js_ehAX_fze$gJL8_37u>sztvlTSmxtYai$b+D7~C;T??~k~F^Eg&xbh zEa|&w*sfvIir)3k89i!P-=5(^(^9@<7NrmKDUGZ4bTSLMuhdhtDjiQ#+E`puAm3Fv zSk4uA{rRlDS$|6KRO3;1PY6TJaw~TKiL4ym;HWoC zv(#Cplh!yc6Q1QeudOusMDpu_KMD%^H4WS{Xx}(qkCVe+4hYEE+52AX?$pMPA9!bi zyFtd%3xBS;BmF5>snhCqH>WF-tMU6oI zskk(~Jia>iOG)+Z>RAmd_~+|OHCIg?@p^i%Te~)k0^>6GW~n;eA2PGYfPwu6G>ssN z{_9d$c&^8_PE+#y!c*dU#<;l*^GE z{MW)9)tROQ(QA2e&8Dhs#h%L3e*zkAe>PWcsM%Ahu5wiG5ndNnNX8f&s1&!;M5lnU z?cc@x?y@pFsIz1cQD_`2?#&)pHms=2v_4}yPf6O8H9SZhsR-JK1#%0F)w=G+M~c|`Y_(c6sYaplmmU<}mF{bJ#~-V9)CT_TD@rf_RxWPX_~~R>ZQa4Y z3+vj{qq1gYxOScWAQ~R<&|^{5rH(J!KJWQAFQ$M$Jg(oC!G_+#LDPmV%jn)MF}-Jp zqe<7iZu#jwbI>p(s`ZcZi1x0^tMPI}m@2wqLra5fPt9GGLH?|9o9w$VUbt2>gN@;Qe`Q6jS@2nb zHPvF#14s$$(YyKJ*#Tq*nT(qJBJGUM-IBwb8(+V1;>)eRIIH!d}8o?);QKGCf6FE ze<;lq*GtAL{?=F54U?X1njtwZ?b9$q+FNm3_+4~DxS>fWruivliyAjnIsQDaUtQ|_ zp|5%K=XrHHy!!E$Jl1(QYMd`zbzU>V?aAi0zMZ1F@6A6xY*XKuzCJ_84Z1tPKlgOE z$6fX(%}X2=+|zfy?*-0Xi4D7ir&R5PhzyZ^Y#t+# z%f2>j=DDNR^R~_*ES;Lh-$Er+?JAPlSE0(e~b5uVKvCJj*?9h$@ox_=wgGrO~ z2j>6mOb(pWZ%N;meY+I+_V|&rJSQr-U&N%COF>6GZ*osLO6*b#&#*_K6-^NC5?878 zjei^W$se^0k>@MAH-8c>Q^$&q@T-Nw+9>#JbARSFF0YQQ=-SNvwYO}HU}m$lWvTSD za;!0xXy@AAB{yVaIIn$V=a(IObzRcqb@wfU1{U-v*x37J_jz4ECbcH{wZ9wLDmuMn72EZJM}3u#GniRhVZP0f&o!lok? zZTQ_Phkws&7*#u|QO~ca>n!eQ@ORF)OYxJuTEF=A17qDftjf8W-M%-{YjU2c5811r z=cl~*th|i2sax6?L=Zkr-kUt)h_|->trc3Wp++-H7%Qk0t&@IJ^$@D~Ve)RW0kSID zSkX<tlA3@iMt{w9pWj#pT6Ei((bT|>fa~Vr+vKws4mR{-#={C`G z@od#_*~*sE7LK%DK3+LWj0w6bwDSMNI-#*)h0r11TwPSPy$-2q-_WP>=MQ@NsO6}FjoPW6c`CeOk_bMnVxSyZaB`Y(e!`imX+RhI< z<>kx%10&hiIN7@1Vx25O`&4yNU~c>--m8$Ro{Ob?PuVWzJ}FDA748w|NvAZ{)HOH# zs!MPDQ=|H|v7%djZ3ETxvw1Fmq57>Yfl9<)Vr|gVv~NrPpS;z5 zWW6Tl@6I}tJ~}fqaYW=Nzgga=yw~ssI9Fb4tGbgj~%!km^+$s&bLhKi;M&&X0` z_43)$V=ZeW5;0HIzWGz#HvX38sw&;zpH*)4Z5zJVZY(>}l&v|UJF8uz`$KQ{S_Pc~ zBO`mo56$S%bx`iXZku~W_Hp*e=+Th3tm~DWAt{3rRz@xe>l||2-3wpfh_q$eo)||c z>zkj6N|Xo{gs>zZJ zYwjhPFE3HQlD%y1B3>?6iT^efG+?5G{My>m^3_#)n-BBHHQcBW)!Edm_49S340CLr zE_Zw*LJPxG;floFnem-@T{h>Q%p>x{dyLB6(rI;eX5D$oDlb7H(IpQEgIs2v2$+M+}~rY$8p!1;G^-{Ky;Ks8P03`Jb}Cu!r=lbiep`%lF3nqTJ?74Vt>vyV$OOfYB*9*QQqT97=pSdJ;Oq#Om>^y$Apl%hpCpurr zex3X?{!KgAHdDez_;e<^aOCWKCe1WM@loZjU2XUxZ_}IyW8}7J-l`-5t%NVRDBmiN z6TXq8w~#G!n}h4zgf5L4^{;Ar|NYQ(Ry4FFSAGS$eXtI__Elp zX@9d5bEo7xa?5qc*L1t@@{d zGNc>gs3NY_??AX;uqkF-Qg*s`*W50LI-Bw$y1(wK&2{D+OBba*Y5O7kQj{$a^SR6^ zba;_6a-}U=6CjC@RVtENC6Z%&w&c2awW>g|w`H7^D_$=-BHAhN6|HMr-*lw*WmQRY zME&r(Gu6_Ha=}*l8^s@)R@vXeV$JZH&D-xWCG1Sx$TXP$)6v|C=(f0-{RZCYt5drA66FjmMJ0UyJ&syMlVYk5MABw zcG}ylIl19o`eaY-9MoleTA%dT)Wh*p6M6;h@k|J~=rP>&0yS8_*R&t*L|v3t;RK07 zamYATHdPQW{VwLVI^>*|iP9iZPw{AeJYOvO*67)6uU=d&5{?w~;(u*OYaFUz8#$I? zM#5HuNIbp66#++r7sM8Kn3h%CDKV{W*7e*DIT4*2vYw^PN&AwpI^=XzZD@txcMo`@ zLB?TY=Vx(h5?OIxdZCjqSgg4hV3F{Bld~CAtAMu6F@}`@jY;A_+sVU!l zg*@k464)!yGcYA;U;M_@8(CiI^%+|`@6KM6iR27V-=F+EksV1!&5bnqfA@_f^63)n z9IdlG(#=zODc)&|^gLOlG)|1k`nOu8tHj;Jf|gsd3*z2_e_Of>Dw_`1u^KP)-I{kc z619gKHb{fjQL5RB4!RAFQucVB)-}{;SkUh_VM!kn%^km`%d>uFkI9vHs!2bTwJAx} zZf5&|(Z!MH{YzW|-M(_y;yKiOczgNYm~Eb~o**riR7n=KhN)^rPb9a6!Lrq|0AYl< zxaCgs(D8h17=B{#WGzCeynI-8}C?KyZgzh zS#3IB%=Yi}D@T_#IL$lbYtpcG5pmogLBMeTt{yjBxp|y+ZW1F@$$212uZEE?cSfNif6`G$q zJy=oRbin(dp%Iy}Gm?TcGBO`$MrIGp5p}A{3CQB5PD+u-osN!=`7h*IKu@xC~` zdN;@M1#cQHIML!Sw8`#NbJNqy`nvft? zCt(U`b=8&`mTM0vEz%hAN5vlAI9M3Tg$-5SQ?px;MS$-{QAx%xaq3 z!pP=mPO2wrUK{_ALF@&tFL}$o&Iehd(FFhY>tWDqT3SiQpR9@LZBrvt`g9Pt>l)t} zy)b;9-%SsN`xw^|taw^uo^9G@b(rj}iOM~)U&?Szu40Vzo5)qvM_M51BH{~o@Gmqq zHCHtU^3OKsH9V=`-_)_?xTL@Iqr|T8&~30~F#QlacG`6&^hf7KU5+-#bxoX`IwWIQ z`j)gE>HAXE$y1X@bokbGNz~Mk4MA`HUA)$F`yszb36>8&)5zwl%|uh@rn!QV zlEKOp^*YTNL%6*jdCfh=`{^O_w}(xNtxVX|eo#`Mlxyi{GtXvhN>iom?3mY~UAxw} zeUS@-WBu-X9(3op)S_AB1lv06L-R>J-fB>eSAJKnQ$CYlmz)vr7B3P%1?^VypEO@- zKGA%unQUrpL>hICmHfrRO0m22qP&A5V3)KyI$^PY@>R%pmH#{`@XI$U*=A@y? z`;u2B(@CjGpAr+>7sVAtjS8C=B=f)LJ(ZWpeuQQ-kEjcd^A=M7UhAUI)@xcZ^%+H5 zWna}xrH6tm-6Ay{lNlUONYi7vy})SE}>_f36`nG z8hH7L83t?rRew`&Yn|09P!Cl`C^T}5T%hQrh?5=>zYrsm_R=9zL^4MFSJEtBtWMJk zwMz_hEgmF`xXC8CyWHRV@Pl$A+D4W}b%~wbc58x1J8AsIwz;wIqU}+gBOe8e{k*-e zxvk|b<}Sn@(o3Dm_JOuNmUBjhZlpF^i|RUPE7TKIE~*@rQe{-?WJ6@%rMD&J()QB# z;_c#bVv9s2Z&nUcT~Slo8q*%fA1aawA=oaj+*N+IfU=+)VXq=Q+61%}#eI&Ajhz-d zI`(aww~;?Wmjrk9m-{aCX?EMnO(T{fa%3gb-_dMV8)^-|jXXpD)*GrGs#KLqC08cO z17#beQPRt@CRs1ZX7N1nItd1^`nM{&DNm~J>8_g6Y=r$^r;te_NZvCKr3dYk5YQ(y zEsBY38?_;NZp@1|#y0C>#F6*H^F#jz-w)RK)p(wC>&5NDEn_j{%Ye@$1b^SeNEb;_`6tyD z^?uDEEou;$&)N@BCzv*PM^2FIRIksz-~6#)mypfjJ)+M>mqvGtJ{8q2x*|#)F*fvD z(E0!YjO}{uA#hp7*^i^xIbDp^;@-C{aCqG?kPVb{V7XQ zjFWbej+FM7ZdB}4tWsQ2RjJQwM;qA|t}VuCrgO0goM*h-?i;)_{Zay>!mfv}jGP&@ z6>d-3#H@^-6j>QIGW1yR+#o;yY_E6jBG&=jS?qDxJGzW)u-mN~%SzJ=gG6U&{ik(z zYooeenXPP=8{}=2zh!HsCaIs)E)&U*%Ua}f6u~M@E3Lh)uP`-Q^PC}!3%-DL-zC+3 z8+4uh2#gKs7xpYHA+j*KFghmspGZ978w_1O74j9jd*6CXJ#M;LxmrSlMI%e8Rn8Z- zEf$sOk^$4t)@fQVYnH1|DxHdQr9`2WU6IX~4wlh!mW%}Xm&(tpcBs=drP@FG>!!iB ztxh+31M0()ab#|^SDDXu|E__1gKvg^jhG$jh&UB~C1OluLd1~J$eqw*~A`QfG7nH~K*zQ&3tq5oct`E5yx-diZ`gT7szkO+9+14FDgY!o$8zVgO1d-On3(rhz24JCRH zJ+39yG3rWnca5tmUYVufDgLN_D=#SQN`F<8=8yIt?P-0n(ZdpJFLlhPt|ATi1J+jV zY`4iCAH660-ty%KkU=v-UWUknmIiMN(F8vZa`-EJr}_5v`R=*Y?Y@hH)4(dm{m@YQ z0vYODZ`*6}F^8FM7?$gTw5wY0w7$~tRn5voB}eU|7ATe~hbkLXt6R@$=C&s2%Jq5X zLzZY;gX1W*7I}r8UyvAmr23DgB`)_pfZ2H|1JL(-=5w) zk2trvyx}hW*vYsZ=CmKA^o~ONOY2BWmMO_#)!o*G>4?_vYCm_ zz~g|CfqMd){9JrmJQqP%VLq>vt77#c8nFrl|6CDLFU_03! z!E1E^?01Q7s&=1thjyf9wpyUFsupXq)T>pU)IZcz>tXF(EvY+XSZEHkuCdK?{2>1_ zchUEF4|cdqIj_!brN;!XS3U;6(|%QcdjGQl!vbdn9Q0H9vi*{MeZ22^JcW11S*`;) zi&+&|KQtYwC%-uUvs<8JV7+Ohp|8G!UaVc(x?eL=Jwy|w*{u?Q?@H3lZq04o(rVOZ z8g>|unz~q0Y!jTTsUNf}x)Z<4)MUk7=@z(Jj&@w(_)3wGMT(+OB%3 z=4#EFj+*q=tJ-XXz%avj$MnZiW$);$BG1qoq#pNWg>c%s9Oh}=xgLCvVlUd~kYAqf zCZ8zZ!G2D^*}liUzIZlz)qCE9riM*iD>s+pPyCCK@J5kK?{@sKacq2eCDvf*qW?#C zMgL11-uhQ_L)}grr47)OYnn8}be#ukxkoG_0!E;78=-PacA7imwlVp^d_ukEF4qdlxUs{dse zYuayKYhGowI(9kMtL%lv(7pt4nTBI%0 zC2PlMrCL-UZQN({H=Z^Lt@mwD?8}@VoRQ2tbQU@UuOQk(cghl%34o(@?)e_yJcdH2 zUx}Bi_kW(RJY!%Y8ppH5{UYz3YXL9RWjp5|mM4A+KZTy4XH!d^tKrQp4gGGfOhu+% z2BAJdcT&4Z-=NI|UAwQ{ua7Y(_0@*Yrg#fs)!VAzwO$cDkd8s7qYto)tfTA!oGj>1 zaqv#V+tXWaCp`vw={%-_w7)&Pz3M!=yKiz6ySch`aaC|Pun)4IvDRRd&_t$%IztH@ z>ut-dhb@?Si0QK767=(R(AQ~;ATpexd!~2jiuB_QO~wfGN_cU*!x471!=^#r3`GGhVVs8T8xj_xRv`*t5H*i-*;HoO=fJsuc5>{R5QyLw7xN7Cz;L z@%XNjT_U&-*<*=`L?FHb>CS{vRnAt2(e}`?%BsA;^*sG>olQ4Rr_$#c z`x(y}ea$?p!1~61(Z1U;l)g*XF}`RwbQM9eq^v>gXPj>?^IT(jIj-@%a`y*r7Pm^b zZf+4CyP@Z725%2fF_#>1uT6jGqi=45N$gtJeC|ddgW%hEm~7G98OL@sao>qKsI~4&s(^Z*$ji zgLzYVYk54k9ngie);-cK&}|s+G4C}`=SsNz;HaS|={IW-aS)w><)H<1Fco}{Hjc^UzW87KX#jfG5Gh73BT1rwCUyL!3~ay{$P#2v}m z#;#%WiFbG)76IHH59uvr8N@~dY(uOv%RZCR*b}7Ewi17w9u-izqM)whI^HPl-ir2|I!l#F@xd!}`9i2`&#^8eQ!!gIyQ8_H)g3 zmAIt3ByiKXeK@1psjR=)dw9RAV)oDj$Qb8M=x$nX{bUZY)S5P#%*OqO_r|lvb;eXv zxaqO^s3qK5Y4x(lIwv}XVJV>N?K$Tyw=a6N8BPtaWS+ zJBag+O>qWuhjVG}6mFu+PnS#=H;{LtOEq^J_Z8nst2XQGYBM`i$hm3#^B zyZ_hKrkAE7czaxA@v>QMSL{6;hn!QXkCcP{hY5mL-v_Y( zdniz*a z!tzlPJoQVm-nqfi(f-;>TRbc)&B-wTXsRj8G~A>!4Kt6h7%fMw70{KXbQqj^Qcg{v z;k`Su11-e5VM%xf@thD6M?mg0_9OOB_ICCR&VL*+Cx@%#T<0>}``kcoB6k7z0%s%# zV^hQcmOCNC!qJ|Lk^V^?g#GSopJTgcZMO2wjiv+UTGK9bceBbg#PZkj-m0>~+jDsJ zzlKaAk5kRSI)1u z8e*@qPj?J*+MMS>;(ox{^OTu`4g@WY!CzxNah&);JS1F*%ft{?73(wWJZl*HHam&q z!$CN=IG;GHIQQ9A>{QN7_AS;B;wU~4Pr-cA5XP16Lz$ry<(hrI?K#+WJL^P?(X2De z%$MMEjJ0Oj?%KQ@Ct&3|@+vip{zac-RLD_OfR;diLmGY!WIlp7;%kY6gpvqgd9ucW z{2ca5wkyZL7PCD#F`Oj$ZabS{^<{k^x)8gd&+rTKhq*zc^m&K~4fcHS9>1-vmi6#; z-ZCFLt|nVgS*JlKOG3zyoxk z7L$FQzo3uws7+#h4qZ2umQ3q*=s%iii?*+aZnUXRHkl6{48!SsV2#>>d_upV*U$mit0rsJ7)uqhTAvVR$FgbFIk6JDQk+Y7fiIk9lIUtocl=$`GdMg$3hQ&3-TV> zh2BB~u%Fm&YykEY`vz>j=aufWd`1;kdulNHXo&Qi0Uv7WN@tnsX#un!%G zJwPjV2F*feB31NLue`Gk8f^EUZV`H%ztQ6~oNiZRH4LgU=!k6O(L?Cew?6;6LpLLCOfVG+B zWJR);5qpS!I2(V5;ixZ?zy#4FseNP*XJ<#X-PLZlmD?Jv<82ph3v7$*jdnliglls4 zA?H&c>F4w^=6}pzr_k@{IJ63V4c)Wl*a++<8j6j=FiebH!2+;UoP~G6^MDv_4Dp_L zK)AC`v$9zg&fizj`&Wvv5pcGh=HtNq7yz5FF|jhQ_&sF5n4mBp=(*}6gwRD7)OMCGxSy} zZJ+G4JhgL)hFZ@>WhgG z0{aV^pMn3tDzJt4J6wx*Ck_!d{36s}N8%G9m+pcUV%w2PfT0`c-INzKlgxE4a9(gA zj*E`f(BmnG=|MTp;m!)^Xs(9-_1^R+W;>IFOhC6lkK1|F2bH6*(EaEFv^z?m!_lYc zek2#zxXvSq&~5b$m1Fe$wHFl z?BSdYUAe=Y9UNC2ZJmrGf!qy!NSml>^f{W%{0j_c+o4bDCh`Y$z>5E&Q_#L>Pjoga zK*UHtG!5yCUPSN0zV-nS0RR z^&I-s0IxGw3eoaC8 z!peU1Bx)A9k@O;?o!6ap4y8lk9PN}ir;-=R<&+P+ zji$nxKSHJ=RY*_ht$KxMm>&oa{f%6JotL9~u@X#)>9Kiu8on1lhCj#J;G3{=v<$Sg z3-XR>qTAC)sIJrmaspZB9OMiqr#iPfv&mP^7vwmYV#lTT(;Q|ra}pT>YivhvA10(ad#qsHjsbG%~TgEjDAL^F>Qcpa1gNR zJ%N})g_NTokoo9p^c$S;DP$TVWTqiTh{h)(>A)d(6?x7)gb5h?kxl3u=#AE(|A3wE z$GYQk^aM5=8-#jeEOaU|2iXSfcAJ4o?l83kR_aA&QDx*|at8H@+(s4B!|1m(#vEd< zGM9lbZzb{%>O=z3D{>VB(R5KFM@|KbZ%7ezkyk zH&6rUdV5Ta=3~X!ZkWk&1oScc0fISA3AT}z>KSdOn>AJ zaPYaIlhB)BWqq;hXgSKmCZZ3}bo4gprZ=)1I0ciK%P=3X4}FjlQ)cP{)ty#TNpvNx zqNl^Z`U5x_A0eseCD`K*px65#w-dweZvh;X1B>GsW(yO^e4xiO?u-Xa{E*YTfTNd# z^hegfPcg`H;211Mo6vRWbMz}U^XNfzEUlzg({}*5 zvY777Cngcug{(%JVCJj^`Xe&{J-dQM{Q*qt4W9EW^B>a7$Z^Do=#lm?4Pz<#5}gY;Fc(b%L^zHV0zczx&~P?mh4}I~;KmAiCw-p&4V|>E zFwN&OFi{Q%-TH|5p_yoZG#j$_c3@8^dJR|+bHJh=GDDec`1@Su3%!RXfrSx)6IX(d z5&b{o;yUC5QjIJGD~ktqz6^^ggQ%|* zv?U5yDks72Zbg5=B#-gvcjPR{{tP4;1?-_;ndiVe8NpyoA+SE)fX^Mo^aReuK`_ni z0&^cuO3GXZj5z~Vu0-6?-DnfwoEX^*e2#4qGeq%1rXT3#9>$C54_uZn={d}4h&CPY zvZ4U6!hpD--snhFj%)!oNFAK_Z8(8q*sm5itCg_R&*`6Fop0%hbUdto6;8^H5z;NFH(k zaX{?H2R)sFZbkD!$6`63I%x-&fuxG&SeN=CyJ3Ja|5&P-t* z1IK12{Om};ytBw#NF`84&6mav5}(LfWF! zfQvH+yzvZR^lGZOeu`++942OiN3V5I#I?BNnpj+DV9i4o{T$OD%F z1L!97EFhvQ#J)d~!^k{f`+NzWJCylDm(ay@27EPAaw?UUgO5w1yU`Z`69lw3vlbAz zib(*CN{5-V13=y*u=XirFR~HY1pK8_VDEP$!vTA~!oMqnu4V-?2%_Af5UUk7~a z4@Alo0>0#eejY^r0nc&@&i@ed1n}Z1Xx{~dMl47JG6`(A2f7##elJ8i<$zt6LGF&Q zHjdr_ZApRisf7+`Z$<+8oI>}eS@a{yLLH$V1Ec8!iUV?I13J6_bm4*pENAWlDhZg* z$THB8V$jipu&Y zaJxQXb}&9*g?H%fu&x*Vges;`$X+f{&*2wI4T4(j2>J>jlMh(TOz`tA@M#M`uM1%1 zTgZ8^Hw3%^g`@zc_CSw=Z#WO0eKAU*M({PCAgccixw9SgY#%xdEeBlK1u}O;jxi&c zbVflhr5Dj{>66g${+L<>b(KQ!J!-0)GEywMBfX3kfxXw#2Ozq+0zK*<09RMTxn4vn zz+z=cI}`)!xe1o@5F+YXs3)d@Nr7RQ1(l*6SSI!iy^H>aoj8D|pa+pkh`pTPFUCV0 zDFJJCp>yeAm=sY*O{emy3Tgqhk-AA;rbJW}oI@cn+m_KS5I>J)?!lgyG2`KsH^V-+ z0xr|Y8t@}a&=#;DrmzXZ*<4hXbN@*q!*z65M#?AF8ddl(f_yGEan^7 z&rZ6UBIpLH7iidgnBH-j>P@YsZcuM2K84Xe=)Sa#u7~w6!P+HEPnce_8cz8((g^!J z8hpY>(56@DaEQqBuz?tZZp4NGzwKyj4CV{@SRS?mBt8f+Xe{~!n4UXB#=Ca`J=H3;AdB(Cqeh8fsIH2qv8Rf&(m$_ zI691;3e3H$C=~QNiHf2YQk|$t)CsDLDxu=(QSjNgj0SvQ30OEAEPE0}O)h8}WDMU> zK4|a^kZ(Hn7vy}4-Nlw-53uvt8rbpa7zv);4eJXG!cpjCu)ZkBQ+9!GFQX5GJ*%iD zu;WB(Gc}yjlM^T#=>iqgt}st?BlQTr+S9W@at@=WQ-LM=Et3iseHPIn!(bQpp=`_x z)_n(2;!VtmUBXmY6ZQnYu41dO-cTViqfh>qJ3@5O0DGN}d;@-FsASM*K!-YlO+AP6 zjie?}9bg(y0Yp}AR0@?2yS^E8u9C{9=h3UdkHx`MrY6W9CPRLJgB@*wDu^2NX)Nq@ zGxi*7#k}!asKgi{H~atS4ZH}Dp7VFJrq zxSQ%r@u)9k8>*I6k_ps!svq?a)e2{@ls-UD2Ok^`o~9S@JzoH8P62#&pnWh0+5%DH zzt{@=I~InI!{1}ExCh>V-Nqhb{V*vy1)C4K+#<9PxdI;G67zt8=$`%$r1qjeLT12& z3B>y`Uu$m35bKLU}D7t@C~CN|Gx^_y9(=! z<=|$hK%9Y^S6_S$9srpU2mgg_{{OWKau~ zxljYBjxd2JfvO~*lQx*T!-o@J0DpRg@}l2UrvX*O^j(P6aPU}_fQVaBB}$@4uwU3^ zY&0HeFn-wbCGk`x@2S4`)Y`+G14mN28EG}et%xkc!Fu>_^kTL9_ zWN`E0N}Yq3+>gmeuyPftCxfYpR1ZM&Fz}yUi3V3o8RZy#(T+J>Xr%5M7UebN&i@*bRJS3pIu64|`ij zB9suamCxio_~6r<;?wasybkp2UqCek=7dB-l!K(RQgJ@t@m zB7c#a$O~}VXUWTCJ!yq`C5K?@mWi53@1s+gQGlaE!8`gxMBE59mt&YK?gdhA#dqQg z{5k#>|AMcBT2~+V(`VQjYzWk0-k^O@Gej%V;OjpFils8&=~WN~wWrHLw>MA$Q2DWu zN6A~{DRMr!1HL;?-UTh!L*90ZdPZeKymF5o4%!(B%;q+zgOq{h9t0asgFSu;(w5*1 zUXA~T)pz1!;oNoDHn2Hw>=x9f7^uLmgb3~r_}u`e8Zxj6bO`MCVK~o`6o(Rk#HYz) zP!m{4t|qsUC&;U$hzx+!xCgbpArPm=K~#*xzhOj{fG5txj$j%r5BSHQ<2Ar(X2U6* z4^p4Q|A8u-2{3ID=7xQQb7mlNSpXdIzW^0F!b#tyN78|`0&=sJR4z!}OnxJ8kju!W z$@l@N`L!5#Df{p-hWdh#y5m2`}Mfax*XfF8Z8{jQSG8J_DDtU#>B`3qW z)5y8xO{jmoAbU^)!GHGz{~@8LF~68K5Pbzh<~162TZT;nPW2W%oDksM2w%bidtQcL zz?b1k_&u=dUf5Ic)G3goT!Sn_4H;A_BZl4kkIttV>OM7w+6mr*54GBrAn9hZCpiRG z9!rh@UHb%8pLu{ajnriN9X%GZ2{(wsvZ1OMjqQbsWtN}!uL)B2jAa?VEzZ(cxn2er8X%ze;HVQuh^(0Rso9GWU^DLMKOu{GGg%1F$ zzKjjW6zCaxNp-{!Uj86ar ze+IFFFYNa?(6K`hr~4ppz^;5i<{R_^@Y3bf9kAK{P+R;+ULyYii3?$_Z2}ojrjxxv z&!3QLGM73Eh_VQ7)5k$JGXyFwn^6Lr3+UMgKMHysL*x>3Vf85>c_^IrQ+z2Ngjc|4 z3&%?W6ie&^Az=ma?F2@q*dMr%RG{(zsj1G*hX^dM#uTZri(buPhziPszO0$c(o-41&P zbt2%;8tq6con_Fz3Q31CS9 z`UP2!^nxnS3CQYj<}*YlV?etlfKwBwZgAK91FWiu98JcO9weI#f;wXwpvqxVO!kL3 z#S`*gJzdPWLN1jKu|XEZ07-zCt#}I2o!A67GHZ!-!~i0WP~sQiw!{k2Q2|70I{>+j zkOk#|c5i~pXgni=ibF3tfEH3;!DA&uH1-BCa}AjXw|qV%LE4?3aMlB%l6xBv?an%S*H-Ka6dK={N+1f>S=~*%LJ&@ zTmtM?GhZQhNeApc0rAE_+Lb;_?ExH%qA=Cm14V*?96Gi35sWh06I!x&Zt} zXUH?Qpj?P@op2lV7j9hBiJio8VmEP-m`&t?RsY6UK=o3FO~K-@mrx~&M4v)D(-C>V zY=(-SE1dLZIB6qggXp6VM1XvVNv^>D=97UiDb@%#egu4mDS&OCAX3{-`9n=eLEmJ0 zAm1S-;=|-$F}4o0+YR=3G;tm7LN>r%cVED8KKScSxBz@O2jYVrC>v@mlMoL?3z^zz zs9BT%j!lPq^$*}TW>XR1rJs|<_Un9rZ80nfMy=HhlBF;eYRI(2ZXoJbBL*OM649WbXC)D;Fn*!0wT78}a~ZJS?ON>QCL_)twN0 z7$c_*ny#}doKzx3Hj)s#?hsZH;HTT?q~$zMSR3pfsTELJf|zR7gkO{Pe2SBgxrIQ-y|jxEbAIn zq;lyr`UZ6xvMLjaQfC3JN0F{zRT@}V08R@2vG)+VJxn~=D+G4>vCdR0~T|CA&^AoMDsBOq1j zC`FN`pr9hX_zI#T76gy%k}R2 z%*6NipZUx@_qq3+v-e(m?S1w+_dYW#vcB59XPurYNZ!A*Lg(?Ji?Mo>p?UE1w;B0E zgZB>HMLn|RzauQ5T*#u*BfDczR#<5)NQL{-~a%*$df;Pnmy`2%5-W z?%kEV{d;5|F=`j;lV_>WHtN5(@ASS^`tD>`qo?;?=E@H;Z(PjUZapIFR&wZ`L*rOk zUQczhDHXzB2YyZNUXw^3ocRL$o)hHvq#Au-U}xry^@ko~&+Mtr57;}nuy-wL`X&2+ z+y83+(xbl1obgjUy&vp;|EMRKOFqTg=VUncIQIrOWgqreu=_1`ZI9_}$vpi871Nrex8hVG*#Fd{AJatM0987yaOK}IF!0!31;%w29Fr(W&OVj zd+1xU*7y?l_CCw{W-8U^j?7hu@%In68@0x$DXjALX0>qzI|y@d=X)>qBCqH=fJ?s9wdL5IqX7K zeS5HzunlwN-qifxXOHzyz3=b)BOKeGb;g23)4o(hJB|80lHX&cb5Q?6{SWb^!6JQ6 zuvfPbJ5)cy(+hG};&?p05_=c=`frVMi~GMr#eX+>ZZ)d0 zli6v1n){+#u;cs;p8hI(&TnyN@7F`S3@twN9{WOHW?$}AYJpP+J_E)pl2PWPI$DKU z`kaAhsfsS=uIo8Nt25{9(DPW&|FDKXgq6&8@aeJs_frXef*iIfGxQARu!C6JJ;7ee zI(^Uf9>=P33cHtIW`BHg=Mr{wH{ssGW84qfxAF8ftg$KlgatbXrM)Ywjg_bpmL4#ey#FVxuzFZytbxS~Qh(me9rP{whj@19 zfBF{eyPSRY72()#*fU;&`yhX1_ha+M)8`L;gBj~R?s5Kzy1sw#33e-XrpncC#g{wtU-M^S|>KB}Kga$WzAcuHyv zx#TN-OR>s1vUfeIvGaQNWkvgL=W0B?4R^_2X0QE-p$&#+8+wQenE&T9IElzQiPh|9 zkh(4_ti8x<*AL81&2-z~8t`HzR>Nzu>%28P=^OQ3*f%G$<@r3J&@*Z*&w-6*MSUY{ z%FiMBdUiyYqwYSjcSG)8UBa%z2Ha7(lUi_Z?y*i~b^Ie{>P7JM`K(&kr8>Eu`RRax zFOsu2ZPt}14ctK8y(9Z3%TxU>&^f&`zUP9T_fzXE$G!4*`##)%A_z}J_B%YG@i++l zq+d@T-NbIB;_B4iPjTn$7S^I$^o-?6o8Pg%Tbmqu4;AemLmwD=js2qU4X#P`{Q#@y zA5(pAPY&Oe|1EKRQlV$j>KXYyC50#Pe%Xi74nk~=cF6D>VzxWq>2-{KF zPa#vE#A;_RRvEhw?9cJ#17ij^8$6495C;yu!?Vt>@?Scpv0w8%F@1U8g7Et!c8VVB zf1c-RZzZc8$qc;+716PMoAeDaU+u|G~~LvQNLq{>>D7yH#5d>kvwx?%fVlAuVY_!`*-R*LM8Zc&u6HL*5m1`w|O4r2dvs3 z#O}LT+n$BwPf~y0+xK(!yl3*n*0-7(^sha8vx6`bPyd|z1uGF*H&gfTOiVw=KGfF+ z*Jm&G(Sd(p>G!CG4&?VKtbN{Q6@SLy%Y(accV~0hy$SW$M(mm_&Ac#yCs4jdP4*YO zeG5-|p3iRNr>MD~WiRJ*ePg-r@FVW3yvhBm?{Nog9(JkDVz%6nyBqg5b?90{Q@MZj zLsmPBf%jEd{S#)3L&+;=!muIs=8omQ-WIG8*5ba-2YSxuNu85DeF$~YJe@~}E?_@-9aea^ z4*rz+@crxy-pL;9sRPHrv||P?VE5o%Z2lp4KepgWo$>4dEXXK*$0Xf zrvDr;|2@q9cK_!1`6>4K_u$^fgVcfB_AX34b{e}TEA_nGxvuj=R`QGUyusgz=}mcp z;1TYc?aR|LV+Wsw&wrrW{@K9kWb%9P^V(#Z2dM*p!~M&ncs^l2YX03>>8uKm?(cga zada?yald8;`7zjUiR1}zV6VQF*lRzVdoN3nP0r^Y>DpxPTd2ssKwb0}^Yk&q^b$kQ z5B`On{w-KhzcFw>`_AWr`31Z_0=G9}Pw*k`ah$?a0{fCdcjXSXo(7qpn*N5q>3ysB z??y%VW3tFM`~Qc!cR^N^7w}Zj>V0q1>gznU@Cy6ghjDLX4(@{KKH%Ey3*I;MTjuH2 zi0P}CleQmRk+u8;c26#)9=V8>>~r{eQ|8h8xqo})(Azwn@i3n-znUHIlX&uE8)lAY z`{rks_-g-tNZ*fW-h?NvCNy_d*5NMJmE2p{h`siIaA#wip2fJAbOABFNoO%U{d=mD z)v5dMrbgI@dr0$gSLfb=e-TgDqroKh`sL@_2j7R}r-ydn>BNkbNU7x_bs*9he8sA3AswfA{`?p{MwdmlyGr_;Kuy?MY-U$jtD! zz8Cry>R%t(yYjPX|0-n9C;Kk$JCLWj$5IC#%`*V=_CCrT&M)+=LiWCayX-rXy`=?I?1Ux-|CGrR3)bmrs^+EYBmaW;{) z4fV(U)ZZ&2dsB9Y*6#lR75CHZI3L2@oVmEC_X~Cemt?N|BeluK%+vR=%K9RA4F`vA zrvl!cwdK34bxsEF4-bxKu6~I+=xJ>442-8X{o&xvgLCqHz_mjwkpJGI9=o|`HSU(Y z2*3HO=Ij8E?O(Bf)&6y{cv0r($3fz7?p@B;_hj!m~&)}_r(X4*9WxwS5!Lh8mFX!I;cUfV5l0D>k zdiUcw%SEUGPVc*;Z))E>{Y!H{V_En$ogLwe*>V0HweNGi7s2jzd*4IXpY&|cQzkF4 z3;TVZ57B!1B6blr9a>=MDRSshR7UF$E>0ah$X$&-_6b*IrTZiHlEx5y7jx%uKX$y= zW+!(>&$g`Zrt|dj5q+2TJ=!;|Z(RQZe2=CMdkD0D#D2+geUo9s_u$x^y${o8U+&Sq z!!v3>Ww&TaYVXU2emwLUB)^WQf5!dcEs!>zoH9G}=91{~Mdp(mnI}I-7VC!{S9O-) z>8}Yqb9X}T~8EmhF#~ydM7ZmA4G-F zOZGm43Sl*#d%K#7YzM0T>3CE8-hn6JRv?EjhV84dSF#_gfjbAsq1|~yGlsS%_s+>4 z+nv;c-(WVHtM6mX#%EF;J>5Kl4xf%#;IzBSm1yqP;!>Vff3o?$pLN%n_+VF78ohn@_MS!!`azy{yT0cbJiSEE zi|pYZL@hW6RsT6W_wOXV2zS+)sLb=ubR%wiUb2FO#dzgim|H=grC2{67zlkKk#9e}nd?vH2{VS3-bXNF?oNE|$`0kh>_J|^^B376uwX9^+4Wl)VDY@<*mKH;STNE)FyZFG}3N7 zll2l+=%M7XId}@;OzzWcI<(Nx8-w?P?JucC4`FURn9Oo2_qXn3O}7jc+s}BaZn4hp zJY71OdhCbX6CcNXZ~|7nMP2q$*!-=&@58O5@bXt0$>X_Ce?QL@9Yi*nhvxv!Wlg&V z+2kr}ldZ^Ok8$VlJIq+i49y&Tl-;27xp#RiIrHejQ;6^@dH$r2%)T2L{yz3*G8#UTQvi2Ip{At)cFLl&O zoyS;he36~!XL^=^WheGt$LujT@%u^a{vUP$_ad`x!#ueJ`+-mLtiW+iHksPY8=Lja zOKoyG&-T2Z=THB|8e$jLcXJKB$^Gen5l81B{cK*Z`iXLs&@xNEu4&^xSIZ(`s5LY}PoGnwUPoF~8VxN7B&X1@e zJDrW#kG!PkZJx*5gDUu+z0dXb_Khcde7tX4X2@;PVO`K3!~MeR;n-KvaQ5B@*?HcT zXDTOBVI06L-QVGpO5DHOVrVIH=yO*klw9%#$_TN+Ml}{Zukne=wnoZhr+QHc%o{`;1l@vI%4St6lWxjfV{C*5iQ7zAI`z2%(%~&^*3qQ>|egd=f z;mm^{BzsRIV?9hH-G+xB8GMx|_ZDY0urn*6zwj*8*v>}abUyXL!aU*l19s0R^Blq_ znYVt;lT=SqkxKT;eQUzE)nM3s+!ub7=Xy@;{Q~vxyF4-fBc3Oompt|h?xBrmmi{|W z-tNL(;)US%OM?^0)DQCed3-!OcTqQk*(cKGIc)wI&)c5I6RKmWpm|b;XCQkRb4Nb& za1N0@0jAAMHL?n8j8*uLbn~(Y`XsaIFUck!A)DL}FL&nY@W(p8?d-wo?p1apj;F@k z7>|yoCYv<)9G1U0_$HsMS(vJLJD7bc`F#@4>}^c`{59*sLFSD;m^rQ`mn}wB{WW6e zuXuYhbI3UI=CWA4Xy4q}{2cYc8LWstP8F&(?H8G`o@1{33VUHBp~<<7=you!Ga!(iEcJfHY}Dvtx0i~ojK zCzHwN?wh}FA?`-cK^A+S=fi)`vtys?eP8eM)NtS6ZqhsK*c{3o;vSwXI+HxMEl++d z#5!vRviT_a;2b=^w<JPq?2&m(U_wm+F?xL)bZ$5V=5d{> zJ+ce2a01WZcetB42FdU8DTKS5JoZ0LHo1Y?WHa_cZ)0z6TXOf)@aIS5=8q9s3vthM z6uTI6u>x3?GrJJWzu@lmqtszbA^EG!?3b~B(8GG=(`4c^8>{EyS;5Z}*{31p0iO1G z7tBYI!)CIJ^&l0*Db&duHXJ*Ns%T+$Y|h~Mhow6&QiJ{!25(6YT@+i#@x3rUUK8K{ z4{NG3sPiA^a}Z0h^8PCOOBb=Wdxt9YqwwW8W`c)_>m|7}_Z1L7ulE`v>19|xfaNpD zFpqQQH}GJ~-nn@a_1E-Sf*qT)SZ%C84SF3l$2Z9)A7Kx8S)L^u&+BTu+kq=X4t3>na z?AU&dXFV>UnwrQx@#TBA>e+{R;7`ofQ+pQWS&}`Fa3;CqJ}QP+df&w3FJtqKWY8nw z*fMbJVs!m58T1PFExbO67GRbjd zv77jeM~9W=20WF2IQLzz=LzRgd_rI=W{2aDa}C~o2HBI5{BKr57xKLG-c&_1$N)#e zu^uw$51D`FgJXYXmfm-08}iAA$R2CK>aDV|?U8%}yWQ7!p6I;W znV+@kHhhNSSSs*4dR}LrZY@4trO5s>RsO$`{UVvc3%uV8OjQ3K4>RZOxr0xI ze42I6b3<3cvHkJu=B)76<>#YRx_cAlr-1eC>@$yoUz>10;sEY^{egY8hw=1i>Z%X- z?7~yPzrfdzl1Z1N^7%6NzJJG#!QIGwju?9qwExarx-U=j&%w^~ujsKT8T9mK&s#g8 zr!!;iiq)IItWB_Z2QWAstS=pUfGjeWrzAe!*%j}cNanth>UT^l`!T}xf?Z=~Oe7H3fj?o5V%o7(X(_9Lf*+hJs;IXX|`&y$JiFETf6*I4}Jq3==a zUc`Lz5_?ihq2K42FTT&*dM>?g;`1|;$YkTFZ#M4Pk%h$ib*Vpms zwY%Z=mDDXqQ!lR0102_qK|jW4TyA8y{gbS)p2N0N@$^^l?=D!rC%gWNs7t7JCzC~1 zK);>1CvXI_B{vg?N2(Ydc-_u{N` z&cu5wQiI+J-p6D0K0{yP=iBT}p2Qq>4bQUkzv#(yTk)jX!7%#_R^OL%U-ovaq8J|kMjGE+XqUv1MBm0os$HDHis2`VQoudr;VLlb~ zIPvpK_`N?h?tWf8hC9T+BaYt!C`vj;ioDYD^LnkO%wq{2K8iO2DMGP~)Q5ou48ITzvTr>MXVL&x9q+}0JWaBlD1 z!}mjcPb8yEXU<%T{qt?9!cSrk>M3@QmZCP>ll<{h5I>`ld@%c*Iy8CgW@2f=ObVfRyxpE0A z?+38+56C_f$$x;^4^fNs!hsD~6MUO`@iesj7x_zA-%CyO2>(U=Vdm z0oCkrHJ{}e%ebfgR32fb<{O zZ@h*4^(vpzSe{vGHzc3HPTZw<`9>t)#i!iv!|wZF*<Jkj(s^3w;IH(uf?{eP0nE?}m(0)9O~l|45) zZN_Ix4yMvR7s*!<$#>%4d&y$=@bdu2kK^lUJb|%n&$Y0y)*lmA^nd4G&*Cj~46@I=ZVxOVAYk#{X3QL-SF!@Z2llM=a-oue~Fj*YZ1tLknevZ z`EI^%=lDM6OwC;5VC+Z9Wydi8Kfqng1zAIGOCJ3ec7GR}cjhy@LH1Qx`~Z??u$o$nnEEO#{0({Ya^&6#ryl)}vi#X5GNg z%^Xc08e+Cs6Xt(~dkJTdMXpB9BUs4)wZ*@WG;(il{QV?3dhkD;c&m+ELiD&eg``3uP1 z8$WN%J(XVWBwfr(bi;(PG%?VP!ZO#U#Myvj2ei@>toh?-wf|4-_fohKbOU}dvA_4cm#d_!{E7E#o>lx*Mo}|0Lghn&*yRCt6OT=3l3?EcY1RLGnZ-Poo-{ zpS|eK*lGPfc~1Pg4~+Q-6XeTRvGH+Ob{CRwZK{;J(Bws;dI9*o3l+*~@cZ$GWh*0j zGh}Z{4qF@CM)0xl^!H-Be*>8Zz<3gK(-dr;i0$`-@m-DV z8$jf4uD;32V_B-5y_vPnr}}@L8hqK_53vIHILuxhhK*uB@J0~XlNn}A&qIuRD0fOX zfKg+q65c`bDC~b9`|qD*raBr-E`mw-G4=~&%Ga2kCSmaeq}_?HFQHnviPtAVVg{e7 z_L}E+_)}SQZtfke!~ME-(O_X_t5;Y7o=Tm(4!islVD1S>_&7TQbF!94QEZ=^y^Pg~ z=6%59cdTk|!pl#wGc*;sFY)?3vY!I$tHJt8ynQ>epQl1un7ya%$Q37&q3`6g?{o2N z$p^7_HP+BevX-3)7N;?veu8Rj5}rDa?7b=T!{Q)5J4_pk%^!f-n-R^2!|f|U`w=8g z0nxXRJ(<1NCy~eBqG2Ank$LPIJpByU#`1sUH|DdwKOk=JVWrp4(|HRcc@;9){H!x3 zvPb<#_ANe6HSr>-ozA@QIp(Zos0H|wQ^NGHY2QI&a zth@2{KX^9tIv99g!@wbGqm5W?9l^b=OR;bgcrQf7yBrqJ1H&dWi=N5VZD98x{eDmE z>`LBP5k@aS)?5^Rtwp5m0_(qzH!jAm3848FatHYRCOS;O@Aot|OY-&j;~tndNMwD8 z`SPoLTA#lZ!(2U`Rpz36FOKAy`2IG~IGkL!EOGS+emowJezb||rI5P>l2>P*`W$zS zkB8g;0@KIg^qW{afX#37I+1=4fcSL{+E>!zUQl_5b;r`|2k*lU`~~>>F(h|b>n(!Z zvAr|E;0`o8iqD6y$DYYE;Ppd1x-I#8H4tAMxvQYXR$#3ip|fHC{mf7=BXN4CkH0xE zfTv%^YSjtXlLfDC)|mIvW-@Ec_p{>pB0E99gva-=AKk&*bF-rB$L1Gd*tul6J*i<9 zq%yghY;gdY@}tz(%k%vKc)oG7NAX>v>O3^N1t!0YMQn-SpLfyV1+vy7V0;TwRiXV8 zc0WLye(J&X;O=**0WM&N^4~n4&_g88$QQ~`fS(o0}G4S!E-B;O7a z%AcFTZvFu?>^+X%pWPeDYY|@`=IOm{m`M($*1iCHZpN!GBe$Pt@cVdXcL>Q-kbMiY z#`VqYeJOe6CUAI@wbnA^W!2v&kiV~i;V}Jeg)oEMLrRY=IU7z?rzU6_=((Yxr{pDUjACZQ$+WjtbZJV+Kn1w zJd&S-ALo&w4~A9y;NjhoyEm)r<6z!j8TYPc4xCBVF&p;IN!8j*uh+qL0#@DvPp)db zeGBM33p!)S-s=)sUu9l83A`_ZSvT=>B}eDOutQjnY(zx&z_B}6WBm%Nzsag~FQk77 ztiMOia2E5)b=a)gUWGqq~L+p+crey+vl`{DZ=cx62MPoHExe+U)d zZ&=;_1`5+J7W`hoTmNAFbOLeoE#`^+&_Mfs$CBMHX2ge(Jf(@| zxxsoaZ0m3heB6Y$f8tf!BG;fKMr zgPThG6tuYrv>!q8RAN}%o|l!ktR8^FZ{yXcu=*x&zL9pSM^%@3Kgq(9Cfgw%>^!&vLd0 zB=y%jJ_q6lv93N5lz&0pc`95#ik!YDRp2`CaDY1cKJdPnYVZ_f{{+iVAh-OQ45k_I zalG+1HuoTLKB@^xo{fm^fW;)(tUUzbeLIpL;Qgy`LY`i|$=~~u!GDZ*e*xEi$1K$)Hc5^(Ew!)5+jJWu`cZqqB)})zptLW4w-EXM>xE03Bvm!>hU{~EN(wc>xs?$vLKmkO(bl`?)%rtm`7mw5j@p*ASmnv z+H2#Fx!IR{sfmt%V)eO1*ID@ZJobbxZF1*BjQ={Tmr-P$`G~6duz5~w9wM{7f}9D+ zyBEp#@FOiWS8C^dJgc3J;P=kdW#5EphhqCdAn;{;uq6yzhLMJdn#a&sYld^N^=#^| z^BaC$&uEWh^BYL+Yckk84cc>pyz0=m;PUg$F2eovzYqPN;QcGKo}GAI1*^B94%mY| z!f%1HklvSPU$-ZFu1)5ipSV&rc^_=LgnL;RVC@Ce1b-)*HTOT+Ska*-8Hc=uv3SA8 zW>w#B5kt?yl1GtwFBad|NPY>)z3eEDXCB^|tg#!J^ef2xIzM~i`E43*ufSM?F!w2X zYc2jaq+dXUUDT}QZegs)K;(5Ir3bm=uy_G%pBv=+!Rl>1F`1rEAX&SE+WUTvGt z9;%a-_%!y%uzE*!j`rbuZ|vWRy5Zw6Y`G>YO=a|lK=xYfycD^Yp}|#nSrz;QY@Ukb zet0z>(ig?z1&Qd{;Ky`w!6fk29^J!?pxsdI#7;%>oK%=AQHg$>Y_=ovzJRrRV*QR- z{Bi1`RY7|!`F{$wJp|s@H{O=VH3Mm1_Zh~X3V!{_nhT5N>ABHjHoWvMG5s>Ko<{Z~ z4YL&qFK~8{yW|Tp7k&VieH5#=gK4|)y#u>LTY|%yXtfZMXQ2B8`rb}t{tIibAe(A^ ztQ~yq7s%!gV=0R%o;6#J2JdO${W6j9G~Rjy>5rrT^I-HgI?aZTOi3lBw7*1lmGyZhlk~%|Y(&BZrD3 zsuG^z`)R%>Hks#L+K-{#crwQ7V7o!%?~h^c#_SHQg%_43;^#s88O$*+qVc~$T>Hn$ zM-SjR?K-{&BGbqueb_uFt;aQz$AUqJieYMl*UPk>|M8e`&w+gb> zNBV~Rd;|=XJC{T9T;#&(jPN4J>b{I*K8yz1(H3@+{2qSe5BC2@viz*Nzo*HeZ#ABM z0m&~Q|0U3S1<5nXCUe5Z_hH=%SiJ@quglLm9IuR*7l#E}wY*1Ltq{dhmxe$%d> zEO@U$Pb;gr8=F=Cjsb;1SU;8T$+Ua1L3$E$C0RWb4f6GJ$X=Zo{t(|EY}oaFz4QJqA-a6N7GJmxFG(1AK!~NKZ_!Ffd;$TiH{Dfn2xq@ptmrd!WeJxUOQkjn+mXx zQM8tn9kO^d-}2>qT%Sg(X-#YG7Ea|@I1S+=Aud)ffb<2BzaUao*{Hr4+pHS+z&-8X z=KGERd{0IDcN)3xAz3m;F^XlY9@M)IV-Gf(z1vu>$X6fDWAc#Jpvry9L1UZjCtK%h zP*Y~o-ixxrdyTGIujyN3y@SkYjm7era(6#JqnlTuEp~Go04c(JChey&it@;GddY{< zcnm#er;lP|E{;^anxUhS+uQhB-kH&8JcC!g)85Pw+Vim+tQ<`b&D3I>M(p8?FqI6U z#vje*Td(?7&(8n$Rc!R~qfun5qmm&&MlsT%hpbne>3vVb0qs$YX=L$1O}>>y^rNWg zFgwU@Nzu-c{L4ppkZZ|WlWA)?R_euj{3`5LRg8Bfw>h2*Q2n#McLBQtm(+oYYaR&S3l9E6E+_1<3(Hg@2P z^cnS^BWoo2%9LuMT&^~1As+|<)p;}Uzxb{=pWggv2UVUKL?gA;sAAk4j3Q3x_w0=Z z9#Ke`6QdYcXsGTPB+?!2>LE7C7iuYgiEZMl=5pN`(ESkIn~)}!EWew5n)y|K%*LzY zQ3%UV!cE$%-sS&^@S`e89y04S>!~hPepR+p{0jvk`rd!!D{2&XiUP4gzEvyDTjS_G z7m+?YF{tQOHKKf>zYsPJZLPKH`Pabm-A1<9rmU)U?l|(|Jm@X`75CChF`_t?)yfyj z>@ztQCnb3_y|pJWFa74Dx8_9k)tBN`GsEjmcAiX6GWz`dDBmjf70W_vDv~Gjt(p84vb$uz z#hB7td13)1FWhA4Maj>~mvb|sm^pwHVKs%`+JjKvR~gS0#3-a_CuCuIX+GB&i!{iK z-CDuDP0uOK{I9BdVl$3-qpPw`nOU{QlFUYn^SuD0X&#r}Z=>aurqWQ|r2Cw@=O_Ou zVw73c_kHwTiq|EPtd*2}Ej9_G=h68Ysx4J$n$gr->nL$cGlHZp$M2=lU2~FVe=$sW zsdms^T|J@j4DVh92R$#KjJyDCmq6;W+znZV-%B<#={WS(Urp97m@0>-snI4hRsFLK zYKly)K;}bd_14_KJhGQWwq^+N`<=#Sts5RiwpKyUfVpO2Rn)VAjwCOM*6-(gIeITn zZ_UZlTa0`Ot5xAXLH}nso(vA}5F5SdEnY0f_fps`%ogRnc1x9M)LWi@6`ZCtSiJ@g z!b@3N@h_Gsrd;ds@3paawuWPpt$9FqQ6%dvj+LF1qa4|)P&Gp-67+?s&6l#F)_`Le zO}kneQ@faoCbgcyNUGdbQ@p{Cc&i+z{HK{kyMU_l#ouwwkK$e#Pm;u;x3FJUskV5N zBSo-wOtk{mKCv07$Z>_B3?QwB!2Io|uOjDVzQz1EIj6Z#8Qqy}eh|_6Kx;c?3s(po zbX0#ut!g7>DaqB$B@U^T^09K2=4kmqvz}%o&0<<}D?2JbI8zEoc}Z7Q?`6J}rK(9( z5h>eime5Zh?{!AfJb$T|W*La(j} zgoiA4U&&K6C}%tCOR}w%MAtf!taF;9hrm*^q4I*p&;;P^`W%a$k9vhB(vn{o!TmYYbB>R60X)uTBfzm%cs#$@2tOZH6j_=I#{lI zxWBw&tj(abI^KM@bMD)Yn(Iz>)2c^wLkqrJJ>O@1Q81^ku{>LG6rm*(nW# ziKW=0$nhA`M_=lrD>|!pKA#+Bt&P6riciY0_NV)rLmo-3jH`RaK8+Nl%!clk*4Unx zHR>mQgolqTOFFBkaf;@$(>Cb5-bH7R6Q8J+y%Pk@RavheS?BBJp|*65oVJ!341<&; z=*zl?Yi5jaaywl&#*PAOX`gvjq85qi%n`Dxn{&3)8nwMG4RlT8>b3n_QlytPkiC{J z>!qjWc5A72z8d|ct))qO$GAN!3ARNHRZmG(f4vW@eO?&)oX4|O>LHtDxvl4rFY98o zjhne5o${qN=8E+fVwNuZG^%WqEjm^N&uBbtd~8t|WE6x%*jV@!x6Be*ElYGSMn7#I z*os2mNA96M(!(};{IoR3tf^zzm_)zwkj65SYF$07xoxwaVU7Liwqb`b)HsO^^{^*& zE?;JH{4d+o$2N#ddY`jC5<|?fF%2^6TbfCN^^ja2NtQ8jyu3#?fA4?!X#JGuHJ){} zH?6&TM<>~vcjAn4mUwMmq@@wkbz5qCx^c9YK}Q@CuiW0)8Y80_run+w=|`4JV~uZ| zY_TMJ22Sss_Z4Fyo180TlRT(7Q8rjBU6q#cZQ|O!)y_ybVtrQat(|^nMoT7;MbWIH zK<8|EwDrj0h|h)*9>eX+KWdZSKGM6yr6W()xJRLAeI(Ba#NxDw4}?K?jCyRziZ868 zW!NXiG1faK<1zIVaEWDWy<~?tmONvf)ZfUN1<4YLe0fh__Oi5(4E5`S zH>zkn2db6%V?3mZ@{S`ya`mH;Jz{yxToDFp)6P?BZFFQ)j;)91MvY}$Wq}bli=wmo zxo7mtNNSN>r_tOad7@?pAGvllg2q7>Xjjkvb`*Gal2(x^ojk+DGl`gB8P>=5_Nnuc z#_?GGEgK>!{7^qhw4`F0&gq(wE#@WD*na)CS2VU{TQBw2m+>fm_P<=jz~Z#x(+)UmhWhQ?KFvkRD=`B%6CiT-}4cUghofxMi)5(pIAxPh${m zV@WYnnpiK5Y@b?YjwQ8yrVvn1w{ks_{H4)-Y;7~Hqc*YM49YK!U=G-F@!Lp9ve7kK z8qL^QLh!0~;;u*3sMgJ|j#gXm0K)p+rQbyNFxB#JlTj{RXvZNGUfDgKf6=4t%xHHBm=TgeOVV=MF9*R8#AGJ?TN z`s$2jOXrMl%k@qgCBmeS$M$Hl->9289xvMaO5{eCUL`e>Jhu91WdB%V#tEx}MPAz; z)Vb2h5^SY4R=r@)CkBG3CEHt;E>F3YMsR-e_?BqT+r!p4@|YaB^!k76x^NU@~Hno^=!sxMr62<#+WgPeh(y?qgj4@84ontP)No!CXmi>84{}x6RTezBSfnrF7PL_s}_` z5maJ{y%e;{3iS-DCA0e5-r_(z7nf#^x}ZI)SL1JMy0u887#cFH zRD0u)zV>FFYX)t8#iON>Z1g+FODrr81lw-CENfOWbR}|vo?~7ks5Z(>pOGC+&b3($ znm>hD`O%WycO?JHziMZ1`KVhfd#73yGaSd=^+egDQQC2-D}|wXHQY1W;nXMMG}DV8 z_NH+!CfN(oGQ80KMElN6w*-6M@-%KTmUB?}o9op#)rUPHd#auY&x2ZCElcm(N3un} z5)k68=1b`Sk-Usvz?(lw(}IM>zoQTd`fS~YgYkQV+C!qzFi*I1V3QRDTr zkF552=SHz5vw^IU4CxS!b;ieawh*xWg`o9{)I>)jz@vwQ${dcaHkQWIbLq(I@qU|) znIX;2;arsEmfOw`IhM4NIBRXHzjJ{1rjuX0^GMs`jBV`oPDg3sIo14&ezU^b`gsg% z(k)TvwEw6mh)%}e*&x169xLD3pCfH{TqxHiHauS2C*}8OCOyJb`OWXUnUN9dRo+UQ zI%?}!l13is%t&^oonMWa8JrATan#n{ybO}zk^2jQ;kZl7oYzsTa&$VkBzmQb+pAT@ zujE_L!q{4R#Q0Pz&bpHkX|W?dEN#NQ(n@lZ!Aq;M-@KG=zANC^ixxG@hsW`^^mkPd zgsP9Uw_WDGq^W;mAoF+lAFRWp_#iSha@ksW#M!{Mq-|tnY`+Fu`_l8FcG|Pk?DHkZ zqZsGZ$mKuV7B94Il$PdZ#d&nMo`q6(mdn*PUotmVR2d2+_RFhhS9R(;0KJExnF4mcHV*b_DCZH7yTB2jv6v z+%>KCZ*%9+8P64lQL7wT;~C57=U9~o6a$IzXl?|f&8#d>OY0n*;w@_$RBXL130o3f zL0#hnm5QV=!|`Q3+Q+deuNmE=<+rhxB!B6>b*dv@vE5~b?J|blDEM3|$LQ%cBhhe8 zVw@#AR%iVGzps%O)*3NKu+GW5VNBU#pF3+Ag^?O$XF!ul%p%y z8#ikjekfwP^|ow}>rvG|NA{2Gk=C|3}-Xrr(Mk^a?4(QIJ88HaB)(o2eZ!8LfAa31UZDgm$v7sx;W%fk(3@X;8 zruN>D#}vxaGgj4+?8`Ovwim>>c&2*<<+bvgj--*!`A1`ff7Z}C7q4`r*|3eGFlhVD zJ%emUunb3w^wU@7ciCOMvM!z%XRU?3867l^5!S1Z%P&DiNVui1xvjL2WX~G8;&aBw zm>8)V)d<9HXQ0R)Ia*1{oZ|T;RhzvMzm`WmZuvME*6ggd!d^@pjz`eYtL2#=t~C6_ z%kY(e@y_}`BdUKmEL-wr_Ov}EeRB2xel6zN21|(dobil=#<4AJHpm09yUoWi(TMnp zaZ3*HY9;K8jbTw6H6I86T$y#taK_%Z{?<4BMzX7oP<$j#SSR<(7`7oO#iDRJ2o-M8 zXShGivWj_+RLnN#g1mGHJ3W%GxP7EbR=Z~MERb5L&7DMiH}5=h7!j{lzcv!W*E%}- zyr<^O=~&41XsTEHFqlN|YSGqSw)m^Y^SoFi1?Om^{<)Kqy}ueqqm^~i&tA>hj25Pt zL(bIJIr*tH5YN@3-Pd+`)LiwLw%_)arskR>$zzqp*525cEkl+IzaWw< zQhj|CdpvvChdSphX+PTkZlT@ctSciwK5KhT=c2jCigl9PUHwN|5DfR)XhqMA61&>- zh{l!8MlKqdMY@*xPrgc9+fa6v%+w7vnn%}oWw~wcw!Gf`H%k&9)l&B9Y_8;eDl9Q2 zdBip*@7j{KJ+?J=SIk>)BUf@{RmpBgyFBGWt#h(e60RyUYW&rj2227&&UZD2{pKzCP-@7=uh4F zU3vDu(+IC2uagGM@VIk=w zk1pB6zdVA^X0*&9MpY8>6`b2%$ThuY1oiPO*WUjZZ)BBPRm)i)C9_p~`@yX(Gup(i zZcMv%w8U^Aqngj!v(3G;M5*+*C)FoBwA@Iq`;5{c+GyV8)X7t7=v6;#zS}*0_oiZ{k~XF7JIMoU$c_csylKblWKV)IK&@x3=|#qtA*9 zLD-SwzP3^px9yd*S{3Q(mfGD7-epsm79Gom_{n|L!)=93P}CXc1+&+BRlTRt!X(?> z=5CT+) z+xSygxu@oe$#_XC9a$4e%u%>t|4B!Ct+IQx&770kqZ_N};@qiw@_uelSY`y{Y6LCU zSFKI)N4k0Rs*8f2_4b(Nm95dJg=Cv0xe~O~%G@#P8ZYhSN9}cF^wQp0p!Uh4i+cJv zXch}Ra(OaRhp$sdw#%9HNc@Gzsfg=(MX&H{+f@36+qzm3Jf`~#J6A`ZIbxZ5c5}SD zzijqQ5nS>b>GoAIsz~y* zx;_hD@!Ty*V};H7R6r{2*cV=@oxb8fd#LS2$4=^DqwRRqy40~!BiJTkZd7VcsZ7!c zL}L8ej@{@N?8S3?!G5!T#v~TbYL}vIk0jSmqS$Bj&R-Q#ZV``G)VhV=r%mFqEzi}> zaG8?Q=3Zqr&v??umJV-eOuBp951CaR{pMJ3t^KI7c;u0zBS<<9jfZ|~taj!W(s>5N zd@Bx=mX#HQfvy=(`#R^_ufy?{m7PX(J#zRMk*`Jq3mwxUAUC-5`V_0Ea^Uq zU8!r#{a|V&6LV&XaFt(OSrp^KB->$2rPFX9>%B%){;8jEPTDKmcz;N}^<^enYOzvz zBI`vy8P+%2%Xkl;~DW%9xckyQ^#GSdZELJ2Qc0MN)aw z-V%RUd}Ea6`5-)4u-)KRV* z+r(vjVLS9r_KT0|nXBpR*FwLM<+iFXq<(MaRuD{b1>eBDfnbidl$nPIJu;yP!H zku8kY$qUgcUKK88qvLPZ`9XRb!P3|nCbpL5;dZdMZE0sI8Y`0R5pyPNbZg7haiL)S z(?433r^*A?H0-E+7)_$Fak4k1YgnYQ!@4>b`*pSE7mcnq)+70(ur#}Zh}%hz`Wnsv zUnvcWZQ*K=7>SMBS{LUYNfhF0n^m!}HnKi5vYxl}vc2s|qv%mJivKc2^A zkxu0&VN++E`GRGWNPq$p3Pizx+UCem@Zd=>d)cXZmq5E&FB+Ht0 z897$fSn;)e9N)DWBqYNr@i1Ny7U~_;-A?c0UAJua(D87IrJHfnc=~E1;5HfA(NMKf zv}-d(a%wJ1?Uu2nYpSt$I}(c>-THS+DOu4Y{q2>!Yd>L~bKSaSZ5aHdPqg#yk9elY zRsUk(@FQOjb8Q`8C`Kkv9IlwNUL)?AB}4aADIK~T%G>y zESTQbWmfo?&BKpMwyn$i#`yo(Z(AKH$&4N+)=Ors+H$u|SH)G?+HINMsYm!`UpfLx z59<(q+e7M^v2T0}J0rM_Ge_E6Q?<~SEYGY_n);l3bk{z% zuVO=ExL@?@w$|_TTVHNjNA_D=ew}ePZI2dRjf1QT2V{pw7sf{4z6xe)sq;cVoGvff z3+?x^-D9ftNOWaGzLJmB&b7HeHB#&-sg7WCAb)4Z(Pd;crjd-s*1L39Yme7`RN7~x z;?T%SFkZLi)~7gW>w}7`zIHuTsK)C#)0SQ5f<)?X=D{N}|M! zDs4)ul2P-8a7tAok7hh!RdaN5tfRMS_P@W}->oxJ8&AuZ9LtxKVqKkW^C-uaFOn-p zYS3n~#vT3>TZMMpYu!579O)Xv%ucte<8Iycd5!u+r!g;-^FC)Xa;z(4;v>t?@7zB! zM$*K#7+v9Rj%r+M7>~(Lwe_Bg;w9K;EcY!8>L^+}zQg41F|2RJTyPa{ofSQc6mA+n zKa$|%jG7p+9mcdf{)|a7w3({hmh37FjBAc;M{x1TvN{~iaWoZng{bz3EOIsi2 z4#z%mDLpbuY?o}0YWp=(%@WeUW4CSaakUDIN47Ms-8Li=gMGc)zh;UvskWtl^c_(*7DDKtHDYTZW3=GXA{elLyV?HWD4 zXj>}H+|qn00s z26@eB@rfo@2CbFmO zC>-l(IJ48<{&cL$)}U>>%Qp89n-UwrQRlPjsok~Y583Xx%u(59iN#N0rmxD8y4L+8 z-SySh)d&;@y02{Rsil!N->O$u4SC8zcXZOGT}fE`FfvSwPuj5{uXwa}W>#hw7RI{m zyZCZ=W)4SWk21YjAG#}|LS*ow-8~hUqK__gRRqd_bLMVuJ*EQwkifgqG7U7YR zb+!^dnJZ*Tw^ZpHM5E__XPI@g9rn1rne);*GIGVttFfhP8<}<_6zk({$tvrNxiJdg zwSEk0g>WI!)~%cOvfVw~zss|}Zaapbja88tJ*(QN8bOS!&#q}y*<>~)Lxi2qT;0+t z7NtwE(5*C@P&bEDm$X?P8HKKnQXiSu&S}Gsg`Lqi^1(8`9r-%kS02F;)aG6=$cTF1 z&MUGrdK(+ZWZTxVUsBuo*t4RotZ{r)UTq)Q&hnla5kFV=kl(cRC! zvIaq=UK1VGFSBGk4Ng&ZKG>A zt5NGow!*=3?IBksW?*@(^l4i({5V)hUUcc!RPXAVjx8hX&3$vF(*5eU<+xSp5qyF^tMx$+!87PZ&ws>eX>UT-%eo`Yft1F3Q>f(dt+MlXM-A-Crv(mb3H+$P- z32iak=M*L7)zo%Is_jA9;~ZW-%DKpJ9P4bi?cLgVHp)4-EyR3OtT8vDiIB79#b)bj zOv4$sN(OTls`xE?jYHPJg`?%kieMU!m=Ts^U5kO4HSHnGlU_cG4YA1ewrg#n;g-U~ zOer3PIl1Cj&FtQ}s>(u=rL+3B_0k#3h~}AXVuQ{nvg9Fa>$7dmlRxt+X<>$XwUKWw zbzOJuB;Djc{~wre)3Q98J=8v9Pj$}}w!wdCu|C~kslMj16DL{4yAHMt>0DMuV_BRp zUss!OPFE@l#WJHVuK4MxXrsPHIHSc6HGa^mmT8kSr9m5)v2J~E!km%>GuI1>GT-0IWa!Nd`vcd|5@DVTPjvmZug99tR;j7{{4 zw^AXrQ7KQij|wMyJ+?C0o}d=S?8XwrR@v(AT^ zMKX%o)|ag9#v@qSs{b1uUA2FlVKdt(&a>ufU+K=y(!#QZh2le_S^MUItFbn(mS#*8 zx$Yy|{H}YJbY4zhOUXOEIwotEqH=s`o!Aaa!NupJv-R!%U0T^Lzwh>O+w*DFJtJjI z^|qh8`E5J$I}zfnADx4#-go;bw&zN+ivDtZcPvW25RAosZ)R&PkVtl28l1xBc!+?cZMCdzZ<~HD}yDTKH8+T2kyVYH1tWJWpAdv{YZ~A1~M! zx?V?aopB;B(Otdt8leYczUWOY504buCBnu-=Oc?jO$Os>hFn zyc1sPXQt|GA!zOV-rR5vr<<|HSEzcfh#j%dezcD~x@SJKNM}m2k7A=^+H;uWSl5N7 z-nms(-zrUBiR;w|&mO ziKP|u@n&@NC?oThuiGb@Q?IYU`~q^Z4`V2p#! za1@J;{%(;WpX}CChTm7tc~2 zD_%C|oJWV>D~yjUKe!hLg^s-)R&8t|GQ-=iwxC+H{hjgK zx`dfwZ!ywZM2>S)-OKe={Vok8+wGm7D`rdIjFZ|pcZ4(3irubDVrywIT%+jF&Cgl4 zFJ?;@GbX=_C-GzOb_+A6J06^ktNrjX68+BGL3dW|!`aNGMksTb{a1QKXW1T0i$_MJ z&8A{xcvza1&TXvCCr7U>PgIDz=Erb}IxlV9!g?j!dG%7cN-V6%%ezQ*rccdMY$)WS zzhniwv`ecxZ%+EWbxd}U&iazJ-O^pHIx=FD+lK4iGg5n$s-+z16?4`*nwIX8ApI=Q z*TolU=JUcUxLelZ{y!LGtkOmV^hg zBwlFuvCgqPEnqCDo_z}&>nShE z0)6Gxm`PGam&cTzu}j*?Llr~KApiR-qj_YZm7nBTWyrQ&>Jh0Ly*%hB6H^?e(xJ^* z3oVK2YwdN;ILu1(s^0C*AlPO=+w(!oqxxNG6b4w&@`CL)meREFH6|HHJ#0gJ{JI+a z;ul*j{pyPG3wq&I$u9ZEH+a{1W9uFoS-NEXmHZw2b=7TzvExhnl!v=f2!0+hm9})K zsL65SximC7(oFWYxnR6}#^?lrTuJNXfFPXfZm$~DtBdlwkWs7PZ0U)-)F;A0W7rez z*wUHU?J>I9)t00&9AjpQIcqN#zNKZiU!`e%*&@ByQ9H7XsJvtk#1`u%eI0MI&HgP5 z1!R+vUYqj_oa`tEfmap|Bju} z!}5)(J>J$XINHxf*T}ZV^wp7V!+^+i-RRFgWCfHqZP{UrXHd5+oWeSzEp5y{A@$#X z+`3}d=twi~2j^av^wHd%irvT<*C3c^6GO~Pvqr7d-$*1Q#Q)8OVRt`f#t{87@{XPtfO|r-FB^_jmV_#z>Yo~2{1fBEw$TK(0s7C2V@4~@+Y{!_q zt?NQZ@smCCLMh|4S)y}#UyKaOx?-(LR?Vu(%H0~OkH^z>wRVMN%oFt)&)V9iNNO`8 zh-A#_Cym<@X3eHT%SX~k@vm_#+ZiYGtvzQ}s7>aA*eV@^c#a)osec>un_p$UrN;Jn z+RU)krM)fId&`cVvBVzn*w#80q-romZRFz*GbWrjDGfC8!bn@CYwyM3e zX;3Mg(j)U)Mw=C@tF5xBWn0(sM;nJ4%UT)}GtBKY;(z-}c5$=}N3z%2jg5# z)RzoLy3r_oiev6+nbE*mF-RNXFfRE!SBnwJrMgmm5*gOa{uqu^IHeIBwH`xOWc>7x z&pn1)pK>uVf$`$P_kauBOWzNb~rode}q3eZ%@Uu-S6V(@`q#2esQ+f-kIOa*5q?p>MwaZb-KqB0>yP7$zIP($r?r~ z-tenYE?s?OIo3kQBabw8jjJ|cgS?nIS(=o6kssf=hj}V{J&N1AXZvg+RyvPliap={ zU6CqJIF}l)kx$}C18Zr?u_@z4p4+9RB$W2)X}M-#yyJecTdy@@x2!~nu8Unp$w%^e zqFwwj7o%g_6XAdRT(^GFT$))I>nf?@Sl%VejO4eiXzP&t?dQ2ALHsdFI*J!0Q(uuA zUDDcIbkx;f$~5!5ma={$b+U};Zz*n7TG&6fOWKXJPO^IkG0@b>kQ>`kiCJR<^IipBgZfi z#iO(i&Uu<(CT9wpSmF_)b77(0p09GpOp@K-^I|(|86`_?`^5ISU-YqE9=+}5jILLG zsc&*e<~(bwE2VMH#p2{@x5}LBF=Loq^t(ot=EYW%!c8)qR~t!PMA%Q)SymK)o; zuUU4x26SCeapx?f_^2P(^=)*A%V_IeBPe!Uy(RC+5{)C=bxlXoDY2T2Xzv9BwGoQO zBAzve6tlzUy5MDgnj?Ptx0oY|j(3f!uP`+@XRR68_LkXipBV+Uw+(uY_1%`pTH{wO zyRA{*=nz&09W&5qxsBHjddk!N;$?HP&dIJ^lV-)8;((*wdJSimdpkaiM_OcDW1!x3 z6n!f@=FIT?;Cy4Ig=N9p{>Zo)-RzM(=R3==hT^jLmYlB zc|fg$PTOa*ngN1sycSvQS7RAnt+ge~uC@ibrq;=xm0fidp1D^n@>z{%MrUMs%lBB~ zi)6mUcVX+^-M{S-V_~KjzlECR>nKbQtA*aIuyX%+JUnzHwrkZ$F8zF@^SNdjMoU*q zLyapete<(H_iB}E#g1+(q@lhnCz=-TeckLxTeo(<{0@J_C~MNrHqosuCx}&@;5kCE zFWqM)O?IlE_JNYK(q8A~506{*NZYcqy`5=nzic;8Y*XUfxwc*DYLv9Kchti+cAruaUTA8%Rb^QG|tA&oPIojymQ7jnUSY>t_1tSs5_0C_iIczni)jn;cwYj6V z6{WUN8M$(qBTu7R-((t{iFDiJcRH3Fzq(EIHxB-tzSgP6vTlj~Su?JDXs$@N$fzTG zU(Bw(foNup<$Fh**Fa&ib;|FuA>*_;msUo{wrgA;<*Lubx9)F0Cq`^(WEpk!HuCLp zjGWI``!<4U9i77QZe~|bF0JF0j4Q3|3$dizD?(a)m#u2=%q82bwZ^I=>uWEWT{_eK z6W?pL&#~~a2lIdRTouI68q2NiO>2{JBhQ{4$<4%9`72myOdlsQbw*#7lXX)uuzI?- ziw~uJY7y(@=qT1YFSJ((_Cxu!j;urUa69`UHH7`ty$caMq`mqY`SN!Atd1*R#y*eG zjYS)uZf*r5*KLX${dKJ2qec%~W1V7Icw~tg&l%BPlScBdWz-eVURg6|Jm-w~G&wSM zn zj~+eM&h}e!un1QY(T+D={crJ8JQEy6gm&7V)1glQgrdeN<=KNcwD8WnW7Zuju5Z~kqMk~)5L$RuIhIrTJp7b<&xi9X!Z01g82Km}-@#w+Qng*F<(pY8x8(ryN zG2(M0y+7Q_aLy9M8}rK;M0zx{$E>NZ#k29NEDf(TW;FG%*G zXm3{^p_QKULdkXwmmIEkLZ{mjoz03W6;UiH?&qkorsTxu-dB=+!@tA{?VIQC$oRUE zHn$z=k*F)lh~54#WNfc~$3E9D8rvMqtSha<7kMRL?&~W-#Bvj>L81_=KGrLGr*0^1 zeZ~3~honhxw&m??+V+}pZT}WyoiC(?{OhYeC#$;q$L8o1ucfc}BI#KVJ6;_-8mX{q z%gZRf8t$vL85MN>8jZ~KT1TX}b+I1dayxE3enzp3#8P=xmL&c(ds_GO^=s*s(Q-zk zj{Hgap`j& zp_Z+5nZQuIXQ6da$Z7n`azgf2~zUX?Jw|ZCavoii~F>X8Cz6);tn{DlVhv*`0 z^*4ptl~aj%5@^Vuya50plrM&sV#@eQqn&#_|fuVzXtNZPl)@NXtl! zG<(UvM_N-JQG01-%@g$=rOnaUmGQ%G$8;NwZr()?%ZaaM6>Y{!IbNARm6GcNjqe{b zr7&yz&APR-ht8C(mSAg>d4jyhcZ|1Xh$rp&zI^07QR9dEnYY?FNb>*nV}FJf?ViCw z=jdCGN% ztx+rcq{fpy<>@+a{r$dhvb7qgt+R8H5YJi5(R*tgsUA<~{pIKun$;tgB+}aBj+79T zEz3DUSw$mP-=1mJ z%3k$TqG6!M*Lh>Bk&If-S*su!t!;~Pf_BQg+ZfIC82Q@fmNhqOZHC(>jij%5&f4j> zwR23xd%-_@8}1eD)GJk>aMPDHschmi<%hIL+*`6Ruzq^4<7i-?)$ibw__DqkC9mmS zoNUW=pEd{6&pz;PYb43V(2|zkHA`D&TXIINE6xSU3bNRiMMfKy>Q$&an{?NsYG;qc z7sb}HA-ecAdIX=gM>CobZda9MS$nMDkQ|dLR3qBHh8E2)dsbZVQ5)x+5yJkmj*fUE zVDzJhx!Ju#m%hrv*2sC?7&(`CCp~@(vocRdeqNyFF?fvs{Y?!As-IF13v>f{I4fPus)xs6ApS@jzsHthOBWcND6ht^~u(5Rox* zrZi^K!{ZLuNh~%C(pNod{Nz535W6yu+TS{F-K!QVTXc2gS9x)ywYsK})YHe36WJs2 zj9!rI!f!(TO4&iSZxNj|j?#p9}Y17wjJcQ{ z(6+v5J!>@LA-A#&%Q51Kw2YWnbItg)IhZ`*NT@Mlu~E0@)mBHvm55mmQ-x2{Yx+QjG89OvGJz=pdG+})=m zZ1De;_V-oZffKuH1iThQ)rX(rjGGXK1gyVax1aM=4HK{uBlt8&TG6BD`<_~4W;u4t zCD-jJcN`4p32*(>LXN{AV=`2|h@*Kju{LJgrX8}kb;#&yTl@Vc6F>bde*K97bgJ8W z!|I*k{U@K5?r)aM*f*EXohL@hfMZi?wfV-%Rg8U(ACH`k=b{MrvL5fv?D|uGwtxFx zO%CnLl-l^U7JZI0PL$S4Mtm>RU0Vyr6$>wmm!&jWl-AccN<$!OjIpL-YBNq!^RV>% zu?m)b5vjppkdf-0D}1&>w1>5Ngr}UBRgYp28@#OlLc8a4skHs3Lzh|lLGEBzVl8lK67Y`_d+N%Jh3WXVz&N$ z{G$AxL=!v3;AmbFTb-Q&C~_HSUeqRe}|S7GT& zj@!w_IR0D(F3(mD9?+v2)K;h zUBzz%Jo2;$xKMcVR0HI`)H`9dc6;;{;@bD zoV!w1r(t{VD)6^mMW+0$-MF=5_O=&N2j+AY{rb*4 zJL2ZL;yFuSsUls;o)&L@W!yEd{cYbm5QltLr9~F!#$%Mm*`A2U^4a=3P3%m@eZSdV zhWM7S&zD+x8E=+icgh>bjMz_Dp-%xJZix|;WmKz7Ei+*YMo z7MWxF%|h6#UnWtQIbdY{`o7N(@pw5lf_G)AYOMlUh40o#Sdgb%v7c)j7^%l86ua3p zYijYuU`~iaHk|WfpWRRqE!KR=Pi?j5%~fKRTcsGEX!K{z?K@@p{`9GRt%G3vm1V8D z_H<5Moa0aIo@eKxRZm8Jy@|vYqLtxVIWouRpsVQlxLk^ z(#`6!Y2B%)UwK{5?-_YXjm?Vh_%CDWhxuf_ijvn)d~j<#vdk5$uUV}A@#QGzJ0{QT z#mDPG+*G%tKpCP`UPSYe4y%|ur#7oZeybB=wjXU@KlQZE$kLscPr3GP#`XJ?1$~wi zbNLHZy2Xnu$6k!R*K)-jgxKW_%Xb~u5`CX9=fhPj5T(Axb!U4Xi>Iy2KA(o!NU4F# zMA|AA(F{X;GD{TAukNg_YOR_uQ|_Jp-jjPpp2nI!FVD>8TE&1lg)9ppRnc5&FEA4--{BT?|orC)0iz{GefG8S_`MxVQkooPbS60dy%@wWBtB9S7-Wu$7%iH5mYdb zMRscn}d-JdEe)}1xp2em}QZIdKItA)&KEt^+hBJ*b5sA@7CIxTlkW-ijEcfpRl zsO#U_nI=Rq@7;mQWbt1miTyWn?HFr_i9oH&I`fww&sa6QU+Z(bG^97)i^_GPYi61a zt#UOLv}P0sM`HQ3{R_KSTyjWe*ys2U!3 z-Rm|&))-C8G+X@bO-8bD-_;dwMjpoOtMWxZeD_Sw@vUYzJ3KD}E9%v^bB)G(xN9{n z&1(_Xk{-yxVp(=nRgr9j8SH&?@xDr&e{-^L8i_)y=G|;wwcPs}?+4U7dz62t(;eT~ z^3G`p#_jTb`MYbYR;+gZ0*|{gSKaK(|Ggq(yrxQPvC5Q3IShfS8^hLLH9AXKHbae< zad&P13WaFxm3#46nf!)GHQHa9-}QYn^j0LQ3M^K$<8C%%p<@-pOy^B2n`bOA(YWSc zJ^m-Gxemo;tI{&$S?_T?O_f_lY%c=y&3Sr~!^>P+l%K2RxMD>7;a?WEn~Y;H@G_5b zqy03)*jltx{AK@p4``0_uJx_mD#-hj6eBLN;8n%xUp#&0xfQ$Kjzsery>mbFob}=X zi&nfBb>+3!X9Rx7$Fq&aA1{?=Hs(=GjtiKBYoai6s$8fuq(Q@7d-sn(3nb;A1$E4-B>pYb1__oz+A#_2tik56?t z8V<~rGswd#?Q-9!aEA~J_f>|h6B*vFvv}|~4R()3oOm@pv0Zkmoz@6z?D{ua{m_mj z6-`yLYi=)-gHTj`AoIoCone_iJ%FlSJb0e>;z1QLAr?od7Cq@jevRFH`aZ_=ECbGu zjo7-a>i@?2Mxq*>A$rgdg-03oU#97IC$Jz+$51oGH(PsV_nTqt-@BtlqXpO)L&njV z&CNp`J3>BX1q6M<(BH|Cq8B3GUEJc>gMh0CFQX4em+UX>EFC!XPK27{jfT4_CCQE z9Z@&e-(beGI(+l=*+!h{wP?kyMv6KV=|#^|3)gXK>*7cu%vFV-qx0O+k+IeMPuB3T zEL2mn)bG~1PvZ6Vmzh~~1aohnbHwaUW~C7azU956EB@uL z{AJmkbwA<$cKM!CM6w(kT`}>b;BQL(*FV`ZC)HcWa^y8v?(fwd*S%yYACB#Az*U!# zw%@U4yE|2`T06>1oa@b2RlT0F`{re9)3}lStGMNFiqXz`z?6@o4v9J?I!5%n`c>ro zmD5nI=k=4NRj_NXRi1Z7`I60L@b_6THa&vTfzU#e24UB<5YF#a0{E z%2MphzkceC7hB^3ExpRJh1T!t!alfFZR?VF^eF=KY5e+!iF?))s8LseF9X`*9ki_v zc?IDZWel?~YxT!;V?^iN`t%&raAchC>PMRlj>w08iKkfm)YN~+ zh<7~WJDx0L;So~B#nvG#iAN_ES87OYf9vr3sQvk3Djf zD)VN0_T(miF=4dyjSFn~+MLwU%4znQ<4oTBjI?hpu~EN%Z>(w*=JoY?W_0zc$2&4a zvU6FDtE+5Ps^F)e*!eOUMRkimp+u1h5bj z-w~Pf)CP867rX0)EoJ(X{rrn!na<)nJC11e2Ya;ow72I8(WrnTH1ha#k0_gYE)I@8 z1H=N^Z8a*BdcaD|<3)6z`N3oNJ0h(ni#}_yw4=WHG=@ET`}A<HqVu5$Y2@8hNqQmhy+{^3)yI#qJv2>OVHqviMShneRLF`21vkDr?@0 zwdX+gr74Me@zrRts|PD9X7d2bvcxlh>{wgQQz_u!RUJ7}-u=y&?U7k=4j6W}dD_b& zcf*d0TQ~t_*9Et+hTBD$!BZ60^rJY{gi1i73mB zwyO1+%FIS1TG^Jddnd1LJsW+BF6)ipy2w(apO5zWc^<5mRCw3$;wNum_Sonu3M_s8 zAd+<8T*gmVV+)M*TxH5?6-lPEVjFXBMhAFTn_!61m?S1i#&dvA$I1zQKY+f9S9lmf4{^Y>P8fq9{e_a zx%iHDnXpsJMF~?5~de?En<- zv-ooN=1S`_l7}kOnvVrD90wkqL66wFf7zTOFHgk)JI)3_7FubzvZeQQ^SS?gVrd`g zo~zY&t48XtrKCTr)Imp<9Wmh)Yk#JpAI&Mjy97d8FgY~NPu;Qp&6 zjBiGDW}CR_?X1WgFZRaH#-2OgaYefs+O}N|z1uf-GvOHC8wUzwS0&;gH_dZ&W6YCU z|NI6j)?VxBY`!~-$vf}OiS?kK+y#EcWL{gFTvPG!X@7pME`Mh}zO=1w_k^c)S0UpI z_j<@b_*13GYr!iBjZGVlrE1}~dDNv(MSYrWWLU6<(s(n_49ds0{`3;YDjdrb?KIETtD2vv&Uk`R9#RwHhN_tp>o9HIj^tPtat1_ z>l0J=GpT8s^7$!U7Q^Z+j_ZLLwcJyPyUwx#^X**K!6E@EOyoiP-gBn7`pQE4TDq9E zPsYsp%avnjyKGOx?blQ9`m!JD;$o~3v!ZA@rikA7tS#nrzqzyW7^>^xwZ6}Mrn5J{ zd35#2V%Rw{vy~!Gu?)W3w@fdhyZRWFv#~w4%RKnUl%F4_JjaCQlPCNb@7*Ory5qNk z*0*&?>*>uoREWF4jruYaHd(zrFk?35NWP~={EcbngUgpa{S6;VaDLpn+H=*9Is!K@ z{fJJ5s0Xc>MOQ#9m*ILMbHR6=}QMPuJsz) zTu)kM_3KV^oHAY(j9VLG<2**<@mEAu5u;(NZ!2tR7{?|*Woh5 z#OYsF?B79mcKKC?|UckdDr2}rdIOoalW~3ymuhtfEhe( zO$0*~7_0ARyVb?M$G=)|zDmUoF~Ev>{kC4zdQIW!P_9HTQf+Fl7PD=}rV?s1S=-f*%)H9N#`ivE%z5dmajDkh@4E!CP0PcUHPk7OAb=z1)LK8+dfpi4!R~6B z>c>X5n4hg7$A+lf5f@_>t_I9me#Z38wg}~YQOUKL-*cz^#X=66adQ>6&wfWc(!YY> z+2mrmS&Vy_=tnkUgyZK+#G3(Uu6b33x{lNEdz91eUjJTI<18+`BV8WG%sL?8H>a0dt>{t2?;i7;OKZ;yD~bX$t>lXMNS-aF|UIJ9CWSujd~yuX5!%v_*{? zu~cQc=Gj`*#j+!#y!!WNU~c3zI6L6h0{+2tM-7vBvc#7e<18uWuHA zpTF$3PMMWhjoa_u^jcmR$t!)8qx!|GR=6WcKSp_VrG`>zIY1*@E7bga#<6V6h3D2^ z*y5`!crET2f1l5=yjbNyR>mqbdnVbv6BvJwx5vMF^xrUI{*Bg+-d&&#vtXXC zZtrL#D-T;W^;RB>PY!Nt+!2@OQw@ybs}~$hJM_RP@mA&Z>&rQ^HVd>e>)xH0%TyzZ zH2qrB(j74s+zf-#oommMMWe+=e{vzuS|$IoQfsR$PpX@lma0^I)9Wm27+w3#%$q%9 z37e>5+1#Q8aWGT;spE3yvEG}Z5WxFoO%C!+U+aeUL%_WIYTRbzp`N^0YvRmLoL7gU zpjEY-Go!^Wo=1bqe=RfQr8BeLE$1&0|Ni zW&7_Oy&1lD1jP*ovQHma)nmRIwId^p_Kt;tw4;@*IjY{UmbcGHiqG+kQsuH(p7T$C zmO^Nb_ghM4L`-DSdz>&@@7il*Sy}{+BEH6#pYF|C$2V8p?Zb?VgBY1YoaKy=aV$1s zx+{fj!^SW+FNCw+O5q4|olY;KdTGzC=UWM~6=(Qc-Az3=gW`1Dt;BxY3tzJePO|w% zRIL;-oKZKfjm$77{nn#b+a1L~G-@uT(LSDSb!G4S%JFhFX3nq`(_@~NGT;8LUv%<& zFV*(Tf-A*~PQ3f9ficCz(day;H0HF5q=s-$d&S{uXP)5q)BYbP|7tZ)&eBRPjR1M9 zl#jAnR5D%`V2y9D_jf)~jN_}D7z)8^I3|ZGT`rHyB-!+*dInia z&~wq2&wIp6{hphVJH|*pN4XNbvxktg>9w&tYTs2MC15MPSOY5Wt@>93MhmvZ&DpDG zHMRKM1KxWnSAjIR8jM%hiSAbEzrSIK&x_go&ToADsy?EQgU_>KF{5h3{HhP%Q{EH_ zvm4RVOgoBp%qWO9x?GbBURW_Uc%OauI7Yu~?y_#g&hYad_YkaoddXM1RYyfdeKn)K zCBB#`M}4y+T5x~=gjF8o@l)@!Y%avRe7c^Q|MoRkYT1%^&-YF&-Hl;u%=M%|i$D4z zC+Z*OZ~njITCC(KCgA3xxpkE0&SL-MK z(u+^L`WF$=_&luk%G)v%TH_WeRjU29-WZs}&&}#(P_F6EN{ni+8Y{v$t39vISAVT2 zm+7mWDyO>|y~o*mKOx8QgcVf-TJy)-?$plXs$sRpRY%($*g1K|BiLU z`+iretx0;+nrc#otBZ1YF&KHew01$8UH3h%nbqq?&ABri5kF~0#;Vapj4i!ubvbAS z8D3($Ph3{W-&e-##ks9wkDMPih``IipbK9f85`-V69ql2LAQxJrw?}?J$W|F2eDmxAh(0ih+H4j4LI}Oz)rb zy#}Ru5pT@6zok@F#fWO|@-n?5wvIetbV!{w|``R|&!Y>)X=Avd#JM$PixWkc;T z27SN$#1}{7RQ33j74z0zLpIdN`=s*ShLx8?uh}u13tD$b|2kRSH}7)jisT+|6lTio zW;0f@qc)1pb%~f5?;ItI^#5Y?tnmLyqu2kh zJU)KR`;$iR%EgaIHR1fhsD_;X=sT)46R*d=b)x^NANr@p2wv+^ON?4%6d-OZ;E%mV zZ8&PzQH)xejN6{{hl^1@2n3yOH0g9Be=v;a!~eyQZWP1+SNiwN=(&H-{!jUl|40~> zG%9aY_9)Av_?O#JEk@a-{g*o!*Ljqye>r0Omy^-D^ZwXF@W;-hmX#2s1Q|gOhXKJ5 zhY`UThbay-f;qtghb0bc9JT}-JlWxJAlMTe|G){)oe0i<;DYBaI9&h04bRasb(HbZ)-%fC zC}X2cjn;LP7vPUQ{(}+^dUV~oPF;ttP1mAp)-~xGb@jSBokmxytJYQN zDs>gQa-B+7rYpsv)G2gwU5Tz(SEMV{75qsBy1f4&SNAV+bffs6Z+ZC9Xo-Bh{OA>h z__am2gc4km0+*=7<(A=hP~&&1#8HjkS%afq*MMu&glpBRYsdBL!qJQC+mGu#+6qS5 z{+DAaUboTSILhZ}Ph;VAALVhBL(w1h^l|%)GH3FKw^7Eda7+HnmOXCYe;N8;X50zx zgfTcg2xI?~Mvq3}@#n0^pU*LPDfd4djpPO#`WUj zI?{2CX?UL)(P?oG+VPrd@YymUax(Ho}?yD|P_g42(_f+>tcVBl$cT0EU zKe&Z|yRUn!d!c)Sm-(*yrHjL_E5zAq#O*}F<%{ul;zF26m`zwl*i6_*I8L}oxI=hG zcuV+92quIPq6ohUaX4ZL(S!&>DB&yNJ^t%0!db!r!aBlSylyhQKdEs&Zs|7YoODAY z@grwOCX8r@zYT9377k|)og8u>>KqInJTf?Y&~lJH2oJLGPyfLigQ~&tL%~Dt!|B62 zM4eN>VRP2I4~D52Xs^gHJtj6`k9(S z)lloHVbs~wIEp(Zn|z17hdhV;jP#MRQTLuOj@Cz5t3wF=gWM4#-TUFyL;k&<9V)F~ z&##8DJ%lz=4W~Q5bxL(p)AW{wEe|^>ZPOZ8wFR`8wx1@h(@yE6k{o*NwB-yD=n8hD zYk?wEk6+8t@}3IRs0o)TxqTD zS5Pxb$M67k2{Vp%s_!3~F{QTeFhxr{r@hyhHyGJBOZB5+Qrp_{^QwJyb4x{uixgPZ zvx4sORq}-y$x6f0_m%fLC)Vy&xV05Gv}hoa4PvBSEoq`;&^w%#%H~-wwZF~_wDa{Y z7yV^CZNf~>c%IZVfw2<$W_e#4zoa{=OC9yX zMY@HM;R-rw)&kKKoskvS%0~wA`uJpKacwUuaLGebIzwcM+PP(CoupO=8bfG51}ghr zbT8<552a=HMN4({>E#cbvKi4HH-|H3ClXGgpC*KrywcFL+)Ootogb`X8U5#V5B}O$eQMX769V_2%amrA(bKqk9|9*)P0E zcbE`kw*{H&anVRtf2dDoIoW(F!GOP)*{b+NGI0%)g8jYZK(M!LGwq|n$0lYmSbn{F zaqP+zGi6lJ%U4}dtKV4NqXkO79>1sfuqB1#UK&5!S+*l?-;eQ?>l2@)7MZ*@ zXR-5KjvBdIe%jnPN4H46`K-&hm3Bu&GrauwZ0q-Q^)}gMJ;_DCYuX3*K`HF<#dd)C zsBbp2)c!n^fh`x+v}e~=T3oQr)d?BR5VfI`iWn5K`#RmLw|4rfE#&phxAV$A?vAld zeD;w4p)dIKr9DsMzp-vzd9dIS`_}$11HX(u9{FJNOYrGgkss32y_As6RFYaPZRb3k z);5bjqui%wam>0Bzuq;IS5v&dtu$F3;IwS|gT=M>_q?{vq!|Y={q@J2{bkZ7%@rQT z%3Dv3q?t!DlYx)DbT}+nfZ6%x@DIL1gu4;1Y``kmxxg7tsbY|?N4^Lt$Kks@R z7`*-S@SP{m7X{3G$oVkz!TovhN1qonK1?miFS}AC)!0^BH>kyj?Fy|Bj}ey_p6>JI zbG?^nSMHzgwD87;kN$tnv|k!9H^XD|+`1WEva9B4_EM8ji!g&UP6hG7z-DQm-guyZ zu@Ro$at7;nc*yDj^BH%wtA};SI$GF(T}4W5Se+nMFXvaD&4-M@_^9)*kmo61dLDG& zeG$kFFbVMaF#XAjFaIQNizj7M3ft1Fo6d2zFyBdk8m#0WaJ(|LafXwB*X(K2KKL(R z6g#J7VZ|c$l*2QS1&5}jy2g5Wd7Imwc3JNh%P2#}7)~={d($Eq-a-u|g2uN!KfBDHQR1`CO}fN<@wvGft48MioOWsc znmM1nH~OS`#W?tk8E0>4epJtb(Q7uKqrRq;7ZasF)Yro$wrVtJW~4A3b*s5)GCC~=V^O|$CqnkJlb90B#@c)21QeM##H1{5 z45;RHs0ufg_LPYteSQ}tM+EKtqz*ptLhnu5FN2RsA45O4zn=U%B9W4IU2!QZqQt#3 zMc0NVN%eVAtUQ816WtE1$n1R$1 zyg4|`?`H*aKJaJK_K_@*Ci)}>#1jqI^?{V*O{IgU24jnLwL{7WDQJ--`RdR5--O6B zL61Xce(DbpgtY_(gm}j<5A2F|PXCzjyWYQfpGw-cayXO7k#4h^DKfE|>G;UbY382s ze@_tl?Q(DNX_@oV(|nv{(s8#P4%MD2v-MI_TPyJ|qnjo+G$D64#su~B%Q^PY6KElI z4K0z^$<3i$fj1194uqi@+7zv-*Qjz|-Lvvj@`2*_Dc6!(<7tuCLpO)4|GfJ1!wAW@ zo}k~s+%LCczorai&5}Fkl&U+M_jTDKMx2-QNt}mf+14N2c24PVo#!1p`J?xS*;1b^ z6WXTdxD&@Vc{A-_nN`^vSg|EO7KXea+Gm-RVZ)y-4j5U0%tZ%?9FdFAi+Br2MlTNE zriU?qc1027osVlv2Om}{UIH$e~>mieyf^MW5$pQOoX%B)}O zGJX^sF4@wyN_U{opD>%%#(HDYVt?75>~Ym0$z$iVt_k}l{hso5e7A?o#I56~c2Vw# z^8(8lyBb48UmywKEi(Kp+{K#67>~r!?=xG4+ZhiiG4NUHS74Yv2uBi>+Es&nJvVw6 zcAl8S8EHdV5)hg zR4Wo2A~1t_m6b?#lp2Z0kyB7BmXgZg>g#EMi(nCL%D^lzg`n+;8*HwLD__#MM1C^M zBmZdXyohn}WzkcDG@iamXP5#!;8WtOgml%izdFn zeNC*u_J9{ge1w7Qis5Orb70TNdw4x68hkdgr%T;5X~3;kzZ`8om~EWlt6Utz43A1& z7*_nDiyZH+=n(c({|c;d$%Z~y)L1)E_(B=A;T~H3DQBXLb5`75WULea>=wgtWd*y$T*;c zbA$|0Hu6@`e00epl3t~@x;LTebnC0SJ$Y+N)78Ckq3JIR>cZS(kkk*~f_{F9?hktz zkHri`j?4Iw!pMXg^lHDWQo2+7$^9JOS(zb!#E4?C#n#h3#9cLRVEi@LM9-^}7P(X1 zjJ(qw##?N3>NKH=^9%urkNAP;5E{Ykp?6an1OT5!G@!>bdk9-tL*h2z7087%MxIlO z$t$(U;GSlARY+@GS!&J$6+b&A;&IxT#FIgWkyB%>zE6(=V+?=niz|trokmp8$V-*$ zb!cm;oqWz*FdK;1TO!^j3vt=y5bAhv%pE(Cx9>PZyTA#bJaf&1-Hy60Hu`P((ppb{ zpWzHWXYK^ye1;|SF9ROBmTDqgLDLf5Wu1%yogK3SaMZm*S=0jj`RQOwWVe6cmU4>b zLgSvy8#zIxWxucgo|>WlWgEUZdU06X&nIDyzaJ!DPOeIE&wX7oQSRIFWU#k=HzApR z7#R}AS_WFOEXO+bIOn(?@T&D#KCauN!+y)S9Oo})Y8Q8#{RZyla)UztIgzJ8%C8pw z#vHIHE=cXe*6YUupMWC1J=Kr$U9c4z()H7#wF6ps7;L`T72h0}S5@`0{B_K|9CBgd z4_Qoo>YmTD!*0duevFNO{rh{=jBGS%aZW}fw<5htG?dmqb|6;RDx&fpn46mBTYq-F z;rPL!1MwexwSy;l3JY9u=hR_U!{%7yLBVk};NH|#-sFP;H-17Gy37@d^Gd?l1l zEMfTr8lpRe*B0Ct+iRkJ*|@qsKRqtrRPib*EJl*q9i|A+jNKd-|7&-|U$NzB2?++7 zy(L~n^GmOGSoWE9=us%h0a~5lk>PD)*mR<$vC~4kK!?0B95-i&$M(f;pRJFW&vN=~ zrZIeA`buxQc#>d=AXgyZ8Z)XGBkW@UmF_IzQCEU4yfYL_>N(C-7$j*x4}xH5iEdBl z&pugKoU)>Ex<;ASro2_U>i7GUPr3a+t)p+HNg_`tzD)+=*XQM>Z_WSR5U;vfBN;a7 zz1RPN=fF#3n47W31$QU+DhH@v`wY$LV(4q)Uxon|@@^6DLX>=~m*I zOd2#zpbr{=^Tf7j3BVTg(=StY@%_28fvt?Q-~c%rSUobI z;?L-|6-uHR^W6Y{@+=;a zvWBukH~{BUpYd9d&y;*N0Jl)`;JW^I!@VQgdPNtjvsj*4zNo4zEhv*#l9Z4gADeM0 zc49(Na&F3=TrfkG-=TgZ52+m26WP79+nTl!GNQE!Skf=jOrsOVU#(7=uQfApDzbiR zl3`V3`Ip`y!@Fi;@k?>K)SlDBnZeFtTxDBg0P+-f4f)gfjN9BmfQ?46Gl2o4>9N@+1(6BQ6AoqnOm<3sotd8TPu`aN zLyGH~bCtCXgW9RR;e#!7B5OCpO3%>1M!(3S)8ePi1zVa0-{FbX6eGC}#eAE@%g|YG zJ!>!jIsXv)H#-#*(jpj7Kr@;jTL=fzwz8|ya#}pco4o>h#q2{T(CpxNvJW|yxVg8b z$EEwV#%?_PLDvNgtCn=}@{Z#VRkSx~a{f!=UFFDHl-FHgNWeyTyMS=$WoI z-)DB%+{3Qas>pP@72Eu<-hD%w@j?+>xJGn=xqxwi^$d-~-lBc9cw{{yp&g}9V|LMA z&^4^}(000<^#-|*E<)S@h&qkH8-6nUS9d`3;neI)vAQ;^I=sza`=NKOt_QKlVi{WDc4@D~*5H#V$9$oQ#0<7NY$(?6 zG^ZKt63x-C7mj89;!WnJ(vulhm?hkVL_%rM1^P+&8@PzE9=!vmGtM)-pd;8}Y#uFw zwhS;McM^9EF76BOUDo`i-l(O&@~a|Uy}0;VR%gLq`M1*_XDrS6E2}5duJD24xIDJ3 zPP4q>cT;EIrID#4hrs)+B`h`1U3S-?)G)yKs~OwO#)xFrXr7CAZ#yFmKY>e=$XU1O zRE{Iu1j;csm=4WA7Xg!DJ~js0i0x&bN9FWL)+}r(b_%P6-a&itG5aGyqYddv?mpAD zsou6Gpz?%5QhZfmSlFDU&b^8eK8+GI_5bBO&OOO4|em{3`AP#t8D6F%#Vc_8}cWIUqy* zX;&ZxN(HTuV*2Qs_AWz-j73%0eA*P+7>eF-xlXHn+|k@|rqQl?c6CL0Y{{a+s)F_` zi>#ly`*J$6zUJf?Ruvg2UR4!V@6)7q4D@QXp(H-E7rDjih{FVEnp@T@Rdl*N3@$YbBb06G;*0-Iqn_!}Z)2S8UO0Tm$k=<#$qHi6Ehha(5E z7DP*R0fQ({bc=}%{TI9YI`=jfHagX3s_&@UOV<}ZEcB9p%h{T(Ds;*r<+msv%Bf{g z<@DOKO=j&6dWHsvbTsNW`f=8N_Cd*G=@MD4ahH+A2r+6gj52zm|5>kB`k23yYr~(* zYC#6pj=P!Lw>g8Yu>k<@%dkJGIL&(v=tmEOjf6MrUN3jb|Cq#buIE5rc0dIK*kBPhqscHxXlOH826PgC z2p8E!dyP~g8(|jxF~b#AVat-Zd=@|C~nE0uXYnZP;Q94t9zHFb=Lgp&@F7V@?Vq3DC znc1ikZGi?U0q_%OC)EU6MKc0kK=p_KSqs}?&ZsvQ%@~8ZqlHizbP3XqZQ*TdTSQ6Tx_oHe4;HIoJ3JU zVbChZGM)pMF5&7&3&-jW>a7*m8(7NT@Lz~H{Hg30+zMtZ{2XzC7K5?y1!^Pkp5{;5 z27g38Qb-7dk-@x&2-Tp+Q!?y3VsQs6G;lR_3G^E@1rE_0u{od-hSJZ& zI?RmW0l9{{a2cFMsRgxkD%=cd7%Bup&#`l`=kN_=1dIfef&HWq!neW8 zJ$=2QU7ih38*1uuRcRHul~?75mEKfQBdmw!`o&FA{os zWz&^mg&P%{@v+-Zu~rGIuU6Mrb@wQLjjMiAO{S4f+YTZj>3gJ3aV!Iz4iL}R3z zWVWL1Vp9>oapvFRub_XUPhdDeci~IOA>asbmRe3h@m0N;N`VJ}OTcu720a8C(#^3E z2xQpOUqf7|mCB)xr<4ub46W9}?Lhm%_Bon`)vh(W%cdv^Di?Wq(Z-T-CH6|AvaYhG zO0-5#lh<~k`&IY-AqDX`$pCrB>SIpkJ{EiyGNg#CT@obC5}gp{NtOxbvf11M1|RXG zYw?*p04Su=sXE{QbsqIRkV%HY*H{=B2$?c&!mY?0&NlicSb@^v!(ckNo03Ev7|QD1 zrd`{U(|DlyP{Vz7MwNTbZiR(1wu~n4QUYpz*&X$@@+;Mv<~hxAE#LYU_JwL~XpuAk zNTKtY4a`l#8sP!qC(%VdBz_=m<1ORQ7EEQFXHMnLLe-Elaut|DvjEE|A=KUES)}*S zVX%{o!atx9$_?xQb1_ti%*TF1D7qA_0XwOIc$}@CFh;wq2kiaWoKiQw#Ypo&MXtiPpSED7-4$_K& ze8vrCI&-n;f+$M3LVSe3Tl`ZZ;5`tm;U{3j3@Q6Lv=bD-2t`1Z18UN2ij0&)j0agj zDCGuB1Xloj2ATN{wxtK*y062oqkqBUX&%IVqyWM~?Zy7!o|(=2v{^68mK7j%!w zV}6H^BWu8kz!$)aNG6mH?(SUO{l0T+y`WyMIj_2)@~a3_6v&S$Gv$zquJ$PpteI4O zt!`cC+O7pXMkDn)4lx$dXJU*WoKM_Ud_8f$s7rE5tmV;#^}^Sj-Rv&*Lv$&83NwPV zR3BhHguEd+qxqDz^#F*g)MYhf3t8k_6*{mFREHnarrktVF18j0I2_|(BeQ2Ml z3#pHw8$cZ}LLb9;gvja1*n9Xm-IUHirhykpH6&Bwm4Ocf=Xwt}zik?BuB|bw+)#D1 zEJhivT2iJ{8Yo|t#a8aC+FV=PxTUGLc}*X6aV|3;=bPDYy)ClpYJtP6;40$nSEH#yKj9NmQm{kLRDI>Kvfm$sPew* z`Bh&u$2&4Rrgme)lZNv~-ogOmE;hi<;COQvh&bXn$#wA^o-NMVKb%a?Lyi`C1`l8d z!DrMbz&LUq*_m>Oe2a33Xh)LM$e=eZ5VnP1(NySd=5fpln}t4xjp6sWMg6IbBVUK- z4`uZ3?ONCA(sZPDWt~lppV~+bsOBh@_&C&6W?kl^K3DBtZ`5GX>eclDUxmCSf1`NQ ze6ff03(U!aYvMn2*PUdbx{WW4)z5xVT>>~<1M|GYEOm9pYZtk#(w*@eXR>yvTALrr&moW zo1hFYl`GE6=gCzHYxTbJnN`>7G71T9ABzB1l^SuNN;c3yJ_>{z1 zMv%gSD4w1G;iNI7%$>-0+Fj5dUs;3@qls%s`XnpzbV?EF2x$Q5hh5a*&_ zHG7&XTBme{Xd4E6byBDUJIYvwN2Rm)zr^=sNAw;@J4B`8WB3+_3;f00S@eGNGG+k* zz)mVi_9dl|UXsY9$%GQ3Cr|<;(l*0Wpwmz={UYlW^DuKGqYRr%AB&F_C{#^4P1rS( z*k96ftMg2wfo4(d=8DwP0Oj10eMR}j?}}TC50~ta3)MwctlH4V*{wU;GyC~ODdiXF zgT>L0b38>#iM#BilrQ})17&_vt>mEKGiNntkSW2AAi>}jsxkEeK6fl5E+uplVRev5z#-iv&jFrq9dNJcK)ja0hB6EKf%ej;Gmf#<{3YUO2~qq)_(W(XN)>(*%;QzFVwk?nIdms9 z0$Kq&gO=cY&;>X{NvB$Z#;_Vb0IR_RPzu^j-^FOfT#(6d20RO4!CQf4#M{GuLxx&) z&&^JD^R)T~jYD-~d6sIjvPphM`A9)l%u`k=U^S`cPOZ3s(45s0*u87$JYg(l7wsMR z4I^;J@q>jgg)RKABBeN6+#-G@aNwQcPUg&EEoZ#Py|9gND0BdvOG}~b!dJ2j!5GK@ zQ~`5g2zx~T&IrK}KkjSD@`k&J%FK6cmsw%N@h&B`iOg@Y9$~Mb zX2=m<0NO*HfPx~UO#*fJ6Equs4(&(uF(qS&<;9?*=TLX_C^~}F(W1$gy7HmZgY~@! zJ6l_P8U|{;syP(~Wj=~y@)Y@_5|`rOlA|T9iq)0Nt9@&Z)`vHBwGZ}d@pcVUSTr|u zDcg%@B7{Y^_zy)-rOpyW@?KQScjD!7?{RFIPqAEd0lFMshS&WhH3o2j^C2JD4|)dP zK$5Vt%m!8%eGpyGK$-6u^~_V~OJFxJld^^g>E>&{cdu#ZHtRLAG$s`~)mYV8g|A#D zuP)qLN z%lY4N8=-73_9JEn=0JajnnRJ`1n3*QAKnG>p%U;hZwIXg9XzeTLnbW~)DX)J~`>1MN`AgN$Qn8#Yw<;wme=5{vWR+g|U$tos zHBAn!vd*i$1q0g%op`Si(OM9F#%E?ZXCrrn{eiQX*UfX|?&4H)f>`&MMJxqFfwJfw z^hsDE=7-%uZlE-}ka>scOaDj@!Mz~iEPGBZV;UBNy+i*-Fr=3DjVvH|k9-=k?7!Td z(4lEQ+rX^Pu6a}ON!_7(R;FJ%OF2ooSn)vVU#=~`TfU+CK`l^!qjd+qrp@c?8eBlWV?uMx)zEd{Rp1zc-Z3Nwe5$so}YY$ehS z|3bc^^~esW9()Z^p>}X95;|}5{K>$AM@7~`V|wE5$es=Qyb2AWc0-K|3mNuw_$q#;|i50_*BoYN9hLP-PM}}F-lOitXa_P)=26-)Uk{}Lbm`!%593N zc)e^q=c;U*u!KXD(2Z^gQ!oSB1@jo%2L;J#zfD91QLz>s}hFM*IrNJeJTcVHec zjhNIMK!u1++C}|9rs!0MwbxD~%D_O{>C`S(qxoEovF z+%}34)^Ir@si5BCtHEBqiLz?0z{JmRI&Ud$8`oZlX}v(B{*lfN-1!}4y$*v}gU0;m zp@&1SX@{u>yp;??U1;A0-Z!RSml5%2opaNEnrX!`)ufX8jM(zs*^K0I>9X{&WcRF# zd3V#UG#qT_se-%y8nBZU*{Jv@%t5o&;|*qhcAw&|b#Iz2m~__FXneQXZD$V$TfsLy zzmbQgi@>AG$bPF44S!MNq{0&eSQ~8k9Wu`7@1YYM7-<8s%@aCPi-Rbv21apPbywKT zTBAhs;EZfZ%=O=^V*7&3LbhitD*Y7+r&99|^n8+J53PY(dF!o>{a6$4IBj!X=xe_5 z>D*`Y7J0RdeYJGB?!xO{yKAPg2i!_9OaDoE1G~>k=2gv{`Y!^`6Il|8d)IL2%C0liObuaiA~! zXr`svW8p;7Hx`kbElm*J1zbk#sJ)!l;p(2XHJfs-wf&uaF1Rb|^!ry|_P^PEW6O*3 zfZls$PoF+&c-H)5Z?x}^hbc*UyEQY}7*Ge@Hw$#HoicB>&om3)n|>$en#?~pd+MZJ zGY(8+kI|cO#_^JMw(SPd4XKj*x1P?VpVGlJ7A-P3Y`zzXfTXNGd`s_gIG3=z_boJC zyS8FNqhtDo_@H#r$A_PjzCV7l;bqYC><44sM}PhFPW8><=e}^$y0bNris{3d)L_YC zml2bRCM{mBGZEh?-&l|62`}fdJ@h7cjz3|QJ|@JdfG0OSX}m^n4s;1#&VMB;G41C0 zB5{n#f;)x*oTDfOm`j<0eJ5NVcvT>l<@s{=#G$pSJfYc;V-``KiGX6 z4yP#7$(3bAH(0-L@g>i)V zIgcj%gnXd-f@b3L{5NDD$btPxFF^brd_vHt9gwE6ID@ac3_50NFEsSZrp#ZG$c zJ=68A$Iy63dta-grY`zN4Eq@C3FqljVJi4#z+#vP{7t{i`AJ(a6iM8PI6;;=8Sz)& zuD1Fve%l>QOYy#f7X?_tuc&8{3xDMOI34lnYyYR{#JW^!43dPFjA>hDQ)vQqX_ry?RA8moCH2g>S@iiuW{{~n(Dj6!+Ap7Oy$&eufv{**Frar>l+)T ziMMH#$qPL;D(58_WpkWSchHIbO)!Ihj6Q2_wI^do)xC}@+V!N5WGQ!ynXl_2`>E#Jy-cU@ zCe9eUb^MR<>zp~B)lQApTb!3#?9~?wy$nFp0iHguhd0PhceY0 z`lK+8Gmcv)ervhN>X;qkzQbdwi`w48stsh#fG@n##B=3NW4Mq_X*Ck?!!-af5q`WxZXT<8N7lyO`4j&0%))AEJKDFoecj!9Br>VQ+xPp?(}U=oU1E+KKNxF>Vj&x?HnYeoNkv z=976U{d=rY+~nA_n9GrRiQ$=t@~;%^&arBm-5%Ok1TG+IS-%XT&8$se%K(=KJ99U= zvynrii<_C3ZK{n%oMC!fcn7`3b74rBCU66o0xu)f;!!U{cqh^^di^2 zsF$@{Jhlk8-s<{ke2Ytw`!!bs$F6bbE!Q}-*|P;7%>6~v+3jKzhAHzJ=_|@&s)29V zTf%JGQ&31LryU#68_w39=#B32X?fqUp=y&NR6QmqB$b#R7gZbMoNyv~c{KhA7N<;1 zD`4i|El;aO%A-d9Chmj~={?>A!(-OruJsOj?vCT=4r3;6avyNqH~zc%3x})r8>Evh zRnkrTAfYdZ#ZRaEvwE>PP!z8TK7rQ5^M?;X^9j9#+e1g%5<6OJ7B}# z@txR_o1zV)rib4O^GTw`TBR&5Hp=L$cv?TEp@48kM*(k(56h~J4qH95TWKR26XUtg zX0unYW4zTFuNS5#?Rspk3j$5&N-8DRvM7#?U7IRVN4DmZChP53#4rXB=i7AAh z{>i;2-QzTXCRTaB;7IYq(sP8ON0t4!aCyU%Qdt8ygm(GBgIFURC|ho_ z+PKhUl0&)84u=$%dLxo^xZPU)dWVgoNvI^dxnBa)0da*x4!jQ{(ba=hzpK z8_!m{cI_rbX;sXlLMfkaly7<5+SJC*{kpxvag6&3i{UZHYHP&CxyVIPf*~3F8$d8lDIE4J@U68j*KZHQ#7`-#D=hQ@a#oX0UQ@r5s3F zpD++FiZxAYNV}LKDq549rqHyFukz^HOu3<3z~KpoxyuY)@twWS7O$M&*qnF4cheds zxm>e5VkmbQ5I`c(+)og}J@$+RK~y zZBsOca#KZazIRSx)~Q6pgwm9xbY?suDI~K!YpJTe$Xj`>eM{qEJhFFm_!pZeJ|?i2 z+%?mgmYSG3Ju+HpanJswew_U(Ps6cYKoMWrB*re^efqiGh36-CH|A?pE@R(yt$&+x^)Ff;GKergX+h)n8h@1r@qQbGqVCv1u{Zk{ zJPGY*8PNB05}2zYkQ0f`Wg5`+$yDScFhgfNe7b)|$I+&MmaWz2<=f=`MMHUoS(`Ek z(&lHW3K}wt@|zSUDzLe=c5P$GNX&>A%w}@XJ;HXQ3ZwUW_sxw>Z<~18J(p?>*O>JS z=Nb9(-!SS$dze~O$oK?5qy3{A?h41obM4;;pAUS{ zU1sQU`QmQ9g|fZI0k&IgMohhIW?DQkUuSz+Z?XOcLqm~=G@a8*KgZq2M$t#`LbMrr z#(atWChI|sa4f$5%_C5BuL&u`2l_|aw>O5=4cEr1y9?hH@bjN#rKhh>k)<@GDYLd_ zW@l9tdz2p4{Ah4$APrOy6QS9xbml5Ss&SuXr5V-gx~15r%2{G1H5XcMGWlw1aA`iv@YZ)t!U&hL%86*2KYiubvL1!^w zGSs1+({9_)T)n+wq1v=$P0s5aD0g=zlD;b~ETbV0E_zfjU7lTer{2DEbGK8^1#%=Z z!c5{_@vpgm#e0w6)YY!tnt`=keB~E#VC{>Q4%?Jgu-LKR8>MNy)a#pO}}Lv%ILN zbgSlR{UXhxj?7^IFhN5QA9RJ}i*c8}k)g36+vK`ssEN73L!(Ff!Lm)#Ih?DUrGo2> zA+!}e1cjip@YuIGPyqVEU+Hwng_uX?0PZw3DWQK!S4UrIudJoJR#f%AVo+5r4=xxk zI94)PNX_fW`zXIu(O7Yy!nyWb+n)Z*BX@|^#2#$7;HGf4^tSYc%*?dXe3FT~(L{p_ zMpH~y=v@`v5_t52hUoP|}E#fn;zdrD5=K??bI?-%z`L zb64#@RR=4)RrbYA`2~gbc>%e0dDVG4i?bE?$`-2ot6Ey__X@Q82DT8Zu=hNTs7E-& z|75JTl$w+!5uli3lxa{3k|6*Wd5Gu|^yke{>*kRvt@ zeGDum-qe+ic#b^Ko^8EYpVwfa2~s~Uxl?!`|835k?54DL87;YaxjwmBc`m9Wn!B3o zO{?2)4PK|+M;_868RnAv#&?WvnQBc~X1Nx&7RSt4Mz;-A2D7Cb#0~r>Ts51|Tur|L z$&pXY0a!;Z1Vl(A9;57|7!#W)p|lZv{%PrU?O)p6*s`G^q55&fXH}9iy6{LrTk*Jj zVqR+g^;c@rcjR6Wm`iqsO)BpE{?y zC)JzF@hy*vizS`R!1p(Sg}-l%q~!_|RJ{$2Mw7xwMaUL{|`E;GUzZ|K{F^)iNZ zrS!4rmDEpKB`Og|bMNtP^KLNAneP}Y7<%;Y=uzY(d=!5Rg-HJe>!41?AtsEafC1zs zREQQtvh6SGy4fAxYTlGxv#2~#tyXdrorUp*M-|#)LW!5$rTj=;tY&75s?D?KBY6(+ z0bGNmFnokFCAG3bv0O~m?~$eGFA%NZUl66R+gO#1Rme|lBQh3PPW6Bgm;+L1(`z-0Rsi=sq-+$R`CNa?W!8HlC2%DS4__u9qtH7M4nTM00R2^j7A0b~x(?yaB$8 zs1SE3khX*Jj5-M|h5Tq5(h{N)?4}ix*Xi^J4-Cc*Z5`OxHoqmHrN1t_?pfuQ@{Wpw z_$Yq1)Ie=mGr4+w&90i|t;D|5J%xj!p}nMT+-JRu<v;jn1nQ-5ZESI^VNw~Y;Twv~#iq_WLrnle|#SEYjjDkoIyS0B>&X?!|L`d?_9 z2xoA=!vgjSZXxdymnve)YW2GHi-b)QUull;yQGOThvUhk(v|2QjE^-U3b>Velghzd z;S%rx@Ha7%b{?Egc}!e2Ts3%nD10!gtGSKcrl}v-*iieroL2Qi1*+$(|1Nu9`MvUZ zb#(RE`X8OEJAM0t`VS4TKo8_6`V=*zC-GtgXGEzy8GnQDIQOnFk9&$4&UI!~q7xVs zvA@x&Xg(AKenZR9?I?_?fk=3ekp|C!<I>=)1OZhvUn-BVo}?uu44>}H>W%OG+4`$ByKYu3p=M3_>WT$wbG4x2pqg8;r}{zt ztftu3^ls~(aYK`+eZUfgi6t{W^YlgI#ODM%g{?w2@iZ}mvzt4cvmd+15TQljF8CHL zf&7$u4qOV1p&g*0WLxA5!~^rd9pqa80+&$TNw0L#gI~2L1|M}#YVB)D*F36wuX$5m zQ0Z7+sXAZoU(T;tuK8BatAEt&-LKN_92rkQh#$~6)>Za7wwTKmYDBN4mBM2JW6?`) z65oWkgF$1p;rn*xAX{J(4Fop<6Yw?AE^r=o6>T1oObnrYrGBFPO{f?g9*!U^A2jJs z?fJWXT8Eb1p!JJp=8x>CEp&c1e^W@!VZ{Z~hE_h3KLm!i9d|82&07~i{y zHZni*toVT}1C9^gm&$oLj6s$!Q-So;H={40Z|HjD5Y-Mmflh|r!~ch(vjA#i>jH3O zcazn);cmsHg(~$|xA*Gq?(W`K*H@$Nr3!7SK%p%ZrxY#j9`_La_aA1$43n8;*}M0i zd(U^iB?EvaQx4M>1LGVeW{z2_Tchr;`zasLeX%>ERnQK!e`pDA?`jEZ z6LzL{dCCh^eRYxAKvOw&3wD?1XKm!Y<^SQVbB)LuQdtPBq7u*I(dA>Nec@ zxA{Z43%FcvFFp>xiDWS*Gr5c*U_ST^sH1$L?}J+CzrjQF%~TZ105<_AXzNHn!hOnh zawb!lhUmDO732uNDQ%a|>Rj5{r;F01?wZ^6rCTQLR$SAR>jBe4(>nVIGIzWPK8q|x zc`P&c8*d~hjysC0;OM!h+22_Ghy?UFHUOOn*T4zP4d4TkqUwes8Q&o-olVZ9&EOVr z9krTriuR4gR=~jhNP0iC;_5XwQKiFCGxeU_WV-z8ir?B*ZgU*q25OyQ&v)mRpClKFs9 z1Vuv$*D)L_!#OQi8KB`=~n`~tF z+%CVa#?Jj+30-*ix1Rm-4Ap(DRR6E(ru7~r5}3h|GkwrMcqDr*cQ)6Ddx#sxeZptDg!AGD8S_rYhTG~Tu2Q`zj(4J$tY@!)v z>bGcHl{NCE(!_2*UBzq=eO=B(hG{m>eJf$x}8S5wUxpG62ZYxGW<90#ul;v z5GgDhi^)l6UnKs)m!mwi4Kb0KdkG^POacCanxQhV1uO>Ug3FonnKb4^Xd!S6GC~u< zS@au@QtL-+o8^p=uN$HcQ#_TQls0$QbZzda?>Ny|3Jb;i42YWJmBrA^H#>!xi9#J*XH&T0t+rS7uOe0c zRW_mLQ#Y&oeOGnQQ5hyrl3!IV)*;3KbAffL<1DQmoCq&O6R-sY!1~Oha#J}ESv^Ew z;wr8p^TE^Lql_nzjC5q206x(P`V!hGdIw34#L+EuJG~r;2TSN7^my7u>Pb?KlV|HO zuQZ)Ac#>s^5_O7Vg8Xk8Po5=9l19rW%L^1z)luzTy{A!R9${NZX{OI3W9HfLIn<9R zAj*jZ)-o1{mB|WWt;CmO(dZZ?61fGRU_1dUK@d`aUSK7i4mrHVS4 zZUzyi7QTya#B>-Hr{O-tRN^gm2CYX3L<--9-!S?^U!c`cB=i8f53V8q<2S)S&_k#X z$$H)ZsDKl&pEiba#_nxR#egwG_5L?FAIjj}^nyNP(b2n!@nkp5v#XN-orNtw_nQZ3X# zzem^7cLOs>nE5==NuLPdfHOUT_JvyLD6=bUQnS(2*O;JV>o85XqOU?Hn-9fW2f z{g|gA88jLiNIqc;05@PTd8a1>fxtvsDm8_4&5WYZ9qX(Y&4s7*E;NK~Y%Iy3{co3#f08&j^i!nVM%k20EG4V;CpljMj$qCwQi0_-8~jxkXs z5{KMG(vVE%0melJ#8|}GK*qaMz2h60_X@jH4Hph}-jx&!m z5W3@gV?Ev~0AdIJ&43pn-9K832z$Pow*> zvBW%lH?|qeL99p;`T)*iBtW0RF;F^m98l4izzfnHvj$vA*6?-!&*;BN&UOWzOBd6M zD7z?s91d%Sc~5Vtq1KS3Pgc9BcPm2_vs62jRq~IDtI7dtxz>R&v-y25VaAO+bcmLv8l<+UmdHQIb+R&9ylS3m zi}E9R&42Vqd!Ji2SZS0;v~j>Ah6lU^xqwZ&fa zXAFjv^!dOhU>Q&V*3(bZM}nK_93Tt0K>I**r9E}rvVXU4vtH?)VlwFBb&qtPRD9)C z`5);Cg+!4jWy*iaU6l_tF1miYL}R6Soz;tyM%x2CWx()gBnbbH7{oHMju6WU1-q41 zi^t&uPyltpHX=(IA0a;D5J|hP1J}{N09yKQdK%D&yy8Y$5apvoOj%)HY@KG=YHBsa zn*Js^vsvo5is7p1f4Uo%+$nRp#H;4>lWRGJO%y3Y9RIBaP@T z^bQ(9yd<9DPw-rH1$qd7hb=>9z~kWAa5fwPr;|pa6O2F5ZE{v)lDmy>^iDF?A59IR z80@I^bMIjD1QSdDUHeG&w`#0#ehD9Q~=a zbO!XA@e!VgTt=d?bND`d3SNW4XeTxXn@zHhRSXW(4PM5~0(X(5_5esp#wh;*1E7Oo zGjJ4qLO)9Sj?yW^94j0qo7ViOcZK1C?q9=N?OfFwe%+sZ5 zw;F`zd6t`Y7jnuR1}k*UIb1<6F_%RM(?DWY5CL`%3;SsyUmtoJxCH)GfnX(xvoolLLH~Nsd=mV zq}ZwOBA@9)YA5vx^#tu6y~^lszG)7%t)+BQN071HQicLWl z#B|&hje`#(ebL#-OePNPVXS8ChmJwj^cdhX@Qmh5W05523P-W6!!pi%+PvSi-7rLV zT)kPFpgE{quh=8&l4B}N`IH=I^HssxL%Kt{F8!rm(7MIm<2XrO0fsPOW-PoN-A}9} z>^P4w5z!ok6UFkz6Ywg+L9SuNa5y{*aUn;U9pnK)WGwugK9FSY%P3tA)Lv?FvNl+b z^xiTq)Q#3(Ft}(V)K&$iJgoLrjV8z0Y=u&_UwuUVR9&YXZ}2lA=Evq;Ho4;t^(K8d zP()t084g7Upto=ZYa^=zpH7~cJ!j8lk0v9+kwgjJ5C0E+$4DgKwVW{wS_@nU%IVvv zR~%36Pwn%pMP`X9#4w6v|6y%|+NwOO_E*;`gB3h^vy!9cs$*0Ms#0ybey#C_@vCWw zHPs$ZNpu{c?Sh!lT1GwjWOHySwiY!a8?a2`Ch-VMMC!4*_$X`;-VYuO{~_rL7=F(P zhkikM;AkM2RznS;4x=o#N17K{JS`uLdkn5x7j3?-OJh{tmzOI?s^_Y1D0zyHswnMo z?Ll3guGaY2th0QyUbA1Oy{9DsTcP#fVB`Zj4oODWp;g!u!j&jSFQX0E4D2MXhj%d6 zqKn|8%z;cMaEsi3%jt)ycd1v&*mIkMZSAqVwT(1K_EsB|x_qNWPt!sgAI(CIw+7LA zs#d5Pv=DiJYT{A)X{*{uhx(a4;w*{=g^&K9+X8CsqMY_uJxwbsdv3;u+G`QG#E96cDg!G zwM+9t8?35PNVTK1*R>*jo@Sb1UGIrrTW`HtY+p%9qb5>@(c2jhz$;7{3^Ri8HH0hr zmZV)(xQ*Ds;$voPJON_Yh}FnJ#vV+{Y=Dk3=FlI|ZULFp-PUT`0o%W3ibbnGq91C~ zYs0kj)i;$dHM=xzicxY`b-5}^by)jCd0lRhKuGw#%x`yu3fcB^Gg$`8laRYmnw1bB>Eyo4|5?@h^fd*)lv9t_950+_C@YgVm@2SIZnWwVvXVyHoB|bk6P?FW*ucO>h0gV&ho{?GHDH%A;`Gaw3Xa*1e&OZYx+;7Al*2_ z<6ge;fN_|q)U>)+U;)h6dUx11k>6mFLu9FOBvK#RPmrtrN6Kq@GB|=Zl71U<1(eVn zh(>Q>e1m*JoUs^q39Kd0hjOXcfmJ{>br%3rYwQ!~k(A|*RL3`4KgTME-S)}4+kVX^ zw@#&8vi!1yQsS+Rwu26#{U)W0=0`t5O9$VRcWy1x0DXXUSR5?EMvyj^H%Kwu0Ia5) z=qDX>>;r8HhFiueeWU7;+DV?&vqE;Ho8Q^qHLtt5GfHNWKI!hOoTvOpol8Egmn_>U zlJpHc+KvE@rl{Eh1s8scei4`=pkkhJ&W1GO2=+_)LESMeej!*ie2QKh>$wN$p+e5#YQ( z>{5VOGQ%gv2N0ZeyAXWPMdI???E|MkaMh*A<+s-nVI+aNuWEF6T!7xmqyl-S+p}zO=jspG-^n{GR>9 z5|((q4X^GipRjbqfC$Hgm$L`PR*yNch#I%J-;wcC0=~E( z94rlu4AZ$WpW*Re@p`Ie5t}H`sJ47 z{Uh5Cy9@CwxH5QEpKD=tO!B=o*t!gpuAgV$&mT25LL{xsI~YYfPG@b$%~ zpqKS87Q9%U`1an4E5B}(-rauV!ZYpH`WJ_u{F54+ZY^4_6=<_N+j>94?k*RC&xPKL z$%{z||1$F71ZCXq(K{zs#|{}(H2P7X!;kdL1U>SnhEC&M=I-*cxLglf7Epzmcpu!H z-9|($kGtS<%=Lp`mYX7k7F@*(5*U$i#|yOBbgJVoS#8a=pGkR_(yzXL@G|tZ@oD6P zrMD+1T)WzGb6}G0HTTY|=XX=Ky$>wQ>e*UZUqz_Zx{F}8B*^WNYnbbi5RV~A{q7H_ z9RmzWh@Cf{`PclYDI*U4Rn_mGL8k|xe)%3FLTW;Kg2wsu<1XfFT>PBB2b^+$fi&_q z3Ig~FGM`$?31buNdges_Ik-yy!1heBzZsU!sZjkKSh(*SCE5N)^@w(J;Js7#K0kc( zc<6s;@8&)q^4y#~xA0B+sj{FJVb>2ji@z4{7EK}915^LEao+vsj6Bn?U;Ob2p>Y{; z*l>H)wurm2@BPa?z6a0r_VN1_a9#LBRL5T=c-;Km=Lk zAzV$A;xuTb-eO2p`%7Khgq6~Yv%mXfp8ZZwJN;%@@`2Ba#LP|Cf3&P_{gaE;DK{Ty^Grz8!}`-=y3nL zVI}@&+*say`8n7G=i5AY$#&Oxcng_7IKhecdFIU*W=jOlqs1S5+`MLU`Uy6`g^mDZ zZ0{X)j%spULiLXdW7hR=i@*N;p*TgF9Q>pxaovmE&$oXq`PlGQmp&@>SuU?>c5_Vo zk)9qI(*j^~*z3d$S3vN=H#aORaABNBOi%EISf7}kk=LVhBcRA1QBh%FK#MQnI@$AT z&|Bx1F26*L%t=nqT#LnP-7au`A_+Wi(RnAxV<YZNZSr*TsX|?s_fgx=)GM5YKu1qe7NPHg7clH7mClp`0+j?H;b;)CQJPs=xhANdNd< zpET{Q$JZ@y8xy5(8y;Oq`u)xOOUlosKi7QAsc2|ocMUVXHg40!K~7wzNaLUGCH2sR z{~gSYoG~COGAxGDZ(U?%7x=;+xD8k~Z%nLS6eh z?L<_pIfR)xl#^f`rXOMBcdl!#=$KwgzM**Vj~A&GpInkJCpWxblXNqsJo)X*52;a^ zgK}FdUXsq6_SRwA;kGEoeC!?ZRT$`c!_N}FHB=Pd85tU3jT{uCh+G^J6?Qt584&F| z*2~$G>+a-M?gk6|xl}%fx6q|W@=R#p7h`VXTW)sYbti{7j2Ough(5BDi1FA=+g$5$ zlZP@zv%UFqb$ZjgB1QJd{JE*^pWkQ1e4P1C{_b*;^wZOCS2LLTS@}g-9o19XOFF)r zF4(@A?lPAOYQmDk z_ujkC0BXf)=k=D8|Z8w2Z4= z`=_SNC+}uXOm1d+RH`B6!G{H3=6pJsCj8d*aY;t}ue9G0e+;d&T6k^yj4h6MMi6h8 zK*Zj^LAl9e-8&hD1tx{ zm%Y)&Azp|5?bOGegeE<+-3kOpg#!h9Imb8%X#<{MzaWJ=^SV@Jn|^)&HSgQ`FHs*4 zC0&0LlGOHgYx3*lhp#_>xsv6bSyA?;R#y78ZJpt7qmmv$q$3?1l}okT=DHJfOgvzRO+neE2?z?zi1AkyNtNy~Wwh{ic_%;55&}^MG@e zc&|qbKLlUT8^~^B_a$Ng9rcB!MV+BbQqvl9o7|g=ip~}v`n5J)oO$Z|&CjpXJ|y$r zMP^Xb+S5G>@$9r8E2{o(ur@~P1{oMy2c3@>3buK;x<~ss^|=@KzoXFbM!Sl7 zgRBhYu;!3)Y`*vXfMPF2A8ODzuZK}!$Q`$s@cBV8&b$5Gd<`O+$62pyqBBkfqJ@0G z<*4wJ=!%Cw`y*?Nhe)u4cgABZ4@1uj>j@NVV>Qw3v4!+ z5Pf*8+fCo0eFTyT&Ko`Z3i3qLoTm!xk~iFLHtc$j|3PrbYo+Kuca6tH=Mh4!+fFf) z_4hv}aG?aIQ^_bd5RRmUkE@Nfi{K?8HP1}|}`0Mx7&Wx(m+i8|;Th7}2 z{K~GHeO2x4LsZqu8qVtT&D~ zZH1W_!2aUW=4A57_RVyE64V*8-D_LW!jN7!C(ph9f1JlltnS0aVf=9)P<}i&2sr?~fLo}P-X7CZ9p0sCVYOYUv6iwb zV~UBKu{k9_{WJSyqCcUWjy!WtL(#12&y^n=Acc3=Jy}1})PJ1b!YSifc_}XMT>E=* z{SSHk2pABEyCnE^1q_uq+>^c8{0IDFPBJ!|y^TML-NreD55}(ue&XJona*PNBUY~S z8^I6mA?IXaU$!Ur5xIxG4O<-Dv{hz{?7NmFTTy?l1!`DZyuFlP5|h(ekWg?Z+g3O> z*EhGdEUoxpnYGEOkiO zufa~6-KB25!hb|TPG0=G_(aZ2jtV`Ccd{q2y5Ya@!Q3`9o)sV%&svSQa!zor<4StJX0mOY50i(LfMInRV9!E|mD{}$JmZATNK7tAZv1*Sl&NCOKhQBxQ6S6JJ+o6C?$C3b z&qn#RqW_%quFu?;xeI(?uS?FQo^>8_al31P%S+yDekOkaS#!IeHJLR4ONUV$X9Xko zi5hM*8pB#B=+91LhYPISUF^Z^!NhXxA-o7Y0gksP>FbSeRN9W)-TAG5Rk>DguAKF2 z&F_!Jy!`b=iUN85iLz6ri_51rC>qqwwX$s0Lv6JEBRvmXiLc@?1PZ4J=Tvu=ce$Iz zgYQ$~e!~s+n&yHDr%Uz;ZR|PRZ)|`3Ig*G>#_7lyxPUm1?SQYc#{~ zKv2ZZ;gqogSxd3|zys3Y=59>WuF<~fEN^<(EUb1eyIcOI_;&uTf~LG(d1Zz1MQ*>N zD*mnTs2kI+Yp<03G+@1L_SuZ}~ynwwv$${NN0(&S?DCt z2!3*J;fn|_7JwGdzbrsZ*R}HuDd*DxlMCg>as(k;cekXaZA`StU-i^@FKbB zT|^I7g!Hi7*-|Wly_)xmJ%F=DXlo6DDzkNZ&~T$iYZmw>*qEV8|!J1)@ckbj7I+&Q8op^uZ+<+`)dZM@rer&^~J$tDpl^ygF9zcF``iZ;Pg z1{*GhT*3PY3|koFxiA`ob+P?8cR3~8i`-6b3s1m7*(%~O^F2&rg7)?HtG#{HBUP7W z>h=%K7aD4+t$*_V94dD$A6Yt}^jhVqio4Yt8-~~a)wZI?SvFaF*fi11q26T-f(h1F z?sRUn?+0-Z+e0iO@0mA}zyRU7@Ni~-M2`%{ z#biD|nRSL!%pJp>%|Fdu#Jb6@#iyeinOo>|%4qX#qo*!QmfZ!kL5-@~K{aKSwvz19 zMDtDI4W~X&dv&1h2FZumAq4*1I zI9dza7?Yt!vgY_KnIn=gBAC~irO18kHhzs|CLq>!b|)KQA118@!SD-41^AwlKx*9i z+H%z#X?$l+>o}4qUS7T6PiFaz^4DcI%e?=LuBxlu-5A@{zq3-dM=3KTTXJm20T~j5 zpXbC2RtfG(W;l;_j&+;o#FjKVUlX|sjY1~p9JxkJ!3L2TVi4*N$RRm!7aGMl2Hayj zhD+dVtOs9;JtpEfS*(YgMz#jKMErv#z$f5W^x+QFwxl;rU!^XSEhdS)zgvuraSd{^ zK4)FEsd7@Ssdhxo+J;drMEkOChO$=W(C)UZa#YbN%t44AzsCNLzeZFibQe*{);U{6 zGSPie2xmB_kV_}pv7eZVkqNG6ra^CkL(mu?14v?QVcbQIA^FTvcqGZCY~k$WtR-Hu z8C)R|PYghHpai;0{cVf0?$MsN8&BVg0E&SLhm6% z$!ww-S_Um=oMF6#Ka%Xz2ZT$^C(Sf7xN;(Z^_(+~eT=<^B}XHX0_G3!3Y|fj(fi%t zuUn}2PuA9z*pk`wxnWV=@Y-ip_@66PjB2D6+!yQ>%EXP%nCO|v%XzrXaHz55X*SDH?<*iT*erAvt}_ z1$~M(Ku>^=bg|uN-DS~g6w0H@UtOy9tL@yz6?HS~pVus}-CMh@I<)>?|#B{?_kk{6JlYwlHqYHT}ml8dG+2k zv@7l?m&o(lp0qW#ooxKmxS~;7ySSmXVRz%7w$ZHv+7e|2axWF5mth`mZlk3@3z#E` zeXLh(Prg6@kVqc_8j?*jUz3C1yC$~G3_dC7)=5c(q2**fJY%O zs2mB!oZ<2K3if5f&VI_tA%+oB{5qyad>Q_9U+Qn`B-1J5BelLqAU)POpheUCx$$jn zeBFThsdahv+ZxcOytbho6MC{$LsbgxFl)JO4%LU`*0;i*EQIrbdyg;Ue&dS;0C%dO zn%57%%X-2phrP(`^>Oem_?;0+a#cQHE8UYJgf5d$0}pm%Rucn=b@&(V2X-#DoULa~ zBhm>SEF^PdX%r^)x&6BSvo>F|Lb|DYR=2%*WaGr9E%l%3lj^iJV8fk;{Y}q0!aGlN zw98>_mv)vVo>FMn&^zGq*ejNp)1SMA_lnml;E3n*^&D@(JB~l031CfnS5mfU(r(-d(*^Lz1FM zc}MoH^1w6YWHF8H-9U4PE-)q$=gEqX zDZC0^wWw3fu{s8tcDWZCMB3WU zGicaU+>^|EKg0b=ui8pxC37#-OPxVs*@l{481CpNC_nc2OFcV(kW}doawC1b!L8|i z)7qw;%@wWM4s}r5zX#t%g3w@g6|arIO6V#4E&N-!g`X-~C)mL1 z<-B9Vs1D6QKY^D>uJRPUoOX%Ep?1=g)W`IE#tQlq`c>G@NM;I%@91jgT#SM3L!P0B z8ATut{N;E@Nw($bpXeo8zU;TOqleR0)^f1*WYgp3r%jg{n_8B%Tx~xgo!ldpV%m0n znaPjxnRbiznX!d*_g0aSk1KxwALUU*df^W43T`S_LKI_riDz&uWP@Ts9m)Q!rG`;M z9kZwyu#dKjdJ8Bat?|QYp5 zyI*%r?aXR<-#o54wqagFWYhBIscq|9Pqf!bm&m-;k4%9kAM+Ec5;)BW#}5&!h$v1B zca$Jm@QTCYzv9~PcuoL&0=yn(BZELUum*fe%_mKmi)rJ)3$!E}S9bbOy8lyi9#>d1cwy%hTLaWXN}R6|_HZzuO$tSU?tsfK68# z`ZcPWTS+V74k=gBAy;U^Oups-%41+A_=Ry6eaq5uQpiZ_Z~h@3Oja4!^WxZS?s?W& z>>YjvJ;TsI(;0fugXBb$=rTYCe1zCY2Xuz96dR8m8hv-aa897;yK6Il1iHhN6P zK3W*9)h;$(*4L{YJ+QO4cRtK^3Y_Xr5dC&?p$lkmO3hZDvL=O$tfn1za&n;2BaIp7YknIznF zYGXm)nAX$;^q&ABYZ@b6A2{!4#pZ(3RZ#eHOLy-mu?s*hB*oOU7MR zAO@(QKcx+!E}~SBo#{L%F7)x>HriEsDg$IpL(&Kz>( z&X}bnPh?g+SD-R+M_DWBCu`C)zHEHnFtYJ#Q(G(ERn%G8{Y)9I8E717H#w}7YhW@g z!X=z|?hh`?d(ABsj22Gkp5xBsBxAAoHvA|v2HFkvkQ8Dq?f>2M&Qp4*z90ciB#HhP z3?}>w=djjeMtn7UJUg1ZiTjb|h11Y8$Oza4TuqI&(|cd&AL|m8#_mI12iu3WTx_1- zxSeda=+bbziP5ZUYHUsH7~6eZDb$_UwHdptODT^@<@PEx6*J>7XCMC(-> zN#-SUe&9LyLhK1ivh8DLfNekvumUuL>wvz13~U6yGtZJt=LRGgk09=_=5w97_gHsH z|MyMeIDQq`z*s~NbKJ6BFc<0esJ!G=UH`TZZavpnP@7frsHSgCcJ-_3VYMFhiA|RF zi(PwrhAUY*ZtqJ+I7t>9hF9TPoEm{2c{V>r+*fo<{Fl?;qEJBse;DUHYcWfMRx=4^ z0W<{?kuKYbbQj=X(vV1UV2p{(M3Ugy%*x~3VeKTStU0XRBuP%i%9&lj8QKl%G#lL% ztrICF(sA8O+x?qQHC(OZ*IlaR)v#-3*PwM|aa3DFTSezJsZ{yLu-Q;;tY(+vkFa7)3eO^YA>IKyz-nkRFp2&j9RL8v9%d#Zp0SjqUtXc}@CjHtxt_H$ z$0ASQ1B}_gV9I~?4lCQtH*VLIDZa}N^xW(O+mE!|C3A|Kns1S7+S8_n7T3-TJ)!ap zMTDBr-!bo?_|V0mkmT8?68*SOcvh~7+ru5p^B~WnHEf(c4L^egV@9SD3Wb_UrsXQ= zPJc-i0~>)1Xg|D{k<0iBuSdgJ*GK|?5JPx9$X#MO z9*Y%VPl+0GN2|iR;9ty@jF(U*D4|@iUNc`b?ADc&cP+L1cqh;?tMzA7c>||mHo1Cy zZ@S+2ldK4ow?onuiZ9BWnm~P|*-Tjm7)T!b21!E;x!d^&FOLuKU-Gg9llcpHQ@L5J zRMv0S6YLQ(3XO-y!cWmlq^ErJSU!SZLOW~gV9V=SCH6=9U*S6JttvA(ftgEYwZ3Nq1bNv}u&=PBh<|6XM=m@kND2Le0*`yoUlerdz zAOqPeqLuV-WAuNi1Z9)sguR8V$zEcZtYN6mDl??pyAE^>>Xf%lY`fjMzja$%ZF_yE zt@BFPYMD_PuU%>MF&9|)b43G$7NM7jBjgJc1mAeuIeS?P zh}YOD7-8-rW9uql3HXGzk@AyLL0L*kB0HjX+h5wk9a|~u9If_^_8v=}nQ9HO{N2kl znGIBfSRbuls~@iIQs-!1D*fa;m77)jRD|w`;kCiV)MFTG{z@5b6FcV7mRK8rB6uP7 zGb+Jrnd{ioSWnUZtRq-oe5&XWs}_eHnpnU?F!&9r9gRqGbh67x)BtMY{L ztf8M~uHjObXX_H(a`Syvu5^WVo|$7G59|d|+iM0+97eC>|EK*>wo-J9OOPXBhK6uC?dfnL*j% zS0#F{t_WS+wCHty=LXh!gt}h}@#DCP*88k?7z_>QENmMVsK4D~X)iI4aOCJuYA*b~ zqZmkaQJthbYtvNC(T$~qcE?vWwGFS>(7L_x%AX-s?Nt@ET_w}A3;(!jE|d?-*icu+ z+@XF*$jHP<+MZuUS#D4-- z*fjK^(TS~eH90>L&8R=5{^lyAJ`k9?(tnm#HAcc z&V23uZE#lb!)+NkW5a22taihE|2W9$Pqc|b#Cv`J?e`^q^T2tbZ%0jvzuxcI(6K&>zO4gA zPHlcay-zqN`YU)1v~b}I*B9I+qP2h&TC8M?4CyHa8UwFW8cO=so@0xa6+Kw_bL>S(Q5S)3|Rp$zDcpx($YA z%W}%(-pQi%5e0ERaeKp`46dBK_@A;-CBxDuZ-{5bjf|=En-kR%tmOrWVFySHC8G?RtCt?c1l< zo}K>kFm215-OogMVa<(A^zNPQQnOhc=(5m_?Oh!;b=chDkA`u_c27Jo^7)u)6RQX9 z8X%8L3wjvxZ|J|?m)s4`nfwmcIIci^RI~`VXk3Rl^8z{7(V6;N#+A&iK(jowHLh!Y zV^euh-k+cOxx2o~-sino|I+1CT++x#u`lL+ee~_r_bb0PS1xD}Xw5WtMkRiiNB4Lc zv24i4!S#_@ao*z#CwmVK9Hbd8j$0gkDZ(#^5tb5~<*FwRI+ysrcN@-z;4r}~$sR#p z&O2rhHdT0vCAS}>y|n+O8lzCi3{Cfbk1MFio|}0q!|%h8H}H$-*Kc2?KW+K==0|Ia z`hCl<)^=agMV_SiVP7Gf;&a2b&b2rE-oUYO8=}18?+(@WyAxLsgG8Q)XbJi5S?V&< zzpwXS;>Y|>(N)0`UaCu?NWxa3i@BF214Ty!JLvv)Kg@x~85eX~dV;zlYxM#wNz3p+-+Q5hV^#+)2#AP% z+_y7iZJ)1!65sRQ=R6afIwjLwr;64xSFkpC>=ti9pCA*s8=Z!_s@Ys-A?76d$vJGV zrw(&m(>tW8U7Nf6|B0#eFTD7p=+}hI7wOrl4>CA;{nJ^$##fh@O{;#|zP$H{Bb*w^ z7=+o}xnWDaE8N!y?dh8oA@FYudlb0I=Y#JwlBEbINv`|&XJ8m7LeK|`qCH2xBE`&N zVJ{geU%?NPJt(!vZ6e0e$MnjUV9C>cY@F28r}aRorQDon|9-ZR^}Qv{=ZC0pUb)}T z;YGJw&UU5h7pe=)4U8m?!dKv&?mIR-CX|%%M%NB12-gNlq9Oyo1Pt<%y1Wvl2(2{i8O^2Y2>#*HsJuEUsIeo%3tT zkKrHxF4**aL?$c4n$@Qw@7K`QvW{)tHHMAma@KMFS|^#$e_kUZ&J3)MJs+d!yD5Ht zED@R1mmhRH@LbSgHxIXuE+<6OIs1^YSQ76P&av-CjzjZ7GvAjsmmUl3rA(uo?@hL1 zvMHn&r=c#nt*Ss;_~=*UXJfWoTKoswx3izxzTV5I&YM&r`kmgeyz7u=Ev?b!Py7;0 zbSsI#1Ir_%F;C+{2c3#_3^hlo!l9U1fmcHcgNAyQyF7DxFZL6Em0ZB~@a-%s7$u1& z5LCoXWrU#V=ttmm3EciAQ<857qs5*d2m7C)Ua8h z3;IMwws=1GV*2u&J>1{891nnA;-OPy8AGG`bUCh_o|? zTVpJvOcOg+X+|~kE2gxJ&R+1_x9nu9Xa3CZ_22CUvvWq2GJkj0)wD=E3auA)n*dMr zKX|wY;hN*=;XBW-D0pO`XV`>*K0d4bf4MAnk8~R@l<+@r!U+R$fgc8@!V|coXg}F< z+(g^KKLwXFN3p7)bmlS2BnNB^H($~3tV@v}uB$2&wQtJaSd5iS{qFdknET;ZZ%K0T zkJ@n!J!Fl_ZS8Gvm18MKC@$iozDIpzexm}_!A=n?0+#g|@7M18E+E0FMFNP&@O%0D z*!!4Mm`Aw@vu?$cb_iv?pjnapeU(& zTE>}QeM;JX4E_b@iOOoLldEYhq3uDY9Y%lq7M2Pb=N97L^#GuS&~Z}96-T0}tb zzu`;$u6iHxo$UI{*;#y1kiZ_rwjwi;N`4h(J)@eFN7=>-Vs_aR@&AB{lmOd!+ZoGM z^;o5>ajI0;)?4tZZhx*lJD@V^%YdxGd6_>l3RdJUE~}~hQ-8i=K(~|SNAGrM6c1ws zc<=Ms>oquRQN+!Nju1n%YjkNJPUs9jPycxV3Xc}aSN?d3Ky;2b8lH_>#C&iKvX{%F zKjf{zT&UOKd89c6v|R2DHvpv+_pGeOZ|BA4rC{@spkiUbmpr()2@`{isXo`5_mnLPV1m#7pk!aR zXNb4f`IF0amy1sOxSQBU;bpF~AO*RFH9Bv=CU7u*CbB~Oi2IH4lmU@v_y+TRv$Lv0 zK2PzdCb&7gYD_`tpT*fomUGeOOuxKcg_XZTe;5C`*|xDQQ~5uR&N{5g_iw}NC)V8? z-KZ#bw_;#-V|RCV`^E0=4h&R4KtTzmo3SxA7;KDfELgtx?>#&Y|J3by?mcmTuIoI@ z%;t{X=u+e?dm;a*uq@Az;#idA}%NXWBV9kUDXVOqI;u*U3zgD;lX6cL@60X8 zp%gY$->6xt%5VIlU+sD4rQo_#F47t$9RFz&cHnEpm0@&R4| zzhwmyTZxm1e=&pbe{c^RKMiZFb6ZzwPHF88L-AQu)b{w>vFcmkPQ3h$>RV*kwRWc=q zS={3vBRejC;y*)C_wx7UDHZYB9&Ia({tmfw9PTP}2kRs|hyOv+Bj9?`y|!+P?`ffyCe z6pt5lFfY*O(^s)R(jP$#7*7qRZ6j}k{t1b?`S@Ju z2}Wk~neC>jCRy{jx@EQdYho($i?96E<<%Di7gp!I%==N6P}8oMt5m7$+IG1Ld|z;M zBAb3)G*ZTq2L!kRCWppGQzG($`vk=X&j>mrA1YA_2aCE3?l8~OR?_jD26`?a$Q6)# zgMHbVgbYBWs6$skgI%3FoX%H{?DiG<&?b@wt=9o3V^|rjOjjITp!s{d*jO^HoLk*o zd9==?yKbIfEp0#Mgh_!6Cu=CHD+>~z32q1&BKs>J6UYz#0I6}c0BI_ z{|jdybqsASb1-u@`wE2uzagKd?xdfC8bA}$2xxTcoNwE@v`o>BZvL&>R6V8YY1zjz zQSsn^4-1j92^G|mkEQ>VO*(&lT#KQt$fYM9BdxlK+8qT+Lv5I`%F~hJ2kqBl#;Ry*@ z{7A^~n;g0+km&bOIz%?juUVMP&*Yv4$viT}hI|2(k2{nQd?E1qga0tX)#Oy*#<{OSz%6YgI|@?Ya=fJylzCYkRuQ z(~fncguOI~Yhw7Z)WSl)SAHwybL3a0{r&S~0!h5&AOE?~3T(I6*#{Y?87>MHSwqeO zPx&pV2wx3dgXsH+RjC4)%8J$sWL5AMCvgTmTNX2`_HE_-PTYkK7S6OJ)^~%!~ z{i-wS>zgx-PmELB-?`2bPSCCZ3(GO?UGZeUk^V_?wlvOvRnTeq44FW5UHD3Li@k@l zlNG_hGQS|H=yb|Y+7p_da0aeLA<*rtCvL*eC0r*OiBax@_Qe*S`ElF#Mt#Eu1ycR0 zx~!kZY=6drS?_+EoCKmaR(ISio^!)eI;DCfMX=ruu z{J!(a7%5#!5dGxN6b<71X4h~U7$Wv7iiZ4% zHkvVtJs)wv4=Cl-v79sHVvsJ1g(A>@xXDh~hFT}sXomA3r}w2+s=8kJ6nJOfl{_eU zT=KePboJzVMm?r}r>?ry<7~rD_ptDN(MWcK=)UkJ-y|diya>qf|0{ngjF7*ReB$*I zZerhHXEQsIr%;P1H8d}>2v3H`z*ngf@)+z;;x^(wWGO=QJ#hDS9q<%%)L3@6RW>Fz z`!(HBI#knZ&Q*P{URb`U@~R?SakMr_6{SwnYi$ZM$!c|f#6PDs&<&K`%yfYj5c|H! z{KN}nfBavGOJoPdb-aEe9=nXaj}y*Zz+8%62ltMj;1ehaal{&8EU6y7gDZAFbZrB( zhZ_4M)9RKBEtUE^4fECC>K0TJ>ef^m%SR~4wa2QHYvYvfw4;svTk~4i+55VRhz_)! zWTO-@AM-wopGxut2L$`%J)|$Bk&^ygt1y*U#$LiZ&(JV?FpiO9sFk#IkWYLJ^(KD@ zxy`W@EZ%^(;v-0U?7@y(wg}5l^C-h7-6;*Oc5B_~>i?x6_m^cB7nF6Wh^)G*oS}|w zh-u~;p4vZSQ@m5~BhkzBwL** zJ!o;{iKLf+vv3gfdo6$u`H|=(t$<;ipSRHY&pE@{VV%}CwJA!swdt+0yE3+hQL(CO zemPops=BfUt;Q;rsP;DvG0$rIZ4$aB_%ui)aMEI!`Md&=Svt^_N9Z2i)7;0ghmcz>v_~TlZHjt{2ojui8{~vVvGKr5aoNpn6d)wtl*{ zqIHYujOm>N>-k8WLtP07>-X6Q1(DK|@>P-?(G_{JY?o}2Oe7d1%}`ELK2!wNq}Ez%$%;qndrkjouj{nN_m(>Md*4Ri958P)(+61 zinT>74SRKKTMDu>pcP&}-SuN$lqEAJ`O8-g35R)^VRnr?^OJF!lX zy$B?Cp}%2!`Cmmdz)noC=#=OpFp;%#mvfRh7eH>efDu5$QHCNTkP&DcxeMUCDhQK_ z0{9!$3g$?$K9BE@uhsp_-fG@z`DNTiwP8?QHI8oHY%Idq;_&qK^>;x{ywsbL;u1hX%2iTvThh5YC2bWRm}E^9h_ zD&qrn66G*D9;BP5fvIa1v;z1bM}gTm1mA$_088XL5=%M;9BVYpW-rC{&RW?v&Nx~B zTjy%TtJf&2>Jos(@TrQf3R3P-_iv2S?>70H$65C|$NFTz*LW1r&BZJYXPS^C8pT)f zO(1c)UYN_%uz#{NjA4wfjD6HVvK&wo8DpEJ+F;F9FeCh`#@B0=v+FlBY-t<>m~Pu!XPd)qE8SbLDr^T~3Zew& zG6Or4HI3uoPT_wOT;^}&+Bt6ab#^CC0;>hE5=T(WssCsw^(5MXEJ605Lb49_k}QN* z__g@HxH#WVXD9nz>t0Jk+f)6TrZ8<-13~RnZLYhlC{=8!i>YsJ*rLTYsapQDU9#SC z@;ueP)dUwD52%nknH$(eoKd`4LIP-l&f?zWOyZp2)UYb)m#HDt9n^(XJ^4I(9q9`P z0aDW+^d8{c20|EQ2hx>v27AjJ;bVIjJ0IBgntTRC|EBqG6RDv|*{v>A;jgSy;Z){2 zF#p$nZ+00En_De|9e8gIVL3tr|JHZ<8ul(BMP%Te=4%Cqgj`XafW~FBvzXVJGnlb- zJe7t1fo>uS@(hFm_=L*|IV3hbj(8Av%ct}eU^2bi+}ZY17R2(w{IspRp4t?lrE5lN zXc|;qqjIPeYHZ`s=9Bt;dSC0p_Auv6-x$m#>{a3dbQog{Yd5om)tlqT6AE7m_VOg$ zkL)X)yWBaPz06s(Ollcz7RY>FBUd6?NQ219*>DYM3;r{9F+LNQ*+u${UZiznC>foZ zi`gp#V!>x_9cL^%jML63WA|jDG(B}7Z9DYu=-aTs8)+Thv9EYd3c zIWP&n;#uQlbXY8_4B0I!TRt}3)6AbP)!rd7P!d+QS1- zKM)Me+7Yy!tRlWbFj!C~c)~v-poj>fd<7W>2SLM# zbD_TQd(v;>AY2NjKi1+Min{TV!n+hNHUbh6`%AVYk|$!m2{*;*`3EE{)f9 zH=FOa#F|gp=eXK@5!gUN9`cnso0$u^r)BKR+(O}h!sS9f{|$Qp?=o*2SHtGhFVp^l zGR{EF z+GPrsx?4kS!%yuTz#1_DKcT6k+4bAA0IaB$_`Ny6fuc#}PYTw%@ywqvcvmVqsk znh1?T^?_Pd&4ju`b=K;5fxdy30t^;6;I)96m=3jpv;74) zln^Y@H_11`X|Z;4vn1zK!p-_nNz#d6rVU zpIhoaf%Ah-BI&gM7zvD@tV%AzpDY|L)UXBI3;ZH(4nLm#o;sOX3sNCd82!jBbTpMj z<&$&JM{o*h9(!`=5iDU!X4pB&*;1)=2!kWn-F+5}$vX8u$+y~GLN#M8K6Xdt1X}^7bLq4K`c4W$@uBD8Sfm^h5+y`0 zP7gBmH##(SucOxNY}wT^OZT_wMN`lE-?i83jP-l#kE-^nMyQT89&fqZa<+|YUTK58 zHJ)R*DWLIjnlhJhn|XvYLDd~#=W zB6I@DMy8Nv0G`|ld=2)n@1y6G_l+yby2bLW?X|J2tyeSBaJ6Bvrmf+Dy06NnD6W?@ zJ<_gjs%yTkk89`HA{}1uLCkx?RiqW^OM44EFe6xI?oR$M!9Gbf?+<$@{}Hd4AILpJ z^J7e9exp^>-jWlcF({jSfYe5$kP<*XvmVpxdFScuncQ*N>NSrtU$nSe9|4PZ9C!}- z+jvslMR~J+ed7jgA1$QA=uWhLGhZ?%+SfU4o-z3S_;6@BwU{=T9?FulmvcS|5Avt6 z&-1?WCi9>31+3G|7FG@uWxc0mp!X@KsWk{6NrF3*evvj3e_#hI!vDC!4ZdivzT=zygUxOGZPl7ywi%2yCX#td8_qV| zCbz9=-|6^3>h0S`SjSj_IYlXuk?2j#2_hRCCtNL?2|B4?1hw!L##+Yv_GQRp{FjDh z*ngNN)yB5O*7&mcrnL@=B27I_3zy9?+|&!yTP%*IgPI*4i@VTNi96xzX^tS{NwDiJ zjp^KkEntjuoI#|N63Z!~hL;UB5u!=ETnzSkK{I+pj$jLXFS;$^PUm*%OJzL3hV|2T zff#>!UXt!6?CeGo_u{2T9I#2yqmlt_q1c<`w34{8z3dv*<{$g?t6xug|I7L+Yrxl2 zzpXE_(q83m{dO&PL1|=??RRZ^Q_+&<&e%;A1Y0PjAMv!5%l2dSj&2Sb9~9b48_?v} zcSOG^|KRhz1_};^sG@pBmiBza+v}b#TiQL2eY3Y=(1Xg^f=3gX=&^pkdJiyGic(^K zg-3-+IYBsA2!XY;dZQS!jedTHcFQpIazgp&A1@9kzLovVzZ`O_@L|96riUAzW<9>0 zI`x_R@%*~ed91wWa7^>>iYNR%{Ee)V-c^C;Vw;C123{F)Ev|EP$b<=_{i0o?o()b2 zRt-8os46C7z){vvcF>qy{-@x@Q`}C-cVciWdk-V7e;Ys1XA^D_RtshFe^fQ=H+GwD z5Mw9dUU|J?wPkAtD`QOMhIgkQ@lsu{k6d-#PkZ+L%E>F^US{09muPrg^J3H+TxS2$ znN{H>yVOT;Cutw}t7Ayfj{<-WCw|uO#S>0VGmrf?`gC0S_{LEahUE>{_AQFZj7zBpoA63><*RyEprjIO`bxQSXqg?$W?a@ju?z2!DnVZQ=Nc-vAL}u9@Ku_T2nS zQjuGEU!19aW#nh6_nJP%--@{s{oMFy!1cr{svG3X96V4@%)`hBd!5PiI~M zB)3@cm(VA@I|e!XZy1gp{Jr;@N%V0)dd(ZJA1;pFK6rVT8Jk%9f|hu!M^vRC5pph4c)i-SYD9}Zy;sPC~bG%V_ZxQyK=FiSpL#t8Sahm-dE z9}OHN%kBDA9!H-TC=D-&ywjy?kXBqS^Rg#!r-`tx!1g(WEoxF}yJ~OdxDTAn%}MNQ z18!wrUv#?i;-x!NZgslwCGpJt?Jw?rNXmbhi~ljHVu$&wb3V%ECopJ2cj%;;8NKN} z`VBZWWJ`~|gL(~r9241n+`wlshdZ--VEkwJ_3kz?Y;GrMbPgLxIKmf(dSf5OE|BB> z>Y|@T-R|P+k|pgcfko4JQ>8+h3$z=)TT(R2hTF=ZoPeLNa$hIUd9~uL|BGAqcRiW= zvheDaOZxk(@63GG_tVQS(C5t`J7ot|j#5oG3EV~YzSse>s-WeeuX=rr_3QC;pk_$& zsCz@BMsyt=G)OzxZ{XM%Qs<~1wOs~=t3y+`LxoKd#Zqse)_(|D&mZnDk(~=$6ncbD z5U^x2;WYn3;UxSwq?6~gslP|txV~a|&F=iOsXbE$Cv|?l<@x-V%BQFAL|uLN!2Z+qqCgC}x0 zyxZ*_g1&RRvU&{bGD$8CsgZw|M@QZke&EnK7%J#yNapiLvdJJNo5RTBKcW3$=D_#d zZAb?;)gA4TH?P+pY5H7#tMY!)#7w`e)!9$pCcGZIg9z{M*X2UzRsDzeJ=)VVXkP4|K5GU9M7@jY)$K@dO>n2+ zRJNNlSDwu3AzmkZikr{Wv*MWh0x^OYbQ4d?T_EZd@zKw2so)?D_eJO3xL&)Tr&9 z+dsR;6Pr9EIj8)W3tmP)3h(8g*5h^;VP`?_p)s2~vtpNbULTqlc0RBw;6~^H(HG$w zPB(O&=!vk2P33n3Ed(uJFMcEml+F;0=S-LW62Y=E|FiT8Mm_Z}4vSuJvy5wv@`hej zcT0kEMx>>thkfsOpO7^3QTx-L@9i(Pe@puE@%7$x*U!rQJoQU;UBhV09LGIuvT!Ot zN+1jCCA|~TskbtGLw8g6qVRP+`**bmJ&GP5^}$~dQ4utpy+Ga~*}?Y&?-4EF@0NtI zrpZZ>PvtxON&=DvbNqXCIxC+n>n2}9tLMZs5(o{j!#1J?*S5J%QDi9jo|X80eum~l z;xqN@h{yJ4@$cR}`SqsZb-}aGsrkSE%aH>$_v@Oqt%NR#{z*#?ew4!^@ zC`K%o&KWsO!HjCDBNzEa+v#nKRQ3?G$!&wKUMd^Js%GF z);%S5cE6Zz^CM$AYr^gY9gF-4*jy3f0(yh^xgVWv6RhWSMX7>p`5u0yaD_0J`i4D` zD_~va{ewKd0^+NVPPPN~cbfHzZ%SS9(VU7ur_!9MBhn_T*LVI@Y9e%#c$v#!#zQ_aJ*M-mn(6AWUVsVjthd|6_aEP??+`-}$hMF%H zZY5`mS)l~LhdCDVS0I)i3XGMeN}~9=fFo zzjY+tdhdPr;?uux_~dUt0BK|CW4MEsEk;J$My0xPnqqQ(=WKl5!_?$tVY1=F_b>N8vpAns!3M`*VY-G?)~0!WXZ-*cwn%zS zjuC9(9bw&~@8?`#&t(vK7FH4^Q~*gp(@*?CQpf(r>o0o7AHg}rIE5}DF2tO58XQLr zf$C=s$0{@P@BW+gXYY^DAIsD3Bxijbl)UuYqf|orsw{Ed>8ysLX__kKRGqnFsU@Vt zNLfn{WUZ74#Wv}pplAMPqIppzvgA$`QO8Be5gQ{H@p}i&3Ejz^C61Q}1{?q&|A#H5o^8DBT`YB&4YN}2eHrW1i+{2TgKji+b*PK@W zzrn$Nny|Ui1A^&c;%=Y9ass*mJM=9{O(5NWiQus0xa2+;;`IPaw1PI5 zsgNxfJmf8vALI{U9tXSq%a{|G&7}Q=c+6w-BwL9QuaGN4sw;9v{gMC9`2H+qaEkb| z|F@!~H=nm;_e^E|lvaf2_A07vx!jc4`UjWivtp}RB`k{MOyGIZu&~(??P5@X?NTeA z9uXIHSFqpjT*w~2T`ct9#Xl^n;dgNN$z}>(34R337Zh+YftTeY#KOQkvJc!3!mff) zUNLZvC6b5XZ*(-dKHAi}A&vbSPFEZ)ZY+M3H{y3t*1F8K8K1t=Qa)wM(m3hroQ*ks zv*abUiqn;bhHTT>mOkxv+*Fc-{Ep2PEEJEG{quVm*%Dq6a6Tq6I@oVf%=pfIWxFEY zL|hb+#=kKdp`S!JeDnmfOM-LS0gymEc} zVSB!FJ|aWSv|hro{4s$Q;nVy(NB8OSI4~k|NVokVcZ0Q^mIr>6s)IJj3Wd4iO2J}Y z4s#dNkNX^ub}KkXc^_#-ERraQ&E(=mi$M_&0}PszX_wJhVppu!xyIDXe57TV!dn+p zVacWC&C5QKJ|ulr%8>7osU^vrv_Uy}nT9_*s~;3ODz_UmTkw{bgl+h2(iBc-o>7bj z4wXF(c^qCT9TZ85wu-NYoe4Mdmdhf7u5jN7#>;QBCv(3ElGyRWkGvG_D%mb>1}Drf z6r4wi-**whP8MeH4$_x02^2MS4ZqVFZ@(j?hG{%iqVIGEkY;jz0ihw*2z7P1D2V%QH^BSkV^BBx&P zog-qTFm>ePa4j^$`_HAae$dUCU*8rcZs9+CSBk(eF1oJ0tH~xw>Rlb(8LZF0gHx zhvCb_iYQ~5zu8RDafw%67mz7?8JrU4l`jwN7XC+~@{0+LmF|^n39J&`;9rmq6sZIc zgpe>*Z_Q>m;mst9sT31!59^3lcu*$X_lZV}ftYduO3=sYh z<;jAAk^;X5lEU8x50_63sSR2rUhcP3P6LdmVZ#3Gxy-GATsDz5i7}BmiPaCV#G`qC zSP`6`{PWxB$taaSZDYW{&#wBuwyEszN4;U{bQVEOkiiTZn95s*gQRdC3g{Tg0PG`i2Z=w zgYlX?1XYp1EXg&<9&5tubxji*imLlmOe<{!gelCwZaM6{oZNuiYXu{VmK2SwNU2dO zx@p7p{f!RmZC3!Mg|HSrK#gOTu_6We;_)K9geT9C)=HQ7jg}>d;zc$w7o_c;34ZY+ zm~m`A?>zkmy*uXu%}5=}?#|HA7qbj>6C;A@q^TL(X}{5bG=wsXz#_%rmN}bTNp_}D zr{C3>q~aG+$(L#0dPsR6YCy#mF-C;Y=?0{KtLVF73qb8pc5(p0pyl!fH=wE2 zz5K%T(wa~u3PGoE(|jK~W?P>FSIJo2VU?ryZPnjWT}gh?iNC+{r~lobKdvCYSWzS_ zpHywE9ji&vaSdbI_jfGyW)q&mQp$QJpVNasL=rD96Jz9Y(v7n3a*L^tYTOOAr(YnW7QgRiG(588p#D=q&OK@=D5Q2yWF1KpWH7T5?I$YVZa?2N1PpsXhI@_?KCCbEUZ|R8jQm~UDEcFB6{WWvWaOMd< ziW-Dt#PF4kSe$?uu0N|=Y;#jQ@C&0>AX%XKl&7AKZ*r8M->Cd)_d|fXf`r| zGLw9WnoT26Gic{n`Ah>dmvxMu$f#fdwK}y6x)`u2tk`xp+d0t2G9J_aYMG_!TQ5~k zu8yf%RJo<%e))saA7#0fO;sCe{pwfMA8xo|aJ20-Go96LkuQ;iA zfmPUDe2906|4ej>`;Sw_y-FWU%VZQ$a?rcvVki;T0n$?$N+$1t_D~q~L`rW)0&@WE z3Lv12VZLJW7;;J+H3XHDfOE%-c2soyv4j}H_0ihhb#Lm1*B-67Q65#AP{J%@mv*l> zS@p8woMM*dre=7{3iBfK6MG#-j^9kGBF~`q2IQL!yzjzTVJH4Kkyx~l*C@#6RWce` z?W{Vg8koSEz+`?LYJdl#C*c`zE9D$8jsS}`eJiaQP%{%*UXF>C#wcelrT3t=qeloY zuz{F7M`!B~^99{Q%`{DhBDrc?rMhHNF|lM^acs$$l5-_7Rj+E(6g{=6x)z<$+}ttO zm5=>F+6PrqaI8#r9rqqLo|hsF68z=&5y5~O*@uT?%jnygN^rF+q0*>9DtQW-*c}i8L9f2F&#{*@NkqXe*g(Xa^}H(UHWhgux*B_R=M@Uu%`L_A=bkbf|aK zzo_m}-J>SEGQ9#V-%*}lMXH7sVd~{7d;_zU-gd*Z)miV#a;u3u&@Gfi#vkSuPBxz- zI4*u6KFSLgUKH!NXL)bR@ySaGI?n5(&FJ8y%&N*{yVmk(g_%F z_M*ed{m5d{Rj3heCDGwhBA)ad#e&R|koFGMBd>r5YX)@)<31HbHc+xa-s2i1!scOG zyxkoa?LDj&`qJi@mdTn^^*+_@T6Qg=wyh>x;jP|LJ6}Cl{YlfPPcU3IEVD(q9IpMi zxkNqjH@cL@Wqks!zNefx?j!ao?p)qTK+vybJ*VxVkZCeBAF={cN-X3cdWd648z4LM z1X+h>L3%Wq7DzSGhB5Y1tyCdnJpB|^NGXKJLK}!vux?C;i(*}4<(bVbq!xTrSp9U> zQPn)fjM@b?*6JR0Ns3rin0B&Stvz8h8w<_1on3+X>ob8347O#|AJpF;IlbtUnA4bd&{1R)B^Ry+sh;;l20WZJ8M=nPBPBq8fVq4DC4f1fZl^qAMzanv zWvpZx1EgYBA$sI9X}d2O_>vgbf#wj?I^849G0k;VmpWBVdlj$RziNMVxMD*sQL$e= zpmAx_&(|b0KXBz7jw;L;)dYbWseu^AS zIYq96f&n*32d5%mplbLSWjXqi62!-yrXX{KZJXz;C&@>FN0LWTmoNt~n^-ToTe&vyySK7v zyfxf##&p(sW;_Z3H{yBd1h681#V;gU2wjN3Aq)W`oJBUk2jE63LN0}}s5WXR%0gNL zx)Ttv!htVw6k&pg<_>Wswhyr^u#j5jG=(?s)#%mV)Rjt(>YggCet~8S@PRvZ;Rd~7 zbNfvDaO+{$2W$>5k(7&g06R0B?qTu(Ylg}6XGb%?(wS@yeG7Vr8bk3w5K;@};EM>~ z0sHGYAqTe*pH8|)`~g<=E7B!sDs?g?fs#hQ2aK$5s3!6#@=P>`AR+9=Y2AlBYUc$r zYF=-;)10asrlYDStH(F!RIk;2>mRAUYwNVTnl!Bn!<5!~+oq0H&VJZAxCU%rfa()d zM$+iiblM$y5w)DLp2?%Uq)epgkpg5QnFa-u1hAGgg;YrFOFTlV1!kQ&XcV#@N+VAK ztPId6ppT=x0%X1(v^44<%6dRCeMD@=XX7q;;yR830{8Z|a^p1ptVU|%NUf*7bA5*D zh0>>zsV_I=YnK8K?t|8;ZSj^oN4w*y^EmbpAasmEedu5EHd+q-2lFX&D_zAXWq7Fv z>2&%UR7%b!e}pdp0^D_AcNW5K;LU#6t973eki zFDZ;T0kh0=#&z13(4J}j1{UN--F3}1vZ5|S*KaI-KKo*38Yj`6kxQy=3g!%|&q z;}30^dQE+_dP#j){q_1$>Ny%QNagzri#z zAF=MTJG0w?&GiSpo(fYVC>~@6=x2U{dXcse(+L%XHN;$^77#g6;wWew`~im13cxPi z0DlKb+b{4^=r=)%e~w*?QG5BWK8}U9RTi%40(c84&~ZVo`GS_IRcc;n3bYrS#y8Ie zNx;>{_hzGYa>q2c%d;4>fN%&Rpj1jU)d^V0K8Br1V_sl{F#v0qc7%GJd>KiB*FiH# zaYR71!e7HZ!0p3h@Y`_H@cRjuh;nEMOoVsBDac(wbbAPrE=|Z+z&M!?O(C}65`gck zp9|{PWkW5`+itXW*RO3B>QWmIYm>Ey8gDgnn)>PJEjtZI+AdkTTYa`^&PGoUKxuOj zRiuR=7Y0#@v`^I4)WOsp6f>oWav2>DzHx78EUAOIkYCtrqZqrI<{ZpkxVX-(2k&|PiX z0sO|(8V_n2joq4{=6Cw8tv6d&wMp8^c7`+5Bl1dp&#-i&395r{Apzjo@EG|dwHx&o z`6qcPc_dnc&P4BnhT%APEv$g_q$eZ>^agqiYmlF?6$(L#WIg#Y^%+G-K26>Q-j=pe zRA@B3m!v18;rHUs0ypgH4vI}@!I+=4K57}(oZnQXwQ7nR1_FX>Pwo50WlbdDKOWtB zz!YOmu(LWeuIa!x9ZJH&TKFiMMz&DZ^se*~z^GyWG{6! zJ&`_xev!VFzKuSOK8v=O8b)bGU!g~lCTI^tg<65nvnF?)TJ!3x^!lsQgWPg!P})F3s|tiRc^wdG!OVe^`nbItc# zmK(Cbr`c{UvrM!tb*yx`y!SEH*sg>uVk%@q?PLZ;Pf<|bQ=iggv|MT#WgMA7zKj-u zbF~Pn1>Rshus|QcJ;qhz+&Cg0#lOS_;cpQ>6MK>vq;evklut^9a4-UmBiSyTCfy*fP20t6pT-V(4YeFv2EYd#P8*UuF z3-J<33uVLp$YO+mipb*to9Zjt8J&rU;6i9R=?cCRc8PC=r_LGFA+YyrpJk?))*ICZ zvSFKEsz0cItw#)}4bxkz+FqK!fP7|{eTQR;E8P3l=fKRut-`kuP5?9aB)A9)gdt=n zd>`f^HmE z6OKiM@NS|J{{|<*Z1A?ah0YcB$JQH`0j6ih;MSY^buAB?7dPM5tmZjJ%>_`lW&1*vIEHgM80D9 zB;+8?fj&bDhy-^9*P9WL0Fj_o$QPt5(jPtsCxYX85IO+ttzU7G*rAxM-doO1jv)Kx z_GjjSrY1n?SPMSSux739xNb-Di58mvo?&I{XyY>Tck4g}c39J&F=fTMOE8Vc@laqwwq zJ;_YGL83x7A_ATV#$vG;v+tL8tUJnC;<#eR*oIpBwKL5Drmm(jZBvZzj9JF3#+0_j zrrV}v=E0W3)>HN<$Lo$p=Nea&$BU`MUdB-g8wf9n^-v+mJ%OWy2tjvhCmKXviza}~ z+c)F@xR1rbBOwLJMOshd60Z?95(UJ?gcpRLU{7cOUPgF~55TA4tk`3~1=)gK0vwnc z?;0=BQ|o^1PIpo|E;}CBgKbIHV5`&epXHb()Uwxn(Y(U6)N-2RzOqcqX%kY zb_$XO=xHC|6sQC`1@{D$;N@^SBnNksnXm7efKdiPu}~&- z7K#J+xp-h7Z6|yrU-H_gS{9n0n9iAonK-89rgGD2 zGs!}3r&&|2X4`8=cjrvkFL$$NgO`YD!a{f);ShmKd`etON`v~tO%MUz0?&s}z=Mz) zK*c-*uZ9ERm(V%rHDmo!fkNTU2B~_C)N4baoW)X5Gx=%)&9yh()Qk#XWL;LW^1?kgLkI09sQjBop@J; z`9_-N!}YFXK?04BwMLB+MrCC9J`Jz~c!M2zvY>oCYhxs(d$mBYcNE&91X9e`k@S z)=^~7w`JL8*ov&ztXbALo6UCH-rMoj(d-CuX1R8{A9-eYH+rLeQJBeCJJy4BVClGt zxF@(cd>A% zN!vqPpuN!^>v-t!I(QvDoUyJt7t=G{^WIb8{p+j7Sg@bKclY2k0dsRJI6q)V5@x{L z;S~5W%s@hrMz{dJ3$wtLVKsOfSr6{}E@BaQ`(Fdtv&RVg@k-nQoEp0Z`xO)Do8i6Y zndkPo%+7?4ua39&$u_NZq&2MFWBF|{n_+N8;#*9XqwRbv-j--PZy(+9)5&u0^o;k; z_6@*p!9B+_iIc%TL<4CCc=ovoUjm=w5xfsx51KSR;QnwF`~Z3fErWcd-Oynu6cPbW zB9k;6B=FmaSBc*U*YQ*E4{@KdF!qfv)JyiXxs*=WndMk(PqU?2o7#)oBil19{_U9d z2FvdD=Js{gFScj)NJpm*B5=$ecR%wy@ow@}_>#br$W`24!22w~_XBUJi--o|1z@f} zOHz|8q^ZzL=sHvZxu9F%@9)qM=n=FUdO|u)QW4z*8{r_~7G4L|^*79XOtMetJLm21 znc)s_Z*>V>I9H7GQ^$vnX&nnz};wz|KcWM+18cXxMpcPZ|a z;!vDYv^W$k+5)95Qna|cySo&3cU^XOCb{qTB;=W;?f>0;W+#(LPR>2@J96*L@EflY zMI+Wm|h=Uyz}?)Ka1q|b|Zg`SQb$y;&gbvh_53`N8E}S6}c?3 zqW_-1PN0MtFb`Ti)c_S=yQ8J#w`IE$laA@tj2cEeV=vz!-N9(ZeSb05keyZXH1nkM z9P$J_4?L%-i8_E3^L>qc+kDwV@`Wg0bYC5B3GaT^O&9fC)yENKi>k#|8*8HZD{K6l z`pwAak!>O?MYfH+j?`X9EQr_?(J}J7$h?vDBa1|qjQl&YhQE_PEnGM7zwmbso9!kVYD65VnnMPZ7*w)i-jf% zZ5^^7T=?YK>&fql=hYU)n7HXrkIV*!GQvSxBlq<{gIg>lSgEam>WLh z)BR6JKiv#_z*^-W!ybhV{dDY8-ta=%{M+Hv%V5hwcjP7cFPBRb(AyqqW6`#)Pg4`61*a@&AsmHNWG&$eYcZ+IthEc&`7V zpVcm_HR_qwici$X2*mQ&i`)|tJtAFruTSyep?+AYunS>b!^(yg{M6>t@K5W*e?xD- zL{9YQ2*fl)t%Pd3TCWahoAvqnK4Yt=3crrr6%78v+sn7Wx0p|wW(?U8G9_d#@w`gt z(U9$-JwtbdbSGw%3ds_(+qcEr*t^`5(DSviP}8*${37sLvx~XMe=G7)q!lqV;%r2R zh(ZxLBErMRMvRM?!kyPdcEo13`MdcG2EqcH07=ts*LunyXG~ ze`~*K2lTIuhDI;&x0!dNx4Q2nIFL4EWyoH>&%R*Dh>#!nyx?eGH}5DOE{k2(0@JWsS<}pD)?e0p^PrjB+HdYMPhb-Zv5HB7{N|{@WB-Xj zyTBBGkw6)G$?XE?1KR^d%{=Bb(7L#lNyXK=C`%>LS7;u+obg!yRj+U4AVQ7dyT8{O z1C8XyX{za@Ms!ahPe&t~XQ1b(Q5x%B!1ty9<{9D{>dEe@?iufyXM`AKj6V8lt-L;2 zYpO=Id>1IhIFgyan!~L6R+yEHZ~ly_-_jf7Vdp(9ybH0b^S;EP>CoBg(2b#QLo$Ru z3rQ7n89Tb-P|*=-CrD(G*u2yLcHrrN6>)(5k$b;TTGPBL?tDb0t034wcop@F6T zSb^LAmytRBvHj5_6GUc?92n6fVoXHWhd|s(K=fZ;VrVA!7kQ z*O7sf4x9K^Lul#wF?ycTNTmQYeV#6`0irkHd(6Q^U#~pH=gg=ALyG9(iL>B z5L%y#EfA7}UqGoGvMFSZuMBp5#?#2tg*;%feqTMcURgEGW`VbX?fzC+aDvEs5xXPy zMEn`C5$q`+xe%M~?XMJA01|dJ?*&SlBdoes4s36sHP;eBi5V=g>;gB1?Nbf4&3-373Fwah-ukO=ZXn&~) z>%O_%>}s|M{27Q09H&RWIq-~}w3Mj=#mt@N3NyY{8M}|8E?Kqcb-c5(V;8TjPi9T) z8}R(58EIZN6Ipl6kDy^AYn62z8|Xolq$h45>zV|*Oq=he!FQ%`u_m-!< z=O-hV(OK`UmC(LXosmXIb7){-;J!b*zqJ2BWZ%dGk&7eeMyBy=Na2S6y1#B9t{Kzp zgw-gsl6BMCY+bhsVTqYlc@<{WR@Kq=w`#J=OODhG%igZF)Z6Nbj3Y*Q>VRtAb>42i z-o7p5N{vFU`0DaGptsabe|TqjJ{XCN?0R!;n6j{v)aIXo$${_v<@|5`x&2{e%ftMm z`~w0H1G&t7W*jv3!Fp?bP#v`2R4r|}HcLg*=He5B`91mv)=KM|)!X{hYGOSx7b4qS z)*#h^xR_lVt!30|5b-bQvyJ`6IisT|hi9HAueYqHre`aNJk-eH$z+V?v(%Bq_PJW5 zdao9$t5#0+$f{^Xl4oVN4)INT>8z7h1)@p~)l4PkuHDsCl~miP#;F9_YSokP#yLkV za7cZxVyOniuou=1tiLTIdt>V0!6_^(yS3chjVDyJhFLxpM}4JktGe1PYJom_Dq{}$ zQB%(u&jim0Pk!%hPb_Z^s-ZDtK~sn`@ANu)W&IEBEuYM<$h z;7oT_7K?wc7OSU9eof$vddHC@DwpbQrR39h?*f|x!vZ_}oBU(^9)ERz9e-;7dVi8Y zwLk$-p_=uLN~9grbYrqn&QsDm%Ugu(C_Y`IA45uoB%+h|n=iZXxHrtxpI`MXVQkgY z={>as+H#denbvK_I@p?ERk5gJtwDI)L2HAx$y#ImY%Q?nScR-m>#EtqY)xF*5}3|C z?~*y*_pkL|_J8m{1UY8}js+%|%4}ohQ43TO&D4I?QyXuLdY*NjF5b1?IKJO}V|?>` zSADbj?vEqBDs<7$cr$qa_Du4eH2NBTJvH;3WwrjQhT3ge<^^Kv`M|lrv_N_MI)C7U zzXEZyRp3nEP~aBPy}UUAy~HKHkHT_(v+f|nN0z3}T2HaGtKeo#B1l2i0*xL}skI*3 z6Rj5UAS)5Sgy$GFz!>i}Z&qJTepPUmuZ=Ii@0z#2H-&cu_>;r>KZJd@#pF|D0z=$w%dM1-??4>(%*|Xl$o9^c! zqor{cl)k8?)wYncZnPR(X{_yFT_>|GztU6*%-wEYGBc2MZMVE?w7N`w+z21ouie*@ z=-H_c;_DgpyV?qEvo=_Zr`=GSRZHyqIo~SR1Dmb}iYBtMStYFUR&8v012$iRn)m>9 zz({SEmRkQq-;L)bFsd1&iNGt2RmL!*I#odyH{pd|XHhs%`jM^x#_ZZ)V zu$2sMKg(+;gRG}iWiW21HO~6p>c;=2);#N?l~NT@lkl+n+&K-n5{rk4wKh^Q)hXgg zn7PI*X|4@1|Isu$VjG;v$ZfI%<)tpyHr2zqqa@|iE6(W`AA!S9VOtc z=d13E<0H)b=6I`O)#E(Rj6boXQF=^$t(F6{tAv-0z>f+MO{bfS%)iWbzt}WwKUoUYN9dv zS-l=vYyq-V%hTTb6`hB>-c{Zqa97IvyXPBE98Vp33{~_|U|wwXs}*S$HY2G-d4XxRh$4;5Lwc228L)fIzUvz&feTc{1=uKPgHmMW`ShwYuW zhFCvY5!MSVd%cRK6$6LL>T`6%7-VRkWb|Zac>eTsz|)6%PLOqXHx6K(2a(HQ_1GF? z#kU3`_jKmYz@)$lP;Pc01v%hYbU(zpWR*nwUHL}*yIO61g}z*Ws@E`{kV`z*=jrA3 zQhK=74t_qUDJnPAE?KjzqE=V*9vgq#g2%1G7AvYQ>WwO^4b+y9ztsSX`{)t+VSS?h zg#Rtb2!7DtXcx3)T2pM|pjxU1st;CNl}7!7?S%7KVx?2*vFSxvPZsTKeqF`DlFR95 z^dm_BsUD{PsLzI@c=~2O8y-`et5Oqfy5g^+%${W8e+4cFehYM>68IE&ANavE%_bmL z1NBMu;4|n`_2fn+V*@?m?4HV=<;3|^o&ugQW0XCHmC0y>~p*JvqIn zJo!8$JrzBTjMT;@J-xmL3)n`qYyy&{w$hlX@va5LjEM9JVT{J4EM!d>>F9?7FiyS=00e z`W{{BMU3OdOMd<8gHhZgxOk0hXdV{e)w^nmsKDoG_Nm{ zO_nqsDZR8}gI#}u`o-{o>)K;}JEE7~5(`|X&(hy98<0<*sLjI~AE@1`m&&gaD9Pw& zlC67qj*YhVT5Iu-CS*aIpfZ7|T}f}Fzt%Gnj|USea~fY86X{eO(J$&1h|eRmB*?jt zIzz^m##%wnKi2f%s{_r%<}cjHb4TPQB5KE>{-g4tweO-%jsO zCiIB-@~u`}3#cE|Oz!`L?~>`M?pYb|um)-<_L6}Jx&b7~j*r(Szgw+0IW&vveRcI4XyRx19oT?w3n(Wwsg@dX?d(z)-UD{ zbU#Lzqp7uCP-6{2djYGl8m^wHf=KgM?F0ySOVjmZ+C}XQsL=r$Z`BR8Ty-WF$)R+W zO$DgS8>(8W0+#h!g@Hi3wAY~P_srGkU~37YeQYCz@vU)MpGqIFF#a`zEMzsg^Goo) zw6)WGYR)r#W_Pl%@W9vR6;SSi*^m0_71CZ$byE|057ry%)yV?iVvntjI>zt%1N|PI z%i4JC1$a%u7E>!j9=$|eCuAm%B`nP&hVq6wIRDT5q{6b#nAsU=keTZ$7 zR1r0ZZbn%(R;>j?UMMg4*@MGHs?B=m~1a zCto>-4d$f&uLK$#vC=T^6I97Ra%B^JhQ3+PY*aC_8e^$Q@{=`YHZ~Jy>*&`d@74CI ziFnun(6u%deGMz7^~$V5^k_lW^VUiMD(t||T5I2-{}}p1Eeqp`4z^|1k5WA@!%Jgn z_tkiM*5&C{q*br2Z5F@IPR&>sZ}@>4s*qMoTdCdClIlJ6@8IV&xx*$9WE%0l1M3(6 zB3_o!Zex4pl~)~MRE??Y@0xGucAPQa5|!c+pXN{leX?pXw(}~UR*mfXG%_lSW!J?w ze$xlhd!2{pbmvGWJrVN`vd-S}Cok)|@;jmljPs#PPA*FBQ4{0iwkad|@dS;tZtn!O94(+@#u@N$rzH zAFiL$GZ<}*PR45EfHBzE$o~R#oe~>^!SL+*pIRj?2A17{NVLT|M*k?C^)1Nu9&Bt6 zKQpL}{vhv7uksNAh7bu}QIWq>Rq0M^T0OXlRE_X56MPBhHw}E+Epiqwm3uVkUQo$E z|&U8n*@ttf_i0g+zL`6OJQ7ca5@DRmP=nwF~S9D*NYu{@XvBmq;MN?F96+y>47@SO4_A^A$JEO(B8hA5^^PD~O6=)8Xj>aRTaRZ| z24kNRd46U*)1i07dJYP-C8Iv2a^T-fv`^qhEi$R0*w9k`cO;h#5W(kaRjJBPsD5;2 z5`i5jsmd0kx8vYa2J|onEqSq<$=W{pzJ3(d2!`*bGURx%8`i7v=2Kpl<$yp9*v5EI{h!|=4CER{?^|{8_ zZ2V^Qq8g1u{W*+yeS^r5fjo2wmA>EFZ%w9B>WG&#qrM$W_x1o)Nk&x-JeZ569-&`z zgM8_vt?!Oh1qs!CWIKzq8`5{JV$}o%W^lv+>lVOC7wtI6GnNHcC5<7*PV$COPkeH7 zlQ^={=ti7A1_C74w<48j+Dg@gPVsBT^d~e2Q_qR+7c&3-giInWd1Vt~TQB@+DEb{u zw5dj3S(W@ep^8s8?j2I_k{_p1CCN1Uf)=-xA6xIPP1TlC|M>KT_;U;5RWH4-K2je- zuGXGcrHL9v(bXF*j9P6yc0C23Z-yslAit7M(gN<=gk0&9^#=L>g_SQLf=mP5R?_MD zn;fDbeV6`V?m23S{OGqW@of%%{R_Fu4`dOQw1U(UXE|d&wNx{xl~9GzSX=m9qOPgv z#J1krPCPBW-kDlxKC_7D^aJ`XeJz@*3g?%Inq~3i&8n5ksWcFGiM0s%Od(@DhXrH@ zgXXH!pnfB5IAgd))V=|(JOELDg@+Q@*lN|Ae%Nz7Ycl=s;&d)@S-Gv&WB}6{MFwK# z5~XvOA^5$Iyrn4~I$!@;pRO+goto>dS<&@cyNT~i$8tJj$;Gs+j4UtcTUl$tl}+&C zE}+K*EWJ6Ll+iN7^EGT|0=lY(rp}V790DcA!9!2-%L#0=8OcFB^dZ<>hWXB_=y(*5 zqxicZSS&iLCDF)8s^=|eDLWeLPn2GdedjPT@;xU_Sdml4$Y&%pUg{gE2O8-S@Yo$+ z{);GAi_CaCNYoA6Zh)0GptkLU{FZ<$NvY=(Q;Rpkr~5D)P=h{Z9;}ENW>8}j98SdN zzeD2>tovBuNb)ledB{;RhOx{y)utlfY{X|yH<72Brxu{=&?09%<=ARl@Ld9rD_(TiUt#3qlZPlWeb^bz_%GPg@) z%7@7){-XA8rRSvDnveD5Bog&S53$uwv^9g6Hh>Y2L~lnKbtbg%Ea;Omzi3`~#T{U<3oOs!NQg5U9UZ#pL`oV16m`x~=50&-H|c&-g$e z;~aJ8Nb-?rWTMNcODfqh@)_~!EWMK}>Ips(6D+96eVgE=J+zLDx*TH*AyRChLz2+%;9DhZXp;2o*H$Ty}&q*ZgjODzi-(8%}^iHzwLSTP=^4_iZ=nnL9k-o`6eCh$O z?h+YqXaTJ{zE_d%$~5xPljQsl^!sEfuc;${!XG>6&sk@%hMXsitg5zBbpL-MPUWH^ zjZW@p*l*4?bW0YvV+maV-|HW zeb^d!zK=e4eULiLIM4i7e$Nx*nQ?^~gDQrlFVO2Ft?6j(98squbZ++kuXy;@CZy1kyx}Br9D_V7fkT;)h@TOprJI!qJSEk~!mlFr zZP<4$V#^~c_8-ZXOQNGA=5^-7=bGou=jH=5i`9~dbJU8%cR0=gMdJ~X`V%#t)AxQ$ zZWapeMWZ`(8f09fEh6SmM2B<8BX*&?vzk{gPRwtP#-_maHu`Ie=}mVgUyr9Bp@ZB| zdq($WAQJxw0`z3O<;WqkFi%!8lX3^Z3b3y6Yt(sMT0TTINf4np5Zm_T_uWdopjh+q}Vg z_PY4oPBQ82Waq<}w>m_uzC#3lPUicXy_<~d5w(nkJrtnNFa~S+sO2a6Z_!`UJ!p?T ztu+qQ`8{pyVFv0uqqGr5o%6M>=q%Mm4l}{KBC~9P1c#$dZ3s;XJjEnm4jHvBBDkm6{2o~ z+SRE|l=+7_n-vTd>EH}zwasMnB3M+CczegHPE5VboZD!yrVwb{5V^0wV%IPmF#=sw zqvw^B`Gp%`(oAyJBRp22yL)!7Tn3--OU#=?H8?~cg|#Qu)jaGyn$v_asBxh_! zb@b9aOJ9B|GuX?`V`hl$;XjjgJf*AIPK{6-vF=w2;Gzam%>F#43g5~~o^{B34mh+2 z4}8b`Vhys1jodp28d*$_{sr|>32LHBMkyl!9ly))(wRA$J4Cm}il^|M94JOHm57{O@bz87qqL6uY) z@47*q*aUxGPt1Bi4Sa|Av4*HI7@W)jHrxThS7R~L$@gZ0U0XnjSM=_Z=|!lR%2TP- zpgM@FKSi3oh*?+ALk@MG=$4Ds2n(6@FKK2qlbQ+5-28S}AG*13%p$ydf>__3%=PHt*7#3Sm~OH1o)#iM^Z2gKm=fya%6N z^SDQrc9)vt8NTlYQ!B&aLaZ_^T&%~ts!=sx(32Qdjo!@YE(5nW&@CKJr>-#fKS-pl zfW(gA<1N8?R?HH^wo_58q35{;3s^^%x(I}tOmCtYI*hI^Q!)3nN};2(%=?Tm+nX)S zhRk{OGv~l>O!T!7RO~>PHVrcr=gFWukXfA3-{~>Q2jd%&%r6|JPVA2s(&}Mg&}O7J z8(mGKx3eBRda8w>pYl{pJ;9)!R0S=_Ny{%Zd+S@=t}1|3o-*# zfgb5B{3|Kh*>Yr43>4ap&16TuUFh~sBWM4G=WodvYT_mN$(f`xUlO$J%zGP%E-~oI zjlho2k%`12mo3B!g~W7OALtvvflAaR+tG3{I_vYW<5EQ0=hPruZR}Y^SM4M#L|)hx zU>t1P2VyHU^o*Kd8I|u8dJO}}dnF?pOQmoPNfo8KT?3a*vDd$~j9B(`^zuyCjF@(8 z4d_?t(a)pPT!l{KBT!&Dv11@zjc=)B`q8PI$&pQTPtGwL^bGGf%arP2xr_YW>ONwy()|a~tL9jFmSE8lhyo3-x+PYdx}kO2(H_ zon*|}tcT_%^GBjaN4ujvj@WUExybZbjUduVwJv4)w)m9x~tDkyVH1$c(ZXU*TJQ={65HI%Dg3u@~;%OJUL3RXXy;aO$1o#E`MX|J+t8)}EbWmSr__o2!U*7vM2Fvrg-%5US!4 z;i@q`QVsgE=@NzOX{dDy7)6bAtf_lK#WS6ln_Ul~qq+%fm;_b~0ud$=lXsJQOLrnI z9hWl9l@w&M}gFw-Sp7&YkG4iL)pNi^60Azd~Cx9 z6QipEWCCZ@|niY91q(VrkJ;t*8l6RyAj~-HG9Y#Ovz@#p!Hk_q)BOKe8@F+*8s3-GpTc~~0AkkIC`677tY4X9wIUa)#`-zeL7@=;$IVpqn9lQgglS}p<;mx7fyPzITKkgS3?UzC zLiU=N9^MHe?qs?NEyy7oVj2CYcvo=O5OUi#MBB~!HR{PoX6!$puS3j*H6l0KOQo2W zPJL&x{=L|6aZ6_wc_TAF^N9JYK*DE4uaZ_5WOc~;$cn|PWZZ&7i|HEgWZOZ_aD>M# za-=s@uxaQww$sM*3-`CUcTqGln(pUu?*5F~PrlNKy*=b~L&?-~GnaaaT4fxSOeJ(3 zO^Z-3$-7Qd?e8GlT1lUA5_fAuwv>_D>^iy|PIo&u)$23j>L$MNdp&vfRc7I`qs6)4 zRVga)t8|D5qKVkVt$|dd+rYikJho#Y1M%rXpwNA6c?L+)hj`JM|HD9st<3Z(5VR87 z97G-aJE$-mS(m1(d>9W*s~xBN5~@~_)d#rycUX36Ga73<kqGP3{JB`GmTaqu;!i$@ONu$V`PH7*AboJ<^t;D`x+H*&# z=s$XlXXFOciTvr9S^1f36ggyXV!}tdpR>sNTQDbHf;AC^$^L6#y&dSgkFwWwt;CA= zkwc!Nqjity|B1DYsqC&^U(n*9`p8O$=FmLIyl{ScX(Q>P?IX6{rlP+~j^_u;uKHEubyXj0+)K@OdT&g(qlLt5(=vKqrG@`lK$ z3M-pNu@3f$nV)L*JafXMu$=<<*EKCZRbYQ?b%9-*{)DHupaP3U6~6<^{~oVy%4}hM zG}a5LZo`*6bRoXR6PDnWr_k|Hx;9fel9QZZ2Isy8Z}X{ZSWOY@t+|a#qpR7H+^mhA znH?pcE{T7wCQr<-deB2XO%zK)9aafkt*3nj8c83g9F=+j@|!Z$d@UIJBr@EK^!UYQ9&7iWZOyqfujg6g_MXY%O_oz$nEGx5Y3CMvykOSSO z2XTm8X)*QNI68iv>3Y@zjmq(;503vpFM?luWHmr*yy2coPoA~}Yf4Jy)01evjL5nb z3;&D#Vc2#_Iu7sY_HVW8|H0ruKWuv}ng2R^qW8&uedI1_*pkq7yNeVjGS-;dZY(LN{D~4Ldg7mg;!l zkKpkM_)J2b(;U4_Adg*&)K(#}S)loM;9yxkUt^Im?50K=gJm~G>(VDoiClC#g^!8Y z$H+&2rytQ2zl%$+b`$aKYrHNwJ%>AB(Lq-FUuL#69-8}(2>g+Cb`!v|v|#cI?S+<^ zyr>WL*&l4Pxo=NmNdYAGMBB?&du*n&+JKi~AmT`*Vh~?DV~vZb z{C48U+pyt3$?t!_b6cRRnrO5Zd)@Hn1@LrVi%X^52p{|fJlaJ@azNk93XPxb8YLz3 z>8r7<5?J;=>Y%20yN|i7{rJ*MV#RoRo8#!3PXeFj!Q%>WNRa3R+cl8rIeoSm*c@d)D zJTc@KyW(7k?56X&C;pp@7<&$!YX#=r#cIC6w!?_2>#*ms=1=q>*3d1A0eX(3mz^B% z+)TA!7rH0O9&!Wo$V)0AvsS#GfkdCsdn*D)_eDYr zk=Qb##|UO43eqLnjt69*%QuGp!%Hl?JvFt$st>U?a5I*3mDT>~n2YRB$Nh$tkUY_m z$s@Aaq*^-q$A!S3B6f9FkhzTFAW;o$rL$eaD5yYIvy+^!0TGX z|Cng&5S$MNi}KS?f6ol#R_tdyzSf>OkMdw-I(t6h33<{%tapx$Mcr8Y-x#lN0V)lo zXS#-YE|b;29YLZ?^sA~N`K@$?6EO06*l90j$$PNsr9Rq|O#cPCT!&W1f|DM ztTc^9by6DtpTc_<2rvj_RIH911Ula#gUp47>fljz;i3@Sc!-yW@x94ts2!G7m#r}s zM_-Qo#XOR%>rDrTRk)%)aw`R|7JZCAK-e^N{H7o$i?Rqu{f;%A z$FZa(%$iREv4^3p;qWm5^qCFUi$U5&$Y%=2+hcoaSv_`$EWRa~>I_y8}YmQcwk(t zB_AAAKxb8uTTvpJtTlK^-n0XR9!adP1r{YEzCI;4IzW{*hu&v@>aB+8A{QPNqTbU* zI)>#erB<6nH2IOZG93wRK!-1^xXgC9rxH2L3fmm$e+-qxMRMp^+&d?C&x@u~5n;pO z=p;P{@u?g-N|DuRxlI$=!?BKL!VX1 zimKbSdvESP4-2})ePihvv9N0RRuf`w4dP8keDXF~(-^MG!Y3y85M^7lsw;bR=V&i5Bl*Po+3-F_s;NIoNK*&8=jrFR>S$(JFNJ zgxCAYOn$X1v-ad8jo6xjNxiX|8T4@W({+2v`LU^bliA#ckqvK0W9>o62(_OmTZjDm z5v!i2QpY!c@zIYk=~jIrHwz}xD+J}b3Z4dywe!d_2@#+fB-3M&7CB^|bV znPJ2@gWTN&6<-kP&LWeIXmb`i9gT$#1Q!PIJdEf!9-E(vY$juC!;sxO z!bS>%lDCo6Eczgwi1ZDpIliVh^CR|t0^TxUNuyXfe4m-OQegd1ux%w;+rvF~;ZrN@ zPTo+gqdi)xkL2ni#nxnB!|gb~m+tK=;(R=8D_yE>? zpS!(cdxl3{BC^ZO$nR)sICqwus2iyL9oF?Dk>WR`vI<>phPRFOdE>#Uub9P;rd`L& zMuWz=l);MiJ!G>}sA|6_FB=Xr&Ihlru*N3?*3+B*@E+zAz05V2=IXDxM{n-YkN;he zPAek2bRKFk?z(7As_7B|GvT)}oWx`{Q#NR|BTi2Rw^w2} z8_6ivaL>7DaRQq99tn0L*0w`>U6JGv>}v+PS_>W=A)`AF-zU)aCa#ytL)LuA%Ee9C zL`Qr-J#)m*=)fF-gPmaaVcT-TiD22un_7d@GpStcPuZbZq!E%}B-(%zFNI0#FeAB|oYbYPUe&&&&aHA{UAfr`5?RY5p=Nm8AjYs%?FG%=*+^Lq>3@f{{zl~7 z0^-T|Ph!_c+24#rmSWvMW8b5&X|cFoXskP1S03G%ooNp`GzL}6&{I!L^nZfR*5GS> zK*?fsLqpVCyy`M}+6jFBZ&oygS+S_}D}ai_8NqHe=4WM3KJd9d8tKLz`f{i5*<|*o zBe_Uxa>rIYcjBJ?kk%AXYbko&0k@}ZZJmbWJ?MBYS&Q_o3iC;W_tX*_S#3RtJ5@yn z8NuVY*mF#fIX(!Kg&e;OHMdA*AocIGvMJBBG^?d`xG+U!SVT^&`3tn8a~RP?TpAp);GMyp0BgMXFpiYx9os? z6Y!!w_By{h_S|u9ye}0pOU))8SAsri3+CF#kW1}kG_P2dmW60kAGyf<)X(Ij3$UCe zM2mUoX*PEr&+%b+pls4B8bGWc0v?R|yp7=acSy1^5wQ??pNCZkn*eJ+j$P#5=}zguN|xteFUZ?T~9hBrGdZFJqa~B_0nq-{JLb$ggGPViW#1S6|~> z?Z_xQvu>;>lIx3Zhk~LLu(#ik=xVym$8E1OiEo+6Woy}0$4GMc`P^?KJnX@)_wj!l z9IYUSlqf6R{Vtr@h$^N$SIL}d2D0J=tSHm9Pgub{@~xwcd^t8UfxC7^FLm&?V%Sg) z;zf2mEkF9Ig4K7jeQXhDpJgqG$-175NV*a-X^Bh(yZbU~iR!^^;QzEVmj2yjkZU^I zG@|`D@Two&$@j!a4=WxkG49g6T8B-HC%^89Y-)kbWy!@#@F>SBoqEWsBXMKsXN+D+ zA7n4roF~fOp_}%CJS2c!$HX$7I9my9X@$N9V?VMALu9oRIqm1Mix{__=rtS82Ej=) z+j7$370R~h^VC5*@wAoL#$4=ZCOABW++$1f1$JK=wt-k3_vOa@Th^d4Ua@xKOwcL;MHu-nnzu|0M1u1E3+9y+KXLZ!U|sz zXG4)wPCFB5iN=OtzmjJx2dkY-V;$bM09j22h5BG|4dK2xSxiba<|U(kgH_%J|Id?k z9Yc4!(ZD9`N!EeSLk>T~$#}BFK}44C(VT33*dGE$PK7(EKlXF=LwX?wQp$qwS0)2# ziAH)8B}O8dal~1%iMQlJWE3l0vY>U|b zVq3z)IkE&T&4t%LkjpH%nt}EIh7=d@{zfD#X!MkTAT2G;!+>El>XSwbQ=Q}BBMXqen*32v83MQZVix8 zLA!$%-JWH9z^d8vbTy7*n|s;z@qB=7Kig66BKv!g*LEysExi0iyqNb{Z=$n5(b*ER z+3lPw>)zfVr9_Oj2!7m<*w76N8iP$w1s{Jwd$aKUG1&Sb^x1)KpnSTb5Hgi2I)pm< zE!g-Jja@-w=iuu!dC?JgIK;LePWIX}qC1hoTJE|QsVrw(0#}P|S^b5q)}q}#Tqo8o z99cxQRCr8bqDdnxsVmwM9GVJ8)5!)TPa6kM{Xa)Dnf1uQ6|vA)1i7eia~5eFg@=8} zUSxET{bTmAle~A5y9u8M;dD2W63#@cV)-JWWpE|7bO62IBrXaXCF35Y$dRS0?u*4t zhL4#x9?pQTpWtU8(rbgySFr6TG5)EL&m&Mstoa;wJc>mhw?}%GBj>@oD`?>||K&4- zV(*7+?QI69R$y0)YlgQ|x7GjN~sSdnnl9|?^^R}xWww&Uq2`00Vo zHzrdlfmfy@&U=WeFQ`CoLgfs6h(wO@e3I=9{0Jx4@t$jJBE?G_JH4JXaIgT29Bn~(^Ta1BN5|!FseB z?t|bju!%Qt{tRmq{a%It6Wn(f2(cPYgrE8L`roBUX%|{O3yR3P%mnzF*mVQE={pc| z43e3QjHbiU7= zID3lBuA;f4XlE;yvjeB7q2&Q{3J4Z%6wxq>^dvnU){#Xp4ib4G&K%h#Ontj zGiS!X5?tpXR}`Cm$CdZF|2f+>130L88^@<};7{6R`1Yg|# znJ2+b!O%6fWuM089x)OR(I6Z8r~*f=iTs1mlk}&@@P8Dqq@&lAJf$oWN{bih^sHWd z#%Phw5u`0TJBgiKM-oqLej-2>3#`+KdltOCeFFj=@c3_s_gb^~4Vqwe5Ibk2-} z>=UrXVc!QwZyCvLZ0|JJ2}(-tyTrEcWyotQl0JhKO136lk=$5q9dy$c-04Rg8G?OF zHQk+Ts5u%d4_-;E4`oi_IUHTGEm|U%Y{zT~-9SE1k+~mT#X!4>=#eEw{t5BTXygbV zx!WVq>l8Yc?-*QbOG&sA2{{~H;~Yf|DQHv@Uy^!6IFjl^Ry_7WX3`U`f=%Z_lF_k> zH=iXhv1<>T#MUE7>>}fP3cXL%5yDS0veJ}zW=i(rbHvL`@(Vb+&RC8h*>%Whxh z(ouAF6Cv($6ivT`ulZpQCj6X$6i~#1FH>PYfdc3w+@UqdWvB!jEtypK!Xs=$>FHipnN7 zQb`LhS@4&vaFrTqCBTwQ`@3zfGM=Spr2E}8HQZk;MjBGCwItQ)$$oqqx|0O~ync*cTn$E#k zGNQS}j3QjkU^eVTVn}oFs59O!9idM6d~-Bb z2`|gcC}Z1pa1VPqfrNI$&o(eE8cgRk62 zLQ=O%4!sqAwzGEx4n_YjkasAao`i^$6HW>f1&ZLIx#2E7GKzy0zTu9y;7BNJKx->( z8(M|DoS1eOIY%-pk`j#OQ=_l^@FSfT`Lw$9DI_0{VUI~HTe?eP6 zNI>%b%2;+o@}qA+M5)IbgHPqjhNMc22emiEtLy0cFkEZ{;kLlj9=JOJ!raC_B0hJ; zG9#fPTvwW?QyQKM^IF!~NXGdI8e)fH*_-V=awU8T7VX7buHZHAvFq4yl$)_sgd?ZI zZuyy?YS?sMY$%D1xGxypCGIbtyA^t(HOa9~VXKdjUx2YC!q;=5DfzaT^7u-5s7jsAHy~oKpYXDHwvXjUK4tNT%4`+cD~cUvhKf{CACTpB@JKSG z&9-f?;lJc+g0Vs;jQNm+$S9Z1k<2vKM{^C3mQ>J1;Z!0=2>GJa&lj;7v4G9+vkAU- zF)}AJeuadhA)yRVDq{0f6$;hC`U>z@0MC(edEn@=ZAYSciEQhU(?(m?f@rtw7%d!$ zuNFljRfu18*lHu63dlPj`cGlU`giz}WYf|e+6p%t;A)$VvS+Z1r$|sFlmdws;<^g( zQVS}zc&>!z3xG+fY>vW^^o`Hny3MAu5lTB5r&!}%FkeQNkdfttlB^4vV_ux&zT`=E1?cf?~uur`rUarmi*jO(%0AyQVhYu{{CccQg0 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 diff --git a/selfdrive/assets/sounds/warning_immediate.wav b/selfdrive/assets/sounds/warning_immediate.wav index b1815a95867b9e4f5e2f5ec41ad29f80ffae228f..fcbfed79ed87d632ebc5c06c6133af59657aadd6 100644 GIT binary patch literal 130 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 diff --git a/selfdrive/assets/sounds/warning_soft.wav b/selfdrive/assets/sounds/warning_soft.wav index 261c7e1376c6729ed56988f67d2aeaea26a6153d..7db30303d60f5d9fd5913f2abdd3edd1446af050 100644 GIT binary patch literal 131 zcmWN?K@!3s3;@78uiyg~LefzC8wgaGQR$f4gRi%j-LrnQkGE~dvFqyGqaU~Drqgx% z=1IoO>0ZU1U`8)iTM9F;94jLiA4Bw5IYU597ewL{qo=J97N(Ljf-vU@5@cDTPl$H` Mqy1Mc%ov%>AJkMPjQ{`u literal 153764 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 z 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%0DYqGDk-Dn1QkOr`0` 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�@zBZ6Y3UbX!?KPrXt7N-;>bTe4I#Te3?! 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

)E` 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 diff --git a/selfdrive/debug/profiling/clpeak/clpeak3 b/selfdrive/debug/profiling/clpeak/clpeak3 deleted file mode 100755 index 4b3c4c130e7a78eedfdce0f493ec4fb294713449..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163696 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 P0Ff_Zv>!`=b%Xc=`EVyq literal 223152 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 diff --git a/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 b/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 index c0c8b6665078a9d8573699b0a346e8b6b7fb24e8..cf5e550faa95f7559ad972b38f37acbbfe5ff53e 100644 GIT binary patch literal 131 zcmWN?OA^8$3;@u5Pr(H&5I!ZnO~6E$QRxWn!qe;9yo*1h^|z{X9%I+~(dO+bWBp&x zyq5mb4t~lMU>|xFTYra3YKb7&k#GVhxB*l8g(R_0bUefFYyH O?3dBLtRO-Hzx)6wRwtzZ literal 262824 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

^0yr1DX23HIxY&Rv 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

=J760L70u5p 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 NK^3$ASOy!t_yfQ7Cqn=L literal 8297 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 From ee3544d283523044cc969065af1c4c16acba7bf0 Mon Sep 17 00:00:00 2001 From: pencilpusher <83676301+jakethesnake420@users.noreply.github.com> Date: Tue, 26 Dec 2023 18:03:59 -0600 Subject: [PATCH 339/724] replay ui: fix typo in variable name (#30851) fix typo in variable name --- tools/replay/ui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 05e8bdec1f..e47aa0416e 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -118,10 +118,10 @@ def ui_thread(addr): imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8).reshape((len(yuv_img_raw.data) // vipc_client.stride, vipc_client.stride)) num_px = vipc_client.width * vipc_client.height - bgr = cv2.cvtColor(imgff[:vipc_client.height * 3 // 2, :vipc_client.width], cv2.COLOR_YUV2RGB_NV12) + rgb = cv2.cvtColor(imgff[:vipc_client.height * 3 // 2, :vipc_client.width], cv2.COLOR_YUV2RGB_NV12) zoom_matrix = _BB_TO_FULL_FRAME[num_px] - cv2.warpAffine(bgr, zoom_matrix[:2], (img.shape[1], img.shape[0]), dst=img, flags=cv2.WARP_INVERSE_MAP) + cv2.warpAffine(rgb, zoom_matrix[:2], (img.shape[1], img.shape[0]), dst=img, flags=cv2.WARP_INVERSE_MAP) intrinsic_matrix = _INTRINSICS[num_px] From a1f53c70e6d955898a3ddb95b74d1d6b3f40da3e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Wed, 27 Dec 2023 08:05:45 +0800 Subject: [PATCH 340/724] uploader.py/listdir_by_creation: list directories only (#30806) list only directories Co-authored-by: Comma Device --- system/loggerd/uploader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 914c9e8079..8f27d4763d 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -50,7 +50,7 @@ def get_directory_sort(d: str) -> List[str]: def listdir_by_creation(d: str) -> List[str]: try: - paths = os.listdir(d) + paths = [f for f in os.listdir(d) if os.path.isdir(os.path.join(d, f))] paths = sorted(paths, key=get_directory_sort) return paths except OSError: From 4df85062c52fbbbdc4e461e317b6cb01a6d76ec1 Mon Sep 17 00:00:00 2001 From: royjr Date: Tue, 26 Dec 2023 22:08:57 -0500 Subject: [PATCH 341/724] setup: Enhance URL Handling to Support Partial Inputs (#30681) * easy urls * better --- selfdrive/ui/qt/setup/setup.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 3b3666b19a..67f1c136d2 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -34,6 +34,11 @@ bool is_elf(char *fname) { } void Setup::download(QString url) { + // autocomplete incomplete urls + if (QRegularExpression("^([^/.]+)/([^/]+)$").match(url).hasMatch()) { + url.prepend("https://installer.comma.ai/"); + } + CURL *curl = curl_easy_init(); if (!curl) { emit finished(url, tr("Something went wrong. Reboot the device.")); From 6810c5b644fe898d87b5bddce67f8ab7dfc100c1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 26 Dec 2023 19:22:34 -0800 Subject: [PATCH 342/724] script for checking out a pr --- scripts/checkout-pr.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100755 scripts/checkout-pr.sh diff --git a/scripts/checkout-pr.sh b/scripts/checkout-pr.sh new file mode 100755 index 0000000000..43f658beff --- /dev/null +++ b/scripts/checkout-pr.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +if [ $# -eq 0 ]; then + echo "usage: $0 " + exit 1 +fi + +BASE="https://github.com/commaai/openpilot/pull/" +PR_NUM="$(echo $1 | grep -o -E '[0-9]+')" +BRANCH=tmp-pr${PR_NUM} + +git branch -D -f $BRANCH || true +git fetch -u -f origin pull/$PR_NUM/head:$BRANCH +git switch $BRANCH +git reset --hard FETCH_HEAD From 059cf0f3a3ac3f1e2365ebbb00d9d924d8fbaed9 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 27 Dec 2023 20:08:36 +0000 Subject: [PATCH 343/724] Torque: Subaru: Ignore paramsd valid checks --- selfdrive/locationd/paramsd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index d5c7015dc1..74a063dde8 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -233,7 +233,8 @@ def main(): 0.2 <= liveParameters.stiffnessFactor <= 5.0, min_sr <= liveParameters.steerRatio <= max_sr, )) - if CP.carName == "chrysler" and CP.spFlags & ChryslerFlagsSP.SP_RAM_HD_PARAMSD_IGNORE: + if (CP.carName == "chrysler" and CP.spFlags & ChryslerFlagsSP.SP_RAM_HD_PARAMSD_IGNORE) or \ + (CP.carName == "subaru" and CP.lateralTuning.which() == 'torque'): liveParameters.valid = True liveParameters.steerRatioStd = float(P[States.STEER_RATIO].item()) liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS].item()) From d33c5bccc68327d8ae740b4866446447e78c7f60 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 28 Dec 2023 10:54:09 -0800 Subject: [PATCH 344/724] CI: update labeler to 5.0.0 release (#30862) * update labeler * update labeler * update labeler * Update labeler (#63) * update labeler * update labeler * update labeler * fix that * Update labeler (#65) * update labeler * update labeler * update labeler * fix that * and that * Update labeler (#66) * update labeler * update labeler * update labeler * fix that * and that * fix that --- .github/labeler.yaml | 120 ++++++++++++++------------ .github/workflows/auto_pr_review.yaml | 2 +- 2 files changed, 66 insertions(+), 56 deletions(-) diff --git a/.github/labeler.yaml b/.github/labeler.yaml index cf7a70a3f5..47d0d480a9 100644 --- a/.github/labeler.yaml +++ b/.github/labeler.yaml @@ -1,69 +1,79 @@ CI / testing: - - all: - - changed-files: ['.github/**', '**/test_*', 'Jenkinsfile'] + - changed-files: + - any-glob-to-all-files: "{.github/**,**/test_*,Jenkinsfile}" -car: - - all: - - changed-files: ['selfdrive/car/**'] +car: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/**' body: - - all: - - changed-files: ['selfdrive/car/body/*'] + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/body/*' + chrysler: - - all: - - changed-files: ['selfdrive/car/chrysler/*'] -ford: - - all: - - changed-files: ['selfdrive/car/ford/*'] -gm: - - all: - - changed-files: ['selfdrive/car/gm/*'] -honda: - - all: - - changed-files: ['selfdrive/car/honda/*'] -hyundai: - - all: - - changed-files: ['selfdrive/car/hyundai/*'] -mazda: - - all: - - changed-files: ['selfdrive/car/mazda/*'] -nissan: - - all: - - changed-files: ['selfdrive/car/nissan/*'] -subaru: - - all: - - changed-files: ['selfdrive/car/subaru/*'] -tesla: - - all: - - changed-files: ['selfdrive/car/tesla/*'] -toyota: - - all: - - changed-files: ['selfdrive/car/toyota/*'] -volkswagen: - - all: - - changed-files: ['selfdrive/car/volkswagen/*'] + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/chrysler/*' + +ford: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/ford/*' + +gm: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/gm/*' + +honda: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/honda/*' + +hyundai: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/hyundai/*' + +mazda: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/mazda/*' + +nissan: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/nissan/*' + +subaru: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/subaru/*' + +tesla: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/telsa/*' + +toyota: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/toyota/*' + +volkswagen: + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/volkswagen/*' fingerprint: - - all: - - changed-files: ['selfdrive/car/*/fingerprints.py'] + - changed-files: + - any-glob-to-all-files: 'selfdrive/car/*/fingerprints.py' simulation: - - all: - - changed-files: ['tools/sim/**'] + - changed-files: + - any-glob-to-all-files: 'tools/sim/**' + ui: - - all: - - changed-files: ['selfdrive/ui/**'] -tools: - - all: - - changed-files: ['tools/**'] + - changed-files: + - any-glob-to-all-files: 'selfdrive/ui/**' + +tools: + - changed-files: + - any-glob-to-all-files: 'tools/**' multilanguage: - - all: - - changed-files: ['selfdrive/ui/translations/**'] + - changed-files: + - any-glob-to-all-files: 'selfdrive/ui/translations/**' research: - - all: - - changed-files: [ - 'selfdrive/modeld/models/**', - 'selfdrive/test/process_replay/model_replay_ref_commit', - ] + - changed-files: + - any-glob-to-all-files: "{selfdrive/modeld/models/**,selfdrive/test/process_replay/model_replay_ref_commit}" diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index fd95592e91..0b748063bc 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 with: submodules: false - - uses: actions/labeler@v5.0.0-alpha.1 + - uses: actions/labeler@v5.0.0 with: dot: true configuration-path: .github/labeler.yaml From 9520153b6a3e2e762dee29cdb0ca2ed9f867e2cb Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 29 Dec 2023 19:39:17 -0500 Subject: [PATCH 345/724] bump submodules (#30866) --- opendbc | 2 +- panda | 2 +- selfdrive/car/subaru/subarucan.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/opendbc b/opendbc index 0cff7a8aa2..40d9c723d4 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 0cff7a8aa2df3be711cab4570bb422cd8661cb8b +Subproject commit 40d9c723d48496229fecc436046538a53af19c11 diff --git a/panda b/panda index 114b85a649..20722a5946 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 114b85a649341d55d6beb36d7414eda5e6d324a2 +Subproject commit 20722a59467fc7d697bfe1edd233a38bafbc89d2 diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index b851bcc088..3f5af825f3 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -204,7 +204,6 @@ def create_es_status(packer, frame, es_status_msg, long_enabled, long_active, cr "Signal1", "Cruise_Fault", "Cruise_RPM", - "Signal2", "Cruise_Activated", "Brake_Lights", "Cruise_Hold", From a58baf48fe9e951f5954fadbc2c4b2b118633493 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 29 Dec 2023 23:47:55 -0500 Subject: [PATCH 346/724] Subaru: longitudinal cleanup and dash status fixes (#30868) --- selfdrive/car/subaru/subarucan.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index 3f5af825f3..290737c3e6 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -48,8 +48,7 @@ def create_es_distance(packer, frame, es_distance_msg, bus, pcm_cancel_cmd, long values["Cruise_Soft_Disable"] = 0 values["Cruise_Fault"] = 0 - if brake_cmd: - values["Cruise_Brake_Active"] = 1 + values["Cruise_Brake_Active"] = brake_cmd if pcm_cancel_cmd: values["Cruise_Cancel"] = 1 @@ -153,14 +152,14 @@ def create_es_dashstatus(packer, frame, dashstatus_msg, enabled, long_enabled, l values["COUNTER"] = frame % 0x10 - if enabled and long_active: + if long_enabled: values["Cruise_State"] = 0 - values["Cruise_Activated"] = 1 + values["Cruise_Activated"] = enabled values["Cruise_Disengaged"] = 0 values["Car_Follow"] = int(lead_visible) - if long_enabled: values["PCB_Off"] = 1 # AEB is not presevered, so show the PCB_Off on dash + values["LDW_Off"] = 0 values["Cruise_Fault"] = 0 # Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts @@ -186,15 +185,12 @@ def create_es_brake(packer, frame, es_brake_msg, long_enabled, long_active, brak if long_enabled: values["Cruise_Brake_Fault"] = 0 + values["Cruise_Activated"] = long_active - if long_active: - values["Cruise_Activated"] = 1 + values["Brake_Pressure"] = brake_value - values["Brake_Pressure"] = brake_value - - if brake_value > 0: - values["Cruise_Brake_Active"] = 1 - values["Cruise_Brake_Lights"] = 1 if brake_value >= 70 else 0 + values["Cruise_Brake_Active"] = brake_value > 0 + values["Cruise_Brake_Lights"] = brake_value >= 70 return packer.make_can_msg("ES_Brake", CanBus.main, values) @@ -216,8 +212,7 @@ def create_es_status(packer, frame, es_status_msg, long_enabled, long_active, cr values["Cruise_RPM"] = cruise_rpm values["Cruise_Fault"] = 0 - if long_active: - values["Cruise_Activated"] = 1 + values["Cruise_Activated"] = long_active return packer.make_can_msg("ES_Status", CanBus.main, values) From fd88990006d8d5d07f4cc79af64ae51999d1159c Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 31 Dec 2023 13:58:15 -0500 Subject: [PATCH 347/724] multilang: bad language translation check (#30783) * compare bad against list * use web * Update test_translations.py * uncomment * override * wrap * AssertionError * better * detent * selfish * check numerusforms * already checked * use name * not again * combined * sets * assume available * fix assume * check regardless of other tests * assert not print * raise for status * better * done * useless * happy ruff * better set * quiet * clean * obvious * clearer Co-authored-by: Adeeb Shihadeh * IGNORED_WORDS * assert match * direct assert * show bad word * fix numerous empty string checks * fix IGNORED_WORDS --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/ui/tests/test_translations.py | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 622d198f7d..c3c1760dbd 100755 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -5,6 +5,8 @@ import re import shutil import unittest import xml.etree.ElementTree as ET +import string +import requests from openpilot.selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations @@ -121,6 +123,33 @@ class TestTranslations(unittest.TestCase): matches = re.findall(r'@(\w+);', cur_translations) self.assertEqual(len(matches), 0, f"The string(s) {matches} were found with '@' instead of '&'") + def test_bad_language(self): + IGNORED_WORDS = {'pédale'} + + for name, file in self.translation_files.items(): + match = re.search(r'_([a-zA-Z]{2,3})', file) + assert match, f"{name} - could not parse language" + + response = requests.get(f"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/{match.group(1)}") + response.raise_for_status() + + banned_words = {line.strip() for line in response.text.splitlines()} + + for context in ET.parse(os.path.join(TRANSLATIONS_DIR, f"{file}.ts")).getroot(): + for message in context.iterfind("message"): + translation = message.find("translation") + if translation.get("type") == "unfinished": + continue + + translation_text = " ".join([t.text for t in translation.findall("numerusform")]) if message.get("numerus") == "yes" else translation.text + + if not translation_text: + continue + + words = set(translation_text.translate(str.maketrans('', '', string.punctuation + '%n')).lower().split()) + bad_words_found = words & (banned_words - IGNORED_WORDS) + assert not bad_words_found, f"Bad language found in {name}: '{translation_text}'. Bad word(s): {', '.join(bad_words_found)}" + if __name__ == "__main__": unittest.main() From 331ea604220ba47460aa72e28f4424c8609f1022 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sun, 31 Dec 2023 14:54:37 -0500 Subject: [PATCH 348/724] Add link to contributing channel (#30876) --- docs/BOUNTIES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BOUNTIES.md b/docs/BOUNTIES.md index 9d6f8bc97b..bfefd52898 100644 --- a/docs/BOUNTIES.md +++ b/docs/BOUNTIES.md @@ -10,7 +10,7 @@ Get paid to improve openpilot! * open a ticket at [comma.ai/support](https://comma.ai/support/shop-order) with links to your PRs to claim * get an extra 20% if you redeem your bounty in [comma shop](https://comma.ai/shop) credit -New bounties can be proposed in the **#contributing** channel in Discord. +New bounties can be proposed in the [**#contributing**](https://discord.com/channels/469524606043160576/1183173332531687454) channel in Discord. ## Issue bounties From eb805e889ef2f9c6e1345dc164533b44494576d0 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Sun, 31 Dec 2023 22:02:05 +0100 Subject: [PATCH 349/724] [Cabana] Fix segfault in sparkline (#30870) --- tools/cabana/chart/sparkline.cc | 37 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/tools/cabana/chart/sparkline.cc b/tools/cabana/chart/sparkline.cc index 42608c030f..34bc76b7c4 100644 --- a/tools/cabana/chart/sparkline.cc +++ b/tools/cabana/chart/sparkline.cc @@ -13,23 +13,30 @@ void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, doubl auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), first_ts, CompareCanEvent()); auto last = std::upper_bound(first, msgs.cend(), ts, CompareCanEvent()); - if (first != last && !size.isEmpty()) { - points.clear(); - double value = 0; - for (auto it = first; it != last; ++it) { - if (sig->getValue((*it)->dat, (*it)->size, &value)) { - points.emplace_back(((*it)->mono_time - (*first)->mono_time) / 1e9, value); - } - } - const auto [min, max] = std::minmax_element(points.begin(), points.end(), - [](auto &l, auto &r) { return l.y() < r.y(); }); - min_val = min->y() == max->y() ? min->y() - 1 : min->y(); - max_val = min->y() == max->y() ? max->y() + 1 : max->y(); - freq_ = points.size() / std::max(points.back().x() - points.front().x(), 1.0); - render(sig->color, range, size); - } else { + if (first == last || size.isEmpty()) { pixmap = QPixmap(); + return; } + + points.clear(); + double value = 0; + for (auto it = first; it != last; ++it) { + if (sig->getValue((*it)->dat, (*it)->size, &value)) { + points.emplace_back(((*it)->mono_time - (*first)->mono_time) / 1e9, value); + } + } + + if (points.empty()) { + pixmap = QPixmap(); + return; + } + + const auto [min, max] = std::minmax_element(points.begin(), points.end(), + [](auto &l, auto &r) { return l.y() < r.y(); }); + min_val = min->y() == max->y() ? min->y() - 1 : min->y(); + max_val = min->y() == max->y() ? max->y() + 1 : max->y(); + freq_ = points.size() / std::max(points.back().x() - points.front().x(), 1.0); + render(sig->color, range, size); } void Sparkline::render(const QColor &color, int range, QSize size) { From d3cdd837fa1061761021236c4fa9053f5c617bd8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 1 Jan 2024 14:28:34 -0800 Subject: [PATCH 350/724] Bump submodules (#30881) bump submodules Co-authored-by: jnewb1 --- cereal | 2 +- rednose_repo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cereal b/cereal index bceb8b942d..e29625c30b 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit bceb8b942d3e622c2476e197102950efc4fe0bfd +Subproject commit e29625c30bb2a4a34ce21134d0e5a91b2d4fa1b1 diff --git a/rednose_repo b/rednose_repo index 44e8a891a2..18b91458fd 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 44e8a891a2810f274a1fa980775155d9463e87b9 +Subproject commit 18b91458fd396530d43e1a2fe9a3ac9055fa9109 From 6ccf2cbfde4e1f5aa67dc0072f9c0dfd080be064 Mon Sep 17 00:00:00 2001 From: Sliicy Date: Mon, 1 Jan 2024 17:29:01 -0500 Subject: [PATCH 351/724] Fixed joystickd.py incorrect steering control (#30879) --- tools/joystick/joystickd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index c6024bf5e4..176ff25be6 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -47,7 +47,7 @@ class Joystick: else: self.cancel_button = 'BTN_TRIGGER' accel_axis = 'ABS_Y' - steer_axis = 'ABS_RZ' + steer_axis = 'ABS_RX' self.min_axis_value = {accel_axis: 0., steer_axis: 0.} self.max_axis_value = {accel_axis: 255., steer_axis: 255.} self.axes_values = {accel_axis: 0., steer_axis: 0.} From 65ece2081a91b84c8b78de404aea2ec17fa81e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 1 Jan 2024 14:36:37 -0800 Subject: [PATCH 352/724] metadrive: Change camera position to reflect typical height of a vehicle (#30831) --- tools/sim/bridge/metadrive/metadrive_bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sim/bridge/metadrive/metadrive_bridge.py b/tools/sim/bridge/metadrive/metadrive_bridge.py index c8d59ff5e6..b99596ac62 100644 --- a/tools/sim/bridge/metadrive/metadrive_bridge.py +++ b/tools/sim/bridge/metadrive/metadrive_bridge.py @@ -10,7 +10,7 @@ from openpilot.tools.sim.bridge.metadrive.metadrive_world import MetaDriveWorld from openpilot.tools.sim.lib.camerad import W, H -C3_POSITION = Vec3(0, 0, 1) +C3_POSITION = Vec3(0.0, 1.0, 1.22) class CopyRamRGBCamera(RGBCamera): From 0f724675580d25734e047904c29bad8be2ce984e Mon Sep 17 00:00:00 2001 From: stevenworks <123902217+stevenworks@users.noreply.github.com> Date: Mon, 1 Jan 2024 14:39:01 -0800 Subject: [PATCH 353/724] Typo fixes / Grammar corrections (#30860) * CONTRIBUTING.md: Misspelling * BOUNTIES.md: Misspelling * SAFETY.md: Grammar * realtime.py: Comment consistency * revert for now --------- Co-authored-by: Adeeb Shihadeh --- common/realtime.py | 2 +- docs/BOUNTIES.md | 2 +- docs/CONTRIBUTING.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/realtime.py b/common/realtime.py index 05e19e770f..a398146166 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -78,7 +78,7 @@ class Ratekeeper: time.sleep(self._remaining) return lagged - # this only monitor the cumulative lag, but does not enforce a rate + # Monitors the cumulative lag, but does not enforce a rate def monitor_time(self) -> bool: prev = self._last_monitor_time self._last_monitor_time = time.monotonic() diff --git a/docs/BOUNTIES.md b/docs/BOUNTIES.md index bfefd52898..a927a33b89 100644 --- a/docs/BOUNTIES.md +++ b/docs/BOUNTIES.md @@ -14,7 +14,7 @@ New bounties can be proposed in the [**#contributing**](https://discord.com/chan ## Issue bounties -We've tagged bounty eligible issues across openpilot and the rest of our repos; check out all the open ones [here](https://github.com/orgs/commaai/projects/26/views/1). These bounties roughly work out like this: +We've tagged bounty-eligible issues across openpilot and the rest of our repos; check out all the open ones [here](https://github.com/orgs/commaai/projects/26/views/1). These bounties roughly work out like this: * **$100** - a few hours of work for an experienced openpilot developer; a good intro for someone new to openpilot * **$300** - a day of work for an experienced openpilot developer * **$500** - a few days of work for an experienced openpilot developer diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index fa80e760de..30f4e0dfd3 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -11,7 +11,7 @@ Our software is open source so you can solve your own problems without needing h ## What contributions are we looking for? -**openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and **all** developoment is towards that goal. +**openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and **all** development is towards that goal. ### What gets merged? From 9d7f618bc5352e3c4a179925b5fd9f024a286fd3 Mon Sep 17 00:00:00 2001 From: royjr Date: Mon, 1 Jan 2024 17:54:16 -0500 Subject: [PATCH 354/724] multilang: parameterize unit tests (#30842) * init * fix indents * remove import * safer * TemporaryDirectory * much cleaner --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/ui/tests/test_translations.py | 152 ++++++++++-------------- 1 file changed, 64 insertions(+), 88 deletions(-) diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index c3c1760dbd..caba386643 100755 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -2,33 +2,26 @@ import json import os import re -import shutil import unittest +import shutil +import tempfile import xml.etree.ElementTree as ET import string import requests +from parameterized import parameterized_class from openpilot.selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations -TMP_TRANSLATIONS_DIR = os.path.join(TRANSLATIONS_DIR, "tmp") +with open(LANGUAGES_FILE, "r") as f: + translation_files = json.load(f) + UNFINISHED_TRANSLATION_TAG = "" not in cur_translations, - f"{file} ({name}) translation file has obsolete translations. Run selfdrive/ui/update_translations.py --vanish to remove them") + cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) + self.assertTrue("" not in cur_translations, + f"{self.file} ({self.name}) translation file has obsolete translations. Run selfdrive/ui/update_translations.py --vanish to remove them") def test_finished_translations(self): """ @@ -80,75 +63,68 @@ class TestTranslations(unittest.TestCase): - that translation is not empty - that translation format arguments are consistent """ - for name, file in self.translation_files.items(): - with self.subTest(name=name, file=file): - tr_xml = ET.parse(os.path.join(TRANSLATIONS_DIR, f"{file}.ts")) + tr_xml = ET.parse(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")) - for context in tr_xml.getroot(): - for message in context.iterfind("message"): - translation = message.find("translation") - source_text = message.find("source").text + for context in tr_xml.getroot(): + for message in context.iterfind("message"): + translation = message.find("translation") + source_text = message.find("source").text - # Do not test unfinished translations - if translation.get("type") == "unfinished": - continue + # Do not test unfinished translations + if translation.get("type") == "unfinished": + continue - if message.get("numerus") == "yes": - numerusform = [t.text for t in translation.findall("numerusform")] + if message.get("numerus") == "yes": + numerusform = [t.text for t in translation.findall("numerusform")] - for nf in numerusform: - self.assertIsNotNone(nf, f"Ensure all plural translation forms are completed: {source_text}") - self.assertIn("%n", nf, "Ensure numerus argument (%n) exists in translation.") - self.assertIsNone(FORMAT_ARG.search(nf), "Plural translations must use %n, not %1, %2, etc.: {}".format(numerusform)) + for nf in numerusform: + self.assertIsNotNone(nf, f"Ensure all plural translation forms are completed: {source_text}") + self.assertIn("%n", nf, "Ensure numerus argument (%n) exists in translation.") + self.assertIsNone(FORMAT_ARG.search(nf), "Plural translations must use %n, not %1, %2, etc.: {}".format(numerusform)) - else: - self.assertIsNotNone(translation.text, f"Ensure translation is completed: {source_text}") + else: + self.assertIsNotNone(translation.text, f"Ensure translation is completed: {source_text}") - source_args = FORMAT_ARG.findall(source_text) - translation_args = FORMAT_ARG.findall(translation.text) - self.assertEqual(sorted(source_args), sorted(translation_args), - f"Ensure format arguments are consistent: `{source_text}` vs. `{translation.text}`") + source_args = FORMAT_ARG.findall(source_text) + translation_args = FORMAT_ARG.findall(translation.text) + self.assertEqual(sorted(source_args), sorted(translation_args), + f"Ensure format arguments are consistent: `{source_text}` vs. `{translation.text}`") def test_no_locations(self): - for name, file in self.translation_files.items(): - with self.subTest(name=name, file=file): - for line in self._read_translation_file(TRANSLATIONS_DIR, file).splitlines(): - self.assertFalse(line.strip().startswith(LOCATION_TAG), - f"Line contains location tag: {line.strip()}, remove all line numbers.") + for line in self._read_translation_file(TRANSLATIONS_DIR, self.file).splitlines(): + self.assertFalse(line.strip().startswith(LOCATION_TAG), + f"Line contains location tag: {line.strip()}, remove all line numbers.") def test_entities_error(self): - for name, file in self.translation_files.items(): - with self.subTest(name=name, file=file): - cur_translations = self._read_translation_file(TRANSLATIONS_DIR, file) - matches = re.findall(r'@(\w+);', cur_translations) - self.assertEqual(len(matches), 0, f"The string(s) {matches} were found with '@' instead of '&'") + cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) + matches = re.findall(r'@(\w+);', cur_translations) + self.assertEqual(len(matches), 0, f"The string(s) {matches} were found with '@' instead of '&'") def test_bad_language(self): IGNORED_WORDS = {'pédale'} - for name, file in self.translation_files.items(): - match = re.search(r'_([a-zA-Z]{2,3})', file) - assert match, f"{name} - could not parse language" + match = re.search(r'_([a-zA-Z]{2,3})', self.file) + assert match, f"{self.name} - could not parse language" - response = requests.get(f"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/{match.group(1)}") - response.raise_for_status() + response = requests.get(f"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/{match.group(1)}") + response.raise_for_status() - banned_words = {line.strip() for line in response.text.splitlines()} + banned_words = {line.strip() for line in response.text.splitlines()} - for context in ET.parse(os.path.join(TRANSLATIONS_DIR, f"{file}.ts")).getroot(): - for message in context.iterfind("message"): - translation = message.find("translation") - if translation.get("type") == "unfinished": - continue + for context in ET.parse(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")).getroot(): + for message in context.iterfind("message"): + translation = message.find("translation") + if translation.get("type") == "unfinished": + continue - translation_text = " ".join([t.text for t in translation.findall("numerusform")]) if message.get("numerus") == "yes" else translation.text + translation_text = " ".join([t.text for t in translation.findall("numerusform")]) if message.get("numerus") == "yes" else translation.text - if not translation_text: - continue + if not translation_text: + continue - words = set(translation_text.translate(str.maketrans('', '', string.punctuation + '%n')).lower().split()) - bad_words_found = words & (banned_words - IGNORED_WORDS) - assert not bad_words_found, f"Bad language found in {name}: '{translation_text}'. Bad word(s): {', '.join(bad_words_found)}" + words = set(translation_text.translate(str.maketrans('', '', string.punctuation + '%n')).lower().split()) + bad_words_found = words & (banned_words - IGNORED_WORDS) + assert not bad_words_found, f"Bad language found in {self.name}: '{translation_text}'. Bad word(s): {', '.join(bad_words_found)}" if __name__ == "__main__": From fba521ecc6d4c260de8f3e93dadbc3e157a9a9a6 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 2 Jan 2024 15:42:08 -0500 Subject: [PATCH 355/724] url_file: fix non-200 files being cached (#30886) * fix + test * fix unclosed * easier to read Co-authored-by: Shane Smiskol * fix that --------- Co-authored-by: Shane Smiskol --- tools/lib/tests/test_caching.py | 79 +++++++++++++++++++++++---------- tools/lib/url_file.py | 2 + 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/tools/lib/tests/test_caching.py b/tools/lib/tests/test_caching.py index 73ed843869..294c5a2233 100755 --- a/tools/lib/tests/test_caching.py +++ b/tools/lib/tests/test_caching.py @@ -1,15 +1,58 @@ #!/usr/bin/env python3 +from functools import wraps +import http.server import os +import threading +import time import unittest -from pathlib import Path from parameterized import parameterized -from unittest import mock -from openpilot.system.hardware.hw import Paths from openpilot.tools.lib.url_file import URLFile +class CachingTestRequestHandler(http.server.BaseHTTPRequestHandler): + FILE_EXISTS = True + + def do_GET(self): + if self.FILE_EXISTS: + self.send_response(200, b'1234') + else: + self.send_response(404) + self.end_headers() + + def do_HEAD(self): + if self.FILE_EXISTS: + self.send_response(200) + self.send_header("Content-Length", "4") + else: + self.send_response(404) + self.end_headers() + + +class CachingTestServer(threading.Thread): + def run(self): + self.server = http.server.HTTPServer(("127.0.0.1", 0), CachingTestRequestHandler) + self.port = self.server.server_port + self.server.serve_forever() + + def stop(self): + self.server.server_close() + self.server.shutdown() + +def with_caching_server(func): + @wraps(func) + def wrapper(*args, **kwargs): + server = CachingTestServer() + server.start() + time.sleep(0.25) # wait for server to get it's port + try: + func(*args, **kwargs, port=server.port) + finally: + server.stop() + return wrapper + + class TestFileDownload(unittest.TestCase): def compare_loads(self, url, start=0, length=None): @@ -66,32 +109,20 @@ class TestFileDownload(unittest.TestCase): self.compare_loads(large_file_url) @parameterized.expand([(True, ), (False, )]) - def test_recover_from_missing_file(self, cache_enabled): + @with_caching_server + def test_recover_from_missing_file(self, cache_enabled, port): os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" - file_url = "http://localhost:5001/test.png" + file_url = f"http://localhost:{port}/test.png" - file_exists = False + CachingTestRequestHandler.FILE_EXISTS = False + length = URLFile(file_url).get_length() + self.assertEqual(length, -1) - def get_length_online_mock(self): - if file_exists: - return 4 - return -1 + CachingTestRequestHandler.FILE_EXISTS = True + length = URLFile(file_url).get_length() + self.assertEqual(length, 4) - patch_length = mock.patch.object(URLFile, "get_length_online", get_length_online_mock) - patch_length.start() - try: - length = URLFile(file_url).get_length() - self.assertEqual(length, -1) - - file_exists = True - length = URLFile(file_url).get_length() - self.assertEqual(length, 4) - finally: - tempfile_length = Path(Paths.download_cache_root()) / "ba2119904385654cb0105a2da174875f8e7648db175f202ecae6d6428b0e838f_length" - if tempfile_length.exists(): - tempfile_length.unlink() - patch_length.stop() if __name__ == "__main__": diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index d055f86577..97c0a639a7 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -57,6 +57,8 @@ class URLFile: def get_length_online(self): timeout = Timeout(connect=50.0, read=500.0) response = self._http_client.request('HEAD', self._url, timeout=timeout, preload_content=False) + if not (200 <= response.status <= 299): + return -1 length = response.headers.get('content-length', 0) return int(length) From 86bd3379522c67da94a3ac6f3e0628bffa14ce50 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 2 Jan 2024 16:17:19 -0500 Subject: [PATCH 356/724] CI: set CI=1 globally (#30888) ci=1 --- .github/workflows/selfdrive_tests.yaml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index d037011701..adb069d077 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -28,6 +28,8 @@ env: PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 + CI: 1 + jobs: build_release: name: build release @@ -51,7 +53,7 @@ jobs: timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache run: | cd $STRIPPED_DIR - ${{ env.RUN }} "CI=1 python selfdrive/manager/build.py" + ${{ env.RUN }} "python selfdrive/manager/build.py" - name: Run tests timeout-minutes: 3 run: | @@ -213,7 +215,7 @@ jobs: - name: Run replay timeout-minutes: 30 run: | - ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ + ${{ env.RUN }} "coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ chmod -R 777 /tmp/comma_download_cache && \ coverage combine && \ coverage xml" @@ -230,7 +232,7 @@ jobs: - name: Upload reference logs if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} run: | - ${{ env.RUN }} "unset PYTHONWARNINGS && CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" + ${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v3 with: @@ -286,7 +288,7 @@ jobs: timeout-minutes: 4 run: | ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ - ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ + ONNXCPU=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ coverage combine && \ coverage xml" - name: Run unit tests From 7e71fd7baefef14d2987b28377e310df41251644 Mon Sep 17 00:00:00 2001 From: Mitchell Goff Date: Tue, 2 Jan 2024 15:27:15 -0600 Subject: [PATCH 357/724] new delhi model (#30822) * 027a1efa-10fb-4291-ad58-90cf0ff150b5/700 * debug * rm for now * bump refs * revert that --------- Co-authored-by: Adeeb Shihadeh --- Jenkinsfile | 2 +- selfdrive/modeld/models/navmodel.onnx | 4 ++-- selfdrive/modeld/models/navmodel_q.dlc | 2 +- selfdrive/modeld/models/supercombo.onnx | 4 ++-- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9868777dfb..5c872664a2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -241,7 +241,7 @@ node { 'replay': { deviceStage("tici", "tici-replay", ["UNSAFE=1"], [ ["build", "cd selfdrive/manager && ./build.py"], - ["model replay", "selfdrive/test/process_replay/model_replay.py", ["tinygrad/", "selfdrive/modeld/"]], + ["model replay", "selfdrive/test/process_replay/model_replay.py"], ]) }, 'tizi': { diff --git a/selfdrive/modeld/models/navmodel.onnx b/selfdrive/modeld/models/navmodel.onnx index 3b0961537e..93edb593cc 100644 --- a/selfdrive/modeld/models/navmodel.onnx +++ b/selfdrive/modeld/models/navmodel.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:adc5aca6753b6ae0a1469f3e5bcb943d00cc9de75218489f2e4c3d960e7af048 -size 14138061 +oid sha256:4971931accb5ba2e534bb3e0c591826ee507e2988df2eccf1fe862c303ddf9c5 +size 14221074 diff --git a/selfdrive/modeld/models/navmodel_q.dlc b/selfdrive/modeld/models/navmodel_q.dlc index e878ec25af..cb6e55b75f 100644 --- a/selfdrive/modeld/models/navmodel_q.dlc +++ b/selfdrive/modeld/models/navmodel_q.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c808717d073a0bb347f9ba929953c0b2b792ce9997f343f7e44a0b2b0e139132 +oid sha256:fa346ada6f8c6326a5ee5fcd27e45e3e710049358079413c6a4624b20c6e1e47 size 3630942 diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 9697c01793..ca8642093d 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cf6133c5bff295a3ee69eeb01297ba77adb6b83dbc1d774442a48117dbaf4626 -size 48457192 +oid sha256:ae44fe832fe48b89998f09cebb1bcd129864a8f51497b636cd38e66e46d69a89 +size 48457850 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index bb111e22e2..1be0aaf8c3 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -91cd2bf71771c2770c0effc26c0bb23d27208138 +ad64b6f38c1362e9d184f3fc95299284eacb56d4 From 9cf57cc4e478d02dd8843c82320d336597b3621c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 2 Jan 2024 13:34:35 -0800 Subject: [PATCH 358/724] CI: bump lewagon/wait-on-check-action --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7c36410b32..cd2ebd9f82 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -21,7 +21,7 @@ jobs: sudo apt-get install -y libyaml-dev - name: Wait for green check mark if: ${{ github.event_name != 'workflow_dispatch' }} - uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd + uses: lewagon/wait-on-check-action@595dabb3acf442d47e29c9ec9ba44db0c6bdd18f with: ref: master wait-interval: 30 From 9b25bfc61866ff7853218e72aebd10df3c28595c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 2 Jan 2024 16:48:16 -0500 Subject: [PATCH 359/724] CI: set CI=1 in docker image (#30889) * ci=1 * wrong spot --- .github/workflows/selfdrive_tests.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index adb069d077..3c95d8055b 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -20,16 +20,14 @@ env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c BUILD_CL: selfdrive/test/docker_build.sh cl - RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/bash -c + RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/bash -c PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 - CI: 1 - jobs: build_release: name: build release From 205a78f02dd8a7e3de2334da419f57231153c69a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 Jan 2024 14:18:52 -0800 Subject: [PATCH 360/724] PR comments: fix image links --- selfdrive/debug/print_docs_diff.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py index 8fb6277d3b..3d35532496 100755 --- a/selfdrive/debug/print_docs_diff.py +++ b/selfdrive/debug/print_docs_diff.py @@ -8,9 +8,10 @@ from openpilot.selfdrive.car.docs import get_all_car_info from openpilot.selfdrive.car.docs_definitions import Column FOOTNOTE_TAG = "{}" -STAR_ICON = '' -VIDEO_ICON = '\ - ' +STAR_ICON = '' +VIDEO_ICON = '' + \ + '' COLUMNS = "|" + "|".join([column.value for column in Column]) + "|" COLUMN_HEADER = "|---|---|---|{}|".format("|".join([":---:"] * (len(Column) - 3))) ARROW_SYMBOL = "➡️" From 543cd4460acd24a7bfc5bd8f6b4f2d8b2242da12 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 Jan 2024 18:10:23 -0700 Subject: [PATCH 361/724] HKG: fix 2021 Niro Hybrid harness (#30895) * originally was Hyundai F in the database, but one user on Discord (US) and one in support (SK) both needed hyundai D * bump --- docs/CARS.md | 5 +++-- selfdrive/car/hyundai/values.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index e48f4765bb..466bf14fff 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 273 Supported Cars +# 274 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -132,7 +132,8 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|

Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Hybrid 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2020|All|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 371478f429..4b92bc5a49 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -251,7 +251,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { ], CAR.KIA_NIRO_PHEV_2022: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_f])), CAR.KIA_NIRO_HEV_2021: [ - HyundaiCarInfo("Kia Niro Hybrid 2021-22", car_parts=CarParts.common([CarHarness.hyundai_f])), # TODO: 2021 could be hyundai_d, verify + HyundaiCarInfo("Kia Niro Hybrid 2021", car_parts=CarParts.common([CarHarness.hyundai_d])), + HyundaiCarInfo("Kia Niro Hybrid 2022", car_parts=CarParts.common([CarHarness.hyundai_f])), ], CAR.KIA_NIRO_HEV_2ND_GEN: HyundaiCarInfo("Kia Niro Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", From de4bc31ba6f9972b6d411a36dc5d32474a3d0287 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 Jan 2024 18:14:51 -0700 Subject: [PATCH 362/724] Toyota: add 2024 RAV4 Hybrid (Canada-built) (#30887) * add 2024 RAV4 * bump reeleases * fix star link in PR comments * ? * consistent --- RELEASES.md | 2 +- docs/CARS.md | 2 +- selfdrive/car/toyota/fingerprints.py | 2 ++ selfdrive/car/toyota/values.py | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 226a272824..b12f22d280 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -8,7 +8,7 @@ Version 0.9.6 (20XX-XX-XX) * Hyundai Staria 2023 support thanks to sunnyhaibin! * Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! * Toyota RAV4 2023 support -* Toyota RAV4 Hybrid 2023 support +* Toyota RAV4 Hybrid 2023-24 support Version 0.9.5 (2023-11-17) ======================== diff --git a/docs/CARS.md b/docs/CARS.md index 466bf14fff..bb2484e816 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -245,7 +245,7 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 Hybrid 2023-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index e7f3fe27ad..51e35fcf70 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -1126,6 +1126,7 @@ FW_VERSIONS = { b'\x01F15264283200\x00\x00\x00\x00', b'\x01F15264283300\x00\x00\x00\x00', b'\x01F152642F1000\x00\x00\x00\x00', + b'\x01F152642F8000\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', @@ -1144,6 +1145,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F0R05100\x00\x00\x00\x008646G0R02100\x00\x00\x00\x00', b'\x028646F0R05200\x00\x00\x00\x008646G0R02200\x00\x00\x00\x00', + b'\x028646F0R11000\x00\x00\x00\x008646G0R04000\x00\x00\x00\x00', ], }, CAR.SIENNA: { diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index a087e25934..fca7c50dc7 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -181,7 +181,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ], CAR.RAV4_TSS2_2023: [ ToyotaCarInfo("Toyota RAV4 2023"), - ToyotaCarInfo("Toyota RAV4 Hybrid 2023"), + ToyotaCarInfo("Toyota RAV4 Hybrid 2023-24"), ], CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"), CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED), From 8c4930d2d300a439aaf7dd9ca4324271cc0c7b52 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 Jan 2024 18:22:06 -0700 Subject: [PATCH 363/724] Revert "test_models: fallback to public route when route isn't uploaded to CI bucket (#30794)" (#30896) * fails if no skip function * Revert "fails if no skip function" This reverts commit ff3d697d38dce56057c83f06033a689f2301a29d. * Revert "test_models: fallback to public route when route isn't uploaded to CI bucket (#30794)" This reverts commit 05dc0b51e6597fb83aed14f5a390fa4254751c98. --- selfdrive/car/tests/test_models.py | 38 +++++++----------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 7306962fa6..e9c2a4ecd5 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -37,7 +37,6 @@ INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "50")) -CI = os.environ.get("CI", None) is not None def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: # build list of test cases @@ -68,33 +67,13 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: class TestCarModelBase(unittest.TestCase): car_model: Optional[str] = None test_route: Optional[CarTestRoute] = None - test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket + ci: bool = True can_msgs: List[capnp.lib.capnp._DynamicStructReader] fingerprint: dict[int, dict[int, int]] elm_frame: Optional[int] car_safety_mode_frame: Optional[int] - @classmethod - def get_logreader(cls, seg): - if len(INTERNAL_SEG_LIST): - route_name = RouteName(cls.test_route.route) - return LogReader(f"cd:/{route_name.dongle_id}/{route_name.time_str}/{seg}/rlog.bz2") - else: - # Attempt to use CI bucket first - try: - return LogReader(get_url(cls.test_route.route, seg)) - except Exception: - cls.test_route_on_bucket = False - - # Fallback to public route, which will fail the test_route_on_ci_bucket when running in CI - try: - return LogReader(Route(cls.test_route.route).log_paths()[seg]) - except Exception: - pass - - raise Exception("Unable to get route. Check that the route is valid, and either public or uploaded to the CI bucket.") - @classmethod def setUpClass(cls): if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'): @@ -116,7 +95,13 @@ class TestCarModelBase(unittest.TestCase): for seg in test_segs: try: - lr = cls.get_logreader(seg) + if len(INTERNAL_SEG_LIST): + route_name = RouteName(cls.test_route.route) + lr = LogReader(f"cd:/{route_name.dongle_id}/{route_name.time_str}/{seg}/rlog.bz2") + elif cls.ci: + lr = LogReader(get_url(cls.test_route.route, seg)) + else: + lr = LogReader(Route(cls.test_route.route).log_paths()[seg]) except Exception: continue @@ -160,7 +145,7 @@ class TestCarModelBase(unittest.TestCase): if len(can_msgs) > int(50 / DT_CTRL): break else: - raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded and public?") + raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded?") # if relay is expected to be open in the route cls.openpilot_enabled = cls.car_safety_mode_frame is not None @@ -466,11 +451,6 @@ class TestCarModelBase(unittest.TestCase): failed_checks = {k: v for k, v in checks.items() if v > 0} self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") - @pytest.mark.skipif(not CI, reason="When running in CI we want to make sure all the routes are uploaded to the preserved CI bucket.") - def test_route_on_ci_bucket(self): - assert self.test_route_on_bucket, "Route not on CI bucket. \ - This is fine to fail for WIP car ports, just let us know and we can upload your routes to the CI bucket." - @parameterized_class(('car_model', 'test_route'), get_test_cases()) @pytest.mark.xdist_group_class_property('test_route') From 633fef5a96d5c8db55a1e6276df6d7f060c4ccd7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 2 Jan 2024 22:48:31 -0700 Subject: [PATCH 364/724] Fingerprints: add missing FW versions from users (#30837) Export Toyota fingerprints from last 2 weeks, 0.9.5-release --- selfdrive/car/toyota/fingerprints.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 51e35fcf70..62c4ddf3ce 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -23,6 +23,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F4702000\x00\x00\x00\x00', b'8821F4702100\x00\x00\x00\x00', + b'8821F4702300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'8646F0701100\x00\x00\x00\x00', @@ -51,6 +52,7 @@ FW_VERSIONS = { b'8965B41090\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896630725100\x00\x00\x00\x00', b'\x01896630725200\x00\x00\x00\x00', b'\x01896630725300\x00\x00\x00\x00', b'\x01896630735100\x00\x00\x00\x00', @@ -142,6 +144,7 @@ FW_VERSIONS = { (Ecu.dsu, 0x791, None): [ b'8821F0601200 ', b'8821F0601300 ', + b'8821F0601400 ', b'8821F0602000 ', b'8821F0603300 ', b'8821F0603400 ', @@ -185,6 +188,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F0601200 ', b'8821F0601300 ', + b'8821F0601400 ', b'8821F0602000 ', b'8821F0603300 ', b'8821F0603400 ', @@ -497,6 +501,7 @@ FW_VERSIONS = { b'\x018965B12510\x00\x00\x00\x00\x00\x00', b'\x018965B12520\x00\x00\x00\x00\x00\x00', b'\x018965B12530\x00\x00\x00\x00\x00\x00', + b'\x018965B1254000\x00\x00\x00\x00', b'\x018965B1255000\x00\x00\x00\x00', b'\x018965B1256000\x00\x00\x00\x00', b'8965B12361\x00\x00\x00\x00\x00\x00', @@ -571,6 +576,7 @@ FW_VERSIONS = { b'\x028646F1202000\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', + b'\x028646F1206000\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1601200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', @@ -578,6 +584,7 @@ FW_VERSIONS = { b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F7603100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F7603200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F7603300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F7605100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, @@ -588,6 +595,7 @@ FW_VERSIONS = { b'\x01896630E43100\x00\x00\x00\x00', b'\x01896630E43200\x00\x00\x00\x00', b'\x01896630E44200\x00\x00\x00\x00', + b'\x01896630E44400\x00\x00\x00\x00', b'\x01896630E45000\x00\x00\x00\x00', b'\x01896630E45100\x00\x00\x00\x00', b'\x01896630E45200\x00\x00\x00\x00', @@ -792,6 +800,7 @@ FW_VERSIONS = { b'\x02896634761100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634761200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634762000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634762100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634763000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634763100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634765000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -800,6 +809,7 @@ FW_VERSIONS = { b'\x02896634769100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634769200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634770000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896634770100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634774000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634774100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896634774200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -812,6 +822,7 @@ FW_VERSIONS = { b'\x03896634759100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', b'\x03896634759200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', b'\x03896634759200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', + b'\x03896634759300\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', b'\x03896634759300\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701004\x00\x00\x00\x00', b'\x03896634760000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701002\x00\x00\x00\x00', b'\x03896634760000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4701003\x00\x00\x00\x00', @@ -1239,6 +1250,7 @@ FW_VERSIONS = { b'\x018821F3301200\x00\x00\x00\x00', b'\x018821F3301300\x00\x00\x00\x00', b'\x018821F3301400\x00\x00\x00\x00', + b'\x018821F6201200\x00\x00\x00\x00', b'\x018821F6201300\x00\x00\x00\x00', b'\x018821F6201400\x00\x00\x00\x00', ], @@ -1251,6 +1263,7 @@ FW_VERSIONS = { b'\x028646F3304200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F3304300\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F3309100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3309100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', ], }, CAR.LEXUS_ES: { @@ -1321,6 +1334,7 @@ FW_VERSIONS = { b'\x01896637851000\x00\x00\x00\x00', b'\x01896637852000\x00\x00\x00\x00', b'\x01896637854000\x00\x00\x00\x00', + b'\x01896637873000\x00\x00\x00\x00', b'\x01896637878000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ @@ -1441,6 +1455,7 @@ FW_VERSIONS = { b'\x018966348R1300\x00\x00\x00\x00', b'\x018966348R8500\x00\x00\x00\x00', b'\x018966348W1300\x00\x00\x00\x00', + b'\x018966348W2300\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'F152648472\x00\x00\x00\x00\x00\x00', @@ -1485,6 +1500,7 @@ FW_VERSIONS = { b'\x02348Q4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348Q4100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348T1100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348T1200\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348T3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348V6000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348Z3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1505,6 +1521,7 @@ FW_VERSIONS = { (Ecu.eps, 0x7a1, None): [ b'8965B0E011\x00\x00\x00\x00\x00\x00', b'8965B0E012\x00\x00\x00\x00\x00\x00', + b'8965B48102\x00\x00\x00\x00\x00\x00', b'8965B48111\x00\x00\x00\x00\x00\x00', b'8965B48112\x00\x00\x00\x00\x00\x00', ], @@ -1640,6 +1657,7 @@ FW_VERSIONS = { b'8965B58052\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ + b'F152658320\x00\x00\x00\x00\x00\x00', b'F152658341\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ From ce64433dc9355bd021530c1665ab96ab78124835 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 3 Jan 2024 12:21:48 -0500 Subject: [PATCH 365/724] MADS: Fix wrong variable name in init function --- selfdrive/car/interfaces.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index bbf3ac5bf5..53071d6f0b 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -567,8 +567,8 @@ class CarInterfaceBase(ABC): self.madsEnabledInitPrev = False return False if not self.mads_main_toggle or self.prev_acc_mads_combo: - return CS.mads_enabled - if not self.madsEnabledInit and CS.mads_enabled: + return CS.madsEnabled + if not self.madsEnabledInit and CS.madsEnabled: self.madsEnabledInit = True self.last_mads_init = time.monotonic() if cs_out.gearShifter not in FORWARD_GEARS: @@ -579,7 +579,7 @@ class CarInterfaceBase(ABC): self.madsEnabledInitPrev = True return cs_out.cruiseState.available else: - return CS.mads_enabled + return CS.madsEnabled def get_sp_common_state(self, cs_out, CS, min_enable_speed_pcm=False, gear_allowed=True, gap_button=False): cs_out.cruiseState.enabled = CS.accEnabled if not self.CP.pcmCruise or not self.CP.pcmCruiseSpeed or min_enable_speed_pcm else \ From 8fbe382fa71710cff7c49d7076cf8ccaf5c1a570 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 3 Jan 2024 11:16:54 -0800 Subject: [PATCH 366/724] make scons parallel by default (#30901) --- SConstruct | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 8790221315..e1a9b82c7f 100644 --- a/SConstruct +++ b/SConstruct @@ -14,6 +14,8 @@ AGNOS = TICI Decider('MD5-timestamp') +SetOption('num_jobs', int(os.cpu_count()/2)) + AddOption('--kaitai', action='store_true', help='Regenerate kaitai struct parsers') @@ -37,7 +39,7 @@ AddOption('--clazy', AddOption('--compile_db', action='store_true', help='build clang compilation database') - + AddOption('--ccflags', action='store', type='string', From 7f398e1cf9b0fd83bb1238b9b347a21d8868ee0f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 Jan 2024 12:54:22 -0700 Subject: [PATCH 367/724] test_models: fallback to public route when route isn't uploaded to CI bucket (#30893) * not sure why this is a test * Revert "not sure why this is a test" This reverts commit c13e16311eb882df65d216bbd59acb5702e985e6. * no ruff for this pattern :( * this is always caught * should fail * simpler * Revert "should fail" This reverts commit 21bd15275c46cd42fd19b058245334bae83cce77. * fix * cleanup this * better comment * better * this can be more localized * better names * better names * should fail * Revert "should fail" This reverts commit c58495e8b3417ce351df8048752ec60177827cb3. --------- Co-authored-by: Justin Newberry --- selfdrive/car/tests/test_models.py | 147 +++++++++++++++++------------ 1 file changed, 89 insertions(+), 58 deletions(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index e9c2a4ecd5..cc52995a33 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -36,6 +36,7 @@ JOB_ID = int(os.environ.get("JOB_ID", "0")) INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "50")) +CI = os.environ.get("CI", None) is not None def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: @@ -67,13 +68,94 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: class TestCarModelBase(unittest.TestCase): car_model: Optional[str] = None test_route: Optional[CarTestRoute] = None - ci: bool = True + test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket can_msgs: List[capnp.lib.capnp._DynamicStructReader] fingerprint: dict[int, dict[int, int]] elm_frame: Optional[int] car_safety_mode_frame: Optional[int] + @classmethod + def get_logreader(cls, seg): + if len(INTERNAL_SEG_LIST): + route_name = RouteName(cls.test_route.route) + return LogReader(f"cd:/{route_name.dongle_id}/{route_name.time_str}/{seg}/rlog.bz2") + else: + return LogReader(get_url(cls.test_route.route, seg)) + + @classmethod + def get_testing_data_from_logreader(cls, lr): + car_fw = [] + can_msgs = [] + cls.elm_frame = None + cls.car_safety_mode_frame = None + cls.fingerprint = gen_empty_fingerprint() + experimental_long = False + for msg in lr: + if msg.which() == "can": + can_msgs.append(msg) + if len(can_msgs) <= FRAME_FINGERPRINT: + for m in msg.can: + if m.src < 64: + cls.fingerprint[m.src][m.address] = len(m.dat) + + elif msg.which() == "carParams": + car_fw = msg.carParams.carFw + if msg.carParams.openpilotLongitudinalControl: + experimental_long = True + if cls.car_model is None and not cls.ci: + cls.car_model = msg.carParams.carFingerprint + + # Log which can frame the panda safety mode left ELM327, for CAN validity checks + elif msg.which() == 'pandaStates': + for ps in msg.pandaStates: + if cls.elm_frame is None and ps.safetyModel != SafetyModel.elm327: + cls.elm_frame = len(can_msgs) + if cls.car_safety_mode_frame is None and ps.safetyModel not in \ + (SafetyModel.elm327, SafetyModel.noOutput): + cls.car_safety_mode_frame = len(can_msgs) + + elif msg.which() == 'pandaStateDEPRECATED': + if cls.elm_frame is None and msg.pandaStateDEPRECATED.safetyModel != SafetyModel.elm327: + cls.elm_frame = len(can_msgs) + if cls.car_safety_mode_frame is None and msg.pandaStateDEPRECATED.safetyModel not in \ + (SafetyModel.elm327, SafetyModel.noOutput): + cls.car_safety_mode_frame = len(can_msgs) + + if len(can_msgs) > int(50 / DT_CTRL): + return car_fw, can_msgs, experimental_long + + raise Exception("no can data found") + + @classmethod + def get_testing_data(cls): + test_segs = (2, 1, 0) + if cls.test_route.segment is not None: + test_segs = (cls.test_route.segment,) + + # Try the primary method first (CI or internal) + for seg in test_segs: + try: + lr = cls.get_logreader(seg) + return cls.get_testing_data_from_logreader(lr) + except Exception: + pass + + # Route is not in CI bucket, assume either user has access (private), or it is public + # test_route_on_ci_bucket will fail when running in CI + if not len(INTERNAL_SEG_LIST): + cls.test_route_on_bucket = False + + for seg in test_segs: + try: + lr = LogReader(Route(cls.test_route.route).log_paths()[seg]) + return cls.get_testing_data_from_logreader(lr) + except Exception: + pass + + raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded and public?") + + @classmethod def setUpClass(cls): if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'): @@ -89,63 +171,7 @@ class TestCarModelBase(unittest.TestCase): raise unittest.SkipTest raise Exception(f"missing test route for {cls.car_model}") - test_segs = (2, 1, 0) - if cls.test_route.segment is not None: - test_segs = (cls.test_route.segment,) - - for seg in test_segs: - try: - if len(INTERNAL_SEG_LIST): - route_name = RouteName(cls.test_route.route) - lr = LogReader(f"cd:/{route_name.dongle_id}/{route_name.time_str}/{seg}/rlog.bz2") - elif cls.ci: - lr = LogReader(get_url(cls.test_route.route, seg)) - else: - lr = LogReader(Route(cls.test_route.route).log_paths()[seg]) - except Exception: - continue - - car_fw = [] - can_msgs = [] - cls.elm_frame = None - cls.car_safety_mode_frame = None - cls.fingerprint = gen_empty_fingerprint() - experimental_long = False - for msg in lr: - if msg.which() == "can": - can_msgs.append(msg) - if len(can_msgs) <= FRAME_FINGERPRINT: - for m in msg.can: - if m.src < 64: - cls.fingerprint[m.src][m.address] = len(m.dat) - - elif msg.which() == "carParams": - car_fw = msg.carParams.carFw - if msg.carParams.openpilotLongitudinalControl: - experimental_long = True - if cls.car_model is None and not cls.ci: - cls.car_model = msg.carParams.carFingerprint - - # Log which can frame the panda safety mode left ELM327, for CAN validity checks - elif msg.which() == 'pandaStates': - for ps in msg.pandaStates: - if cls.elm_frame is None and ps.safetyModel != SafetyModel.elm327: - cls.elm_frame = len(can_msgs) - if cls.car_safety_mode_frame is None and ps.safetyModel not in \ - (SafetyModel.elm327, SafetyModel.noOutput): - cls.car_safety_mode_frame = len(can_msgs) - - elif msg.which() == 'pandaStateDEPRECATED': - if cls.elm_frame is None and msg.pandaStateDEPRECATED.safetyModel != SafetyModel.elm327: - cls.elm_frame = len(can_msgs) - if cls.car_safety_mode_frame is None and msg.pandaStateDEPRECATED.safetyModel not in \ - (SafetyModel.elm327, SafetyModel.noOutput): - cls.car_safety_mode_frame = len(can_msgs) - - if len(can_msgs) > int(50 / DT_CTRL): - break - else: - raise Exception(f"Route: {repr(cls.test_route.route)} with segments: {test_segs} not found or no CAN msgs found. Is it uploaded?") + car_fw, can_msgs, experimental_long = cls.get_testing_data() # if relay is expected to be open in the route cls.openpilot_enabled = cls.car_safety_mode_frame is not None @@ -451,6 +477,11 @@ class TestCarModelBase(unittest.TestCase): failed_checks = {k: v for k, v in checks.items() if v > 0} self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") + @unittest.skipIf(not CI, "Accessing non CI-bucket routes is allowed only when not in CI") + def test_route_on_ci_bucket(self): + self.assertTrue(self.test_route_on_bucket, "Route not on CI bucket. " + + "This is fine to fail for WIP car ports, just let us know and we can upload your routes to the CI bucket.") + @parameterized_class(('car_model', 'test_route'), get_test_cases()) @pytest.mark.xdist_group_class_property('test_route') From 17ac1d3c7b3b481826d51eb82aaf9dbcc3f3bd81 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 4 Jan 2024 05:14:29 +0800 Subject: [PATCH 368/724] UI: single-threaded CameraView (#30397) * single-threaded CameraView * updateFrame * inherit from CameraWidget * clear frame on offroadTransition * check frame_id * optional * unique_ptr * CameraView * cleanup * cleanup * log skipping frame * fix driverview * disconnectVipc * debug inconsistent frame * skip frame if uiPlan is outdate * cleanup * set connected = false * support camerad reboot&seeking in replay * cleanup * qDebug * use deque * cleanup --------- Co-authored-by: Comma Device --- selfdrive/ui/qt/offroad/driverview.cc | 13 +- selfdrive/ui/qt/offroad/driverview.h | 2 +- selfdrive/ui/qt/onroad.cc | 30 ++-- selfdrive/ui/qt/onroad.h | 2 - selfdrive/ui/qt/widgets/cameraview.cc | 197 ++++++++++---------------- selfdrive/ui/qt/widgets/cameraview.h | 51 ++++--- selfdrive/ui/watch3.cc | 8 +- tools/cabana/videowidget.cc | 5 +- tools/cabana/videowidget.h | 4 +- 9 files changed, 129 insertions(+), 183 deletions(-) diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index df9bb24651..08f6f10642 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -7,7 +7,7 @@ const int FACE_IMG_SIZE = 130; -DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraWidget("camerad", VISION_STREAM_DRIVER, true, parent) { +DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraView("camerad", VISION_STREAM_DRIVER, true, parent) { face_img = loadPixmap("../assets/img_driver_face_static.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); QObject::connect(this, &CameraWidget::clicked, this, &DriverViewWindow::done); QObject::connect(device(), &Device::interactiveTimeout, this, [this]() { @@ -20,22 +20,20 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraWidget("camerad", VI void DriverViewWindow::showEvent(QShowEvent* event) { params.putBool("IsDriverViewEnabled", true); device()->resetInteractiveTimeout(60); - CameraWidget::showEvent(event); + CameraView::showEvent(event); } void DriverViewWindow::hideEvent(QHideEvent* event) { params.putBool("IsDriverViewEnabled", false); - stopVipcThread(); - CameraWidget::hideEvent(event); + CameraView::hideEvent(event); } void DriverViewWindow::paintGL() { - CameraWidget::paintGL(); + CameraView::paintGL(); - std::lock_guard lk(frame_lock); QPainter p(this); // startup msg - if (frames.empty()) { + if (!frame) { p.setPen(Qt::white); p.setRenderHint(QPainter::TextAntialiasing); p.setFont(InterFont(100, QFont::Bold)); @@ -47,7 +45,6 @@ void DriverViewWindow::paintGL() { cereal::DriverStateV2::Reader driver_state = sm["driverStateV2"].getDriverStateV2(); bool is_rhd = driver_state.getWheelOnRightProb() > 0.5; auto driver_data = is_rhd ? driver_state.getRightDriverData() : driver_state.getLeftDriverData(); - bool face_detected = driver_data.getFaceProb() > 0.7; if (face_detected) { auto fxy_list = driver_data.getFacePosition(); diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h index 155e4ede32..97dd2faa78 100644 --- a/selfdrive/ui/qt/offroad/driverview.h +++ b/selfdrive/ui/qt/offroad/driverview.h @@ -2,7 +2,7 @@ #include "selfdrive/ui/qt/widgets/cameraview.h" -class DriverViewWindow : public CameraWidget { +class DriverViewWindow : public CameraView { Q_OBJECT public: diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 4df0ed81cb..ed4bdc15fb 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -44,12 +44,12 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { split->addWidget(nvg); if (getenv("DUAL_CAMERA_VIEW")) { - CameraWidget *arCam = new CameraWidget("camerad", VISION_STREAM_ROAD, true, this); + CameraWidget *arCam = new CameraView("camerad", VISION_STREAM_ROAD, true, this); split->insertWidget(0, arCam); } if (getenv("MAP_RENDER_VIEW")) { - CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); + CameraWidget *map_render = new CameraView("navd", VISION_STREAM_MAP, false, this); split->insertWidget(0, map_render); } @@ -126,6 +126,7 @@ void OnroadWindow::offroadTransition(bool offroad) { #endif alerts->updateAlert({}); + nvg->disconnectVipc(); } void OnroadWindow::primeChanged(bool prime) { @@ -328,6 +329,8 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { map_settings_btn->setVisible(!hideBottomIcons); main_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); } + + update(); } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -609,21 +612,6 @@ void AnnotatedCameraWidget::paintGL() { // draw camera frame { - std::lock_guard lk(frame_lock); - - if (frames.empty()) { - if (skip_frame_count > 0) { - skip_frame_count--; - qDebug() << "skipping frame, not ready"; - return; - } - } else { - // skip drawing up to this many frames if we're - // missing camera frames. this smooths out the - // transitions from the narrow and wide cameras - skip_frame_count = 5; - } - // Wide or narrow cam dependent on speed bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); if (has_wide_cam) { @@ -639,14 +627,18 @@ void AnnotatedCameraWidget::paintGL() { } CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); - s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD; + s->scene.wide_cam = CameraWidget::streamType() == VISION_STREAM_WIDE_ROAD; if (s->scene.calibration_valid) { auto calib = s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib; CameraWidget::updateCalibration(calib); } else { CameraWidget::updateCalibration(DEFAULT_CALIBRATION); } - CameraWidget::setFrameId(model.getFrameId()); + + if (!CameraWidget::receiveFrame(sm["uiPlan"].getUiPlan().getFrameId())) { + qDebug() << "skipping frame, not ready"; + return; + } CameraWidget::paintGL(); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index b3ba411453..f6b80a75de 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -93,8 +93,6 @@ private: bool v_ego_cluster_seen = false; int status = STATUS_DISENGAGED; std::unique_ptr pm; - - int skip_frame_count = 0; bool wide_cam_requested = false; protected: diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 7b1f2f1d24..4d370b8cc5 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -6,14 +6,6 @@ #include #endif -#include -#include -#include -#include - -#include -#include - namespace { const char frame_vertex_shader[] = @@ -95,24 +87,25 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio) } // namespace -CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) : - stream_name(stream_name), requested_stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { - setAttribute(Qt::WA_OpaquePaintEvent); - qRegisterMetaType>("availableStreams"); - QObject::connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); - QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection); - QObject::connect(this, &CameraWidget::vipcAvailableStreamsUpdated, this, &CameraWidget::availableStreamsUpdated, Qt::QueuedConnection); +CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget *parent) + : stream_name(stream_name), requested_stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { } CameraWidget::~CameraWidget() { makeCurrent(); - stopVipcThread(); if (isValid()) { glDeleteVertexArrays(1, &frame_vao); glDeleteBuffers(1, &frame_vbo); glDeleteBuffers(1, &frame_ibo); glDeleteBuffers(2, textures); } +#ifdef QCOM2 + EGLDisplay egl_display = eglGetCurrentDisplay(); + for (auto &pair : egl_images) { + eglDestroyImageKHR(egl_display, pair.second); + } + egl_images.clear(); +#endif doneCurrent(); } @@ -176,45 +169,11 @@ void CameraWidget::initializeGL() { #endif } -void CameraWidget::showEvent(QShowEvent *event) { - if (!vipc_thread) { - clearFrames(); - vipc_thread = new QThread(); - connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); - connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater); - vipc_thread->start(); - } -} - -void CameraWidget::stopVipcThread() { - makeCurrent(); - if (vipc_thread) { - vipc_thread->requestInterruption(); - vipc_thread->quit(); - vipc_thread->wait(); - vipc_thread = nullptr; - } - -#ifdef QCOM2 - EGLDisplay egl_display = eglGetCurrentDisplay(); - assert(egl_display != EGL_NO_DISPLAY); - for (auto &pair : egl_images) { - eglDestroyImageKHR(egl_display, pair.second); - assert(eglGetError() == EGL_SUCCESS); - } - egl_images.clear(); -#endif -} - -void CameraWidget::availableStreamsUpdated(std::set streams) { - available_streams = streams; -} - void CameraWidget::updateFrameMat() { int w = glWidth(), h = glHeight(); if (zoomed_view) { - if (active_stream_type == VISION_STREAM_DRIVER) { + if (streamType() == VISION_STREAM_DRIVER) { if (stream_width > 0 && stream_height > 0) { frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); } @@ -223,7 +182,7 @@ void CameraWidget::updateFrameMat() { // to ensure this ends up in the middle of the screen // for narrow come and a little lower for wide cam. // TODO: use proper perspective transform? - if (active_stream_type == VISION_STREAM_WIDE_ROAD) { + if (streamType() == VISION_STREAM_WIDE_ROAD) { intrinsic_matrix = ECAM_INTRINSIC_MATRIX; zoom = 2.0; } else { @@ -269,25 +228,15 @@ void CameraWidget::paintGL() { glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - std::lock_guard lk(frame_lock); - if (frames.empty()) return; - - int frame_idx = frames.size() - 1; - - // Always draw latest frame until sync logic is more stable - // for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) { - // if (frames[frame_idx].first == draw_frame_id) break; - // } + if (!frame) return; // Log duplicate/dropped frames - if (frames[frame_idx].first == prev_frame_id) { - qDebug() << "Drawing same frame twice" << frames[frame_idx].first; - } else if (frames[frame_idx].first != prev_frame_id + 1) { - qDebug() << "Skipped frame" << frames[frame_idx].first; + if (frame_id == prev_frame_id) { + qDebug() << "Drawing same frame twice" << frame_id; + } else if (frame_id != prev_frame_id + 1) { + qDebug() << "Skipped frame" << frame_id; } - prev_frame_id = frames[frame_idx].first; - VisionBuf *frame = frames[frame_idx].second; - assert(frame != nullptr); + prev_frame_id = frame_id; updateFrameMat(); @@ -327,7 +276,7 @@ void CameraWidget::paintGL() { glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } -void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { +void CameraWidget::vipcConnected() { makeCurrent(); stream_width = vipc_client->buffers[0].width; stream_height = vipc_client->buffers[0].height; @@ -377,59 +326,69 @@ void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { #endif } -void CameraWidget::vipcFrameReceived() { - update(); +bool CameraWidget::receiveFrame(uint64_t request_frame_id) { + if (!vipc_client || vipc_client->type != requested_stream_type) { + qDebug().nospace() << "connecting to stream" << requested_stream_type + << (vipc_client ? QString(", was connected to %1").arg(vipc_client->type) : ""); + vipc_client.reset(new VisionIpcClient(stream_name, requested_stream_type, false)); + } + + if (!vipc_client->connected) { + frame = nullptr; + recent_frames.clear(); + available_streams = VisionIpcClient::getAvailableStreams(stream_name, false); + if (available_streams.empty() || !vipc_client->connect(false)) { + return false; + } + emit vipcAvailableStreamsUpdated(); + vipcConnected(); + } + + VisionIpcBufExtra meta_main = {}; + while (auto buf = vipc_client->recv(&meta_main, 0)) { + if (recent_frames.size() > FRAME_BUFFER_SIZE) { + recent_frames.pop_front(); + } + recent_frames.emplace_back(meta_main.frame_id, buf); + } + + frame = nullptr; + if (request_frame_id > 0) { + auto it = std::find_if(recent_frames.begin(), recent_frames.end(), + [request_frame_id](auto &f) { return f.first == request_frame_id; }); + if (it != recent_frames.end()) { + std::tie(frame_id, frame) = *it; + } + } else if (!recent_frames.empty()) { + std::tie(frame_id, frame) = recent_frames.back(); + } + return frame != nullptr; } -void CameraWidget::vipcThread() { - VisionStreamType cur_stream = requested_stream_type; - std::unique_ptr vipc_client; - VisionIpcBufExtra meta_main = {0}; - - while (!QThread::currentThread()->isInterruptionRequested()) { - if (!vipc_client || cur_stream != requested_stream_type) { - clearFrames(); - qDebug().nospace() << "connecting to stream " << requested_stream_type << ", was connected to " << cur_stream; - cur_stream = requested_stream_type; - vipc_client.reset(new VisionIpcClient(stream_name, cur_stream, false)); - } - active_stream_type = cur_stream; - - if (!vipc_client->connected) { - clearFrames(); - auto streams = VisionIpcClient::getAvailableStreams(stream_name, false); - if (streams.empty()) { - QThread::msleep(100); - continue; - } - emit vipcAvailableStreamsUpdated(streams); - - if (!vipc_client->connect(false)) { - QThread::msleep(100); - continue; - } - emit vipcThreadConnected(vipc_client.get()); - } - - if (VisionBuf *buf = vipc_client->recv(&meta_main, 1000)) { - { - std::lock_guard lk(frame_lock); - frames.push_back(std::make_pair(meta_main.frame_id, buf)); - while (frames.size() > FRAME_BUFFER_SIZE) { - frames.pop_front(); - } - } - emit vipcThreadFrameReceived(); - } else { - if (!isVisible()) { - vipc_client->connected = false; - } - } +void CameraWidget::disconnectVipc() { + recent_frames.clear(); + frame = nullptr; + frame_id = 0; + prev_frame_id = 0; + if (vipc_client) { + vipc_client->connected = false; } } -void CameraWidget::clearFrames() { - std::lock_guard lk(frame_lock); - frames.clear(); - available_streams.clear(); +// Cameraview + +CameraView::CameraView(const std::string &name, VisionStreamType stream_type, bool zoom, QWidget *parent) + : CameraWidget(name, stream_type, zoom, parent) { + timer = new QTimer(this); + timer->setInterval(1000.0 / UI_FREQ); + timer->callOnTimeout(this, [this]() { if (receiveFrame()) update(); }); +} + +void CameraView::showEvent(QShowEvent *event) { + timer->start(); +} + +void CameraView::hideEvent(QHideEvent *event) { + timer->stop(); + disconnectVipc(); } diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index c97038cf43..a862079f1a 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -11,7 +10,7 @@ #include #include #include -#include +#include #ifdef QCOM2 #define EGL_EGLEXT_PROTOTYPES @@ -33,31 +32,27 @@ class CameraWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: - using QOpenGLWidget::QOpenGLWidget; explicit CameraWidget(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr); ~CameraWidget(); + void disconnectVipc(); void setBackgroundColor(const QColor &color) { bg = color; } - void setFrameId(int frame_id) { draw_frame_id = frame_id; } void setStreamType(VisionStreamType type) { requested_stream_type = type; } - VisionStreamType getStreamType() { return active_stream_type; } - void stopVipcThread(); + inline VisionStreamType streamType() const { return requested_stream_type; } + inline const std::set &availableStreams() const { return available_streams; } + bool receiveFrame(uint64_t request_frame_id = 0); signals: + void vipcAvailableStreamsUpdated(); void clicked(); - void vipcThreadConnected(VisionIpcClient *); - void vipcThreadFrameReceived(); - void vipcAvailableStreamsUpdated(std::set); protected: void paintGL() override; void initializeGL() override; void resizeGL(int w, int h) override { updateFrameMat(); } - void showEvent(QShowEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); } virtual void updateFrameMat(); void updateCalibration(const mat3 &calib); - void vipcThread(); - void clearFrames(); + void vipcConnected(); int glWidth(); int glHeight(); @@ -73,14 +68,18 @@ protected: std::map egl_images; #endif + // vipc std::string stream_name; int stream_width = 0; int stream_height = 0; int stream_stride = 0; - std::atomic active_stream_type; - std::atomic requested_stream_type; + VisionStreamType requested_stream_type; std::set available_streams; - QThread *vipc_thread = nullptr; + std::unique_ptr vipc_client; + std::deque> recent_frames; + VisionBuf *frame = nullptr; + uint64_t frame_id = 0; + uint64_t prev_frame_id = 0; // Calibration float x_offset = 0; @@ -88,16 +87,16 @@ protected: float zoom = 1.0; mat3 calibration = DEFAULT_CALIBRATION; mat3 intrinsic_matrix = FCAM_INTRINSIC_MATRIX; - - std::recursive_mutex frame_lock; - std::deque> frames; - uint32_t draw_frame_id = 0; - uint32_t prev_frame_id = 0; - -protected slots: - void vipcConnected(VisionIpcClient *vipc_client); - void vipcFrameReceived(); - void availableStreamsUpdated(std::set streams); }; -Q_DECLARE_METATYPE(std::set); +// update frames based on timer +class CameraView : public CameraWidget { + Q_OBJECT +public: + CameraView(const std::string &name, VisionStreamType stream_type, bool zoom, QWidget *parent = nullptr); + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; + +private: + QTimer *timer; +}; diff --git a/selfdrive/ui/watch3.cc b/selfdrive/ui/watch3.cc index ec35c29b6b..cb12f82ea8 100644 --- a/selfdrive/ui/watch3.cc +++ b/selfdrive/ui/watch3.cc @@ -19,15 +19,15 @@ int main(int argc, char *argv[]) { { QHBoxLayout *hlayout = new QHBoxLayout(); layout->addLayout(hlayout); - hlayout->addWidget(new CameraWidget("navd", VISION_STREAM_MAP, false)); - hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_ROAD, false)); + hlayout->addWidget(new CameraView("navd", VISION_STREAM_MAP, false)); + hlayout->addWidget(new CameraView("camerad", VISION_STREAM_ROAD, false)); } { QHBoxLayout *hlayout = new QHBoxLayout(); layout->addLayout(hlayout); - hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_DRIVER, false)); - hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_WIDE_ROAD, false)); + hlayout->addWidget(new CameraView("camerad", VISION_STREAM_DRIVER, false)); + hlayout->addWidget(new CameraView("camerad", VISION_STREAM_WIDE_ROAD, false)); } return a.exec(); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index a6fd0b2b64..654c34ed37 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -139,7 +139,7 @@ QWidget *VideoWidget::createCameraWidget() { QStackedLayout *stacked = new QStackedLayout(); stacked->setStackingMode(QStackedLayout::StackAll); - stacked->addWidget(cam_widget = new CameraWidget("camerad", VISION_STREAM_ROAD, false)); + stacked->addWidget(cam_widget = new CameraView("camerad", VISION_STREAM_ROAD, false)); cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT); cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); stacked->addWidget(alert_label = new InfoLabel(this)); @@ -160,12 +160,13 @@ QWidget *VideoWidget::createCameraWidget() { return w; } -void VideoWidget::vipcAvailableStreamsUpdated(std::set streams) { +void VideoWidget::vipcAvailableStreamsUpdated() { static const QString stream_names[] = { [VISION_STREAM_ROAD] = "Road camera", [VISION_STREAM_WIDE_ROAD] = "Wide road camera", [VISION_STREAM_DRIVER] = "Driver camera"}; + const auto &streams = cam_widget->availableStreams(); for (int i = 0; i < streams.size(); ++i) { if (camera_tab->count() <= i) { camera_tab->addTab(QString()); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index b2039e09a4..ae095fd559 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -73,9 +73,9 @@ protected: QWidget *createCameraWidget(); QHBoxLayout *createPlaybackController(); void loopPlaybackClicked(); - void vipcAvailableStreamsUpdated(std::set streams); + void vipcAvailableStreamsUpdated(); - CameraWidget *cam_widget; + CameraView *cam_widget; double maximum_time = 0; QToolButton *time_btn = nullptr; ToolButton *seek_backward_btn = nullptr; From 318d1204e5dc74fd990dd6dfc10c73750e8cbae0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 3 Jan 2024 14:11:11 -0800 Subject: [PATCH 369/724] CI: bump other lewagon/wait-on-check-action --- .github/workflows/prebuilt.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index f5b0b8c558..58095cf19c 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -18,7 +18,7 @@ jobs: steps: - name: Wait for green check mark if: ${{ github.event_name != 'workflow_dispatch' }} - uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd + uses: lewagon/wait-on-check-action@595dabb3acf442d47e29c9ec9ba44db0c6bdd18f with: ref: master wait-interval: 30 From 4430944b2966d0bcdf54357dfdfafacf24e6de32 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 3 Jan 2024 19:55:16 -0500 Subject: [PATCH 370/724] jenkins: lock "pc" label to limit concurrent workers (#30902) * lock pc label * remove that --- Jenkinsfile | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5c872664a2..0ae5f5076d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -110,18 +110,20 @@ def pcStage(String stageName, Closure body) { return docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .") } - openpilot_base.inside(dockerArgs) { - timeout(time: 20, unit: 'MINUTES') { - try { - retryWithDelay (3, 15) { - sh "git config --global --add safe.directory '*'" - sh "git submodule update --init --recursive" - sh "git lfs pull" - } - body() - } finally { - sh "rm -rf ${env.WORKSPACE}/* || true" - sh "rm -rf .* || true" + lock(resource: "", label: 'pc', inversePrecedence: true, quantity: 1) { + openpilot_base.inside(dockerArgs) { + timeout(time: 20, unit: 'MINUTES') { + try { + retryWithDelay (3, 15) { + sh "git config --global --add safe.directory '*'" + sh "git submodule update --init --recursive" + sh "git lfs pull" + } + body() + } finally { + sh "rm -rf ${env.WORKSPACE}/* || true" + sh "rm -rf .* || true" + } } } } From 115047d85860806958f50e241155d5f2bde5fa53 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 3 Jan 2024 17:53:40 -0800 Subject: [PATCH 371/724] timezoned: run while onroad (#30903) --- system/timezoned.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/system/timezoned.py b/system/timezoned.py index 546405c9a0..4a881c53a3 100755 --- a/system/timezoned.py +++ b/system/timezoned.py @@ -44,10 +44,6 @@ def main() -> NoReturn: while True: time.sleep(60) - is_onroad = not params.get_bool("IsOffroad") - if is_onroad: - continue - # Set based on param timezone = params.get("Timezone", encoding='utf8') if timezone is not None: From 28cf3155e21da663040040076f9fb9e746fd6834 Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Thu, 4 Jan 2024 04:34:13 +0100 Subject: [PATCH 372/724] [HKG-CAN] Consider FCW alerts from SCC (#30885) * FCW is triggered differently when SCC is active * Update selfdrive/car/hyundai/carstate.py * Update selfdrive/car/hyundai/carstate.py * cmt --------- Co-authored-by: Shane Smiskol --- selfdrive/car/hyundai/carstate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index edc9e4855f..2f1fb10804 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -147,8 +147,9 @@ class CarState(CarStateBase): aeb_src = "FCA11" if self.CP.flags & HyundaiFlags.USE_FCA.value else "SCC12" aeb_sig = "FCA_CmdAct" if self.CP.flags & HyundaiFlags.USE_FCA.value else "AEB_CmdAct" aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0 + scc_warning = cp_cruise.vl["SCC12"]["TakeOverReq"] == 1 # sometimes only SCC system shows an FCW aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0 - ret.stockFcw = aeb_warning and not aeb_braking + ret.stockFcw = (aeb_warning or scc_warning) and not aeb_braking ret.stockAeb = aeb_warning and aeb_braking if self.CP.enableBsm: From ff0fed07ed92c7e4b2f3d348a769058652351c87 Mon Sep 17 00:00:00 2001 From: duetschlandftw <90730497+duetschlandftw@users.noreply.github.com> Date: Wed, 3 Jan 2024 22:43:59 -0500 Subject: [PATCH 373/724] Fingerprint for '21 Explorer ST (#30877) * carparams gave me a new firmware entry for "shiftByWire" from 'ford', so i added it here, as well as a new fw value under ecs.engine * Update selfdrive/car/ford/fingerprints.py --------- Co-authored-by: Shane Smiskol --- selfdrive/car/ford/fingerprints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py index 8594ab95df..4a82a5439e 100644 --- a/selfdrive/car/ford/fingerprints.py +++ b/selfdrive/car/ford/fingerprints.py @@ -92,6 +92,7 @@ FW_VERSIONS = { b'LB5A-14C204-AZL\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-BUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MB5A-14C204-LE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', From fe4090584923b559ef95131319a0ef77cfa1938a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 3 Jan 2024 20:07:58 -0800 Subject: [PATCH 374/724] Revert "Fingerprint for '21 Explorer ST (#30877)" This reverts commit ff0fed07ed92c7e4b2f3d348a769058652351c87. --- selfdrive/car/ford/fingerprints.py | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py index 4a82a5439e..8594ab95df 100644 --- a/selfdrive/car/ford/fingerprints.py +++ b/selfdrive/car/ford/fingerprints.py @@ -92,7 +92,6 @@ FW_VERSIONS = { b'LB5A-14C204-AZL\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-BUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MB5A-14C204-LE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', From 3c4150c542c6117292f29f2fa4906b0090d8e9b9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 3 Jan 2024 20:18:29 -0800 Subject: [PATCH 375/724] timezoned: set only from gps (#30904) * timezoned: set only from gps * unused --- system/timezoned.py | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/system/timezoned.py b/system/timezoned.py index 4a881c53a3..2cfc0076e9 100755 --- a/system/timezoned.py +++ b/system/timezoned.py @@ -5,7 +5,6 @@ import time import subprocess from typing import NoReturn -import requests from timezonefinder import TimezoneFinder from openpilot.common.params import Params @@ -41,35 +40,18 @@ def main() -> NoReturn: # Get allowed timezones valid_timezones = subprocess.check_output('timedatectl list-timezones', shell=True, encoding='utf8').strip().split('\n') + timezone = params.get("Timezone", encoding='utf8') + if timezone is not None: + cloudlog.debug("Setting timezone based on param") + set_timezone(valid_timezones, timezone) + while True: time.sleep(60) - # Set based on param - timezone = params.get("Timezone", encoding='utf8') - if timezone is not None: - cloudlog.debug("Setting timezone based on param") - set_timezone(valid_timezones, timezone) - continue - location = params.get("LastGPSPosition", encoding='utf8') - # Find timezone based on IP geolocation if no gps location is available - if location is None: - cloudlog.debug("Setting timezone based on IP lookup") - try: - r = requests.get("https://ipapi.co/timezone", headers=REQUEST_HEADERS, timeout=10) - if r.status_code == 200: - set_timezone(valid_timezones, r.text) - else: - cloudlog.error(f"Unexpected status code from api {r.status_code}") - - time.sleep(3600) # Don't make too many API requests - except requests.exceptions.RequestException: - cloudlog.exception("Error getting timezone based on IP") - continue - # Find timezone by reverse geocoding the last known gps location - else: + if location is not None: cloudlog.debug("Setting timezone based on GPS location") try: location = json.loads(location) From e85e401d073c3f64b882d52757d9653f22adfba2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Jan 2024 00:50:30 -0800 Subject: [PATCH 376/724] Fingerprints: add missing FW versions from users --- selfdrive/car/honda/fingerprints.py | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 1667b46188..237ec18313 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -218,6 +218,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TWA-A070\x00\x00', b'36802-TWA-A080\x00\x00', + b'36802-TWA-A210\x00\x00', b'36802-TWA-A330\x00\x00', b'36802-TWB-H060\x00\x00', ], @@ -253,6 +254,7 @@ FW_VERSIONS = { b'37805-5BA-A760\x00\x00', b'37805-5BA-A930\x00\x00', b'37805-5BA-A960\x00\x00', + b'37805-5BA-C640\x00\x00', b'37805-5BA-C860\x00\x00', b'37805-5BA-L410\x00\x00', b'37805-5BA-L760\x00\x00', @@ -296,6 +298,7 @@ FW_VERSIONS = { b'78109-TBA-A510\x00\x00', b'78109-TBA-A520\x00\x00', b'78109-TBA-A530\x00\x00', + b'78109-TBA-C310\x00\x00', b'78109-TBA-C520\x00\x00', b'78109-TBC-A310\x00\x00', b'78109-TBC-A320\x00\x00', @@ -574,6 +577,7 @@ FW_VERSIONS = { b'37805-5PA-6630\x00\x00', b'37805-5PA-6640\x00\x00', b'37805-5PA-7630\x00\x00', + b'37805-5PA-9530\x00\x00', b'37805-5PA-9630\x00\x00', b'37805-5PA-9640\x00\x00', b'37805-5PA-9730\x00\x00', @@ -590,6 +594,7 @@ FW_VERSIONS = { b'37805-5PA-AD10\x00\x00', b'37805-5PA-AF20\x00\x00', b'37805-5PA-AH20\x00\x00', + b'37805-5PA-BF10\x00\x00', b'37805-5PA-C680\x00\x00', b'37805-5PD-Q630\x00\x00', b'37805-5PF-F730\x00\x00', @@ -634,6 +639,7 @@ FW_VERSIONS = { b'46114-TMC-U020\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TLA-A020\x00\x00', b'78109-TLA-A110\x00\x00', b'78109-TLA-A120\x00\x00', b'78109-TLA-A210\x00\x00', @@ -816,6 +822,7 @@ FW_VERSIONS = { ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-5MR-4080\x00\x00', + b'37805-5MR-4180\x00\x00', b'37805-5MR-A240\x00\x00', b'37805-5MR-A250\x00\x00', b'37805-5MR-A310\x00\x00', @@ -854,6 +861,7 @@ FW_VERSIONS = { b'28102-5MX-A001\x00\x00', b'28102-5MX-A600\x00\x00', b'28102-5MX-A610\x00\x00', + b'28102-5MX-A700\x00\x00', b'28102-5MX-A710\x00\x00', b'28102-5MX-A900\x00\x00', b'28102-5MX-A910\x00\x00', @@ -874,6 +882,7 @@ FW_VERSIONS = { b'78109-THR-A420\x00\x00', b'78109-THR-A430\x00\x00', b'78109-THR-A720\x00\x00', + b'78109-THR-A730\x00\x00', b'78109-THR-A820\x00\x00', b'78109-THR-A830\x00\x00', b'78109-THR-AB20\x00\x00', @@ -891,6 +900,7 @@ FW_VERSIONS = { b'78109-THR-AL10\x00\x00', b'78109-THR-AN10\x00\x00', b'78109-THR-C220\x00\x00', + b'78109-THR-C320\x00\x00', b'78109-THR-C330\x00\x00', b'78109-THR-CE20\x00\x00', b'78109-THR-DA20\x00\x00', @@ -927,11 +937,14 @@ FW_VERSIONS = { (Ecu.transmission, 0x18da1ef1, None): [ b'28101-5EY-A050\x00\x00', b'28101-5EY-A100\x00\x00', + b'28101-5EY-A500\x00\x00', b'28101-5EZ-A050\x00\x00', b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A100\x00\x00', b'28101-5EZ-A210\x00\x00', + b'28101-5EZ-A330\x00\x00', b'28101-5EZ-A430\x00\x00', + b'28101-5EZ-A500\x00\x00', b'28101-5EZ-A600\x00\x00', b'28101-5EZ-A700\x00\x00', ], @@ -943,13 +956,23 @@ FW_VERSIONS = { b'37805-RLV-B210\x00\x00', b'37805-RLV-B220\x00\x00', b'37805-RLV-B420\x00\x00', + b'37805-RLV-B430\x00\x00', + b'37805-RLV-B620\x00\x00', + b'37805-RLV-B710\x00\x00', + b'37805-RLV-B720\x00\x00', b'37805-RLV-C430\x00\x00', b'37805-RLV-C510\x00\x00', b'37805-RLV-C520\x00\x00', b'37805-RLV-C530\x00\x00', b'37805-RLV-C910\x00\x00', b'37805-RLV-F120\x00\x00', + b'37805-RLV-L080\x00\x00', + b'37805-RLV-L090\x00\x00', b'37805-RLV-L160\x00\x00', + b'37805-RLV-L180\x00\x00', + b'37805-RLV-L410\x00\x00', + b'37805-RLV-L430\x00\x00', + b'37805-RLV-L850\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TG7-A030\x00\x00', @@ -976,6 +999,7 @@ FW_VERSIONS = { b'36161-TG7-D520\x00\x00', b'36161-TG7-D630\x00\x00', b'36161-TG7-Y630\x00\x00', + b'36161-TG8-A410\x00\x00', b'36161-TG8-A520\x00\x00', b'36161-TG8-A630\x00\x00', b'36161-TG8-A720\x00\x00', @@ -983,6 +1007,7 @@ FW_VERSIONS = { b'36161-TGS-A030\x00\x00', b'36161-TGS-A130\x00\x00', b'36161-TGS-A220\x00\x00', + b'36161-TGS-A320\x00\x00', b'36161-TGT-A030\x00\x00', b'36161-TGT-A130\x00\x00', ], @@ -1019,7 +1044,10 @@ FW_VERSIONS = { b'78109-TG8-AJ10\x00\x00', b'78109-TG8-AJ20\x00\x00', b'78109-TG8-AK20\x00\x00', + b'78109-TG8-AS20\x00\x00', + b'78109-TGS-AB10\x00\x00', b'78109-TGS-AC10\x00\x00', + b'78109-TGS-AD10\x00\x00', b'78109-TGS-AJ20\x00\x00', b'78109-TGS-AK20\x00\x00', b'78109-TGS-AP20\x00\x00', @@ -1072,13 +1100,17 @@ FW_VERSIONS = { b'37805-5YF-A420\x00\x00', b'37805-5YF-A430\x00\x00', b'37805-5YF-A750\x00\x00', + b'37805-5YF-A760\x00\x00', b'37805-5YF-A850\x00\x00', + b'37805-5YF-A860\x00\x00', b'37805-5YF-A870\x00\x00', b'37805-5YF-AD20\x00\x00', b'37805-5YF-C210\x00\x00', b'37805-5YF-C220\x00\x00', + b'37805-5YF-C230\x00\x00', b'37805-5YF-C410\x00\x00', b'37805-5YF-C420\x00\x00', + b'37805-5YF-C430\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TJB-A030\x00\x00', @@ -1113,6 +1145,7 @@ FW_VERSIONS = { b'78109-TJB-A140\x00\x00', b'78109-TJB-A240\x00\x00', b'78109-TJB-A420\x00\x00', + b'78109-TJB-A440\x00\x00', b'78109-TJB-AB10\x00\x00', b'78109-TJB-AD10\x00\x00', b'78109-TJB-AF10\x00\x00', @@ -1121,6 +1154,7 @@ FW_VERSIONS = { b'78109-TJB-AS10\x00\x00', b'78109-TJB-AU10\x00\x00', b'78109-TJB-AW10\x00\x00', + b'78109-TJC-A240\x00\x00', b'78109-TJC-A420\x00\x00', b'78109-TJC-AA10\x00\x00', b'78109-TJC-AD10\x00\x00', @@ -1340,12 +1374,14 @@ FW_VERSIONS = { b'77959-T47-A950\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78108-T20-A220\x00\x00', b'78108-T21-A220\x00\x00', b'78108-T21-A230\x00\x00', b'78108-T21-A620\x00\x00', b'78108-T21-A740\x00\x00', b'78108-T21-MB10\x00\x00', b'78108-T22-A020\x00\x00', + b'78108-T22-A030\x00\x00', b'78108-T23-A110\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ @@ -1354,6 +1390,8 @@ FW_VERSIONS = { b'36161-T20-A080\x00\x00', b'36161-T24-T070\x00\x00', b'36161-T47-A070\x00\x00', + b'8S102-T20-AA10\x00\x00', + b'8S102-T47-AA10\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-T20-AB40\x00\x00', @@ -1370,6 +1408,7 @@ FW_VERSIONS = { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-64A-A540\x00\x00', b'37805-64A-A620\x00\x00', + b'37805-64A-AD10\x00\x00', b'37805-64D-P510\x00\x00', b'37805-64L-A540\x00\x00', b'37805-64S-A540\x00\x00', From 43b0b0f022b8e409b399110f95c3ba34480b5235 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Jan 2024 00:51:34 -0800 Subject: [PATCH 377/724] Revert "Fingerprints: add missing FW versions from users" This reverts commit e85e401d073c3f64b882d52757d9653f22adfba2. Meant for another branch. --- selfdrive/car/honda/fingerprints.py | 39 ----------------------------- 1 file changed, 39 deletions(-) diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 237ec18313..1667b46188 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -218,7 +218,6 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TWA-A070\x00\x00', b'36802-TWA-A080\x00\x00', - b'36802-TWA-A210\x00\x00', b'36802-TWA-A330\x00\x00', b'36802-TWB-H060\x00\x00', ], @@ -254,7 +253,6 @@ FW_VERSIONS = { b'37805-5BA-A760\x00\x00', b'37805-5BA-A930\x00\x00', b'37805-5BA-A960\x00\x00', - b'37805-5BA-C640\x00\x00', b'37805-5BA-C860\x00\x00', b'37805-5BA-L410\x00\x00', b'37805-5BA-L760\x00\x00', @@ -298,7 +296,6 @@ FW_VERSIONS = { b'78109-TBA-A510\x00\x00', b'78109-TBA-A520\x00\x00', b'78109-TBA-A530\x00\x00', - b'78109-TBA-C310\x00\x00', b'78109-TBA-C520\x00\x00', b'78109-TBC-A310\x00\x00', b'78109-TBC-A320\x00\x00', @@ -577,7 +574,6 @@ FW_VERSIONS = { b'37805-5PA-6630\x00\x00', b'37805-5PA-6640\x00\x00', b'37805-5PA-7630\x00\x00', - b'37805-5PA-9530\x00\x00', b'37805-5PA-9630\x00\x00', b'37805-5PA-9640\x00\x00', b'37805-5PA-9730\x00\x00', @@ -594,7 +590,6 @@ FW_VERSIONS = { b'37805-5PA-AD10\x00\x00', b'37805-5PA-AF20\x00\x00', b'37805-5PA-AH20\x00\x00', - b'37805-5PA-BF10\x00\x00', b'37805-5PA-C680\x00\x00', b'37805-5PD-Q630\x00\x00', b'37805-5PF-F730\x00\x00', @@ -639,7 +634,6 @@ FW_VERSIONS = { b'46114-TMC-U020\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TLA-A020\x00\x00', b'78109-TLA-A110\x00\x00', b'78109-TLA-A120\x00\x00', b'78109-TLA-A210\x00\x00', @@ -822,7 +816,6 @@ FW_VERSIONS = { ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-5MR-4080\x00\x00', - b'37805-5MR-4180\x00\x00', b'37805-5MR-A240\x00\x00', b'37805-5MR-A250\x00\x00', b'37805-5MR-A310\x00\x00', @@ -861,7 +854,6 @@ FW_VERSIONS = { b'28102-5MX-A001\x00\x00', b'28102-5MX-A600\x00\x00', b'28102-5MX-A610\x00\x00', - b'28102-5MX-A700\x00\x00', b'28102-5MX-A710\x00\x00', b'28102-5MX-A900\x00\x00', b'28102-5MX-A910\x00\x00', @@ -882,7 +874,6 @@ FW_VERSIONS = { b'78109-THR-A420\x00\x00', b'78109-THR-A430\x00\x00', b'78109-THR-A720\x00\x00', - b'78109-THR-A730\x00\x00', b'78109-THR-A820\x00\x00', b'78109-THR-A830\x00\x00', b'78109-THR-AB20\x00\x00', @@ -900,7 +891,6 @@ FW_VERSIONS = { b'78109-THR-AL10\x00\x00', b'78109-THR-AN10\x00\x00', b'78109-THR-C220\x00\x00', - b'78109-THR-C320\x00\x00', b'78109-THR-C330\x00\x00', b'78109-THR-CE20\x00\x00', b'78109-THR-DA20\x00\x00', @@ -937,14 +927,11 @@ FW_VERSIONS = { (Ecu.transmission, 0x18da1ef1, None): [ b'28101-5EY-A050\x00\x00', b'28101-5EY-A100\x00\x00', - b'28101-5EY-A500\x00\x00', b'28101-5EZ-A050\x00\x00', b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A100\x00\x00', b'28101-5EZ-A210\x00\x00', - b'28101-5EZ-A330\x00\x00', b'28101-5EZ-A430\x00\x00', - b'28101-5EZ-A500\x00\x00', b'28101-5EZ-A600\x00\x00', b'28101-5EZ-A700\x00\x00', ], @@ -956,23 +943,13 @@ FW_VERSIONS = { b'37805-RLV-B210\x00\x00', b'37805-RLV-B220\x00\x00', b'37805-RLV-B420\x00\x00', - b'37805-RLV-B430\x00\x00', - b'37805-RLV-B620\x00\x00', - b'37805-RLV-B710\x00\x00', - b'37805-RLV-B720\x00\x00', b'37805-RLV-C430\x00\x00', b'37805-RLV-C510\x00\x00', b'37805-RLV-C520\x00\x00', b'37805-RLV-C530\x00\x00', b'37805-RLV-C910\x00\x00', b'37805-RLV-F120\x00\x00', - b'37805-RLV-L080\x00\x00', - b'37805-RLV-L090\x00\x00', b'37805-RLV-L160\x00\x00', - b'37805-RLV-L180\x00\x00', - b'37805-RLV-L410\x00\x00', - b'37805-RLV-L430\x00\x00', - b'37805-RLV-L850\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TG7-A030\x00\x00', @@ -999,7 +976,6 @@ FW_VERSIONS = { b'36161-TG7-D520\x00\x00', b'36161-TG7-D630\x00\x00', b'36161-TG7-Y630\x00\x00', - b'36161-TG8-A410\x00\x00', b'36161-TG8-A520\x00\x00', b'36161-TG8-A630\x00\x00', b'36161-TG8-A720\x00\x00', @@ -1007,7 +983,6 @@ FW_VERSIONS = { b'36161-TGS-A030\x00\x00', b'36161-TGS-A130\x00\x00', b'36161-TGS-A220\x00\x00', - b'36161-TGS-A320\x00\x00', b'36161-TGT-A030\x00\x00', b'36161-TGT-A130\x00\x00', ], @@ -1044,10 +1019,7 @@ FW_VERSIONS = { b'78109-TG8-AJ10\x00\x00', b'78109-TG8-AJ20\x00\x00', b'78109-TG8-AK20\x00\x00', - b'78109-TG8-AS20\x00\x00', - b'78109-TGS-AB10\x00\x00', b'78109-TGS-AC10\x00\x00', - b'78109-TGS-AD10\x00\x00', b'78109-TGS-AJ20\x00\x00', b'78109-TGS-AK20\x00\x00', b'78109-TGS-AP20\x00\x00', @@ -1100,17 +1072,13 @@ FW_VERSIONS = { b'37805-5YF-A420\x00\x00', b'37805-5YF-A430\x00\x00', b'37805-5YF-A750\x00\x00', - b'37805-5YF-A760\x00\x00', b'37805-5YF-A850\x00\x00', - b'37805-5YF-A860\x00\x00', b'37805-5YF-A870\x00\x00', b'37805-5YF-AD20\x00\x00', b'37805-5YF-C210\x00\x00', b'37805-5YF-C220\x00\x00', - b'37805-5YF-C230\x00\x00', b'37805-5YF-C410\x00\x00', b'37805-5YF-C420\x00\x00', - b'37805-5YF-C430\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TJB-A030\x00\x00', @@ -1145,7 +1113,6 @@ FW_VERSIONS = { b'78109-TJB-A140\x00\x00', b'78109-TJB-A240\x00\x00', b'78109-TJB-A420\x00\x00', - b'78109-TJB-A440\x00\x00', b'78109-TJB-AB10\x00\x00', b'78109-TJB-AD10\x00\x00', b'78109-TJB-AF10\x00\x00', @@ -1154,7 +1121,6 @@ FW_VERSIONS = { b'78109-TJB-AS10\x00\x00', b'78109-TJB-AU10\x00\x00', b'78109-TJB-AW10\x00\x00', - b'78109-TJC-A240\x00\x00', b'78109-TJC-A420\x00\x00', b'78109-TJC-AA10\x00\x00', b'78109-TJC-AD10\x00\x00', @@ -1374,14 +1340,12 @@ FW_VERSIONS = { b'77959-T47-A950\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78108-T20-A220\x00\x00', b'78108-T21-A220\x00\x00', b'78108-T21-A230\x00\x00', b'78108-T21-A620\x00\x00', b'78108-T21-A740\x00\x00', b'78108-T21-MB10\x00\x00', b'78108-T22-A020\x00\x00', - b'78108-T22-A030\x00\x00', b'78108-T23-A110\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ @@ -1390,8 +1354,6 @@ FW_VERSIONS = { b'36161-T20-A080\x00\x00', b'36161-T24-T070\x00\x00', b'36161-T47-A070\x00\x00', - b'8S102-T20-AA10\x00\x00', - b'8S102-T47-AA10\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-T20-AB40\x00\x00', @@ -1408,7 +1370,6 @@ FW_VERSIONS = { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-64A-A540\x00\x00', b'37805-64A-A620\x00\x00', - b'37805-64A-AD10\x00\x00', b'37805-64D-P510\x00\x00', b'37805-64L-A540\x00\x00', b'37805-64S-A540\x00\x00', From 63f0d775bc938ba38dbc7f42957fc2a80094686c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Jan 2024 16:54:50 -0700 Subject: [PATCH 378/724] Fingerprints: add missing FW versions from users (#30906) Export fingerprints from Fingerprint stage --- selfdrive/car/toyota/fingerprints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 62c4ddf3ce..f162d287f4 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -145,6 +145,7 @@ FW_VERSIONS = { b'8821F0601200 ', b'8821F0601300 ', b'8821F0601400 ', + b'8821F0601500 ', b'8821F0602000 ', b'8821F0603300 ', b'8821F0603400 ', @@ -189,6 +190,7 @@ FW_VERSIONS = { b'8821F0601200 ', b'8821F0601300 ', b'8821F0601400 ', + b'8821F0601500 ', b'8821F0602000 ', b'8821F0603300 ', b'8821F0603400 ', From 1148c65d232fdfe7aa46b80c078721d78876da4a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 4 Jan 2024 20:52:07 -0500 Subject: [PATCH 379/724] add read_seglist helper (#30908) add helper --- selfdrive/car/tests/test_models.py | 10 ++++------ selfdrive/test/helpers.py | 7 +++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index cc52995a33..253841877f 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -21,6 +21,7 @@ from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute from openpilot.selfdrive.controls.controlsd import Controls +from openpilot.selfdrive.test.helpers import read_segment_list from openpilot.selfdrive.test.openpilotci import get_url from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import Route, SegmentName, RouteName @@ -52,12 +53,9 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: test_cases.extend(sorted((c.value, r) for r in routes_by_car.get(c, (None,)))) else: - with open(os.path.join(BASEDIR, INTERNAL_SEG_LIST), "r") as f: - seg_list = f.read().splitlines() - - seg_list_grouped = [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] - seg_list_grouped = random.sample(seg_list_grouped, INTERNAL_SEG_CNT or len(seg_list_grouped)) - for platform, segment in seg_list_grouped: + segment_list = read_segment_list(os.path.join(BASEDIR, INTERNAL_SEG_LIST)) + segment_list = random.sample(segment_list, INTERNAL_SEG_CNT or len(segment_list)) + for platform, segment in segment_list: segment_name = SegmentName(segment) test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform, segment=segment_name.segment_num))) diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 78b01c983f..134f1150b8 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -72,3 +72,10 @@ def with_processes(processes, init_time=0, ignore_stopped=None): def noop(*args, **kwargs): pass + + +def read_segment_list(segment_list_path): + with open(segment_list_path, "r") as f: + seg_list = f.read().splitlines() + + return [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] From 397c1e373c1eada0cb27bfb070812f0fa466bdcf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Jan 2024 19:53:23 -0800 Subject: [PATCH 380/724] selfdrive/test: fix indentation no ruff rule for this --- selfdrive/test/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 134f1150b8..6cba371fc2 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -76,6 +76,6 @@ def noop(*args, **kwargs): def read_segment_list(segment_list_path): with open(segment_list_path, "r") as f: - seg_list = f.read().splitlines() + seg_list = f.read().splitlines() return [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] From 002ab5637b7d8dc22ad2197f4d98544d0518b941 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 4 Jan 2024 23:16:39 -0700 Subject: [PATCH 381/724] Car docs: utilize post init (#30912) move these to earlier post_init --- selfdrive/car/docs_definitions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 2e80dde010..f2ce743607 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -244,10 +244,13 @@ class CarInfo: # all the parts needed for the supported car car_parts: CarParts = field(default_factory=CarParts) + def __post_init__(self): + self.make, self.model, self.years = split_name(self.name) + self.year_list = get_year_list(self.years) + def init(self, CP: car.CarParams, all_footnotes: Dict[Enum, int]): self.car_name = CP.carName self.car_fingerprint = CP.carFingerprint - self.make, self.model, self.years = split_name(self.name) # longitudinal column op_long = "Stock" @@ -309,7 +312,6 @@ class CarInfo: self.row[Column.STEERING_TORQUE] = Star.FULL self.all_footnotes = all_footnotes - self.year_list = get_year_list(self.years) self.detail_sentence = self.get_detail_sentence(CP) return self From 15e0a906ad70f50594c58a38fb4d2cb1a6c9c89a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 5 Jan 2024 14:52:24 -0500 Subject: [PATCH 382/724] tools: add car porting example notebook (#30918) * add car porting example notebooks * not required --- .../examples/subaru_steer_temp_fault.ipynb | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tools/car_porting/examples/subaru_steer_temp_fault.ipynb diff --git a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb new file mode 100644 index 0000000000..9dea025c62 --- /dev/null +++ b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb @@ -0,0 +1,114 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# An example of searching through the database of segments for a specific condition, and plotting the results.\n", + "\n", + "segments = [\n", + " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20--6\",\n", + " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20--7\",\n", + " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20--8\",\n", + "]\n", + "platform = \"SUBARU OUTBACK 6TH GEN\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from openpilot.tools.lib.logreader import logreader_from_route_or_segment\n", + "\n", + "\n", + "from selfdrive.car.subaru.values import CanBus, DBC\n", + "\n", + "from opendbc.can.parser import CANParser\n", + "\n", + "\"\"\"\n", + "In this example, we search for positive transitions of Steer_Warning, which indicate that the EPS\n", + "has stopped responding to our messages. This analysis would allow you to find the cause of these\n", + "steer warnings and potentially work around them.\n", + "\"\"\"\n", + "\n", + "for segment in segments:\n", + " print(segment)\n", + " lr = logreader_from_route_or_segment(segment)\n", + "\n", + " can_msgs = [msg for msg in lr if msg.which() == \"can\"]\n", + "\n", + " messages = [\n", + " (\"Steering_Torque\", 50)\n", + " ]\n", + "\n", + " cp = CANParser(DBC[platform][\"pt\"], messages, CanBus.main)\n", + "\n", + " steering_torque_history = []\n", + " examples = []\n", + "\n", + " for msg in can_msgs:\n", + " cp.update_strings([msg.as_builder().to_bytes()])\n", + " steering_torque_history.append(copy.copy(cp.vl[\"Steering_Torque\"]))\n", + " \n", + " steer_warning_last = False\n", + " for i, steering_torque_msg in enumerate(steering_torque_history):\n", + " steer_warning = steering_torque_msg[\"Steer_Warning\"]\n", + "\n", + " steer_angle = steering_torque_msg[\"Steering_Angle\"]\n", + "\n", + " if steer_warning and not steer_warning_last: # positive transition of \"Steer_Warning\"\n", + " examples.append(i)\n", + "\n", + " steer_warning_last = steer_warning\n", + "\n", + " FRAME_DELTA = 100 # plot this many frames around the positive transition\n", + "\n", + " for example in examples:\n", + " fig, axs = plt.subplots(2)\n", + "\n", + " min_frame = int(example-FRAME_DELTA/2)\n", + " max_frame = int(example+FRAME_DELTA/2)\n", + "\n", + " steering_angle_history = [msg[\"Steering_Angle\"] for msg in steering_torque_history[min_frame:max_frame]]\n", + " steering_warning_history = [msg[\"Steer_Warning\"] for msg in steering_torque_history[min_frame:max_frame]]\n", + "\n", + " xs = np.arange(-FRAME_DELTA/2, FRAME_DELTA/2)\n", + "\n", + " axs[0].plot(xs, steering_angle_history)\n", + " axs[0].set_ylabel(\"Steering Angle (deg)\")\n", + " axs[1].plot(xs, steering_warning_history)\n", + " axs[1].set_ylabel(\"Steer Warning\")\n", + "\n", + " plt.show()\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From e86d74b347f60a3e575d45b4c99d94d23c8c7d84 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 5 Jan 2024 14:53:03 -0500 Subject: [PATCH 383/724] move car porting tools to tools/car_porting (#30916) just this for now --- selfdrive/car/README.md | 60 ------------------- tools/car_porting/README.md | 58 ++++++++++++++++++ .../car_porting}/auto_fingerprint.py | 0 .../car_porting}/test_car_model.py | 0 4 files changed, 58 insertions(+), 60 deletions(-) create mode 100644 tools/car_porting/README.md rename {selfdrive/debug => tools/car_porting}/auto_fingerprint.py (100%) rename {selfdrive/debug => tools/car_porting}/test_car_model.py (100%) diff --git a/selfdrive/car/README.md b/selfdrive/car/README.md index f77bea205b..2c49cf2051 100644 --- a/selfdrive/car/README.md +++ b/selfdrive/car/README.md @@ -1,63 +1,3 @@ -# selfdrive/car - -Check out [this blog post](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/) for a high-level overview of porting a car. - -## Useful car porting utilities - -Testing car ports in your car is very time-consuming. Check out these utilities to do basic checks on your work before running it in your car. - -### [Cabana](/tools/cabana/README.md) - -View your car's CAN signals through DBC files, which openpilot uses to parse and create messages that talk to the car. - -Example: -```bash -> tools/cabana/cabana '1bbe6bf2d62f58a8|2022-07-14--17-11-43' -``` - -### [selfdrive/debug/auto_fingerprint.py](/selfdrive/debug/auto_fingerprint.py) - -Given a route and platform, automatically inserts FW fingerprints from the platform into the correct place in values.py - -Example: -```bash -> python selfdrive/debug/auto_fingerprint.py '1bbe6bf2d62f58a8|2022-07-14--17-11-43' 'SUBARU OUTBACK 6TH GEN' -Attempting to add fw version for: SUBARU OUTBACK 6TH GEN -``` - -### [selfdrive/car/tests/test_car_interfaces.py](/selfdrive/car/tests/test_car_interfaces.py) - -Finds common bugs for car interfaces, without even requiring a route. - - -#### Example: Typo in signal name -```bash -> pytest selfdrive/car/tests/test_car_interfaces.py -k subaru # replace with the brand you are working on - -===================================================================== -FAILED selfdrive/car/tests/test_car_interfaces.py::TestCarInterfaces::test_car_interfaces_165_SUBARU_LEGACY_7TH_GEN - KeyError: 'CruiseControlOOPS' - -``` - -### [selfdrive/debug/test_car_model.py](/selfdrive/debug/test_car_model.py) - -Given a route, runs most of the car interface to check for common errors like missing signals, blocked panda messages, and safety mismatches. - -#### Example: panda safety mismatch for gasPressed -```bash -> python selfdrive/debug/test_car_model.py '4822a427b188122a|2023-08-14--16-22-21' - -===================================================================== -FAIL: test_panda_safety_carstate (__main__.CarModelTestCase.test_panda_safety_carstate) -Assert that panda safety matches openpilot's carState ----------------------------------------------------------------------- -Traceback (most recent call last): - File "/home/batman/xx/openpilot/openpilot/selfdrive/car/tests/test_models.py", line 380, in test_panda_safety_carstate - self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") -AssertionError: 1 is not false : panda safety doesn't agree with openpilot: {'gasPressed': 116} -``` - - ## Car port structure ### interface.py diff --git a/tools/car_porting/README.md b/tools/car_porting/README.md new file mode 100644 index 0000000000..14e6c71e58 --- /dev/null +++ b/tools/car_porting/README.md @@ -0,0 +1,58 @@ +# tools/car_porting + +Check out [this blog post](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/) for a high-level overview of porting a car. + +## Useful car porting utilities + +Testing car ports in your car is very time-consuming. Check out these utilities to do basic checks on your work before running it in your car. + +### [Cabana](/tools/cabana/README.md) + +View your car's CAN signals through DBC files, which openpilot uses to parse and create messages that talk to the car. + +Example: +```bash +> tools/cabana/cabana '1bbe6bf2d62f58a8|2022-07-14--17-11-43' +``` + +### [tools/car_porting/auto_fingerprint.py](/tools/car_porting/auto_fingerprint.py) + +Given a route and platform, automatically inserts FW fingerprints from the platform into the correct place in fingerprints.py + +Example: +```bash +> python tools/car_porting/auto_fingerprint.py '1bbe6bf2d62f58a8|2022-07-14--17-11-43' 'SUBARU OUTBACK 6TH GEN' +Attempting to add fw version for: SUBARU OUTBACK 6TH GEN +``` + +### [selfdrive/car/tests/test_car_interfaces.py](/selfdrive/car/tests/test_car_interfaces.py) + +Finds common bugs for car interfaces, without even requiring a route. + + +#### Example: Typo in signal name +```bash +> pytest selfdrive/car/tests/test_car_interfaces.py -k subaru # replace with the brand you are working on + +===================================================================== +FAILED selfdrive/car/tests/test_car_interfaces.py::TestCarInterfaces::test_car_interfaces_165_SUBARU_LEGACY_7TH_GEN - KeyError: 'CruiseControlOOPS' + +``` + +### [tools/car_porting/test_car_model.py](/tools/car_porting/test_car_model.py) + +Given a route, runs most of the car interface to check for common errors like missing signals, blocked panda messages, and safety mismatches. + +#### Example: panda safety mismatch for gasPressed +```bash +> python tools/car_porting/test_car_model.py '4822a427b188122a|2023-08-14--16-22-21' + +===================================================================== +FAIL: test_panda_safety_carstate (__main__.CarModelTestCase.test_panda_safety_carstate) +Assert that panda safety matches openpilot's carState +---------------------------------------------------------------------- +Traceback (most recent call last): + File "/home/batman/xx/openpilot/openpilot/selfdrive/car/tests/test_models.py", line 380, in test_panda_safety_carstate + self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") +AssertionError: 1 is not false : panda safety doesn't agree with openpilot: {'gasPressed': 116} +``` \ No newline at end of file diff --git a/selfdrive/debug/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py similarity index 100% rename from selfdrive/debug/auto_fingerprint.py rename to tools/car_porting/auto_fingerprint.py diff --git a/selfdrive/debug/test_car_model.py b/tools/car_porting/test_car_model.py similarity index 100% rename from selfdrive/debug/test_car_model.py rename to tools/car_porting/test_car_model.py From 239b228ca6987daa54c20090346d458c9bd4493d Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 5 Jan 2024 16:57:02 -0500 Subject: [PATCH 384/724] add the notebook example to car port docs (#30920) * add the notebook example * fix link * add image * no lfs * no lfs --- tools/car_porting/README.md | 10 +++++++++- .../car_porting/examples/subaru_steer_temp_fault.ipynb | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/car_porting/README.md b/tools/car_porting/README.md index 14e6c71e58..ca21cdd329 100644 --- a/tools/car_porting/README.md +++ b/tools/car_porting/README.md @@ -55,4 +55,12 @@ Traceback (most recent call last): File "/home/batman/xx/openpilot/openpilot/selfdrive/car/tests/test_models.py", line 380, in test_panda_safety_carstate self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") AssertionError: 1 is not false : panda safety doesn't agree with openpilot: {'gasPressed': 116} -``` \ No newline at end of file +``` + +### [tools/car_porting/examples/subaru_steer_temp_fault.ipynb](/tools/car_porting/examples/subaru_steer_temp_fault.ipynb) + +An example of searching through a database of segments for a specific condition, and plotting the results. + +![steer warning example](https://github.com/commaai/openpilot/assets/9648890/d60ad120-4b44-4974-ac79-adc660fb8fe2) + +*a plot of the steer_warning vs steering angle, where we can see it is clearly caused by a large steering angle change* diff --git a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb index 9dea025c62..a20d7d5062 100644 --- a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb +++ b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb @@ -6,7 +6,7 @@ "metadata": {}, "outputs": [], "source": [ - "# An example of searching through the database of segments for a specific condition, and plotting the results.\n", + "# An example of searching through a database of segments for a specific condition, and plotting the results.\n", "\n", "segments = [\n", " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20--6\",\n", From 46f3fdc090cef3b4b1b8bb4938ad68e1ca7cb280 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 5 Jan 2024 15:17:49 -0800 Subject: [PATCH 385/724] Update Python packages and pre-commit hooks (#30597) * Update Python packages and pre-commit hooks * fix --------- Co-authored-by: jnewb1 --- .pre-commit-config.yaml | 8 +- poetry.lock | 1701 ++++++++++++++-------------- selfdrive/athena/athenad.py | 11 +- tools/webcam/front_mount_helper.py | 35 - tools/webcam/warp_vis.py | 43 - 5 files changed, 871 insertions(+), 927 deletions(-) delete mode 100755 tools/webcam/front_mount_helper.py delete mode 100755 tools/webcam/warp_vis.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f1ba1ab97..d3e309eba9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,10 +39,12 @@ repos: entry: mypy language: system types: [python] - args: ['--explicit-package-bases', '--local-partial-types'] + args: + - --local-partial-types + - --explicit-package-bases exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.9 hooks: - id: ruff exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' @@ -87,7 +89,7 @@ repos: args: - --lock - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.2 + rev: 0.27.3 hooks: - id: check-github-workflows # - repo: local diff --git a/poetry.lock b/poetry.lock index 2731ff1c48..d7e512ef8a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -180,90 +180,113 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] +[[package]] +name = "anyio" +version = "4.2.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.8" +files = [ + {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, + {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, +] + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.23)"] + [[package]] name = "attrs" -version = "23.1.0" +version = "23.2.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, + {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, + {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, ] [package.extras] cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]", "pre-commit"] +dev = ["attrs[tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] +tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] [[package]] name = "av" -version = "10.0.0" +version = "11.0.0" description = "Pythonic bindings for FFmpeg's libraries." optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "av-10.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d19bb54197155d045a2b683d993026d4bcb06e31c2acad0327e3e8711571899c"}, - {file = "av-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dba96a85cd37315529998e6dbbe3fa05c2344eb19a431dc24996be030a904ee"}, - {file = "av-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27d6d38c7c8d46d578c008ffcb8aad1eae14d0621fff41f4ad62395589045fe4"}, - {file = "av-10.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51037f4bde03daf924236af4f444e17345792ad7f6f70760a5e5863407e14f2b"}, - {file = "av-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0577a38664e453b4ffb63d616a0d23c295827b16ae96a090e89527a753de8718"}, - {file = "av-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:07c971573035d22ce50069d3f2bbdb4d6d02d626ab13db12fda3ce519cda3f22"}, - {file = "av-10.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e5085d11345484c0097898994bb3f515002e7e1deeb43dd11d30dd6f45402c49"}, - {file = "av-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:157bde3ffd1615a9006b56e4daf3b46848d3ee2bd46b0394f7568e43ed7ab5a9"}, - {file = "av-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115e144d5a1f205378a4b3a3657b7ed3e45918ebe5d2003a891e45984e8f443a"}, - {file = "av-10.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7d6e2b3fbda6464f74fe010dbcff361394bb014b0cb4aa4dc9f2bb713ce882"}, - {file = "av-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fd5a38395191a0f4b71adf31057ff177c9f0762914d73d8797742339ad67d0"}, - {file = "av-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:836d69a9543d284976b229cc8d4343ffcfc0bbaf05239e13fb7e613b13d5291d"}, - {file = "av-10.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eba192274538617bbe60097a013d83637f1a5ba9844bbbcf3ca7e43c6499b9d5"}, - {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1301e4cf1a2c899851073720cd541066c8539b64f9eb0d52216f8d0a59f20429"}, - {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eebd5aa9d8b1e33e715c5409544a712f13ec805bb0110d75f394ff28d2fb64ad"}, - {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04cd0ce13a87870fb0a0ea4673f04934af2b9ac7ae844eafe92e2c19c092ab11"}, - {file = "av-10.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10facb5b933551dd6a30d8015bc91eef5d1c864ee86aa3463ffbaff1a99f6c6a"}, - {file = "av-10.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:088636ded03724a2ab51136f6f4be0bc457bdb3c0d2ac7158792fe81150d4c1a"}, - {file = "av-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff0f7d3b1003a9ed0d06038f3f521a5ff0d3e056ec5111e2a78e303f98b815a7"}, - {file = "av-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccaf786e747b126a5b3b9a8f5ffbb6a20c5f528775cc7084c95732ca72606fba"}, - {file = "av-10.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c579d718b52beb812ea2a7bd68f812d0920b00937804d52d31d41bb71aa5557"}, - {file = "av-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2cfd39baa5d82768d2a8898de7bfd450a083ef22b837d57e5dc1b6de3244218"}, - {file = "av-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:81b5264d9752f49286bc1dc4d2cc66187418c4948a326dbed837c766c9892139"}, - {file = "av-10.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:16bd82b63d0b4c1b855b3c36b13337f7cdc5925bd8284fab893bdf6c290fc3a9"}, - {file = "av-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a6c8f3f8c26d35eefe45b849c81fd0816ba4b6f589baec7357c25b4c5537d3c4"}, - {file = "av-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91ea46fea7259abdfabe00b0ed3a9ca18e7fff7ce80d2a2c66a28f797cce838a"}, - {file = "av-10.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a62edd533d330aa61902ae8cd82966affa487fa337a0c4f58ae8866ccb5d31c0"}, - {file = "av-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b67b7d028c9cf68215376662fd2e0be6ca0cc02d32d3ed8514fec67b12db9cbd"}, - {file = "av-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:0f9c88062ebfd2ce547c522b64f79e487ed2b0a6a9d6693c801b28df0d944607"}, - {file = "av-10.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:63dbafcd02415127d97509523bc285f1ab260988f87b744d7fb1baee6ffbdf96"}, - {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ea4424d0be62fe18c843420284a0907bcb38d577062d62c4b75a8e940e6057"}, - {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b6326fd0755761e3ee999e4bf90339e869fe71d548b679fee89157858b8d04a"}, - {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3fae238751ec0db6377b2106e13762ca84dbe104bd44c1ce9b424163aef4ab5"}, - {file = "av-10.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:86bb3f6e8cce62ad18cd34eb2eadd091d99f51b40be81c929b53fbd8fecf6d90"}, - {file = "av-10.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f7b508813abbc100162d305a1ac9b2dd16e5128d56f2ac69639fc6a4b5aca69e"}, - {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cc376199c0aa6e9365d03e0f4e67cfb209e40fe9c0cf566372f9daf2a0c779"}, - {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b459ca0ef25c1a0e370112556bdc5b7752f76dc9bd497acaf3e653171e4b946"}, - {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab930735112c1f788cc4d47c42c59ba0dd214d815aa906e1addf39af91d15194"}, - {file = "av-10.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13fe0b48b9211539323ecebbf84154c86c72d16723c6d0af76e29ae5c3a614b2"}, - {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2eeec7beaebfe9e2213b3c94b482381187d0afdcb632f93239b44dc668b97df"}, - {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dac2a8b0791c3373270e32f6cd27e6b60628565a188e40a5d9660d3aab05e33"}, - {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdede2325cb750b5bf79238bbf06f9c2a70b757b12726003769a43493b7233a"}, - {file = "av-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9788e6e15db0910fb8e1548ba7540799d07066177710590a5794a524c4910e05"}, - {file = "av-10.0.0.tar.gz", hash = "sha256:8afd3d5610e1086f3b2d8389d66672ea78624516912c93612de64dcaa4c67e05"}, + {file = "av-11.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a01f13b37eb6d181e03bbbbda29093fe2d68f10755795188220acdc89560ec27"}, + {file = "av-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b2236faee1b5d71dff3cdef81ef6eec22cc8b71dbfb45eb037e6437fe80f24e7"}, + {file = "av-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40543a08e5c84aecd2bc84da5d43548743201897f0ba21bf5ae3a4dcddefca2b"}, + {file = "av-11.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2907376884d956376aaf3bc1905fa4e0dcb9ba4e0d183e519392a19d89317d1b"}, + {file = "av-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8d5581dcdc81cd601e3ce036809f14da82c46ff187bcefe981ec819390e0ab0"}, + {file = "av-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:150490f2a62cfa470f3cb60f3a0060ff93afd807e2b7b3b0eeeb5a992eb8d67b"}, + {file = "av-11.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d9bac0de62f09e2cb4e2132b5a46a89bc31c898189aa285b484c17351d991afe"}, + {file = "av-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2122ff8bdace4ce50207920f37de472517921e2ca1f0503464f748fdb8e20506"}, + {file = "av-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:527d840697fee6ad4cf47eba987eaf30cd76bd96b2d20eaa907e166b9b8065c8"}, + {file = "av-11.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abeaedddfca9101886eb6fc47318c5f5ece8480d330d73aacf6917d7421981a2"}, + {file = "av-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13790fbb889b955baf885fe3761e923e85537ef414173465ec293177cedb7b99"}, + {file = "av-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc27e27f52480287f44226ad4ae3eb53346bf027959d0f00a9154530bd98b371"}, + {file = "av-11.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:892583e2c6b8c2500e5d24310f499caefcdaa2e48c8f7169ad41041aaaf4da11"}, + {file = "av-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6943679d70a9f4de974049e7ae2cf0b20afe0d7ddab650526c02a6cf9adcd08f"}, + {file = "av-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6d73b038ccf1df5c16bc643eee5c694fb7732e09375e2f4903c1f4ce90dfb72"}, + {file = "av-11.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c83422db3333e97b9680700df5185139352fc3a568b14179da3bdcbeb2f0e91b"}, + {file = "av-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8413900f6a3639e0088c018a3a516a1656d4d16799e7aa759a16ddf3bd268e2b"}, + {file = "av-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:908e49ee336223801d8f2f7dca5a1deb64e9d8256138b8e7a79013b682a6ebb5"}, + {file = "av-11.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:82411ae4a562da07b76028d2f349fb0e6a86aa78ad2b18d2d7bf5b06b17fba14"}, + {file = "av-11.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:621104bd63e38fa4eca554da3722b1aac329619de39152f27eec8999acc72342"}, + {file = "av-11.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:442878990c094455a16c10127edcc54bc4e78d355e6a13ad2a27608b0ecda38f"}, + {file = "av-11.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:658199c92987dc72511f5ee8ade62faef6234b7a04c8b5788de99e366be5e073"}, + {file = "av-11.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4b381665c49267b46f87297573898b85e5c41384750fee2e70267fbc4ba318"}, + {file = "av-11.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:60de14f71293e36ca4e297cc8a8460f0cf74f38a201694f3c6fc7f40301582f2"}, + {file = "av-11.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a90f04af96374dab94028a7471597bdfcf03083338b9be2eb8ca4805a8ec7ab5"}, + {file = "av-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8821ab2d23e4cb5c8abea6b08d2b1bfceca6af2d88fab1d1dc1b3ec7b34933c7"}, + {file = "av-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a92342ed307eeaf9509a6b0f3bafd4337c4880c851b50acc18df48c625b63b6"}, + {file = "av-11.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbe3502975bc844f5d432c1f24d331bf6ef3e05532ebf06f7ed08b60719b8ea5"}, + {file = "av-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c278b3a4fd111b4c9190abe6b1a5ca358d5f91e851d470b62577b957e0187b09"}, + {file = "av-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:478aa1d54fbc3058ea65ff41086b6adbe1326b456a027d2f3b59dbe60b4ac2ca"}, + {file = "av-11.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e8df10bb2d56a981d02a8a0b41491912b76dad06305d174a2575ef55ad451100"}, + {file = "av-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30c51e597785a89241bd61865faff2dbd3327856a8285a1e120dbf60e18348b"}, + {file = "av-11.0.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8b8bd92edb096699b306e7b090ad096925ca3bdae6f89656f023fa2a2da627d"}, + {file = "av-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9383af733abfc44f6fc29307a6c922fbf671ee343dc97b78b74eac6a2346a46d"}, + {file = "av-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a9df4a60579198b560f641cdfe4c2139948a70193ddc096b275f2cf6d94e3e04"}, + {file = "av-11.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8ae5f7ae0a7093fb813686d4aa4c554531f80a28480427f5c155da51b747eff0"}, + {file = "av-11.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50fb7d606f8236891d773c701d5650b93af8dbf78eeaac36fc7e1f7f64a9d664"}, + {file = "av-11.0.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:543e0f9bf6ff02dedbe66d906fbc89c8907c80a8ea7413fc3fed68ce4a6e9b44"}, + {file = "av-11.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daa279c884457ab194ce78bdd89c0aa391af733da95fb3258d4c6eb8c258299a"}, + {file = "av-11.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1aacc21f4cf96447117a61edfb776afb73186750a5e08a21484ddfc3599aefb5"}, + {file = "av-11.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2568b38eef777b916a5d02e42b8f67f92e12023531239ddd32e1ca4f3cdf8c5b"}, + {file = "av-11.0.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747c6d347e27c59cc2e78c9c505d23cd88eceff0cc9386be73693ae9009a577c"}, + {file = "av-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbd8f4941b9d3450eff40003b9b9d904667aec7ab085fa31f0f9bca32d755e0"}, + {file = "av-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f39c1244ba0cf185b2722aeec116b8a98a2ee5728ce687cec0bda60ee0360dfc"}, + {file = "av-11.0.0.tar.gz", hash = "sha256:48223f000a252070f8e700ff634bb7fb3aa1b7bc7e450373029fbdd6f369ac31"}, ] [[package]] name = "azure-core" -version = "1.29.5" +version = "1.29.6" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "azure-core-1.29.5.tar.gz", hash = "sha256:52983c89d394c6f881a121e5101c5fa67278ca3b1f339c8fb2ef39230c70e9ac"}, - {file = "azure_core-1.29.5-py3-none-any.whl", hash = "sha256:0fa04b7b1f7d44a4fb8468c4093deb2ea01fdf4faddbf802ed9205615f99d68c"}, + {file = "azure-core-1.29.6.tar.gz", hash = "sha256:13b485252ecd9384ae624894fe51cfa6220966207264c360beada239f88b738a"}, + {file = "azure_core-1.29.6-py3-none-any.whl", hash = "sha256:604a005bce6a49ba661bb7b2be84a9b169047e52fcfcd0a4e4770affab4178f7"}, ] [package.dependencies] -requests = ">=2.18.4" +anyio = ">=3.0,<5.0" +requests = ">=2.21.0" six = ">=1.11.0" typing-extensions = ">=4.6.0" @@ -309,13 +332,13 @@ aio = ["azure-core[aio] (>=1.28.0,<2.0.0)"] [[package]] name = "babel" -version = "2.13.1" +version = "2.14.0" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, ] [package.extras] @@ -749,63 +772,63 @@ test = ["pytest", "pytest-timeout"] [[package]] name = "coverage" -version = "7.3.2" +version = "7.4.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, + {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, + {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, + {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, + {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, + {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, + {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, + {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, + {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, + {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, + {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, + {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, + {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, + {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, + {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, + {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, + {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, + {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, + {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, + {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, + {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, + {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, + {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, + {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, + {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, + {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, + {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, + {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, ] [package.extras] @@ -823,34 +846,34 @@ files = [ [[package]] name = "cryptography" -version = "41.0.5" +version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797"}, - {file = "cryptography-41.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da"}, - {file = "cryptography-41.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548"}, - {file = "cryptography-41.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d"}, - {file = "cryptography-41.0.5-cp37-abi3-win32.whl", hash = "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936"}, - {file = "cryptography-41.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88"}, - {file = "cryptography-41.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179"}, - {file = "cryptography-41.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723"}, - {file = "cryptography-41.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84"}, - {file = "cryptography-41.0.5.tar.gz", hash = "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, + {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, + {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, + {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, + {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, + {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, + {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, + {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, + {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, + {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, ] [package.dependencies] @@ -883,69 +906,62 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "cython" -version = "3.0.6" +version = "3.0.7" description = "The Cython compiler for writing C extensions in the Python language." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Cython-3.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcdfbf6fc7d0bd683d55e617c3d5a5f25b28ce8b405bc1e89054fc7c52a97e5"}, - {file = "Cython-3.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccbee314f8d15ee8ddbe270859dda427e1187123f2c7c41526d1f260eee6c8f7"}, - {file = "Cython-3.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b992f36ffa1294921fca5f6488ea192fadd75770dc64fa25975379382551e9"}, - {file = "Cython-3.0.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ca2e90a75d405070f3c41e701bb8005892f14d42322f1d8fd00a61d660bbae7"}, - {file = "Cython-3.0.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4121c1160bc1bd8828546e8ce45906bd9ff27799d14747ce3fbbc9d67efbb1b8"}, - {file = "Cython-3.0.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:519814b8f80869ee5f9ee2cb2363e5c310067c0298cbea291c556b22da1ef6ae"}, - {file = "Cython-3.0.6-cp310-cp310-win32.whl", hash = "sha256:b029d8c754ef867ab4d67fc2477dde9782bf0409cb8e4024a7d29cf5aff37530"}, - {file = "Cython-3.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:2262390f453eedf600e084b074144286576ed2a56bb7fbfe15ad8d9499eceb52"}, - {file = "Cython-3.0.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfe8c7ac60363769ed8d91fca26398aaa9640368ab999a79b0ccb5e788d3bcf8"}, - {file = "Cython-3.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e31a9b18ec6ce57eb3479df920e6093596fe4ba8010dcc372720040386b4bdb"}, - {file = "Cython-3.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca2542f1f34f0141475b13777df040c31f2073a055097734a0a793ac3a4fb72"}, - {file = "Cython-3.0.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b24c1c38dad4bd85e142ccbe2f88122807f8d5a75352321e1e4baf2b293df7c6"}, - {file = "Cython-3.0.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dc4b4e76c1414584bb55465dfb6f41dd6bd27fd53fb41ddfcaca9edf00c1f80e"}, - {file = "Cython-3.0.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:805a2c532feee09aeed064eaeb7b6ee35cbab650569d0a3756975f3cc4f246cf"}, - {file = "Cython-3.0.6-cp311-cp311-win32.whl", hash = "sha256:dcdb9a177c7c385fe0c0709a9a6790b6508847d67dcac76bb65a2c7ea447efe5"}, - {file = "Cython-3.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:b8640b7f6503292c358cef925df5a69adf230045719893ffe20ad98024fdf7ae"}, - {file = "Cython-3.0.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:16b3b02cc7b3bc42ee1a0118b1465ca46b0f3fb32d003e6f1a3a352a819bb9a3"}, - {file = "Cython-3.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11e1d9b153573c425846b627bef52b3b99cb73d4fbfbb136e500a878d4b5e803"}, - {file = "Cython-3.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85a7a406f78c2f297bf82136ff5deac3150288446005ed1e56552a9e3ac1469f"}, - {file = "Cython-3.0.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88be4fbc760de8f313df89ca8256098c0963c9ec72f3aa88538384b80ef1a6ef"}, - {file = "Cython-3.0.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea2e5a7c503b41618bfb10e4bc610f780ab1c729280531b5cabb24e05aa21cf2"}, - {file = "Cython-3.0.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d296b48e1410cab50220a28a834167f2d7ac6c0e7de12834d66e42248a1b0f6"}, - {file = "Cython-3.0.6-cp312-cp312-win32.whl", hash = "sha256:7f19e99c6e334e9e30dfa844c3ca4ac09931b94dbba406c646bde54687aed758"}, - {file = "Cython-3.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:9cae02e26967ffb6503c6e91b77010acbadfb7189a5a11d6158d634fb0f73679"}, - {file = "Cython-3.0.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cb6a54543869a5b0ad009d86eb0ebc0879fab838392bfd253ad6d4f5e0f17d84"}, - {file = "Cython-3.0.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d2d9e53bf021cc7a5c7b6b537b5b5a7ba466ba7348d498aa17499d0ad12637e"}, - {file = "Cython-3.0.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05d15854b2b363b35c755d22015c1c2fc590b8128202f8c9eb85578461101d9c"}, - {file = "Cython-3.0.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5548316497a3b8b2d9da575ea143476472db90dee73c67def061621940f78ae"}, - {file = "Cython-3.0.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9b853e0855e4b3d164c05b24718e5e2df369e5af54f47cb8d923c4f497dfc92c"}, - {file = "Cython-3.0.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2c77f97f462a40a319dda7e28c1669370cb26f9175f3e8f9bab99d2f8f3f2f09"}, - {file = "Cython-3.0.6-cp36-cp36m-win32.whl", hash = "sha256:3ac8b6734f2cad5640f2da21cd33cf88323547d07e445fb7453ab38ec5033b1f"}, - {file = "Cython-3.0.6-cp36-cp36m-win_amd64.whl", hash = "sha256:8dd5f5f3587909ff71f0562f50e00d4b836c948e56e8f74897b12f38a29e41b9"}, - {file = "Cython-3.0.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9c0472c6394750469062deb2c166125b10411636f63a0418b5c36a60d0c9a96a"}, - {file = "Cython-3.0.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97081932c8810bb99cb26b4b0402202a1764b58ee287c8b306071d2848148c24"}, - {file = "Cython-3.0.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e781b3880dfd0d4d37983c9d414bfd5f26c2141f6d763d20ef1964a0a4cb2405"}, - {file = "Cython-3.0.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef88c46e91e21772a5d3b6b1e70a6da5fe098154ad4768888129b1c05e93bba7"}, - {file = "Cython-3.0.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a38b9e7a252ec27dbc21ee8f00f09a896e88285eebb6ed99207b2ff1ea6af28e"}, - {file = "Cython-3.0.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4975cdaf720d29288ec225b76b4f4471ff03f4f8b51841ba85d6587699ab2ad5"}, - {file = "Cython-3.0.6-cp37-cp37m-win32.whl", hash = "sha256:9b89463ea330318461ca47d3e49b5f606e7e82446b6f37e5c19b60392439674c"}, - {file = "Cython-3.0.6-cp37-cp37m-win_amd64.whl", hash = "sha256:0ca8f379b47417bfad98faeb14bf8a3966fc92cf69f8aaf7635cf6885e50d001"}, - {file = "Cython-3.0.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b3dda1e80eb577b9563cee6cf31923a7b88836b9f9be0043ec545b138b95d8e8"}, - {file = "Cython-3.0.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e34e9a96f98c379100ef4192994a311678fb5c9af34c83ba5230223577581"}, - {file = "Cython-3.0.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345d9112fde4ae0347d656f58591fd52017c61a19779c95423bb38735fe4a401"}, - {file = "Cython-3.0.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25da0e51331ac12ff16cd858d1d836e092c984e1dc45d338166081d3802297c0"}, - {file = "Cython-3.0.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:eebbf09089b4988b9f398ed46f168892e32fcfeec346b15954fdd818aa103456"}, - {file = "Cython-3.0.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e3ed0c125556324fa49b9e92bea13be7b158fcae6f72599d63c8733688257788"}, - {file = "Cython-3.0.6-cp38-cp38-win32.whl", hash = "sha256:86e1e5a5c9157a547d0a769de59c98a1fc5e46cfad976f32f60423cc6de11052"}, - {file = "Cython-3.0.6-cp38-cp38-win_amd64.whl", hash = "sha256:0d45a84a315bd84d1515cd3571415a0ee0709eb4e2cd4b13668ede928af344a7"}, - {file = "Cython-3.0.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a8e788e64b659bb8fe980bc37da3118e1f7285dec40c5fb293adabc74d4205f2"}, - {file = "Cython-3.0.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a77a174c7fb13d80754c8bf9912efd3f3696d13285b2f568eca17324263b3f7"}, - {file = "Cython-3.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1074e84752cd0daf3226823ddbc37cca8bc45f61c94a1db2a34e641f2b9b0797"}, - {file = "Cython-3.0.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49d5cae02d56e151e1481e614a1af9a0fe659358f2aa5eca7a18f05aa641db61"}, - {file = "Cython-3.0.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b94610fa49e36db068446cfd149a42e3246f38a4256bbe818512ac181446b4b"}, - {file = "Cython-3.0.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fabb2d14dd71add618a7892c40ffec584d1dae1e477caa193778e52e06821d83"}, - {file = "Cython-3.0.6-cp39-cp39-win32.whl", hash = "sha256:ce442c0be72ab014c305399d955b78c3d1e69d5a5ce24398122b605691b69078"}, - {file = "Cython-3.0.6-cp39-cp39-win_amd64.whl", hash = "sha256:8a05f79a0761fc76c42e945e5a9cb5d7986aa9e8e526fdf52bd9ca61a12d4567"}, - {file = "Cython-3.0.6-py2.py3-none-any.whl", hash = "sha256:5921a175ea20779d4443ef99276cfa9a1a47de0e32d593be7679be741c9ed93b"}, - {file = "Cython-3.0.6.tar.gz", hash = "sha256:399d185672c667b26eabbdca420c98564583798af3bc47670a8a09e9f19dd660"}, + {file = "Cython-3.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3c0e19bb41de6be9d8afc85795159ca16296be81a586cd9588be0400d44a855"}, + {file = "Cython-3.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e8bf00ec1dd1d92e9ae74d2e6891f087a939e1dfb40c9c7fa5d8d6a26c94f5a"}, + {file = "Cython-3.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd6ae43ef2e596c9a88dbf2a8895be2e32cc2f5bc3c8ba2e7753b69068fc0b2d"}, + {file = "Cython-3.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f674be92673e87dd8ee7cfe553d5960ec4effc5ab15063b9a5e265a51585a31a"}, + {file = "Cython-3.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:861cf254bf5836d47c2aee86aa75dd93d3de00ccd1b077c3c7a2bb22cba358e7"}, + {file = "Cython-3.0.7-cp310-cp310-win32.whl", hash = "sha256:f6d8ff62ad55dc0393686438eac4b457a916e4d1118a0b550746bb52b4c756cc"}, + {file = "Cython-3.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:e13abb14843397b76d0472c7d33cd260d5f262ab05cc27ed423317e645e29643"}, + {file = "Cython-3.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c636c9ab92c7838231a1ba769e519d953af8294612f3f772a54d3a5250ff23f"}, + {file = "Cython-3.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d2a684122dfb531853d57c8c85c1d5d44be709e12466dca99fa6aee7d8054f"}, + {file = "Cython-3.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1bdf8a107fdf9e174991aa87a0be7504f60de1ec6bfb1ccfb30e33acac818a0"}, + {file = "Cython-3.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3a83e04fde663b84905f3a20213a4333d13a07b79434300704b70dc552761f8b"}, + {file = "Cython-3.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e34b4b08d795ccca920fa26b099558f4f1e4e3f794e4ba8d3433c5bc2454d50a"}, + {file = "Cython-3.0.7-cp311-cp311-win32.whl", hash = "sha256:133057ac45b6fa7fe5d7baada9d3545d09339432f75c0545f556e8c6fecc2932"}, + {file = "Cython-3.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:b65abca78aa5ebc8675c8480b9a53006f6efea9910ad099cf32c9fb5617ef251"}, + {file = "Cython-3.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ceac5315fe899c229e874328742154e331fa41337bb03f6f5264636c351c9e"}, + {file = "Cython-3.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea936cf5931297ba07bce121388c4c6266c1b63a9f4d648ae16c92ff090204b"}, + {file = "Cython-3.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fcd9a18ee3ac7f460e0841954feb495102ffbdbec0e6c78562f3495cda000dd"}, + {file = "Cython-3.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7c8d579d13cb81abe704c8b0908d122b81d6e2623265a19c4a6a7377f440debb"}, + {file = "Cython-3.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ef5bb0268bfe5992da3ef9292463a5a895ed8700b134ed2c00008d5471b3ba6e"}, + {file = "Cython-3.0.7-cp312-cp312-win32.whl", hash = "sha256:55f93d3822bc196b37a8bdfa4ec6a35232a399e97f2baa714bd5ed8ea9b0ce68"}, + {file = "Cython-3.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:f3845c4506e0d207c5e268fb02813928f3a1e135de954a379f165ef0d581da47"}, + {file = "Cython-3.0.7-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ad7c2303a338b2c0b6c6c68f101a6768725934538756096cf3388a5c07a7525"}, + {file = "Cython-3.0.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed25959e4025870fdde5f895fcb126196d22affd4f4fad85a2823e0dddc85b0"}, + {file = "Cython-3.0.7-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79868ec74e4907a8a6e63effe13547c6157f196a162920b1de066da5849ffb8e"}, + {file = "Cython-3.0.7-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5e3a038332973b12e72236e8884dc99601a840334c2c46cfbbb5851cb94166eb"}, + {file = "Cython-3.0.7-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f2602a5c97a3d618b3b847514204ef3349fb414c59e1126c0c2c708d2c5680f8"}, + {file = "Cython-3.0.7-cp36-cp36m-win32.whl", hash = "sha256:539ad5a21141e6420035cf616bcba48d999bf878839e52692f97fc7e2f16265c"}, + {file = "Cython-3.0.7-cp36-cp36m-win_amd64.whl", hash = "sha256:848a28ea49166454c3bff927e5a47629eecf1aa755d6fb3290569cba0fc93766"}, + {file = "Cython-3.0.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82f27a0134fc6bb46032ca5f728d8af984f3be94a3cb01cb70ff1224e551b9cf"}, + {file = "Cython-3.0.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f20c61114c7948cf1214585066406cef4b54a9b935160980e0b6e70ada3a69"}, + {file = "Cython-3.0.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d51709e10ad6213b4bf094af7be7ff82bab43216b3c92a07d05b451deeca79"}, + {file = "Cython-3.0.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3f02c7240abab48d59f0d5fef7064f18f01a2a204616165fa6367a8abf5a8832"}, + {file = "Cython-3.0.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:225f8bba6428b8d711ca2d6c738d2e3a4667f6a2ae40f8a7a5256f69f6a3600e"}, + {file = "Cython-3.0.7-cp37-cp37m-win32.whl", hash = "sha256:30eb2d2938b9195e2c82951713429aff3ad1be9f104437d1536a04eb0cb3dc0e"}, + {file = "Cython-3.0.7-cp37-cp37m-win_amd64.whl", hash = "sha256:167b3f3894dcc697cefefac1d198304fae8eb4d5860a7b8bc2459d572e838470"}, + {file = "Cython-3.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c67105f2c6ccf5b3adbcfaecf3c5c9fa8940f9f97955c9ad7d2542151d97d93"}, + {file = "Cython-3.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a1859af761977530df2cd5c36e31d54e8d6708ad2c4656e7125c482364dc216"}, + {file = "Cython-3.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01b94304aab87496e81d1f546e71abf57b430b39be4269df1cd7da9928d70b5b"}, + {file = "Cython-3.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:931aade65f77cf59f2a702ac1f549a4836ce221107c740502cbad18d6d8e9511"}, + {file = "Cython-3.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:812b193c26553f1f375d4f1c50f805c227b24ed2d595bc9cdaf78c992ecc64a4"}, + {file = "Cython-3.0.7-cp38-cp38-win32.whl", hash = "sha256:b227643d8a40b68554dc7d37fcd03fc97b4fb0bd2614aeb5f2e07ab244642d36"}, + {file = "Cython-3.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:0d8a98c7d86ac4d05b251c39faf49423780381aab55fbf2e147f6e006a34a58a"}, + {file = "Cython-3.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:816f5285d596062c7ef22790de7d75354b58d4417a9fc64cba914aeeb900db0b"}, + {file = "Cython-3.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9d0dae6dccd349b8ccf197c10ef2d05c711ca36a649c7eddbab1de2c90b63a1"}, + {file = "Cython-3.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13211b67b29f6ed8e87c137496c73d93aff0330d97940b4fbed72eae37a4a2a0"}, + {file = "Cython-3.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b1853bc34ced5ff6473e881fcf6de29da83262552c8f268a0df53b49c2b89e2c"}, + {file = "Cython-3.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:51e8164b1270625ff101e95c3c1c234421520c07a0a3a20ded9e9431d98afce7"}, + {file = "Cython-3.0.7-cp39-cp39-win32.whl", hash = "sha256:45319d2471f4dbf19893ca53785a421107266e18b8cccd2054fce1e3f72a85f1"}, + {file = "Cython-3.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:612d83fd1eb5aaa5401a755c1f1aafacd9dab404cd350b90d5f404c98b33e4b3"}, + {file = "Cython-3.0.7-py2.py3-none-any.whl", hash = "sha256:936ec37b261b226d7404eff23a9aad284098338150d42a53d6a9af12b18d3892"}, + {file = "Cython-3.0.7.tar.gz", hash = "sha256:fb299acf3a578573c190c858d49e0cf9d75f4bc49c3f24c5a63804997ef09213"}, ] [[package]] @@ -967,13 +983,13 @@ tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest (==5.4.3)", "pytes [[package]] name = "distlib" -version = "0.3.7" +version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, - {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] @@ -997,13 +1013,13 @@ wmi = ["wmi (>=1.5.1,<2.0.0)"] [[package]] name = "docutils" -version = "0.18.1" +version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] [[package]] @@ -1108,59 +1124,59 @@ files = [ [[package]] name = "fonttools" -version = "4.45.1" +version = "4.47.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.45.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:45fa321c458ea29224067700954ec44493ae869b47e7c5485a350a149a19fb53"}, - {file = "fonttools-4.45.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0dc7617d96b1e668eea9250e1c1fe62d0c78c3f69573ce7e3332cc40e6d84356"}, - {file = "fonttools-4.45.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ed3bda541e86725f6b4e1b94213f13ed1ae51a5a1f167028534cedea38c010"}, - {file = "fonttools-4.45.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f4a5870e3b56788fb196da8cf30d0dfd51a76dc3b907861d018165f76ae4c2"}, - {file = "fonttools-4.45.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a3c11d9687479f01eddef729aa737abcdea0a44fdaffb62a930a18892f186c9b"}, - {file = "fonttools-4.45.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:316cec50581e844c3ab69d7c82455b54c7cf18236b2f09e722faf665fbfcac58"}, - {file = "fonttools-4.45.1-cp310-cp310-win32.whl", hash = "sha256:e2277cba9f0b525e30de2a9ad3cb4219aa4bc697230c1645666b0deee9f914f0"}, - {file = "fonttools-4.45.1-cp310-cp310-win_amd64.whl", hash = "sha256:1b9e9ad2bcded9a1431afaa57c8d3c39143ac1f050862d66bddd863c515464a2"}, - {file = "fonttools-4.45.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff6a698bdd435d24c379f6e8a54908cd9bb7dda23719084d56bf8c87709bf3bd"}, - {file = "fonttools-4.45.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c980d60cd6ec1376206fe55013d166e5627ad0b149b5c81e74eaa913ab6134f"}, - {file = "fonttools-4.45.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a12dee6523c02ca78aeedd0a5e12bfa9b7b29896350edd5241542897b072ae23"}, - {file = "fonttools-4.45.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37cd1ced6efb3dd6fe82e9f9bf92fd74ac58a5aefc284045f59ecd517a5fb9ab"}, - {file = "fonttools-4.45.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e3d24248221bd7151dfff0d88b1b5da02dccd7134bd576ce8888199827bbaa19"}, - {file = "fonttools-4.45.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba6c23591427844dfb0a13658f1718489de75de6a46b64234584c0d17573162d"}, - {file = "fonttools-4.45.1-cp311-cp311-win32.whl", hash = "sha256:cebcddbe9351b67166292b4f71ffdbfcce01ba4b07d4267824eb46b277aeb19a"}, - {file = "fonttools-4.45.1-cp311-cp311-win_amd64.whl", hash = "sha256:f22eb69996a0bd49f76bdefb30be54ce8dbb89a0d1246874d610f05c2aa2e69e"}, - {file = "fonttools-4.45.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:794de93e83297db7b4943f2431e206d8b1ea69cb3ae14638a49cc50332bf0db8"}, - {file = "fonttools-4.45.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ba17822a6681d06849078daaf6e03eccc9f467efe7c4c60280e28a78e8e5df9"}, - {file = "fonttools-4.45.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e50f794d09df0675da8d9dbd7c66bfcab2f74a708343aabcad41936d26556891"}, - {file = "fonttools-4.45.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b07b857d4f9de3199a8c3d1b1bf2078c0f37447891ca1a8d9234106b9a27aff"}, - {file = "fonttools-4.45.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:777ba42b94a27bb7fb2b4082522fccfd345667c32a56011e1c3e105979af5b79"}, - {file = "fonttools-4.45.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:21e96b99878348c74aa58059b8578d7586f9519cbcdadacf56486737038aa043"}, - {file = "fonttools-4.45.1-cp312-cp312-win32.whl", hash = "sha256:5cbf02cda8465b69769d07385f5d11e7bba19954e7787792f46fe679ec755ebb"}, - {file = "fonttools-4.45.1-cp312-cp312-win_amd64.whl", hash = "sha256:800e354e0c3afaeb8d9552769773d02f228e98c37b8cb03041157c3d0687cffc"}, - {file = "fonttools-4.45.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6eb2c54f7a07c92108daabcf02caf31df97825738db02a28270633946bcda4d0"}, - {file = "fonttools-4.45.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:43a3d267334109ff849c37cf3629476b5feb392ef1d2e464a167b83de8cd599c"}, - {file = "fonttools-4.45.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e1aefc2bf3c43e0f33f995f828a7bbeff4adc9393a7760b11456dbcf14388f6"}, - {file = "fonttools-4.45.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f53a19dcdd5737440839b8394eeebb35da9ec8109f7926cb6456639b5b58e47"}, - {file = "fonttools-4.45.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a17706b9cc24b27721613fe5773d93331ab7f0ecaca9955aead89c6b843d3a7"}, - {file = "fonttools-4.45.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fb36e5f40191274a95938b40c0a1fa7f895e36935aea8709e1d6deff0b2d0d4f"}, - {file = "fonttools-4.45.1-cp38-cp38-win32.whl", hash = "sha256:46eabddec12066829b8a1efe45ae552ba2f1796981ecf538d5f68284c354c589"}, - {file = "fonttools-4.45.1-cp38-cp38-win_amd64.whl", hash = "sha256:b6de2f0fcd3302fb82f94801002cb473959e998c14c24ec28234adb674aed345"}, - {file = "fonttools-4.45.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:392d0e3cc23daee910193625f7cf1b387aff9dd5b6f1a5f4a925680acb6dcbc2"}, - {file = "fonttools-4.45.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b9544b1346d99848ac0e9b05b5d45ee703d7562fc4c9c48cf4b781de9632e57"}, - {file = "fonttools-4.45.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8717db3e4895e4820ade64ea379187738827ee60748223cb0438ef044ee208c6"}, - {file = "fonttools-4.45.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e29d5f298d616a93a4c5963682dc6cc8cc09f6d89cad2c29019fc5fb3b4d9472"}, - {file = "fonttools-4.45.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cb472905da3049960e80fc1cf808231880d79727a8410e156bf3e5063a1c574f"}, - {file = "fonttools-4.45.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ba299f1fbaa2a1e33210aaaf6fa816d4059e4d3cfe2ae9871368d4ab548c1c6a"}, - {file = "fonttools-4.45.1-cp39-cp39-win32.whl", hash = "sha256:105099968b58a5b4cef6f3eb409db8ea8578b302a9d05e23fecba1b8b0177b5f"}, - {file = "fonttools-4.45.1-cp39-cp39-win_amd64.whl", hash = "sha256:847f3f49dd3423e5a678c098e2ba92c7f4955d4aab3044f6a507b0bb0ecb07e0"}, - {file = "fonttools-4.45.1-py3-none-any.whl", hash = "sha256:3bdd7dfca8f6c9f4779384064027e8477ad6a037d6a327b09381f43e0247c6f3"}, - {file = "fonttools-4.45.1.tar.gz", hash = "sha256:6e441286d55fe7ec7c4fb36812bf914924813776ff514b744b510680fc2733f2"}, + {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d2404107626f97a221dc1a65b05396d2bb2ce38e435f64f26ed2369f68675d9"}, + {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01f409be619a9a0f5590389e37ccb58b47264939f0e8d58bfa1f3ba07d22671"}, + {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d986b66ff722ef675b7ee22fbe5947a41f60a61a4da15579d5e276d897fbc7fa"}, + {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8acf6dd0434b211b3bd30d572d9e019831aae17a54016629fa8224783b22df8"}, + {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:495369c660e0c27233e3c572269cbe520f7f4978be675f990f4005937337d391"}, + {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c59227d7ba5b232281c26ae04fac2c73a79ad0e236bca5c44aae904a18f14faf"}, + {file = "fonttools-4.47.0-cp310-cp310-win32.whl", hash = "sha256:59a6c8b71a245800e923cb684a2dc0eac19c56493e2f896218fcf2571ed28984"}, + {file = "fonttools-4.47.0-cp310-cp310-win_amd64.whl", hash = "sha256:52c82df66201f3a90db438d9d7b337c7c98139de598d0728fb99dab9fd0495ca"}, + {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:854421e328d47d70aa5abceacbe8eef231961b162c71cbe7ff3f47e235e2e5c5"}, + {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:511482df31cfea9f697930f61520f6541185fa5eeba2fa760fe72e8eee5af88b"}, + {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0e2c88c8c985b7b9a7efcd06511fb0a1fe3ddd9a6cd2895ef1dbf9059719d7"}, + {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7a0a8848726956e9d9fb18c977a279013daadf0cbb6725d2015a6dd57527992"}, + {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e869da810ae35afb3019baa0d0306cdbab4760a54909c89ad8904fa629991812"}, + {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dd23848f877c3754f53a4903fb7a593ed100924f9b4bff7d5a4e2e8a7001ae11"}, + {file = "fonttools-4.47.0-cp311-cp311-win32.whl", hash = "sha256:bf1810635c00f7c45d93085611c995fc130009cec5abdc35b327156aa191f982"}, + {file = "fonttools-4.47.0-cp311-cp311-win_amd64.whl", hash = "sha256:61df4dee5d38ab65b26da8efd62d859a1eef7a34dcbc331299a28e24d04c59a7"}, + {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3f4d61f3a8195eac784f1d0c16c0a3105382c1b9a74d99ac4ba421da39a8826"}, + {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:174995f7b057e799355b393e97f4f93ef1f2197cbfa945e988d49b2a09ecbce8"}, + {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea592e6a09b71cb7a7661dd93ac0b877a6228e2d677ebacbad0a4d118494c86d"}, + {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40bdbe90b33897d9cc4a39f8e415b0fcdeae4c40a99374b8a4982f127ff5c767"}, + {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:843509ae9b93db5aaf1a6302085e30bddc1111d31e11d724584818f5b698f500"}, + {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9acfa1cdc479e0dde528b61423855913d949a7f7fe09e276228298fef4589540"}, + {file = "fonttools-4.47.0-cp312-cp312-win32.whl", hash = "sha256:66c92ec7f95fd9732550ebedefcd190a8d81beaa97e89d523a0d17198a8bda4d"}, + {file = "fonttools-4.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8fa20748de55d0021f83754b371432dca0439e02847962fc4c42a0e444c2d78"}, + {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c75e19971209fbbce891ebfd1b10c37320a5a28e8d438861c21d35305aedb81c"}, + {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e79f1a3970d25f692bbb8c8c2637e621a66c0d60c109ab48d4a160f50856deff"}, + {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:562681188c62c024fe2c611b32e08b8de2afa00c0c4e72bed47c47c318e16d5c"}, + {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a77a60315c33393b2bd29d538d1ef026060a63d3a49a9233b779261bad9c3f71"}, + {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4fabb8cc9422efae1a925160083fdcbab8fdc96a8483441eb7457235df625bd"}, + {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a78dba8c2a1e9d53a0fb5382979f024200dc86adc46a56cbb668a2249862fda"}, + {file = "fonttools-4.47.0-cp38-cp38-win32.whl", hash = "sha256:e6b968543fde4119231c12c2a953dcf83349590ca631ba8216a8edf9cd4d36a9"}, + {file = "fonttools-4.47.0-cp38-cp38-win_amd64.whl", hash = "sha256:4a9a51745c0439516d947480d4d884fa18bd1458e05b829e482b9269afa655bc"}, + {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:62d8ddb058b8e87018e5dc26f3258e2c30daad4c87262dfeb0e2617dd84750e6"}, + {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5dde0eab40faaa5476133123f6a622a1cc3ac9b7af45d65690870620323308b4"}, + {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4da089f6dfdb822293bde576916492cd708c37c2501c3651adde39804630538"}, + {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:253bb46bab970e8aae254cebf2ae3db98a4ef6bd034707aa68a239027d2b198d"}, + {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1193fb090061efa2f9e2d8d743ae9850c77b66746a3b32792324cdce65784154"}, + {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:084511482dd265bce6dca24c509894062f0117e4e6869384d853f46c0e6d43be"}, + {file = "fonttools-4.47.0-cp39-cp39-win32.whl", hash = "sha256:97620c4af36e4c849e52661492e31dc36916df12571cb900d16960ab8e92a980"}, + {file = "fonttools-4.47.0-cp39-cp39-win_amd64.whl", hash = "sha256:e77bdf52185bdaf63d39f3e1ac3212e6cfa3ab07d509b94557a8902ce9c13c82"}, + {file = "fonttools-4.47.0-py3-none-any.whl", hash = "sha256:d6477ba902dd2d7adda7f0fd3bfaeb92885d45993c9e1928c9f28fc3961415f7"}, + {file = "fonttools-4.47.0.tar.gz", hash = "sha256:ec13a10715eef0e031858c1c23bfaee6cba02b97558e4a7bfa089dba4a8c2ebf"}, ] [package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] +interpolatable = ["munkres", "pycairo", "scipy"] lxml = ["lxml (>=4.0,<5)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] @@ -1173,72 +1189,88 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "frozenlist" -version = "1.4.0" +version = "1.4.1" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:764226ceef3125e53ea2cb275000e309c0aa5464d43bd72abd661e27fffc26ab"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d6484756b12f40003c6128bfcc3fa9f0d49a687e171186c2d85ec82e3758c559"}, - {file = "frozenlist-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9ac08e601308e41eb533f232dbf6b7e4cea762f9f84f6357136eed926c15d12c"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d081f13b095d74b67d550de04df1c756831f3b83dc9881c38985834387487f1b"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:71932b597f9895f011f47f17d6428252fc728ba2ae6024e13c3398a087c2cdea"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:981b9ab5a0a3178ff413bca62526bb784249421c24ad7381e39d67981be2c326"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e41f3de4df3e80de75845d3e743b3f1c4c8613c3997a912dbf0229fc61a8b963"}, - {file = "frozenlist-1.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6918d49b1f90821e93069682c06ffde41829c346c66b721e65a5c62b4bab0300"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e5c8764c7829343d919cc2dfc587a8db01c4f70a4ebbc49abde5d4b158b007b"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8d0edd6b1c7fb94922bf569c9b092ee187a83f03fb1a63076e7774b60f9481a8"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e29cda763f752553fa14c68fb2195150bfab22b352572cb36c43c47bedba70eb"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0c7c1b47859ee2cac3846fde1c1dc0f15da6cec5a0e5c72d101e0f83dcb67ff9"}, - {file = "frozenlist-1.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:901289d524fdd571be1c7be054f48b1f88ce8dddcbdf1ec698b27d4b8b9e5d62"}, - {file = "frozenlist-1.4.0-cp310-cp310-win32.whl", hash = "sha256:1a0848b52815006ea6596c395f87449f693dc419061cc21e970f139d466dc0a0"}, - {file = "frozenlist-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:b206646d176a007466358aa21d85cd8600a415c67c9bd15403336c331a10d956"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:de343e75f40e972bae1ef6090267f8260c1446a1695e77096db6cfa25e759a95"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad2a9eb6d9839ae241701d0918f54c51365a51407fd80f6b8289e2dfca977cc3"}, - {file = "frozenlist-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7bd3b3830247580de99c99ea2a01416dfc3c34471ca1298bccabf86d0ff4dc"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdf1847068c362f16b353163391210269e4f0569a3c166bc6a9f74ccbfc7e839"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38461d02d66de17455072c9ba981d35f1d2a73024bee7790ac2f9e361ef1cd0c"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5a32087d720c608f42caed0ef36d2b3ea61a9d09ee59a5142d6070da9041b8f"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd65632acaf0d47608190a71bfe46b209719bf2beb59507db08ccdbe712f969b"}, - {file = "frozenlist-1.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261b9f5d17cac914531331ff1b1d452125bf5daa05faf73b71d935485b0c510b"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b89ac9768b82205936771f8d2eb3ce88503b1556324c9f903e7156669f521472"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e74b0506fa5aa5598ac6a975a12aa8928cbb58e1f5ac8360792ef15de1aa848f"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:490132667476f6781b4c9458298b0c1cddf237488abd228b0b3650e5ecba7467"}, - {file = "frozenlist-1.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:76d4711f6f6d08551a7e9ef28c722f4a50dd0fc204c56b4bcd95c6cc05ce6fbb"}, - {file = "frozenlist-1.4.0-cp311-cp311-win32.whl", hash = "sha256:a02eb8ab2b8f200179b5f62b59757685ae9987996ae549ccf30f983f40602431"}, - {file = "frozenlist-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:515e1abc578dd3b275d6a5114030b1330ba044ffba03f94091842852f806f1c1"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0ed05f5079c708fe74bf9027e95125334b6978bf07fd5ab923e9e55e5fbb9d3"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca265542ca427bf97aed183c1676e2a9c66942e822b14dc6e5f42e038f92a503"}, - {file = "frozenlist-1.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:491e014f5c43656da08958808588cc6c016847b4360e327a62cb308c791bd2d9"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ae5cd0f333f94f2e03aaf140bb762c64783935cc764ff9c82dff626089bebf"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e78fb68cf9c1a6aa4a9a12e960a5c9dfbdb89b3695197aa7064705662515de2"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5655a942f5f5d2c9ed93d72148226d75369b4f6952680211972a33e59b1dfdc"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11b0746f5d946fecf750428a95f3e9ebe792c1ee3b1e96eeba145dc631a9672"}, - {file = "frozenlist-1.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e66d2a64d44d50d2543405fb183a21f76b3b5fd16f130f5c99187c3fb4e64919"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f7bc0fcca81f985f78dd0fa68d2c75abf8272b1f5c323ea4a01a4d7a614efc"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5833593c25ac59ede40ed4de6d67eb42928cca97f26feea219f21d0ed0959b79"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fec520865f42e5c7f050c2a79038897b1c7d1595e907a9e08e3353293ffc948e"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:b826d97e4276750beca7c8f0f1a4938892697a6bcd8ec8217b3312dad6982781"}, - {file = "frozenlist-1.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ceb6ec0a10c65540421e20ebd29083c50e6d1143278746a4ef6bcf6153171eb8"}, - {file = "frozenlist-1.4.0-cp38-cp38-win32.whl", hash = "sha256:2b8bcf994563466db019fab287ff390fffbfdb4f905fc77bc1c1d604b1c689cc"}, - {file = "frozenlist-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:a6c8097e01886188e5be3e6b14e94ab365f384736aa1fca6a0b9e35bd4a30bc7"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6c38721585f285203e4b4132a352eb3daa19121a035f3182e08e437cface44bf"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0c6da9aee33ff0b1a451e867da0c1f47408112b3391dd43133838339e410963"}, - {file = "frozenlist-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93ea75c050c5bb3d98016b4ba2497851eadf0ac154d88a67d7a6816206f6fa7f"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f61e2dc5ad442c52b4887f1fdc112f97caeff4d9e6ebe78879364ac59f1663e1"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa384489fefeb62321b238e64c07ef48398fe80f9e1e6afeff22e140e0850eef"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10ff5faaa22786315ef57097a279b833ecab1a0bfb07d604c9cbb1c4cdc2ed87"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6"}, - {file = "frozenlist-1.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f4f399d28478d1f604c2ff9119907af9726aed73680e5ed1ca634d377abb087"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c5374b80521d3d3f2ec5572e05adc94601985cc526fb276d0c8574a6d749f1b3"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ce31ae3e19f3c902de379cf1323d90c649425b86de7bbdf82871b8a2a0615f3d"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7211ef110a9194b6042449431e08c4d80c0481e5891e58d429df5899690511c2"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:556de4430ce324c836789fa4560ca62d1591d2538b8ceb0b4f68fb7b2384a27a"}, - {file = "frozenlist-1.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7645a8e814a3ee34a89c4a372011dcd817964ce8cb273c8ed6119d706e9613e3"}, - {file = "frozenlist-1.4.0-cp39-cp39-win32.whl", hash = "sha256:19488c57c12d4e8095a922f328df3f179c820c212940a498623ed39160bc3c2f"}, - {file = "frozenlist-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:6221d84d463fb110bdd7619b69cb43878a11d51cbb9394ae3105d082d5199167"}, - {file = "frozenlist-1.4.0.tar.gz", hash = "sha256:09163bdf0b2907454042edb19f887c6d33806adc71fbd54afc14908bfdc22251"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, ] [[package]] @@ -1524,13 +1556,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"] [[package]] name = "identify" -version = "2.5.32" +version = "2.5.33" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.32-py2.py3-none-any.whl", hash = "sha256:0b7656ef6cba81664b783352c73f8c24b39cf82f926f78f4550eda928e5e0545"}, - {file = "identify-2.5.32.tar.gz", hash = "sha256:5d9979348ec1a21c768ae07e0a652924538e8bce67313a73cb0f681cf08ba407"}, + {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, + {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, ] [package.extras] @@ -1571,20 +1603,20 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.8.0" +version = "7.0.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, - {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] @@ -1891,110 +1923,106 @@ test = ["pytest"] [[package]] name = "lxml" -version = "4.9.3" +version = "5.0.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ - {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, - {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, - {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, - {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, - {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, - {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, - {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, - {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, - {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, - {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, - {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, - {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, - {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, - {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, - {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, - {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, - {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, - {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, - {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, - {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, - {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, - {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, - {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, - {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, - {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, + {file = "lxml-5.0.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73bfab795d354aaf2f4eb7a5b0db513031734fd371047342d5803834ce19ec18"}, + {file = "lxml-5.0.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cb564bbe55ff0897d9cf1225041a44576d7ae87f06fd60163544c91de2623d3f"}, + {file = "lxml-5.0.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a5501438dd521bb7e0dde5008c40c7bfcfaafaf86eccb3f9bd27509abb793da"}, + {file = "lxml-5.0.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7ba26a7dc929a1b3487d51bbcb0099afed2fc06e891b82845c8f37a2d7d7fbbd"}, + {file = "lxml-5.0.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:9b59c429e1a2246da86ae237ffc3565efcdc71c281cd38ca8b44d5fb6a3b993a"}, + {file = "lxml-5.0.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:3ffa066db40b0347e48334bd4465de768e295a3525b9a59831228b5f4f93162d"}, + {file = "lxml-5.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8ce8b468ab50f9e944719d1134709ec11fe0d2840891a6cae369e22141b1094c"}, + {file = "lxml-5.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:583c0e15ae06adc81035346ae2abb2e748f0b5197e7740d8af31222db41bbf7b"}, + {file = "lxml-5.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:904d36165848b59c4e04ae5b969072e602bd987485076fca8ec42c6cd7a7aedc"}, + {file = "lxml-5.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac21aace6712472e77ea9dfc38329f53830c4259ece54c786107105ebb069053"}, + {file = "lxml-5.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f92d73faa0b1a76d1932429d684b7ce95829e93c3eef3715ec9b98ab192c9d31"}, + {file = "lxml-5.0.0-cp310-cp310-win32.whl", hash = "sha256:03290e2f714f2e7431c8430c08b48167f657da7bc689c6248e828ff3c66d5b1b"}, + {file = "lxml-5.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:3e6cbb68bf70081f036bfc018649cf4b46c4e7eaf7860a277cae92dee2a57f69"}, + {file = "lxml-5.0.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5382612ba2424cea5d2c89e2c29077023d8de88f8d60d5ceff5f76334516df9e"}, + {file = "lxml-5.0.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:07a900735bad9af7be3085480bf384f68ed5580ba465b39a098e6a882c060d6b"}, + {file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:980ba47c8db4b9d870014c7040edb230825b79017a6a27aa54cdb6fcc02d8cc0"}, + {file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6507c58431dbd95b50654b3313c5ad54f90e54e5f2cdacf733de61eae478eec5"}, + {file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4a45a278518e4308865c1e9dbb2c42ce84fb154efb03adeb16fdae3c1687c7c9"}, + {file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:59cea9ba1c675fbd6867ca1078fc717a113e7f5b7644943b74137b7cc55abebf"}, + {file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd39ef87fd1f7bb5c4aa53454936e6135cbfe03fe3744e8218be193f9e4fef16"}, + {file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e6bb39d91bf932e7520cb5718ae3c2f498052aca53294d5d59fdd9068fe1a7f2"}, + {file = "lxml-5.0.0-cp311-cp311-win32.whl", hash = "sha256:21af2c3862db6f4f486cddf73ec1157b40d5828876c47cd880edcbad8240ea1b"}, + {file = "lxml-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:c1249aa4eaced30b59ecf8b8cae0b1ccede04583c74ca7d10b6f8bbead908b2c"}, + {file = "lxml-5.0.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f30e697b6215e759d0824768b2c5b0618d2dc19abe6c67eeed2b0460f52470d1"}, + {file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d1bb64646480c36a4aa1b6a44a5b6e33d0fcbeab9f53f1b39072cd3bb2c6243a"}, + {file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4e69c36c8618707a90ed3fb6f48a6cc9254ffcdbf7b259e439a5ae5fbf9c5206"}, + {file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9ca498f8554a09fbc3a2f8fc4b23261e07bc27bef99b3df98e2570688033f6fc"}, + {file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0326e9b8176ea77269fb39e7af4010906e73e9496a9f8eaf06d253b1b1231ceb"}, + {file = "lxml-5.0.0-cp312-cp312-win32.whl", hash = "sha256:5fb988e15378d6e905ca8f60813950a0c56da9469d0e8e5d8fe785b282684ec5"}, + {file = "lxml-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:bb58e8f4b2cfe012cd312239b8d5139995fe8f5945c7c26d5fbbbb1ddb9acd47"}, + {file = "lxml-5.0.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81509dffd8aba3bdb43e90cbd218c9c068a1f4047d97bc9546b3ac9e3a4ae81d"}, + {file = "lxml-5.0.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e675a4b95208e74c34ac0751cc4bab9170e7728b61601fb0f4746892c2bb7e0b"}, + {file = "lxml-5.0.0-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:405e3760f83a8ba3bdb6e622ec79595cdc20db916ce37377bbcb95b5711fa4ca"}, + {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f15844a1b93dcaa09c2b22e22a73384f3ae4502347c3881cfdd674e14ac04e21"}, + {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88f559f8beb6b90e41a7faae4aca4c8173a4819874a9bf8e74c8d7c1d51f3162"}, + {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e8c63f5c7d87e7044880b01851ac4e863c3349e6f6b6ab456fe218d9346e816d"}, + {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:0d277d4717756fe8816f0beeff229cb72f9dd02a43b70e1d3f07c8efadfb9fe1"}, + {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8954da15403db1acfc0544b3c3f963a6ef4e428283ab6555e3e298bbbff1cf6"}, + {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aebd8fd378e074b22e79cad329dcccd243c40ff1cafaa512d19276c5bb9554e1"}, + {file = "lxml-5.0.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b6d4e148edee59c2ad38af15810dbcb8b5d7b13e5de3509d8cf3edfe74c0adca"}, + {file = "lxml-5.0.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:70ab4e02f7aa5fb4131c8b222a111ce7676f3767e36084fba3a4e7338dc82dcd"}, + {file = "lxml-5.0.0-cp36-cp36m-win32.whl", hash = "sha256:de1a8b54170024cf1c0c2718c82412bca42cd82e390556e3d8031af9541b416f"}, + {file = "lxml-5.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5b39f63edbe7e018c2ac1cf0259ee0dd2355274e8a3003d404699b040782e55e"}, + {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:77b73952534967a4497d9e4f26fbeebfba19950cbc66b7cc3a706214429d8106"}, + {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8cc0a951e5616ac626f7036309c41fb9774adcd4aa7db0886463da1ce5b65edb"}, + {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4b9d5b01900a760eb3acf6cef50aead4ef2fa79e7ddb927084244e41dfe37b65"}, + {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:173bcead3af5d87c7bca9a030675073ddaad8e0a9f0b04be07cd9390453e7226"}, + {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:44fa9afd632210f1eeda51cf284ed8dbab0c7ec8b008dd39ba02818e0e114e69"}, + {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fef10f27d6318d2d7c88680e113511ddecf09ee4f9559b3623b73ee89fa8f6cc"}, + {file = "lxml-5.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3663542aee845129a981889c19b366beab0b1dadcf5ca164696aabfe1aa51667"}, + {file = "lxml-5.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7188495c1bf71bfda87d78ed50601e72d252119ce11710d6e71ff36e35fea5a0"}, + {file = "lxml-5.0.0-cp37-cp37m-win32.whl", hash = "sha256:6a2de85deabf939b0af89e2e1ea46bfb1239545e2da6f8ac96522755a388025f"}, + {file = "lxml-5.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ea56825c1e23c9c8ea385a191dac75f9160477057285b88c88736d9305e6118f"}, + {file = "lxml-5.0.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:3f908afd0477cace17f941d1b9cfa10b769fe1464770abe4cfb3d9f35378d0f8"}, + {file = "lxml-5.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52a9ab31853d3808e7cf0183b3a5f7e8ffd622ea4aee1deb5252dbeaefd5b40d"}, + {file = "lxml-5.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c7fe19abb3d3c55a9e65d289b12ad73b3a31a3f0bda3c539a890329ae9973bd6"}, + {file = "lxml-5.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:1ef0793e1e2dd221fce7c142177008725680f7b9e4a184ab108d90d5d3ab69b7"}, + {file = "lxml-5.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:581a78f299a9f5448b2c3aea904bfcd17c59bf83016d221d7f93f83633bb2ab2"}, + {file = "lxml-5.0.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:affdd833f82334fdb10fc9a1c7b35cdb5a86d0b672b4e14dd542e1fe7bcea894"}, + {file = "lxml-5.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bba06d8982be0f0f6432d289a8d104417a0ab9ed04114446c4ceb6d4a40c65d"}, + {file = "lxml-5.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:80209b31dd3908bc5b014f540fd192c97ea52ab179713a730456c5baf7ce80c1"}, + {file = "lxml-5.0.0-cp38-cp38-win32.whl", hash = "sha256:dac2733fe4e159b0aae0439db6813b7b1d23ff96d0b34c0107b87faf79208c4e"}, + {file = "lxml-5.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:ee60f33456ff34b2dd1d048a740a2572798356208e4c494301c931de3a0ab3a2"}, + {file = "lxml-5.0.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:5eff173f0ff408bfa578cbdafd35a7e0ca94d1a9ffe09a8a48e0572d0904d486"}, + {file = "lxml-5.0.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:78d6d8e5b54ed89dc0f0901eaaa579c384ad8d59fa43cc7fb06e9bb89115f8f4"}, + {file = "lxml-5.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:71a7cee869578bc17b18050532bb2f0bc682a7b97dda77041741a1bd2febe6c7"}, + {file = "lxml-5.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7df433d08d4587dc3932f7fcfc3194519a6824824104854e76441fd3bc000d29"}, + {file = "lxml-5.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:793be9b4945c2dfd69828fb5948d7d9569b78e0599e4a2e88d92affeb0ff3aa3"}, + {file = "lxml-5.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7cfb6af73602c8d288581df8a225989d7e9d5aab0a174be0e19fcfa800b6797"}, + {file = "lxml-5.0.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bfdc4668ac56687a89ca3eca44231144a2e9d02ba3b877558db74ba20e2bd9fa"}, + {file = "lxml-5.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2992591e2294bb07faf7f5f6d5cb60710c046404f4bfce09fb488b85d2a8f58f"}, + {file = "lxml-5.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4786b0af7511ea614fd86407a52a7bc161aa5772d311d97df2591ed2351de768"}, + {file = "lxml-5.0.0-cp39-cp39-win32.whl", hash = "sha256:016de3b29a262655fc3d2075dc1b2611f84f4c3d97a71d579c883d45e201eee4"}, + {file = "lxml-5.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:52c0acc2f29b0a204efc11a5ed911a74f50a25eb7d7d5069c2b1fd3b3346ce11"}, + {file = "lxml-5.0.0-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:96095bfc0c02072fc89afa67626013a253596ea5118b8a7f4daaae049dafa096"}, + {file = "lxml-5.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:992029258ed719f130d5a9c443d142c32843046f1263f2c492862b2a853be570"}, + {file = "lxml-5.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:db40e85cffd22f7d65dcce30e85af565a66401a6ed22fc0c56ed342cfa4ffc43"}, + {file = "lxml-5.0.0-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:cfa8a4cdc3765574b7fd0c7cfa5fbd1e2108014c9dfd299c679e5152bea9a55e"}, + {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:049fef98d02513c34f5babd07569fc1cf1ed14c0f2fbff18fe72597f977ef3c2"}, + {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a85136d0ee18a41c91cc3e2844c683be0e72e6dda4cb58da9e15fcaef3726af7"}, + {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:766868f729f3ab84125350f1a0ea2594d8b1628a608a574542a5aff7355b9941"}, + {file = "lxml-5.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:99cad5c912f359e59e921689c04e54662cdd80835d80eeaa931e22612f515df7"}, + {file = "lxml-5.0.0-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:c90c593aa8dd57d5dab0ef6d7d64af894008971d98e6a41b320fdd75258fbc6e"}, + {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8134d5441d1ed6a682e3de3d7a98717a328dce619ee9c4c8b3b91f0cb0eb3e28"}, + {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f298ac9149037d6a3d5c74991bded39ac46292520b9c7c182cb102486cc87677"}, + {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:894c5f71186b410679aaab5774543fcb9cbabe8893f0b31d11cf28a0740e80be"}, + {file = "lxml-5.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9cd3d6c2c67d4fdcd795e4945e2ba5434909c96640b4cc09453bd0dc7e8e1bac"}, + {file = "lxml-5.0.0.zip", hash = "sha256:2219cbf790e701acf9a21a31ead75f983e73daf0eceb9da6990212e4d20ebefe"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.35)"] +source = ["Cython (>=3.0.7)"] [[package]] name = "markdown-it-py" @@ -2214,13 +2242,13 @@ resolved_reference = "72e842cd1d025bf676e4af8797a01e4aa282109f" [[package]] name = "mpld3" -version = "0.5.9" +version = "0.5.10" description = "D3 Viewer for Matplotlib" optional = false python-versions = "*" files = [ - {file = "mpld3-0.5.9-py3-none-any.whl", hash = "sha256:cecdd988b24f8d7c7f362b8f72bb91df1a24b1993dd02a958223918a31c31052"}, - {file = "mpld3-0.5.9.tar.gz", hash = "sha256:d8d228b2911132fd0154e2a49543b7f97247f9851cfe468c6b616f452c676158"}, + {file = "mpld3-0.5.10-py3-none-any.whl", hash = "sha256:80877acce87ea447380fad7374668737505c8c0684aab05238e7c5dc1fab38c1"}, + {file = "mpld3-0.5.10.tar.gz", hash = "sha256:a478eb404fa5212505c59133cf272cd9a94105872e605597720e7f84de38fbc7"}, ] [package.dependencies] @@ -2246,13 +2274,13 @@ tests = ["pytest (>=4.6)"] [[package]] name = "msal" -version = "1.25.0" -description = "The Microsoft Authentication Library (MSAL) for Python library" +version = "1.26.0" +description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." optional = false python-versions = ">=2.7" files = [ - {file = "msal-1.25.0-py2.py3-none-any.whl", hash = "sha256:386df621becb506bc315a713ec3d4d5b5d6163116955c7dde23622f156b81af6"}, - {file = "msal-1.25.0.tar.gz", hash = "sha256:f44329fdb59f4f044c779164a34474b8a44ad9e4940afbc4c3a3a2bbe90324d9"}, + {file = "msal-1.26.0-py2.py3-none-any.whl", hash = "sha256:be77ba6a8f49c9ff598bbcdc5dfcf1c9842f3044300109af738e8c3e371065b5"}, + {file = "msal-1.26.0.tar.gz", hash = "sha256:224756079fe338be838737682b49f8ebc20a87c1c5eeaf590daae4532b83de15"}, ] [package.dependencies] @@ -2265,20 +2293,21 @@ broker = ["pymsalruntime (>=0.13.2,<0.14)"] [[package]] name = "msal-extensions" -version = "1.0.0" +version = "1.1.0" description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "msal-extensions-1.0.0.tar.gz", hash = "sha256:c676aba56b0cce3783de1b5c5ecfe828db998167875126ca4b47dc6436451354"}, - {file = "msal_extensions-1.0.0-py2.py3-none-any.whl", hash = "sha256:91e3db9620b822d0ed2b4d1850056a0f133cba04455e62f11612e40f5502f2ee"}, + {file = "msal-extensions-1.1.0.tar.gz", hash = "sha256:6ab357867062db7b253d0bd2df6d411c7891a0ee7308d54d1e4317c1d1c54252"}, + {file = "msal_extensions-1.1.0-py3-none-any.whl", hash = "sha256:01be9711b4c0b1a151450068eeb2c4f0997df3bba085ac299de3a66f585e382f"}, ] [package.dependencies] msal = ">=0.4.1,<2.0.0" +packaging = "*" portalocker = [ - {version = ">=1.0,<3", markers = "python_version >= \"3.5\" and platform_system != \"Windows\""}, - {version = ">=1.6,<3", markers = "python_version >= \"3.5\" and platform_system == \"Windows\""}, + {version = ">=1.0,<3", markers = "platform_system != \"Windows\""}, + {version = ">=1.6,<3", markers = "platform_system == \"Windows\""}, ] [[package]] @@ -2366,38 +2395,38 @@ files = [ [[package]] name = "mypy" -version = "1.7.1" +version = "1.8.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, - {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, - {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, - {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, - {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, - {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, - {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, - {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, - {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, - {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, - {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, - {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, - {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, - {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, - {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, - {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, - {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, - {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, - {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] @@ -2622,18 +2651,18 @@ sympy = "*" [[package]] name = "opencv-python" -version = "4.8.1.78" +version = "4.9.0.80" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-4.8.1.78.tar.gz", hash = "sha256:cc7adbbcd1112877a39274106cb2752e04984bc01a031162952e97450d6117f6"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:91d5f6f5209dc2635d496f6b8ca6573ecdad051a09e6b5de4c399b8e673c60da"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31f47e05447da8b3089faa0a07ffe80e114c91ce0b171e6424f9badbd1c5cd"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9814beca408d3a0eca1bae7e3e5be68b07c17ecceb392b94170881216e09b319"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c406bdb41eb21ea51b4e90dfbc989c002786c3f601c236a99c59a54670a394"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:a7aac3900fbacf55b551e7b53626c3dad4c71ce85643645c43e91fcb19045e47"}, - {file = "opencv_python-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:b983197f97cfa6fcb74e1da1802c7497a6f94ed561aba6980f1f33123f904956"}, + {file = "opencv-python-4.9.0.80.tar.gz", hash = "sha256:1a9f0e6267de3a1a1db0c54213d022c7c8b5b9ca4b580e80bdc58516c922c9e1"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:7e5f7aa4486651a6ebfa8ed4b594b65bd2d2f41beeb4241a3e4b1b85acbbbadb"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71dfb9555ccccdd77305fc3dcca5897fbf0cf28b297c51ee55e079c065d812a3"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b34a52e9da36dda8c151c6394aed602e4b17fa041df0b9f5b93ae10b0fcca2a"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4088cab82b66a3b37ffc452976b14a3c599269c247895ae9ceb4066d8188a57"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:dcf000c36dd1651118a2462257e3a9e76db789a78432e1f303c7bac54f63ef6c"}, + {file = "opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:3f16f08e02b2a2da44259c7cc712e779eff1dd8b55fdb0323e8cab09548086c0"}, ] [package.dependencies] @@ -2641,18 +2670,18 @@ numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "opencv-python-headless" -version = "4.8.1.78" +version = "4.9.0.80" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-headless-4.8.1.78.tar.gz", hash = "sha256:bc7197b42352f6f865c302a49140b889ec7cd957dd697e2d7fc016ad0d3f28f1"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:f3a33f644249f9ce1c913eac580e4b3ef4ce7cab0a71900274708959c2feb5e3"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:2c7d45721df9801c4dcd34683a15caa0e30f38b185263fec04a6eb274bc720f0"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b6bd6e1132b6f5dcb3a5bfe30fc4d341a7bfb26134da349a06c9255288ded94"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58e70d2f0915fe23e02c6e405588276c9397844a47d38b9c87fac5f7f9ba2dcc"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:382f8c7a6a14f80091284eecedd52cee4812231ee0eff1118592197b538d9252"}, - {file = "opencv_python_headless-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:0a0f1e9f836f7d5bad1dd164694944c8761711cbdf4b36ebbd4815a8ef731079"}, + {file = "opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958"}, + {file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a"}, + {file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6"}, + {file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d"}, + {file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df"}, + {file = "opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670"}, + {file = "opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c"}, ] [package.dependencies] @@ -2771,53 +2800,54 @@ panda3d-simplepbr = ">=0.6" [[package]] name = "panda3d-simplepbr" -version = "0.10" +version = "0.11.2" description = "A simple, lightweight PBR render pipeline for Panda3D" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "panda3d-simplepbr-0.10.tar.gz", hash = "sha256:b6bd902671b02c8794eb60d8039f256a4115faef067c1368cc70147f8ed83a31"}, - {file = "panda3d_simplepbr-0.10-py3-none-any.whl", hash = "sha256:c526a0fa5b2af8a2f16ee2837dcedbf5987ca917ee95699830675204bf765fb4"}, + {file = "panda3d-simplepbr-0.11.2.tar.gz", hash = "sha256:89469f9be0e15fd8888a277f60ba08eb31b90354dcba80fc5aa7ccccee56be2a"}, + {file = "panda3d_simplepbr-0.11.2-py3-none-any.whl", hash = "sha256:eca198004ed4a6635ac9180695440390d52c73eec0b6e02c317d000e1e4d1518"}, ] [package.dependencies] panda3d = ">=1.10.8" +typing-extensions = ">=4.7,<5.0" [package.extras] -test = ["pylint (==2.4.*)", "pytest", "pytest-pylint"] +test = ["pylint (>=3.0.0,<3.1.0)", "pytest", "pytest-pylint"] [[package]] name = "pandas" -version = "2.1.3" +version = "2.1.4" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f"}, - {file = "pandas-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb"}, - {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb"}, - {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4"}, - {file = "pandas-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03"}, - {file = "pandas-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e"}, - {file = "pandas-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82"}, - {file = "pandas-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042"}, - {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef"}, - {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58"}, - {file = "pandas-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2"}, - {file = "pandas-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f"}, - {file = "pandas-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140"}, - {file = "pandas-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d"}, - {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e"}, - {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683"}, - {file = "pandas-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00"}, - {file = "pandas-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549"}, - {file = "pandas-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8"}, - {file = "pandas-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d"}, - {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a"}, - {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2"}, - {file = "pandas-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a"}, - {file = "pandas-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71"}, - {file = "pandas-2.1.3.tar.gz", hash = "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f"}, + {file = "pandas-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdec823dc6ec53f7a6339a0e34c68b144a7a1fd28d80c260534c39c62c5bf8c9"}, + {file = "pandas-2.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:294d96cfaf28d688f30c918a765ea2ae2e0e71d3536754f4b6de0ea4a496d034"}, + {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b728fb8deba8905b319f96447a27033969f3ea1fea09d07d296c9030ab2ed1d"}, + {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00028e6737c594feac3c2df15636d73ace46b8314d236100b57ed7e4b9ebe8d9"}, + {file = "pandas-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:426dc0f1b187523c4db06f96fb5c8d1a845e259c99bda74f7de97bd8a3bb3139"}, + {file = "pandas-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:f237e6ca6421265643608813ce9793610ad09b40154a3344a088159590469e46"}, + {file = "pandas-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b7d852d16c270e4331f6f59b3e9aa23f935f5c4b0ed2d0bc77637a8890a5d092"}, + {file = "pandas-2.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7d5f2f54f78164b3d7a40f33bf79a74cdee72c31affec86bfcabe7e0789821"}, + {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa6e92e639da0d6e2017d9ccff563222f4eb31e4b2c3cf32a2a392fc3103c0d"}, + {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d797591b6846b9db79e65dc2d0d48e61f7db8d10b2a9480b4e3faaddc421a171"}, + {file = "pandas-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2d3e7b00f703aea3945995ee63375c61b2e6aa5aa7871c5d622870e5e137623"}, + {file = "pandas-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc9bf7ade01143cddc0074aa6995edd05323974e6e40d9dbde081021ded8510e"}, + {file = "pandas-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:482d5076e1791777e1571f2e2d789e940dedd927325cc3cb6d0800c6304082f6"}, + {file = "pandas-2.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a706cfe7955c4ca59af8c7a0517370eafbd98593155b48f10f9811da440248b"}, + {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0513a132a15977b4a5b89aabd304647919bc2169eac4c8536afb29c07c23540"}, + {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9f17f2b6fc076b2a0078862547595d66244db0f41bf79fc5f64a5c4d635bead"}, + {file = "pandas-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:45d63d2a9b1b37fa6c84a68ba2422dc9ed018bdaa668c7f47566a01188ceeec1"}, + {file = "pandas-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f69b0c9bb174a2342818d3e2778584e18c740d56857fc5cdb944ec8bbe4082cf"}, + {file = "pandas-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f06bda01a143020bad20f7a85dd5f4a1600112145f126bc9e3e42077c24ef34"}, + {file = "pandas-2.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab5796839eb1fd62a39eec2916d3e979ec3130509930fea17fe6f81e18108f6a"}, + {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbaf9e8d3a63a9276d707b4d25930a262341bca9874fcb22eff5e3da5394732"}, + {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ebfd771110b50055712b3b711b51bee5d50135429364d0498e1213a7adc2be8"}, + {file = "pandas-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ea107e0be2aba1da619cc6ba3f999b2bfc9669a83554b1904ce3dd9507f0860"}, + {file = "pandas-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:d65148b14788b3758daf57bf42725caa536575da2b64df9964c563b015230984"}, + {file = "pandas-2.1.4.tar.gz", hash = "sha256:fcb68203c833cc735321512e13861358079a96c174a61f5116a1de89c58c0ef7"}, ] [package.dependencies] @@ -2933,13 +2963,13 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "4.0.0" +version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, - {file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, + {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, + {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, ] [package.extras] @@ -3007,13 +3037,13 @@ files = [ [[package]] name = "pre-commit" -version = "3.5.0" +version = "3.6.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, - {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, + {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, + {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, ] [package.dependencies] @@ -3055,27 +3085,27 @@ files = [ [[package]] name = "psutil" -version = "5.9.6" +version = "5.9.7" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "psutil-5.9.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d"}, - {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c"}, - {file = "psutil-5.9.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28"}, - {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017"}, - {file = "psutil-5.9.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c"}, - {file = "psutil-5.9.6-cp27-none-win32.whl", hash = "sha256:70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9"}, - {file = "psutil-5.9.6-cp27-none-win_amd64.whl", hash = "sha256:51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac"}, - {file = "psutil-5.9.6-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a"}, - {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c"}, - {file = "psutil-5.9.6-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4"}, - {file = "psutil-5.9.6-cp36-cp36m-win32.whl", hash = "sha256:3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602"}, - {file = "psutil-5.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa"}, - {file = "psutil-5.9.6-cp37-abi3-win32.whl", hash = "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c"}, - {file = "psutil-5.9.6-cp37-abi3-win_amd64.whl", hash = "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a"}, - {file = "psutil-5.9.6-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57"}, - {file = "psutil-5.9.6.tar.gz", hash = "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a"}, + {file = "psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"}, + {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"}, + {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:fe361f743cb3389b8efda21980d93eb55c1f1e3898269bc9a2a1d0bb7b1f6508"}, + {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e469990e28f1ad738f65a42dcfc17adaed9d0f325d55047593cb9033a0ab63df"}, + {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3c4747a3e2ead1589e647e64aad601981f01b68f9398ddf94d01e3dc0d1e57c7"}, + {file = "psutil-5.9.7-cp27-none-win32.whl", hash = "sha256:1d4bc4a0148fdd7fd8f38e0498639ae128e64538faa507df25a20f8f7fb2341c"}, + {file = "psutil-5.9.7-cp27-none-win_amd64.whl", hash = "sha256:4c03362e280d06bbbfcd52f29acd79c733e0af33d707c54255d21029b8b32ba6"}, + {file = "psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e"}, + {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284"}, + {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe"}, + {file = "psutil-5.9.7-cp36-cp36m-win32.whl", hash = "sha256:b27f8fdb190c8c03914f908a4555159327d7481dac2f01008d483137ef3311a9"}, + {file = "psutil-5.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:44969859757f4d8f2a9bd5b76eba8c3099a2c8cf3992ff62144061e39ba8568e"}, + {file = "psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68"}, + {file = "psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414"}, + {file = "psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340"}, + {file = "psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c"}, ] [package.extras] @@ -3157,43 +3187,43 @@ files = [ [[package]] name = "pycryptodome" -version = "3.19.0" +version = "3.19.1" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3006c44c4946583b6de24fe0632091c2653d6256b99a02a3db71ca06472ea1e4"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c760c8a0479a4042111a8dd2f067d3ae4573da286c53f13cf6f5c53a5c1f631"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:08ce3558af5106c632baf6d331d261f02367a6bc3733086ae43c0f988fe042db"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45430dfaf1f421cf462c0dd824984378bef32b22669f2635cb809357dbaab405"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a9bcd5f3794879e91970f2bbd7d899780541d3ff439d8f2112441769c9f2ccea"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:190c53f51e988dceb60472baddce3f289fa52b0ec38fbe5fd20dd1d0f795c551"}, - {file = "pycryptodome-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:22e0ae7c3a7f87dcdcf302db06ab76f20e83f09a6993c160b248d58274473bfa"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:7822f36d683f9ad7bc2145b2c2045014afdbbd1d9922a6d4ce1cbd6add79a01e"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:05e33267394aad6db6595c0ce9d427fe21552f5425e116a925455e099fdf759a"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829b813b8ee00d9c8aba417621b94bc0b5efd18c928923802ad5ba4cf1ec709c"}, - {file = "pycryptodome-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:fc7a79590e2b5d08530175823a242de6790abc73638cc6dc9d2684e7be2f5e49"}, - {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:542f99d5026ac5f0ef391ba0602f3d11beef8e65aae135fa5b762f5ebd9d3bfb"}, - {file = "pycryptodome-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:61bb3ccbf4bf32ad9af32da8badc24e888ae5231c617947e0f5401077f8b091f"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d49a6c715d8cceffedabb6adb7e0cbf41ae1a2ff4adaeec9432074a80627dea1"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e249a784cc98a29c77cea9df54284a44b40cafbfae57636dd2f8775b48af2434"}, - {file = "pycryptodome-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d033947e7fd3e2ba9a031cb2d267251620964705a013c5a461fa5233cc025270"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:84c3e4fffad0c4988aef0d5591be3cad4e10aa7db264c65fadbc633318d20bde"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:139ae2c6161b9dd5d829c9645d781509a810ef50ea8b657e2257c25ca20efe33"}, - {file = "pycryptodome-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5b1986c761258a5b4332a7f94a83f631c1ffca8747d75ab8395bf2e1b93283d9"}, - {file = "pycryptodome-3.19.0-cp35-abi3-win32.whl", hash = "sha256:536f676963662603f1f2e6ab01080c54d8cd20f34ec333dcb195306fa7826997"}, - {file = "pycryptodome-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:04dd31d3b33a6b22ac4d432b3274588917dcf850cc0c51c84eca1d8ed6933810"}, - {file = "pycryptodome-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:8999316e57abcbd8085c91bc0ef75292c8618f41ca6d2b6132250a863a77d1e7"}, - {file = "pycryptodome-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:a0ab84755f4539db086db9ba9e9f3868d2e3610a3948cbd2a55e332ad83b01b0"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0101f647d11a1aae5a8ce4f5fad6644ae1b22bb65d05accc7d322943c69a74a6"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c1601e04d32087591d78e0b81e1e520e57a92796089864b20e5f18c9564b3fa"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:506c686a1eee6c00df70010be3b8e9e78f406af4f21b23162bbb6e9bdf5427bc"}, - {file = "pycryptodome-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7919ccd096584b911f2a303c593280869ce1af9bf5d36214511f5e5a1bed8c34"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:560591c0777f74a5da86718f70dfc8d781734cf559773b64072bbdda44b3fc3e"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1cc2f2ae451a676def1a73c1ae9120cd31af25db3f381893d45f75e77be2400"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17940dcf274fcae4a54ec6117a9ecfe52907ed5e2e438fe712fe7ca502672ed5"}, - {file = "pycryptodome-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d04f5f623a280fbd0ab1c1d8ecbd753193ab7154f09b6161b0f857a1a676c15f"}, - {file = "pycryptodome-3.19.0.tar.gz", hash = "sha256:bc35d463222cdb4dbebd35e0784155c81e161b9284e567e7e933d722e533331e"}, + {file = "pycryptodome-3.19.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:694020d2ff985cd714381b9da949a21028c24b86f562526186f6af7c7547e986"}, + {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:4464b0e8fd5508bff9baf18e6fd4c6548b1ac2ce9862d6965ff6a84ec9cb302a"}, + {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:420972f9c62978e852c74055d81c354079ce3c3a2213a92c9d7e37bbc63a26e2"}, + {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1bc0c49d986a1491d66d2a56570f12e960b12508b7e71f2423f532e28857f36"}, + {file = "pycryptodome-3.19.1-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e038ab77fec0956d7aa989a3c647652937fc142ef41c9382c2ebd13c127d5b4a"}, + {file = "pycryptodome-3.19.1-cp27-cp27m-win32.whl", hash = "sha256:a991f8ffe8dfe708f86690948ae46442eebdd0fff07dc1b605987939a34ec979"}, + {file = "pycryptodome-3.19.1-cp27-cp27m-win_amd64.whl", hash = "sha256:2c16426ef49d9cba018be2340ea986837e1dfa25c2ea181787971654dd49aadd"}, + {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6d0d2b97758ebf2f36c39060520447c26455acb3bcff309c28b1c816173a6ff5"}, + {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:b8b80ff92049fd042177282917d994d344365ab7e8ec2bc03e853d93d2401786"}, + {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd4e7e8bf0fc1ada854688b9b309ee607e2aa85a8b44180f91021a4dd330a928"}, + {file = "pycryptodome-3.19.1-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:8cf5d3d6cf921fa81acd1f632f6cedcc03f5f68fc50c364cd39490ba01d17c49"}, + {file = "pycryptodome-3.19.1-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:67939a3adbe637281c611596e44500ff309d547e932c449337649921b17b6297"}, + {file = "pycryptodome-3.19.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:11ddf6c9b52116b62223b6a9f4741bc4f62bb265392a4463282f7f34bb287180"}, + {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e6f89480616781d2a7f981472d0cdb09b9da9e8196f43c1234eff45c915766"}, + {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e1efcb68993b7ce5d1d047a46a601d41281bba9f1971e6be4aa27c69ab8065"}, + {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c6273ca5a03b672e504995529b8bae56da0ebb691d8ef141c4aa68f60765700"}, + {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b0bfe61506795877ff974f994397f0c862d037f6f1c0bfc3572195fc00833b96"}, + {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:f34976c5c8eb79e14c7d970fb097482835be8d410a4220f86260695ede4c3e17"}, + {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7c9e222d0976f68d0cf6409cfea896676ddc1d98485d601e9508f90f60e2b0a2"}, + {file = "pycryptodome-3.19.1-cp35-abi3-win32.whl", hash = "sha256:4805e053571140cb37cf153b5c72cd324bb1e3e837cbe590a19f69b6cf85fd03"}, + {file = "pycryptodome-3.19.1-cp35-abi3-win_amd64.whl", hash = "sha256:a470237ee71a1efd63f9becebc0ad84b88ec28e6784a2047684b693f458f41b7"}, + {file = "pycryptodome-3.19.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:ed932eb6c2b1c4391e166e1a562c9d2f020bfff44a0e1b108f67af38b390ea89"}, + {file = "pycryptodome-3.19.1-pp27-pypy_73-win32.whl", hash = "sha256:81e9d23c0316fc1b45d984a44881b220062336bbdc340aa9218e8d0656587934"}, + {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37e531bf896b70fe302f003d3be5a0a8697737a8d177967da7e23eff60d6483c"}, + {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd4e95b0eb4b28251c825fe7aa941fe077f993e5ca9b855665935b86fbb1cc08"}, + {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22c80246c3c880c6950d2a8addf156cee74ec0dc5757d01e8e7067a3c7da015"}, + {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e70f5c839c7798743a948efa2a65d1fe96bb397fe6d7f2bde93d869fe4f0ad69"}, + {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6c3df3613592ea6afaec900fd7189d23c8c28b75b550254f4bd33fe94acb84b9"}, + {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08b445799d571041765e7d5c9ca09c5d3866c2f22eeb0dd4394a4169285184f4"}, + {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:954d156cd50130afd53f8d77f830fe6d5801bd23e97a69d358fed068f433fbfe"}, + {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b7efd46b0b4ac869046e814d83244aeab14ef787f4850644119b1c8b0ec2d637"}, + {file = "pycryptodome-3.19.1.tar.gz", hash = "sha256:8ae0dd1bcfada451c35f9e29a3e5db385caabc190f98e4a80ad02a61098fb776"}, ] [[package]] @@ -3316,66 +3346,42 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylibsrtp" -version = "0.8.0" +version = "0.9.1" description = "Python wrapper around the libsrtp library" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "pylibsrtp-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e42071588f006da698b821c43a9f4db6c377835eb41378876fd08e6240213fb6"}, - {file = "pylibsrtp-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1da3b1c19c3e1f24bb258076b7fb2e8980a3e1116afdd2569e7904d212f122a7"}, - {file = "pylibsrtp-0.8.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dd443522ebb59aef3fdc06a85cdafdb17052ada3ab02f00e59aac99207409d5"}, - {file = "pylibsrtp-0.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:154f63b43360da574c9af3a81d032a3c51a2e801cb7a71deb9f74a930755df44"}, - {file = "pylibsrtp-0.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fdf9f2e373b740325e9ea577cc173bef416d84bccafafba8ee16d08db6abea9"}, - {file = "pylibsrtp-0.8.0-cp310-cp310-win32.whl", hash = "sha256:aa729b70d17998fc8f60a137ee2befb65dc883981c9b3dc10c39f4437cd2249e"}, - {file = "pylibsrtp-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:a2e1c8c09f4706e7ca8e78bbbbae837b4ce708c3d6facc6719bcbc601004f1e8"}, - {file = "pylibsrtp-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c52b846acb092b2f728684e6430116b532903b71e788b6713e928bd91cafb5c5"}, - {file = "pylibsrtp-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d2a11ef5e41bb490e871cced1e9dea35485befb5c1478f1aa5cbd430c934509"}, - {file = "pylibsrtp-0.8.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30380f1fd3976056095352484f38606a2fc05ab14fff03900fc3202201e42967"}, - {file = "pylibsrtp-0.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f6374312fb94c51b22260e0f620299c7c4f12fad5b75e862b2b2c55e2d493d6"}, - {file = "pylibsrtp-0.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:754bb1aaac9f3f477dca2c8b3e31b13587e762a182e64e1ed271a4e6715d2f51"}, - {file = "pylibsrtp-0.8.0-cp311-cp311-win32.whl", hash = "sha256:cdf5261d80ae821f4cda90c5835577632db2a6e36af3985d94b922d461ddb34b"}, - {file = "pylibsrtp-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba2c32188595f3c70f1ac44a81f549b118f54de037158ac96929bdf48dcb8145"}, - {file = "pylibsrtp-0.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6462390d7b3e6452e9504088094dadfa7e83d6fe33225e66ed4a483ab8937262"}, - {file = "pylibsrtp-0.8.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:102c4d4198c9e02ed765e2d74322fb86c623554e4425303b15fff9af2dc4f854"}, - {file = "pylibsrtp-0.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b1781e8bc43fb07694ec2ee3f82c34eb956c350fd97b781c81b13c175bd786"}, - {file = "pylibsrtp-0.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cddabf775098282667396da37d343b0dd8199cf7f3bec4b150a5ce38c482e30e"}, - {file = "pylibsrtp-0.8.0-cp37-cp37m-win32.whl", hash = "sha256:9ae2f71ac4b1d6d52c38eca609c360685f6c410b1b89f38488f96533c50e6b69"}, - {file = "pylibsrtp-0.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a82c40c28a28383c35d7c163f1cd3a31c418ac5f3e1c129a644c9e521cac8d"}, - {file = "pylibsrtp-0.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1f33bb54320b337dfbafd43b693315b14c7c454258c4dd9b35a5b5f10a2f3b84"}, - {file = "pylibsrtp-0.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3284034b0c4671cda2d0b38f4e85766fe4bd61a55ee3f966ba7c77eb6c96579a"}, - {file = "pylibsrtp-0.8.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8eda6e155679f070e7ac65fe39287cab6523a75c781dd0a457024fe588438599"}, - {file = "pylibsrtp-0.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:088c9d5cf3b0200cf78b68ab03b4118cb76160baedbee5dac7503dafc6d394f4"}, - {file = "pylibsrtp-0.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffbba66a97f61ddd24e9eaa2bff967c21e1028fbe0ef35b4b701717e10e33e96"}, - {file = "pylibsrtp-0.8.0-cp38-cp38-win32.whl", hash = "sha256:8a2b71dfddcaa71671970e4ce452293836bad0b93a1c1766d7cffb231c4601e4"}, - {file = "pylibsrtp-0.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:b42f0068409cdcb1e54f2a48fea92bae07232991938601f0c16f837299df1583"}, - {file = "pylibsrtp-0.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8150b70eb2557ffaae46172ebf86b6e1ae67bb25c207445988d573d4b57e6aa6"}, - {file = "pylibsrtp-0.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d5dd8837a522565766f427793ce644ed2d2bcd0d372f8683269e6d525646b7c6"}, - {file = "pylibsrtp-0.8.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51e06452152a36a97545becf6617141ac68def6df90c1e6857d61432b97d5e0"}, - {file = "pylibsrtp-0.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1de6b0e4e5ceecbe908b0f86db0d9da28fdcb05cc605e5a1f06628540ac0e7a"}, - {file = "pylibsrtp-0.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c42b2eb3106d34906eedcba9d72f121f6e2f28f061b91ccde4f68af58fc1c84"}, - {file = "pylibsrtp-0.8.0-cp39-cp39-win32.whl", hash = "sha256:684ee6b57be78cdae34ede1641c95eba0ab647f47c12456b6393bfc1425737bd"}, - {file = "pylibsrtp-0.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:6973fbaa0aecb24c4736e953b37afe7245952b4b8b15ab69b236edff04703f5d"}, - {file = "pylibsrtp-0.8.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7333af56ec3f6989cf41c804df42123615f696c3d1e8270f6d4ab51a6e631f14"}, - {file = "pylibsrtp-0.8.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1332e7165a2419033bc90497083345fa802ffe8220e0efc176e892e8be1c19f"}, - {file = "pylibsrtp-0.8.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02d66058ed065163c6aa61fd64a07b7a89a13a5114bc63276ec3a0007cc3aa3e"}, - {file = "pylibsrtp-0.8.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:472f98d128db2791e8931b0260a6309b0258903fe8e6f9669fff115d6e6b094e"}, - {file = "pylibsrtp-0.8.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:89278f1e1ffc6afdcbab4898e04f28f9fd7f15c548d9583b05b691133671d7b7"}, - {file = "pylibsrtp-0.8.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a9ab76d23295e44d46c4cfd7c12e16b6956d465982aae652588beb62184c6232"}, - {file = "pylibsrtp-0.8.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836ba84b644c3ccb2155dea8695e8f578774465ff4c921646191be5ae051b756"}, - {file = "pylibsrtp-0.8.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9aab5ca601a60160d4afe462f8497d726055eb8cd8eeaae5ef07f1bfdac529f"}, - {file = "pylibsrtp-0.8.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3626cf72519170be626acc5a4a409b5c82879d1d59cfcd0cd834a0dd26d10695"}, - {file = "pylibsrtp-0.8.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d102822356026aff7f6b2b8dbc0c0d570c13504ea14bcfaf4e41cd14aa494286"}, - {file = "pylibsrtp-0.8.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:974a810cba9e69b3ce055a4935f725b0fa91d635c0e90b42b0af5514bfb5d147"}, - {file = "pylibsrtp-0.8.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2db61226c3783fde40313b93df7e51d70f28e1de3439ee6f227da10c3f3e277d"}, - {file = "pylibsrtp-0.8.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bc815735333d87adee37aa8494ec466ed9cb188437ffaf82e01ba403b293022"}, - {file = "pylibsrtp-0.8.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c18b542e70b6b995dfb7d79eddf9e5f81c618fb540fc4a52b79bef92f7e34e0"}, - {file = "pylibsrtp-0.8.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:736ee2ac2b1d7dab1671bc40fab84dc7877ee7abb92cf72218e8b2faeff67c93"}, - {file = "pylibsrtp-0.8.0.tar.gz", hash = "sha256:b2e5191fcf027f81a58db3dec743716ed0e1508c27d0883bcb47730ff380d6f0"}, + {file = "pylibsrtp-0.9.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed9e4babf175f5d3eb3814ea4c4481fff31156050697ca771b292e93da5ff666"}, + {file = "pylibsrtp-0.9.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e9e4ee8f82cca3d6a5d79da653ce727cb3c3a65ad1837aaa0a8c2c8bc10e763"}, + {file = "pylibsrtp-0.9.1-cp38-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54da85cff840b0b7dfb6ee7f62bc90741e489dae2b4cc524dbf6702a91ccd2ad"}, + {file = "pylibsrtp-0.9.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d7da7d6c9f420ef4cf44044ac0543a2584704ee13a48c6169abba2b27624b51"}, + {file = "pylibsrtp-0.9.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ebf704c1e9d827d63cfde4999cdb954247ddf97e47b8f768c1ea47cc20ec51c"}, + {file = "pylibsrtp-0.9.1-cp38-abi3-win32.whl", hash = "sha256:7f5bbd06b823e412deeacd617f09fbdcff484f9dd0d85fd377acb674b35b903f"}, + {file = "pylibsrtp-0.9.1-cp38-abi3-win_amd64.whl", hash = "sha256:4034d8eee4b0d6df018585717329698591492d0762055478dc46169516639f78"}, + {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d5ae5225d6555cc7e1fc3d80c4c802d938080259c0cabacc88572ddacb143512"}, + {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8f866e5564ea714dec283cd0064d4770eb3099ca993eea211a6b6d34f98aeb1"}, + {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11e84d0eb0c912b737202bcbe4c406105d488c61553479a3a66eeef130ff92e0"}, + {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1cffede6a3e90c19bbbe44a2ca5eee857de1483eb749cd532e0208bf53be6f"}, + {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:83d240572bfb35de7c93dc024722556b62b35d89813f40d4a9f7d0f3bf84554e"}, + {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9ee457fc74eb75ba78c5144e94ab8aa0be8447d74ab8b95d802c2c34f07a787b"}, + {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94b3dd175c9c1177f420cfabdbd425dc76499855d9defd1b33591d0f50465acf"}, + {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58f7a0bdfdc40d4fa95446b639e683e969d56a53b3255eae1ea3cdb785cc396d"}, + {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3a736189dc08ef2e6abdde2e7a94cc52c23b88030956023b4b92a946eccb57"}, + {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4927b4672041c0200d5ccb69d9a6f9934b340b7035b6e2ddd9c5e0069b22e9f8"}, + {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f51c91b89a692784fea909d8e3a64222ad5145d056d21fdaec92112efaa6c60"}, + {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:215bcd54f9072be8a8985bdb60bdd432bf63173ce4cc5bd417209724524b4d53"}, + {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41ffbe0cdcba5014bdbd080abccdc79066d2860a499791dd9f049ab6aec6a3ec"}, + {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9550f2b0313a7efb34762b6df444a75f1021ac8e0b824448173eafb7537018bd"}, + {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ccc5147887bf9eec58fc8c54e1fe200a3041b61143024a7b38389d932ee7ebd2"}, + {file = "pylibsrtp-0.9.1.tar.gz", hash = "sha256:3a51d7a18aa2338e10f54c1bd03e94fd760fabb69c2f5bf1dc590305b7ec2402"}, ] [package.dependencies] cffi = ">=1.0.0" +[package.extras] +dev = ["coverage[toml] (>=7.2.2)"] + [[package]] name = "pyopencl" version = "2023.1.4" @@ -3581,13 +3587,13 @@ cp2110 = ["hidapi"] [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -3834,104 +3840,104 @@ files = [ [[package]] name = "pyzmq" -version = "25.1.1" +version = "25.1.2" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.6" files = [ - {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, - {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, - {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, - {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, - {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, - {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, - {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, - {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, - {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, - {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, - {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, - {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, - {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, - {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, - {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, - {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, - {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, - {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, - {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, - {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, - {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, - {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, - {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, - {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, - {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, - {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, - {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, - {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, - {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, - {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, - {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, - {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, - {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, - {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, - {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, - {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, - {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, - {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, - {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, - {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, - {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, - {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, - {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, - {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, - {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, + {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:e624c789359f1a16f83f35e2c705d07663ff2b4d4479bad35621178d8f0f6ea4"}, + {file = "pyzmq-25.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:49151b0efece79f6a79d41a461d78535356136ee70084a1c22532fc6383f4ad0"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9a5f194cf730f2b24d6af1f833c14c10f41023da46a7f736f48b6d35061e76e"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:faf79a302f834d9e8304fafdc11d0d042266667ac45209afa57e5efc998e3872"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f51a7b4ead28d3fca8dda53216314a553b0f7a91ee8fc46a72b402a78c3e43d"}, + {file = "pyzmq-25.1.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0ddd6d71d4ef17ba5a87becf7ddf01b371eaba553c603477679ae817a8d84d75"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:246747b88917e4867e2367b005fc8eefbb4a54b7db363d6c92f89d69abfff4b6"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:00c48ae2fd81e2a50c3485de1b9d5c7c57cd85dc8ec55683eac16846e57ac979"}, + {file = "pyzmq-25.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5a68d491fc20762b630e5db2191dd07ff89834086740f70e978bb2ef2668be08"}, + {file = "pyzmq-25.1.2-cp310-cp310-win32.whl", hash = "sha256:09dfe949e83087da88c4a76767df04b22304a682d6154de2c572625c62ad6886"}, + {file = "pyzmq-25.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:fa99973d2ed20417744fca0073390ad65ce225b546febb0580358e36aa90dba6"}, + {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:82544e0e2d0c1811482d37eef297020a040c32e0687c1f6fc23a75b75db8062c"}, + {file = "pyzmq-25.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01171fc48542348cd1a360a4b6c3e7d8f46cdcf53a8d40f84db6707a6768acc1"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc69c96735ab501419c432110016329bf0dea8898ce16fab97c6d9106dc0b348"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3e124e6b1dd3dfbeb695435dff0e383256655bb18082e094a8dd1f6293114642"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7598d2ba821caa37a0f9d54c25164a4fa351ce019d64d0b44b45540950458840"}, + {file = "pyzmq-25.1.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d1299d7e964c13607efd148ca1f07dcbf27c3ab9e125d1d0ae1d580a1682399d"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4e6f689880d5ad87918430957297c975203a082d9a036cc426648fcbedae769b"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cc69949484171cc961e6ecd4a8911b9ce7a0d1f738fcae717177c231bf77437b"}, + {file = "pyzmq-25.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9880078f683466b7f567b8624bfc16cad65077be046b6e8abb53bed4eeb82dd3"}, + {file = "pyzmq-25.1.2-cp311-cp311-win32.whl", hash = "sha256:4e5837af3e5aaa99a091302df5ee001149baff06ad22b722d34e30df5f0d9097"}, + {file = "pyzmq-25.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:25c2dbb97d38b5ac9fd15586e048ec5eb1e38f3d47fe7d92167b0c77bb3584e9"}, + {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:11e70516688190e9c2db14fcf93c04192b02d457b582a1f6190b154691b4c93a"}, + {file = "pyzmq-25.1.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:313c3794d650d1fccaaab2df942af9f2c01d6217c846177cfcbc693c7410839e"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b3cbba2f47062b85fe0ef9de5b987612140a9ba3a9c6d2543c6dec9f7c2ab27"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc31baa0c32a2ca660784d5af3b9487e13b61b3032cb01a115fce6588e1bed30"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c9087b109070c5ab0b383079fa1b5f797f8d43e9a66c07a4b8b8bdecfd88ee"}, + {file = "pyzmq-25.1.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f8429b17cbb746c3e043cb986328da023657e79d5ed258b711c06a70c2ea7537"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5074adeacede5f810b7ef39607ee59d94e948b4fd954495bdb072f8c54558181"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7ae8f354b895cbd85212da245f1a5ad8159e7840e37d78b476bb4f4c3f32a9fe"}, + {file = "pyzmq-25.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b264bf2cc96b5bc43ce0e852be995e400376bd87ceb363822e2cb1964fcdc737"}, + {file = "pyzmq-25.1.2-cp312-cp312-win32.whl", hash = "sha256:02bbc1a87b76e04fd780b45e7f695471ae6de747769e540da909173d50ff8e2d"}, + {file = "pyzmq-25.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:ced111c2e81506abd1dc142e6cd7b68dd53747b3b7ae5edbea4578c5eeff96b7"}, + {file = "pyzmq-25.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7b6d09a8962a91151f0976008eb7b29b433a560fde056ec7a3db9ec8f1075438"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967668420f36878a3c9ecb5ab33c9d0ff8d054f9c0233d995a6d25b0e95e1b6b"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5edac3f57c7ddaacdb4d40f6ef2f9e299471fc38d112f4bc6d60ab9365445fb0"}, + {file = "pyzmq-25.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0dabfb10ef897f3b7e101cacba1437bd3a5032ee667b7ead32bbcdd1a8422fe7"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2c6441e0398c2baacfe5ba30c937d274cfc2dc5b55e82e3749e333aabffde561"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:16b726c1f6c2e7625706549f9dbe9b06004dfbec30dbed4bf50cbdfc73e5b32a"}, + {file = "pyzmq-25.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a86c2dd76ef71a773e70551a07318b8e52379f58dafa7ae1e0a4be78efd1ff16"}, + {file = "pyzmq-25.1.2-cp36-cp36m-win32.whl", hash = "sha256:359f7f74b5d3c65dae137f33eb2bcfa7ad9ebefd1cab85c935f063f1dbb245cc"}, + {file = "pyzmq-25.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:55875492f820d0eb3417b51d96fea549cde77893ae3790fd25491c5754ea2f68"}, + {file = "pyzmq-25.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8c8a419dfb02e91b453615c69568442e897aaf77561ee0064d789705ff37a92"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8807c87fa893527ae8a524c15fc505d9950d5e856f03dae5921b5e9aa3b8783b"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5e319ed7d6b8f5fad9b76daa0a68497bc6f129858ad956331a5835785761e003"}, + {file = "pyzmq-25.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3c53687dde4d9d473c587ae80cc328e5b102b517447456184b485587ebd18b62"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9add2e5b33d2cd765ad96d5eb734a5e795a0755f7fc49aa04f76d7ddda73fd70"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e690145a8c0c273c28d3b89d6fb32c45e0d9605b2293c10e650265bf5c11cfec"}, + {file = "pyzmq-25.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:00a06faa7165634f0cac1abb27e54d7a0b3b44eb9994530b8ec73cf52e15353b"}, + {file = "pyzmq-25.1.2-cp37-cp37m-win32.whl", hash = "sha256:0f97bc2f1f13cb16905a5f3e1fbdf100e712d841482b2237484360f8bc4cb3d7"}, + {file = "pyzmq-25.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6cc0020b74b2e410287e5942e1e10886ff81ac77789eb20bec13f7ae681f0fdd"}, + {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:bef02cfcbded83473bdd86dd8d3729cd82b2e569b75844fb4ea08fee3c26ae41"}, + {file = "pyzmq-25.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e10a4b5a4b1192d74853cc71a5e9fd022594573926c2a3a4802020360aa719d8"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8c5f80e578427d4695adac6fdf4370c14a2feafdc8cb35549c219b90652536ae"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5dde6751e857910c1339890f3524de74007958557593b9e7e8c5f01cd919f8a7"}, + {file = "pyzmq-25.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea1608dd169da230a0ad602d5b1ebd39807ac96cae1845c3ceed39af08a5c6df"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0f513130c4c361201da9bc69df25a086487250e16b5571ead521b31ff6b02220"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:019744b99da30330798bb37df33549d59d380c78e516e3bab9c9b84f87a9592f"}, + {file = "pyzmq-25.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2e2713ef44be5d52dd8b8e2023d706bf66cb22072e97fc71b168e01d25192755"}, + {file = "pyzmq-25.1.2-cp38-cp38-win32.whl", hash = "sha256:07cd61a20a535524906595e09344505a9bd46f1da7a07e504b315d41cd42eb07"}, + {file = "pyzmq-25.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb7e49a17fb8c77d3119d41a4523e432eb0c6932187c37deb6fbb00cc3028088"}, + {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:94504ff66f278ab4b7e03e4cba7e7e400cb73bfa9d3d71f58d8972a8dc67e7a6"}, + {file = "pyzmq-25.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6dd0d50bbf9dca1d0bdea219ae6b40f713a3fb477c06ca3714f208fd69e16fd8"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:004ff469d21e86f0ef0369717351073e0e577428e514c47c8480770d5e24a565"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c0b5ca88a8928147b7b1e2dfa09f3b6c256bc1135a1338536cbc9ea13d3b7add"}, + {file = "pyzmq-25.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9a79f1d2495b167119d02be7448bfba57fad2a4207c4f68abc0bab4b92925b"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:518efd91c3d8ac9f9b4f7dd0e2b7b8bf1a4fe82a308009016b07eaa48681af82"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1ec23bd7b3a893ae676d0e54ad47d18064e6c5ae1fadc2f195143fb27373f7f6"}, + {file = "pyzmq-25.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db36c27baed588a5a8346b971477b718fdc66cf5b80cbfbd914b4d6d355e44e2"}, + {file = "pyzmq-25.1.2-cp39-cp39-win32.whl", hash = "sha256:39b1067f13aba39d794a24761e385e2eddc26295826530a8c7b6c6c341584289"}, + {file = "pyzmq-25.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:8e9f3fabc445d0ce320ea2c59a75fe3ea591fdbdeebec5db6de530dd4b09412e"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a8c1d566344aee826b74e472e16edae0a02e2a044f14f7c24e123002dcff1c05"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:759cfd391a0996345ba94b6a5110fca9c557ad4166d86a6e81ea526c376a01e8"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c61e346ac34b74028ede1c6b4bcecf649d69b707b3ff9dc0fab453821b04d1e"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cb8fc1f8d69b411b8ec0b5f1ffbcaf14c1db95b6bccea21d83610987435f1a4"}, + {file = "pyzmq-25.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3c00c9b7d1ca8165c610437ca0c92e7b5607b2f9076f4eb4b095c85d6e680a1d"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:df0c7a16ebb94452d2909b9a7b3337940e9a87a824c4fc1c7c36bb4404cb0cde"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:45999e7f7ed5c390f2e87ece7f6c56bf979fb213550229e711e45ecc7d42ccb8"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ac170e9e048b40c605358667aca3d94e98f604a18c44bdb4c102e67070f3ac9b"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b604734bec94f05f81b360a272fc824334267426ae9905ff32dc2be433ab96"}, + {file = "pyzmq-25.1.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a793ac733e3d895d96f865f1806f160696422554e46d30105807fdc9841b9f7d"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0806175f2ae5ad4b835ecd87f5f85583316b69f17e97786f7443baaf54b9bb98"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ef12e259e7bc317c7597d4f6ef59b97b913e162d83b421dd0db3d6410f17a244"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea253b368eb41116011add00f8d5726762320b1bda892f744c91997b65754d73"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b9b1f2ad6498445a941d9a4fee096d387fee436e45cc660e72e768d3d8ee611"}, + {file = "pyzmq-25.1.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:8b14c75979ce932c53b79976a395cb2a8cd3aaf14aef75e8c2cb55a330b9b49d"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:889370d5174a741a62566c003ee8ddba4b04c3f09a97b8000092b7ca83ec9c49"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a18fff090441a40ffda8a7f4f18f03dc56ae73f148f1832e109f9bffa85df15"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99a6b36f95c98839ad98f8c553d8507644c880cf1e0a57fe5e3a3f3969040882"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4345c9a27f4310afbb9c01750e9461ff33d6fb74cd2456b107525bbeebcb5be3"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3516e0b6224cf6e43e341d56da15fd33bdc37fa0c06af4f029f7d7dfceceabbc"}, + {file = "pyzmq-25.1.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:146b9b1f29ead41255387fb07be56dc29639262c0f7344f570eecdcd8d683314"}, + {file = "pyzmq-25.1.2.tar.gz", hash = "sha256:93f1aa311e8bb912e34f004cf186407a4e90eec4f0ecc0efd26056bf7eda0226"}, ] [package.dependencies] @@ -3960,28 +3966,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.1.6" +version = "0.1.9" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:88b8cdf6abf98130991cbc9f6438f35f6e8d41a02622cc5ee130a02a0ed28703"}, - {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c549ed437680b6105a1299d2cd30e4964211606eeb48a0ff7a93ef70b902248"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf5f701062e294f2167e66d11b092bba7af6a057668ed618a9253e1e90cfd76"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05991ee20d4ac4bb78385360c684e4b417edd971030ab12a4fbd075ff535050e"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87455a0c1f739b3c069e2f4c43b66479a54dea0276dd5d4d67b091265f6fd1dc"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:683aa5bdda5a48cb8266fcde8eea2a6af4e5700a392c56ea5fb5f0d4bfdc0240"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:137852105586dcbf80c1717facb6781555c4e99f520c9c827bd414fac67ddfb6"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd98138a98d48a1c36c394fd6b84cd943ac92a08278aa8ac8c0fdefcf7138f35"}, - {file = "ruff-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0cd909d25f227ac5c36d4e7e681577275fb74ba3b11d288aff7ec47e3ae745"}, - {file = "ruff-0.1.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8fd1c62a47aa88a02707b5dd20c5ff20d035d634aa74826b42a1da77861b5ff"}, - {file = "ruff-0.1.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd89b45d374935829134a082617954120d7a1470a9f0ec0e7f3ead983edc48cc"}, - {file = "ruff-0.1.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:491262006e92f825b145cd1e52948073c56560243b55fb3b4ecb142f6f0e9543"}, - {file = "ruff-0.1.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea284789861b8b5ca9d5443591a92a397ac183d4351882ab52f6296b4fdd5462"}, - {file = "ruff-0.1.6-py3-none-win32.whl", hash = "sha256:1610e14750826dfc207ccbcdd7331b6bd285607d4181df9c1c6ae26646d6848a"}, - {file = "ruff-0.1.6-py3-none-win_amd64.whl", hash = "sha256:4558b3e178145491e9bc3b2ee3c4b42f19d19384eaa5c59d10acf6e8f8b57e33"}, - {file = "ruff-0.1.6-py3-none-win_arm64.whl", hash = "sha256:03910e81df0d8db0e30050725a5802441c2022ea3ae4fe0609b76081731accbc"}, - {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, + {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6a212f436122ac73df851f0cf006e0c6612fe6f9c864ed17ebefce0eff6a5fd"}, + {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:28d920e319783d5303333630dae46ecc80b7ba294aeffedf946a02ac0b7cc3db"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:104aa9b5e12cb755d9dce698ab1b97726b83012487af415a4512fedd38b1459e"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e63bf5a4a91971082a4768a0aba9383c12392d0d6f1e2be2248c1f9054a20da"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d0738917c203246f3e275b37006faa3aa96c828b284ebfe3e99a8cb413c8c4b"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69dac82d63a50df2ab0906d97a01549f814b16bc806deeac4f064ff95c47ddf5"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2aec598fb65084e41a9c5d4b95726173768a62055aafb07b4eff976bac72a592"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:744dfe4b35470fa3820d5fe45758aace6269c578f7ddc43d447868cfe5078bcb"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479ca4250cab30f9218b2e563adc362bd6ae6343df7c7b5a7865300a5156d5a6"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:aa8344310f1ae79af9ccd6e4b32749e93cddc078f9b5ccd0e45bd76a6d2e8bb6"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:837c739729394df98f342319f5136f33c65286b28b6b70a87c28f59354ec939b"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6837202c2859b9f22e43cb01992373c2dbfeae5c0c91ad691a4a2e725392464"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:331aae2cd4a0554667ac683243b151c74bd60e78fb08c3c2a4ac05ee1e606a39"}, + {file = "ruff-0.1.9-py3-none-win32.whl", hash = "sha256:8151425a60878e66f23ad47da39265fc2fad42aed06fb0a01130e967a7a064f4"}, + {file = "ruff-0.1.9-py3-none-win_amd64.whl", hash = "sha256:c497d769164df522fdaf54c6eba93f397342fe4ca2123a2e014a5b8fc7df81c7"}, + {file = "ruff-0.1.9-py3-none-win_arm64.whl", hash = "sha256:0e17f53bcbb4fff8292dfd84cf72d767b5e146f009cccd40c2fad27641f8a7a9"}, + {file = "ruff-0.1.9.tar.gz", hash = "sha256:b041dee2734719ddbb4518f762c982f2e912e7f28b8ee4fe1dee0b15d1b6e800"}, ] [[package]] @@ -4042,17 +4048,17 @@ setuptools = "*" [[package]] name = "seaborn" -version = "0.13.0" +version = "0.13.1" description = "Statistical data visualization" optional = false python-versions = ">=3.8" files = [ - {file = "seaborn-0.13.0-py3-none-any.whl", hash = "sha256:70d740828c48de0f402bb17234e475eda687e3c65f4383ea25d0cc4728f7772e"}, - {file = "seaborn-0.13.0.tar.gz", hash = "sha256:0e76abd2ec291c655b516703c6a022f0fd5afed26c8e714e8baef48150f73598"}, + {file = "seaborn-0.13.1-py3-none-any.whl", hash = "sha256:6baa69b6d1169ae59037971491c450c0b73332b42bd4b23570b62a546bc61cb8"}, + {file = "seaborn-0.13.1.tar.gz", hash = "sha256:bfad65e9c5989e5e1897e61bdbd2f22e62455940ca76fd49eca3ed69345b9179"}, ] [package.dependencies] -matplotlib = ">=3.3,<3.6.1 || >3.6.1" +matplotlib = ">=3.4,<3.6.1 || >3.6.1" numpy = ">=1.20,<1.24.0 || >1.24.0" pandas = ">=1.2" @@ -4205,13 +4211,13 @@ test = ["pytest"] [[package]] name = "setuptools" -version = "69.0.2" +version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, - {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, ] [package.extras] @@ -4303,6 +4309,17 @@ docs = ["sphinx (>=1.5.3)"] qa = ["flake8"] test = ["mock", "nose"] +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] + [[package]] name = "snowballstemmer" version = "2.2.0" @@ -4381,18 +4398,18 @@ test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools [[package]] name = "sphinx-rtd-theme" -version = "1.3.0" +version = "2.0.0" description = "Read the Docs theme for Sphinx" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=3.6" files = [ - {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, - {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, + {file = "sphinx_rtd_theme-2.0.0-py2.py3-none-any.whl", hash = "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586"}, + {file = "sphinx_rtd_theme-2.0.0.tar.gz", hash = "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b"}, ] [package.dependencies] -docutils = "<0.19" -sphinx = ">=1.6,<8" +docutils = "<0.21" +sphinx = ">=5,<8" sphinxcontrib-jquery = ">=4,<5" [package.extras] @@ -4637,13 +4654,13 @@ telegram = ["requests"] [[package]] name = "types-requests" -version = "2.31.0.10" +version = "2.31.0.20231231" description = "Typing stubs for requests" optional = false python-versions = ">=3.7" files = [ - {file = "types-requests-2.31.0.10.tar.gz", hash = "sha256:dc5852a76f1eaf60eafa81a2e50aefa3d1f015c34cf0cba130930866b1b22a92"}, - {file = "types_requests-2.31.0.10-py3-none-any.whl", hash = "sha256:b32b9a86beffa876c0c3ac99a4cd3b8b51e973fb8e3bd4e0a6bb32c7efad80fc"}, + {file = "types-requests-2.31.0.20231231.tar.gz", hash = "sha256:0f8c0c9764773384122813548d9eea92a5c4e1f33ed54556b508968ec5065cee"}, + {file = "types_requests-2.31.0.20231231-py3-none-any.whl", hash = "sha256:2e2230c7bc8dd63fa3153c1c0ae335f8a368447f0582fc332f17d54f88e69027"}, ] [package.dependencies] @@ -4662,24 +4679,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] name = "tzdata" -version = "2023.3" +version = "2023.4" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, - {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, + {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, + {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, ] [[package]] @@ -4700,13 +4717,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.7" +version = "20.25.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.7-py3-none-any.whl", hash = "sha256:a18b3fd0314ca59a2e9f4b556819ed07183b3e9a3702ecfe213f593d44f7b3fd"}, - {file = "virtualenv-20.24.7.tar.gz", hash = "sha256:69050ffb42419c91f6c1284a7b24e0475d793447e35929b488bf6a0aade39353"}, + {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, + {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, ] [package.dependencies] @@ -4720,13 +4737,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "websocket-client" -version = "1.6.4" +version = "1.7.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, - {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, + {file = "websocket-client-1.7.0.tar.gz", hash = "sha256:10e511ea3a8c744631d3bd77e61eb17ed09304c413ad42cf6ddfa4c7787e8fe6"}, + {file = "websocket_client-1.7.0-py3-none-any.whl", hash = "sha256:f4c3d22fec12a2461427a29957ff07d35098ee2d976d3ba244e688b8b4057588"}, ] [package.extras] @@ -4752,101 +4769,101 @@ tomli = ">=2.0.1" [[package]] name = "yarl" -version = "1.9.3" +version = "1.9.4" description = "Yet another URL library" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32435d134414e01d937cd9d6cc56e8413a8d4741dea36af5840c7750f04d16ab"}, - {file = "yarl-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9a5211de242754b5e612557bca701f39f8b1a9408dff73c6db623f22d20f470e"}, - {file = "yarl-1.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:525cd69eff44833b01f8ef39aa33a9cc53a99ff7f9d76a6ef6a9fb758f54d0ff"}, - {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc94441bcf9cb8c59f51f23193316afefbf3ff858460cb47b5758bf66a14d130"}, - {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e36021db54b8a0475805acc1d6c4bca5d9f52c3825ad29ae2d398a9d530ddb88"}, - {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0f17d1df951336a02afc8270c03c0c6e60d1f9996fcbd43a4ce6be81de0bd9d"}, - {file = "yarl-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f3faeb8100a43adf3e7925d556801d14b5816a0ac9e75e22948e787feec642"}, - {file = "yarl-1.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aed37db837ecb5962469fad448aaae0f0ee94ffce2062cf2eb9aed13328b5196"}, - {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:721ee3fc292f0d069a04016ef2c3a25595d48c5b8ddc6029be46f6158d129c92"}, - {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b8bc5b87a65a4e64bc83385c05145ea901b613d0d3a434d434b55511b6ab0067"}, - {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:dd952b9c64f3b21aedd09b8fe958e4931864dba69926d8a90c90d36ac4e28c9a"}, - {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:c405d482c320a88ab53dcbd98d6d6f32ada074f2d965d6e9bf2d823158fa97de"}, - {file = "yarl-1.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9df9a0d4c5624790a0dea2e02e3b1b3c69aed14bcb8650e19606d9df3719e87d"}, - {file = "yarl-1.9.3-cp310-cp310-win32.whl", hash = "sha256:d34c4f80956227f2686ddea5b3585e109c2733e2d4ef12eb1b8b4e84f09a2ab6"}, - {file = "yarl-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:cf7a4e8de7f1092829caef66fd90eaf3710bc5efd322a816d5677b7664893c93"}, - {file = "yarl-1.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d61a0ca95503867d4d627517bcfdc28a8468c3f1b0b06c626f30dd759d3999fd"}, - {file = "yarl-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73cc83f918b69110813a7d95024266072d987b903a623ecae673d1e71579d566"}, - {file = "yarl-1.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d81657b23e0edb84b37167e98aefb04ae16cbc5352770057893bd222cdc6e45f"}, - {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a1a8443091c7fbc17b84a0d9f38de34b8423b459fb853e6c8cdfab0eacf613"}, - {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe34befb8c765b8ce562f0200afda3578f8abb159c76de3ab354c80b72244c41"}, - {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c757f64afe53a422e45e3e399e1e3cf82b7a2f244796ce80d8ca53e16a49b9f"}, - {file = "yarl-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72a57b41a0920b9a220125081c1e191b88a4cdec13bf9d0649e382a822705c65"}, - {file = "yarl-1.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:632c7aeb99df718765adf58eacb9acb9cbc555e075da849c1378ef4d18bf536a"}, - {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b0b8c06afcf2bac5a50b37f64efbde978b7f9dc88842ce9729c020dc71fae4ce"}, - {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1d93461e2cf76c4796355494f15ffcb50a3c198cc2d601ad8d6a96219a10c363"}, - {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4003f380dac50328c85e85416aca6985536812c082387255c35292cb4b41707e"}, - {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4d6d74a97e898c1c2df80339aa423234ad9ea2052f66366cef1e80448798c13d"}, - {file = "yarl-1.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b61e64b06c3640feab73fa4ff9cb64bd8182de52e5dc13038e01cfe674ebc321"}, - {file = "yarl-1.9.3-cp311-cp311-win32.whl", hash = "sha256:29beac86f33d6c7ab1d79bd0213aa7aed2d2f555386856bb3056d5fdd9dab279"}, - {file = "yarl-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:f7271d6bd8838c49ba8ae647fc06469137e1c161a7ef97d778b72904d9b68696"}, - {file = "yarl-1.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:dd318e6b75ca80bff0b22b302f83a8ee41c62b8ac662ddb49f67ec97e799885d"}, - {file = "yarl-1.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c4b1efb11a8acd13246ffb0bee888dd0e8eb057f8bf30112e3e21e421eb82d4a"}, - {file = "yarl-1.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c6f034386e5550b5dc8ded90b5e2ff7db21f0f5c7de37b6efc5dac046eb19c10"}, - {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd49a908cb6d387fc26acee8b7d9fcc9bbf8e1aca890c0b2fdfd706057546080"}, - {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa4643635f26052401750bd54db911b6342eb1a9ac3e74f0f8b58a25d61dfe41"}, - {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e741bd48e6a417bdfbae02e088f60018286d6c141639359fb8df017a3b69415a"}, - {file = "yarl-1.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c86d0d0919952d05df880a1889a4f0aeb6868e98961c090e335671dea5c0361"}, - {file = "yarl-1.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d5434b34100b504aabae75f0622ebb85defffe7b64ad8f52b8b30ec6ef6e4b9"}, - {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79e1df60f7c2b148722fb6cafebffe1acd95fd8b5fd77795f56247edaf326752"}, - {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:44e91a669c43f03964f672c5a234ae0d7a4d49c9b85d1baa93dec28afa28ffbd"}, - {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3cfa4dbe17b2e6fca1414e9c3bcc216f6930cb18ea7646e7d0d52792ac196808"}, - {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:88d2c3cc4b2f46d1ba73d81c51ec0e486f59cc51165ea4f789677f91a303a9a7"}, - {file = "yarl-1.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cccdc02e46d2bd7cb5f38f8cc3d9db0d24951abd082b2f242c9e9f59c0ab2af3"}, - {file = "yarl-1.9.3-cp312-cp312-win32.whl", hash = "sha256:96758e56dceb8a70f8a5cff1e452daaeff07d1cc9f11e9b0c951330f0a2396a7"}, - {file = "yarl-1.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:c4472fe53ebf541113e533971bd8c32728debc4c6d8cc177f2bff31d011ec17e"}, - {file = "yarl-1.9.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:126638ab961633f0940a06e1c9d59919003ef212a15869708dcb7305f91a6732"}, - {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c99ddaddb2fbe04953b84d1651149a0d85214780e4d0ee824e610ab549d98d92"}, - {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dab30b21bd6fb17c3f4684868c7e6a9e8468078db00f599fb1c14e324b10fca"}, - {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:828235a2a169160ee73a2fcfb8a000709edf09d7511fccf203465c3d5acc59e4"}, - {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc391e3941045fd0987c77484b2799adffd08e4b6735c4ee5f054366a2e1551d"}, - {file = "yarl-1.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51382c72dd5377861b573bd55dcf680df54cea84147c8648b15ac507fbef984d"}, - {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:28a108cb92ce6cf867690a962372996ca332d8cda0210c5ad487fe996e76b8bb"}, - {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8f18a7832ff85dfcd77871fe677b169b1bc60c021978c90c3bb14f727596e0ae"}, - {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:7eaf13af79950142ab2bbb8362f8d8d935be9aaf8df1df89c86c3231e4ff238a"}, - {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:66a6dbf6ca7d2db03cc61cafe1ee6be838ce0fbc97781881a22a58a7c5efef42"}, - {file = "yarl-1.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a0a4f3aaa18580038cfa52a7183c8ffbbe7d727fe581300817efc1e96d1b0e9"}, - {file = "yarl-1.9.3-cp37-cp37m-win32.whl", hash = "sha256:946db4511b2d815979d733ac6a961f47e20a29c297be0d55b6d4b77ee4b298f6"}, - {file = "yarl-1.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2dad8166d41ebd1f76ce107cf6a31e39801aee3844a54a90af23278b072f1ccf"}, - {file = "yarl-1.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bb72d2a94481e7dc7a0c522673db288f31849800d6ce2435317376a345728225"}, - {file = "yarl-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9a172c3d5447b7da1680a1a2d6ecdf6f87a319d21d52729f45ec938a7006d5d8"}, - {file = "yarl-1.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2dc72e891672343b99db6d497024bf8b985537ad6c393359dc5227ef653b2f17"}, - {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8d51817cf4b8d545963ec65ff06c1b92e5765aa98831678d0e2240b6e9fd281"}, - {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53ec65f7eee8655bebb1f6f1607760d123c3c115a324b443df4f916383482a67"}, - {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cfd77e8e5cafba3fb584e0f4b935a59216f352b73d4987be3af51f43a862c403"}, - {file = "yarl-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e73db54c967eb75037c178a54445c5a4e7461b5203b27c45ef656a81787c0c1b"}, - {file = "yarl-1.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09c19e5f4404574fcfb736efecf75844ffe8610606f3fccc35a1515b8b6712c4"}, - {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6280353940f7e5e2efaaabd686193e61351e966cc02f401761c4d87f48c89ea4"}, - {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c25ec06e4241e162f5d1f57c370f4078797ade95c9208bd0c60f484834f09c96"}, - {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7217234b10c64b52cc39a8d82550342ae2e45be34f5bff02b890b8c452eb48d7"}, - {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4ce77d289f8d40905c054b63f29851ecbfd026ef4ba5c371a158cfe6f623663e"}, - {file = "yarl-1.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5f74b015c99a5eac5ae589de27a1201418a5d9d460e89ccb3366015c6153e60a"}, - {file = "yarl-1.9.3-cp38-cp38-win32.whl", hash = "sha256:8a2538806be846ea25e90c28786136932ec385c7ff3bc1148e45125984783dc6"}, - {file = "yarl-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:6465d36381af057d0fab4e0f24ef0e80ba61f03fe43e6eeccbe0056e74aadc70"}, - {file = "yarl-1.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2f3c8822bc8fb4a347a192dd6a28a25d7f0ea3262e826d7d4ef9cc99cd06d07e"}, - {file = "yarl-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7831566595fe88ba17ea80e4b61c0eb599f84c85acaa14bf04dd90319a45b90"}, - {file = "yarl-1.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff34cb09a332832d1cf38acd0f604c068665192c6107a439a92abfd8acf90fe2"}, - {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe8080b4f25dfc44a86bedd14bc4f9d469dfc6456e6f3c5d9077e81a5fedfba7"}, - {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8535e111a064f3bdd94c0ed443105934d6f005adad68dd13ce50a488a0ad1bf3"}, - {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d155a092bf0ebf4a9f6f3b7a650dc5d9a5bbb585ef83a52ed36ba46f55cc39d"}, - {file = "yarl-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:778df71c8d0c8c9f1b378624b26431ca80041660d7be7c3f724b2c7a6e65d0d6"}, - {file = "yarl-1.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f9cafaf031c34d95c1528c16b2fa07b710e6056b3c4e2e34e9317072da5d1a"}, - {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ca6b66f69e30f6e180d52f14d91ac854b8119553b524e0e28d5291a724f0f423"}, - {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0e7e83f31e23c5d00ff618045ddc5e916f9e613d33c5a5823bc0b0a0feb522f"}, - {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:af52725c7c39b0ee655befbbab5b9a1b209e01bb39128dce0db226a10014aacc"}, - {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0ab5baaea8450f4a3e241ef17e3d129b2143e38a685036b075976b9c415ea3eb"}, - {file = "yarl-1.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6d350388ba1129bc867c6af1cd17da2b197dff0d2801036d2d7d83c2d771a682"}, - {file = "yarl-1.9.3-cp39-cp39-win32.whl", hash = "sha256:e2a16ef5fa2382af83bef4a18c1b3bcb4284c4732906aa69422cf09df9c59f1f"}, - {file = "yarl-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:d92d897cb4b4bf915fbeb5e604c7911021a8456f0964f3b8ebbe7f9188b9eabb"}, - {file = "yarl-1.9.3-py3-none-any.whl", hash = "sha256:271d63396460b6607b588555ea27a1a02b717ca2e3f2cf53bdde4013d7790929"}, - {file = "yarl-1.9.3.tar.gz", hash = "sha256:4a14907b597ec55740f63e52d7fee0e9ee09d5b9d57a4f399a7423268e457b57"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, + {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, + {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, + {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, + {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, + {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, + {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, + {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, + {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, + {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, + {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, + {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, + {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, + {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, + {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, + {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, + {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, + {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, + {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, + {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, + {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, + {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, + {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, + {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, + {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, + {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, + {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, + {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, + {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, + {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, + {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, + {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, + {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, ] [package.dependencies] diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 719d50c1ab..ce9e64188b 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -637,6 +637,8 @@ def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket while not (end_event.is_set() or global_end_event.is_set()): try: data = ws.recv() + if isinstance(data, str): + data = data.encode("utf-8") local_sock.sendall(data) except WebSocketTimeoutException: pass @@ -728,10 +730,11 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None: if onroad != onroad_prev: onroad_prev = onroad - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, 16000 if onroad else 0) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7 if onroad else 30) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3) + if sock is not None: + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, 16000 if onroad else 0) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7 if onroad else 30) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3) if end_event.wait(5): break diff --git a/tools/webcam/front_mount_helper.py b/tools/webcam/front_mount_helper.py deleted file mode 100755 index 0b7b676f6e..0000000000 --- a/tools/webcam/front_mount_helper.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python -import numpy as np - -# copied from openpilot.common.transformations/camera.py -eon_dcam_focal_length = 860.0 # pixels -webcam_focal_length = 908.0 # pixels - -eon_dcam_intrinsics = np.array([ - [eon_dcam_focal_length, 0, 1152/2.], - [ 0, eon_dcam_focal_length, 864/2.], - [ 0, 0, 1]]) - -webcam_intrinsics = np.array([ - [webcam_focal_length, 0., 1280/2.], - [ 0., webcam_focal_length, 720/2.], - [ 0., 0., 1.]]) - -cam_id = 2 - -if __name__ == "__main__": - import cv2 - - trans_webcam_to_eon_front = np.dot(eon_dcam_intrinsics, np.linalg.inv(webcam_intrinsics)) - - cap = cv2.VideoCapture(cam_id) - cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) - cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) - - while (True): - ret, img = cap.read() - if ret: - img = cv2.warpPerspective(img, trans_webcam_to_eon_front, (1152, 864), borderMode=cv2.BORDER_CONSTANT, borderValue=0) - img = img[:, -864//2:, :] - cv2.imshow('preview', img) - cv2.waitKey(10) diff --git a/tools/webcam/warp_vis.py b/tools/webcam/warp_vis.py deleted file mode 100755 index e3f1284a45..0000000000 --- a/tools/webcam/warp_vis.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -import numpy as np - -# copied from openpilot.common.transformations/camera.py -eon_focal_length = 910.0 # pixels -eon_dcam_focal_length = 860.0 # pixels - -webcam_focal_length = -908.0/1.5 # pixels - -eon_intrinsics = np.array([ - [eon_focal_length, 0., 1164/2.], - [ 0., eon_focal_length, 874/2.], - [ 0., 0., 1.]]) - -eon_dcam_intrinsics = np.array([ - [eon_dcam_focal_length, 0, 1152/2.], - [ 0, eon_dcam_focal_length, 864/2.], - [ 0, 0, 1]]) - -webcam_intrinsics = np.array([ - [webcam_focal_length, 0., 1280/2/1.5], - [ 0., webcam_focal_length, 720/2/1.5], - [ 0., 0., 1.]]) - -if __name__ == "__main__": - import cv2 - trans_webcam_to_eon_rear = np.dot(eon_intrinsics, np.linalg.inv(webcam_intrinsics)) - trans_webcam_to_eon_front = np.dot(eon_dcam_intrinsics, np.linalg.inv(webcam_intrinsics)) - print("trans_webcam_to_eon_rear:\n", trans_webcam_to_eon_rear) - print("trans_webcam_to_eon_front:\n", trans_webcam_to_eon_front) - - cap = cv2.VideoCapture(1) - cap.set(cv2.CAP_PROP_FRAME_WIDTH, 853) - cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) - - while (True): - ret, img = cap.read() - if ret: - # img = cv2.warpPerspective(img, trans_webcam_to_eon_rear, (1164,874), borderMode=cv2.BORDER_CONSTANT, borderValue=0) - img = cv2.warpPerspective(img, trans_webcam_to_eon_front, (1164, 874), borderMode=cv2.BORDER_CONSTANT, borderValue=0) - print(img.shape, end='\r') - cv2.imshow('preview', img) - cv2.waitKey(10) From 4cc48b75e859f5a41c86cf4241dab36509896491 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 5 Jan 2024 19:20:37 -0500 Subject: [PATCH 386/724] add sanitizer function (#30919) * sanetizer function * spelling * better * move to helpers --- selfdrive/test/helpers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 6cba371fc2..693604442d 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -8,6 +8,7 @@ from openpilot.common.params import Params from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.system.hardware import PC from openpilot.system.version import training_version, terms_version +from openpilot.tools.lib.logreader import LogIterable def set_params_enabled(): @@ -79,3 +80,11 @@ def read_segment_list(segment_list_path): seg_list = f.read().splitlines() return [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] + + +# Utilities for sanitizing routes of only essential data for testing car ports and doing validation. + +PRESERVE_SERVICES = ["can", "carParams", "pandaStates", "pandaStateDEPRECATED"] + +def sanitize(lr: LogIterable) -> LogIterable: + return filter(lambda msg: msg.which() in PRESERVE_SERVICES, lr) From 03d6233743dbf8913ca48b59dd54f111be2ab595 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 5 Jan 2024 19:22:45 -0500 Subject: [PATCH 387/724] move profiling tools to tools/profiling (#30917) move profiling --- {selfdrive/debug => tools}/profiling/clpeak/.gitignore | 0 {selfdrive/debug => tools}/profiling/clpeak/build.sh | 0 {selfdrive/debug => tools}/profiling/clpeak/no_print.patch | 0 .../debug => tools}/profiling/clpeak/run_continuously.patch | 0 {selfdrive/debug => tools}/profiling/ftrace.sh | 0 {selfdrive/debug => tools}/profiling/palanteer/.gitignore | 0 {selfdrive/debug => tools}/profiling/palanteer/setup.sh | 0 {selfdrive/debug => tools}/profiling/perfetto/.gitignore | 0 {selfdrive/debug => tools}/profiling/perfetto/build.sh | 0 {selfdrive/debug => tools}/profiling/perfetto/copy.sh | 0 {selfdrive/debug => tools}/profiling/perfetto/record.sh | 0 {selfdrive/debug => tools}/profiling/perfetto/server.sh | 0 {selfdrive/debug => tools}/profiling/perfetto/traces.sh | 0 {selfdrive/debug => tools}/profiling/py-spy/profile.sh | 0 {selfdrive/debug => tools}/profiling/snapdragon/.gitignore | 0 {selfdrive/debug => tools}/profiling/snapdragon/README.md | 0 {selfdrive/debug => tools}/profiling/snapdragon/setup-agnos.sh | 0 {selfdrive/debug => tools}/profiling/snapdragon/setup-profiler.sh | 0 {selfdrive/debug => tools}/profiling/watch-irqs.sh | 0 19 files changed, 0 insertions(+), 0 deletions(-) rename {selfdrive/debug => tools}/profiling/clpeak/.gitignore (100%) rename {selfdrive/debug => tools}/profiling/clpeak/build.sh (100%) rename {selfdrive/debug => tools}/profiling/clpeak/no_print.patch (100%) rename {selfdrive/debug => tools}/profiling/clpeak/run_continuously.patch (100%) rename {selfdrive/debug => tools}/profiling/ftrace.sh (100%) rename {selfdrive/debug => tools}/profiling/palanteer/.gitignore (100%) rename {selfdrive/debug => tools}/profiling/palanteer/setup.sh (100%) rename {selfdrive/debug => tools}/profiling/perfetto/.gitignore (100%) rename {selfdrive/debug => tools}/profiling/perfetto/build.sh (100%) rename {selfdrive/debug => tools}/profiling/perfetto/copy.sh (100%) rename {selfdrive/debug => tools}/profiling/perfetto/record.sh (100%) rename {selfdrive/debug => tools}/profiling/perfetto/server.sh (100%) rename {selfdrive/debug => tools}/profiling/perfetto/traces.sh (100%) rename {selfdrive/debug => tools}/profiling/py-spy/profile.sh (100%) rename {selfdrive/debug => tools}/profiling/snapdragon/.gitignore (100%) rename {selfdrive/debug => tools}/profiling/snapdragon/README.md (100%) rename {selfdrive/debug => tools}/profiling/snapdragon/setup-agnos.sh (100%) rename {selfdrive/debug => tools}/profiling/snapdragon/setup-profiler.sh (100%) rename {selfdrive/debug => tools}/profiling/watch-irqs.sh (100%) diff --git a/selfdrive/debug/profiling/clpeak/.gitignore b/tools/profiling/clpeak/.gitignore similarity index 100% rename from selfdrive/debug/profiling/clpeak/.gitignore rename to tools/profiling/clpeak/.gitignore diff --git a/selfdrive/debug/profiling/clpeak/build.sh b/tools/profiling/clpeak/build.sh similarity index 100% rename from selfdrive/debug/profiling/clpeak/build.sh rename to tools/profiling/clpeak/build.sh diff --git a/selfdrive/debug/profiling/clpeak/no_print.patch b/tools/profiling/clpeak/no_print.patch similarity index 100% rename from selfdrive/debug/profiling/clpeak/no_print.patch rename to tools/profiling/clpeak/no_print.patch diff --git a/selfdrive/debug/profiling/clpeak/run_continuously.patch b/tools/profiling/clpeak/run_continuously.patch similarity index 100% rename from selfdrive/debug/profiling/clpeak/run_continuously.patch rename to tools/profiling/clpeak/run_continuously.patch diff --git a/selfdrive/debug/profiling/ftrace.sh b/tools/profiling/ftrace.sh similarity index 100% rename from selfdrive/debug/profiling/ftrace.sh rename to tools/profiling/ftrace.sh diff --git a/selfdrive/debug/profiling/palanteer/.gitignore b/tools/profiling/palanteer/.gitignore similarity index 100% rename from selfdrive/debug/profiling/palanteer/.gitignore rename to tools/profiling/palanteer/.gitignore diff --git a/selfdrive/debug/profiling/palanteer/setup.sh b/tools/profiling/palanteer/setup.sh similarity index 100% rename from selfdrive/debug/profiling/palanteer/setup.sh rename to tools/profiling/palanteer/setup.sh diff --git a/selfdrive/debug/profiling/perfetto/.gitignore b/tools/profiling/perfetto/.gitignore similarity index 100% rename from selfdrive/debug/profiling/perfetto/.gitignore rename to tools/profiling/perfetto/.gitignore diff --git a/selfdrive/debug/profiling/perfetto/build.sh b/tools/profiling/perfetto/build.sh similarity index 100% rename from selfdrive/debug/profiling/perfetto/build.sh rename to tools/profiling/perfetto/build.sh diff --git a/selfdrive/debug/profiling/perfetto/copy.sh b/tools/profiling/perfetto/copy.sh similarity index 100% rename from selfdrive/debug/profiling/perfetto/copy.sh rename to tools/profiling/perfetto/copy.sh diff --git a/selfdrive/debug/profiling/perfetto/record.sh b/tools/profiling/perfetto/record.sh similarity index 100% rename from selfdrive/debug/profiling/perfetto/record.sh rename to tools/profiling/perfetto/record.sh diff --git a/selfdrive/debug/profiling/perfetto/server.sh b/tools/profiling/perfetto/server.sh similarity index 100% rename from selfdrive/debug/profiling/perfetto/server.sh rename to tools/profiling/perfetto/server.sh diff --git a/selfdrive/debug/profiling/perfetto/traces.sh b/tools/profiling/perfetto/traces.sh similarity index 100% rename from selfdrive/debug/profiling/perfetto/traces.sh rename to tools/profiling/perfetto/traces.sh diff --git a/selfdrive/debug/profiling/py-spy/profile.sh b/tools/profiling/py-spy/profile.sh similarity index 100% rename from selfdrive/debug/profiling/py-spy/profile.sh rename to tools/profiling/py-spy/profile.sh diff --git a/selfdrive/debug/profiling/snapdragon/.gitignore b/tools/profiling/snapdragon/.gitignore similarity index 100% rename from selfdrive/debug/profiling/snapdragon/.gitignore rename to tools/profiling/snapdragon/.gitignore diff --git a/selfdrive/debug/profiling/snapdragon/README.md b/tools/profiling/snapdragon/README.md similarity index 100% rename from selfdrive/debug/profiling/snapdragon/README.md rename to tools/profiling/snapdragon/README.md diff --git a/selfdrive/debug/profiling/snapdragon/setup-agnos.sh b/tools/profiling/snapdragon/setup-agnos.sh similarity index 100% rename from selfdrive/debug/profiling/snapdragon/setup-agnos.sh rename to tools/profiling/snapdragon/setup-agnos.sh diff --git a/selfdrive/debug/profiling/snapdragon/setup-profiler.sh b/tools/profiling/snapdragon/setup-profiler.sh similarity index 100% rename from selfdrive/debug/profiling/snapdragon/setup-profiler.sh rename to tools/profiling/snapdragon/setup-profiler.sh diff --git a/selfdrive/debug/profiling/watch-irqs.sh b/tools/profiling/watch-irqs.sh similarity index 100% rename from selfdrive/debug/profiling/watch-irqs.sh rename to tools/profiling/watch-irqs.sh From 2378f311d2955ed0983fc0b7f2b93b938a3256fa Mon Sep 17 00:00:00 2001 From: Meir Date: Sat, 6 Jan 2024 08:28:56 +0200 Subject: [PATCH 388/724] Auto translate with OpenAI (#30899) * auto translate with OpenAI * change model * fix mypy typing errors * update * fix lint errors * check api key after args parser * fix input args * use languages.json * just print --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/ui/translations/auto_translate.py | 136 ++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100755 selfdrive/ui/translations/auto_translate.py diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py new file mode 100755 index 0000000000..431e6d0905 --- /dev/null +++ b/selfdrive/ui/translations/auto_translate.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 + +import argparse +import json +import os +import pathlib +import xml.etree.ElementTree as ET +from typing import cast + +import requests + +TRANSLATIONS_DIR = pathlib.Path(__file__).resolve().parent +TRANSLATIONS_LANGUAGES = TRANSLATIONS_DIR / "languages.json" + +OPENAI_MODEL = "gpt-4" +OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") +OPENAI_PROMPT = "You are a professional translator from English to {language} (ISO 639 language code)." + \ + "The following sentence or word is in the GUI of a software called openpilot, translate it accordingly." + + +def get_language_files(languages: list[str] | None = None) -> dict[str, pathlib.Path]: + files = {} + + with open(TRANSLATIONS_LANGUAGES) as fp: + language_dict = json.load(fp) + + for filename in language_dict.values(): + path = TRANSLATIONS_DIR / f"{filename}.ts" + language = path.stem.split("main_")[1] + + if languages is None or language in languages: + files[language] = path + + return files + + +def translate_phrase(text: str, language: str) -> str: + response = requests.post( + "https://api.openai.com/v1/chat/completions", + json={ + "model": OPENAI_MODEL, + "messages": [ + { + "role": "system", + "content": OPENAI_PROMPT.format(language=language), + }, + { + "role": "user", + "content": text, + }, + ], + "temperature": 0.8, + "max_tokens": 1024, + "top_p": 1, + }, + headers={ + "Authorization": f"Bearer {OPENAI_API_KEY}", + }, + ) + + response.raise_for_status() + + data = response.json() + + return cast(str, data["choices"][0]["message"]["content"]) + + +def translate_file(path: pathlib.Path, language: str, all_: bool) -> None: + tree = ET.parse(path) + + root = tree.getroot() + + for context in root.findall("./context"): + name = context.find("name") + if name is None: + raise ValueError("name not found") + + print(f"Context: {name.text}") + + for message in context.findall("./message"): + source = message.find("source") + translation = message.find("translation") + + if source is None or translation is None: + raise ValueError("source or translation not found") + + if not all_ and translation.attrib.get("type") != "unfinished": + continue + + llm_translation = translate_phrase(cast(str, source.text), language) + + print(f"Source: {source.text}\n" + \ + f"Current translation: {translation.text}\n" + \ + f"LLM translation: {llm_translation}") + + translation.text = llm_translation + + with path.open("w", encoding="utf-8") as fp: + fp.write('\n' + + '\n' + + ET.tostring(root, encoding="utf-8").decode()) + + +def main(): + arg_parser = argparse.ArgumentParser("Auto translate") + + group = arg_parser.add_mutually_exclusive_group(required=True) + group.add_argument("-a", "--all-files", action="store_true", help="Translate all files") + group.add_argument("-f", "--file", nargs="+", help="Translate the selected files. (Example: -f fr de)") + + arg_parser.add_argument("-t", "--all-translations", action="store_true", default=False, help="Translate all sections. (Default: only unfinished)") + + args = arg_parser.parse_args() + + if OPENAI_API_KEY is None: + print("OpenAI API key is missing. (Hint: use `export OPENAI_API_KEY=YOUR-KEY` before you run the script).\n" + \ + "If you don't have one go to: https://beta.openai.com/account/api-keys.") + exit(1) + + files = get_language_files(None if args.all_files else args.file) + + if args.file: + missing_files = set(args.file) - set(files) + if len(missing_files): + print(f"No language files found: {missing_files}") + exit(1) + + print(f"Translation mode: {'all' if args.all_translations else 'only unfinished'}. Files: {list(files)}") + + for lang, path in files.items(): + print(f"Translate {lang} ({path})") + translate_file(path, lang, args.all_translations) + + +if __name__ == "__main__": + main() From 084871ed5bea42326bff40c35d6c02a55b3fdb13 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 6 Jan 2024 00:44:54 -0700 Subject: [PATCH 389/724] Auto translate: fix prompt & formatting (#30922) fixes --- selfdrive/ui/translations/auto_translate.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py index 431e6d0905..8613feb245 100755 --- a/selfdrive/ui/translations/auto_translate.py +++ b/selfdrive/ui/translations/auto_translate.py @@ -14,7 +14,7 @@ TRANSLATIONS_LANGUAGES = TRANSLATIONS_DIR / "languages.json" OPENAI_MODEL = "gpt-4" OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") -OPENAI_PROMPT = "You are a professional translator from English to {language} (ISO 639 language code)." + \ +OPENAI_PROMPT = "You are a professional translator from English to {language} (ISO 639 language code). " + \ "The following sentence or word is in the GUI of a software called openpilot, translate it accordingly." @@ -55,10 +55,12 @@ def translate_phrase(text: str, language: str) -> str: }, headers={ "Authorization": f"Bearer {OPENAI_API_KEY}", + "Content-Type": "application/json", }, ) - response.raise_for_status() + if 400 <= response.status_code < 600: + raise requests.HTTPError(f'Error {response.status_code}: {response.json()}', response=response) data = response.json() @@ -89,9 +91,9 @@ def translate_file(path: pathlib.Path, language: str, all_: bool) -> None: llm_translation = translate_phrase(cast(str, source.text), language) - print(f"Source: {source.text}\n" + \ - f"Current translation: {translation.text}\n" + \ - f"LLM translation: {llm_translation}") + print(f"Source: {source.text}\n" + + f"Current translation: {translation.text}\n" + + f"LLM translation: {llm_translation}") translation.text = llm_translation @@ -113,7 +115,7 @@ def main(): args = arg_parser.parse_args() if OPENAI_API_KEY is None: - print("OpenAI API key is missing. (Hint: use `export OPENAI_API_KEY=YOUR-KEY` before you run the script).\n" + \ + print("OpenAI API key is missing. (Hint: use `export OPENAI_API_KEY=YOUR-KEY` before you run the script).\n" + "If you don't have one go to: https://beta.openai.com/account/api-keys.") exit(1) From f0ccb84ae3ed890fdf5610573a8b8882f4e29d3e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 6 Jan 2024 16:20:28 -0800 Subject: [PATCH 390/724] remove common.profiler (#30927) * remove common.profiler * cleanup --- common/profiler.py | 45 --------------------------------- selfdrive/controls/controlsd.py | 9 ------- 2 files changed, 54 deletions(-) delete mode 100644 common/profiler.py diff --git a/common/profiler.py b/common/profiler.py deleted file mode 100644 index 8b1a7a8cfa..0000000000 --- a/common/profiler.py +++ /dev/null @@ -1,45 +0,0 @@ -import time - -class Profiler(): - def __init__(self, enabled=False): - self.enabled = enabled - self.cp = {} - self.cp_ignored = [] - self.iter = 0 - self.start_time = time.time() - self.last_time = self.start_time - self.tot = 0. - - def reset(self, enabled=False): - self.enabled = enabled - self.cp = {} - self.cp_ignored = [] - self.iter = 0 - self.start_time = time.time() - self.last_time = self.start_time - - def checkpoint(self, name, ignore=False): - # ignore flag needed when benchmarking threads with ratekeeper - if not self.enabled: - return - tt = time.time() - if name not in self.cp: - self.cp[name] = 0. - if ignore: - self.cp_ignored.append(name) - self.cp[name] += tt - self.last_time - if not ignore: - self.tot += tt - self.last_time - self.last_time = tt - - def display(self): - if not self.enabled: - return - self.iter += 1 - print("******* Profiling %d *******" % self.iter) - for n, ms in sorted(self.cp.items(), key=lambda x: -x[1]): - if n in self.cp_ignored: - print("%30s: %9.2f avg: %7.2f percent: %3.0f IGNORED" % (n, ms*1000.0, ms*1000.0/self.iter, ms/self.tot*100)) - else: - print("%30s: %9.2f avg: %7.2f percent: %3.0f" % (n, ms*1000.0, ms*1000.0/self.iter, ms/self.tot*100)) - print(f"Iter clock: {self.tot / self.iter:2.6f} TOTAL: {self.tot:2.2f}") diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 46a0b7f462..698c149515 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -8,7 +8,6 @@ from typing import SupportsFloat from cereal import car, log from openpilot.common.numpy_fast import clip from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL -from openpilot.common.profiler import Profiler from openpilot.common.params import Params import cereal.messaging as messaging from cereal.visionipc import VisionIpcClient, VisionStreamType @@ -195,7 +194,6 @@ class Controls: # controlsd is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) - self.prof = Profiler(False) # off by default def set_initial_state(self): if REPLAY: @@ -851,12 +849,10 @@ class Controls: def step(self): start_time = time.monotonic() - self.prof.checkpoint("Ratekeeper", ignore=True) # Sample data from sockets and get a carState CS = self.data_sample() cloudlog.timestamp("Data sampled") - self.prof.checkpoint("Sample") self.update_events(CS) cloudlog.timestamp("Events updated") @@ -864,16 +860,12 @@ class Controls: if not self.CP.passive and self.initialized: # Update control state self.state_transition(CS) - self.prof.checkpoint("State transition") # Compute actuators (runs PID loops and lateral MPC) CC, lac_log = self.state_control(CS) - self.prof.checkpoint("State Control") - # Publish data self.publish_logs(CS, start_time, CC, lac_log) - self.prof.checkpoint("Sent") self.CS_prev = CS @@ -893,7 +885,6 @@ class Controls: while True: self.step() self.rk.monitor_time() - self.prof.display() except SystemExit: e.set() t.join() From 7997fccdfa81b402e2059ec50915edeb3f3a024e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 6 Jan 2024 17:46:44 -0800 Subject: [PATCH 391/724] update mapsd cpu --- selfdrive/test/test_onroad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 3f97ef6bc6..bc12234c9c 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -34,7 +34,7 @@ PROCS = { "./encoderd": 17.0, "./camerad": 14.5, "./locationd": 11.0, - "./mapsd": (1.0, 10.0), + "./mapsd": (0.5, 10.0), "selfdrive.controls.plannerd": 11.0, "./ui": 18.0, "selfdrive.locationd.paramsd": 9.0, From e867aa40dbd1239aadf8f31bb89c82512f02bff4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 7 Jan 2024 14:34:04 -0800 Subject: [PATCH 392/724] ignore "car port" for stale pr detection --- .github/workflows/stale.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index f2a24e4368..2d60d2f0e9 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -20,7 +20,7 @@ jobs: stale-pr-message: 'This PR has had no activity for ${{ env.DAYS_BEFORE_PR_STALE }} days. It will be automatically closed in ${{ env.DAYS_BEFORE_PR_CLOSE }} days if there is no activity.' close-pr-message: 'This PR has been automatically closed due to inactivity. Feel free to re-open once activity resumes.' delete-branch: ${{ github.event.pull_request.head.repo.full_name == 'commaai/openpilot' }} # only delete branches on the main repo - exempt-pr-labels: "ignore stale,needs testing" # if wip or it needs testing from the community, don't mark as stale + exempt-pr-labels: "ignore stale,needs testing,car port" # if wip or it needs testing from the community, don't mark as stale days-before-pr-stale: ${{ env.DAYS_BEFORE_PR_STALE }} days-before-pr-close: ${{ env.DAYS_BEFORE_PR_CLOSE }} From 073fc89ad49461c01ea474125a89a505543d093d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 7 Jan 2024 14:54:39 -0800 Subject: [PATCH 393/724] Revert "UI: single-threaded CameraView (#30397)" This reverts commit 17ac1d3c7b3b481826d51eb82aaf9dbcc3f3bd81. --- selfdrive/ui/qt/offroad/driverview.cc | 13 +- selfdrive/ui/qt/offroad/driverview.h | 2 +- selfdrive/ui/qt/onroad.cc | 30 ++-- selfdrive/ui/qt/onroad.h | 2 + selfdrive/ui/qt/widgets/cameraview.cc | 189 ++++++++++++++++---------- selfdrive/ui/qt/widgets/cameraview.h | 51 +++---- selfdrive/ui/watch3.cc | 8 +- tools/cabana/videowidget.cc | 5 +- tools/cabana/videowidget.h | 4 +- 9 files changed, 179 insertions(+), 125 deletions(-) diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 08f6f10642..df9bb24651 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -7,7 +7,7 @@ const int FACE_IMG_SIZE = 130; -DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraView("camerad", VISION_STREAM_DRIVER, true, parent) { +DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraWidget("camerad", VISION_STREAM_DRIVER, true, parent) { face_img = loadPixmap("../assets/img_driver_face_static.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); QObject::connect(this, &CameraWidget::clicked, this, &DriverViewWindow::done); QObject::connect(device(), &Device::interactiveTimeout, this, [this]() { @@ -20,20 +20,22 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : CameraView("camerad", VISI void DriverViewWindow::showEvent(QShowEvent* event) { params.putBool("IsDriverViewEnabled", true); device()->resetInteractiveTimeout(60); - CameraView::showEvent(event); + CameraWidget::showEvent(event); } void DriverViewWindow::hideEvent(QHideEvent* event) { params.putBool("IsDriverViewEnabled", false); - CameraView::hideEvent(event); + stopVipcThread(); + CameraWidget::hideEvent(event); } void DriverViewWindow::paintGL() { - CameraView::paintGL(); + CameraWidget::paintGL(); + std::lock_guard lk(frame_lock); QPainter p(this); // startup msg - if (!frame) { + if (frames.empty()) { p.setPen(Qt::white); p.setRenderHint(QPainter::TextAntialiasing); p.setFont(InterFont(100, QFont::Bold)); @@ -45,6 +47,7 @@ void DriverViewWindow::paintGL() { cereal::DriverStateV2::Reader driver_state = sm["driverStateV2"].getDriverStateV2(); bool is_rhd = driver_state.getWheelOnRightProb() > 0.5; auto driver_data = is_rhd ? driver_state.getRightDriverData() : driver_state.getLeftDriverData(); + bool face_detected = driver_data.getFaceProb() > 0.7; if (face_detected) { auto fxy_list = driver_data.getFacePosition(); diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h index 97dd2faa78..155e4ede32 100644 --- a/selfdrive/ui/qt/offroad/driverview.h +++ b/selfdrive/ui/qt/offroad/driverview.h @@ -2,7 +2,7 @@ #include "selfdrive/ui/qt/widgets/cameraview.h" -class DriverViewWindow : public CameraView { +class DriverViewWindow : public CameraWidget { Q_OBJECT public: diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index ed4bdc15fb..4df0ed81cb 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -44,12 +44,12 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { split->addWidget(nvg); if (getenv("DUAL_CAMERA_VIEW")) { - CameraWidget *arCam = new CameraView("camerad", VISION_STREAM_ROAD, true, this); + CameraWidget *arCam = new CameraWidget("camerad", VISION_STREAM_ROAD, true, this); split->insertWidget(0, arCam); } if (getenv("MAP_RENDER_VIEW")) { - CameraWidget *map_render = new CameraView("navd", VISION_STREAM_MAP, false, this); + CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); split->insertWidget(0, map_render); } @@ -126,7 +126,6 @@ void OnroadWindow::offroadTransition(bool offroad) { #endif alerts->updateAlert({}); - nvg->disconnectVipc(); } void OnroadWindow::primeChanged(bool prime) { @@ -329,8 +328,6 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { map_settings_btn->setVisible(!hideBottomIcons); main_layout->setAlignment(map_settings_btn, (rightHandDM ? Qt::AlignLeft : Qt::AlignRight) | Qt::AlignBottom); } - - update(); } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -612,6 +609,21 @@ void AnnotatedCameraWidget::paintGL() { // draw camera frame { + std::lock_guard lk(frame_lock); + + if (frames.empty()) { + if (skip_frame_count > 0) { + skip_frame_count--; + qDebug() << "skipping frame, not ready"; + return; + } + } else { + // skip drawing up to this many frames if we're + // missing camera frames. this smooths out the + // transitions from the narrow and wide cameras + skip_frame_count = 5; + } + // Wide or narrow cam dependent on speed bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); if (has_wide_cam) { @@ -627,18 +639,14 @@ void AnnotatedCameraWidget::paintGL() { } CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); - s->scene.wide_cam = CameraWidget::streamType() == VISION_STREAM_WIDE_ROAD; + s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD; if (s->scene.calibration_valid) { auto calib = s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib; CameraWidget::updateCalibration(calib); } else { CameraWidget::updateCalibration(DEFAULT_CALIBRATION); } - - if (!CameraWidget::receiveFrame(sm["uiPlan"].getUiPlan().getFrameId())) { - qDebug() << "skipping frame, not ready"; - return; - } + CameraWidget::setFrameId(model.getFrameId()); CameraWidget::paintGL(); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index f6b80a75de..b3ba411453 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -93,6 +93,8 @@ private: bool v_ego_cluster_seen = false; int status = STATUS_DISENGAGED; std::unique_ptr pm; + + int skip_frame_count = 0; bool wide_cam_requested = false; protected: diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 4d370b8cc5..7b1f2f1d24 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -6,6 +6,14 @@ #include #endif +#include +#include +#include +#include + +#include +#include + namespace { const char frame_vertex_shader[] = @@ -87,25 +95,24 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio) } // namespace -CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget *parent) - : stream_name(stream_name), requested_stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { +CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) : + stream_name(stream_name), requested_stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { + setAttribute(Qt::WA_OpaquePaintEvent); + qRegisterMetaType>("availableStreams"); + QObject::connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); + QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection); + QObject::connect(this, &CameraWidget::vipcAvailableStreamsUpdated, this, &CameraWidget::availableStreamsUpdated, Qt::QueuedConnection); } CameraWidget::~CameraWidget() { makeCurrent(); + stopVipcThread(); if (isValid()) { glDeleteVertexArrays(1, &frame_vao); glDeleteBuffers(1, &frame_vbo); glDeleteBuffers(1, &frame_ibo); glDeleteBuffers(2, textures); } -#ifdef QCOM2 - EGLDisplay egl_display = eglGetCurrentDisplay(); - for (auto &pair : egl_images) { - eglDestroyImageKHR(egl_display, pair.second); - } - egl_images.clear(); -#endif doneCurrent(); } @@ -169,11 +176,45 @@ void CameraWidget::initializeGL() { #endif } +void CameraWidget::showEvent(QShowEvent *event) { + if (!vipc_thread) { + clearFrames(); + vipc_thread = new QThread(); + connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); + connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater); + vipc_thread->start(); + } +} + +void CameraWidget::stopVipcThread() { + makeCurrent(); + if (vipc_thread) { + vipc_thread->requestInterruption(); + vipc_thread->quit(); + vipc_thread->wait(); + vipc_thread = nullptr; + } + +#ifdef QCOM2 + EGLDisplay egl_display = eglGetCurrentDisplay(); + assert(egl_display != EGL_NO_DISPLAY); + for (auto &pair : egl_images) { + eglDestroyImageKHR(egl_display, pair.second); + assert(eglGetError() == EGL_SUCCESS); + } + egl_images.clear(); +#endif +} + +void CameraWidget::availableStreamsUpdated(std::set streams) { + available_streams = streams; +} + void CameraWidget::updateFrameMat() { int w = glWidth(), h = glHeight(); if (zoomed_view) { - if (streamType() == VISION_STREAM_DRIVER) { + if (active_stream_type == VISION_STREAM_DRIVER) { if (stream_width > 0 && stream_height > 0) { frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); } @@ -182,7 +223,7 @@ void CameraWidget::updateFrameMat() { // to ensure this ends up in the middle of the screen // for narrow come and a little lower for wide cam. // TODO: use proper perspective transform? - if (streamType() == VISION_STREAM_WIDE_ROAD) { + if (active_stream_type == VISION_STREAM_WIDE_ROAD) { intrinsic_matrix = ECAM_INTRINSIC_MATRIX; zoom = 2.0; } else { @@ -228,15 +269,25 @@ void CameraWidget::paintGL() { glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - if (!frame) return; + std::lock_guard lk(frame_lock); + if (frames.empty()) return; + + int frame_idx = frames.size() - 1; + + // Always draw latest frame until sync logic is more stable + // for (frame_idx = 0; frame_idx < frames.size() - 1; frame_idx++) { + // if (frames[frame_idx].first == draw_frame_id) break; + // } // Log duplicate/dropped frames - if (frame_id == prev_frame_id) { - qDebug() << "Drawing same frame twice" << frame_id; - } else if (frame_id != prev_frame_id + 1) { - qDebug() << "Skipped frame" << frame_id; + if (frames[frame_idx].first == prev_frame_id) { + qDebug() << "Drawing same frame twice" << frames[frame_idx].first; + } else if (frames[frame_idx].first != prev_frame_id + 1) { + qDebug() << "Skipped frame" << frames[frame_idx].first; } - prev_frame_id = frame_id; + prev_frame_id = frames[frame_idx].first; + VisionBuf *frame = frames[frame_idx].second; + assert(frame != nullptr); updateFrameMat(); @@ -276,7 +327,7 @@ void CameraWidget::paintGL() { glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } -void CameraWidget::vipcConnected() { +void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { makeCurrent(); stream_width = vipc_client->buffers[0].width; stream_height = vipc_client->buffers[0].height; @@ -326,69 +377,59 @@ void CameraWidget::vipcConnected() { #endif } -bool CameraWidget::receiveFrame(uint64_t request_frame_id) { - if (!vipc_client || vipc_client->type != requested_stream_type) { - qDebug().nospace() << "connecting to stream" << requested_stream_type - << (vipc_client ? QString(", was connected to %1").arg(vipc_client->type) : ""); - vipc_client.reset(new VisionIpcClient(stream_name, requested_stream_type, false)); - } +void CameraWidget::vipcFrameReceived() { + update(); +} - if (!vipc_client->connected) { - frame = nullptr; - recent_frames.clear(); - available_streams = VisionIpcClient::getAvailableStreams(stream_name, false); - if (available_streams.empty() || !vipc_client->connect(false)) { - return false; +void CameraWidget::vipcThread() { + VisionStreamType cur_stream = requested_stream_type; + std::unique_ptr vipc_client; + VisionIpcBufExtra meta_main = {0}; + + while (!QThread::currentThread()->isInterruptionRequested()) { + if (!vipc_client || cur_stream != requested_stream_type) { + clearFrames(); + qDebug().nospace() << "connecting to stream " << requested_stream_type << ", was connected to " << cur_stream; + cur_stream = requested_stream_type; + vipc_client.reset(new VisionIpcClient(stream_name, cur_stream, false)); } - emit vipcAvailableStreamsUpdated(); - vipcConnected(); - } + active_stream_type = cur_stream; - VisionIpcBufExtra meta_main = {}; - while (auto buf = vipc_client->recv(&meta_main, 0)) { - if (recent_frames.size() > FRAME_BUFFER_SIZE) { - recent_frames.pop_front(); + if (!vipc_client->connected) { + clearFrames(); + auto streams = VisionIpcClient::getAvailableStreams(stream_name, false); + if (streams.empty()) { + QThread::msleep(100); + continue; + } + emit vipcAvailableStreamsUpdated(streams); + + if (!vipc_client->connect(false)) { + QThread::msleep(100); + continue; + } + emit vipcThreadConnected(vipc_client.get()); } - recent_frames.emplace_back(meta_main.frame_id, buf); - } - frame = nullptr; - if (request_frame_id > 0) { - auto it = std::find_if(recent_frames.begin(), recent_frames.end(), - [request_frame_id](auto &f) { return f.first == request_frame_id; }); - if (it != recent_frames.end()) { - std::tie(frame_id, frame) = *it; + if (VisionBuf *buf = vipc_client->recv(&meta_main, 1000)) { + { + std::lock_guard lk(frame_lock); + frames.push_back(std::make_pair(meta_main.frame_id, buf)); + while (frames.size() > FRAME_BUFFER_SIZE) { + frames.pop_front(); + } + } + emit vipcThreadFrameReceived(); + } else { + if (!isVisible()) { + vipc_client->connected = false; + } } - } else if (!recent_frames.empty()) { - std::tie(frame_id, frame) = recent_frames.back(); - } - return frame != nullptr; -} - -void CameraWidget::disconnectVipc() { - recent_frames.clear(); - frame = nullptr; - frame_id = 0; - prev_frame_id = 0; - if (vipc_client) { - vipc_client->connected = false; } } -// Cameraview - -CameraView::CameraView(const std::string &name, VisionStreamType stream_type, bool zoom, QWidget *parent) - : CameraWidget(name, stream_type, zoom, parent) { - timer = new QTimer(this); - timer->setInterval(1000.0 / UI_FREQ); - timer->callOnTimeout(this, [this]() { if (receiveFrame()) update(); }); -} - -void CameraView::showEvent(QShowEvent *event) { - timer->start(); -} - -void CameraView::hideEvent(QHideEvent *event) { - timer->stop(); - disconnectVipc(); +void CameraWidget::clearFrames() { + std::lock_guard lk(frame_lock); + frames.clear(); + available_streams.clear(); } diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index a862079f1a..c97038cf43 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,7 +11,7 @@ #include #include #include -#include +#include #ifdef QCOM2 #define EGL_EGLEXT_PROTOTYPES @@ -32,27 +33,31 @@ class CameraWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: + using QOpenGLWidget::QOpenGLWidget; explicit CameraWidget(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr); ~CameraWidget(); - void disconnectVipc(); void setBackgroundColor(const QColor &color) { bg = color; } + void setFrameId(int frame_id) { draw_frame_id = frame_id; } void setStreamType(VisionStreamType type) { requested_stream_type = type; } - inline VisionStreamType streamType() const { return requested_stream_type; } - inline const std::set &availableStreams() const { return available_streams; } - bool receiveFrame(uint64_t request_frame_id = 0); + VisionStreamType getStreamType() { return active_stream_type; } + void stopVipcThread(); signals: - void vipcAvailableStreamsUpdated(); void clicked(); + void vipcThreadConnected(VisionIpcClient *); + void vipcThreadFrameReceived(); + void vipcAvailableStreamsUpdated(std::set); protected: void paintGL() override; void initializeGL() override; void resizeGL(int w, int h) override { updateFrameMat(); } + void showEvent(QShowEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); } virtual void updateFrameMat(); void updateCalibration(const mat3 &calib); - void vipcConnected(); + void vipcThread(); + void clearFrames(); int glWidth(); int glHeight(); @@ -68,18 +73,14 @@ protected: std::map egl_images; #endif - // vipc std::string stream_name; int stream_width = 0; int stream_height = 0; int stream_stride = 0; - VisionStreamType requested_stream_type; + std::atomic active_stream_type; + std::atomic requested_stream_type; std::set available_streams; - std::unique_ptr vipc_client; - std::deque> recent_frames; - VisionBuf *frame = nullptr; - uint64_t frame_id = 0; - uint64_t prev_frame_id = 0; + QThread *vipc_thread = nullptr; // Calibration float x_offset = 0; @@ -87,16 +88,16 @@ protected: float zoom = 1.0; mat3 calibration = DEFAULT_CALIBRATION; mat3 intrinsic_matrix = FCAM_INTRINSIC_MATRIX; + + std::recursive_mutex frame_lock; + std::deque> frames; + uint32_t draw_frame_id = 0; + uint32_t prev_frame_id = 0; + +protected slots: + void vipcConnected(VisionIpcClient *vipc_client); + void vipcFrameReceived(); + void availableStreamsUpdated(std::set streams); }; -// update frames based on timer -class CameraView : public CameraWidget { - Q_OBJECT -public: - CameraView(const std::string &name, VisionStreamType stream_type, bool zoom, QWidget *parent = nullptr); - void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; - -private: - QTimer *timer; -}; +Q_DECLARE_METATYPE(std::set); diff --git a/selfdrive/ui/watch3.cc b/selfdrive/ui/watch3.cc index cb12f82ea8..ec35c29b6b 100644 --- a/selfdrive/ui/watch3.cc +++ b/selfdrive/ui/watch3.cc @@ -19,15 +19,15 @@ int main(int argc, char *argv[]) { { QHBoxLayout *hlayout = new QHBoxLayout(); layout->addLayout(hlayout); - hlayout->addWidget(new CameraView("navd", VISION_STREAM_MAP, false)); - hlayout->addWidget(new CameraView("camerad", VISION_STREAM_ROAD, false)); + hlayout->addWidget(new CameraWidget("navd", VISION_STREAM_MAP, false)); + hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_ROAD, false)); } { QHBoxLayout *hlayout = new QHBoxLayout(); layout->addLayout(hlayout); - hlayout->addWidget(new CameraView("camerad", VISION_STREAM_DRIVER, false)); - hlayout->addWidget(new CameraView("camerad", VISION_STREAM_WIDE_ROAD, false)); + hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_DRIVER, false)); + hlayout->addWidget(new CameraWidget("camerad", VISION_STREAM_WIDE_ROAD, false)); } return a.exec(); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 654c34ed37..a6fd0b2b64 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -139,7 +139,7 @@ QWidget *VideoWidget::createCameraWidget() { QStackedLayout *stacked = new QStackedLayout(); stacked->setStackingMode(QStackedLayout::StackAll); - stacked->addWidget(cam_widget = new CameraView("camerad", VISION_STREAM_ROAD, false)); + stacked->addWidget(cam_widget = new CameraWidget("camerad", VISION_STREAM_ROAD, false)); cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT); cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); stacked->addWidget(alert_label = new InfoLabel(this)); @@ -160,13 +160,12 @@ QWidget *VideoWidget::createCameraWidget() { return w; } -void VideoWidget::vipcAvailableStreamsUpdated() { +void VideoWidget::vipcAvailableStreamsUpdated(std::set streams) { static const QString stream_names[] = { [VISION_STREAM_ROAD] = "Road camera", [VISION_STREAM_WIDE_ROAD] = "Wide road camera", [VISION_STREAM_DRIVER] = "Driver camera"}; - const auto &streams = cam_widget->availableStreams(); for (int i = 0; i < streams.size(); ++i) { if (camera_tab->count() <= i) { camera_tab->addTab(QString()); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index ae095fd559..b2039e09a4 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -73,9 +73,9 @@ protected: QWidget *createCameraWidget(); QHBoxLayout *createPlaybackController(); void loopPlaybackClicked(); - void vipcAvailableStreamsUpdated(); + void vipcAvailableStreamsUpdated(std::set streams); - CameraView *cam_widget; + CameraWidget *cam_widget; double maximum_time = 0; QToolButton *time_btn = nullptr; ToolButton *seek_backward_btn = nullptr; From 2a49f94872c03086e52c0318c93bdd0c1d4bcafd Mon Sep 17 00:00:00 2001 From: Nelson Chen Date: Sun, 7 Jan 2024 15:02:25 -0800 Subject: [PATCH 394/724] Add necessary permissions/tokens for "lewagon/wait-on-check-action" so it can run nightly (#30923) --- .github/workflows/prebuilt.yaml | 3 +++ .github/workflows/release.yaml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index 58095cf19c..23b3574355 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -15,6 +15,8 @@ jobs: if: github.repository == 'commaai/openpilot' env: PUSH_IMAGE: true + permissions: + checks: read steps: - name: Wait for green check mark if: ${{ github.event_name != 'workflow_dispatch' }} @@ -23,6 +25,7 @@ jobs: ref: master wait-interval: 30 running-workflow-name: 'build prebuilt' + repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build master-ci).*).)*$ - uses: actions/checkout@v4 with: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index cd2ebd9f82..f1dc4c2e4a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -14,6 +14,8 @@ jobs: image: ghcr.io/commaai/openpilot-base:latest runs-on: ubuntu-20.04 if: github.repository == 'commaai/openpilot' + permissions: + checks: read steps: - name: Install wait-on-check-action dependencies run: | @@ -26,6 +28,7 @@ jobs: ref: master wait-interval: 30 running-workflow-name: 'build master-ci' + repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build prebuilt).*).)*$ - uses: actions/checkout@v4 with: From 91c5c5eca765c990ff185a4a0bda7207eb55787d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Jan 2024 09:16:50 -0800 Subject: [PATCH 395/724] camerad: log request id (#30935) * camerad: log request id * bump cereal * bump cereal --- cereal | 2 +- system/camerad/cameras/camera_common.cc | 1 + system/camerad/cameras/camera_common.h | 1 + system/camerad/cameras/camera_qcom2.cc | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cereal b/cereal index e29625c30b..d81d86e7cd 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit e29625c30bb2a4a34ce21134d0e5a91b2d4fa1b1 +Subproject commit d81d86e7cd83d1eb40314964a4d194231381d557 diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index f74fb15bff..91c6d44f84 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -136,6 +136,7 @@ void CameraBuf::queue(size_t buf_idx) { void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &frame_data, CameraState *c) { framed.setFrameId(frame_data.frame_id); + framed.setRequestId(frame_data.request_id); framed.setTimestampEof(frame_data.timestamp_eof); framed.setTimestampSof(frame_data.timestamp_sof); framed.setIntegLines(frame_data.integ_lines); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 029d69990c..f98691ef00 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -26,6 +26,7 @@ const bool env_ctrl_exp_from_params = getenv("CTRL_EXP_FROM_PARAMS") != NULL; typedef struct FrameMetadata { uint32_t frame_id; + uint32_t request_id; // Timestamps uint64_t timestamp_sof; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 7915ad21fc..cbb959423c 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -766,6 +766,7 @@ void CameraState::handle_camera_event(void *evdat) { auto &meta_data = buf.camera_bufs_metadata[buf_idx]; meta_data.frame_id = main_id - idx_offset; + meta_data.request_id = real_id; meta_data.timestamp_sof = timestamp; exp_lock.lock(); meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); From 6743487d78f607b482a498518472b1e6c55d159f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Jan 2024 13:54:09 -0800 Subject: [PATCH 396/724] Update Python packages and pre-commit hooks (#30939) Co-authored-by: jnewb1 --- .pre-commit-config.yaml | 2 +- poetry.lock | 368 ++++++++++++++++++++-------------------- 2 files changed, 187 insertions(+), 183 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3e309eba9..0a92b9d38e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: - --explicit-package-bases exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.9 + rev: v0.1.11 hooks: - id: ruff exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' diff --git a/poetry.lock b/poetry.lock index d7e512ef8a..a991c76b1f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,13 +171,13 @@ frozenlist = ">=1.1.0" [[package]] name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" +version = "0.7.15" +description = "A light, configurable Sphinx theme" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, + {file = "alabaster-0.7.15-py3-none-any.whl", hash = "sha256:d99c6fd0f7a86fca68ecc5231c9de45227991c10ee6facfb894cf6afb953b142"}, + {file = "alabaster-0.7.15.tar.gz", hash = "sha256:0127f4b1db0afc914883f930e3d40763131aebac295522fc4a04d9e77c703705"}, ] [[package]] @@ -1333,13 +1333,13 @@ rewrite = ["tokenize-rt (>=3)"] [[package]] name = "geopandas" -version = "0.14.1" +version = "0.14.2" description = "Geographic pandas extensions" optional = false python-versions = ">=3.9" files = [ - {file = "geopandas-0.14.1-py3-none-any.whl", hash = "sha256:ed5a7cae7874bfc3238fb05e0501cc1760e1b7b11e5b76ecad29da644ca305da"}, - {file = "geopandas-0.14.1.tar.gz", hash = "sha256:4853ff89ecb6d1cfc43e7b3671092c8160e8a46a3dd7368f25906283314e42bb"}, + {file = "geopandas-0.14.2-py3-none-any.whl", hash = "sha256:0efa61235a68862c1c6be89fc3707cdeba67667d5676bb19e24f3c57a8c2f723"}, + {file = "geopandas-0.14.2.tar.gz", hash = "sha256:6e71d57b8376f9fdc9f1c3aa3170e7e420e91778de854f51013ae66fd371ccdb"}, ] [package.dependencies] @@ -1923,99 +1923,85 @@ test = ["pytest"] [[package]] name = "lxml" -version = "5.0.0" +version = "5.1.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +python-versions = ">=3.6" files = [ - {file = "lxml-5.0.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73bfab795d354aaf2f4eb7a5b0db513031734fd371047342d5803834ce19ec18"}, - {file = "lxml-5.0.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cb564bbe55ff0897d9cf1225041a44576d7ae87f06fd60163544c91de2623d3f"}, - {file = "lxml-5.0.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a5501438dd521bb7e0dde5008c40c7bfcfaafaf86eccb3f9bd27509abb793da"}, - {file = "lxml-5.0.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7ba26a7dc929a1b3487d51bbcb0099afed2fc06e891b82845c8f37a2d7d7fbbd"}, - {file = "lxml-5.0.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:9b59c429e1a2246da86ae237ffc3565efcdc71c281cd38ca8b44d5fb6a3b993a"}, - {file = "lxml-5.0.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:3ffa066db40b0347e48334bd4465de768e295a3525b9a59831228b5f4f93162d"}, - {file = "lxml-5.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8ce8b468ab50f9e944719d1134709ec11fe0d2840891a6cae369e22141b1094c"}, - {file = "lxml-5.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:583c0e15ae06adc81035346ae2abb2e748f0b5197e7740d8af31222db41bbf7b"}, - {file = "lxml-5.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:904d36165848b59c4e04ae5b969072e602bd987485076fca8ec42c6cd7a7aedc"}, - {file = "lxml-5.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ac21aace6712472e77ea9dfc38329f53830c4259ece54c786107105ebb069053"}, - {file = "lxml-5.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f92d73faa0b1a76d1932429d684b7ce95829e93c3eef3715ec9b98ab192c9d31"}, - {file = "lxml-5.0.0-cp310-cp310-win32.whl", hash = "sha256:03290e2f714f2e7431c8430c08b48167f657da7bc689c6248e828ff3c66d5b1b"}, - {file = "lxml-5.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:3e6cbb68bf70081f036bfc018649cf4b46c4e7eaf7860a277cae92dee2a57f69"}, - {file = "lxml-5.0.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:5382612ba2424cea5d2c89e2c29077023d8de88f8d60d5ceff5f76334516df9e"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:07a900735bad9af7be3085480bf384f68ed5580ba465b39a098e6a882c060d6b"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:980ba47c8db4b9d870014c7040edb230825b79017a6a27aa54cdb6fcc02d8cc0"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6507c58431dbd95b50654b3313c5ad54f90e54e5f2cdacf733de61eae478eec5"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:4a45a278518e4308865c1e9dbb2c42ce84fb154efb03adeb16fdae3c1687c7c9"}, - {file = "lxml-5.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:59cea9ba1c675fbd6867ca1078fc717a113e7f5b7644943b74137b7cc55abebf"}, - {file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd39ef87fd1f7bb5c4aa53454936e6135cbfe03fe3744e8218be193f9e4fef16"}, - {file = "lxml-5.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e6bb39d91bf932e7520cb5718ae3c2f498052aca53294d5d59fdd9068fe1a7f2"}, - {file = "lxml-5.0.0-cp311-cp311-win32.whl", hash = "sha256:21af2c3862db6f4f486cddf73ec1157b40d5828876c47cd880edcbad8240ea1b"}, - {file = "lxml-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:c1249aa4eaced30b59ecf8b8cae0b1ccede04583c74ca7d10b6f8bbead908b2c"}, - {file = "lxml-5.0.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:f30e697b6215e759d0824768b2c5b0618d2dc19abe6c67eeed2b0460f52470d1"}, - {file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d1bb64646480c36a4aa1b6a44a5b6e33d0fcbeab9f53f1b39072cd3bb2c6243a"}, - {file = "lxml-5.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4e69c36c8618707a90ed3fb6f48a6cc9254ffcdbf7b259e439a5ae5fbf9c5206"}, - {file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9ca498f8554a09fbc3a2f8fc4b23261e07bc27bef99b3df98e2570688033f6fc"}, - {file = "lxml-5.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0326e9b8176ea77269fb39e7af4010906e73e9496a9f8eaf06d253b1b1231ceb"}, - {file = "lxml-5.0.0-cp312-cp312-win32.whl", hash = "sha256:5fb988e15378d6e905ca8f60813950a0c56da9469d0e8e5d8fe785b282684ec5"}, - {file = "lxml-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:bb58e8f4b2cfe012cd312239b8d5139995fe8f5945c7c26d5fbbbb1ddb9acd47"}, - {file = "lxml-5.0.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81509dffd8aba3bdb43e90cbd218c9c068a1f4047d97bc9546b3ac9e3a4ae81d"}, - {file = "lxml-5.0.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e675a4b95208e74c34ac0751cc4bab9170e7728b61601fb0f4746892c2bb7e0b"}, - {file = "lxml-5.0.0-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:405e3760f83a8ba3bdb6e622ec79595cdc20db916ce37377bbcb95b5711fa4ca"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f15844a1b93dcaa09c2b22e22a73384f3ae4502347c3881cfdd674e14ac04e21"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88f559f8beb6b90e41a7faae4aca4c8173a4819874a9bf8e74c8d7c1d51f3162"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e8c63f5c7d87e7044880b01851ac4e863c3349e6f6b6ab456fe218d9346e816d"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:0d277d4717756fe8816f0beeff229cb72f9dd02a43b70e1d3f07c8efadfb9fe1"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8954da15403db1acfc0544b3c3f963a6ef4e428283ab6555e3e298bbbff1cf6"}, - {file = "lxml-5.0.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:aebd8fd378e074b22e79cad329dcccd243c40ff1cafaa512d19276c5bb9554e1"}, - {file = "lxml-5.0.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b6d4e148edee59c2ad38af15810dbcb8b5d7b13e5de3509d8cf3edfe74c0adca"}, - {file = "lxml-5.0.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:70ab4e02f7aa5fb4131c8b222a111ce7676f3767e36084fba3a4e7338dc82dcd"}, - {file = "lxml-5.0.0-cp36-cp36m-win32.whl", hash = "sha256:de1a8b54170024cf1c0c2718c82412bca42cd82e390556e3d8031af9541b416f"}, - {file = "lxml-5.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5b39f63edbe7e018c2ac1cf0259ee0dd2355274e8a3003d404699b040782e55e"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:77b73952534967a4497d9e4f26fbeebfba19950cbc66b7cc3a706214429d8106"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8cc0a951e5616ac626f7036309c41fb9774adcd4aa7db0886463da1ce5b65edb"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4b9d5b01900a760eb3acf6cef50aead4ef2fa79e7ddb927084244e41dfe37b65"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:173bcead3af5d87c7bca9a030675073ddaad8e0a9f0b04be07cd9390453e7226"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:44fa9afd632210f1eeda51cf284ed8dbab0c7ec8b008dd39ba02818e0e114e69"}, - {file = "lxml-5.0.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fef10f27d6318d2d7c88680e113511ddecf09ee4f9559b3623b73ee89fa8f6cc"}, - {file = "lxml-5.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3663542aee845129a981889c19b366beab0b1dadcf5ca164696aabfe1aa51667"}, - {file = "lxml-5.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7188495c1bf71bfda87d78ed50601e72d252119ce11710d6e71ff36e35fea5a0"}, - {file = "lxml-5.0.0-cp37-cp37m-win32.whl", hash = "sha256:6a2de85deabf939b0af89e2e1ea46bfb1239545e2da6f8ac96522755a388025f"}, - {file = "lxml-5.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ea56825c1e23c9c8ea385a191dac75f9160477057285b88c88736d9305e6118f"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:3f908afd0477cace17f941d1b9cfa10b769fe1464770abe4cfb3d9f35378d0f8"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52a9ab31853d3808e7cf0183b3a5f7e8ffd622ea4aee1deb5252dbeaefd5b40d"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c7fe19abb3d3c55a9e65d289b12ad73b3a31a3f0bda3c539a890329ae9973bd6"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:1ef0793e1e2dd221fce7c142177008725680f7b9e4a184ab108d90d5d3ab69b7"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:581a78f299a9f5448b2c3aea904bfcd17c59bf83016d221d7f93f83633bb2ab2"}, - {file = "lxml-5.0.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:affdd833f82334fdb10fc9a1c7b35cdb5a86d0b672b4e14dd542e1fe7bcea894"}, - {file = "lxml-5.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6bba06d8982be0f0f6432d289a8d104417a0ab9ed04114446c4ceb6d4a40c65d"}, - {file = "lxml-5.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:80209b31dd3908bc5b014f540fd192c97ea52ab179713a730456c5baf7ce80c1"}, - {file = "lxml-5.0.0-cp38-cp38-win32.whl", hash = "sha256:dac2733fe4e159b0aae0439db6813b7b1d23ff96d0b34c0107b87faf79208c4e"}, - {file = "lxml-5.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:ee60f33456ff34b2dd1d048a740a2572798356208e4c494301c931de3a0ab3a2"}, - {file = "lxml-5.0.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:5eff173f0ff408bfa578cbdafd35a7e0ca94d1a9ffe09a8a48e0572d0904d486"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:78d6d8e5b54ed89dc0f0901eaaa579c384ad8d59fa43cc7fb06e9bb89115f8f4"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:71a7cee869578bc17b18050532bb2f0bc682a7b97dda77041741a1bd2febe6c7"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7df433d08d4587dc3932f7fcfc3194519a6824824104854e76441fd3bc000d29"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:793be9b4945c2dfd69828fb5948d7d9569b78e0599e4a2e88d92affeb0ff3aa3"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7cfb6af73602c8d288581df8a225989d7e9d5aab0a174be0e19fcfa800b6797"}, - {file = "lxml-5.0.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bfdc4668ac56687a89ca3eca44231144a2e9d02ba3b877558db74ba20e2bd9fa"}, - {file = "lxml-5.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2992591e2294bb07faf7f5f6d5cb60710c046404f4bfce09fb488b85d2a8f58f"}, - {file = "lxml-5.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4786b0af7511ea614fd86407a52a7bc161aa5772d311d97df2591ed2351de768"}, - {file = "lxml-5.0.0-cp39-cp39-win32.whl", hash = "sha256:016de3b29a262655fc3d2075dc1b2611f84f4c3d97a71d579c883d45e201eee4"}, - {file = "lxml-5.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:52c0acc2f29b0a204efc11a5ed911a74f50a25eb7d7d5069c2b1fd3b3346ce11"}, - {file = "lxml-5.0.0-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:96095bfc0c02072fc89afa67626013a253596ea5118b8a7f4daaae049dafa096"}, - {file = "lxml-5.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:992029258ed719f130d5a9c443d142c32843046f1263f2c492862b2a853be570"}, - {file = "lxml-5.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:db40e85cffd22f7d65dcce30e85af565a66401a6ed22fc0c56ed342cfa4ffc43"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:cfa8a4cdc3765574b7fd0c7cfa5fbd1e2108014c9dfd299c679e5152bea9a55e"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:049fef98d02513c34f5babd07569fc1cf1ed14c0f2fbff18fe72597f977ef3c2"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a85136d0ee18a41c91cc3e2844c683be0e72e6dda4cb58da9e15fcaef3726af7"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:766868f729f3ab84125350f1a0ea2594d8b1628a608a574542a5aff7355b9941"}, - {file = "lxml-5.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:99cad5c912f359e59e921689c04e54662cdd80835d80eeaa931e22612f515df7"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:c90c593aa8dd57d5dab0ef6d7d64af894008971d98e6a41b320fdd75258fbc6e"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8134d5441d1ed6a682e3de3d7a98717a328dce619ee9c4c8b3b91f0cb0eb3e28"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f298ac9149037d6a3d5c74991bded39ac46292520b9c7c182cb102486cc87677"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:894c5f71186b410679aaab5774543fcb9cbabe8893f0b31d11cf28a0740e80be"}, - {file = "lxml-5.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9cd3d6c2c67d4fdcd795e4945e2ba5434909c96640b4cc09453bd0dc7e8e1bac"}, - {file = "lxml-5.0.0.zip", hash = "sha256:2219cbf790e701acf9a21a31ead75f983e73daf0eceb9da6990212e4d20ebefe"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, + {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, + {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, + {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, + {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, + {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, + {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, + {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, + {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, + {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, + {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, + {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, + {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cfbac9f6149174f76df7e08c2e28b19d74aed90cad60383ad8671d3af7d0502f"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, + {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, + {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, + {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, + {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, + {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, ] [package.extras] @@ -2896,70 +2882,88 @@ dev = ["jinja2"] [[package]] name = "pillow" -version = "10.1.0" +version = "10.2.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, - {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, - {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, - {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, - {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, - {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, - {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, - {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, - {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, - {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, - {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, - {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, - {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, - {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, - {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, - {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, - {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, - {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, - {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, - {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, - {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, - {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, - {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, - {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, ] [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] [[package]] name = "platformdirs" @@ -3966,28 +3970,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.1.9" +version = "0.1.11" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6a212f436122ac73df851f0cf006e0c6612fe6f9c864ed17ebefce0eff6a5fd"}, - {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:28d920e319783d5303333630dae46ecc80b7ba294aeffedf946a02ac0b7cc3db"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:104aa9b5e12cb755d9dce698ab1b97726b83012487af415a4512fedd38b1459e"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e63bf5a4a91971082a4768a0aba9383c12392d0d6f1e2be2248c1f9054a20da"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d0738917c203246f3e275b37006faa3aa96c828b284ebfe3e99a8cb413c8c4b"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69dac82d63a50df2ab0906d97a01549f814b16bc806deeac4f064ff95c47ddf5"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2aec598fb65084e41a9c5d4b95726173768a62055aafb07b4eff976bac72a592"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:744dfe4b35470fa3820d5fe45758aace6269c578f7ddc43d447868cfe5078bcb"}, - {file = "ruff-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479ca4250cab30f9218b2e563adc362bd6ae6343df7c7b5a7865300a5156d5a6"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:aa8344310f1ae79af9ccd6e4b32749e93cddc078f9b5ccd0e45bd76a6d2e8bb6"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:837c739729394df98f342319f5136f33c65286b28b6b70a87c28f59354ec939b"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6837202c2859b9f22e43cb01992373c2dbfeae5c0c91ad691a4a2e725392464"}, - {file = "ruff-0.1.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:331aae2cd4a0554667ac683243b151c74bd60e78fb08c3c2a4ac05ee1e606a39"}, - {file = "ruff-0.1.9-py3-none-win32.whl", hash = "sha256:8151425a60878e66f23ad47da39265fc2fad42aed06fb0a01130e967a7a064f4"}, - {file = "ruff-0.1.9-py3-none-win_amd64.whl", hash = "sha256:c497d769164df522fdaf54c6eba93f397342fe4ca2123a2e014a5b8fc7df81c7"}, - {file = "ruff-0.1.9-py3-none-win_arm64.whl", hash = "sha256:0e17f53bcbb4fff8292dfd84cf72d767b5e146f009cccd40c2fad27641f8a7a9"}, - {file = "ruff-0.1.9.tar.gz", hash = "sha256:b041dee2734719ddbb4518f762c982f2e912e7f28b8ee4fe1dee0b15d1b6e800"}, + {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a7f772696b4cdc0a3b2e527fc3c7ccc41cdcb98f5c80fdd4f2b8c50eb1458196"}, + {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934832f6ed9b34a7d5feea58972635c2039c7a3b434fe5ba2ce015064cb6e955"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea0d3e950e394c4b332bcdd112aa566010a9f9c95814844a7468325290aabfd9"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bd4025b9c5b429a48280785a2b71d479798a69f5c2919e7d274c5f4b32c3607"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ad00662305dcb1e987f5ec214d31f7d6a062cae3e74c1cbccef15afd96611d"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4b077ce83f47dd6bea1991af08b140e8b8339f0ba8cb9b7a484c30ebab18a23f"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a88efecec23c37b11076fe676e15c6cdb1271a38f2b415e381e87fe4517f18"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b25093dad3b055667730a9b491129c42d45e11cdb7043b702e97125bcec48a1"}, + {file = "ruff-0.1.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231d8fb11b2cc7c0366a326a66dafc6ad449d7fcdbc268497ee47e1334f66f77"}, + {file = "ruff-0.1.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:09c415716884950080921dd6237767e52e227e397e2008e2bed410117679975b"}, + {file = "ruff-0.1.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f58948c6d212a6b8d41cd59e349751018797ce1727f961c2fa755ad6208ba45"}, + {file = "ruff-0.1.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:190a566c8f766c37074d99640cd9ca3da11d8deae2deae7c9505e68a4a30f740"}, + {file = "ruff-0.1.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6464289bd67b2344d2a5d9158d5eb81025258f169e69a46b741b396ffb0cda95"}, + {file = "ruff-0.1.11-py3-none-win32.whl", hash = "sha256:9b8f397902f92bc2e70fb6bebfa2139008dc72ae5177e66c383fa5426cb0bf2c"}, + {file = "ruff-0.1.11-py3-none-win_amd64.whl", hash = "sha256:eb85ee287b11f901037a6683b2374bb0ec82928c5cbc984f575d0437979c521a"}, + {file = "ruff-0.1.11-py3-none-win_arm64.whl", hash = "sha256:97ce4d752f964ba559c7023a86e5f8e97f026d511e48013987623915431c7ea9"}, + {file = "ruff-0.1.11.tar.gz", hash = "sha256:f9d4d88cb6eeb4dfe20f9f0519bd2eaba8119bde87c3d5065c541dbae2b5a2cb"}, ] [[package]] @@ -4034,13 +4038,13 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo [[package]] name = "scons" -version = "4.6.0" +version = "4.6.0.post1" description = "Open Source next-generation build tool." optional = false python-versions = ">=3.6" files = [ - {file = "SCons-4.6.0-py3-none-any.whl", hash = "sha256:07cc8ed992561c9a3af3d5b65a4635cb05b2a8c0695be90c4879ac6018d84ccc"}, - {file = "SCons-4.6.0.tar.gz", hash = "sha256:7db28958b188b800f803c287d0680cc3ac7c422ed0b1cf9895042c52567803ec"}, + {file = "SCons-4.6.0.post1-py3-none-any.whl", hash = "sha256:9e0527b7a924e7af2b312c1f8961ef2776847bdc46a4d886af5a75f301da7ce3"}, + {file = "SCons-4.6.0.post1.tar.gz", hash = "sha256:d467a34546f5366a32e4d4419611d0138dd680210f24aa8491ebe9e4b83456cf"}, ] [package.dependencies] @@ -4654,13 +4658,13 @@ telegram = ["requests"] [[package]] name = "types-requests" -version = "2.31.0.20231231" +version = "2.31.0.20240106" description = "Typing stubs for requests" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.20231231.tar.gz", hash = "sha256:0f8c0c9764773384122813548d9eea92a5c4e1f33ed54556b508968ec5065cee"}, - {file = "types_requests-2.31.0.20231231-py3-none-any.whl", hash = "sha256:2e2230c7bc8dd63fa3153c1c0ae335f8a368447f0582fc332f17d54f88e69027"}, + {file = "types-requests-2.31.0.20240106.tar.gz", hash = "sha256:0e1c731c17f33618ec58e022b614a1a2ecc25f7dc86800b36ef341380402c612"}, + {file = "types_requests-2.31.0.20240106-py3-none-any.whl", hash = "sha256:da997b3b6a72cc08d09f4dba9802fdbabc89104b35fe24ee588e674037689354"}, ] [package.dependencies] @@ -4668,13 +4672,13 @@ urllib3 = ">=2" [[package]] name = "types-tabulate" -version = "0.9.0.3" +version = "0.9.0.20240106" description = "Typing stubs for tabulate" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "types-tabulate-0.9.0.3.tar.gz", hash = "sha256:197651f9d6467193cd166d8500116a6d3a26f2a4eb2db093bc9535ee1c0be55e"}, - {file = "types_tabulate-0.9.0.3-py3-none-any.whl", hash = "sha256:462d1b62e01728416e8277614d6a3eb172d53a8efaf04a04a973ff2dd45238f6"}, + {file = "types-tabulate-0.9.0.20240106.tar.gz", hash = "sha256:c9b6db10dd7fcf55bd1712dd3537f86ddce72a08fd62bb1af4338c7096ce947e"}, + {file = "types_tabulate-0.9.0.20240106-py3-none-any.whl", hash = "sha256:0378b7b6fe0ccb4986299496d027a6d4c218298ecad67199bbd0e2d7e9d335a1"}, ] [[package]] From 5f191321fddca4eafb55c992415209a9389e2617 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Jan 2024 15:09:38 -0800 Subject: [PATCH 397/724] tici: check that peripherals are fully booted (#30942) * tici: check that peripherals are fully booted * default * typo * fix --------- Co-authored-by: Comma Device --- selfdrive/thermald/thermald.py | 3 +++ system/hardware/base.py | 3 +++ system/hardware/tici/hardware.py | 11 +++++++++++ 3 files changed, 17 insertions(+) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 8b51aedbdb..7011ff0a99 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -312,6 +312,9 @@ def thermald_thread(end_event, hw_queue) -> None: # must be at an engageable thermal band to go onroad startup_conditions["device_temp_engageable"] = thermal_status < ThermalStatus.red + # ensure device is fully booted + startup_conditions["device_booted"] = startup_conditions.get("device_booted", False) or HARDWARE.booted() + # if the temperature enters the danger zone, go offroad to cool down onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger extra_text = f"{offroad_comp_temp:.1f}C" diff --git a/system/hardware/base.py b/system/hardware/base.py index 0b6ca44c3c..b432a41907 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -23,6 +23,9 @@ class HardwareBase(ABC): except Exception: return default + def booted(self) -> bool: + return True + @abstractmethod def reboot(self, reason=None): pass diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index f895bbc508..d1ac52c8dc 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -74,6 +74,11 @@ def sudo_write(val, path): # fallback for debugfs files os.system(f"sudo su -c 'echo {val} > {path}'") +def sudo_read(path: str) -> str: + try: + return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8') + except Exception: + return "" def affine_irq(val, action): irqs = get_irqs_for_action(action) @@ -554,6 +559,12 @@ class Tici(HardwareBase): time.sleep(0.5) gpio_set(GPIO.STM_BOOT0, 0) + def booted(self): + # this normally boots within 8s, but on rare occasions takes 30+s + encoder_state = sudo_read("/sys/kernel/debug/msm_vidc/core0/info") + if "Core state: 0" in encoder_state and (time.monotonic() < 60*2): + return False + return True if __name__ == "__main__": t = Tici() From d689a4e6537b6b48e0ad9948ac919065317258c1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 8 Jan 2024 16:36:43 -0700 Subject: [PATCH 398/724] Fix string indentation (#30943) * fix bad spacing * more * these aren't important --- selfdrive/car/tests/test_lateral_limits.py | 4 ++-- selfdrive/debug/test_fw_query_on_routes.py | 4 ++-- selfdrive/ui/translations/create_badges.py | 4 ++-- tools/tuning/measure_steering_accuracy.py | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/car/tests/test_lateral_limits.py b/selfdrive/car/tests/test_lateral_limits.py index 1fc626972f..10e24ff4c5 100755 --- a/selfdrive/car/tests/test_lateral_limits.py +++ b/selfdrive/car/tests/test_lateral_limits.py @@ -95,8 +95,8 @@ if __name__ == "__main__": _jerks["down_jerk"] > MAX_LAT_JERK_DOWN violation_str = " - VIOLATION" if violation else "" - print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} \ - m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}") + print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} " + + f"m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}") # exit with test result sys.exit(not result.result.wasSuccessful()) diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 83e173244e..20c5486c32 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -112,8 +112,8 @@ if __name__ == "__main__": padding = max([len(fw.brand or UNKNOWN_BRAND) for fw in car_fw]) for version in sorted(car_fw, key=lambda fw: fw.brand): subaddr = None if version.subAddress == 0 else hex(version.subAddress) - print(f" Brand: {version.brand or UNKNOWN_BRAND:{padding}}, bus: {version.bus} - \ - (Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],") + print(f" Brand: {version.brand or UNKNOWN_BRAND:{padding}}, bus: {version.bus} - " + + "(Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],") print("Mismatches") found = False diff --git a/selfdrive/ui/translations/create_badges.py b/selfdrive/ui/translations/create_badges.py index a1b2ecb289..f6c8aae448 100755 --- a/selfdrive/ui/translations/create_badges.py +++ b/selfdrive/ui/translations/create_badges.py @@ -54,8 +54,8 @@ if __name__ == "__main__": badge_svg.extend([f'', content_svg, ""]) - badge_svg.insert(0, f'') + badge_svg.insert(0, '') badge_svg.append("") with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f: diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index c615608852..a7718043cb 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -105,10 +105,10 @@ class SteeringAccuracyTool: print(f" {'-'*118}") for k in sorted(self.speed_group_stats[group].keys()): v = self.speed_group_stats[group][k] - print(f' {k:#2}° | actuator:{int(v["steer"] / v["cnt"] * 100):#3}% \ - | error: {round(v["err"] / v["cnt"], 2):2.2f}° | -:{int(v["-"] / v["cnt"] * 100):#3}% \ - | =:{int(v["="] / v["cnt"] * 100):#3}% | +:{int(v["+"] / v["cnt"] * 100):#3}% | lim:{v["limited"]:#5} \ - | sat:{v["saturated"]:#5} | path dev: {round(v["dpp"] / v["cnt"], 2):2.2f}m | total: {v["cnt"]:#5}') + print(f' {k:#2}° | actuator:{int(v["steer"] / v["cnt"] * 100):#3}% ' + + f'| error: {round(v["err"] / v["cnt"], 2):2.2f}° | -:{int(v["-"] / v["cnt"] * 100):#3}% ' + + f'| =:{int(v["="] / v["cnt"] * 100):#3}% | +:{int(v["+"] / v["cnt"] * 100):#3}% | lim:{v["limited"]:#5} ' + + f'| sat:{v["saturated"]:#5} | path dev: {round(v["dpp"] / v["cnt"], 2):2.2f}m | total: {v["cnt"]:#5}') print("") From c8df0bd1d01ba41e1034edad1775eaf9595b58d4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Jan 2024 17:02:40 -0800 Subject: [PATCH 399/724] Bump submodules (#30938) bump submodules Co-authored-by: jnewb1 --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 20722a5946..d0184f7266 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 20722a59467fc7d697bfe1edd233a38bafbc89d2 +Subproject commit d0184f7266571ba563e5f992b6648ff4089b2dba From 90010754d6da2fa62c1b19d660510fc53af8c7da Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Jan 2024 17:16:15 -0800 Subject: [PATCH 400/724] unpin metadrive package (#30944) * unpin metadrive package * lock --- poetry.lock | 33 ++++++--------------------------- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/poetry.lock b/poetry.lock index a991c76b1f..597ed19b28 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2061,16 +2061,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2191,6 +2181,7 @@ files = [] develop = false [package.dependencies] +filelock = "*" geopandas = "*" gymnasium = ">=0.28,<0.29" lxml = "*" @@ -2199,12 +2190,12 @@ numpy = ">=1.21.6,<=1.24.2" opencv-python = "*" panda3d = "1.10.13" panda3d-gltf = "0.13" -panda3d-simplepbr = "*" pandas = "*" pillow = "*" progressbar = "*" psutil = "*" pygame = "*" +Pygments = "*" pytest = "*" requests = "*" scipy = "*" @@ -2214,17 +2205,15 @@ tqdm = "*" yapf = "*" [package.extras] -all = ["PyOpenGL (==3.1.6)", "PyOpenGL-accelerate (==3.1.6)", "aioboto3", "aiofiles", "bokeh (==2.4)", "boto3", "chardet", "cuda-python (==12.0.0)", "glfw", "hydra-core", "pyarrow", "pyrr (==0.10.3)", "retry"] cuda = ["PyOpenGL (==3.1.6)", "PyOpenGL-accelerate (==3.1.6)", "cuda-python (==12.0.0)", "glfw", "pyrr (==0.10.3)"] gym = ["gym (>=0.19.0,<=0.26.0)"] -nuplan = ["aioboto3", "aiofiles", "bokeh (==2.4)", "boto3", "chardet", "hydra-core", "pyarrow", "retry"] -waymo = ["waymo-open-dataset-tf-2.11.0 (==1.5.0)"] +ros = ["zmq"] [package.source] type = "git" url = "https://github.com/metadriverse/metadrive.git" -reference = "72e842cd1d025bf676e4af8797a01e4aa282109f" -resolved_reference = "72e842cd1d025bf676e4af8797a01e4aa282109f" +reference = "main" +resolved_reference = "9276a2cc3a149614d528e7e2a36c9d7a1307fb50" [[package]] name = "mpld3" @@ -3795,7 +3784,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3803,15 +3791,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3828,7 +3809,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3836,7 +3816,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -4892,4 +4871,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "3ab4ab63ba35d780759581d6572a6d6359c019a1be419f3c72ff3f0ee6008095" +content-hash = "ddef5baa3e9a1f50713ddc370ead968173200467a56df6a718ef3d2ada95e832" diff --git a/pyproject.toml b/pyproject.toml index 474c11e705..2fec88f244 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -125,7 +125,7 @@ inputs = "*" Jinja2 = "*" lru-dict = "*" matplotlib = "*" -metadrive-simulator = { git = "https://github.com/metadriverse/metadrive.git", rev ="72e842cd1d025bf676e4af8797a01e4aa282109f", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies +metadrive-simulator = { git = "https://github.com/metadriverse/metadrive.git", rev ="main", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies mpld3 = "*" mypy = "*" myst-parser = "*" From 7fdd5c4a7d72d16eb2246b4766a4da6c9aad0765 Mon Sep 17 00:00:00 2001 From: Hoang Bui <47828508+bongbui321@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:22:21 -0500 Subject: [PATCH 401/724] Python camerad for webcam (#30930) * webcamd * remove destroy windows * block * shebang execute * executaleeeee * executableeeeeee * she bang for camerad.py * listener * no signal? * good * ruff fix * delete listener * recover sim * remove assert * refactor * fix shell * fix namedtuple * little change * to tuple * cleanup * update readme --------- Co-authored-by: Adeeb Shihadeh --- tools/webcam/README.md | 5 --- tools/webcam/camera.py | 29 +++++++++++++++ tools/webcam/camerad.py | 68 +++++++++++++++++++++++++++++++++++ tools/webcam/start_camerad.sh | 14 ++++++++ 4 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 tools/webcam/camera.py create mode 100755 tools/webcam/camerad.py create mode 100755 tools/webcam/start_camerad.sh diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 237e07cdb6..bdf39b8145 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -1,5 +1,3 @@ -# NOTE: this README is outdated. #24590 tracks adding back webcam support - # Run openpilot with webcam on PC What's needed: @@ -24,9 +22,6 @@ git clone https://github.com/commaai/openpilot.git ## Build openpilot for webcam ``` cd ~/openpilot -``` -- check out system/camerad/cameras/camera_webcam.cc lines 72 and 146 before building if any camera is upside down -``` USE_WEBCAM=1 scons -j$(nproc) ``` diff --git a/tools/webcam/camera.py b/tools/webcam/camera.py new file mode 100644 index 0000000000..22438f0c98 --- /dev/null +++ b/tools/webcam/camera.py @@ -0,0 +1,29 @@ +import cv2 as cv +import numpy as np + +class Camera: + def __init__(self, cam_type_state, stream_type, camera_id): + self.cam_type_state = cam_type_state + self.stream_type = stream_type + self.cur_frame_id = 0 + + self.cap = cv.VideoCapture(camera_id) + self.W = self.cap.get(cv.CAP_PROP_FRAME_WIDTH) + self.H = self.cap.get(cv.CAP_PROP_FRAME_HEIGHT) + + @classmethod + def bgr2nv12(self, bgr): + yuv = cv.cvtColor(bgr, cv.COLOR_BGR2YUV_I420) + uv_row_cnt = yuv.shape[0] // 3 + uv_plane = np.transpose(yuv[uv_row_cnt * 2:].reshape(2, -1), [1, 0]) + yuv[uv_row_cnt * 2:] = uv_plane.reshape(uv_row_cnt, -1) + return yuv + + def read_frames(self): + while True: + sts , frame = self.cap.read() + if not sts: + break + yuv = Camera.bgr2nv12(frame) + yield yuv.data.tobytes() + self.cap.release() diff --git a/tools/webcam/camerad.py b/tools/webcam/camerad.py new file mode 100755 index 0000000000..caf044e7b6 --- /dev/null +++ b/tools/webcam/camerad.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import threading +import os +from collections import namedtuple + +from cereal.visionipc import VisionIpcServer, VisionStreamType +from cereal import messaging + +from openpilot.tools.webcam.camera import Camera +from openpilot.common.realtime import Ratekeeper + +DUAL_CAM = os.getenv("DUAL_CAMERA") +CameraType = namedtuple("CameraType", ["msg_name", "stream_type", "cam_id"]) +CAMERAS = [ + CameraType("roadCameraState", VisionStreamType.VISION_STREAM_ROAD, os.getenv("CAMERA_ROAD_ID", "0")), + CameraType("driverCameraState", VisionStreamType.VISION_STREAM_DRIVER, os.getenv("CAMERA_DRIVER_ID", "1")), +] +if DUAL_CAM: + CAMERAS.append(CameraType("wideRoadCameraState", VisionStreamType.VISION_STREAM_WIDE_ROAD, DUAL_CAM)) + +class Camerad: + def __init__(self): + self.pm = messaging.PubMaster([c.msg_name for c in CAMERAS]) + self.vipc_server = VisionIpcServer("camerad") + + self.cameras = [] + for c in CAMERAS: + cam = Camera(c.msg_name, c.stream_type, int(c.cam_id)) + assert cam.cap.isOpened(), f"Can't find camera {c}" + self.cameras.append(cam) + self.vipc_server.create_buffers(c.stream_type, 20, False, cam.W, cam.H) + + self.vipc_server.start_listener() + + def _send_yuv(self, yuv, frame_id, pub_type, yuv_type): + eof = int(frame_id * 0.05 * 1e9) + self.vipc_server.send(yuv_type, yuv, frame_id, eof, eof) + dat = messaging.new_message(pub_type, valid=True) + msg = { + "frameId": frame_id, + "transform": [1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0] + } + setattr(dat, pub_type, msg) + self.pm.send(pub_type, dat) + + def camera_runner(self, cam): + rk = Ratekeeper(20, None) + while cam.cap.isOpened(): + for yuv in cam.read_frames(): + self._send_yuv(yuv, cam.cur_frame_id, cam.cam_type_state, cam.stream_type) + cam.cur_frame_id += 1 + rk.keep_time() + + def run(self): + threads = [] + for cam in self.cameras: + cam_thread = threading.Thread(target=self.camera_runner, args=(cam,)) + cam_thread.start() + threads.append(cam_thread) + + for t in threads: + t.join() + +if __name__ == "__main__": + camerad = Camerad() + camerad.run() diff --git a/tools/webcam/start_camerad.sh b/tools/webcam/start_camerad.sh new file mode 100755 index 0000000000..fbda2be9f3 --- /dev/null +++ b/tools/webcam/start_camerad.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# export the block below when call manager.py +export BLOCK="${BLOCK},camerad" +export USE_WEBCAM="1" + +# Change camera index according to your setting +export CAMERA_ROAD_ID="0" +export CAMERA_DRIVER_ID="1" +export DUAL_CAMERA="2" # camera index for wide road camera + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +$DIR/camerad.py From 88e445ef154e6da795b653cbefd6391eef731d8e Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:40:41 -0500 Subject: [PATCH 402/724] VW MQB: Improved longitudinal starting and stopping (#30874) * VW MQB: Improved stopping and starting * get longcontrol to take off sooner * oops * use the carParam instead of the constant * update refs --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/car/volkswagen/carcontroller.py | 2 +- selfdrive/car/volkswagen/interface.py | 6 ++---- selfdrive/car/volkswagen/mqbcan.py | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index e7a7f2a998..2bc03a0e69 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -71,7 +71,7 @@ class CarController: acc_control = self.CCS.acc_control_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive) accel = clip(actuators.accel, self.CCP.ACCEL_MIN, self.CCP.ACCEL_MAX) if CC.longActive else 0 stopping = actuators.longControlState == LongCtrlState.stopping - starting = actuators.longControlState == LongCtrlState.starting + starting = actuators.longControlState == LongCtrlState.pid and (CS.esp_hold_confirmation or CS.out.vEgo < self.CP.vEgoStopping) can_sends.extend(self.CCS.create_acc_accel_control(self.packer_pt, CANBUS.pt, CS.acc_type, CC.longActive, accel, acc_control, stopping, starting, CS.esp_hold_confirmation)) diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index d5377453c1..0a6573217c 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -90,11 +90,9 @@ class CarInterface(CarInterfaceBase): ret.pcmCruise = not ret.openpilotLongitudinalControl ret.stoppingControl = True - ret.startingState = True - ret.startAccel = 1.0 ret.stopAccel = -0.55 - ret.vEgoStarting = 1.0 - ret.vEgoStopping = 1.0 + ret.vEgoStarting = 0.1 + ret.vEgoStopping = 0.5 ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py index 849d99592f..96e05aa8f0 100644 --- a/selfdrive/car/volkswagen/mqbcan.py +++ b/selfdrive/car/volkswagen/mqbcan.py @@ -94,7 +94,7 @@ def create_acc_accel_control(packer, bus, acc_type, acc_enabled, accel, acc_cont acc_hold_type = 0 acc_07_values = { - "ACC_Anhalteweg": 0.75 if stopping else 20.46, # Distance to stop (stopping coordinator handles terminal roll-out) + "ACC_Anhalteweg": 0.3 if stopping else 20.46, # Distance to stop (stopping coordinator handles terminal roll-out) "ACC_Freilauf_Info": 2 if acc_enabled else 0, "ACC_Folgebeschl": 3.02, # Not using secondary controller accel unless and until we understand its impact "ACC_Sollbeschleunigung_02": accel if acc_enabled else 3.01, diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 559749be14..13baf9df40 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -ea96f935a7a16c53623c3b03e70c0fbfa6b249e7 +94d2c0b18c2829b487fd23f079b08e9e3c75f4b4 \ No newline at end of file From d6cece756cc1fd9c8e6ca6290bdc7b0fd6d69d5b Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Mon, 8 Jan 2024 19:46:13 -0800 Subject: [PATCH 403/724] longplanner: expose dt (#30941) --- selfdrive/controls/lib/longitudinal_planner.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 13127b4634..018768f248 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -47,13 +47,14 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): class LongitudinalPlanner: - def __init__(self, CP, init_v=0.0, init_a=0.0): + def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): self.CP = CP self.mpc = LongitudinalMpc() self.fcw = False + self.dt = dt self.a_desired = init_a - self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL) + self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt) self.v_model_error = 0.0 self.v_desired_trajectory = np.zeros(CONTROL_N) @@ -148,8 +149,8 @@ class LongitudinalPlanner: # Interpolate 0.05 seconds and save as starting point for next iteration a_prev = self.a_desired - self.a_desired = float(interp(DT_MDL, ModelConstants.T_IDXS[:CONTROL_N], self.a_desired_trajectory)) - self.v_desired_filter.x = self.v_desired_filter.x + DT_MDL * (self.a_desired + a_prev) / 2.0 + self.a_desired = float(interp(self.dt, ModelConstants.T_IDXS[:CONTROL_N], self.a_desired_trajectory)) + self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0 def publish(self, sm, pm): plan_send = messaging.new_message('longitudinalPlan') From 1d370a0880902099bbb7c2b6ff126ab61ae149bf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 8 Jan 2024 20:46:27 -0700 Subject: [PATCH 404/724] Fingerprints: add missing FW versions from users (#30945) Export FW versions --- selfdrive/car/toyota/fingerprints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index f162d287f4..955ace8ef7 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -738,6 +738,7 @@ FW_VERSIONS = { b'\x018966353Q4000\x00\x00\x00\x00', b'\x018966353R1100\x00\x00\x00\x00', b'\x018966353R7100\x00\x00\x00\x00', + b'\x018966353R8000\x00\x00\x00\x00', b'\x018966353R8100\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ From af137bac5869a6ec2227d9183993a1ebcedd4ad4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 8 Jan 2024 19:49:22 -0800 Subject: [PATCH 405/724] GM: remove GMLAN dash msg (#30547) * remove GMLAN * update refs --- selfdrive/car/gm/carcontroller.py | 15 --------------- selfdrive/car/gm/values.py | 1 - selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 1 insertion(+), 17 deletions(-) diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index f51cb75372..6c7e8007f2 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -154,21 +154,6 @@ class CarController: if self.frame % 10 == 0: can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status)) - # Show green icon when LKA torque is applied, and - # alarming orange icon when approaching torque limit. - # If not sent again, LKA icon disappears in about 5 seconds. - # Conveniently, sending camera message periodically also works as a keepalive. - lka_active = CS.lkas_status == 1 - lka_critical = lka_active and abs(actuators.steer) > 0.9 - lka_icon_status = (lka_active, lka_critical) - - # SW_GMLAN not yet on cam harness, no HUD alerts - if self.CP.networkLocation != NetworkLocation.fwdCamera and \ - (self.frame % self.params.CAMERA_KEEPALIVE_STEP == 0 or lka_icon_status != self.lka_icon_status_last): - steer_alert = hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw) - can_sends.append(gmcan.create_lka_icon_command(CanBus.SW_GMLAN, lka_active, lka_critical, steer_alert)) - self.lka_icon_status_last = lka_icon_status - new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX new_actuators.steerOutputCan = self.apply_steer_last diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 2a8577164f..dbbf15100f 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -139,7 +139,6 @@ class CanBus: OBSTACLE = 1 CAMERA = 2 CHASSIS = 2 - SW_GMLAN = 3 LOOPBACK = 128 DROPPED = 192 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 13baf9df40..474d5056fd 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -94d2c0b18c2829b487fd23f079b08e9e3c75f4b4 \ No newline at end of file +80c700f9b87e069dcb5b7ec76cd6036667615f2e \ No newline at end of file From ca57d329e2e835c9dfd8545bc5780c0e2afd95fe Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 9 Jan 2024 01:48:48 -0700 Subject: [PATCH 406/724] Toyota: add 2024 RAV4 (#30909) * add 2024 RAV4 ICE FP * docs * also try vin bus 0 toyota * it works! --- RELEASES.md | 2 +- docs/CARS.md | 2 +- selfdrive/car/toyota/fingerprints.py | 2 ++ selfdrive/car/toyota/values.py | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index b12f22d280..36a4a89dd2 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -7,7 +7,7 @@ Version 0.9.6 (20XX-XX-XX) * comma body streaming and controls over WebRTC * Hyundai Staria 2023 support thanks to sunnyhaibin! * Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! -* Toyota RAV4 2023 support +* Toyota RAV4 2023-24 support * Toyota RAV4 Hybrid 2023-24 support Version 0.9.5 (2023-11-17) diff --git a/docs/CARS.md b/docs/CARS.md index bb2484e816..407c1d8cc7 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -240,7 +240,7 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts
- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 2023-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 955ace8ef7..485e3246d1 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -1137,6 +1137,7 @@ FW_VERSIONS = { CAR.RAV4_TSS2_2023: { (Ecu.abs, 0x7b0, None): [ b'\x01F15260R450\x00\x00\x00\x00\x00\x00', + b'\x01F15260R51000\x00\x00\x00\x00', b'\x01F15264283200\x00\x00\x00\x00', b'\x01F15264283300\x00\x00\x00\x00', b'\x01F152642F1000\x00\x00\x00\x00', @@ -1152,6 +1153,7 @@ FW_VERSIONS = { b'\x01896634AE1001\x00\x00\x00\x00', b'\x01896634AF0000\x00\x00\x00\x00', b'\x01896634AJ2000\x00\x00\x00\x00', + b'\x01896634AL5000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R03100\x00\x00\x00\x00', diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index fca7c50dc7..b3b6fd7377 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -180,7 +180,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), ], CAR.RAV4_TSS2_2023: [ - ToyotaCarInfo("Toyota RAV4 2023"), + ToyotaCarInfo("Toyota RAV4 2023-24"), ToyotaCarInfo("Toyota RAV4 Hybrid 2023-24"), ], CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"), From 2a074eefac19c9859eec12e1ec7f8a0f10789f1c Mon Sep 17 00:00:00 2001 From: Nelson Chen Date: Tue, 9 Jan 2024 10:40:45 -0800 Subject: [PATCH 407/724] Fix badges workflow blocking release/prebuilt workflows from permission and Git-LFS complications (#30947) --- .github/workflows/badges.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index 923e249849..2f1a7d67c4 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -14,6 +14,8 @@ jobs: name: create badges runs-on: ubuntu-20.04 if: github.repository == 'commaai/openpilot' + permissions: + contents: write steps: - uses: actions/checkout@v4 with: @@ -23,6 +25,8 @@ jobs: run: | ${{ env.RUN }} "scons -j$(nproc) && python selfdrive/ui/translations/create_badges.py" + rm .gitattributes + git checkout --orphan badges git rm -rf --cached . git config user.email "badge-researcher@comma.ai" From 221f81bbd3736dc764c2d57e93eedca3eb3db80d Mon Sep 17 00:00:00 2001 From: Nelson Chen Date: Tue, 9 Jan 2024 11:35:14 -0800 Subject: [PATCH 408/724] Add more permissions for pushing in prebuilt workflow (#30948) Continuing on from https://github.com/commaai/openpilot/pull/30947 --- .github/workflows/prebuilt.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index 23b3574355..990be73f72 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -17,6 +17,8 @@ jobs: PUSH_IMAGE: true permissions: checks: read + contents: read + packages: write steps: - name: Wait for green check mark if: ${{ github.event_name != 'workflow_dispatch' }} From cd1d7eb4d2e590c56a570e6f45189eb10401401c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 9 Jan 2024 14:19:10 -0800 Subject: [PATCH 409/724] OS04C10 support (#30658) * getting frames * ok * support both output * cleanup * fix unused * undo * fix release files --------- Co-authored-by: Comma Device --- release/files_common | 7 +- system/camerad/SConscript | 2 +- system/camerad/cameras/camera_qcom2.cc | 21 +- system/camerad/sensors/ar0231.cc | 5 +- system/camerad/sensors/os04c10.cc | 105 ++++++++ system/camerad/sensors/os04c10_registers.h | 298 +++++++++++++++++++++ system/camerad/sensors/ox03c10.cc | 5 +- system/camerad/sensors/sensor.h | 16 +- 8 files changed, 439 insertions(+), 20 deletions(-) create mode 100644 system/camerad/sensors/os04c10.cc create mode 100644 system/camerad/sensors/os04c10_registers.h diff --git a/release/files_common b/release/files_common index 0facb5cc5f..71c096716b 100644 --- a/release/files_common +++ b/release/files_common @@ -291,11 +291,8 @@ system/camerad/main.cc system/camerad/snapshot/* system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.cc -system/camerad/sensors/ar0231.cc -system/camerad/sensors/ar0231_registers.h -system/camerad/sensors/ox03c10.cc -system/camerad/sensors/ox03c10_registers.h -system/camerad/sensors/sensor.h +system/camerad/sensors/*.h +system/camerad/sensors/*.cc selfdrive/manager/__init__.py selfdrive/manager/build.py diff --git a/system/camerad/SConscript b/system/camerad/SConscript index a0056c1f9f..60a8f261e5 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -3,7 +3,7 @@ Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon, 'atomic'] camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc', - 'sensors/ar0231.cc', 'sensors/ox03c10.cc']) + 'sensors/ar0231.cc', 'sensors/ox03c10.cc', 'sensors/os04c10.cc']) env.Program('camerad', ['main.cc', camera_obj], LIBS=libs) if GetOption("extras") and arch == "x86_64": diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index cbb959423c..e17203a7dd 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -150,7 +150,7 @@ int CameraState::sensors_init() { power->count = 1; power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP; power->power_settings[0].power_seq_type = 0; - power->power_settings[0].config_val_low = ci->power_config_val_low; + power->power_settings[0].config_val_low = ci->mclk_frequency; power = power_set_wait(power, 1); // reset high @@ -316,10 +316,10 @@ void CameraState::config_isp(int io_mem_handle, int fence, int request_id, int b .h_init = 0x0, .v_init = 0x0, }; - io_cfg[0].format = CAM_FORMAT_MIPI_RAW_12; // CAM_FORMAT_UBWC_TP10 for YUV + io_cfg[0].format = ci->mipi_format; // CAM_FORMAT_UBWC_TP10 for YUV io_cfg[0].color_space = CAM_COLOR_SPACE_BASE; // CAM_COLOR_SPACE_BT601_FULL for YUV io_cfg[0].color_pattern = 0x5; // 0x0 for YUV - io_cfg[0].bpp = 0xc; + io_cfg[0].bpp = (ci->mipi_format == CAM_FORMAT_MIPI_RAW_10 ? 0xa : 0xc); // bits per pixel io_cfg[0].resource_type = CAM_ISP_IFE_OUT_RES_RDI_0; // CAM_ISP_IFE_OUT_RES_FULL for YUV io_cfg[0].fence = fence; io_cfg[0].direction = CAM_BUF_OUTPUT; @@ -459,7 +459,8 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num // Try different sensors one by one until it success. if (!init_sensor_lambda(new AR0231) && - !init_sensor_lambda(new OX03C10)) { + !init_sensor_lambda(new OX03C10) && + !init_sensor_lambda(new OS04C10)) { LOGE("** sensor %d FAILED bringup, disabling", camera_num); enabled = false; return; @@ -481,7 +482,6 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num LOG("-- Configuring sensor"); sensors_i2c(ci->init_reg_array.data(), ci->init_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, ci->data_word); - printf("dt is %x\n", ci->in_port_info_dt); // NOTE: to be able to disable road and wide road, we still have to configure the sensor over i2c // If you don't do this, the strobe GPIO is an output (even in reset it seems!) @@ -495,8 +495,8 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .lane_cfg = 0x3210, .vc = 0x0, - .dt = ci->in_port_info_dt, - .format = CAM_FORMAT_MIPI_RAW_12, + .dt = ci->frame_data_type, + .format = ci->mipi_format, .test_pattern = 0x2, // 0x3? .usage_type = 0x0, @@ -522,7 +522,7 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num .num_out_res = 0x1, .data[0] = (struct cam_isp_out_port_info){ .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, - .format = CAM_FORMAT_MIPI_RAW_12, + .format = ci->mipi_format, .width = ci->frame_width, .height = ci->frame_height + ci->extra_height, .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, @@ -971,6 +971,9 @@ void cameras_run(MultiCameraState *s) { event_data->u.frame_msg.frame_id, event_data->u.frame_msg.request_id, event_data->u.frame_msg.timestamp/1e6, event_data->u.frame_msg.sof_status); } + // for debugging + //do_exit = do_exit || event_data->u.frame_msg.frame_id > (30*20); + if (event_data->session_hdl == s->road_cam.session_handle) { s->road_cam.handle_camera_event(event_data); } else if (event_data->session_hdl == s->wide_road_cam.session_handle) { @@ -981,6 +984,8 @@ void cameras_run(MultiCameraState *s) { LOGE("Unknown vidioc event source"); assert(false); } + } else { + LOGE("unhandled event %d\n", ev.type); } } else { LOGE("VIDIOC_DQEVENT failed, errno=%d", errno); diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc index 559370ca62..1ca4b3f1ad 100644 --- a/system/camerad/sensors/ar0231.cc +++ b/system/camerad/sensors/ar0231.cc @@ -93,8 +93,9 @@ AR0231::AR0231() { init_reg_array.assign(std::begin(init_array_ar0231), std::end(init_array_ar0231)); probe_reg_addr = 0x3000; probe_expected_data = 0x354; - in_port_info_dt = 0x12; // Changing stats to 0x2C doesn't work, so change pixels to 0x12 instead - power_config_val_low = 19200000; //Hz + mipi_format = CAM_FORMAT_MIPI_RAW_12; + frame_data_type = 0x12; // Changing stats to 0x2C doesn't work, so change pixels to 0x12 instead + mclk_frequency = 19200000; //Hz dc_gain_factor = 2.5; dc_gain_min_weight = 0; diff --git a/system/camerad/sensors/os04c10.cc b/system/camerad/sensors/os04c10.cc new file mode 100644 index 0000000000..449e06be83 --- /dev/null +++ b/system/camerad/sensors/os04c10.cc @@ -0,0 +1,105 @@ +#include "system/camerad/sensors/sensor.h" + +namespace { + +const float sensor_analog_gains_OS04C10[] = { + 1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875, + 1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0, + 3.125, 3.375, 3.625, 3.875, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5, + 5.75, 6.0, 6.25, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, + 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5}; + +const uint32_t os04c10_analog_gains_reg[] = { + 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0, + 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300, + 0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, + 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, + 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; + +const uint32_t VS_TIME_MIN_OS04C10 = 1; +//const uint32_t VS_TIME_MAX_OS04C10 = 34; // vs < 35 + +} // namespace + +OS04C10::OS04C10() { + image_sensor = cereal::FrameData::ImageSensor::OS04C10; + data_word = false; + + frame_width = 1920; + frame_height = 1080; + frame_stride = (1920*10/8); + + /* + frame_width = 0xa80; + frame_height = 0x5f0; + frame_stride = 0xd20; + */ + + extra_height = 0; + frame_offset = 0; + + start_reg_array.assign(std::begin(start_reg_array_os04c10), std::end(start_reg_array_os04c10)); + init_reg_array.assign(std::begin(init_array_os04c10), std::end(init_array_os04c10)); + probe_reg_addr = 0x300a; + probe_expected_data = 0x5304; + mipi_format = CAM_FORMAT_MIPI_RAW_10; + frame_data_type = 0x2b; + mclk_frequency = 24000000; // Hz + + dc_gain_factor = 7.32; + dc_gain_min_weight = 1; // always on is fine + dc_gain_max_weight = 1; + dc_gain_on_grey = 0.9; + dc_gain_off_grey = 1.0; + exposure_time_min = 2; // 1x + exposure_time_max = 2016; + analog_gain_min_idx = 0x0; + analog_gain_rec_idx = 0x0; // 1x + analog_gain_max_idx = 0x36; + analog_gain_cost_delta = -1; + analog_gain_cost_low = 0.4; + analog_gain_cost_high = 6.4; + for (int i = 0; i <= analog_gain_max_idx; i++) { + sensor_analog_gains[i] = sensor_analog_gains_OS04C10[i]; + } + min_ev = (exposure_time_min + VS_TIME_MIN_OS04C10) * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; + target_grey_factor = 0.01; +} + +std::vector OS04C10::getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { + // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD + uint32_t hcg_time = exposure_time; + //uint32_t lcg_time = hcg_time; + //uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OS04C10) / 3), exposure_time_max + VS_TIME_MAX_OS04C10); + //uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OS04C10), VS_TIME_MAX_OS04C10); + + uint32_t real_gain = os04c10_analog_gains_reg[new_exp_g]; + + hcg_time = 100; + real_gain = 0x320; + + return { + {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, + //{0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, + //{0x3541, spd_time>>8}, {0x3542, spd_time&0xFF}, + //{0x35c2, vs_time&0xFF}, + + {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, + }; +} + +int OS04C10::getSlaveAddress(int port) const { + assert(port >= 0 && port <= 2); + return (int[]){0x6C, 0x20, 0x6C}[port]; +} + +float OS04C10::getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const { + float score = std::abs(desired_ev - (exp_t * exp_gain)); + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + score += ((1 - analog_gain_cost_delta) + + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * + std::abs(exp_g_idx - gain_idx) * 5.0; + return score; +} diff --git a/system/camerad/sensors/os04c10_registers.h b/system/camerad/sensors/os04c10_registers.h new file mode 100644 index 0000000000..c92389d1e1 --- /dev/null +++ b/system/camerad/sensors/os04c10_registers.h @@ -0,0 +1,298 @@ +#pragma once + +const struct i2c_random_wr_payload start_reg_array_os04c10[] = {{0x100, 1}}; +const struct i2c_random_wr_payload stop_reg_array_os04c10[] = {{0x100, 0}}; + +const struct i2c_random_wr_payload init_array_os04c10[] = { + // OS04C10_AA_00_02_17_wAO_1920x1080_MIPI728Mbps_Linear12bit_20FPS_4Lane_MCLK24MHz + {0x0103, 0x01}, + {0x0301, 0x84}, + {0x0303, 0x01}, + {0x0305, 0x5b}, + {0x0306, 0x01}, + {0x0307, 0x17}, + {0x0323, 0x04}, + {0x0324, 0x01}, + {0x0325, 0x62}, + {0x3012, 0x06}, + {0x3013, 0x02}, + {0x3016, 0x72}, + {0x3021, 0x03}, + {0x3106, 0x21}, + {0x3107, 0xa1}, + {0x3500, 0x00}, + {0x3501, 0x00}, + {0x3502, 0x40}, + {0x3503, 0x88}, + {0x3508, 0x07}, + {0x3509, 0xc0}, + {0x350a, 0x04}, + {0x350b, 0x00}, + {0x350c, 0x07}, + {0x350d, 0xc0}, + {0x350e, 0x04}, + {0x350f, 0x00}, + {0x3510, 0x00}, + {0x3511, 0x00}, + {0x3512, 0x20}, + {0x3624, 0x00}, + {0x3625, 0x4c}, + {0x3660, 0x00}, + {0x3666, 0xa5}, + {0x3667, 0xa5}, + {0x366a, 0x64}, + {0x3673, 0x0d}, + {0x3672, 0x0d}, + {0x3671, 0x0d}, + {0x3670, 0x0d}, + {0x3685, 0x00}, + {0x3694, 0x0d}, + {0x3693, 0x0d}, + {0x3692, 0x0d}, + {0x3691, 0x0d}, + {0x3696, 0x4c}, + {0x3697, 0x4c}, + {0x3698, 0x40}, + {0x3699, 0x80}, + {0x369a, 0x18}, + {0x369b, 0x1f}, + {0x369c, 0x14}, + {0x369d, 0x80}, + {0x369e, 0x40}, + {0x369f, 0x21}, + {0x36a0, 0x12}, + {0x36a1, 0x5d}, + {0x36a2, 0x66}, + {0x370a, 0x00}, + {0x370e, 0x0c}, + {0x3710, 0x00}, + {0x3713, 0x00}, + {0x3725, 0x02}, + {0x372a, 0x03}, + {0x3738, 0xce}, + {0x3748, 0x00}, + {0x374a, 0x00}, + {0x374c, 0x00}, + {0x374e, 0x00}, + {0x3756, 0x00}, + {0x3757, 0x0e}, + {0x3767, 0x00}, + {0x3771, 0x00}, + {0x377b, 0x20}, + {0x377c, 0x00}, + {0x377d, 0x0c}, + {0x3781, 0x03}, + {0x3782, 0x00}, + {0x3789, 0x14}, + {0x3795, 0x02}, + {0x379c, 0x00}, + {0x379d, 0x00}, + {0x37b8, 0x04}, + {0x37ba, 0x03}, + {0x37bb, 0x00}, + {0x37bc, 0x04}, + {0x37be, 0x08}, + {0x37c4, 0x11}, + {0x37c5, 0x80}, + {0x37c6, 0x14}, + {0x37c7, 0x08}, + {0x37da, 0x11}, + {0x381f, 0x08}, + {0x3829, 0x03}, + {0x3881, 0x00}, + {0x3888, 0x04}, + {0x388b, 0x00}, + {0x3c80, 0x10}, + {0x3c86, 0x00}, + {0x3c8c, 0x20}, + {0x3c9f, 0x01}, + {0x3d85, 0x1b}, + {0x3d8c, 0x71}, + {0x3d8d, 0xe2}, + {0x3f00, 0x0b}, + {0x3f06, 0x04}, + {0x400a, 0x01}, + {0x400b, 0x50}, + {0x400e, 0x08}, + {0x4043, 0x7e}, + {0x4045, 0x7e}, + {0x4047, 0x7e}, + {0x4049, 0x7e}, + {0x4090, 0x14}, + {0x40b0, 0x00}, + {0x40b1, 0x00}, + {0x40b2, 0x00}, + {0x40b3, 0x00}, + {0x40b4, 0x00}, + {0x40b5, 0x00}, + {0x40b7, 0x00}, + {0x40b8, 0x00}, + {0x40b9, 0x00}, + {0x40ba, 0x00}, + {0x4301, 0x00}, + {0x4303, 0x00}, + {0x4502, 0x04}, + {0x4503, 0x00}, + {0x4504, 0x06}, + {0x4506, 0x00}, + {0x4507, 0x64}, + {0x4803, 0x00}, + {0x480c, 0x32}, + {0x480e, 0x00}, + {0x4813, 0x00}, + {0x4819, 0x70}, + {0x481f, 0x30}, + {0x4823, 0x3f}, + {0x4825, 0x30}, + {0x4833, 0x10}, + {0x484b, 0x07}, + {0x488b, 0x00}, + {0x4d00, 0x04}, + {0x4d01, 0xad}, + {0x4d02, 0xbc}, + {0x4d03, 0xa1}, + {0x4d04, 0x1f}, + {0x4d05, 0x4c}, + {0x4d0b, 0x01}, + {0x4e00, 0x2a}, + {0x4e0d, 0x00}, + {0x5001, 0x09}, + {0x5004, 0x00}, + {0x5080, 0x04}, + {0x5036, 0x00}, + {0x5180, 0x70}, + {0x5181, 0x10}, + {0x520a, 0x03}, + {0x520b, 0x06}, + {0x520c, 0x0c}, + {0x580b, 0x0f}, + {0x580d, 0x00}, + {0x580f, 0x00}, + {0x5820, 0x00}, + {0x5821, 0x00}, + {0x301c, 0xf8}, + {0x301e, 0xb4}, + {0x301f, 0xd0}, + {0x3022, 0x01}, + {0x3109, 0xe7}, + {0x3600, 0x00}, + {0x3610, 0x65}, + {0x3611, 0x85}, + {0x3613, 0x3a}, + {0x3615, 0x60}, + {0x3621, 0x90}, + {0x3620, 0x0c}, + {0x3629, 0x00}, + {0x3661, 0x04}, + {0x3664, 0x70}, + {0x3665, 0x00}, + {0x3681, 0xa6}, + {0x3682, 0x53}, + {0x3683, 0x2a}, + {0x3684, 0x15}, + {0x3700, 0x2a}, + {0x3701, 0x12}, + {0x3703, 0x28}, + {0x3704, 0x0e}, + {0x3706, 0x4a}, + {0x3709, 0x4a}, + {0x370b, 0xa2}, + {0x370c, 0x01}, + {0x370f, 0x04}, + {0x3714, 0x24}, + {0x3716, 0x24}, + {0x3719, 0x11}, + {0x371a, 0x1e}, + {0x3720, 0x00}, + {0x3724, 0x13}, + {0x373f, 0xb0}, + {0x3741, 0x4a}, + {0x3743, 0x4a}, + {0x3745, 0x4a}, + {0x3747, 0x4a}, + {0x3749, 0xa2}, + {0x374b, 0xa2}, + {0x374d, 0xa2}, + {0x374f, 0xa2}, + {0x3755, 0x10}, + {0x376c, 0x00}, + {0x378d, 0x30}, + {0x3790, 0x4a}, + {0x3791, 0xa2}, + {0x3798, 0x40}, + {0x379e, 0x00}, + {0x379f, 0x04}, + {0x37a1, 0x10}, + {0x37a2, 0x1e}, + {0x37a8, 0x10}, + {0x37a9, 0x1e}, + {0x37ac, 0xa0}, + {0x37b9, 0x01}, + {0x37bd, 0x01}, + {0x37bf, 0x26}, + {0x37c0, 0x11}, + {0x37c2, 0x04}, + {0x37cd, 0x19}, + {0x37e0, 0x08}, + {0x37e6, 0x04}, + {0x37e5, 0x02}, + {0x37e1, 0x0c}, + {0x3737, 0x04}, + {0x37d8, 0x02}, + {0x37e2, 0x10}, + {0x3739, 0x10}, + {0x3662, 0x10}, + {0x37e4, 0x20}, + {0x37e3, 0x08}, + {0x37d9, 0x08}, + {0x4040, 0x00}, + {0x4041, 0x07}, + {0x4008, 0x02}, + {0x4009, 0x0d}, + {0x3800, 0x01}, + {0x3801, 0x80}, + {0x3802, 0x00}, + {0x3803, 0xdc}, + {0x3804, 0x09}, + {0x3805, 0x0f}, + {0x3806, 0x05}, + {0x3807, 0x23}, + {0x3808, 0x07}, + {0x3809, 0x80}, + {0x380a, 0x04}, + {0x380b, 0x38}, + {0x380c, 0x04}, + {0x380d, 0x2e}, + {0x380e, 0x12}, + {0x380f, 0x70}, + {0x3811, 0x08}, + {0x3813, 0x08}, + {0x3814, 0x01}, + {0x3815, 0x01}, + {0x3816, 0x01}, + {0x3817, 0x01}, + {0x3820, 0x88}, + {0x3821, 0x00}, + {0x3880, 0x25}, + {0x3882, 0x20}, + {0x3c91, 0x0b}, + {0x3c94, 0x45}, + {0x3cad, 0x00}, + {0x3cae, 0x00}, + {0x4000, 0xf3}, + {0x4001, 0x60}, + {0x4003, 0x40}, + {0x4300, 0xff}, + {0x4302, 0x0f}, + {0x4305, 0x83}, + {0x4505, 0x84}, + {0x4809, 0x1e}, + {0x480a, 0x04}, + {0x4837, 0x15}, + {0x4c00, 0x08}, + {0x4c01, 0x08}, + {0x4c04, 0x00}, + {0x4c05, 0x00}, + {0x5000, 0xf9}, + {0x3c8c, 0x10}, +}; diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc index 2bad50cff9..1f0609820b 100644 --- a/system/camerad/sensors/ox03c10.cc +++ b/system/camerad/sensors/ox03c10.cc @@ -34,8 +34,9 @@ OX03C10::OX03C10() { init_reg_array.assign(std::begin(init_array_ox03c10), std::end(init_array_ox03c10)); probe_reg_addr = 0x300a; probe_expected_data = 0x5803; - in_port_info_dt = 0x2c; // one is 0x2a, two are 0x2b - power_config_val_low = 24000000; //Hz + mipi_format = CAM_FORMAT_MIPI_RAW_12; + frame_data_type = 0x2c; // one is 0x2a, two are 0x2b + mclk_frequency = 24000000; //Hz dc_gain_factor = 7.32; dc_gain_min_weight = 1; // always on is fine diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h index 06d3a43a21..4e2194d914 100644 --- a/system/camerad/sensors/sensor.h +++ b/system/camerad/sensors/sensor.h @@ -9,12 +9,14 @@ #include "system/camerad/cameras/camera_common.h" #include "system/camerad/sensors/ar0231_registers.h" #include "system/camerad/sensors/ox03c10_registers.h" +#include "system/camerad/sensors/os04c10_registers.h" #define ANALOG_GAIN_MAX_CNT 55 const size_t FRAME_WIDTH = 1928; const size_t FRAME_HEIGHT = 1208; const size_t FRAME_STRIDE = 2896; // for 12 bit output. 1928 * 12 / 8 + 4 (alignment) + class SensorInfo { public: SensorInfo() = default; @@ -56,8 +58,10 @@ public: uint32_t probe_expected_data; std::vector start_reg_array; std::vector init_reg_array; - uint32_t in_port_info_dt; - uint32_t power_config_val_low; + + uint32_t mipi_format; + uint32_t mclk_frequency; + uint32_t frame_data_type; }; class AR0231 : public SensorInfo { @@ -79,3 +83,11 @@ public: float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override; int getSlaveAddress(int port) const override; }; + +class OS04C10 : public SensorInfo { +public: + OS04C10(); + std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override; + float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override; + int getSlaveAddress(int port) const override; +}; From 4eba5fe68dc89bf9a39cc2c1f5db6c154ced9d12 Mon Sep 17 00:00:00 2001 From: Nelson Chen Date: Tue, 9 Jan 2024 16:46:09 -0800 Subject: [PATCH 410/724] Add permissions to build_masterci in release workflow so it can push master-ci (#30949) --- .github/workflows/release.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f1dc4c2e4a..38dbf07a47 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -16,6 +16,7 @@ jobs: if: github.repository == 'commaai/openpilot' permissions: checks: read + contents: write steps: - name: Install wait-on-check-action dependencies run: | From 61ebb5b668be32f0fc5547bf3b70ee55f7cbbecd Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:24:46 -0500 Subject: [PATCH 411/724] VW MQB: EA driver inactivity workaround (#24711) * VW MQB: Emergency Assist mitigation * elide superfluous newline * update refs --------- Co-authored-by: Adeeb Shihadeh --- panda | 2 +- selfdrive/car/volkswagen/carcontroller.py | 11 ++++++++++- selfdrive/car/volkswagen/carstate.py | 13 ++++++++++++- selfdrive/car/volkswagen/interface.py | 5 ++++- selfdrive/car/volkswagen/mqbcan.py | 18 ++++++++++++++++++ selfdrive/car/volkswagen/values.py | 6 +++++- selfdrive/test/process_replay/ref_commit | 2 +- 7 files changed, 51 insertions(+), 6 deletions(-) diff --git a/panda b/panda index d0184f7266..2a0536c631 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d0184f7266571ba563e5f992b6648ff4089b2dba +Subproject commit 2a0536c63148a02add52555386b5533f3555ef58 diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index 2bc03a0e69..0cee2c7fce 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -5,7 +5,7 @@ from openpilot.common.conversions import Conversions as CV from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car import apply_driver_steer_torque_limits from openpilot.selfdrive.car.volkswagen import mqbcan, pqcan -from openpilot.selfdrive.car.volkswagen.values import CANBUS, PQ_CARS, CarControllerParams +from openpilot.selfdrive.car.volkswagen.values import CANBUS, PQ_CARS, CarControllerParams, VolkswagenFlags VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState @@ -65,6 +65,15 @@ class CarController: self.apply_steer_last = apply_steer can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_steer, hca_enabled)) + if self.CP.flags & VolkswagenFlags.STOCK_HCA_PRESENT: + # Pacify VW Emergency Assist driver inactivity detection by changing its view of driver steering input torque + # to the greatest of actual driver input or 2x openpilot's output (1x openpilot output is not enough to + # consistently reset inactivity detection on straight level roads). See commaai/openpilot#23274 for background. + ea_simulated_torque = clip(apply_steer * 2, -self.CCP.STEER_MAX, self.CCP.STEER_MAX) + if abs(CS.out.steeringTorque) > abs(ea_simulated_torque): + ea_simulated_torque = CS.out.steeringTorque + can_sends.append(self.CCS.create_eps_update(self.packer_pt, CANBUS.cam, CS.eps_stock_values, ea_simulated_torque)) + # **** Acceleration Controls ******************************************** # if self.frame % self.CCP.ACC_CONTROL_STEP == 0 and self.CP.openpilotLongitudinalControl: diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index cbe8918d48..25c5bc04bc 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -4,7 +4,7 @@ from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser from openpilot.selfdrive.car.volkswagen.values import DBC, CANBUS, PQ_CARS, NetworkLocation, TransmissionType, GearShifter, \ - CarControllerParams + CarControllerParams, VolkswagenFlags class CarState(CarStateBase): @@ -14,6 +14,7 @@ class CarState(CarStateBase): self.button_states = {button.event_type: False for button in self.CCP.BUTTONS} self.esp_hold_confirmation = False self.upscale_lead_car_signal = False + self.eps_stock_values = False def create_button_events(self, pt_cp, buttons): button_events = [] @@ -59,6 +60,11 @@ class CarState(CarStateBase): ret.steerFaultPermanent = hca_status in ("DISABLED", "FAULT") ret.steerFaultTemporary = hca_status in ("INITIALIZING", "REJECTED") + # VW Emergency Assist status tracking and mitigation + self.eps_stock_values = pt_cp.vl["LH_EPS_03"] + if self.CP.flags & VolkswagenFlags.STOCK_HCA_PRESENT: + ret.carFaultedNonCritical = bool(cam_cp.vl["HCA_01"]["EA_Ruckfreigabe"]) or cam_cp.vl["HCA_01"]["EA_ACC_Sollstatus"] > 0 + # Update gas, brakes, and gearshift. ret.gas = pt_cp.vl["Motor_20"]["MO_Fahrpedalrohwert_01"] / 100.0 ret.gasPressed = ret.gas > 0 @@ -293,6 +299,11 @@ class CarState(CarStateBase): messages = [] + if CP.flags & VolkswagenFlags.STOCK_HCA_PRESENT: + messages += [ + ("HCA_01", 1), # From R242 Driver assistance camera, 50Hz if steering/1Hz if not + ] + if CP.networkLocation == NetworkLocation.fwdCamera: messages += [ # sig_address, frequency diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 0a6573217c..710e779d0a 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -3,7 +3,7 @@ from panda import Panda from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car import get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase -from openpilot.selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter +from openpilot.selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -67,6 +67,9 @@ class CarInterface(CarInterfaceBase): else: ret.networkLocation = NetworkLocation.fwdCamera + if 0x126 in fingerprint[2]: # HCA_01 + ret.flags |= VolkswagenFlags.STOCK_HCA_PRESENT.value + # Global lateral tuning defaults, can be overridden per-vehicle ret.steerActuatorDelay = 0.1 diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py index 96e05aa8f0..787c7de530 100644 --- a/selfdrive/car/volkswagen/mqbcan.py +++ b/selfdrive/car/volkswagen/mqbcan.py @@ -10,6 +10,24 @@ def create_steering_control(packer, bus, apply_steer, lkas_enabled): return packer.make_can_msg("HCA_01", bus, values) +def create_eps_update(packer, bus, eps_stock_values, ea_simulated_torque): + values = {s: eps_stock_values[s] for s in [ + "COUNTER", # Sync counter value to EPS output + "EPS_Lenkungstyp", # EPS rack type + "EPS_Berechneter_LW", # Absolute raw steering angle + "EPS_VZ_BLW", # Raw steering angle sign + "EPS_HCA_Status", # EPS HCA control status + ]} + + values.update({ + # Absolute driver torque input and sign, with EA inactivity mitigation + "EPS_Lenkmoment": abs(ea_simulated_torque), + "EPS_VZ_Lenkmoment": 1 if ea_simulated_torque < 0 else 0, + }) + + return packer.make_can_msg("LH_EPS_03", bus, values) + + def create_lka_hud_control(packer, bus, ldw_stock_values, enabled, steering_pressed, hud_alert, hud_control): values = {} if len(ldw_stock_values): diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 35e88895db..f4809e4523 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,6 +1,6 @@ from collections import defaultdict, namedtuple from dataclasses import dataclass, field -from enum import Enum, StrEnum +from enum import Enum, IntFlag, StrEnum from typing import Dict, List, Union from cereal import car @@ -109,6 +109,10 @@ class CANBUS: cam = 2 +class VolkswagenFlags(IntFlag): + STOCK_HCA_PRESENT = 1 + + # Check the 7th and 8th characters of the VIN before adding a new CAR. If the # chassis code is already listed below, don't add a new CAR, just add to the # FW_VERSIONS for that existing CAR. diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 474d5056fd..9163bcae75 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -80c700f9b87e069dcb5b7ec76cd6036667615f2e \ No newline at end of file +1b981ce7f817974d4a7a28b06f01f727a5a7ea7b \ No newline at end of file From db3ef3e1d87859c20251be641304dd6be6febce2 Mon Sep 17 00:00:00 2001 From: ugtthis <142481257+ugtthis@users.noreply.github.com> Date: Tue, 9 Jan 2024 20:32:23 -0800 Subject: [PATCH 412/724] UI/MapETA: Increased contrast of widget texts for better readability (#30952) --- selfdrive/ui/qt/maps/map_eta.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/qt/maps/map_eta.cc b/selfdrive/ui/qt/maps/map_eta.cc index 322861ed15..161844c618 100644 --- a/selfdrive/ui/qt/maps/map_eta.cc +++ b/selfdrive/ui/qt/maps/map_eta.cc @@ -20,7 +20,7 @@ void MapETA::paintEvent(QPaintEvent *event) { QPainter p(this); p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::NoPen); - p.setBrush(QColor(0, 0, 0, 150)); + p.setBrush(QColor(0, 0, 0, 255)); QSizeF txt_size = eta_doc.size(); p.drawRoundedRect((width() - txt_size.width()) / 2 - UI_BORDER_SIZE, 0, txt_size.width() + UI_BORDER_SIZE * 2, height() + 25, 25, 25); p.translate((width() - txt_size.width()) / 2, (height() - txt_size.height()) / 2); From 0d126e1e9e60a3d995bc90454bee206b5d70024f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 10 Jan 2024 14:55:19 -0500 Subject: [PATCH 413/724] SegmentRangeReader: new format for reading multiple segments (#30940) * segment range reader * rename that * revert that * cleanup * revert this for now * revert this for now * Fix + test * rm that * rm that * use for auto_fingerprint * simpler * for notebook too * match numpy indexing * just use numpy directly * remove that * spacing * spacing * use qlog for auto fingerprint * add 'read mode' * pass in read mode * add test for modes * numpy indexing * fix that case * more examples * fix the notebook * cleanup the notebook * cleaner * fix those --- tools/car_porting/auto_fingerprint.py | 6 +- .../examples/subaru_steer_temp_fault.ipynb | 14 ++-- tools/lib/helpers.py | 5 +- tools/lib/route.py | 22 +++++ tools/lib/srreader.py | 81 +++++++++++++++++++ tools/lib/tests/test_srreader.py | 63 +++++++++++++++ 6 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 tools/lib/srreader.py create mode 100644 tools/lib/tests/test_srreader.py diff --git a/tools/car_porting/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py index 1870dcba80..ef3efc1cf3 100755 --- a/tools/car_porting/auto_fingerprint.py +++ b/tools/car_porting/auto_fingerprint.py @@ -5,10 +5,9 @@ from collections import defaultdict from typing import Optional from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versions -from openpilot.tools.lib.logreader import MultiLogIterator -from openpilot.tools.lib.route import Route from openpilot.selfdrive.car.fw_versions import match_fw_to_car from openpilot.selfdrive.car.interfaces import get_interface_attr +from openpilot.tools.lib.srreader import SegmentRangeReader, ReadMode ALL_FW_VERSIONS = get_interface_attr("FW_VERSIONS") @@ -25,8 +24,7 @@ if __name__ == "__main__": parser.add_argument("platform", help="The platform, or leave empty to auto-determine using fuzzy", default=None, nargs='?') args = parser.parse_args() - route = Route(args.route) - lr = MultiLogIterator(route.qlog_paths()) + lr = SegmentRangeReader(args.route, ReadMode.QLOG) carFw = None carVin = None diff --git a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb index a20d7d5062..b60915e6ec 100644 --- a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb +++ b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb @@ -9,9 +9,7 @@ "# An example of searching through a database of segments for a specific condition, and plotting the results.\n", "\n", "segments = [\n", - " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20--6\",\n", - " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20--7\",\n", - " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20--8\",\n", + " \"c3d1ccb52f5f9d65|2023-07-22--01-23-20/6:10\",\n", "]\n", "platform = \"SUBARU OUTBACK 6TH GEN\"" ] @@ -25,13 +23,12 @@ "import copy\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from openpilot.tools.lib.logreader import logreader_from_route_or_segment\n", - "\n", - "\n", - "from selfdrive.car.subaru.values import CanBus, DBC\n", "\n", "from opendbc.can.parser import CANParser\n", "\n", + "from openpilot.selfdrive.car.subaru.values import CanBus, DBC\n", + "from openpilot.tools.lib.srreader import SegmentRangeReader\n", + "\n", "\"\"\"\n", "In this example, we search for positive transitions of Steer_Warning, which indicate that the EPS\n", "has stopped responding to our messages. This analysis would allow you to find the cause of these\n", @@ -39,8 +36,7 @@ "\"\"\"\n", "\n", "for segment in segments:\n", - " print(segment)\n", - " lr = logreader_from_route_or_segment(segment)\n", + " lr = SegmentRangeReader(segment)\n", "\n", " can_msgs = [msg for msg in lr if msg.which() == \"can\"]\n", "\n", diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index 067b64b6ac..539d1ce34f 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -7,8 +7,11 @@ TIME_FMT = "%Y-%m-%d--%H-%M-%S" class RE: DONGLE_ID = r'(?P[a-z0-9]{16})' TIMESTAMP = r'(?P[0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2})' - ROUTE_NAME = r'{}[|_/]{}'.format(DONGLE_ID, TIMESTAMP) + ROUTE_NAME = r'(?P{}[|_/]{})'.format(DONGLE_ID, TIMESTAMP) SEGMENT_NAME = r'{}(?:--|/)(?P[0-9]+)'.format(ROUTE_NAME) + INDEX = r'-?[0-9]+' + SLICE = r'(?P{})?:?(?P{})?:?(?P{})?'.format(INDEX, INDEX, INDEX) + SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?'.format(ROUTE_NAME, SLICE) BOOTLOG_NAME = ROUTE_NAME EXPLORER_FILE = r'^(?P{})--(?P[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME) diff --git a/tools/lib/route.py b/tools/lib/route.py index e37b7d4434..17048773b2 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -229,3 +229,25 @@ class SegmentName: def data_dir(self) -> Optional[str]: return self._data_dir def __str__(self) -> str: return self._canonical_name + + +class SegmentRange: + def __init__(self, segment_range: str): + self.m = re.fullmatch(RE.SEGMENT_RANGE, segment_range) + assert self.m, f"Segment range is not valid {segment_range}" + + @property + def route_name(self): + return self.m.group("route_name") + + @property + def dongle_id(self): + return self.m.group("dongle_id") + + @property + def timestamp(self): + return self.m.group("timestamp") + + @property + def _slice(self): + return self.m.group("slice") diff --git a/tools/lib/srreader.py b/tools/lib/srreader.py new file mode 100644 index 0000000000..5ad13352d9 --- /dev/null +++ b/tools/lib/srreader.py @@ -0,0 +1,81 @@ +import enum +import re +import numpy as np +from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.helpers import RE +from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.route import Route, SegmentRange + +class ReadMode(enum.Enum): + RLOG = 0 # only read rlogs + QLOG = 1 # only read qlogs + #AUTO = 2 # default to rlogs, fallback to qlogs, not supported yet + + +def create_slice_from_string(s: str): + m = re.fullmatch(RE.SLICE, s) + assert m is not None, f"Invalid slice: {s}" + start, end, step = m.groups() + start = int(start) if start is not None else None + end = int(end) if end is not None else None + step = int(step) if step is not None else None + + if start is not None and ":" not in s and end is None and step is None: + return start + return slice(start, end, step) + + +def parse_slice(sr: SegmentRange): + route = Route(sr.route_name) + segs = np.arange(route.max_seg_number+1) + s = create_slice_from_string(sr._slice) + return segs[s] if isinstance(s, slice) else [segs[s]] + +def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG): + segs = parse_slice(sr) + route = Route(sr.route_name) + + log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() + + for seg in segs: + yield LogReader(log_paths[seg]) + +def internal_source(sr: SegmentRange, mode=ReadMode.RLOG): + segs = parse_slice(sr) + + for seg in segs: + yield LogReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2") + +def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG): + segs = parse_slice(sr) + + for seg in segs: + yield LogReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog')) + +def auto_source(sr: SegmentRange, mode=ReadMode.RLOG): + # Automatically determine viable source + + try: + next(internal_source(sr, mode)) + return internal_source(sr, mode) + except Exception: + pass + + try: + next(openpilotci_source(sr, mode)) + return openpilotci_source(sr, mode) + except Exception: + pass + + return comma_api_source(sr, mode) + + +class SegmentRangeReader: + def __init__(self, segment_range: str, mode=ReadMode.RLOG, source=auto_source): + sr = SegmentRange(segment_range) + self.lrs = source(sr, mode) + + def __iter__(self): + for lr in self.lrs: + for m in lr: + yield m diff --git a/tools/lib/tests/test_srreader.py b/tools/lib/tests/test_srreader.py new file mode 100644 index 0000000000..2a017be6ae --- /dev/null +++ b/tools/lib/tests/test_srreader.py @@ -0,0 +1,63 @@ +import numpy as np +import unittest +from parameterized import parameterized + +from openpilot.tools.lib.route import SegmentRange +from openpilot.tools.lib.srreader import ReadMode, SegmentRangeReader, parse_slice + +NUM_SEGS = 17 # number of segments in the test route +ALL_SEGS = list(np.arange(NUM_SEGS)) +TEST_ROUTE = "344c5c15b34f2d8a/2024-01-03--09-37-12" + +class TestSegmentRangeReader(unittest.TestCase): + @parameterized.expand([ + (f"{TEST_ROUTE}", ALL_SEGS), + (f"{TEST_ROUTE.replace('/', '|')}", ALL_SEGS), + (f"{TEST_ROUTE}--0", [0]), + (f"{TEST_ROUTE}--5", [5]), + (f"{TEST_ROUTE}/0", [0]), + (f"{TEST_ROUTE}/5", [5]), + (f"{TEST_ROUTE}/0:10", ALL_SEGS[0:10]), + (f"{TEST_ROUTE}/0:0", []), + (f"{TEST_ROUTE}/4:6", ALL_SEGS[4:6]), + (f"{TEST_ROUTE}/0:-1", ALL_SEGS[0:-1]), + (f"{TEST_ROUTE}/:5", ALL_SEGS[:5]), + (f"{TEST_ROUTE}/2:", ALL_SEGS[2:]), + (f"{TEST_ROUTE}/2:-1", ALL_SEGS[2:-1]), + (f"{TEST_ROUTE}/-1", [ALL_SEGS[-1]]), + (f"{TEST_ROUTE}/-2", [ALL_SEGS[-2]]), + (f"{TEST_ROUTE}/-2:-1", ALL_SEGS[-2:-1]), + (f"{TEST_ROUTE}/-4:-2", ALL_SEGS[-4:-2]), + (f"{TEST_ROUTE}/:10:2", ALL_SEGS[:10:2]), + (f"{TEST_ROUTE}/5::2", ALL_SEGS[5::2]), + ]) + def test_parse_slice(self, segment_range, expected): + sr = SegmentRange(segment_range) + segs = parse_slice(sr) + self.assertListEqual(list(segs), expected) + + @parameterized.expand([ + (f"{TEST_ROUTE}//",), + (f"{TEST_ROUTE}---",), + (f"{TEST_ROUTE}/-4:--2",), + (f"{TEST_ROUTE}/-a",), + (f"{TEST_ROUTE}/0:1:2:3",), + (f"{TEST_ROUTE}/:::3",), + ]) + def test_bad_ranges(self, segment_range): + with self.assertRaises(AssertionError): + sr = SegmentRange(segment_range) + parse_slice(sr) + + @parameterized.expand([ + (ReadMode.QLOG, 11643), + (ReadMode.RLOG, 70577), + ]) + def test_modes(self, mode, expected): + lr = SegmentRangeReader(TEST_ROUTE+"/0", mode) + + self.assertEqual(len(list(lr)), expected) + + +if __name__ == "__main__": + unittest.main() From 926793b17ff92c57036b16f343bba263498b7423 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 10 Jan 2024 16:02:39 -0500 Subject: [PATCH 414/724] migrate can_print_changes to segmentrangereader (#30953) more tools --- selfdrive/debug/can_print_changes.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/selfdrive/debug/can_print_changes.py b/selfdrive/debug/can_print_changes.py index ea1160d60b..20fe502efc 100755 --- a/selfdrive/debug/can_print_changes.py +++ b/selfdrive/debug/can_print_changes.py @@ -3,10 +3,12 @@ import argparse import binascii import time from collections import defaultdict +from typing import Optional import cereal.messaging as messaging from openpilot.selfdrive.debug.can_table import can_table -from openpilot.tools.lib.logreader import logreader_from_route_or_segment +from openpilot.tools.lib.logreader import LogIterable +from openpilot.tools.lib.srreader import SegmentRangeReader RED = '\033[91m' CLEAR = '\033[0m' @@ -95,13 +97,15 @@ if __name__ == "__main__": args = parser.parse_args() - init_lr, new_lr = None, None + init_lr: Optional[LogIterable] = None + new_lr: Optional[LogIterable] = None + if args.init: if args.init == '': init_lr = [] else: - init_lr = logreader_from_route_or_segment(args.init) + init_lr = SegmentRangeReader(args.init) if args.comp: - new_lr = logreader_from_route_or_segment(args.comp) + new_lr = SegmentRangeReader(args.comp) can_printer(args.bus, init_msgs=init_lr, new_msgs=new_lr, table=args.table) From cfb23eb2d160639b870690a9bd6e7ba5350e3bc6 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 10 Jan 2024 16:36:55 -0500 Subject: [PATCH 415/724] segmentrangereader: support sort_by_time (#30954) support sort-by-time --- tools/lib/srreader.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tools/lib/srreader.py b/tools/lib/srreader.py index 5ad13352d9..9824e0466b 100644 --- a/tools/lib/srreader.py +++ b/tools/lib/srreader.py @@ -31,49 +31,49 @@ def parse_slice(sr: SegmentRange): s = create_slice_from_string(sr._slice) return segs[s] if isinstance(s, slice) else [segs[s]] -def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG): +def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): segs = parse_slice(sr) route = Route(sr.route_name) log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() for seg in segs: - yield LogReader(log_paths[seg]) + yield LogReader(log_paths[seg], sort_by_time=sort_by_time) -def internal_source(sr: SegmentRange, mode=ReadMode.RLOG): +def internal_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): segs = parse_slice(sr) for seg in segs: - yield LogReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2") + yield LogReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2", sort_by_time=sort_by_time) -def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG): +def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): segs = parse_slice(sr) for seg in segs: - yield LogReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog')) + yield LogReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog'), sort_by_time=sort_by_time) -def auto_source(sr: SegmentRange, mode=ReadMode.RLOG): +def auto_source(*args, **kwargs): # Automatically determine viable source try: - next(internal_source(sr, mode)) - return internal_source(sr, mode) + next(internal_source(*args, **kwargs)) + return internal_source(*args, **kwargs) except Exception: pass try: - next(openpilotci_source(sr, mode)) - return openpilotci_source(sr, mode) + next(openpilotci_source(*args, **kwargs)) + return openpilotci_source(*args, **kwargs) except Exception: pass - return comma_api_source(sr, mode) + return comma_api_source(*args, **kwargs) class SegmentRangeReader: - def __init__(self, segment_range: str, mode=ReadMode.RLOG, source=auto_source): + def __init__(self, segment_range: str, mode=ReadMode.RLOG, source=auto_source, sort_by_time=False): sr = SegmentRange(segment_range) - self.lrs = source(sr, mode) + self.lrs = source(sr, mode, sort_by_time) def __iter__(self): for lr in self.lrs: From 576cba217d1334b6429762bb47dfdb77adc576d1 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 10 Jan 2024 16:48:35 -0500 Subject: [PATCH 416/724] buildjet: pin location to de (#30955) * pin location * stringify * Fix * fromjson * flip the parens * and those --- .github/workflows/selfdrive_tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 3c95d8055b..0e1e47c012 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -75,7 +75,7 @@ jobs: ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} + runs-on: ${{ fromJson( (matrix.arch == 'aarch64') && '["buildjet-2vcpu-ubuntu-2204-arm", "buildjet-pinned-location_de_arm"]' || 'ubuntu-20.04' ) }} steps: - uses: actions/checkout@v4 with: @@ -94,7 +94,7 @@ jobs: strategy: matrix: arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} + runs-on: ${{ fromJson( (matrix.arch == 'aarch64') && '["buildjet-2vcpu-ubuntu-2204-arm", "buildjet-pinned-location_de_arm"]' || 'ubuntu-20.04' ) }} if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v4 From af1b7e5c5912f328d196ff7da952f756a0fc5d04 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 10 Jan 2024 18:11:20 -0500 Subject: [PATCH 417/724] Revert "buildjet: pin location to de" (#30956) Revert "buildjet: pin location to de (#30955)" This reverts commit 576cba217d1334b6429762bb47dfdb77adc576d1. --- .github/workflows/selfdrive_tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 0e1e47c012..3c95d8055b 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -75,7 +75,7 @@ jobs: ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ fromJson( (matrix.arch == 'aarch64') && '["buildjet-2vcpu-ubuntu-2204-arm", "buildjet-pinned-location_de_arm"]' || 'ubuntu-20.04' ) }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -94,7 +94,7 @@ jobs: strategy: matrix: arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ fromJson( (matrix.arch == 'aarch64') && '["buildjet-2vcpu-ubuntu-2204-arm", "buildjet-pinned-location_de_arm"]' || 'ubuntu-20.04' ) }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v4 From 1da08460cba9f3d67b7b84249ccd8ee9bd282bf7 Mon Sep 17 00:00:00 2001 From: Hoang Bui <47828508+bongbui321@users.noreply.github.com> Date: Wed, 10 Jan 2024 21:13:21 -0500 Subject: [PATCH 418/724] Enable variable pass between test and metadrive processes (#30961) pass started between processes --- tools/sim/tests/test_sim_bridge.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/sim/tests/test_sim_bridge.py b/tools/sim/tests/test_sim_bridge.py index 2a12ef791a..850fd83bac 100644 --- a/tools/sim/tests/test_sim_bridge.py +++ b/tools/sim/tests/test_sim_bridge.py @@ -3,7 +3,7 @@ import subprocess import time import unittest -from multiprocessing import Queue +from multiprocessing import Queue, Value from cereal import messaging from openpilot.common.basedir import BASEDIR @@ -27,6 +27,7 @@ class TestSimBridgeBase(unittest.TestCase): sm = messaging.SubMaster(['controlsState', 'onroadEvents', 'managerState']) q = Queue() bridge = self.create_bridge() + bridge.started = Value('b', False) p_bridge = bridge.run(q, retries=10) self.processes.append(p_bridge) From 3918039e293c1784cecc49cca41bc1907f23f146 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 10 Jan 2024 19:37:10 -0800 Subject: [PATCH 419/724] manager: clean exit on ctrl-c --- selfdrive/manager/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 37947a9a03..8b9bba4a47 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -229,6 +229,8 @@ if __name__ == "__main__": try: main() + except KeyboardInterrupt: + print("got CTRL-C, exiting") except Exception: add_file_handler(cloudlog) cloudlog.exception("Manager failed to start") From e9e8f4df38cb54d0f97f2c5aede0a76921807c47 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 10 Jan 2024 23:19:16 -0800 Subject: [PATCH 420/724] update count_events.py to SegmentRangeReader --- selfdrive/debug/count_events.py | 48 +++++++++++++++------------------ 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py index a2c3576b6f..70f412bff1 100755 --- a/selfdrive/debug/count_events.py +++ b/selfdrive/debug/count_events.py @@ -4,16 +4,12 @@ import math import datetime from collections import Counter from pprint import pprint -from tqdm import tqdm from typing import List, Tuple, cast from cereal.services import SERVICE_LIST -from openpilot.tools.lib.route import Route -from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.srreader import SegmentRangeReader, ReadMode if __name__ == "__main__": - r = Route(sys.argv[1]) - cnt_valid: Counter = Counter() cnt_events: Counter = Counter() @@ -24,31 +20,29 @@ if __name__ == "__main__": start_time = math.inf end_time = -math.inf ignition_off = None - for q in tqdm(r.qlog_paths()): - if q is None: - continue - lr = list(LogReader(q)) - for msg in lr: - end_time = max(end_time, msg.logMonoTime) - start_time = min(start_time, msg.logMonoTime) + for msg in SegmentRangeReader(sys.argv[1], ReadMode.QLOG): + end_time = max(end_time, msg.logMonoTime) + start_time = min(start_time, msg.logMonoTime) - if msg.which() == 'onroadEvents': - for e in msg.onroadEvents: - cnt_events[e.name] += 1 - elif msg.which() == 'controlsState': - if len(alerts) == 0 or alerts[-1][1] != msg.controlsState.alertType: + if msg.which() == 'onroadEvents': + for e in msg.onroadEvents: + cnt_events[e.name] += 1 + elif msg.which() == 'controlsState': + at = msg.controlsState.alertType + if "/override" not in at or "lanechange" in at.lower(): + if len(alerts) == 0 or alerts[-1][1] != at: t = (msg.logMonoTime - start_time) / 1e9 - alerts.append((t, msg.controlsState.alertType)) - elif msg.which() == 'pandaStates': - if ignition_off is None: - ign = any(ps.ignitionLine or ps.ignitionCan for ps in msg.pandaStates) - if not ign: - ignition_off = msg.logMonoTime - elif msg.which() in cams: - cnt_cameras[msg.which()] += 1 + alerts.append((t, at)) + elif msg.which() == 'pandaStates': + if ignition_off is None: + ign = any(ps.ignitionLine or ps.ignitionCan for ps in msg.pandaStates) + if not ign: + ignition_off = msg.logMonoTime + elif msg.which() in cams: + cnt_cameras[msg.which()] += 1 - if not msg.valid: - cnt_valid[msg.which()] += 1 + if not msg.valid: + cnt_valid[msg.which()] += 1 duration = (end_time - start_time) / 1e9 From b92e71a2b6a2067fa67578106a6b5c15f3f5dfb1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 10 Jan 2024 23:23:13 -0800 Subject: [PATCH 421/724] updated: persist more state between runs for UI (#30963) * persist branches and more * fix target * typo --------- Co-authored-by: Comma Device --- common/params.cc | 2 +- selfdrive/updated.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/common/params.cc b/common/params.cc index 620cd42641..7b14ac6955 100644 --- a/common/params.cc +++ b/common/params.cc @@ -199,7 +199,7 @@ std::unordered_map keys = { {"UbloxAvailable", PERSISTENT}, {"UpdateAvailable", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START}, - {"UpdaterAvailableBranches", CLEAR_ON_MANAGER_START}, + {"UpdaterAvailableBranches", PERSISTENT}, {"UpdaterCurrentDescription", CLEAR_ON_MANAGER_START}, {"UpdaterCurrentReleaseNotes", CLEAR_ON_MANAGER_START}, {"UpdaterFetchAvailable", CLEAR_ON_MANAGER_START}, diff --git a/selfdrive/updated.py b/selfdrive/updated.py index db6a06dc9e..a623aaefc8 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -230,7 +230,6 @@ class Updater: b: Union[str, None] = self.params.get("UpdaterTargetBranch", encoding='utf-8') if b is None: b = self.get_branch(BASEDIR) - self.params.put("UpdaterTargetBranch", b) return b @property @@ -245,7 +244,7 @@ class Updater: @property def update_available(self) -> bool: - if os.path.isdir(OVERLAY_MERGED): + if os.path.isdir(OVERLAY_MERGED) and len(self.branches) > 0: hash_mismatch = self.get_commit_hash(OVERLAY_MERGED) != self.branches[self.target_branch] branch_mismatch = self.get_branch(OVERLAY_MERGED) != self.target_branch return hash_mismatch or branch_mismatch @@ -259,9 +258,11 @@ class Updater: def set_params(self, update_success: bool, failed_count: int, exception: Optional[str]) -> None: self.params.put("UpdateFailedCount", str(failed_count)) + self.params.put("UpdaterTargetBranch", self.target_branch) self.params.put_bool("UpdaterFetchAvailable", self.update_available) - self.params.put("UpdaterAvailableBranches", ','.join(self.branches.keys())) + if len(self.branches): + self.params.put("UpdaterAvailableBranches", ','.join(self.branches.keys())) last_update = datetime.datetime.utcnow() if update_success: @@ -428,10 +429,11 @@ def main() -> None: # invalidate old finalized update set_consistent_flag(False) - # wait a bit before first cycle - wait_helper.sleep(60) + # set initial state + params.put("UpdaterState", "idle") # Run the update loop + first_run = True while True: wait_helper.ready_event.clear() @@ -444,7 +446,8 @@ def main() -> None: # ensure we have some params written soon after startup updater.set_params(False, update_failed_count, exception) - if not system_time_valid(): + if not system_time_valid() or first_run: + first_run = False wait_helper.sleep(60) continue From bef6d7f0bb1f985fa959627d674210c46870d914 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 11 Jan 2024 01:39:15 -0700 Subject: [PATCH 422/724] pre-commit: speed up translation step (#30966) * formatting fix * fix missing class attr warning * add translation_files option, remove pluralonly, type hint * updates * update --- selfdrive/ui/tests/test_translations.py | 7 +++++-- selfdrive/ui/update_translations.py | 20 +++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index caba386643..9ba9054ea1 100755 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -22,6 +22,9 @@ FORMAT_ARG = re.compile("%[0-9]+") @parameterized_class(("name", "file"), translation_files.items()) class TestTranslations(unittest.TestCase): + name: str + file: str + @staticmethod def _read_translation_file(path, file): tr_file = os.path.join(path, f"{file}.ts") @@ -30,12 +33,12 @@ class TestTranslations(unittest.TestCase): def test_missing_translation_files(self): self.assertTrue(os.path.exists(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")), - f"{self.name} has no XML translation file, run selfdrive/ui/update_translations.py") + f"{self.name} has no XML translation file, run selfdrive/ui/update_translations.py") def test_translations_updated(self): with tempfile.TemporaryDirectory() as tmpdir: shutil.copytree(TRANSLATIONS_DIR, tmpdir, dirs_exist_ok=True) - update_translations(plural_only=["main_en"], translations_dir=tmpdir) + update_translations(translation_files=[self.file], translations_dir=tmpdir) cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) new_translations = self._read_translation_file(tmpdir, self.file) diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index 004c6425dc..e988a13b7f 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -9,6 +9,7 @@ UI_DIR = os.path.join(BASEDIR, "selfdrive", "ui") TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations") LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") TRANSLATIONS_INCLUDE_FILE = os.path.join(TRANSLATIONS_DIR, "alerts_generated.h") +PLURAL_ONLY = ["main_en"] # base language, only create entries for strings with plural forms def generate_translations_include(): @@ -22,21 +23,20 @@ def generate_translations_include(): with open(TRANSLATIONS_INCLUDE_FILE, "w") as f: f.write(content) -def update_translations(vanish=False, plural_only=None, translations_dir=TRANSLATIONS_DIR): + +def update_translations(vanish: bool = False, translation_files: None | list[str] = None, translations_dir: str = TRANSLATIONS_DIR): generate_translations_include() - if plural_only is None: - plural_only = [] + if translation_files is None: + with open(LANGUAGES_FILE, "r") as f: + translation_files = json.load(f).values() - with open(LANGUAGES_FILE, "r") as f: - translation_files = json.load(f) - - for file in translation_files.values(): + for file in translation_files: tr_file = os.path.join(translations_dir, f"{file}.ts") args = f"lupdate -locations none -recursive {UI_DIR} -ts {tr_file} -I {BASEDIR}" if vanish: args += " -no-obsolete" - if file in plural_only: + if file in PLURAL_ONLY: args += " -pluralonly" ret = os.system(args) assert ret == 0 @@ -46,8 +46,6 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description="Update translation files for UI", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--vanish", action="store_true", help="Remove translations with source text no longer found") - parser.add_argument("--plural-only", type=str, nargs="*", default=["main_en"], - help="Translation codes to only create plural translations for (ie. the base language)") args = parser.parse_args() - update_translations(args.vanish, args.plural_only) + update_translations(args.vanish) From 3c74a61c979cb318768699dc8d4fe39c64779622 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 11 Jan 2024 01:39:26 -0700 Subject: [PATCH 423/724] Honda: remove hud ECU from CR-V Hybrid (#30965) remove almost absent ECU --- selfdrive/car/honda/fingerprints.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 1667b46188..543a283e47 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -744,9 +744,6 @@ FW_VERSIONS = { b'78109-TPG-A110\x00\x00', b'78109-TPG-A210\x00\x00', ], - (Ecu.hud, 0x18da61f1, None): [ - b'78209-TLA-X010\x00\x00', - ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TMB-H040\x00\x00', b'36802-TPA-E040\x00\x00', From 32dfb3f8f28df60657e8d94c53d219e4606f2375 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 13:14:10 -0500 Subject: [PATCH 424/724] More migration to segmentrangereader (#30959) * filter log message * ffr * gtfs * gtfs --- selfdrive/debug/filter_log_message.py | 37 ++++++------------- selfdrive/debug/fingerprint_from_route.py | 6 +-- .../test/get_thumbnails_for_segment.py | 15 ++------ 3 files changed, 17 insertions(+), 41 deletions(-) diff --git a/selfdrive/debug/filter_log_message.py b/selfdrive/debug/filter_log_message.py index 20cef0fcc0..f8e78c8b98 100755 --- a/selfdrive/debug/filter_log_message.py +++ b/selfdrive/debug/filter_log_message.py @@ -1,11 +1,9 @@ #!/usr/bin/env python3 -import os import argparse import json import cereal.messaging as messaging -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.route import Route +from openpilot.tools.lib.srreader import SegmentRangeReader LEVELS = { "DEBUG": 10, @@ -53,31 +51,18 @@ if __name__ == "__main__": parser.add_argument("route", type=str, nargs='*', help="route name + segment number for offline usage") args = parser.parse_args() - logs = None - if len(args.route): - if os.path.exists(args.route[0]): - logs = [args.route[0]] - else: - r = Route(args.route[0]) - logs = [q_log if r_log is None else r_log for (q_log, r_log) in zip(r.qlog_paths(), r.log_paths(), strict=True)] - - if len(args.route) == 2 and logs: - n = int(args.route[1]) - logs = [logs[n]] - min_level = LEVELS[args.level] - if logs: - for log in logs: - if log: - lr = LogReader(log) - for m in lr: - if m.which() == 'logMessage': - print_logmessage(m.logMonoTime, m.logMessage, min_level) - elif m.which() == 'errorLogMessage' and 'qlog' in log: - print_logmessage(m.logMonoTime, m.errorLogMessage, min_level) - elif m.which() == 'androidLog': - print_androidlog(m.logMonoTime, m.androidLog) + if args.route: + for route in args.route: + lr = SegmentRangeReader(route) + for m in lr: + if m.which() == 'logMessage': + print_logmessage(m.logMonoTime, m.logMessage, min_level) + elif m.which() == 'errorLogMessage': + print_logmessage(m.logMonoTime, m.errorLogMessage, min_level) + elif m.which() == 'androidLog': + print_androidlog(m.logMonoTime, m.androidLog) else: sm = messaging.SubMaster(['logMessage', 'androidLog'], addr=args.addr) while True: diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index c2bff7c638..a019e09260 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 import sys -from openpilot.tools.lib.route import Route -from openpilot.tools.lib.logreader import MultiLogIterator +from openpilot.tools.lib.srreader import ReadMode, SegmentRangeReader def get_fingerprint(lr): @@ -40,6 +39,5 @@ if __name__ == "__main__": print("Usage: ./fingerprint_from_route.py ") sys.exit(1) - route = Route(sys.argv[1]) - lr = MultiLogIterator(route.log_paths()[:5]) + lr = SegmentRangeReader(sys.argv[1], mode=ReadMode.QLOG) get_fingerprint(lr) diff --git a/system/camerad/test/get_thumbnails_for_segment.py b/system/camerad/test/get_thumbnails_for_segment.py index 43f543f1bd..95d865e4e4 100755 --- a/system/camerad/test/get_thumbnails_for_segment.py +++ b/system/camerad/test/get_thumbnails_for_segment.py @@ -1,26 +1,19 @@ #!/usr/bin/env python3 - +import argparse import os - from tqdm import tqdm -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.route import Route - -import argparse +from openpilot.tools.lib.srreader import SegmentRangeReader if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("route", help="The route name") - parser.add_argument("segment", type=int, help="The index of the segment") args = parser.parse_args() - out_path = os.path.join("jpegs", f"{args.route.replace('|', '_')}_{args.segment}") + out_path = os.path.join("jpegs", f"{args.route.replace('|', '_').replace('/', '_')}") os.makedirs(out_path, exist_ok=True) - r = Route(args.route) - path = r.log_paths()[args.segment] or r.qlog_paths()[args.segment] - lr = list(LogReader(path)) + lr = SegmentRangeReader(args.route) for msg in tqdm(lr): if msg.which() == 'thumbnail': From be814ed44747ab09f8db429ff0ee1e7f87180fec Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 13:25:24 -0500 Subject: [PATCH 425/724] SegmentRangeReader: log type selector (#30960) * log type selector * test this too * and this * invalid selector * use strenum * don't hardcode size * oneline --- tools/lib/helpers.py | 2 +- tools/lib/route.py | 4 ++++ tools/lib/srreader.py | 13 ++++++++----- tools/lib/tests/test_srreader.py | 20 ++++++++++++-------- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index 539d1ce34f..c184efe6a2 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -11,7 +11,7 @@ class RE: SEGMENT_NAME = r'{}(?:--|/)(?P[0-9]+)'.format(ROUTE_NAME) INDEX = r'-?[0-9]+' SLICE = r'(?P{})?:?(?P{})?:?(?P{})?'.format(INDEX, INDEX, INDEX) - SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?'.format(ROUTE_NAME, SLICE) + SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?/?(?P([qr]))?'.format(ROUTE_NAME, SLICE) BOOTLOG_NAME = ROUTE_NAME EXPLORER_FILE = r'^(?P{})--(?P[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME) diff --git a/tools/lib/route.py b/tools/lib/route.py index 17048773b2..d3df93ccda 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -251,3 +251,7 @@ class SegmentRange: @property def _slice(self): return self.m.group("slice") + + @property + def selector(self): + return self.m.group("selector") diff --git a/tools/lib/srreader.py b/tools/lib/srreader.py index 9824e0466b..2c08072e98 100644 --- a/tools/lib/srreader.py +++ b/tools/lib/srreader.py @@ -6,10 +6,10 @@ from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import Route, SegmentRange -class ReadMode(enum.Enum): - RLOG = 0 # only read rlogs - QLOG = 1 # only read qlogs - #AUTO = 2 # default to rlogs, fallback to qlogs, not supported yet +class ReadMode(enum.StrEnum): + RLOG = "r" # only read rlogs + QLOG = "q" # only read qlogs + #AUTO = "a" # default to rlogs, fallback to qlogs, not supported yet def create_slice_from_string(s: str): @@ -71,8 +71,11 @@ def auto_source(*args, **kwargs): class SegmentRangeReader: - def __init__(self, segment_range: str, mode=ReadMode.RLOG, source=auto_source, sort_by_time=False): + def __init__(self, segment_range: str, default_mode=ReadMode.RLOG, source=auto_source, sort_by_time=False): sr = SegmentRange(segment_range) + + mode = default_mode if sr.selector is None else ReadMode(sr.selector) + self.lrs = source(sr, mode, sort_by_time) def __iter__(self): diff --git a/tools/lib/tests/test_srreader.py b/tools/lib/tests/test_srreader.py index 2a017be6ae..84521ae219 100644 --- a/tools/lib/tests/test_srreader.py +++ b/tools/lib/tests/test_srreader.py @@ -37,10 +37,11 @@ class TestSegmentRangeReader(unittest.TestCase): self.assertListEqual(list(segs), expected) @parameterized.expand([ - (f"{TEST_ROUTE}//",), + (f"{TEST_ROUTE}///",), (f"{TEST_ROUTE}---",), (f"{TEST_ROUTE}/-4:--2",), (f"{TEST_ROUTE}/-a",), + (f"{TEST_ROUTE}/j",), (f"{TEST_ROUTE}/0:1:2:3",), (f"{TEST_ROUTE}/:::3",), ]) @@ -49,14 +50,17 @@ class TestSegmentRangeReader(unittest.TestCase): sr = SegmentRange(segment_range) parse_slice(sr) - @parameterized.expand([ - (ReadMode.QLOG, 11643), - (ReadMode.RLOG, 70577), - ]) - def test_modes(self, mode, expected): - lr = SegmentRangeReader(TEST_ROUTE+"/0", mode) + def test_modes(self): + qlog_len = len(list(SegmentRangeReader(f"{TEST_ROUTE}/0", ReadMode.QLOG))) + rlog_len = len(list(SegmentRangeReader(f"{TEST_ROUTE}/0", ReadMode.RLOG))) - self.assertEqual(len(list(lr)), expected) + self.assertLess(qlog_len * 6, rlog_len) + + def test_modes_from_name(self): + qlog_len = len(list(SegmentRangeReader(f"{TEST_ROUTE}/0/q"))) + rlog_len = len(list(SegmentRangeReader(f"{TEST_ROUTE}/0/r"))) + + self.assertLess(qlog_len * 6, rlog_len) if __name__ == "__main__": From 0320fb385ad02a096257561c61c8c2b585741c97 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 10:54:53 -0800 Subject: [PATCH 426/724] fix static analysis --- selfdrive/debug/fingerprint_from_route.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index a019e09260..7126a282c9 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -39,5 +39,5 @@ if __name__ == "__main__": print("Usage: ./fingerprint_from_route.py ") sys.exit(1) - lr = SegmentRangeReader(sys.argv[1], mode=ReadMode.QLOG) + lr = SegmentRangeReader(sys.argv[1], ReadMode.QLOG) get_fingerprint(lr) From ed153141fc733b9ac3d6c1cedfbcd01039c6ff20 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 11 Jan 2024 10:56:46 -0800 Subject: [PATCH 427/724] rm old debug script --- selfdrive/debug/sensor_data_to_hist.py | 111 ------------------------- 1 file changed, 111 deletions(-) delete mode 100755 selfdrive/debug/sensor_data_to_hist.py diff --git a/selfdrive/debug/sensor_data_to_hist.py b/selfdrive/debug/sensor_data_to_hist.py deleted file mode 100755 index 73f98b285c..0000000000 --- a/selfdrive/debug/sensor_data_to_hist.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -''' -printing the gap between interrupts in a histogram to check if the -frequency is what we expect, the bmx is not interrupt driven for as we -get interrupts in a 2kHz rate. -''' - -import argparse -import sys -import numpy as np -from collections import defaultdict - -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.route import Route - -import matplotlib.pyplot as plt - -SRC_BMX = "bmx055" -SRC_LSM = "lsm6ds3" - - -def parseEvents(log_reader): - bmx_data = defaultdict(list) - lsm_data = defaultdict(list) - - for m in log_reader: - if m.which() not in ['accelerometer', 'gyroscope']: - continue - - d = getattr(m, m.which()).to_dict() - - if d["source"] == SRC_BMX and "acceleration" in d: - bmx_data["accel"].append(d["timestamp"] / 1e9) - - if d["source"] == SRC_BMX and "gyroUncalibrated" in d: - bmx_data["gyro"].append(d["timestamp"] / 1e9) - - if d["source"] == SRC_LSM and "acceleration" in d: - lsm_data["accel"].append(d["timestamp"] / 1e9) - - if d["source"] == SRC_LSM and "gyroUncalibrated" in d: - lsm_data["gyro"].append(d["timestamp"] / 1e9) - - return bmx_data, lsm_data - - -def cleanData(data): - if len(data) == 0: - return [], [] - - data.sort() - diffs = np.diff(data) - return data, diffs - - -def logAvgValues(data, sensor): - if len(data) == 0: - print(f"{sensor}: no data to average") - return - - avg = sum(data) / len(data) - hz = 1 / avg - print(f"{sensor}: data_points: {len(data)} avg [ns]: {avg} avg [Hz]: {hz}") - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("route", type=str, help="route name") - parser.add_argument("segment", type=int, help="segment number") - args = parser.parse_args() - - r = Route(args.route) - logs = r.log_paths() - - if len(logs) == 0: - print("NO data routes") - sys.exit(0) - - if args.segment >= len(logs): - print(f"RouteID: {args.segment} out of range, max: {len(logs) -1}") - sys.exit(0) - - lr = LogReader(logs[args.segment]) - bmx_data, lsm_data = parseEvents(lr) - - # sort bmx accel data, and then cal all the diffs, and to a histogram of those - bmx_accel, bmx_accel_diffs = cleanData(bmx_data["accel"]) - bmx_gyro, bmx_gyro_diffs = cleanData(bmx_data["gyro"]) - lsm_accel, lsm_accel_diffs = cleanData(lsm_data["accel"]) - lsm_gyro, lsm_gyro_diffs = cleanData(lsm_data["gyro"]) - - # get out the averages - logAvgValues(bmx_accel_diffs, "bmx accel") - logAvgValues(bmx_gyro_diffs, "bmx gyro ") - logAvgValues(lsm_accel_diffs, "lsm accel") - logAvgValues(lsm_gyro_diffs, "lsm gyro ") - - fig, axs = plt.subplots(1, 2, tight_layout=True) - axs[0].hist(bmx_accel_diffs, bins=50) - axs[0].set_title("bmx_accel") - axs[1].hist(bmx_gyro_diffs, bins=50) - axs[1].set_title("bmx_gyro") - - figl, axsl = plt.subplots(1, 2, tight_layout=True) - axsl[0].hist(lsm_accel_diffs, bins=50) - axsl[0].set_title("lsm_accel") - axsl[1].hist(lsm_gyro_diffs, bins=50) - axsl[1].set_title("lsm_gyro") - - print("check plot...") - plt.show() From f820b7cd94bf366c8ea2bd48a8d15ab1ccd7d2e0 Mon Sep 17 00:00:00 2001 From: Hoang Bui <47828508+bongbui321@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:16:09 -0500 Subject: [PATCH 428/724] FIx violations and enable --warn=all (#30854) * --warn=all * setoption default warn=all * to old test yaml * cleanup * advanced cscanner * SCons conditional scanner * not time yet --------- Co-authored-by: Adeeb Shihadeh --- SConstruct | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SConstruct b/SConstruct index e1a9b82c7f..11b95702c3 100644 --- a/SConstruct +++ b/SConstruct @@ -9,6 +9,9 @@ import SCons.Errors SCons.Warnings.warningAsException(True) +# pending upstream fix - https://github.com/SCons/scons/issues/4461 +#SetOption('warn', 'all') + TICI = os.path.isfile('/TICI') AGNOS = TICI From 7e9843b5e6d0756bc7c17ef7338c384b7b330df0 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 14:46:31 -0500 Subject: [PATCH 429/724] replace more tools with SegmentRangeReader (#30968) * replace lrfros * and those ones too --- selfdrive/debug/run_process_on_route.py | 9 +++------ selfdrive/debug/toyota_eps_factor.py | 6 ++---- tools/latencylogger/latency_logger.py | 4 ++-- tools/replay/can_replay.py | 4 ++-- tools/tuning/measure_steering_accuracy.py | 4 ++-- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index 8209b409ca..51ba06ec14 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -3,14 +3,12 @@ import argparse from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, replay_process -from openpilot.tools.lib.logreader import MultiLogIterator -from openpilot.tools.lib.route import Route from openpilot.tools.lib.helpers import save_log +from openpilot.tools.lib.srreader import SegmentRangeReader if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run process on route and create new logs", formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("--qlog", help="Use qlog instead of log", action="store_true") parser.add_argument("--fingerprint", help="The fingerprint to use") parser.add_argument("route", help="The route name to use") parser.add_argument("process", help="The process to run") @@ -18,8 +16,7 @@ if __name__ == "__main__": cfg = [c for c in CONFIGS if c.proc_name == args.process][0] - route = Route(args.route) - lr = MultiLogIterator(route.qlog_paths() if args.qlog else route.log_paths()) + lr = SegmentRangeReader(args.route) inputs = list(lr) outputs = replay_process(cfg, inputs, fingerprint=args.fingerprint) @@ -29,5 +26,5 @@ if __name__ == "__main__": inputs = [i for i in inputs if i.which() not in produces] outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime) - fn = f"{args.route}_{args.process}.bz2" + fn = f"{args.route.replace('/', '_')}_{args.process}.bz2" save_log(fn, outputs) diff --git a/selfdrive/debug/toyota_eps_factor.py b/selfdrive/debug/toyota_eps_factor.py index d60d2d372b..1f72fa296a 100755 --- a/selfdrive/debug/toyota_eps_factor.py +++ b/selfdrive/debug/toyota_eps_factor.py @@ -5,8 +5,7 @@ import matplotlib.pyplot as plt from sklearn import linear_model from openpilot.selfdrive.car.toyota.values import STEER_THRESHOLD -from openpilot.tools.lib.route import Route -from openpilot.tools.lib.logreader import MultiLogIterator +from openpilot.tools.lib.srreader import SegmentRangeReader MIN_SAMPLES = 30 * 100 @@ -58,7 +57,6 @@ def get_eps_factor(lr, plot=False): if __name__ == "__main__": - r = Route(sys.argv[1]) - lr = MultiLogIterator(r.log_paths()) + lr = SegmentRangeReader(sys.argv[1]) n = get_eps_factor(lr, plot="--plot" in sys.argv) print("EPS torque factor: ", n) diff --git a/tools/latencylogger/latency_logger.py b/tools/latencylogger/latency_logger.py index f2adc97e01..29d4889d35 100755 --- a/tools/latencylogger/latency_logger.py +++ b/tools/latencylogger/latency_logger.py @@ -8,7 +8,7 @@ import sys from bisect import bisect_left, bisect_right from collections import defaultdict -from openpilot.tools.lib.logreader import logreader_from_route_or_segment +from openpilot.tools.lib.srreader import SegmentRangeReader DEMO_ROUTE = "9f583b1d93915c31|2022-05-18--10-49-51--0" @@ -236,7 +236,7 @@ if __name__ == "__main__": args = parser.parse_args() r = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip() - lr = logreader_from_route_or_segment(r, sort_by_time=True) + lr = SegmentRangeReader(r, sort_by_time=True) data, _ = get_timestamps(lr) print_timestamps(data['timestamp'], data['duration'], data['start'], args.relative) diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index 0827ca799f..590a37734c 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -11,7 +11,7 @@ os.environ['FILEREADER_CACHE'] = '1' from openpilot.common.realtime import config_realtime_process, Ratekeeper, DT_CTRL from openpilot.selfdrive.boardd.boardd import can_capnp_to_can_list from openpilot.tools.plotjuggler.juggle import load_segment -from openpilot.tools.lib.logreader import logreader_from_route_or_segment +from openpilot.tools.lib.srreader import SegmentRangeReader from panda import Panda, PandaJungle def send_thread(s, flock): @@ -102,7 +102,7 @@ if __name__ == "__main__": for lr in tqdm(pool.map(load_segment, logs)): CAN_MSGS += [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] else: - lr = logreader_from_route_or_segment(args.route_or_segment_name) + lr = SegmentRangeReader(args.route_or_segment_name) CAN_MSGS = [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] # set both to cycle ignition diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index a7718043cb..de4de46c2a 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -8,7 +8,7 @@ import signal from collections import defaultdict import cereal.messaging as messaging -from openpilot.tools.lib.logreader import logreader_from_route_or_segment +from openpilot.tools.lib.srreader import SegmentRangeReader def sigint_handler(signal, frame): exit(0) @@ -128,7 +128,7 @@ if __name__ == "__main__": if args.route is not None: print(f"loading {args.route}...") - lr = logreader_from_route_or_segment(args.route, sort_by_time=True) + lr = SegmentRangeReader(args.route, sort_by_time=True) sm = {} for msg in lr: From fb994ae782259e8ed4d9212edc3c9fb277f831f6 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 15:55:19 -0500 Subject: [PATCH 430/724] segmentrangereader: support more sources (#30970) * support more sources * remove this --- tools/lib/route.py | 21 ++++++++++++++++++++- tools/lib/srreader.py | 4 ++-- tools/lib/tests/test_srreader.py | 5 +++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/tools/lib/route.py b/tools/lib/route.py index d3df93ccda..93788e7ab3 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -1,6 +1,6 @@ import os import re -from urllib.parse import urlparse +from urllib.parse import parse_qs, urlparse from collections import defaultdict from itertools import chain from typing import Optional @@ -231,8 +231,27 @@ class SegmentName: def __str__(self) -> str: return self._canonical_name +def parse_useradmin(segment_range): + if "useradmin.comma.ai" in segment_range: + query = parse_qs(urlparse(segment_range).query) + return query["onebox"][0] + return segment_range + +def parse_cabana(segment_range): + if "cabana.comma.ai" in segment_range: + query = parse_qs(urlparse(segment_range).query) + return query["route"][0] + return segment_range + +def parse_cd(segment_range): + return segment_range.replace("cd:/", "") + class SegmentRange: def __init__(self, segment_range: str): + segment_range = parse_useradmin(segment_range) + segment_range = parse_cabana(segment_range) + segment_range = parse_cd(segment_range) + self.m = re.fullmatch(RE.SEGMENT_RANGE, segment_range) assert self.m, f"Segment range is not valid {segment_range}" diff --git a/tools/lib/srreader.py b/tools/lib/srreader.py index 2c08072e98..08abd03d33 100644 --- a/tools/lib/srreader.py +++ b/tools/lib/srreader.py @@ -71,12 +71,12 @@ def auto_source(*args, **kwargs): class SegmentRangeReader: - def __init__(self, segment_range: str, default_mode=ReadMode.RLOG, source=auto_source, sort_by_time=False): + def __init__(self, segment_range: str, default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False): sr = SegmentRange(segment_range) mode = default_mode if sr.selector is None else ReadMode(sr.selector) - self.lrs = source(sr, mode, sort_by_time) + self.lrs = default_source(sr, mode, sort_by_time) def __iter__(self): for lr in self.lrs: diff --git a/tools/lib/tests/test_srreader.py b/tools/lib/tests/test_srreader.py index 84521ae219..e1c54557be 100644 --- a/tools/lib/tests/test_srreader.py +++ b/tools/lib/tests/test_srreader.py @@ -30,6 +30,11 @@ class TestSegmentRangeReader(unittest.TestCase): (f"{TEST_ROUTE}/-4:-2", ALL_SEGS[-4:-2]), (f"{TEST_ROUTE}/:10:2", ALL_SEGS[:10:2]), (f"{TEST_ROUTE}/5::2", ALL_SEGS[5::2]), + (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE}", ALL_SEGS), + (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE.replace('/', '|')}", ALL_SEGS), + (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE.replace('/', '%7C')}", ALL_SEGS), + (f"https://cabana.comma.ai/?route={TEST_ROUTE}", ALL_SEGS), + (f"cd:/{TEST_ROUTE}", ALL_SEGS), ]) def test_parse_slice(self, segment_range, expected): sr = SegmentRange(segment_range) From 042ccb9244ac167a07d289864c99e0d169bb9e1a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 16:11:59 -0500 Subject: [PATCH 431/724] move can replay to segmentrangereader (#30971) can replay --- tools/replay/can_replay.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index 590a37734c..4be516a108 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -4,13 +4,11 @@ import os import time import threading import multiprocessing -from tqdm import tqdm os.environ['FILEREADER_CACHE'] = '1' from openpilot.common.realtime import config_realtime_process, Ratekeeper, DT_CTRL from openpilot.selfdrive.boardd.boardd import can_capnp_to_can_list -from openpilot.tools.plotjuggler.juggle import load_segment from openpilot.tools.lib.srreader import SegmentRangeReader from panda import Panda, PandaJungle @@ -92,18 +90,21 @@ if __name__ == "__main__": parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name to replay. If not specified, a default public route will be used.") args = parser.parse_args() + def process(lr): + return [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] + print("Loading log...") if args.route_or_segment_name is None: - ROUTE = "77611a1fac303767/2020-03-24--09-50-38" - REPLAY_SEGS = list(range(10, 16)) # route has 82 segments available + args.route_or_segment_name = "77611a1fac303767/2020-03-24--09-50-38/10:16" + + sr = SegmentRangeReader(args.route_or_segment_name) + + with multiprocessing.Pool(24) as pool: CAN_MSGS = [] - logs = [f"https://commadataci.blob.core.windows.net/openpilotci/{ROUTE}/{i}/rlog.bz2" for i in REPLAY_SEGS] - with multiprocessing.Pool(24) as pool: - for lr in tqdm(pool.map(load_segment, logs)): - CAN_MSGS += [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] - else: - lr = SegmentRangeReader(args.route_or_segment_name) - CAN_MSGS = [can_capnp_to_can_list(m.can) for m in lr if m.which() == 'can'] + for p in pool.map(process, sr.lrs): + CAN_MSGS.extend(p) + + print("Finished loading...") # set both to cycle ignition IGN_ON = int(os.getenv("ON", "0")) From 8d9e431f43d5974d7908aae4645d6fa6fb0e7324 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 16:35:50 -0500 Subject: [PATCH 432/724] car porting examples: add example of plotting the response of brake_pressure vs acceleration (#30958) * another example * add comment * fix that * oop * fix link --- tools/car_porting/README.md | 8 ++ .../examples/subaru_long_accel.ipynb | 120 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 tools/car_porting/examples/subaru_long_accel.ipynb diff --git a/tools/car_porting/README.md b/tools/car_porting/README.md index ca21cdd329..4197e3ab76 100644 --- a/tools/car_porting/README.md +++ b/tools/car_porting/README.md @@ -64,3 +64,11 @@ An example of searching through a database of segments for a specific condition, ![steer warning example](https://github.com/commaai/openpilot/assets/9648890/d60ad120-4b44-4974-ac79-adc660fb8fe2) *a plot of the steer_warning vs steering angle, where we can see it is clearly caused by a large steering angle change* + +### [tools/car_porting/examples/subaru_long_accel.ipynb](/tools/car_porting/examples/subaru_long_accel.ipynb) + +An example of plotting the response of an actuator when it is active. + +![brake pressure example](https://github.com/commaai/openpilot/assets/9648890/8f32cf1d-8fc0-4407-b540-70625ebbf082) + +*a plot of the brake_pressure vs acceleration, where we can see it is a fairly linear response.* \ No newline at end of file diff --git a/tools/car_porting/examples/subaru_long_accel.ipynb b/tools/car_porting/examples/subaru_long_accel.ipynb new file mode 100644 index 0000000000..ea685dcd4f --- /dev/null +++ b/tools/car_porting/examples/subaru_long_accel.ipynb @@ -0,0 +1,120 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "segments = [\n", + " \"d9df6f87e8feff94|2023-03-28--17-41-10/1:12\"\n", + "]\n", + "platform = \"SUBARU OUTBACK 6TH GEN\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "import numpy as np\n", + "\n", + "from opendbc.can.parser import CANParser\n", + "\n", + "from openpilot.selfdrive.car.subaru.values import DBC\n", + "from openpilot.tools.lib.srreader import SegmentRangeReader\n", + "\n", + "\"\"\"\n", + "In this example, we plot the relationship between Cruise_Brake and Acceleration for stock eyesight.\n", + "\"\"\"\n", + "\n", + "for segment in segments:\n", + " lr = SegmentRangeReader(segment)\n", + "\n", + " messages = [\n", + " (\"ES_Distance\", 20),\n", + " (\"ES_Brake\", 20),\n", + " (\"ES_Status\", 20),\n", + " ]\n", + "\n", + " cp = CANParser(DBC[platform][\"pt\"], messages, 1)\n", + "\n", + " es_distance_history = []\n", + " es_status_history = []\n", + " es_brake_history = []\n", + " acceleration_history = []\n", + "\n", + " last_acc = 0\n", + "\n", + " for msg in lr:\n", + " if msg.which() == \"can\":\n", + " cp.update_strings([msg.as_builder().to_bytes()])\n", + " es_distance_history.append(copy.copy(cp.vl[\"ES_Distance\"]))\n", + " es_brake_history.append(copy.copy(cp.vl[\"ES_Brake\"]))\n", + " es_status_history.append(copy.copy(cp.vl[\"ES_Status\"]))\n", + "\n", + " acceleration_history.append(last_acc)\n", + " \n", + " if msg.which() == \"carState\":\n", + " last_acc = msg.carState.aEgo" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def process(history, func):\n", + " return np.array([func(h) for h in history])\n", + "\n", + "cruise_activated = process(es_status_history, lambda es_status: es_status[\"Cruise_Activated\"])\n", + "cruise_throttle = process(es_distance_history, lambda es_distance: es_distance[\"Cruise_Throttle\"])\n", + "cruise_rpm = process(es_status_history, lambda es_status: es_status[\"Cruise_RPM\"])\n", + "cruise_brake = process(es_brake_history, lambda es_brake: es_brake[\"Brake_Pressure\"])\n", + "acceleration = process(acceleration_history, lambda acc: acc)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "valid_brake = (cruise_activated==1) & (cruise_brake>0) # only when cruise is activated and eyesight is braking\n", + "\n", + "ax = plt.figure().add_subplot()\n", + "\n", + "ax.set_title(\"Brake_Pressure vs Acceleration\")\n", + "ax.set_xlabel(\"Brake_Pessure\")\n", + "ax.set_ylabel(\"Acceleration\")\n", + "ax.scatter(cruise_brake[valid_brake], -acceleration[valid_brake])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 68bcb6b8da0db090c37d6d9a633d7ffdd58e24fe Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 11 Jan 2024 15:57:04 -0800 Subject: [PATCH 433/724] Fingerprints: add missing FW versions from users (#30972) Export fingerprints --- selfdrive/car/toyota/fingerprints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 485e3246d1..c72766f40a 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -1412,6 +1412,7 @@ FW_VERSIONS = { CAR.LEXUS_RC: { (Ecu.engine, 0x700, None): [ b'\x01896632461100\x00\x00\x00\x00', + b'\x01896632478100\x00\x00\x00\x00', b'\x01896632478200\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ From eb09294fc265ffc9cc2ed7f03acc5037d064de81 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 19:56:48 -0500 Subject: [PATCH 434/724] segmentrangereader: support direct parsing (#30973) * use correct source * revert * cleanup imports * clean * direct parsing * rename * move up * fixes * fix that * better error message --- tools/lib/route.py | 21 +--------- tools/lib/srreader.py | 67 +++++++++++++++++++++++++++++--- tools/lib/tests/test_srreader.py | 22 +++++++++-- 3 files changed, 82 insertions(+), 28 deletions(-) diff --git a/tools/lib/route.py b/tools/lib/route.py index 93788e7ab3..d3df93ccda 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -1,6 +1,6 @@ import os import re -from urllib.parse import parse_qs, urlparse +from urllib.parse import urlparse from collections import defaultdict from itertools import chain from typing import Optional @@ -231,27 +231,8 @@ class SegmentName: def __str__(self) -> str: return self._canonical_name -def parse_useradmin(segment_range): - if "useradmin.comma.ai" in segment_range: - query = parse_qs(urlparse(segment_range).query) - return query["onebox"][0] - return segment_range - -def parse_cabana(segment_range): - if "cabana.comma.ai" in segment_range: - query = parse_qs(urlparse(segment_range).query) - return query["route"][0] - return segment_range - -def parse_cd(segment_range): - return segment_range.replace("cd:/", "") - class SegmentRange: def __init__(self, segment_range: str): - segment_range = parse_useradmin(segment_range) - segment_range = parse_cabana(segment_range) - segment_range = parse_cd(segment_range) - self.m = re.fullmatch(RE.SEGMENT_RANGE, segment_range) assert self.m, f"Segment range is not valid {segment_range}" diff --git a/tools/lib/srreader.py b/tools/lib/srreader.py index 08abd03d33..db2ebd1936 100644 --- a/tools/lib/srreader.py +++ b/tools/lib/srreader.py @@ -1,6 +1,9 @@ import enum -import re import numpy as np +import pathlib +import re +from urllib.parse import parse_qs, urlparse + from openpilot.selfdrive.test.openpilotci import get_url from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.logreader import LogReader @@ -37,6 +40,10 @@ def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() + invalid_segs = [seg for seg in segs if log_paths[seg] is None] + + assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" + for seg in segs: yield LogReader(log_paths[seg], sort_by_time=sort_by_time) @@ -52,6 +59,9 @@ def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False) for seg in segs: yield LogReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog'), sort_by_time=sort_by_time) +def direct_source(file_or_url, sort_by_time): + yield LogReader(file_or_url, sort_by_time=sort_by_time) + def auto_source(*args, **kwargs): # Automatically determine viable source @@ -69,14 +79,61 @@ def auto_source(*args, **kwargs): return comma_api_source(*args, **kwargs) +def parse_useradmin(identifier): + if "useradmin.comma.ai" in identifier: + query = parse_qs(urlparse(identifier).query) + return query["onebox"][0] + return None + +def parse_cabana(identifier): + if "cabana.comma.ai" in identifier: + query = parse_qs(urlparse(identifier).query) + return query["route"][0] + return None + +def parse_cd(identifier): + if "cd:/" in identifier: + return identifier.replace("cd:/", "") + return None + +def parse_direct(identifier): + if "https://" in identifier or "http://" in identifier or pathlib.Path(identifier).exists(): + return identifier + return None + +def parse_indirect(identifier): + parsed = parse_useradmin(identifier) or parse_cabana(identifier) + + if parsed is not None: + return parsed, comma_api_source, True + + parsed = parse_cd(identifier) + if parsed is not None: + return parsed, internal_source, True + + return identifier, None, False class SegmentRangeReader: - def __init__(self, segment_range: str, default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False): - sr = SegmentRange(segment_range) + def _logreaders_from_identifier(self, identifier): + parsed, source, is_indirect = parse_indirect(identifier) - mode = default_mode if sr.selector is None else ReadMode(sr.selector) + if not is_indirect: + direct_parsed = parse_direct(identifier) + if direct_parsed is not None: + return direct_source(identifier, sort_by_time=self.sort_by_time) - self.lrs = default_source(sr, mode, sort_by_time) + sr = SegmentRange(parsed) + mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) + source = self.default_source if source is None else source + + return source(sr, mode, sort_by_time=self.sort_by_time) + + def __init__(self, identifier: str, default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False): + self.default_mode = default_mode + self.default_source = default_source + self.sort_by_time = sort_by_time + + self.lrs = self._logreaders_from_identifier(identifier) def __iter__(self): for lr in self.lrs: diff --git a/tools/lib/tests/test_srreader.py b/tools/lib/tests/test_srreader.py index e1c54557be..b22599dee8 100644 --- a/tools/lib/tests/test_srreader.py +++ b/tools/lib/tests/test_srreader.py @@ -1,13 +1,17 @@ +import shutil +import tempfile import numpy as np import unittest from parameterized import parameterized +import requests from openpilot.tools.lib.route import SegmentRange -from openpilot.tools.lib.srreader import ReadMode, SegmentRangeReader, parse_slice +from openpilot.tools.lib.srreader import ReadMode, SegmentRangeReader, parse_slice, parse_indirect NUM_SEGS = 17 # number of segments in the test route ALL_SEGS = list(np.arange(NUM_SEGS)) TEST_ROUTE = "344c5c15b34f2d8a/2024-01-03--09-37-12" +QLOG_FILE = "https://commadataci.blob.core.windows.net/openpilotci/0375fdf7b1ce594d/2019-06-13--08-32-25/3/qlog.bz2" class TestSegmentRangeReader(unittest.TestCase): @parameterized.expand([ @@ -36,11 +40,23 @@ class TestSegmentRangeReader(unittest.TestCase): (f"https://cabana.comma.ai/?route={TEST_ROUTE}", ALL_SEGS), (f"cd:/{TEST_ROUTE}", ALL_SEGS), ]) - def test_parse_slice(self, segment_range, expected): - sr = SegmentRange(segment_range) + def test_indirect_parsing(self, identifier, expected): + parsed, _, _ = parse_indirect(identifier) + sr = SegmentRange(parsed) segs = parse_slice(sr) self.assertListEqual(list(segs), expected) + def test_direct_parsing(self): + qlog = tempfile.NamedTemporaryFile(mode='wb', delete=False) + + with requests.get(QLOG_FILE, stream=True) as r: + with qlog as f: + shutil.copyfileobj(r.raw, f) + + for f in [QLOG_FILE, qlog.name]: + l = len(list(SegmentRangeReader(f))) + self.assertGreater(l, 100) + @parameterized.expand([ (f"{TEST_ROUTE}///",), (f"{TEST_ROUTE}---",), From d7e7659852f14ca458605f13c2bdae90f5de5a45 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 20:02:47 -0500 Subject: [PATCH 435/724] move plotjuggler to segment range reader (#30969) * pj * limit test to 2 segs * remove * fix test --- tools/plotjuggler/juggle.py | 71 +++++---------------------- tools/plotjuggler/test_plotjuggler.py | 6 +-- 2 files changed, 15 insertions(+), 62 deletions(-) diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 02858b1edf..3df777c959 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 +import multiprocessing import os import sys -import multiprocessing import platform import shutil import subprocess @@ -9,13 +9,12 @@ import tarfile import tempfile import requests import argparse +from functools import partial from openpilot.common.basedir import BASEDIR -from openpilot.selfdrive.test.openpilotci import get_url -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.route import Route, SegmentName from openpilot.tools.lib.helpers import save_log -from urllib.parse import urlparse, parse_qs + +from openpilot.tools.lib.srreader import SegmentRangeReader juggle_dir = os.path.dirname(os.path.realpath(__file__)) @@ -53,17 +52,6 @@ def get_plotjuggler_version(): return tuple(map(int, version.split("."))) -def load_segment(segment_name): - if segment_name is None: - return [] - - try: - return list(LogReader(segment_name)) - except (AssertionError, ValueError) as e: - print(f"Error parsing {segment_name}: {e}") - return [] - - def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None): env = os.environ.copy() env["BASEDIR"] = BASEDIR @@ -82,48 +70,16 @@ def start_juggler(fn=None, dbc=None, layout=None, route_or_segment_name=None): cmd = f'{PLOTJUGGLER_BIN} --buffer_size {MAX_STREAMING_BUFFER_SIZE} --plugin_folders {INSTALL_DIR}{extra_args}' subprocess.call(cmd, shell=True, env=env, cwd=juggle_dir) +def process(can, lr): + return [d for d in lr if can or d.which() not in ['can', 'sendcan']] -def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=None, ci=False): - segment_start = 0 - if 'cabana' in route_or_segment_name: - query = parse_qs(urlparse(route_or_segment_name).query) - route_or_segment_name = query["route"][0] +def juggle_route(route_or_segment_name, can, layout, dbc=None): + sr = SegmentRangeReader(route_or_segment_name) - if route_or_segment_name.startswith(("http://", "https://", "cd:/")) or os.path.isfile(route_or_segment_name): - logs = [route_or_segment_name] - elif ci: - route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) - route = route_or_segment_name.route_name.canonical_name - segment_start = max(route_or_segment_name.segment_num, 0) - logs = [get_url(route, i) for i in range(100)] # Assume there not more than 100 segments - else: - route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) - segment_start = max(route_or_segment_name.segment_num, 0) - - if route_or_segment_name.segment_num != -1 and segment_count is None: - segment_count = 1 - - r = Route(route_or_segment_name.route_name.canonical_name, route_or_segment_name.data_dir) - logs = r.qlog_paths() if qlog else r.log_paths() - - segment_end = segment_start + segment_count if segment_count else None - logs = logs[segment_start:segment_end] - - if None in logs: - resp = input(f"{logs.count(None)}/{len(logs)} of the rlogs in this segment are missing, would you like to fall back to the qlogs? (y/n) ") - if resp == 'y': - logs = r.qlog_paths()[segment_start:segment_end] - else: - print("Please try a different route or segment") - return - - all_data = [] with multiprocessing.Pool(24) as pool: - for d in pool.map(load_segment, logs): - all_data += d - - if not can: - all_data = [d for d in all_data if d.which() not in ['can', 'sendcan']] + all_data = [] + for p in pool.map(partial(process, can), sr.lrs): + all_data.extend(p) # Infer DBC name from logs if dbc is None: @@ -146,15 +102,12 @@ if __name__ == "__main__": formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") - parser.add_argument("--qlog", action="store_true", help="Use qlogs") - parser.add_argument("--ci", action="store_true", help="Download data from openpilot CI bucket") parser.add_argument("--can", action="store_true", help="Parse CAN data") parser.add_argument("--stream", action="store_true", help="Start PlotJuggler in streaming mode") parser.add_argument("--layout", nargs='?', help="Run PlotJuggler with a pre-defined layout") parser.add_argument("--install", action="store_true", help="Install or update PlotJuggler + plugins") parser.add_argument("--dbc", help="Set the DBC name to load for parsing CAN data. If not set, the DBC will be automatically inferred from the logs.") parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name to plot (cabana share URL accepted)") - parser.add_argument("segment_count", type=int, nargs='?', help="The number of segments to plot") if len(sys.argv) == 1: parser.print_help() @@ -177,4 +130,4 @@ if __name__ == "__main__": start_juggler(layout=args.layout) else: route_or_segment_name = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip() - juggle_route(route_or_segment_name, args.segment_count, args.qlog, args.can, args.layout, args.dbc, args.ci) + juggle_route(route_or_segment_name, args.can, args.layout, args.dbc) diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index b002331cd7..1cb2dc0674 100755 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -8,7 +8,7 @@ import unittest from openpilot.common.basedir import BASEDIR from openpilot.common.timeout import Timeout -from openpilot.tools.plotjuggler.juggle import install +from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE, install PJ_DIR = os.path.join(BASEDIR, "tools/plotjuggler") @@ -18,8 +18,8 @@ class TestPlotJuggler(unittest.TestCase): install() pj = os.path.join(PJ_DIR, "juggle.py") - with subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} --demo None 1 --qlog', - stderr=subprocess.PIPE, shell=True, start_new_session=True) as p: + with subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} "{DEMO_ROUTE}/:2"', + stderr=subprocess.PIPE, shell=True, start_new_session=True) as p: # Wait for "Done reading Rlog data" signal from the plugin output = "\n" with Timeout(180, error_msg=output): From e7657d896f3cb7ec5044d4ac4c48a714c8e4c512 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 20:15:37 -0500 Subject: [PATCH 436/724] Subaru: fix disable eyesight enum (#30974) value --- selfdrive/car/subaru/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 8a33ce5e24..1296aead5e 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -122,7 +122,7 @@ class CarInterface(CarInterfaceBase): ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable if candidate in GLOBAL_GEN2 and ret.openpilotLongitudinalControl: - ret.flags |= SubaruFlags.DISABLE_EYESIGHT + ret.flags |= SubaruFlags.DISABLE_EYESIGHT.value if ret.openpilotLongitudinalControl: ret.longitudinalTuning.kpBP = [0., 5., 35.] From f2c73039d7799da139ec4e55520f525db4fb95cc Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 11 Jan 2024 21:09:39 -0500 Subject: [PATCH 437/724] fix metadrive after bump (#30967) * fix clip * wip * oop * 1.22 * ... ugly * fix tuning --- tools/sim/bridge/metadrive/metadrive_bridge.py | 8 +------- tools/sim/bridge/metadrive/metadrive_process.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_bridge.py b/tools/sim/bridge/metadrive/metadrive_bridge.py index b99596ac62..76d09a4529 100644 --- a/tools/sim/bridge/metadrive/metadrive_bridge.py +++ b/tools/sim/bridge/metadrive/metadrive_bridge.py @@ -3,15 +3,13 @@ import numpy as np from metadrive.component.sensors.rgb_camera import RGBCamera from metadrive.component.sensors.base_camera import _cuda_enable from metadrive.component.map.pg_map import MapGenerateMethod -from panda3d.core import Vec3, Texture, GraphicsOutput +from panda3d.core import Texture, GraphicsOutput from openpilot.tools.sim.bridge.common import SimulatorBridge from openpilot.tools.sim.bridge.metadrive.metadrive_world import MetaDriveWorld from openpilot.tools.sim.lib.camerad import W, H -C3_POSITION = Vec3(0.0, 1.0, 1.22) - class CopyRamRGBCamera(RGBCamera): """Camera which copies its content into RAM during the render process, for faster image grabbing.""" @@ -33,8 +31,6 @@ class CopyRamRGBCamera(RGBCamera): class RGBCameraWide(CopyRamRGBCamera): def __init__(self, *args, **kwargs): super(RGBCameraWide, self).__init__(*args, **kwargs) - cam = self.get_cam() - cam.setPos(C3_POSITION) lens = self.get_lens() lens.setFov(120) lens.setNear(0.1) @@ -42,8 +38,6 @@ class RGBCameraWide(CopyRamRGBCamera): class RGBCameraRoad(CopyRamRGBCamera): def __init__(self, *args, **kwargs): super(RGBCameraRoad, self).__init__(*args, **kwargs) - cam = self.get_cam() - cam.setPos(C3_POSITION) lens = self.get_lens() lens.setFov(40) lens.setNear(0.1) diff --git a/tools/sim/bridge/metadrive/metadrive_process.py b/tools/sim/bridge/metadrive/metadrive_process.py index 3fb4186bf9..8040d0d050 100644 --- a/tools/sim/bridge/metadrive/metadrive_process.py +++ b/tools/sim/bridge/metadrive/metadrive_process.py @@ -2,6 +2,7 @@ import math import numpy as np from collections import namedtuple +from panda3d.core import Vec3 from multiprocessing.connection import Connection from metadrive.engine.core.engine_core import EngineCore @@ -10,9 +11,13 @@ from metadrive.envs.metadrive_env import MetaDriveEnv from metadrive.obs.image_obs import ImageObservation from openpilot.common.realtime import Ratekeeper + from openpilot.tools.sim.lib.common import vec3 from openpilot.tools.sim.lib.camerad import W, H +C3_POSITION = Vec3(0.0, 0, 1.22) +C3_HPR = Vec3(0, 0,0) + metadrive_state = namedtuple("metadrive_state", ["velocity", "position", "bearing", "steering_angle"]) @@ -58,14 +63,17 @@ def metadrive_process(dual_camera: bool, config: dict, camera_array, wide_camera def get_cam_as_rgb(cam): cam = env.engine.sensors[cam] - img = cam.perceive(env.vehicle, clip=False) + cam.get_cam().reparentTo(env.vehicle.origin) + cam.get_cam().setPos(C3_POSITION) + cam.get_cam().setHpr(C3_HPR) + img = cam.perceive(clip=False) if type(img) != np.ndarray: img = img.get() # convert cupy array to numpy return img rk = Ratekeeper(100, None) - steer_ratio = 15 + steer_ratio = 8 vc = [0,0] while not exit_event.is_set(): From 96c91c486e9b39c11632691fe5761975d9aa53b6 Mon Sep 17 00:00:00 2001 From: royjr Date: Thu, 11 Jan 2024 22:42:20 -0500 Subject: [PATCH 438/724] networking: add hidden network option (#30808) * init * once * match * fix * maybe * maybe * Revert "maybe" This reverts commit 8386a0a381d5cfe94d7bbf0187d5e311d4b4e9c3. * Revert "maybe" This reverts commit 3abf7b19836e5f353fbb94c9bee52b185bd3fc17. * Revert "fix" This reverts commit 09e0146f4deeac07b9c26e916a75b70c7dcff1f1. * Revert "match" This reverts commit 39a90c6cf88f310623d5e9058152d025ee7213ea. * fix not at bottom * padding * match * Update networking.cc * min chars * translations * shouldn't need * need! * just in case * Revert "just in case" This reverts commit a3c1e090682e30400ba9c20b04407b9e5677ad7a. * finish translations * not secret * optional * allow open networks * fix * try * working * add divider * update translations * fix * better name * translate hidden network * auto * v2 * remove v1 * fix * fix translations --- selfdrive/ui/qt/network/networking.cc | 20 ++++++++++++++++++++ selfdrive/ui/qt/network/networking.h | 2 ++ selfdrive/ui/translations/main_ar.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_de.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_fr.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_ja.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_ko.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_pt-BR.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_th.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_tr.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_zh-CHS.ts | 20 ++++++++++++++++++++ selfdrive/ui/translations/main_zh-CHT.ts | 20 ++++++++++++++++++++ 12 files changed, 222 insertions(+) diff --git a/selfdrive/ui/qt/network/networking.cc b/selfdrive/ui/qt/network/networking.cc index 090b9b578c..5354a01fa0 100644 --- a/selfdrive/ui/qt/network/networking.cc +++ b/selfdrive/ui/qt/network/networking.cc @@ -48,6 +48,7 @@ Networking::Networking(QWidget* parent, bool show_advanced) : QFrame(parent) { an = new AdvancedNetworking(this, wifi); connect(an, &AdvancedNetworking::backPress, [=]() { main_layout->setCurrentWidget(wifiScreen); }); + connect(an, &AdvancedNetworking::requestWifiScreen, [=]() { main_layout->setCurrentWidget(wifiScreen); }); main_layout->addWidget(an); QPalette pal = palette(); @@ -181,6 +182,25 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid }); list->addItem(meteredToggle); + // Hidden Network + hiddenNetworkButton = new ButtonControl(tr("Hidden Network"), tr("CONNECT")); + connect(hiddenNetworkButton, &ButtonControl::clicked, [=]() { + QString ssid = InputDialog::getText(tr("Enter SSID"), this, "", false, 1); + if (!ssid.isEmpty()) { + QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(ssid), true, -1); + Network hidden_network; + hidden_network.ssid = ssid.toUtf8(); + if (!pass.isEmpty()) { + hidden_network.security_type = SecurityType::WPA; + wifi->connect(hidden_network, pass); + } else { + wifi->connect(hidden_network); + } + emit requestWifiScreen(); + } + }); + list->addItem(hiddenNetworkButton); + // Set initial config wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered); diff --git a/selfdrive/ui/qt/network/networking.h b/selfdrive/ui/qt/network/networking.h index 4ff7380f42..9b6af005ea 100644 --- a/selfdrive/ui/qt/network/networking.h +++ b/selfdrive/ui/qt/network/networking.h @@ -62,12 +62,14 @@ private: ToggleControl* tetheringToggle; ToggleControl* roamingToggle; ButtonControl* editApnButton; + ButtonControl* hiddenNetworkButton; ToggleControl* meteredToggle; WifiManager* wifi = nullptr; Params params; signals: void backPress(); + void requestWifiScreen(); public slots: void toggleTethering(bool enabled); diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index dc771b8c6e..2032807630 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection منع تحميل البيانات الكبيرة عندما يكون الاتصال محدوداً
+ + Hidden Network + شبكة مخفية + + + CONNECT + الاتصال + + + Enter SSID + أدخل SSID + + + Enter password + أدخل كلمة المرور + + + for "%1" + من أجل "%1" +
AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 498d4c4438..782d65b06f 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection Hochladen großer Dateien über getaktete Verbindungen unterbinden + + Hidden Network + + + + CONNECT + CONNECT + + + Enter SSID + SSID eingeben + + + Enter password + Passwort eingeben + + + for "%1" + für "%1" + AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 5ae3acdc1e..219e9351cd 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection Éviter les transferts de données importants sur une connexion limitée + + Hidden Network + + + + CONNECT + CONNECTER + + + Enter SSID + Entrer le SSID + + + Enter password + Entrer le mot de passe + + + for "%1" + pour "%1" + AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index d01657f8e4..ba3ca8faf6 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection 大量のデータのアップロードを防止します。 + + Hidden Network + + + + CONNECT + 接続 + + + Enter SSID + SSID を入力 + + + Enter password + パスワードを入力 + + + for "%1" + ネットワーク名:%1 + AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index d2d6a4ddc1..8c1a0fb40e 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection 데이터 요금제 연결 시 대용량 데이터 업로드를 방지합니다 + + Hidden Network + + + + CONNECT + 연결됨 + + + Enter SSID + SSID 입력 + + + Enter password + 비밀번호를 입력하세요 + + + for "%1" + "%1"에 접속하려면 비밀번호가 필요합니다 + AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 04458974cb..f983c8fb4b 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection Evite grandes uploads de dados quando estiver em uma conexão limitada + + Hidden Network + + + + CONNECT + CONEXÃO + + + Enter SSID + Insira SSID + + + Enter password + Insira a senha + + + for "%1" + para "%1" + AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 5f77f6c9de..0d0bda4abe 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection ปิดการอัพโหลดข้อมูลขนาดใหญ่เมื่อเชื่อมต่อผ่านเซลลูล่าร์ + + Hidden Network + + + + CONNECT + เชื่อมต่อ + + + Enter SSID + ป้อนค่า SSID + + + Enter password + ใส่รหัสผ่าน + + + for "%1" + สำหรับ "%1" + AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index f8a76669a2..d14dd518a1 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection + + Hidden Network + + + + CONNECT + BAĞLANTI + + + Enter SSID + APN Gir + + + Enter password + Parolayı girin + + + for "%1" + için "%1" + AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 371106d571..ef0d78402e 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection 当使用按流量计费的连接时,避免上传大流量数据 + + Hidden Network + + + + CONNECT + CONNECT + + + Enter SSID + 输入SSID + + + Enter password + 输入密码 + + + for "%1" + 网络名称:"%1" + AnnotatedCameraWidget diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 6aa6295ce2..121bf58f1a 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -66,6 +66,26 @@ Prevent large data uploads when on a metered connection 防止使用行動網路上傳大量的數據 + + Hidden Network + + + + CONNECT + 雲端服務 + + + Enter SSID + 輸入 SSID + + + Enter password + 輸入密碼 + + + for "%1" + 給 "%1" + AnnotatedCameraWidget From ef73ee7b37d8173191ed58fd695d39d56164954f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 11 Jan 2024 22:55:58 -0800 Subject: [PATCH 439/724] simple navd test (#30976) * simple test * can't check that * cleanup --- selfdrive/navd/tests/test_navd.py | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100755 selfdrive/navd/tests/test_navd.py diff --git a/selfdrive/navd/tests/test_navd.py b/selfdrive/navd/tests/test_navd.py new file mode 100755 index 0000000000..e2a5944c2b --- /dev/null +++ b/selfdrive/navd/tests/test_navd.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +import json +import random +import unittest +import numpy as np + +import cereal.messaging as messaging +from openpilot.common.params import Params +from openpilot.selfdrive.manager.process_config import managed_processes + + +class TestNavd(unittest.TestCase): + def setUp(self): + self.params = Params() + self.sm = messaging.SubMaster(['navRoute', 'navInstruction']) + + def tearDown(self): + managed_processes['navd'].stop() + + def _check_route(self, start, end, check_coords=True): + self.params.put("NavDestination", json.dumps(end)) + self.params.put("LastGPSPosition", json.dumps(start)) + + managed_processes['navd'].start() + for _ in range(30): + self.sm.update(1000) + if all(f > 0 for f in self.sm.rcv_frame.values()): + break + else: + raise Exception("didn't get a route") + + assert managed_processes['navd'].proc.is_alive() + managed_processes['navd'].stop() + + # ensure start and end match up + if check_coords: + coords = self.sm['navRoute'].coordinates + assert np.allclose([start['latitude'], start['longitude'], end['latitude'], end['longitude']], + [coords[0].latitude, coords[0].longitude, coords[-1].latitude, coords[-1].longitude], + rtol=1e-3) + + def test_simple(self): + start = { + "latitude": 32.7427228, + "longitude": -117.2321177, + } + end = { + "latitude": 32.7557004, + "longitude": -117.268002, + } + self._check_route(start, end) + + def test_random(self): + for _ in range(10): + start = {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} + end = {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} + self._check_route(start, end, check_coords=False) + + +if __name__ == "__main__": + unittest.main() From 5da573ff39afd137fb8d9c0b0c75c2ef82106b17 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 11 Jan 2024 23:15:29 -0800 Subject: [PATCH 440/724] navd: handle errors in parsing api response (#30977) --- selfdrive/navd/navd.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index fcaf46aff4..2be703cdb2 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -71,8 +71,11 @@ class RouteEngine: self.ui_pid = ui_pid[0] self.update_location() - self.recompute_route() - self.send_instruction() + try: + self.recompute_route() + self.send_instruction() + except Exception: + cloudlog.exception("navd.failed_to_compute") def update_location(self): location = self.sm['liveLocationKalman'] @@ -256,7 +259,10 @@ class RouteEngine: for i in range(self.step_idx + 1, len(self.route)): total_distance += self.route[i]['distance'] total_time += self.route[i]['duration'] - total_time_typical += self.route[i]['duration_typical'] + if self.route[i]['duration_typical'] is None: + total_time_typical += self.route[i]['duration'] + else: + total_time_typical += self.route[i]['duration_typical'] msg.navInstruction.distanceRemaining = total_distance msg.navInstruction.timeRemaining = total_time From ba792d576a49a0899b88a753fa1c52956bedf9e6 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Fri, 12 Jan 2024 01:16:04 -0600 Subject: [PATCH 441/724] VW MQB: Add FW for 2018 Volkswagen Tiguan (#30951) --- selfdrive/car/volkswagen/fingerprints.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/volkswagen/fingerprints.py b/selfdrive/car/volkswagen/fingerprints.py index fa9061e582..f98a822f1e 100644 --- a/selfdrive/car/volkswagen/fingerprints.py +++ b/selfdrive/car/volkswagen/fingerprints.py @@ -540,6 +540,7 @@ FW_VERSIONS = { b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890005', b'\xf1\x875NA907115J \xf1\x890002', + b'\xf1\x875NA907115K \xf1\x890004', b'\xf1\x8783A907115 \xf1\x890007', b'\xf1\x8783A907115B \xf1\x890005', b'\xf1\x8783A907115F \xf1\x890002', @@ -570,6 +571,7 @@ FW_VERSIONS = { b'\xf1\x870GC300046Q \xf1\x892802', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AG\xf1\x890336\xf1\x82\x1316143231313500314617011730179333423100', b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1316143231313500314617011730179333423100', b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\x1331310031333334313132573732379333313100', b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', @@ -587,6 +589,7 @@ FW_VERSIONS = { (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6050705', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527A6070705', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6017A00', From 9f1b72ac792b8961cd903e425874f3a00a33ab3e Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 12 Jan 2024 14:43:32 -0500 Subject: [PATCH 442/724] Replace multilogiterator (#30980) --- tools/lib/README.md | 18 ------- tools/lib/logreader.py | 70 --------------------------- tools/lib/srreader.py | 15 ++++-- tools/scripts/save_ubloxraw_stream.py | 22 ++------- 4 files changed, 16 insertions(+), 109 deletions(-) diff --git a/tools/lib/README.md b/tools/lib/README.md index daf74aaf40..7d8f2f8bf9 100644 --- a/tools/lib/README.md +++ b/tools/lib/README.md @@ -31,21 +31,3 @@ for msg in lr: if msg.which() == "carState": print(msg.carState.steeringAngleDeg) ``` - -### MultiLogIterator - -`MultiLogIterator` is similar to `LogReader`, but reads multiple logs. - -```python -from openpilot.tools.lib.route import Route -from openpilot.tools.lib.logreader import MultiLogIterator - -# setup a MultiLogIterator to read all the logs in the route -r = Route("a2a0ccea32023010|2023-07-27--13-01-19") -lr = MultiLogIterator(r.log_paths()) - -# print all the steering angles values from all the logs in the route -for msg in lr: - if msg.which() == "carState": - print(msg.carState.steeringAngleDeg) -``` diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 4af922c774..924e07afe9 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -10,71 +10,9 @@ from typing import Iterable, Iterator from cereal import log as capnp_log from openpilot.tools.lib.filereader import FileReader -from openpilot.tools.lib.route import Route, SegmentName LogIterable = Iterable[capnp._DynamicStructReader] -# this is an iterator itself, and uses private variables from LogReader -class MultiLogIterator: - def __init__(self, log_paths, sort_by_time=False): - self._log_paths = log_paths - self.sort_by_time = sort_by_time - - self._first_log_idx = next(i for i in range(len(log_paths)) if log_paths[i] is not None) - self._current_log = self._first_log_idx - self._idx = 0 - self._log_readers = [None]*len(log_paths) - self.start_time = self._log_reader(self._first_log_idx)._ts[0] - - def _log_reader(self, i): - if self._log_readers[i] is None and self._log_paths[i] is not None: - log_path = self._log_paths[i] - self._log_readers[i] = LogReader(log_path, sort_by_time=self.sort_by_time) - - return self._log_readers[i] - - def __iter__(self) -> Iterator[capnp._DynamicStructReader]: - return self - - def _inc(self): - lr = self._log_reader(self._current_log) - if self._idx < len(lr._ents)-1: - self._idx += 1 - else: - self._idx = 0 - self._current_log = next(i for i in range(self._current_log + 1, len(self._log_readers) + 1) - if i == len(self._log_readers) or self._log_paths[i] is not None) - if self._current_log == len(self._log_readers): - raise StopIteration - - def __next__(self): - while 1: - lr = self._log_reader(self._current_log) - ret = lr._ents[self._idx] - self._inc() - return ret - - def tell(self): - # returns seconds from start of log - return (self._log_reader(self._current_log)._ts[self._idx] - self.start_time) * 1e-9 - - def seek(self, ts): - # seek to nearest minute - minute = int(ts/60) - if minute >= len(self._log_paths) or self._log_paths[minute] is None: - return False - - self._current_log = minute - - # HACK: O(n) seek afterward - self._idx = 0 - while self.tell() < ts: - self._inc() - return True - - def reset(self): - self.__init__(self._log_paths, sort_by_time=self.sort_by_time) - class LogReader: def __init__(self, fn, canonicalize=True, only_union_types=False, sort_by_time=False, dat=None): @@ -121,14 +59,6 @@ class LogReader: else: yield ent -def logreader_from_route_or_segment(r, sort_by_time=False): - sn = SegmentName(r, allow_route_name=True) - route = Route(sn.route_name.canonical_name) - if sn.segment_num < 0: - return MultiLogIterator(route.log_paths(), sort_by_time=sort_by_time) - else: - return LogReader(route.log_paths()[sn.segment_num], sort_by_time=sort_by_time) - if __name__ == "__main__": import codecs diff --git a/tools/lib/srreader.py b/tools/lib/srreader.py index db2ebd1936..ea11e5e3c5 100644 --- a/tools/lib/srreader.py +++ b/tools/lib/srreader.py @@ -1,4 +1,5 @@ import enum +import itertools import numpy as np import pathlib import re @@ -132,10 +133,16 @@ class SegmentRangeReader: self.default_mode = default_mode self.default_source = default_source self.sort_by_time = sort_by_time + self.identifier = identifier - self.lrs = self._logreaders_from_identifier(identifier) + self.reset() def __iter__(self): - for lr in self.lrs: - for m in lr: - yield m + return self + + def __next__(self): + return next(self.chain) + + def reset(self): + self.lrs = self._logreaders_from_identifier(self.identifier) + self.chain = itertools.chain(*self.lrs) diff --git a/tools/scripts/save_ubloxraw_stream.py b/tools/scripts/save_ubloxraw_stream.py index 541252d270..39722b4f29 100755 --- a/tools/scripts/save_ubloxraw_stream.py +++ b/tools/scripts/save_ubloxraw_stream.py @@ -3,8 +3,7 @@ import argparse import os import sys from openpilot.common.basedir import BASEDIR -from openpilot.tools.lib.logreader import MultiLogIterator -from openpilot.tools.lib.route import Route +from openpilot.tools.lib.srreader import SegmentRangeReader os.environ['BASEDIR'] = BASEDIR @@ -14,28 +13,17 @@ def get_arg_parser(): description="Unlogging and save to file", formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("data_dir", nargs='?', - help="Path to directory in which log and camera files are located.") - parser.add_argument("route_name", type=(lambda x: x.replace("#", "|")), nargs="?", + parser.add_argument("route", type=(lambda x: x.replace("#", "|")), nargs="?", help="The route whose messages will be published.") parser.add_argument("--out_path", nargs='?', default='/data/ubloxRaw.stream', help="Output pickle file path") return parser -def main(argv): +def main(): args = get_arg_parser().parse_args(sys.argv[1:]) - if not args.data_dir: - print('Data directory invalid.') - return - if not args.route_name: - # Extract route name from path - args.route_name = os.path.basename(args.data_dir) - args.data_dir = os.path.dirname(args.data_dir) - - route = Route(args.route_name, args.data_dir) - lr = MultiLogIterator(route.log_paths()) + lr = SegmentRangeReader(args.route) with open(args.out_path, 'wb') as f: try: @@ -56,4 +44,4 @@ def main(argv): if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) + main() From 3d73512c0702598d27e04039976fe0a91d5952f0 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 12 Jan 2024 15:53:50 -0500 Subject: [PATCH 443/724] rename segmentrangereader to logreader (#30981) * Replace multilogiterator * replace logreader * update readme * fix from_Bytes * new section * reset before iter * selector * fix internal * whitespace --- selfdrive/debug/can_print_changes.py | 7 +- selfdrive/debug/count_events.py | 4 +- selfdrive/debug/filter_log_message.py | 4 +- selfdrive/debug/fingerprint_from_route.py | 4 +- selfdrive/debug/run_process_on_route.py | 4 +- selfdrive/debug/toyota_eps_factor.py | 4 +- .../test/get_thumbnails_for_segment.py | 4 +- tools/car_porting/auto_fingerprint.py | 4 +- .../examples/subaru_long_accel.ipynb | 4 +- .../examples/subaru_steer_temp_fault.ipynb | 4 +- tools/latencylogger/latency_logger.py | 4 +- tools/lib/README.md | 20 +++ tools/lib/logreader.py | 155 +++++++++++++++++- tools/lib/srreader.py | 148 ----------------- .../{test_srreader.py => test_logreader.py} | 16 +- tools/plotjuggler/juggle.py | 4 +- tools/replay/can_replay.py | 4 +- tools/scripts/save_ubloxraw_stream.py | 4 +- tools/tuning/measure_steering_accuracy.py | 4 +- 19 files changed, 205 insertions(+), 197 deletions(-) delete mode 100644 tools/lib/srreader.py rename tools/lib/tests/{test_srreader.py => test_logreader.py} (83%) diff --git a/selfdrive/debug/can_print_changes.py b/selfdrive/debug/can_print_changes.py index 20fe502efc..810e45cfc6 100755 --- a/selfdrive/debug/can_print_changes.py +++ b/selfdrive/debug/can_print_changes.py @@ -7,8 +7,7 @@ from typing import Optional import cereal.messaging as messaging from openpilot.selfdrive.debug.can_table import can_table -from openpilot.tools.lib.logreader import LogIterable -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogIterable, LogReader RED = '\033[91m' CLEAR = '\033[0m' @@ -104,8 +103,8 @@ if __name__ == "__main__": if args.init == '': init_lr = [] else: - init_lr = SegmentRangeReader(args.init) + init_lr = LogReader(args.init) if args.comp: - new_lr = SegmentRangeReader(args.comp) + new_lr = LogReader(args.comp) can_printer(args.bus, init_msgs=init_lr, new_msgs=new_lr, table=args.table) diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py index 70f412bff1..0d545b3153 100755 --- a/selfdrive/debug/count_events.py +++ b/selfdrive/debug/count_events.py @@ -7,7 +7,7 @@ from pprint import pprint from typing import List, Tuple, cast from cereal.services import SERVICE_LIST -from openpilot.tools.lib.srreader import SegmentRangeReader, ReadMode +from openpilot.tools.lib.logreader import LogReader, ReadMode if __name__ == "__main__": cnt_valid: Counter = Counter() @@ -20,7 +20,7 @@ if __name__ == "__main__": start_time = math.inf end_time = -math.inf ignition_off = None - for msg in SegmentRangeReader(sys.argv[1], ReadMode.QLOG): + for msg in LogReader(sys.argv[1], ReadMode.QLOG): end_time = max(end_time, msg.logMonoTime) start_time = min(start_time, msg.logMonoTime) diff --git a/selfdrive/debug/filter_log_message.py b/selfdrive/debug/filter_log_message.py index f8e78c8b98..20028f8fd2 100755 --- a/selfdrive/debug/filter_log_message.py +++ b/selfdrive/debug/filter_log_message.py @@ -3,7 +3,7 @@ import argparse import json import cereal.messaging as messaging -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader LEVELS = { "DEBUG": 10, @@ -55,7 +55,7 @@ if __name__ == "__main__": if args.route: for route in args.route: - lr = SegmentRangeReader(route) + lr = LogReader(route) for m in lr: if m.which() == 'logMessage': print_logmessage(m.logMonoTime, m.logMessage, min_level) diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 7126a282c9..68308bb627 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import sys -from openpilot.tools.lib.srreader import ReadMode, SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader, ReadMode def get_fingerprint(lr): @@ -39,5 +39,5 @@ if __name__ == "__main__": print("Usage: ./fingerprint_from_route.py ") sys.exit(1) - lr = SegmentRangeReader(sys.argv[1], ReadMode.QLOG) + lr = LogReader(sys.argv[1], ReadMode.QLOG) get_fingerprint(lr) diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index 51ba06ec14..441de9ef57 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -4,7 +4,7 @@ import argparse from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, replay_process from openpilot.tools.lib.helpers import save_log -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run process on route and create new logs", @@ -16,7 +16,7 @@ if __name__ == "__main__": cfg = [c for c in CONFIGS if c.proc_name == args.process][0] - lr = SegmentRangeReader(args.route) + lr = LogReader(args.route) inputs = list(lr) outputs = replay_process(cfg, inputs, fingerprint=args.fingerprint) diff --git a/selfdrive/debug/toyota_eps_factor.py b/selfdrive/debug/toyota_eps_factor.py index 1f72fa296a..dc83c8f7a4 100755 --- a/selfdrive/debug/toyota_eps_factor.py +++ b/selfdrive/debug/toyota_eps_factor.py @@ -5,7 +5,7 @@ import matplotlib.pyplot as plt from sklearn import linear_model from openpilot.selfdrive.car.toyota.values import STEER_THRESHOLD -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader MIN_SAMPLES = 30 * 100 @@ -57,6 +57,6 @@ def get_eps_factor(lr, plot=False): if __name__ == "__main__": - lr = SegmentRangeReader(sys.argv[1]) + lr = LogReader(sys.argv[1]) n = get_eps_factor(lr, plot="--plot" in sys.argv) print("EPS torque factor: ", n) diff --git a/system/camerad/test/get_thumbnails_for_segment.py b/system/camerad/test/get_thumbnails_for_segment.py index 95d865e4e4..21409f398d 100755 --- a/system/camerad/test/get_thumbnails_for_segment.py +++ b/system/camerad/test/get_thumbnails_for_segment.py @@ -3,7 +3,7 @@ import argparse import os from tqdm import tqdm -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -13,7 +13,7 @@ if __name__ == "__main__": out_path = os.path.join("jpegs", f"{args.route.replace('|', '_').replace('/', '_')}") os.makedirs(out_path, exist_ok=True) - lr = SegmentRangeReader(args.route) + lr = LogReader(args.route) for msg in tqdm(lr): if msg.which() == 'thumbnail': diff --git a/tools/car_porting/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py index ef3efc1cf3..fce7e33182 100755 --- a/tools/car_porting/auto_fingerprint.py +++ b/tools/car_porting/auto_fingerprint.py @@ -7,7 +7,7 @@ from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versio from openpilot.selfdrive.car.fw_versions import match_fw_to_car from openpilot.selfdrive.car.interfaces import get_interface_attr -from openpilot.tools.lib.srreader import SegmentRangeReader, ReadMode +from openpilot.tools.lib.logreader import LogReader, ReadMode ALL_FW_VERSIONS = get_interface_attr("FW_VERSIONS") @@ -24,7 +24,7 @@ if __name__ == "__main__": parser.add_argument("platform", help="The platform, or leave empty to auto-determine using fuzzy", default=None, nargs='?') args = parser.parse_args() - lr = SegmentRangeReader(args.route, ReadMode.QLOG) + lr = LogReader(args.route, ReadMode.QLOG) carFw = None carVin = None diff --git a/tools/car_porting/examples/subaru_long_accel.ipynb b/tools/car_porting/examples/subaru_long_accel.ipynb index ea685dcd4f..24470b44d2 100644 --- a/tools/car_porting/examples/subaru_long_accel.ipynb +++ b/tools/car_porting/examples/subaru_long_accel.ipynb @@ -24,14 +24,14 @@ "from opendbc.can.parser import CANParser\n", "\n", "from openpilot.selfdrive.car.subaru.values import DBC\n", - "from openpilot.tools.lib.srreader import SegmentRangeReader\n", + "from openpilot.tools.lib.logreader import LogReader\n", "\n", "\"\"\"\n", "In this example, we plot the relationship between Cruise_Brake and Acceleration for stock eyesight.\n", "\"\"\"\n", "\n", "for segment in segments:\n", - " lr = SegmentRangeReader(segment)\n", + " lr = LogReader(segment)\n", "\n", " messages = [\n", " (\"ES_Distance\", 20),\n", diff --git a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb index b60915e6ec..46d8dc413e 100644 --- a/tools/car_porting/examples/subaru_steer_temp_fault.ipynb +++ b/tools/car_porting/examples/subaru_steer_temp_fault.ipynb @@ -27,7 +27,7 @@ "from opendbc.can.parser import CANParser\n", "\n", "from openpilot.selfdrive.car.subaru.values import CanBus, DBC\n", - "from openpilot.tools.lib.srreader import SegmentRangeReader\n", + "from openpilot.tools.lib.logreader import LogReader\n", "\n", "\"\"\"\n", "In this example, we search for positive transitions of Steer_Warning, which indicate that the EPS\n", @@ -36,7 +36,7 @@ "\"\"\"\n", "\n", "for segment in segments:\n", - " lr = SegmentRangeReader(segment)\n", + " lr = LogReader(segment)\n", "\n", " can_msgs = [msg for msg in lr if msg.which() == \"can\"]\n", "\n", diff --git a/tools/latencylogger/latency_logger.py b/tools/latencylogger/latency_logger.py index 29d4889d35..b561b65105 100755 --- a/tools/latencylogger/latency_logger.py +++ b/tools/latencylogger/latency_logger.py @@ -8,7 +8,7 @@ import sys from bisect import bisect_left, bisect_right from collections import defaultdict -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader DEMO_ROUTE = "9f583b1d93915c31|2022-05-18--10-49-51--0" @@ -236,7 +236,7 @@ if __name__ == "__main__": args = parser.parse_args() r = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip() - lr = SegmentRangeReader(r, sort_by_time=True) + lr = LogReader(r, sort_by_time=True) data, _ = get_timestamps(lr) print_timestamps(data['timestamp'], data['duration'], data['start'], args.relative) diff --git a/tools/lib/README.md b/tools/lib/README.md index 7d8f2f8bf9..a0c4a0863b 100644 --- a/tools/lib/README.md +++ b/tools/lib/README.md @@ -31,3 +31,23 @@ for msg in lr: if msg.which() == "carState": print(msg.carState.steeringAngleDeg) ``` + +### Segment Ranges + +We also support a new format called a "segment range", where you can specify which segments from a route to load. + +```python + +lr = LogReader("a2a0ccea32023010|2023-07-27--13-01-19/4") # 4th segment +lr = LogReader("a2a0ccea32023010|2023-07-27--13-01-19/4:6") # 4th and 5th segment +lr = LogReader("a2a0ccea32023010|2023-07-27--13-01-19/-1") # last segment +lr = LogReader("a2a0ccea32023010|2023-07-27--13-01-19/:5") # first 5 segments +lr = LogReader("a2a0ccea32023010|2023-07-27--13-01-19/1:") # all except first segment +``` + +and can select which type of logs to grab + +```python +lr = LogReader("a2a0ccea32023010|2023-07-27--13-01-19/4/q") # get qlogs +lr = LogReader("a2a0ccea32023010|2023-07-27--13-01-19/4/r") # get rlogs (default) +``` diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 924e07afe9..7142c7f854 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -1,20 +1,29 @@ #!/usr/bin/env python3 -import os -import sys import bz2 -import urllib.parse import capnp +import enum +import itertools +import numpy as np +import os +import pathlib +import re +import sys +import urllib.parse import warnings from typing import Iterable, Iterator +from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log +from openpilot.selfdrive.test.openpilotci import get_url from openpilot.tools.lib.filereader import FileReader +from openpilot.tools.lib.helpers import RE +from openpilot.tools.lib.route import Route, SegmentRange LogIterable = Iterable[capnp._DynamicStructReader] -class LogReader: +class _LogFileReader: def __init__(self, fn, canonicalize=True, only_union_types=False, sort_by_time=False, dat=None): self.data_version = None self._only_union_types = only_union_types @@ -44,10 +53,6 @@ class LogReader: self._ents = list(sorted(_ents, key=lambda x: x.logMonoTime) if sort_by_time else _ents) self._ts = [x.logMonoTime for x in self._ents] - @classmethod - def from_bytes(cls, dat): - return cls("", dat=dat) - def __iter__(self) -> Iterator[capnp._DynamicStructReader]: for ent in self._ents: if self._only_union_types: @@ -60,6 +65,140 @@ class LogReader: yield ent +class ReadMode(enum.StrEnum): + RLOG = "r" # only read rlogs + QLOG = "q" # only read qlogs + #AUTO = "a" # default to rlogs, fallback to qlogs, not supported yet + + +def create_slice_from_string(s: str): + m = re.fullmatch(RE.SLICE, s) + assert m is not None, f"Invalid slice: {s}" + start, end, step = m.groups() + start = int(start) if start is not None else None + end = int(end) if end is not None else None + step = int(step) if step is not None else None + + if start is not None and ":" not in s and end is None and step is None: + return start + return slice(start, end, step) + +def parse_slice(sr: SegmentRange): + route = Route(sr.route_name) + segs = np.arange(route.max_seg_number+1) + s = create_slice_from_string(sr._slice) + return segs[s] if isinstance(s, slice) else [segs[s]] + +def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): + segs = parse_slice(sr) + route = Route(sr.route_name) + + log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() + + invalid_segs = [seg for seg in segs if log_paths[seg] is None] + + assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" + + for seg in segs: + yield _LogFileReader(log_paths[seg], sort_by_time=sort_by_time) + +def internal_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): + segs = parse_slice(sr) + + for seg in segs: + yield _LogFileReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2", sort_by_time=sort_by_time) + +def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): + segs = parse_slice(sr) + + for seg in segs: + yield _LogFileReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog'), sort_by_time=sort_by_time) + +def direct_source(file_or_url, sort_by_time): + yield _LogFileReader(file_or_url, sort_by_time=sort_by_time) + +def auto_source(*args, **kwargs): + # Automatically determine viable source + + try: + next(internal_source(*args, **kwargs)) + return internal_source(*args, **kwargs) + except Exception: + pass + + try: + next(openpilotci_source(*args, **kwargs)) + return openpilotci_source(*args, **kwargs) + except Exception: + pass + + return comma_api_source(*args, **kwargs) + +def parse_useradmin(identifier): + if "useradmin.comma.ai" in identifier: + query = parse_qs(urlparse(identifier).query) + return query["onebox"][0] + return None + +def parse_cabana(identifier): + if "cabana.comma.ai" in identifier: + query = parse_qs(urlparse(identifier).query) + return query["route"][0] + return None + +def parse_direct(identifier): + if identifier.startswith(("http://", "https://", "cd:/")) or pathlib.Path(identifier).exists(): + return identifier + return None + +def parse_indirect(identifier): + parsed = parse_useradmin(identifier) or parse_cabana(identifier) + + if parsed is not None: + return parsed, comma_api_source, True + + return identifier, None, False + + +class LogReader: + def _logreaders_from_identifier(self, identifier): + parsed, source, is_indirect = parse_indirect(identifier) + + if not is_indirect: + direct_parsed = parse_direct(identifier) + if direct_parsed is not None: + return direct_source(identifier, sort_by_time=self.sort_by_time) + + sr = SegmentRange(parsed) + mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) + source = self.default_source if source is None else source + + return source(sr, mode, sort_by_time=self.sort_by_time) + + def __init__(self, identifier: str, default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False): + self.default_mode = default_mode + self.default_source = default_source + self.sort_by_time = sort_by_time + self.identifier = identifier + + self.reset() + + def __iter__(self): + self.reset() + return self + + def __next__(self): + return next(self.chain) + + def reset(self): + self.lrs = self._logreaders_from_identifier(self.identifier) + self.chain = itertools.chain(*self.lrs) + + @staticmethod + def from_bytes(dat): + return _LogFileReader("", dat=dat) + + if __name__ == "__main__": import codecs # capnproto <= 0.8.0 throws errors converting byte data to string diff --git a/tools/lib/srreader.py b/tools/lib/srreader.py deleted file mode 100644 index ea11e5e3c5..0000000000 --- a/tools/lib/srreader.py +++ /dev/null @@ -1,148 +0,0 @@ -import enum -import itertools -import numpy as np -import pathlib -import re -from urllib.parse import parse_qs, urlparse - -from openpilot.selfdrive.test.openpilotci import get_url -from openpilot.tools.lib.helpers import RE -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.route import Route, SegmentRange - -class ReadMode(enum.StrEnum): - RLOG = "r" # only read rlogs - QLOG = "q" # only read qlogs - #AUTO = "a" # default to rlogs, fallback to qlogs, not supported yet - - -def create_slice_from_string(s: str): - m = re.fullmatch(RE.SLICE, s) - assert m is not None, f"Invalid slice: {s}" - start, end, step = m.groups() - start = int(start) if start is not None else None - end = int(end) if end is not None else None - step = int(step) if step is not None else None - - if start is not None and ":" not in s and end is None and step is None: - return start - return slice(start, end, step) - - -def parse_slice(sr: SegmentRange): - route = Route(sr.route_name) - segs = np.arange(route.max_seg_number+1) - s = create_slice_from_string(sr._slice) - return segs[s] if isinstance(s, slice) else [segs[s]] - -def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): - segs = parse_slice(sr) - route = Route(sr.route_name) - - log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() - - invalid_segs = [seg for seg in segs if log_paths[seg] is None] - - assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" - - for seg in segs: - yield LogReader(log_paths[seg], sort_by_time=sort_by_time) - -def internal_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): - segs = parse_slice(sr) - - for seg in segs: - yield LogReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2", sort_by_time=sort_by_time) - -def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): - segs = parse_slice(sr) - - for seg in segs: - yield LogReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog'), sort_by_time=sort_by_time) - -def direct_source(file_or_url, sort_by_time): - yield LogReader(file_or_url, sort_by_time=sort_by_time) - -def auto_source(*args, **kwargs): - # Automatically determine viable source - - try: - next(internal_source(*args, **kwargs)) - return internal_source(*args, **kwargs) - except Exception: - pass - - try: - next(openpilotci_source(*args, **kwargs)) - return openpilotci_source(*args, **kwargs) - except Exception: - pass - - return comma_api_source(*args, **kwargs) - -def parse_useradmin(identifier): - if "useradmin.comma.ai" in identifier: - query = parse_qs(urlparse(identifier).query) - return query["onebox"][0] - return None - -def parse_cabana(identifier): - if "cabana.comma.ai" in identifier: - query = parse_qs(urlparse(identifier).query) - return query["route"][0] - return None - -def parse_cd(identifier): - if "cd:/" in identifier: - return identifier.replace("cd:/", "") - return None - -def parse_direct(identifier): - if "https://" in identifier or "http://" in identifier or pathlib.Path(identifier).exists(): - return identifier - return None - -def parse_indirect(identifier): - parsed = parse_useradmin(identifier) or parse_cabana(identifier) - - if parsed is not None: - return parsed, comma_api_source, True - - parsed = parse_cd(identifier) - if parsed is not None: - return parsed, internal_source, True - - return identifier, None, False - -class SegmentRangeReader: - def _logreaders_from_identifier(self, identifier): - parsed, source, is_indirect = parse_indirect(identifier) - - if not is_indirect: - direct_parsed = parse_direct(identifier) - if direct_parsed is not None: - return direct_source(identifier, sort_by_time=self.sort_by_time) - - sr = SegmentRange(parsed) - mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) - source = self.default_source if source is None else source - - return source(sr, mode, sort_by_time=self.sort_by_time) - - def __init__(self, identifier: str, default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False): - self.default_mode = default_mode - self.default_source = default_source - self.sort_by_time = sort_by_time - self.identifier = identifier - - self.reset() - - def __iter__(self): - return self - - def __next__(self): - return next(self.chain) - - def reset(self): - self.lrs = self._logreaders_from_identifier(self.identifier) - self.chain = itertools.chain(*self.lrs) diff --git a/tools/lib/tests/test_srreader.py b/tools/lib/tests/test_logreader.py similarity index 83% rename from tools/lib/tests/test_srreader.py rename to tools/lib/tests/test_logreader.py index b22599dee8..db207a6522 100644 --- a/tools/lib/tests/test_srreader.py +++ b/tools/lib/tests/test_logreader.py @@ -4,16 +4,15 @@ import numpy as np import unittest from parameterized import parameterized import requests - +from openpilot.tools.lib.logreader import LogReader, parse_indirect, parse_slice, ReadMode from openpilot.tools.lib.route import SegmentRange -from openpilot.tools.lib.srreader import ReadMode, SegmentRangeReader, parse_slice, parse_indirect NUM_SEGS = 17 # number of segments in the test route ALL_SEGS = list(np.arange(NUM_SEGS)) TEST_ROUTE = "344c5c15b34f2d8a/2024-01-03--09-37-12" QLOG_FILE = "https://commadataci.blob.core.windows.net/openpilotci/0375fdf7b1ce594d/2019-06-13--08-32-25/3/qlog.bz2" -class TestSegmentRangeReader(unittest.TestCase): +class TestLogReader(unittest.TestCase): @parameterized.expand([ (f"{TEST_ROUTE}", ALL_SEGS), (f"{TEST_ROUTE.replace('/', '|')}", ALL_SEGS), @@ -38,7 +37,6 @@ class TestSegmentRangeReader(unittest.TestCase): (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE.replace('/', '|')}", ALL_SEGS), (f"https://useradmin.comma.ai/?onebox={TEST_ROUTE.replace('/', '%7C')}", ALL_SEGS), (f"https://cabana.comma.ai/?route={TEST_ROUTE}", ALL_SEGS), - (f"cd:/{TEST_ROUTE}", ALL_SEGS), ]) def test_indirect_parsing(self, identifier, expected): parsed, _, _ = parse_indirect(identifier) @@ -54,7 +52,7 @@ class TestSegmentRangeReader(unittest.TestCase): shutil.copyfileobj(r.raw, f) for f in [QLOG_FILE, qlog.name]: - l = len(list(SegmentRangeReader(f))) + l = len(list(LogReader(f))) self.assertGreater(l, 100) @parameterized.expand([ @@ -72,14 +70,14 @@ class TestSegmentRangeReader(unittest.TestCase): parse_slice(sr) def test_modes(self): - qlog_len = len(list(SegmentRangeReader(f"{TEST_ROUTE}/0", ReadMode.QLOG))) - rlog_len = len(list(SegmentRangeReader(f"{TEST_ROUTE}/0", ReadMode.RLOG))) + qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.QLOG))) + rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.RLOG))) self.assertLess(qlog_len * 6, rlog_len) def test_modes_from_name(self): - qlog_len = len(list(SegmentRangeReader(f"{TEST_ROUTE}/0/q"))) - rlog_len = len(list(SegmentRangeReader(f"{TEST_ROUTE}/0/r"))) + qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/q"))) + rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/r"))) self.assertLess(qlog_len * 6, rlog_len) diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 3df777c959..b497bdaa39 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -14,7 +14,7 @@ from functools import partial from openpilot.common.basedir import BASEDIR from openpilot.tools.lib.helpers import save_log -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader juggle_dir = os.path.dirname(os.path.realpath(__file__)) @@ -74,7 +74,7 @@ def process(can, lr): return [d for d in lr if can or d.which() not in ['can', 'sendcan']] def juggle_route(route_or_segment_name, can, layout, dbc=None): - sr = SegmentRangeReader(route_or_segment_name) + sr = LogReader(route_or_segment_name) with multiprocessing.Pool(24) as pool: all_data = [] diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index 4be516a108..d0a5304cff 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -9,7 +9,7 @@ os.environ['FILEREADER_CACHE'] = '1' from openpilot.common.realtime import config_realtime_process, Ratekeeper, DT_CTRL from openpilot.selfdrive.boardd.boardd import can_capnp_to_can_list -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader from panda import Panda, PandaJungle def send_thread(s, flock): @@ -97,7 +97,7 @@ if __name__ == "__main__": if args.route_or_segment_name is None: args.route_or_segment_name = "77611a1fac303767/2020-03-24--09-50-38/10:16" - sr = SegmentRangeReader(args.route_or_segment_name) + sr = LogReader(args.route_or_segment_name) with multiprocessing.Pool(24) as pool: CAN_MSGS = [] diff --git a/tools/scripts/save_ubloxraw_stream.py b/tools/scripts/save_ubloxraw_stream.py index 39722b4f29..ecbc2bb31e 100755 --- a/tools/scripts/save_ubloxraw_stream.py +++ b/tools/scripts/save_ubloxraw_stream.py @@ -3,7 +3,7 @@ import argparse import os import sys from openpilot.common.basedir import BASEDIR -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader os.environ['BASEDIR'] = BASEDIR @@ -23,7 +23,7 @@ def get_arg_parser(): def main(): args = get_arg_parser().parse_args(sys.argv[1:]) - lr = SegmentRangeReader(args.route) + lr = LogReader(args.route) with open(args.out_path, 'wb') as f: try: diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index de4de46c2a..06523497a5 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -8,7 +8,7 @@ import signal from collections import defaultdict import cereal.messaging as messaging -from openpilot.tools.lib.srreader import SegmentRangeReader +from openpilot.tools.lib.logreader import LogReader def sigint_handler(signal, frame): exit(0) @@ -128,7 +128,7 @@ if __name__ == "__main__": if args.route is not None: print(f"loading {args.route}...") - lr = SegmentRangeReader(args.route, sort_by_time=True) + lr = LogReader(args.route, sort_by_time=True) sm = {} for msg in lr: From 28e5d1bb49000b0740d36d6cd714126e9dab825c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 12 Jan 2024 17:42:40 -0500 Subject: [PATCH 444/724] Metadrive: fix map config on main (#30983) * Fix map api too * dont render --- .../sim/bridge/metadrive/metadrive_bridge.py | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_bridge.py b/tools/sim/bridge/metadrive/metadrive_bridge.py index 76d09a4529..821af7c7f7 100644 --- a/tools/sim/bridge/metadrive/metadrive_bridge.py +++ b/tools/sim/bridge/metadrive/metadrive_bridge.py @@ -60,6 +60,24 @@ def curve_block(length, angle=45, direction=0): "dir": direction } +def create_map(track_size=60): + return dict( + type=MapGenerateMethod.PG_MAP_FILE, + lane_num=2, + lane_width=3.5, + config=[ + None, + straight_block(track_size), + curve_block(track_size*2, 90), + straight_block(track_size), + curve_block(track_size*2, 90), + straight_block(track_size), + curve_block(track_size*2, 90), + straight_block(track_size), + curve_block(track_size*2, 90), + ] + ) + class MetaDriveBridge(SimulatorBridge): TICKS_PER_FRAME = 5 @@ -82,7 +100,6 @@ class MetaDriveBridge(SimulatorBridge): vehicle_config=dict( enable_reverse=False, image_source="rgb_road", - spawn_longitude=15 ), sensors=sensors, image_on_cuda=_cuda_enable, @@ -93,20 +110,7 @@ class MetaDriveBridge(SimulatorBridge): crash_vehicle_done=False, crash_object_done=False, traffic_density=0.0, # traffic is incredibly expensive - map_config=dict( - type=MapGenerateMethod.PG_MAP_FILE, - config=[ - None, - straight_block(120), - curve_block(240, 90), - straight_block(120), - curve_block(240, 90), - straight_block(120), - curve_block(240, 90), - straight_block(120), - curve_block(240, 90), - ] - ), + map_config=create_map(), decision_repeat=1, physics_world_step_size=self.TICKS_PER_FRAME/100, preload_models=False From d39b31aed1f63075d83fa29cad3a2d62b28d6a8b Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 12 Jan 2024 20:36:38 -0500 Subject: [PATCH 445/724] remove get_url instances (#30986) rm --- selfdrive/locationd/test/test_locationd_scenarios.py | 6 ++---- selfdrive/test/process_replay/test_debayer.py | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/selfdrive/locationd/test/test_locationd_scenarios.py b/selfdrive/locationd/test/test_locationd_scenarios.py index b1792e0fd8..4cfbd6f8a9 100755 --- a/selfdrive/locationd/test/test_locationd_scenarios.py +++ b/selfdrive/locationd/test/test_locationd_scenarios.py @@ -5,12 +5,10 @@ import numpy as np from collections import defaultdict from enum import Enum - -from openpilot.selfdrive.test.openpilotci import get_url from openpilot.tools.lib.logreader import LogReader from openpilot.selfdrive.test.process_replay.process_replay import replay_process_with_name -TEST_ROUTE, TEST_SEG_NUM = "ff2bd20623fcaeaa|2023-09-05--10-14-54", 4 +TEST_ROUTE = "ff2bd20623fcaeaa|2023-09-05--10-14-54/4" GPS_MESSAGES = ['gpsLocationExternal', 'gpsLocation'] SELECT_COMPARE_FIELDS = { 'yaw_rate': ['angularVelocityCalibrated', 'value', 2], @@ -108,7 +106,7 @@ class TestLocationdScenarios(unittest.TestCase): @classmethod def setUpClass(cls): - cls.logs = list(LogReader(get_url(TEST_ROUTE, TEST_SEG_NUM))) + cls.logs = list(LogReader(TEST_ROUTE)) def test_base(self): """ diff --git a/selfdrive/test/process_replay/test_debayer.py b/selfdrive/test/process_replay/test_debayer.py index a6e6955dbf..baa9e255e6 100755 --- a/selfdrive/test/process_replay/test_debayer.py +++ b/selfdrive/test/process_replay/test_debayer.py @@ -8,14 +8,13 @@ import pyopencl as cl # install with `PYOPENCL_CL_PRETEND_VERSION=2.0 pip insta from openpilot.system.hardware import PC, TICI from openpilot.common.basedir import BASEDIR -from openpilot.selfdrive.test.openpilotci import BASE_URL, get_url +from openpilot.selfdrive.test.openpilotci import BASE_URL from openpilot.system.version import get_commit from openpilot.system.camerad.snapshot.snapshot import yuv_to_rgb from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.filereader import FileReader -TEST_ROUTE = "8345e3b82948d454|2022-05-04--13-45-33" -SEGMENT = 0 +TEST_ROUTE = "8345e3b82948d454|2022-05-04--13-45-33/0" FRAME_WIDTH = 1928 FRAME_HEIGHT = 1208 @@ -116,7 +115,7 @@ if __name__ == "__main__": ref_commit_fn = os.path.join(replay_dir, "debayer_replay_ref_commit") # load logs - lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT))) + lr = list(LogReader(TEST_ROUTE)) # run replay frames = debayer_replay(lr) From 4178f7c1e16d9b97fb0f1859310bc8a1581def68 Mon Sep 17 00:00:00 2001 From: Alexandre Nobuharu Sato <66435071+AlexandreSato@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:25:41 -0300 Subject: [PATCH 446/724] Multilang: Update pt-BR translation (#30987) * add instructions to my fork * Revert "add instructions to my fork" This reverts commit 46fbee3ebe7ecbb211d855e63b2d4c8919c31b63. * test llm * Revert "test llm" This reverts commit fc531f0a1ba81b3444a34a1be8d02a51a060ec7a. * update pt-BR translation --- selfdrive/ui/translations/main_pt-BR.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index f983c8fb4b..e89a3b1f5a 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -68,23 +68,23 @@ Hidden Network - + Rede Oculta CONNECT - CONEXÃO + CONECTE Enter SSID - Insira SSID + Digite o SSID Enter password - Insira a senha + Insira a senha for "%1" - para "%1" + para "%1" From f55d04c17212cb90ccb82ae1b96105e2e3971a48 Mon Sep 17 00:00:00 2001 From: Lee Jong Mun <43285072+crwusiz@users.noreply.github.com> Date: Sat, 13 Jan 2024 13:44:03 +0900 Subject: [PATCH 447/724] kor translation update (#30990) --- selfdrive/ui/translations/main_ko.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 8c1a0fb40e..b2b76f3903 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -68,23 +68,23 @@ Hidden Network - + 숨겨진 네트워크 CONNECT - 연결됨 + 연결됨 Enter SSID - SSID 입력 + SSID 입력 Enter password - 비밀번호를 입력하세요 + 비밀번호를 입력하세요 for "%1" - "%1"에 접속하려면 비밀번호가 필요합니다 + "%1"에 접속하려면 비밀번호가 필요합니다 From dafde02da36cc13b6b9d3f9bd56aecdf2ddf93f7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 12 Jan 2024 20:58:13 -0800 Subject: [PATCH 448/724] Fingerprints: automatically add missing FW versions from users [bot] (#30988) Export fingerprints --- selfdrive/car/honda/fingerprints.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 543a283e47..f28c85a076 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -218,6 +218,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TWA-A070\x00\x00', b'36802-TWA-A080\x00\x00', + b'36802-TWA-A210\x00\x00', b'36802-TWA-A330\x00\x00', b'36802-TWB-H060\x00\x00', ], @@ -574,6 +575,7 @@ FW_VERSIONS = { b'37805-5PA-6630\x00\x00', b'37805-5PA-6640\x00\x00', b'37805-5PA-7630\x00\x00', + b'37805-5PA-9530\x00\x00', b'37805-5PA-9630\x00\x00', b'37805-5PA-9640\x00\x00', b'37805-5PA-9730\x00\x00', @@ -634,6 +636,7 @@ FW_VERSIONS = { b'46114-TMC-U020\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-TLA-A020\x00\x00', b'78109-TLA-A110\x00\x00', b'78109-TLA-A120\x00\x00', b'78109-TLA-A210\x00\x00', @@ -928,7 +931,9 @@ FW_VERSIONS = { b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A100\x00\x00', b'28101-5EZ-A210\x00\x00', + b'28101-5EZ-A330\x00\x00', b'28101-5EZ-A430\x00\x00', + b'28101-5EZ-A500\x00\x00', b'28101-5EZ-A600\x00\x00', b'28101-5EZ-A700\x00\x00', ], @@ -940,13 +945,19 @@ FW_VERSIONS = { b'37805-RLV-B210\x00\x00', b'37805-RLV-B220\x00\x00', b'37805-RLV-B420\x00\x00', + b'37805-RLV-B430\x00\x00', + b'37805-RLV-B720\x00\x00', b'37805-RLV-C430\x00\x00', b'37805-RLV-C510\x00\x00', b'37805-RLV-C520\x00\x00', b'37805-RLV-C530\x00\x00', b'37805-RLV-C910\x00\x00', b'37805-RLV-F120\x00\x00', + b'37805-RLV-L090\x00\x00', b'37805-RLV-L160\x00\x00', + b'37805-RLV-L180\x00\x00', + b'37805-RLV-L410\x00\x00', + b'37805-RLV-L850\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TG7-A030\x00\x00', @@ -980,6 +991,7 @@ FW_VERSIONS = { b'36161-TGS-A030\x00\x00', b'36161-TGS-A130\x00\x00', b'36161-TGS-A220\x00\x00', + b'36161-TGS-A320\x00\x00', b'36161-TGT-A030\x00\x00', b'36161-TGT-A130\x00\x00', ], @@ -1016,7 +1028,9 @@ FW_VERSIONS = { b'78109-TG8-AJ10\x00\x00', b'78109-TG8-AJ20\x00\x00', b'78109-TG8-AK20\x00\x00', + b'78109-TGS-AB10\x00\x00', b'78109-TGS-AC10\x00\x00', + b'78109-TGS-AD10\x00\x00', b'78109-TGS-AJ20\x00\x00', b'78109-TGS-AK20\x00\x00', b'78109-TGS-AP20\x00\x00', @@ -1069,6 +1083,7 @@ FW_VERSIONS = { b'37805-5YF-A420\x00\x00', b'37805-5YF-A430\x00\x00', b'37805-5YF-A750\x00\x00', + b'37805-5YF-A760\x00\x00', b'37805-5YF-A850\x00\x00', b'37805-5YF-A870\x00\x00', b'37805-5YF-AD20\x00\x00', @@ -1118,6 +1133,7 @@ FW_VERSIONS = { b'78109-TJB-AS10\x00\x00', b'78109-TJB-AU10\x00\x00', b'78109-TJB-AW10\x00\x00', + b'78109-TJC-A240\x00\x00', b'78109-TJC-A420\x00\x00', b'78109-TJC-AA10\x00\x00', b'78109-TJC-AD10\x00\x00', From 884de6e4570545ccf4ee3e6ff8d2f59e4a0847c6 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sat, 13 Jan 2024 00:30:55 -0500 Subject: [PATCH 449/724] test_proclog: fix conflict with pytest (#30989) --- system/proclogd/tests/test_proclog.cc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/system/proclogd/tests/test_proclog.cc b/system/proclogd/tests/test_proclog.cc index 20298cd3d8..613c326166 100644 --- a/system/proclogd/tests/test_proclog.cc +++ b/system/proclogd/tests/test_proclog.cc @@ -143,11 +143,10 @@ TEST_CASE("buildProcLogerMessage") { std::ifstream stream(cmd_path); auto cmdline = Parser::cmdline(stream); REQUIRE(cmdline.size() == p.getCmdline().size()); - // do not check the cmdline of pytest as it will change. - if (cmdline.size() > 0 && cmdline[0].find("[pytest") != 0) { - for (int i = 0; i < p.getCmdline().size(); ++i) { - REQUIRE(cmdline[i] == p.getCmdline()[i].cStr()); - } + for (int i = 0; i < p.getCmdline().size(); ++i) { + // do not check the cmdline of pytest as it will change. + if (cmdline[i].find("[pytest") || std::string(p.getCmdline()[i]).find("[pytest")) continue; + REQUIRE(cmdline[i] == p.getCmdline()[i].cStr()); } } } From 2f744f5df464c1980da218e1dac9141ee56fa7df Mon Sep 17 00:00:00 2001 From: Korben Date: Sat, 13 Jan 2024 22:03:23 +0100 Subject: [PATCH 450/724] Update French language (#30992) Update main_fr.ts validation of new elements --- selfdrive/ui/translations/main_fr.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 219e9351cd..f3aef3d43c 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -68,23 +68,23 @@ Hidden Network - + Réseau Caché CONNECT - CONNECTER + CONNECTER Enter SSID - Entrer le SSID + Entrer le SSID Enter password - Entrer le mot de passe + Entrer le mot de passe for "%1" - pour "%1" + pour "%1" From 66ddfece826ed41bb52175dffb1f5252be15f086 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 13 Jan 2024 15:01:35 -0800 Subject: [PATCH 451/724] speedup devcontainer ci job (#30993) --- .github/workflows/tools_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index d564606c3e..f549ece1b1 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -85,7 +85,7 @@ jobs: devcontainer up --workspace-folder . - name: Test environment run: | - devcontainer exec --workspace-folder . scons -j$(nproc) + devcontainer exec --workspace-folder . scons -j$(nproc) cereal/ common/ devcontainer exec --workspace-folder . pip install pip-install-test devcontainer exec --workspace-folder . touch /home/batman/.comma/auth.json devcontainer exec --workspace-folder . sudo touch /root/test.txt \ No newline at end of file From 3e601b5e8fcaf2fd0885cc64949fa1349a8069eb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 13 Jan 2024 15:40:25 -0800 Subject: [PATCH 452/724] camerad: move ae test to pytest/catch2 (#30994) * mv ae gray test * cleanup --- .github/workflows/selfdrive_tests.yaml | 3 +-- pyproject.toml | 1 + system/camerad/SConscript | 2 +- system/camerad/test/.gitignore | 1 + system/camerad/test/ae_gray_test.h | 17 ------------ .../test/{ae_gray_test.cc => test_ae_gray.cc} | 26 +++++++++++++++---- 6 files changed, 25 insertions(+), 25 deletions(-) delete mode 100644 system/camerad/test/ae_gray_test.h rename system/camerad/test/{ae_gray_test.cc => test_ae_gray.cc} (63%) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 3c95d8055b..e00be7958e 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -180,8 +180,7 @@ jobs: $PYTEST --timeout 30 -m 'not slow' && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ - ./selfdrive/ui/tests/test_translations.py && \ - ./system/camerad/test/ae_gray_test" + ./selfdrive/ui/tests/test_translations.py" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v3 with: diff --git a/pyproject.toml b/pyproject.toml index 2fec88f244..da027ae95b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ testpaths = [ "selfdrive/thermald", "selfdrive/test/longitudinal_maneuvers", "selfdrive/test/process_replay/test_fuzzy.py", + "system/camerad", "system/hardware/tici", "system/loggerd", "system/proclogd", diff --git a/system/camerad/SConscript b/system/camerad/SConscript index 60a8f261e5..8f19e7ee19 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -7,4 +7,4 @@ camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', env.Program('camerad', ['main.cc', camera_obj], LIBS=libs) if GetOption("extras") and arch == "x86_64": - env.Program('test/ae_gray_test', ['test/ae_gray_test.cc', camera_obj], LIBS=libs) + env.Program('test/test_ae_gray', ['test/test_ae_gray.cc', camera_obj], LIBS=libs) diff --git a/system/camerad/test/.gitignore b/system/camerad/test/.gitignore index 44cd0b2730..d67473ebcd 100644 --- a/system/camerad/test/.gitignore +++ b/system/camerad/test/.gitignore @@ -1 +1,2 @@ jpegs/ +test_ae_gray diff --git a/system/camerad/test/ae_gray_test.h b/system/camerad/test/ae_gray_test.h deleted file mode 100644 index 8953fb017f..0000000000 --- a/system/camerad/test/ae_gray_test.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#define W 240 -#define H 160 - -#define TONE_SPLITS 3 - -float gts[TONE_SPLITS * TONE_SPLITS * TONE_SPLITS * TONE_SPLITS] = { - 0.917969, 0.917969, 0.375000, 0.917969, 0.375000, 0.375000, 0.187500, 0.187500, 0.187500, 0.917969, - 0.375000, 0.375000, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.093750, 0.093750, - 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.917969, 0.375000, 0.375000, - 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.093750, 0.093750, 0.093750, 0.093750, - 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, - 0.093750, 0.093750, 0.093750, 0.093750, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, - 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, - 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, - 0.000000}; diff --git a/system/camerad/test/ae_gray_test.cc b/system/camerad/test/test_ae_gray.cc similarity index 63% rename from system/camerad/test/ae_gray_test.cc rename to system/camerad/test/test_ae_gray.cc index 8d18f7e93b..06d784927a 100644 --- a/system/camerad/test/ae_gray_test.cc +++ b/system/camerad/test/test_ae_gray.cc @@ -1,6 +1,5 @@ -// unittest for set_exposure_target - -#include "system/camerad/test/ae_gray_test.h" +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" #include @@ -10,7 +9,25 @@ #include "common/util.h" #include "system/camerad/cameras/camera_common.h" -int main() { +#define W 240 +#define H 160 + + +#define TONE_SPLITS 3 + +float gts[TONE_SPLITS * TONE_SPLITS * TONE_SPLITS * TONE_SPLITS] = { + 0.917969, 0.917969, 0.375000, 0.917969, 0.375000, 0.375000, 0.187500, 0.187500, 0.187500, 0.917969, + 0.375000, 0.375000, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.093750, 0.093750, + 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.917969, 0.375000, 0.375000, + 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.187500, 0.093750, 0.093750, 0.093750, 0.093750, + 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, 0.093750, + 0.093750, 0.093750, 0.093750, 0.093750, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, 0.000000, + 0.000000}; + + +TEST_CASE("camera.test_set_exposure_target") { // set up fake camerabuf CameraBuf cb = {}; VisionBuf vb = {}; @@ -63,5 +80,4 @@ int main() { assert(passed); delete[] fb_y; - return 0; } From e17e66180c3a9432e6d303ca36ff0e4af763dace Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 13 Jan 2024 21:16:35 -0800 Subject: [PATCH 453/724] CI: speedup unit test job (#30995) * start * mv to buildjet * fix --- .github/workflows/selfdrive_tests.yaml | 6 +++++- selfdrive/test/process_replay/test_fuzzy.py | 4 +++- tools/lib/tests/test_logreader.py | 2 ++ tools/sim/tests/test_metadrive_bridge.py | 3 ++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index e00be7958e..78502d3799 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -163,12 +163,16 @@ jobs: unit_tests: name: unit tests - runs-on: ubuntu-20.04 + runs-on: ${{ ((github.repository == 'commaai/openpilot') && + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: submodules: true - uses: ./.github/workflows/setup-with-retry + with: + docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} - name: Build openpilot timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache run: ${{ env.RUN }} "scons -j$(nproc)" diff --git a/selfdrive/test/process_replay/test_fuzzy.py b/selfdrive/test/process_replay/test_fuzzy.py index 4b8629fc7d..adff06f88a 100755 --- a/selfdrive/test/process_replay/test_fuzzy.py +++ b/selfdrive/test/process_replay/test_fuzzy.py @@ -14,13 +14,15 @@ import openpilot.selfdrive.test.process_replay.process_replay as pr # that openpilot makes causing error with NaN, inf, int size, array indexing ... # TODO: Make each one testable NOT_TESTED = ['controlsd', 'plannerd', 'calibrationd', 'dmonitoringd', 'paramsd', 'dmonitoringmodeld', 'modeld'] + TEST_CASES = [(cfg.proc_name, copy.deepcopy(cfg)) for cfg in pr.CONFIGS if cfg.proc_name not in NOT_TESTED] class TestFuzzProcesses(unittest.TestCase): + # TODO: make this faster and increase examples @parameterized.expand(TEST_CASES) @given(st.data()) - @settings(phases=[Phase.generate, Phase.target], max_examples=50, deadline=1000, suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large]) + @settings(phases=[Phase.generate, Phase.target], max_examples=10, deadline=1000, suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large]) def test_fuzz_process(self, proc_name, cfg, data): msgs = FuzzyGenerator.get_random_event_msg(data.draw, events=cfg.pubs, real_floats=True) lr = [log.Event.new_message(**m).as_reader() for m in msgs] diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index db207a6522..10bb7d9737 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -69,12 +69,14 @@ class TestLogReader(unittest.TestCase): sr = SegmentRange(segment_range) parse_slice(sr) + @unittest.skip("this test is too slow for the minimal coverage it provides") def test_modes(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.QLOG))) rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.RLOG))) self.assertLess(qlog_len * 6, rlog_len) + @unittest.skip("this test is too slow for the minimal coverage it provides") def test_modes_from_name(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/q"))) rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/r"))) diff --git a/tools/sim/tests/test_metadrive_bridge.py b/tools/sim/tests/test_metadrive_bridge.py index 2c534656bb..4d784956d2 100755 --- a/tools/sim/tests/test_metadrive_bridge.py +++ b/tools/sim/tests/test_metadrive_bridge.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 +import pytest import unittest from openpilot.tools.sim.run_bridge import parse_args from openpilot.tools.sim.bridge.metadrive.metadrive_bridge import MetaDriveBridge from openpilot.tools.sim.tests.test_sim_bridge import TestSimBridgeBase - +@pytest.mark.slow class TestMetaDriveBridge(TestSimBridgeBase): def create_bridge(self): return MetaDriveBridge(parse_args([])) From fe6aff01311ca3c5a4a549b0eb5d081fa1740f86 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 13 Jan 2024 22:21:43 -0800 Subject: [PATCH 454/724] more unit test speedup (#30997) * that's super slow * fix startup test * fix --- selfdrive/controls/tests/test_startup.py | 27 ++++++++++++------------ tools/replay/tests/test_replay.cc | 6 ++---- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py index 6eb803e8aa..a93152e84e 100755 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -import time +import os import unittest from parameterized import parameterized @@ -11,7 +11,7 @@ from openpilot.selfdrive.car.fingerprints import _FINGERPRINTS from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA from openpilot.selfdrive.car.mazda.values import CAR as MAZDA from openpilot.selfdrive.controls.lib.events import EVENT_NAME -from openpilot.selfdrive.test.helpers import with_processes +from openpilot.selfdrive.manager.process_config import managed_processes EventName = car.CarEvent.EventName Ecu = car.CarParams.Ecu @@ -38,6 +38,9 @@ CX5_FW_VERSIONS = [ class TestStartup(unittest.TestCase): + def tearDown(self): + managed_processes['controlsd'].stop() + @parameterized.expand([ # TODO: test EventName.startup for release branches @@ -61,15 +64,11 @@ class TestStartup(unittest.TestCase): (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), ]) - @with_processes(['controlsd']) def test_startup_alert(self, expected_event, car_model, fw_versions, brand): - - # TODO: this should be done without any real sockets controls_sock = messaging.sub_sock("controlsState") pm = messaging.PubMaster(['can', 'pandaStates']) params = Params() - params.clear_all() params.put_bool("Passive", False) params.put_bool("OpenpilotEnabledToggle", True) @@ -91,11 +90,13 @@ class TestStartup(unittest.TestCase): cp.carVin = "1" * 17 cp.carFw = car_fw params.put("CarParamsCache", cp.to_bytes()) + else: + os.environ['SKIP_FW_QUERY'] = '1' - time.sleep(2) # wait for controlsd to be ready + managed_processes['controlsd'].start() + assert pm.wait_for_readers_to_update('can', 5) pm.send('can', can_list_to_can_capnp([[0, 0, b"", 0]])) - time.sleep(0.1) msg = messaging.new_message('pandaStates', 1) msg.pandaStates[0].pandaType = log.PandaState.PandaType.uno @@ -107,18 +108,18 @@ class TestStartup(unittest.TestCase): else: finger = _FINGERPRINTS[car_model][0] + msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()] for _ in range(1000): # controlsd waits for boardd to echo back that it has changed the multiplexing mode if not params.get_bool("ObdMultiplexingChanged"): params.put_bool("ObdMultiplexingChanged", True) - msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()] pm.send('can', can_list_to_can_capnp(msgs)) + assert pm.wait_for_readers_to_update('can', 2, dt=0.001) - time.sleep(0.01) - msgs = messaging.drain_sock(controls_sock) - if len(msgs): - event_name = msgs[0].controlsState.alertType.split("/")[0] + ctrls = messaging.drain_sock(controls_sock) + if len(ctrls): + event_name = ctrls[0].controlsState.alertType.split("/")[0] self.assertEqual(EVENT_NAME[expected_event], event_name, f"expected {EVENT_NAME[expected_event]} for '{car_model}', got {event_name}") break diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index 1873daaf4b..a01e0c2c5b 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -126,8 +126,6 @@ std::string download_demo_route() { std::string log_path = util::string_format("%s/%s--%d/", data_dir.c_str(), route_name.c_str(), i); util::create_directories(log_path, 0755); REQUIRE(download_to_file(remote_route.at(i).rlog.toStdString(), log_path + "rlog.bz2")); - REQUIRE(download_to_file(remote_route.at(i).driver_cam.toStdString(), log_path + "dcamera.hevc")); - REQUIRE(download_to_file(remote_route.at(i).wide_road_cam.toStdString(), log_path + "ecamera.hevc")); REQUIRE(download_to_file(remote_route.at(i).qcamera.toStdString(), log_path + "qcamera.ts")); } } @@ -139,7 +137,7 @@ std::string download_demo_route() { TEST_CASE("Local route") { std::string data_dir = download_demo_route(); - auto flags = GENERATE(REPLAY_FLAG_DCAM | REPLAY_FLAG_ECAM, REPLAY_FLAG_QCAMERA); + auto flags = GENERATE(0, REPLAY_FLAG_QCAMERA); Route route(DEMO_ROUTE, QString::fromStdString(data_dir)); REQUIRE(route.load()); REQUIRE(route.segments().size() == 2); @@ -149,7 +147,7 @@ TEST_CASE("Local route") { } TEST_CASE("Remote route") { - auto flags = GENERATE(REPLAY_FLAG_DCAM | REPLAY_FLAG_ECAM, REPLAY_FLAG_QCAMERA); + auto flags = GENERATE(0, REPLAY_FLAG_QCAMERA); Route route(DEMO_ROUTE); REQUIRE(route.load()); REQUIRE(route.segments().size() == 13); From 4c6f7da4049869d3dd1fe66a13f1aba7362c9b8c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sun, 14 Jan 2024 22:55:15 -0500 Subject: [PATCH 455/724] Subaru: remove incorrect fingerprints from other brands queries (#30984) s --- selfdrive/car/subaru/fingerprints.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py index 73bab9ecac..25fc2a0aa9 100644 --- a/selfdrive/car/subaru/fingerprints.py +++ b/selfdrive/car/subaru/fingerprints.py @@ -8,7 +8,6 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'\xa5 \x19\x02\x00', b'\xa5 !\x02\x00', - b'\xf1\x82\xa5 \x19\x02\x00', ], (Ecu.eps, 0x746, None): [ b'\x05\xc0\xd0\x00', @@ -25,10 +24,6 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xbb,\xa0t\x07', b'\xd1,\xa0q\x07', - b'\xf1\x82\xbb,\xa0t\x07', - b'\xf1\x82\xbb,\xa0t\x87', - b'\xf1\x82\xd1,\xa0q\x07', - b'\xf1\x82\xd9,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\x00\xfe\xf7\x00\x00', @@ -96,7 +91,6 @@ FW_VERSIONS = { b'\xa2 \x193\x00', b'\xa2 \x194\x00', b'\xa2 \x19`\x00', - b'\xf1\x00\xb2\x06\x04', ], (Ecu.eps, 0x746, None): [ b'z\xc0\x00\x00', @@ -170,7 +164,6 @@ FW_VERSIONS = { b'\xa2 !3\x00', b'\xa2 !`\x00', b'\xa2 !i\x00', - b'\xf1\x00\xb2\x06\x04', ], (Ecu.eps, 0x746, None): [ b'\n\xc0\x04\x00', @@ -214,7 +207,6 @@ FW_VERSIONS = { b'\xe9\xf5B0\x00', b'\xe9\xf6B0\x00', b'\xe9\xf6F0\x00', - b'\xf1\x00\xd7\x10@', ], }, CAR.CROSSTREK_HYBRID: { @@ -243,7 +235,6 @@ FW_VERSIONS = { b'\xa3 \x19&\x00', b'\xa3 \x14\x00', b'\xa3 \x14\x01', - b'\xf1\x00\xbb\r\x05', ], (Ecu.eps, 0x746, None): [ b'\x8d\xc0\x00\x00', @@ -257,7 +248,6 @@ FW_VERSIONS = { b'\x00\x00e`\x1f@ ', b'\x00\x00e\x97\x00\x00\x00\x00', b'\x00\x00e\x97\x1f@ 0', - b'\xf1\x00\xac\x02\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xb6"`A\x07', @@ -266,7 +256,6 @@ FW_VERSIONS = { b'\xcb"`p\x07', b'\xcf"`0\x07', b'\xcf"`p\x07', - b'\xf1\x00\xa2\x10\n', ], (Ecu.transmission, 0x7e1, None): [ b'\x1a\xe6B1\x00', @@ -299,7 +288,6 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'm\x97\x14@', b'}\x97\x14@', - b'\xf1\x00\xbb\x0c\x04', ], (Ecu.eps, 0x746, None): [ b'm\xc0\x10\x00', @@ -316,7 +304,6 @@ FW_VERSIONS = { b'\xa7)\xa0q\x07', b'\xba"@@\x07', b'\xba"@p\x07', - b'\xf1\x82\xa7)\xa0q\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\x1a\xf6F`\x00', @@ -387,7 +374,6 @@ FW_VERSIONS = { b'\x00\x00c\xd1\x1f@\x10\x17', b'\x00\x00c\xec\x1f@ \x04', b'\x00\x00c\xec7@\x04', - b'\xf1\x00\xf0\xe0\x0e', ], (Ecu.engine, 0x7e0, None): [ b'\xa0"@\x80\x07', @@ -494,9 +480,6 @@ FW_VERSIONS = { b'\xe2"`0\x07', b'\xe2"`p\x07', b'\xe3,\xa0@\x07', - b'\xf1\x82\xbc,\xa0q\x07', - b'\xf1\x82\xe2,\xa0@\x07', - b'\xf1\x82\xe3,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xa5\xf6D@\x00', @@ -506,7 +489,6 @@ FW_VERSIONS = { b'\xa7\x8e\xf40\x00', b'\xa7\xf6D@\x00', b'\xa7\xfe\xf4@\x00', - b'\xf1\x82\xa7\xf6D@\x00', ], }, CAR.FORESTER_2022: { From f256225f294f5312ed425c7022300787c60f1d66 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 15 Jan 2024 04:58:43 -0800 Subject: [PATCH 456/724] docs: include dashcam (#31004) include dashcam --- selfdrive/car/docs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index 8475a69d8a..5142b6fc15 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -25,7 +25,7 @@ CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md") CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md") -def get_all_car_info() -> List[CarInfo]: +def get_all_car_info(include_dashcam_only: bool = False) -> List[CarInfo]: all_car_info: List[CarInfo] = [] footnotes = get_all_footnotes() for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items(): @@ -33,7 +33,7 @@ def get_all_car_info() -> List[CarInfo]: CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), car_fw=[car.CarParams.CarFw(ecu="unknown")], experimental_long=True, docs=True) - if CP.dashcamOnly or car_info is None: + if (CP.dashcamOnly and not include_dashcam_only) or car_info is None: continue # A platform can include multiple car models From a4fac38cf9ce34dc498f2622ed44172ccc5318ec Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 15 Jan 2024 10:34:08 -0800 Subject: [PATCH 457/724] Bump submodules (#31002) bump submodules Co-authored-by: jnewb1 --- body | 2 +- panda | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/body b/body index 3aa61382b7..61ace31efa 160000 --- a/body +++ b/body @@ -1 +1 @@ -Subproject commit 3aa61382b7ea9328cab7f1a2fe1ec701dffd018f +Subproject commit 61ace31efad27ae0d6d86888842f82bc92545e72 diff --git a/panda b/panda index 2a0536c631..d66161966d 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 2a0536c63148a02add52555386b5533f3555ef58 +Subproject commit d66161966d8468223b645c8eba1324e9a49de916 From a8afaf39d4d8c1b1b2b4070441d05aed14f982f5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 15 Jan 2024 10:34:25 -0800 Subject: [PATCH 458/724] Update Python packages and pre-commit hooks (#31003) * Update Python packages and pre-commit hooks * fix --------- Co-authored-by: jnewb1 --- .pre-commit-config.yaml | 2 +- common/logging_extra.py | 4 +- poetry.lock | 411 +++++++++++++++++++----------------- selfdrive/athena/athenad.py | 2 +- 4 files changed, 220 insertions(+), 199 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a92b9d38e..4bae3294ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: - --explicit-package-bases exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.11 + rev: v0.1.13 hooks: - id: ruff exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' diff --git a/common/logging_extra.py b/common/logging_extra.py index 5e0584c7bc..f53d503108 100644 --- a/common/logging_extra.py +++ b/common/logging_extra.py @@ -65,7 +65,7 @@ class SwagFormatter(logging.Formatter): return record_dict - def format(self, record): # noqa: A003 + def format(self, record): if self.swaglogger is None: raise Exception("must set swaglogger before calling format()") return json_robust_dumps(self.format_dict(record)) @@ -95,7 +95,7 @@ class SwagLogFileFormatter(SwagFormatter): k += "$a" return k, v - def format(self, record): # noqa: A003 + def format(self, record): if isinstance(record, str): v = json.loads(record) else: diff --git a/poetry.lock b/poetry.lock index 597ed19b28..e58fe0f128 100644 --- a/poetry.lock +++ b/poetry.lock @@ -171,13 +171,13 @@ frozenlist = ">=1.1.0" [[package]] name = "alabaster" -version = "0.7.15" +version = "0.7.16" description = "A light, configurable Sphinx theme" optional = false python-versions = ">=3.9" files = [ - {file = "alabaster-0.7.15-py3-none-any.whl", hash = "sha256:d99c6fd0f7a86fca68ecc5231c9de45227991c10ee6facfb894cf6afb953b142"}, - {file = "alabaster-0.7.15.tar.gz", hash = "sha256:0127f4b1db0afc914883f930e3d40763131aebac295522fc4a04d9e77c703705"}, + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] [[package]] @@ -906,62 +906,69 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "cython" -version = "3.0.7" +version = "3.0.8" description = "The Cython compiler for writing C extensions in the Python language." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Cython-3.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3c0e19bb41de6be9d8afc85795159ca16296be81a586cd9588be0400d44a855"}, - {file = "Cython-3.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e8bf00ec1dd1d92e9ae74d2e6891f087a939e1dfb40c9c7fa5d8d6a26c94f5a"}, - {file = "Cython-3.0.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd6ae43ef2e596c9a88dbf2a8895be2e32cc2f5bc3c8ba2e7753b69068fc0b2d"}, - {file = "Cython-3.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f674be92673e87dd8ee7cfe553d5960ec4effc5ab15063b9a5e265a51585a31a"}, - {file = "Cython-3.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:861cf254bf5836d47c2aee86aa75dd93d3de00ccd1b077c3c7a2bb22cba358e7"}, - {file = "Cython-3.0.7-cp310-cp310-win32.whl", hash = "sha256:f6d8ff62ad55dc0393686438eac4b457a916e4d1118a0b550746bb52b4c756cc"}, - {file = "Cython-3.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:e13abb14843397b76d0472c7d33cd260d5f262ab05cc27ed423317e645e29643"}, - {file = "Cython-3.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c636c9ab92c7838231a1ba769e519d953af8294612f3f772a54d3a5250ff23f"}, - {file = "Cython-3.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d2a684122dfb531853d57c8c85c1d5d44be709e12466dca99fa6aee7d8054f"}, - {file = "Cython-3.0.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1bdf8a107fdf9e174991aa87a0be7504f60de1ec6bfb1ccfb30e33acac818a0"}, - {file = "Cython-3.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3a83e04fde663b84905f3a20213a4333d13a07b79434300704b70dc552761f8b"}, - {file = "Cython-3.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e34b4b08d795ccca920fa26b099558f4f1e4e3f794e4ba8d3433c5bc2454d50a"}, - {file = "Cython-3.0.7-cp311-cp311-win32.whl", hash = "sha256:133057ac45b6fa7fe5d7baada9d3545d09339432f75c0545f556e8c6fecc2932"}, - {file = "Cython-3.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:b65abca78aa5ebc8675c8480b9a53006f6efea9910ad099cf32c9fb5617ef251"}, - {file = "Cython-3.0.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ceac5315fe899c229e874328742154e331fa41337bb03f6f5264636c351c9e"}, - {file = "Cython-3.0.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea936cf5931297ba07bce121388c4c6266c1b63a9f4d648ae16c92ff090204b"}, - {file = "Cython-3.0.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fcd9a18ee3ac7f460e0841954feb495102ffbdbec0e6c78562f3495cda000dd"}, - {file = "Cython-3.0.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7c8d579d13cb81abe704c8b0908d122b81d6e2623265a19c4a6a7377f440debb"}, - {file = "Cython-3.0.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ef5bb0268bfe5992da3ef9292463a5a895ed8700b134ed2c00008d5471b3ba6e"}, - {file = "Cython-3.0.7-cp312-cp312-win32.whl", hash = "sha256:55f93d3822bc196b37a8bdfa4ec6a35232a399e97f2baa714bd5ed8ea9b0ce68"}, - {file = "Cython-3.0.7-cp312-cp312-win_amd64.whl", hash = "sha256:f3845c4506e0d207c5e268fb02813928f3a1e135de954a379f165ef0d581da47"}, - {file = "Cython-3.0.7-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ad7c2303a338b2c0b6c6c68f101a6768725934538756096cf3388a5c07a7525"}, - {file = "Cython-3.0.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed25959e4025870fdde5f895fcb126196d22affd4f4fad85a2823e0dddc85b0"}, - {file = "Cython-3.0.7-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79868ec74e4907a8a6e63effe13547c6157f196a162920b1de066da5849ffb8e"}, - {file = "Cython-3.0.7-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5e3a038332973b12e72236e8884dc99601a840334c2c46cfbbb5851cb94166eb"}, - {file = "Cython-3.0.7-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f2602a5c97a3d618b3b847514204ef3349fb414c59e1126c0c2c708d2c5680f8"}, - {file = "Cython-3.0.7-cp36-cp36m-win32.whl", hash = "sha256:539ad5a21141e6420035cf616bcba48d999bf878839e52692f97fc7e2f16265c"}, - {file = "Cython-3.0.7-cp36-cp36m-win_amd64.whl", hash = "sha256:848a28ea49166454c3bff927e5a47629eecf1aa755d6fb3290569cba0fc93766"}, - {file = "Cython-3.0.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82f27a0134fc6bb46032ca5f728d8af984f3be94a3cb01cb70ff1224e551b9cf"}, - {file = "Cython-3.0.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f20c61114c7948cf1214585066406cef4b54a9b935160980e0b6e70ada3a69"}, - {file = "Cython-3.0.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d51709e10ad6213b4bf094af7be7ff82bab43216b3c92a07d05b451deeca79"}, - {file = "Cython-3.0.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3f02c7240abab48d59f0d5fef7064f18f01a2a204616165fa6367a8abf5a8832"}, - {file = "Cython-3.0.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:225f8bba6428b8d711ca2d6c738d2e3a4667f6a2ae40f8a7a5256f69f6a3600e"}, - {file = "Cython-3.0.7-cp37-cp37m-win32.whl", hash = "sha256:30eb2d2938b9195e2c82951713429aff3ad1be9f104437d1536a04eb0cb3dc0e"}, - {file = "Cython-3.0.7-cp37-cp37m-win_amd64.whl", hash = "sha256:167b3f3894dcc697cefefac1d198304fae8eb4d5860a7b8bc2459d572e838470"}, - {file = "Cython-3.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c67105f2c6ccf5b3adbcfaecf3c5c9fa8940f9f97955c9ad7d2542151d97d93"}, - {file = "Cython-3.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a1859af761977530df2cd5c36e31d54e8d6708ad2c4656e7125c482364dc216"}, - {file = "Cython-3.0.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01b94304aab87496e81d1f546e71abf57b430b39be4269df1cd7da9928d70b5b"}, - {file = "Cython-3.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:931aade65f77cf59f2a702ac1f549a4836ce221107c740502cbad18d6d8e9511"}, - {file = "Cython-3.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:812b193c26553f1f375d4f1c50f805c227b24ed2d595bc9cdaf78c992ecc64a4"}, - {file = "Cython-3.0.7-cp38-cp38-win32.whl", hash = "sha256:b227643d8a40b68554dc7d37fcd03fc97b4fb0bd2614aeb5f2e07ab244642d36"}, - {file = "Cython-3.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:0d8a98c7d86ac4d05b251c39faf49423780381aab55fbf2e147f6e006a34a58a"}, - {file = "Cython-3.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:816f5285d596062c7ef22790de7d75354b58d4417a9fc64cba914aeeb900db0b"}, - {file = "Cython-3.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9d0dae6dccd349b8ccf197c10ef2d05c711ca36a649c7eddbab1de2c90b63a1"}, - {file = "Cython-3.0.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13211b67b29f6ed8e87c137496c73d93aff0330d97940b4fbed72eae37a4a2a0"}, - {file = "Cython-3.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b1853bc34ced5ff6473e881fcf6de29da83262552c8f268a0df53b49c2b89e2c"}, - {file = "Cython-3.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:51e8164b1270625ff101e95c3c1c234421520c07a0a3a20ded9e9431d98afce7"}, - {file = "Cython-3.0.7-cp39-cp39-win32.whl", hash = "sha256:45319d2471f4dbf19893ca53785a421107266e18b8cccd2054fce1e3f72a85f1"}, - {file = "Cython-3.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:612d83fd1eb5aaa5401a755c1f1aafacd9dab404cd350b90d5f404c98b33e4b3"}, - {file = "Cython-3.0.7-py2.py3-none-any.whl", hash = "sha256:936ec37b261b226d7404eff23a9aad284098338150d42a53d6a9af12b18d3892"}, - {file = "Cython-3.0.7.tar.gz", hash = "sha256:fb299acf3a578573c190c858d49e0cf9d75f4bc49c3f24c5a63804997ef09213"}, + {file = "Cython-3.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a846e0a38e2b24e9a5c5dc74b0e54c6e29420d88d1dafabc99e0fc0f3e338636"}, + {file = "Cython-3.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45523fdc2b78d79b32834cc1cc12dc2ca8967af87e22a3ee1bff20e77c7f5520"}, + {file = "Cython-3.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa0b7f3f841fe087410cab66778e2d3fb20ae2d2078a2be3dffe66c6574be39"}, + {file = "Cython-3.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87294e33e40c289c77a135f491cd721bd089f193f956f7b8ed5aa2d0b8c558f"}, + {file = "Cython-3.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a1df7a129344b1215c20096d33c00193437df1a8fcca25b71f17c23b1a44f782"}, + {file = "Cython-3.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:13c2a5e57a0358da467d97667297bf820b62a1a87ae47c5f87938b9bb593acbd"}, + {file = "Cython-3.0.8-cp310-cp310-win32.whl", hash = "sha256:96b028f044f5880e3cb18ecdcfc6c8d3ce9d0af28418d5ab464509f26d8adf12"}, + {file = "Cython-3.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:8140597a8b5cc4f119a1190f5a2228a84f5ca6d8d9ec386cfce24663f48b2539"}, + {file = "Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba"}, + {file = "Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3"}, + {file = "Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee"}, + {file = "Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1"}, + {file = "Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b"}, + {file = "Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795"}, + {file = "Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871"}, + {file = "Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02"}, + {file = "Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f"}, + {file = "Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419"}, + {file = "Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6"}, + {file = "Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717"}, + {file = "Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658"}, + {file = "Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388"}, + {file = "Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c"}, + {file = "Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a"}, + {file = "Cython-3.0.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:870d2a0a7e3cbd5efa65aecdb38d715ea337a904ea7bb22324036e78fb7068e7"}, + {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7e8f2454128974905258d86534f4fd4f91d2f1343605657ecab779d80c9d6d5e"}, + {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1949d6aa7bc792554bee2b67a9fe41008acbfe22f4f8df7b6ec7b799613a4b3"}, + {file = "Cython-3.0.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9f2c6e1b8f3bcd6cb230bac1843f85114780bb8be8614855b1628b36bb510e0"}, + {file = "Cython-3.0.8-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:05d7eddc668ae7993643f32c7661f25544e791edb745758672ea5b1a82ecffa6"}, + {file = "Cython-3.0.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bfabe115deef4ada5d23c87bddb11289123336dcc14347011832c07db616dd93"}, + {file = "Cython-3.0.8-cp36-cp36m-win32.whl", hash = "sha256:0c38c9f0bcce2df0c3347285863621be904ac6b64c5792d871130569d893efd7"}, + {file = "Cython-3.0.8-cp36-cp36m-win_amd64.whl", hash = "sha256:6c46939c3983217d140999de7c238c3141f56b1ea349e47ca49cae899969aa2c"}, + {file = "Cython-3.0.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:115f0a50f752da6c99941b103b5cb090da63eb206abbc7c2ad33856ffc73f064"}, + {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c0f29246734561c90f36e70ed0506b61aa3d044e4cc4cba559065a2a741fae"}, + {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ab75242869ff71e5665fe5c96f3378e79e792fa3c11762641b6c5afbbbbe026"}, + {file = "Cython-3.0.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6717c06e9cfc6c1df18543cd31a21f5d8e378a40f70c851fa2d34f0597037abc"}, + {file = "Cython-3.0.8-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9d3f74388db378a3c6fd06e79a809ed98df3f56484d317b81ee762dbf3c263e0"}, + {file = "Cython-3.0.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae7ac561fd8253a9ae96311e91d12af5f701383564edc11d6338a7b60b285a6f"}, + {file = "Cython-3.0.8-cp37-cp37m-win32.whl", hash = "sha256:97b2a45845b993304f1799664fa88da676ee19442b15fdcaa31f9da7e1acc434"}, + {file = "Cython-3.0.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9e2be2b340fea46fb849d378f9b80d3c08ff2e81e2bfbcdb656e2e3cd8c6b2dc"}, + {file = "Cython-3.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2cde23c555470db3f149ede78b518e8274853745289c956a0e06ad8d982e4db9"}, + {file = "Cython-3.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7990ca127e1f1beedaf8fc8bf66541d066ef4723ad7d8d47a7cbf842e0f47580"}, + {file = "Cython-3.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b983c8e6803f016146c26854d9150ddad5662960c804ea7f0c752c9266752f0"}, + {file = "Cython-3.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a973268d7ca1a2bdf78575e459a94a78e1a0a9bb62a7db0c50041949a73b02ff"}, + {file = "Cython-3.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:61a237bc9dd23c7faef0fcfce88c11c65d0c9bb73c74ccfa408b3a012073c20e"}, + {file = "Cython-3.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3a3d67f079598af49e90ff9655bf85bd358f093d727eb21ca2708f467c489cae"}, + {file = "Cython-3.0.8-cp38-cp38-win32.whl", hash = "sha256:17a642bb01a693e34c914106566f59844b4461665066613913463a719e0dd15d"}, + {file = "Cython-3.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:2cdfc32252f3b6dc7c94032ab744dcedb45286733443c294d8f909a4854e7f83"}, + {file = "Cython-3.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa97893d99385386925d00074654aeae3a98867f298d1e12ceaf38a9054a9bae"}, + {file = "Cython-3.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05c0bf9d085c031df8f583f0d506aa3be1692023de18c45d0aaf78685bbb944"}, + {file = "Cython-3.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de892422582f5758bd8de187e98ac829330ec1007bc42c661f687792999988a7"}, + {file = "Cython-3.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:314f2355a1f1d06e3c431eaad4708cf10037b5e91e4b231d89c913989d0bdafd"}, + {file = "Cython-3.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:78825a3774211e7d5089730f00cdf7f473042acc9ceb8b9eeebe13ed3a5541de"}, + {file = "Cython-3.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:df8093deabc55f37028190cf5e575c26aad23fc673f34b85d5f45076bc37ce39"}, + {file = "Cython-3.0.8-cp39-cp39-win32.whl", hash = "sha256:1aca1b97e0095b3a9a6c33eada3f661a4ed0d499067d121239b193e5ba3bb4f0"}, + {file = "Cython-3.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:16873d78be63bd38ffb759da7ab82814b36f56c769ee02b1d5859560e4c3ac3c"}, + {file = "Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6"}, + {file = "Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6"}, ] [[package]] @@ -1124,53 +1131,53 @@ files = [ [[package]] name = "fonttools" -version = "4.47.0" +version = "4.47.2" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2d2404107626f97a221dc1a65b05396d2bb2ce38e435f64f26ed2369f68675d9"}, - {file = "fonttools-4.47.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01f409be619a9a0f5590389e37ccb58b47264939f0e8d58bfa1f3ba07d22671"}, - {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d986b66ff722ef675b7ee22fbe5947a41f60a61a4da15579d5e276d897fbc7fa"}, - {file = "fonttools-4.47.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8acf6dd0434b211b3bd30d572d9e019831aae17a54016629fa8224783b22df8"}, - {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:495369c660e0c27233e3c572269cbe520f7f4978be675f990f4005937337d391"}, - {file = "fonttools-4.47.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c59227d7ba5b232281c26ae04fac2c73a79ad0e236bca5c44aae904a18f14faf"}, - {file = "fonttools-4.47.0-cp310-cp310-win32.whl", hash = "sha256:59a6c8b71a245800e923cb684a2dc0eac19c56493e2f896218fcf2571ed28984"}, - {file = "fonttools-4.47.0-cp310-cp310-win_amd64.whl", hash = "sha256:52c82df66201f3a90db438d9d7b337c7c98139de598d0728fb99dab9fd0495ca"}, - {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:854421e328d47d70aa5abceacbe8eef231961b162c71cbe7ff3f47e235e2e5c5"}, - {file = "fonttools-4.47.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:511482df31cfea9f697930f61520f6541185fa5eeba2fa760fe72e8eee5af88b"}, - {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0e2c88c8c985b7b9a7efcd06511fb0a1fe3ddd9a6cd2895ef1dbf9059719d7"}, - {file = "fonttools-4.47.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7a0a8848726956e9d9fb18c977a279013daadf0cbb6725d2015a6dd57527992"}, - {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e869da810ae35afb3019baa0d0306cdbab4760a54909c89ad8904fa629991812"}, - {file = "fonttools-4.47.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dd23848f877c3754f53a4903fb7a593ed100924f9b4bff7d5a4e2e8a7001ae11"}, - {file = "fonttools-4.47.0-cp311-cp311-win32.whl", hash = "sha256:bf1810635c00f7c45d93085611c995fc130009cec5abdc35b327156aa191f982"}, - {file = "fonttools-4.47.0-cp311-cp311-win_amd64.whl", hash = "sha256:61df4dee5d38ab65b26da8efd62d859a1eef7a34dcbc331299a28e24d04c59a7"}, - {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e3f4d61f3a8195eac784f1d0c16c0a3105382c1b9a74d99ac4ba421da39a8826"}, - {file = "fonttools-4.47.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:174995f7b057e799355b393e97f4f93ef1f2197cbfa945e988d49b2a09ecbce8"}, - {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea592e6a09b71cb7a7661dd93ac0b877a6228e2d677ebacbad0a4d118494c86d"}, - {file = "fonttools-4.47.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40bdbe90b33897d9cc4a39f8e415b0fcdeae4c40a99374b8a4982f127ff5c767"}, - {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:843509ae9b93db5aaf1a6302085e30bddc1111d31e11d724584818f5b698f500"}, - {file = "fonttools-4.47.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9acfa1cdc479e0dde528b61423855913d949a7f7fe09e276228298fef4589540"}, - {file = "fonttools-4.47.0-cp312-cp312-win32.whl", hash = "sha256:66c92ec7f95fd9732550ebedefcd190a8d81beaa97e89d523a0d17198a8bda4d"}, - {file = "fonttools-4.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8fa20748de55d0021f83754b371432dca0439e02847962fc4c42a0e444c2d78"}, - {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c75e19971209fbbce891ebfd1b10c37320a5a28e8d438861c21d35305aedb81c"}, - {file = "fonttools-4.47.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e79f1a3970d25f692bbb8c8c2637e621a66c0d60c109ab48d4a160f50856deff"}, - {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:562681188c62c024fe2c611b32e08b8de2afa00c0c4e72bed47c47c318e16d5c"}, - {file = "fonttools-4.47.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a77a60315c33393b2bd29d538d1ef026060a63d3a49a9233b779261bad9c3f71"}, - {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4fabb8cc9422efae1a925160083fdcbab8fdc96a8483441eb7457235df625bd"}, - {file = "fonttools-4.47.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2a78dba8c2a1e9d53a0fb5382979f024200dc86adc46a56cbb668a2249862fda"}, - {file = "fonttools-4.47.0-cp38-cp38-win32.whl", hash = "sha256:e6b968543fde4119231c12c2a953dcf83349590ca631ba8216a8edf9cd4d36a9"}, - {file = "fonttools-4.47.0-cp38-cp38-win_amd64.whl", hash = "sha256:4a9a51745c0439516d947480d4d884fa18bd1458e05b829e482b9269afa655bc"}, - {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:62d8ddb058b8e87018e5dc26f3258e2c30daad4c87262dfeb0e2617dd84750e6"}, - {file = "fonttools-4.47.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5dde0eab40faaa5476133123f6a622a1cc3ac9b7af45d65690870620323308b4"}, - {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4da089f6dfdb822293bde576916492cd708c37c2501c3651adde39804630538"}, - {file = "fonttools-4.47.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:253bb46bab970e8aae254cebf2ae3db98a4ef6bd034707aa68a239027d2b198d"}, - {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1193fb090061efa2f9e2d8d743ae9850c77b66746a3b32792324cdce65784154"}, - {file = "fonttools-4.47.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:084511482dd265bce6dca24c509894062f0117e4e6869384d853f46c0e6d43be"}, - {file = "fonttools-4.47.0-cp39-cp39-win32.whl", hash = "sha256:97620c4af36e4c849e52661492e31dc36916df12571cb900d16960ab8e92a980"}, - {file = "fonttools-4.47.0-cp39-cp39-win_amd64.whl", hash = "sha256:e77bdf52185bdaf63d39f3e1ac3212e6cfa3ab07d509b94557a8902ce9c13c82"}, - {file = "fonttools-4.47.0-py3-none-any.whl", hash = "sha256:d6477ba902dd2d7adda7f0fd3bfaeb92885d45993c9e1928c9f28fc3961415f7"}, - {file = "fonttools-4.47.0.tar.gz", hash = "sha256:ec13a10715eef0e031858c1c23bfaee6cba02b97558e4a7bfa089dba4a8c2ebf"}, + {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df"}, + {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1"}, + {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c"}, + {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8"}, + {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670"}, + {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c"}, + {file = "fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0"}, + {file = "fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1"}, + {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b"}, + {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac"}, + {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c"}, + {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70"}, + {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e"}, + {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703"}, + {file = "fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c"}, + {file = "fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9"}, + {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635"}, + {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d"}, + {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb"}, + {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07"}, + {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71"}, + {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f"}, + {file = "fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085"}, + {file = "fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4"}, + {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:254d9a6f7be00212bf0c3159e0a420eb19c63793b2c05e049eb337f3023c5ecc"}, + {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eabae77a07c41ae0b35184894202305c3ad211a93b2eb53837c2a1143c8bc952"}, + {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86a5ab2873ed2575d0fcdf1828143cfc6b977ac448e3dc616bb1e3d20efbafa"}, + {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13819db8445a0cec8c3ff5f243af6418ab19175072a9a92f6cc8ca7d1452754b"}, + {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4e743935139aa485fe3253fc33fe467eab6ea42583fa681223ea3f1a93dd01e6"}, + {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d49ce3ea7b7173faebc5664872243b40cf88814ca3eb135c4a3cdff66af71946"}, + {file = "fonttools-4.47.2-cp38-cp38-win32.whl", hash = "sha256:94208ea750e3f96e267f394d5588579bb64cc628e321dbb1d4243ffbc291b18b"}, + {file = "fonttools-4.47.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f750037e02beb8b3569fbff701a572e62a685d2a0e840d75816592280e5feae"}, + {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d71606c9321f6701642bd4746f99b6089e53d7e9817fc6b964e90d9c5f0ecc6"}, + {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86e0427864c6c91cf77f16d1fb9bf1bbf7453e824589e8fb8461b6ee1144f506"}, + {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a00bd0e68e88987dcc047ea31c26d40a3c61185153b03457956a87e39d43c37"}, + {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d77479fb885ef38a16a253a2f4096bc3d14e63a56d6246bfdb56365a12b20c"}, + {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5465df494f20a7d01712b072ae3ee9ad2887004701b95cb2cc6dcb9c2c97a899"}, + {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4c811d3c73b6abac275babb8aa439206288f56fdb2c6f8835e3d7b70de8937a7"}, + {file = "fonttools-4.47.2-cp39-cp39-win32.whl", hash = "sha256:5b60e3afa9635e3dfd3ace2757039593e3bd3cf128be0ddb7a1ff4ac45fa5a50"}, + {file = "fonttools-4.47.2-cp39-cp39-win_amd64.whl", hash = "sha256:7ee48bd9d6b7e8f66866c9090807e3a4a56cf43ffad48962725a190e0dd774c8"}, + {file = "fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184"}, + {file = "fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3"}, ] [package.extras] @@ -1676,13 +1683,13 @@ testing = ["pytest (==7.1.3)"] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -1928,6 +1935,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li optional = false python-versions = ">=3.6" files = [ + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, @@ -1937,6 +1945,7 @@ files = [ {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, @@ -1946,6 +1955,7 @@ files = [ {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, @@ -1971,8 +1981,8 @@ files = [ {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, - {file = "lxml-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cfbac9f6149174f76df7e08c2e28b19d74aed90cad60383ad8671d3af7d0502f"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, @@ -1980,6 +1990,7 @@ files = [ {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, @@ -2061,6 +2072,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -3058,22 +3079,22 @@ files = [ [[package]] name = "protobuf" -version = "4.25.1" +version = "4.25.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.1-cp310-abi3-win32.whl", hash = "sha256:193f50a6ab78a970c9b4f148e7c750cfde64f59815e86f686c22e26b4fe01ce7"}, - {file = "protobuf-4.25.1-cp310-abi3-win_amd64.whl", hash = "sha256:3497c1af9f2526962f09329fd61a36566305e6c72da2590ae0d7d1322818843b"}, - {file = "protobuf-4.25.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:0bf384e75b92c42830c0a679b0cd4d6e2b36ae0cf3dbb1e1dfdda48a244f4bcd"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:0f881b589ff449bf0b931a711926e9ddaad3b35089cc039ce1af50b21a4ae8cb"}, - {file = "protobuf-4.25.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:ca37bf6a6d0046272c152eea90d2e4ef34593aaa32e8873fc14c16440f22d4b7"}, - {file = "protobuf-4.25.1-cp38-cp38-win32.whl", hash = "sha256:abc0525ae2689a8000837729eef7883b9391cd6aa7950249dcf5a4ede230d5dd"}, - {file = "protobuf-4.25.1-cp38-cp38-win_amd64.whl", hash = "sha256:1484f9e692091450e7edf418c939e15bfc8fc68856e36ce399aed6889dae8bb0"}, - {file = "protobuf-4.25.1-cp39-cp39-win32.whl", hash = "sha256:8bdbeaddaac52d15c6dce38c71b03038ef7772b977847eb6d374fc86636fa510"}, - {file = "protobuf-4.25.1-cp39-cp39-win_amd64.whl", hash = "sha256:becc576b7e6b553d22cbdf418686ee4daa443d7217999125c045ad56322dda10"}, - {file = "protobuf-4.25.1-py3-none-any.whl", hash = "sha256:a19731d5e83ae4737bb2a089605e636077ac001d18781b3cf489b9546c7c80d6"}, - {file = "protobuf-4.25.1.tar.gz", hash = "sha256:57d65074b4f5baa4ab5da1605c02be90ac20c8b40fb137d6a8df9f416b0d0ce2"}, + {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, + {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, + {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, + {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, + {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, + {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, + {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, + {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, + {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, + {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, ] [[package]] @@ -3180,43 +3201,43 @@ files = [ [[package]] name = "pycryptodome" -version = "3.19.1" +version = "3.20.0" description = "Cryptographic library for Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodome-3.19.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:694020d2ff985cd714381b9da949a21028c24b86f562526186f6af7c7547e986"}, - {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:4464b0e8fd5508bff9baf18e6fd4c6548b1ac2ce9862d6965ff6a84ec9cb302a"}, - {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:420972f9c62978e852c74055d81c354079ce3c3a2213a92c9d7e37bbc63a26e2"}, - {file = "pycryptodome-3.19.1-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1bc0c49d986a1491d66d2a56570f12e960b12508b7e71f2423f532e28857f36"}, - {file = "pycryptodome-3.19.1-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e038ab77fec0956d7aa989a3c647652937fc142ef41c9382c2ebd13c127d5b4a"}, - {file = "pycryptodome-3.19.1-cp27-cp27m-win32.whl", hash = "sha256:a991f8ffe8dfe708f86690948ae46442eebdd0fff07dc1b605987939a34ec979"}, - {file = "pycryptodome-3.19.1-cp27-cp27m-win_amd64.whl", hash = "sha256:2c16426ef49d9cba018be2340ea986837e1dfa25c2ea181787971654dd49aadd"}, - {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6d0d2b97758ebf2f36c39060520447c26455acb3bcff309c28b1c816173a6ff5"}, - {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:b8b80ff92049fd042177282917d994d344365ab7e8ec2bc03e853d93d2401786"}, - {file = "pycryptodome-3.19.1-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd4e7e8bf0fc1ada854688b9b309ee607e2aa85a8b44180f91021a4dd330a928"}, - {file = "pycryptodome-3.19.1-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:8cf5d3d6cf921fa81acd1f632f6cedcc03f5f68fc50c364cd39490ba01d17c49"}, - {file = "pycryptodome-3.19.1-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:67939a3adbe637281c611596e44500ff309d547e932c449337649921b17b6297"}, - {file = "pycryptodome-3.19.1-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:11ddf6c9b52116b62223b6a9f4741bc4f62bb265392a4463282f7f34bb287180"}, - {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3e6f89480616781d2a7f981472d0cdb09b9da9e8196f43c1234eff45c915766"}, - {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e1efcb68993b7ce5d1d047a46a601d41281bba9f1971e6be4aa27c69ab8065"}, - {file = "pycryptodome-3.19.1-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c6273ca5a03b672e504995529b8bae56da0ebb691d8ef141c4aa68f60765700"}, - {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b0bfe61506795877ff974f994397f0c862d037f6f1c0bfc3572195fc00833b96"}, - {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:f34976c5c8eb79e14c7d970fb097482835be8d410a4220f86260695ede4c3e17"}, - {file = "pycryptodome-3.19.1-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7c9e222d0976f68d0cf6409cfea896676ddc1d98485d601e9508f90f60e2b0a2"}, - {file = "pycryptodome-3.19.1-cp35-abi3-win32.whl", hash = "sha256:4805e053571140cb37cf153b5c72cd324bb1e3e837cbe590a19f69b6cf85fd03"}, - {file = "pycryptodome-3.19.1-cp35-abi3-win_amd64.whl", hash = "sha256:a470237ee71a1efd63f9becebc0ad84b88ec28e6784a2047684b693f458f41b7"}, - {file = "pycryptodome-3.19.1-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:ed932eb6c2b1c4391e166e1a562c9d2f020bfff44a0e1b108f67af38b390ea89"}, - {file = "pycryptodome-3.19.1-pp27-pypy_73-win32.whl", hash = "sha256:81e9d23c0316fc1b45d984a44881b220062336bbdc340aa9218e8d0656587934"}, - {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37e531bf896b70fe302f003d3be5a0a8697737a8d177967da7e23eff60d6483c"}, - {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd4e95b0eb4b28251c825fe7aa941fe077f993e5ca9b855665935b86fbb1cc08"}, - {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22c80246c3c880c6950d2a8addf156cee74ec0dc5757d01e8e7067a3c7da015"}, - {file = "pycryptodome-3.19.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e70f5c839c7798743a948efa2a65d1fe96bb397fe6d7f2bde93d869fe4f0ad69"}, - {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6c3df3613592ea6afaec900fd7189d23c8c28b75b550254f4bd33fe94acb84b9"}, - {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08b445799d571041765e7d5c9ca09c5d3866c2f22eeb0dd4394a4169285184f4"}, - {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:954d156cd50130afd53f8d77f830fe6d5801bd23e97a69d358fed068f433fbfe"}, - {file = "pycryptodome-3.19.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b7efd46b0b4ac869046e814d83244aeab14ef787f4850644119b1c8b0ec2d637"}, - {file = "pycryptodome-3.19.1.tar.gz", hash = "sha256:8ae0dd1bcfada451c35f9e29a3e5db385caabc190f98e4a80ad02a61098fb776"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, ] [[package]] @@ -3784,6 +3805,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3791,8 +3813,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3809,6 +3838,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3816,6 +3846,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3949,28 +3980,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.1.11" +version = "0.1.13" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a7f772696b4cdc0a3b2e527fc3c7ccc41cdcb98f5c80fdd4f2b8c50eb1458196"}, - {file = "ruff-0.1.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:934832f6ed9b34a7d5feea58972635c2039c7a3b434fe5ba2ce015064cb6e955"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea0d3e950e394c4b332bcdd112aa566010a9f9c95814844a7468325290aabfd9"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9bd4025b9c5b429a48280785a2b71d479798a69f5c2919e7d274c5f4b32c3607"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1ad00662305dcb1e987f5ec214d31f7d6a062cae3e74c1cbccef15afd96611d"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4b077ce83f47dd6bea1991af08b140e8b8339f0ba8cb9b7a484c30ebab18a23f"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a88efecec23c37b11076fe676e15c6cdb1271a38f2b415e381e87fe4517f18"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b25093dad3b055667730a9b491129c42d45e11cdb7043b702e97125bcec48a1"}, - {file = "ruff-0.1.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:231d8fb11b2cc7c0366a326a66dafc6ad449d7fcdbc268497ee47e1334f66f77"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:09c415716884950080921dd6237767e52e227e397e2008e2bed410117679975b"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f58948c6d212a6b8d41cd59e349751018797ce1727f961c2fa755ad6208ba45"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:190a566c8f766c37074d99640cd9ca3da11d8deae2deae7c9505e68a4a30f740"}, - {file = "ruff-0.1.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6464289bd67b2344d2a5d9158d5eb81025258f169e69a46b741b396ffb0cda95"}, - {file = "ruff-0.1.11-py3-none-win32.whl", hash = "sha256:9b8f397902f92bc2e70fb6bebfa2139008dc72ae5177e66c383fa5426cb0bf2c"}, - {file = "ruff-0.1.11-py3-none-win_amd64.whl", hash = "sha256:eb85ee287b11f901037a6683b2374bb0ec82928c5cbc984f575d0437979c521a"}, - {file = "ruff-0.1.11-py3-none-win_arm64.whl", hash = "sha256:97ce4d752f964ba559c7023a86e5f8e97f026d511e48013987623915431c7ea9"}, - {file = "ruff-0.1.11.tar.gz", hash = "sha256:f9d4d88cb6eeb4dfe20f9f0519bd2eaba8119bde87c3d5065c541dbae2b5a2cb"}, + {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e3fd36e0d48aeac672aa850045e784673449ce619afc12823ea7868fcc41d8ba"}, + {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9fb6b3b86450d4ec6a6732f9f60c4406061b6851c4b29f944f8c9d91c3611c7a"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b13ba5d7156daaf3fd08b6b993360a96060500aca7e307d95ecbc5bb47a69296"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ebb40442f7b531e136d334ef0851412410061e65d61ca8ce90d894a094feb22"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226b517f42d59a543d6383cfe03cccf0091e3e0ed1b856c6824be03d2a75d3b6"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f0312ba1061e9b8c724e9a702d3c8621e3c6e6c2c9bd862550ab2951ac75c16"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f59bcf5217c661254bd6bc42d65a6fd1a8b80c48763cb5c2293295babd945dd"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6894b00495e00c27b6ba61af1fc666f17de6140345e5ef27dd6e08fb987259d"}, + {file = "ruff-0.1.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1600942485c6e66119da294c6294856b5c86fd6df591ce293e4a4cc8e72989"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ee3febce7863e231a467f90e681d3d89210b900d49ce88723ce052c8761be8c7"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dcaab50e278ff497ee4d1fe69b29ca0a9a47cd954bb17963628fa417933c6eb1"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f57de973de4edef3ad3044d6a50c02ad9fc2dff0d88587f25f1a48e3f72edf5e"}, + {file = "ruff-0.1.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a36fa90eb12208272a858475ec43ac811ac37e91ef868759770b71bdabe27b6"}, + {file = "ruff-0.1.13-py3-none-win32.whl", hash = "sha256:a623349a505ff768dad6bd57087e2461be8db58305ebd5577bd0e98631f9ae69"}, + {file = "ruff-0.1.13-py3-none-win_amd64.whl", hash = "sha256:f988746e3c3982bea7f824c8fa318ce7f538c4dfefec99cd09c8770bd33e6539"}, + {file = "ruff-0.1.13-py3-none-win_arm64.whl", hash = "sha256:6bbbc3042075871ec17f28864808540a26f0f79a4478c357d3e3d2284e832998"}, + {file = "ruff-0.1.13.tar.gz", hash = "sha256:e261f1baed6291f434ffb1d5c6bd8051d1c2a26958072d38dfbec39b3dda7352"}, ] [[package]] @@ -4414,56 +4445,50 @@ sphinx = ">=1.2" [[package]] name = "sphinxcontrib-applehelp" -version = "1.0.7" +version = "1.0.8" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, - {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-devhelp" -version = "1.0.5" +version = "1.0.6" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, - {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-htmlhelp" -version = "2.0.4" +version = "2.0.5" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, - {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["html5lib", "pytest"] [[package]] @@ -4496,38 +4521,34 @@ test = ["flake8", "mypy", "pytest"] [[package]] name = "sphinxcontrib-qthelp" -version = "1.0.6" +version = "1.0.7" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, - {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] name = "sphinxcontrib-serializinghtml" -version = "1.1.9" +version = "1.1.10" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" optional = false python-versions = ">=3.9" files = [ - {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, - {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, ] -[package.dependencies] -Sphinx = ">=5" - [package.extras] lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] test = ["pytest"] [[package]] diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index ce9e64188b..2df44cb090 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -79,7 +79,7 @@ class UploadItem: url: str headers: Dict[str, str] created_at: int - id: Optional[str] # noqa: A003 (to match the response from the remote server) + id: Optional[str] retry_count: int = 0 current: bool = False progress: float = 0 From 94cd4c90464615d763af43904fd3902730b13515 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 15 Jan 2024 15:52:15 -0500 Subject: [PATCH 459/724] Subaru: ensure consistent firmware version size (#31001) * fix copying mistakes * fix SA * remove print * ensure size is same * fix SA * not in database --- selfdrive/car/subaru/fingerprints.py | 5 ++--- selfdrive/car/subaru/tests/test_subaru.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 selfdrive/car/subaru/tests/test_subaru.py diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py index 25fc2a0aa9..aaee2e1c71 100644 --- a/selfdrive/car/subaru/fingerprints.py +++ b/selfdrive/car/subaru/fingerprints.py @@ -54,7 +54,7 @@ FW_VERSIONS = { b'\xa1 \x02\x01', b'\xa1 \x02\x02', b'\xa1 \x03\x03', - b'\xa1\\ x04\x01', + b'\xa1 \x04\x01', ], (Ecu.eps, 0x746, None): [ b'\x9b\xc0\x11\x00', @@ -373,7 +373,6 @@ FW_VERSIONS = { b'\x00\x00c\xb7\x1f@\x10\x16', b'\x00\x00c\xd1\x1f@\x10\x17', b'\x00\x00c\xec\x1f@ \x04', - b'\x00\x00c\xec7@\x04', ], (Ecu.engine, 0x7e0, None): [ b'\xa0"@\x80\x07', @@ -447,9 +446,9 @@ FW_VERSIONS = { }, CAR.OUTBACK: { (Ecu.abs, 0x7b0, None): [ - b'\xa1 \x06\x02', b'\xa1 \x06\x00', b'\xa1 \x06\x01', + b'\xa1 \x06\x02', b'\xa1 \x07\x00', b'\xa1 \x07\x02', b'\xa1 \x08\x00', diff --git a/selfdrive/car/subaru/tests/test_subaru.py b/selfdrive/car/subaru/tests/test_subaru.py new file mode 100644 index 0000000000..c8cdf66065 --- /dev/null +++ b/selfdrive/car/subaru/tests/test_subaru.py @@ -0,0 +1,20 @@ +from cereal import car +import unittest +from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS + +Ecu = car.CarParams.Ecu + +ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} + + +class TestSubaruFingerprint(unittest.TestCase): + def test_fw_version_format(self): + for platform, fws_per_ecu in FW_VERSIONS.items(): + for (ecu, _, _), fws in fws_per_ecu.items(): + fw_size = len(fws[0]) + for fw in fws: + self.assertEqual(len(fw), fw_size, f"{platform} {ecu}: {len(fw)} {fw_size}") + + +if __name__ == "__main__": + unittest.main() From e1a05deadd08d375537df4304bcad826da2d125e Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 15 Jan 2024 19:57:12 -0500 Subject: [PATCH 460/724] test_athena: make test more independent (#31012) get port auto --- selfdrive/athena/tests/helpers.py | 36 +++++++++----------------- selfdrive/athena/tests/test_athenad.py | 31 +++++++++++++++++----- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/selfdrive/athena/tests/helpers.py b/selfdrive/athena/tests/helpers.py index 34edeb2de5..5aca899cc2 100644 --- a/selfdrive/athena/tests/helpers.py +++ b/selfdrive/athena/tests/helpers.py @@ -1,12 +1,7 @@ import http.server -import random -import requests +import threading import socket -import time from functools import wraps -from multiprocessing import Process - -from openpilot.common.timeout import Timeout class MockResponse: @@ -101,30 +96,23 @@ class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): self.end_headers() -def with_http_server(func): +def with_http_server(func, handler=http.server.BaseHTTPRequestHandler, setup=None): @wraps(func) def inner(*args, **kwargs): - with Timeout(2, 'HTTP Server did not start'): - p = None - host = '127.0.0.1' - while p is None or p.exitcode is not None: - port = random.randrange(40000, 50000) - p = Process(target=http.server.test, - kwargs={'port': port, 'HandlerClass': HTTPRequestHandler, 'bind': host}) - p.start() - time.sleep(0.1) + host = '127.0.0.1' + server = http.server.HTTPServer((host, 0), handler) + port = server.server_port + t = threading.Thread(target=server.serve_forever) + t.start() - with Timeout(2, 'HTTP Server seeding failed'): - while True: - try: - requests.put(f'http://{host}:{port}/qlog.bz2', data='', timeout=10) - break - except requests.exceptions.ConnectionError: - time.sleep(0.1) + if setup is not None: + setup(host, port) try: return func(*args, f'http://{host}:{port}', **kwargs) finally: - p.terminate() + server.shutdown() + server.server_close() + t.join() return inner diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index a562ae8582..8f6372ec84 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from functools import partial import json import multiprocessing import os @@ -17,11 +18,27 @@ from unittest import mock from websocket import ABNF from websocket._exceptions import WebSocketConnectionClosedException +from cereal import messaging + +from openpilot.common.timeout import Timeout from openpilot.selfdrive.athena import athenad from openpilot.selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher from openpilot.selfdrive.athena.tests.helpers import MockWebsocket, MockParams, MockApi, EchoSocket, with_http_server -from cereal import messaging from openpilot.system.hardware.hw import Paths +from openpilot.selfdrive.athena.tests.helpers import HTTPRequestHandler + + +def seed_athena_server(host, port): + with Timeout(2, 'HTTP Server seeding failed'): + while True: + try: + requests.put(f'http://{host}:{port}/qlog.bz2', data='', timeout=10) + break + except requests.exceptions.ConnectionError: + time.sleep(0.1) + + +with_mock_athena = partial(with_http_server, handler=HTTPRequestHandler, setup=seed_athena_server) class TestAthenadMethods(unittest.TestCase): @@ -138,7 +155,7 @@ class TestAthenadMethods(unittest.TestCase): self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4]) @parameterized.expand([(True,), (False,)]) - @with_http_server + @with_mock_athena def test_do_upload(self, compress, host): # random bytes to ensure rather large object post-compression fn = self._create_file('qlog', data=os.urandom(10000 * 1024)) @@ -152,7 +169,7 @@ class TestAthenadMethods(unittest.TestCase): resp = athenad._do_upload(item) self.assertEqual(resp.status_code, 201) - @with_http_server + @with_mock_athena def test_uploadFileToUrl(self, host): fn = self._create_file('qlog.bz2') @@ -163,7 +180,7 @@ class TestAthenadMethods(unittest.TestCase): self.assertIsNotNone(resp['items'][0].get('id')) self.assertEqual(athenad.upload_queue.qsize(), 1) - @with_http_server + @with_mock_athena def test_uploadFileToUrl_duplicate(self, host): self._create_file('qlog.bz2') @@ -175,12 +192,12 @@ class TestAthenadMethods(unittest.TestCase): resp = dispatcher["uploadFileToUrl"]("qlog.bz2", url2, {}) self.assertEqual(resp, {'enqueued': 0, 'items': []}) - @with_http_server + @with_mock_athena def test_uploadFileToUrl_does_not_exist(self, host): not_exists_resp = dispatcher["uploadFileToUrl"]("does_not_exist.bz2", "http://localhost:1238", {}) self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']}) - @with_http_server + @with_mock_athena def test_upload_handler(self, host): fn = self._create_file('qlog.bz2') item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) @@ -199,7 +216,7 @@ class TestAthenadMethods(unittest.TestCase): finally: end_event.set() - @with_http_server + @with_mock_athena @mock.patch('requests.put') def test_upload_handler_retry(self, host, mock_put): for status, retry in ((500, True), (412, False)): From 8e9be48b7b02e679868b50aab34cd38cb48b3905 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 15 Jan 2024 23:49:07 -0800 Subject: [PATCH 461/724] Revert "docs: include dashcam" (#31017) --- selfdrive/car/docs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index 5142b6fc15..8475a69d8a 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -25,7 +25,7 @@ CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md") CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md") -def get_all_car_info(include_dashcam_only: bool = False) -> List[CarInfo]: +def get_all_car_info() -> List[CarInfo]: all_car_info: List[CarInfo] = [] footnotes = get_all_footnotes() for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items(): @@ -33,7 +33,7 @@ def get_all_car_info(include_dashcam_only: bool = False) -> List[CarInfo]: CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), car_fw=[car.CarParams.CarFw(ecu="unknown")], experimental_long=True, docs=True) - if (CP.dashcamOnly and not include_dashcam_only) or car_info is None: + if CP.dashcamOnly or car_info is None: continue # A platform can include multiple car models From 4da8d165144404e7356e611e68b036ecb33126ef Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 13:03:55 -0500 Subject: [PATCH 462/724] LogReader: support only_union_types (#31019) support only-union-types --- tools/lib/logreader.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 7142c7f854..86c38f2749 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -89,7 +89,7 @@ def parse_slice(sr: SegmentRange): s = create_slice_from_string(sr._slice) return segs[s] if isinstance(s, slice) else [segs[s]] -def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): +def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): segs = parse_slice(sr) route = Route(sr.route_name) @@ -100,22 +100,22 @@ def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" for seg in segs: - yield _LogFileReader(log_paths[seg], sort_by_time=sort_by_time) + yield _LogFileReader(log_paths[seg], **kwargs) -def internal_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): +def internal_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): segs = parse_slice(sr) for seg in segs: - yield _LogFileReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2", sort_by_time=sort_by_time) + yield _LogFileReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2", **kwargs) -def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG, sort_by_time=False): +def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): segs = parse_slice(sr) for seg in segs: - yield _LogFileReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog'), sort_by_time=sort_by_time) + yield _LogFileReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog'), **kwargs) -def direct_source(file_or_url, sort_by_time): - yield _LogFileReader(file_or_url, sort_by_time=sort_by_time) +def direct_source(file_or_url, **kwargs): + yield _LogFileReader(file_or_url, **kwargs) def auto_source(*args, **kwargs): # Automatically determine viable source @@ -173,14 +173,16 @@ class LogReader: mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) source = self.default_source if source is None else source - return source(sr, mode, sort_by_time=self.sort_by_time) + return source(sr, mode, sort_by_time=self.sort_by_time, only_union_types=self.only_union_types) - def __init__(self, identifier: str, default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False): + def __init__(self, identifier: str, default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False, only_union_types=False): self.default_mode = default_mode self.default_source = default_source - self.sort_by_time = sort_by_time self.identifier = identifier + self.sort_by_time = sort_by_time + self.only_union_types = only_union_types + self.reset() def __iter__(self): From 0739d5d7b25f1fbcb70a4dd2ef9b5071afb2c526 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 13:40:00 -0500 Subject: [PATCH 463/724] LogReader: support lists of identifiers (#31020) * support lists of logs * mark as slow instead --- tools/lib/logreader.py | 9 ++++++--- tools/lib/tests/test_logreader.py | 12 ++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 86c38f2749..2512be8ee2 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -11,7 +11,7 @@ import sys import urllib.parse import warnings -from typing import Iterable, Iterator +from typing import Iterable, Iterator, List from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log @@ -161,7 +161,10 @@ def parse_indirect(identifier): class LogReader: - def _logreaders_from_identifier(self, identifier): + def _logreaders_from_identifier(self, identifier: str | List[str]): + if isinstance(identifier, list): + return [LogReader(i) for i in identifier] + parsed, source, is_indirect = parse_indirect(identifier) if not is_indirect: @@ -175,7 +178,7 @@ class LogReader: return source(sr, mode, sort_by_time=self.sort_by_time, only_union_types=self.only_union_types) - def __init__(self, identifier: str, default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False, only_union_types=False): + def __init__(self, identifier: str | List[str], default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False, only_union_types=False): self.default_mode = default_mode self.default_source = default_source self.identifier = identifier diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index 10bb7d9737..a9769e5e63 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -2,6 +2,7 @@ import shutil import tempfile import numpy as np import unittest +import pytest from parameterized import parameterized import requests from openpilot.tools.lib.logreader import LogReader, parse_indirect, parse_slice, ReadMode @@ -69,20 +70,27 @@ class TestLogReader(unittest.TestCase): sr = SegmentRange(segment_range) parse_slice(sr) - @unittest.skip("this test is too slow for the minimal coverage it provides") + @pytest.mark.slow def test_modes(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.QLOG))) rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0", ReadMode.RLOG))) self.assertLess(qlog_len * 6, rlog_len) - @unittest.skip("this test is too slow for the minimal coverage it provides") + @pytest.mark.slow def test_modes_from_name(self): qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/q"))) rlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/r"))) self.assertLess(qlog_len * 6, rlog_len) + @pytest.mark.slow + def test_list(self): + qlog_len = len(list(LogReader(f"{TEST_ROUTE}/0/q"))) + qlog_len_2 = len(list(LogReader([f"{TEST_ROUTE}/0/q", f"{TEST_ROUTE}/0/q"]))) + + self.assertEqual(qlog_len*2, qlog_len_2) + if __name__ == "__main__": unittest.main() From 7d400112df6692ef026245b03951345cc36dd83e Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 13:58:51 -0500 Subject: [PATCH 464/724] LogReader: move opci to tools/lib (#31021) move opci --- selfdrive/car/tests/test_models.py | 2 +- selfdrive/test/process_replay/model_replay.py | 6 +++--- selfdrive/test/process_replay/test_debayer.py | 4 ++-- selfdrive/test/process_replay/test_processes.py | 2 +- selfdrive/test/process_replay/test_regen.py | 2 +- selfdrive/test/test_valgrind_replay.py | 2 +- selfdrive/test/update_ci_routes.py | 2 +- tools/lib/logreader.py | 2 +- {selfdrive/test => tools/lib}/openpilotci.py | 0 tools/replay/unlog_ci_segment.py | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) rename {selfdrive/test => tools/lib}/openpilotci.py (100%) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 253841877f..abca99b55a 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -22,7 +22,7 @@ from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute from openpilot.selfdrive.controls.controlsd import Controls from openpilot.selfdrive.test.helpers import read_segment_list -from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import Route, SegmentName, RouteName diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 2841386bf4..a96dd18503 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -9,7 +9,7 @@ import cereal.messaging as messaging from openpilot.common.params import Params from openpilot.system.hardware import PC from openpilot.selfdrive.manager.process_config import managed_processes -from openpilot.selfdrive.test.openpilotci import BASE_URL, get_url +from openpilot.tools.lib.openpilotci import BASE_URL, get_url from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff from openpilot.selfdrive.test.process_replay.process_replay import get_process_config, replay_process from openpilot.system.version import get_commit @@ -143,7 +143,7 @@ if __name__ == "__main__": import requests import threading import http.server - from openpilot.selfdrive.test.openpilotci import upload_bytes + from openpilot.tools.lib.openpilotci import upload_bytes os.environ['MAPS_HOST'] = 'http://localhost:5000' class HTTPRequestHandler(http.server.BaseHTTPRequestHandler): @@ -229,7 +229,7 @@ if __name__ == "__main__": # upload new refs if (update or failed) and not PC: - from openpilot.selfdrive.test.openpilotci import upload_file + from openpilot.tools.lib.openpilotci import upload_file print("Uploading new refs") diff --git a/selfdrive/test/process_replay/test_debayer.py b/selfdrive/test/process_replay/test_debayer.py index baa9e255e6..bea1b1fb00 100755 --- a/selfdrive/test/process_replay/test_debayer.py +++ b/selfdrive/test/process_replay/test_debayer.py @@ -8,7 +8,7 @@ import pyopencl as cl # install with `PYOPENCL_CL_PRETEND_VERSION=2.0 pip insta from openpilot.system.hardware import PC, TICI from openpilot.common.basedir import BASEDIR -from openpilot.selfdrive.test.openpilotci import BASE_URL +from openpilot.tools.lib.openpilotci import BASE_URL from openpilot.system.version import get_commit from openpilot.system.camerad.snapshot.snapshot import yuv_to_rgb from openpilot.tools.lib.logreader import LogReader @@ -171,7 +171,7 @@ if __name__ == "__main__": # upload new refs if update or (failed and TICI): - from openpilot.selfdrive.test.openpilotci import upload_file + from openpilot.tools.lib.openpilotci import upload_file print("Uploading new refs") diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 5429c9b63e..46fa93ba4d 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -8,7 +8,7 @@ from tqdm import tqdm from typing import Any, DefaultDict, Dict from openpilot.selfdrive.car.car_helpers import interface_names -from openpilot.selfdrive.test.openpilotci import get_url, upload_file +from openpilot.tools.lib.openpilotci import get_url, upload_file from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, check_openpilot_enabled, replay_process from openpilot.system.version import get_commit diff --git a/selfdrive/test/process_replay/test_regen.py b/selfdrive/test/process_replay/test_regen.py index f352205564..41d67ea376 100755 --- a/selfdrive/test/process_replay/test_regen.py +++ b/selfdrive/test/process_replay/test_regen.py @@ -6,7 +6,7 @@ from parameterized import parameterized from openpilot.selfdrive.test.process_replay.regen import regen_segment, DummyFrameReader from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled -from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.framereader import FrameReader diff --git a/selfdrive/test/test_valgrind_replay.py b/selfdrive/test/test_valgrind_replay.py index a8a3463104..75520df91b 100755 --- a/selfdrive/test/test_valgrind_replay.py +++ b/selfdrive/test/test_valgrind_replay.py @@ -15,7 +15,7 @@ else: import cereal.messaging as messaging from collections import namedtuple from openpilot.tools.lib.logreader import LogReader -from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.openpilotci import get_url from openpilot.common.basedir import BASEDIR ProcessConfig = namedtuple('ProcessConfig', ['proc_name', 'pub_sub', 'ignore', 'command', 'path', 'segment', 'wait_for_response']) diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index ccaa7f9b84..2a841025e3 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -11,7 +11,7 @@ from tqdm import tqdm from openpilot.selfdrive.car.tests.routes import routes as test_car_models_routes from openpilot.selfdrive.test.process_replay.test_processes import source_segments as replay_segments -from openpilot.selfdrive.test.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, OPENPILOT_CI_CONTAINER, +from openpilot.tools.lib.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, OPENPILOT_CI_CONTAINER, DATA_CI_CONTAINER, get_azure_credential, get_container_sas, upload_file) DATA_PROD_ACCOUNT = "commadata2" diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 2512be8ee2..ee893c7e15 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -15,7 +15,7 @@ from typing import Iterable, Iterator, List from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log -from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.filereader import FileReader from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.route import Route, SegmentRange diff --git a/selfdrive/test/openpilotci.py b/tools/lib/openpilotci.py similarity index 100% rename from selfdrive/test/openpilotci.py rename to tools/lib/openpilotci.py diff --git a/tools/replay/unlog_ci_segment.py b/tools/replay/unlog_ci_segment.py index ae97ad45d6..a2011f6876 100755 --- a/tools/replay/unlog_ci_segment.py +++ b/tools/replay/unlog_ci_segment.py @@ -12,7 +12,7 @@ from collections import defaultdict import cereal.messaging as messaging from openpilot.tools.lib.framereader import FrameReader from openpilot.tools.lib.logreader import LogReader -from openpilot.selfdrive.test.openpilotci import get_url +from openpilot.tools.lib.openpilotci import get_url IGNORE = ['initData', 'sentinel'] From 61fe705160ddb9e5c55292c7f702c5502e2c0d2e Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 15:40:49 -0500 Subject: [PATCH 465/724] LogReader: fix plotjuggler not reading properly (#31023) * fix plotjuggler * simpler * fix juggler * fix that --- tools/lib/logreader.py | 62 +++++++++++++++------------ tools/plotjuggler/juggle.py | 6 +-- tools/plotjuggler/test_plotjuggler.py | 2 + tools/replay/can_replay.py | 6 +-- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index ee893c7e15..36cff0d724 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import bz2 +from functools import partial +import multiprocessing import capnp import enum -import itertools import numpy as np import os import pathlib @@ -89,7 +90,7 @@ def parse_slice(sr: SegmentRange): s = create_slice_from_string(sr._slice) return segs[s] if isinstance(s, slice) else [segs[s]] -def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): +def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG): segs = parse_slice(sr) route = Route(sr.route_name) @@ -99,40 +100,39 @@ def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" - for seg in segs: - yield _LogFileReader(log_paths[seg], **kwargs) + return [(log_paths[seg]) for seg in segs] -def internal_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): +def internal_source(sr: SegmentRange, mode=ReadMode.RLOG): segs = parse_slice(sr) - for seg in segs: - yield _LogFileReader(f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2", **kwargs) + return [f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2" for seg in segs] -def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG, **kwargs): +def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG): segs = parse_slice(sr) - for seg in segs: - yield _LogFileReader(get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog'), **kwargs) + return [get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog') for seg in segs] -def direct_source(file_or_url, **kwargs): - yield _LogFileReader(file_or_url, **kwargs) +def direct_source(file_or_url): + return [file_or_url] -def auto_source(*args, **kwargs): +def auto_source(*args): # Automatically determine viable source try: - next(internal_source(*args, **kwargs)) - return internal_source(*args, **kwargs) + identifiers = internal_source(*args) + _LogFileReader(identifiers[0]) + return internal_source(*args) except Exception: pass try: - next(openpilotci_source(*args, **kwargs)) - return openpilotci_source(*args, **kwargs) + identifiers = openpilotci_source(*args) + _LogFileReader(identifiers[0]) + return openpilotci_source(*args) except Exception: pass - return comma_api_source(*args, **kwargs) + return comma_api_source(*args) def parse_useradmin(identifier): if "useradmin.comma.ai" in identifier: @@ -161,22 +161,22 @@ def parse_indirect(identifier): class LogReader: - def _logreaders_from_identifier(self, identifier: str | List[str]): + def _parse_identifiers(self, identifier: str | List[str]): if isinstance(identifier, list): - return [LogReader(i) for i in identifier] + return [i for j in identifier for i in self._parse_identifiers(j)] parsed, source, is_indirect = parse_indirect(identifier) if not is_indirect: direct_parsed = parse_direct(identifier) if direct_parsed is not None: - return direct_source(identifier, sort_by_time=self.sort_by_time) + return direct_source(identifier) sr = SegmentRange(parsed) mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) source = self.default_source if source is None else source - return source(sr, mode, sort_by_time=self.sort_by_time, only_union_types=self.only_union_types) + return source(sr, mode) def __init__(self, identifier: str | List[str], default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False, only_union_types=False): self.default_mode = default_mode @@ -190,14 +190,22 @@ class LogReader: def __iter__(self): self.reset() - return self + for identifier in self.logreader_identifiers: + yield from _LogFileReader(identifier) - def __next__(self): - return next(self.chain) + def _run_on_segment(self, func, identifier): + lr = _LogFileReader(identifier) + return func(lr) + + def run_across_segments(self, num_processes, func): + with multiprocessing.Pool(num_processes) as pool: + ret = [] + for p in pool.map(partial(self._run_on_segment, func), self.logreader_identifiers): + ret.extend(p) + return ret def reset(self): - self.lrs = self._logreaders_from_identifier(self.identifier) - self.chain = itertools.chain(*self.lrs) + self.logreader_identifiers = self._parse_identifiers(self.identifier) @staticmethod def from_bytes(dat): diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index b497bdaa39..292db5a50a 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import multiprocessing import os import sys import platform @@ -76,10 +75,7 @@ def process(can, lr): def juggle_route(route_or_segment_name, can, layout, dbc=None): sr = LogReader(route_or_segment_name) - with multiprocessing.Pool(24) as pool: - all_data = [] - for p in pool.map(partial(process, can), sr.lrs): - all_data.extend(p) + all_data = sr.run_across_segments(24, partial(process, can)) # Infer DBC name from logs if dbc is None: diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index 1cb2dc0674..17287fb803 100755 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -31,6 +31,8 @@ class TestPlotJuggler(unittest.TestCase): self.assertEqual(p.poll(), None) os.killpg(os.getpgid(p.pid), signal.SIGTERM) + self.assertNotIn("Raw file read failed", output) + # TODO: also test that layouts successfully load def test_layouts(self): bad_strings = ( diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index d0a5304cff..8ed6c63aa4 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -3,7 +3,6 @@ import argparse import os import time import threading -import multiprocessing os.environ['FILEREADER_CACHE'] = '1' @@ -99,10 +98,7 @@ if __name__ == "__main__": sr = LogReader(args.route_or_segment_name) - with multiprocessing.Pool(24) as pool: - CAN_MSGS = [] - for p in pool.map(process, sr.lrs): - CAN_MSGS.extend(p) + CAN_MSGS = sr.run_across_segments(24, process) print("Finished loading...") From 8a05d212ec91598264a53747167c1bbeaae643a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20W=C3=B6rmann?= Date: Tue, 16 Jan 2024 22:40:38 +0100 Subject: [PATCH 466/724] CI: Add GH Actions workflow Bot to check for a PR template (#30999) * Add GH Actions workflow to check for a PR template * switch to headings and checkboxes for template detection * check for bold text in template too * move template check over to auto_pr_review action and remove label only if check succeeded * only run template check on external PRs * wording --------- Co-authored-by: Justin Newberry --- .github/workflows/auto_pr_review.yaml | 164 ++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 0b748063bc..74911b9100 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -1,6 +1,7 @@ name: "PR review" on: pull_request_target: + types: [opened, reopened, synchronize, edited, edited] jobs: labeler: @@ -32,3 +33,166 @@ jobs: change-to: ${{ github.base_ref }} already-exists-action: close_this already-exists-comment: "Your PR should be made against the `master` branch" + + check-pr-template: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + pull-requests: write + actions: read + if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' + steps: + - uses: actions/github-script@v7 + with: + script: | + // Comment to add to the PR if no template has been used + const NO_TEMPLATE_MESSAGE = + "It looks like you didn't use on of the Pull Request templates. Please check [the contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md). \ + Also make sure that you didn't modify any of the checkboxes or headings within the template."; + // body data for future requests + const body_data = { + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }; + + // Utility function to extract all headings + const extractHeadings = (markdown) => { + const headingRegex = /^(#{1,6})\s+(.+)$/gm; + const boldTextRegex = /^(?:\*\*|__)(.+?)(?:\*\*|__)\s*$/gm; + const headings = []; + let headingMatch; + while ((headingMatch = headingRegex.exec(markdown))) { + headings.push(headingMatch[2].trim()); + } + let boldMatch; + while ((boldMatch = boldTextRegex.exec(markdown))) { + headings.push(boldMatch[1].trim()); + } + return headings; + }; + + // Utility function to extract all check box descriptions + const extractCheckBoxTexts = (markdown) => { + const checkboxRegex = /^\s*-\s*\[( |x)\]\s+(.+)$/gm; + const checkboxes = []; + let match; + while ((match = checkboxRegex.exec(markdown))) { + checkboxes.push(match[2].trim()); + } + return checkboxes; + }; + + // Utility function to check if a list is a subset of another list + isSubset = (subset, superset) => { + return subset.every((item) => superset.includes(item)); + }; + + // Add an 'in-bot-review' label while this PR is under review + github.rest.issues.addLabels({ + ...body_data, + labels: ["in-bot-review"], + }); + + // Get filenames of all currently checked-in PR templates + const template_contents = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: ".github/PULL_REQUEST_TEMPLATE", + }); + var template_filenames = []; + for (const content of template_contents.data) { + template_filenames.push(content.path); + } + console.debug("Received template filenames: " + template_filenames); + // Retrieve templates + var templates = []; + for (const template_filename of template_filenames) { + const template_response = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: template_filename, + }); + // Convert Base64 content back + const decoded_template = atob(template_response.data.content); + const headings = extractHeadings(decoded_template); + const checkboxes = extractCheckBoxTexts(decoded_template); + if (!headings.length && !checkboxes.length) { + console.warn( + "Invalid template! Contains neither headings nor checkboxes, ignoring it: \n" + + decoded_template + ); + } else { + templates.push({ headings: headings, checkboxes: checkboxes }); + } + } + // Retrieve the PR Body + const pull_request = await github.rest.issues.get({ + ...body_data, + }); + const pull_request_text = pull_request.data.body; + console.debug("Received Pull Request body: \n" + pull_request_text); + + /* Check if the PR Body matches one of the templates + A template is defined by all headings and checkboxes it contains + We extract all Headings and Checkboxes from the PR text and check if any of the templates is a subset of that + */ + const pr_headings = extractHeadings(pull_request_text); + const pr_checkboxes = extractCheckBoxTexts(pull_request_text); + console.debug("Found Headings in PR body:\n" + pr_headings); + console.debug("Found Checkboxes in PR body:\n" + pr_checkboxes); + var template_found = false; + // Iterate over each template to check if it applies + for (const template of templates) { + console.log( + "Checking for headings: [" + + template.headings + + "] and checkboxes: [" + + template.checkboxes + "]" + ); + if ( + isSubset(template.checkboxes, pr_checkboxes) && + isSubset(template.headings, pr_headings) + ) { + console.debug("Found matching template!"); + template_found = true; + } + } + + // List comments from previous runs + var existing_comments = []; + const comments = await github.rest.issues.listComments({ + ...body_data, + }); + for (const comment of comments.data) { + if (comment.body === NO_TEMPLATE_MESSAGE) { + existing_comments.push(comment); + } + } + + // Add a comment to the PR that it is not using a the template (but only if this comment does not exist already) + if (!template_found) { + var comment_already_sent = false; + + if (existing_comments.length < 1) { + github.rest.issues.createComment({ + ...body_data, + body: NO_TEMPLATE_MESSAGE, + }); + } + } else { + // If template has been found, delete any old comment about missing template + for (const existing_comment of existing_comments) { + github.rest.issues.deleteComment({ + ...body_data, + comment_id: existing_comment.id, + }); + } + // Remove the 'in-bot-review' label after the review is done and the PR has passed + github.rest.issues.removeLabel({ + ...body_data, + name: "in-bot-review", + }); + } + From 5c24527683cd4a9d338fe64dd9e98179e903316e Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 18:08:44 -0500 Subject: [PATCH 467/724] test_models: sanitize segment before running test (#31026) to ensure sanetized routes will work --- selfdrive/car/tests/test_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index abca99b55a..cce5979899 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -21,7 +21,7 @@ from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute from openpilot.selfdrive.controls.controlsd import Controls -from openpilot.selfdrive.test.helpers import read_segment_list +from openpilot.selfdrive.test.helpers import read_segment_list, sanitize from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import Route, SegmentName, RouteName @@ -83,6 +83,7 @@ class TestCarModelBase(unittest.TestCase): @classmethod def get_testing_data_from_logreader(cls, lr): + lr = sanitize(lr) car_fw = [] can_msgs = [] cls.elm_frame = None From 2967cada71fd1b40e86356585fa5d68301d9f1c8 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 20:24:37 -0500 Subject: [PATCH 468/724] auto_source: optimize api calls and use head to determine if file exists (#31025) * fast * catch all * source * fix file_exists * remove duplicate reset * test multiple loops * iterations * cleanup imports --- tools/lib/filereader.py | 8 ++++++ tools/lib/logreader.py | 48 ++++++++++++++----------------- tools/lib/tests/test_logreader.py | 13 +++++++-- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/tools/lib/filereader.py b/tools/lib/filereader.py index 4aec965f1a..15e618f649 100644 --- a/tools/lib/filereader.py +++ b/tools/lib/filereader.py @@ -1,4 +1,6 @@ import os +import requests + from openpilot.tools.lib.url_file import URLFile DATA_ENDPOINT = os.getenv("DATA_ENDPOINT", "http://data-raw.comma.internal/") @@ -8,6 +10,12 @@ def resolve_name(fn): return fn.replace("cd:/", DATA_ENDPOINT) return fn +def file_exists(fn): + fn = resolve_name(fn) + if fn.startswith(("http://", "https://")): + return requests.head(fn, allow_redirects=True).status_code == 200 + return os.path.exists(fn) + def FileReader(fn, debug=False): fn = resolve_name(fn) if fn.startswith(("http://", "https://")): diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 36cff0d724..36d099de50 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -17,7 +17,7 @@ from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log from openpilot.tools.lib.openpilotci import get_url -from openpilot.tools.lib.filereader import FileReader +from openpilot.tools.lib.filereader import FileReader, file_exists from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.route import Route, SegmentRange @@ -84,15 +84,13 @@ def create_slice_from_string(s: str): return start return slice(start, end, step) -def parse_slice(sr: SegmentRange): - route = Route(sr.route_name) +def parse_slice(sr: SegmentRange, route: Route): segs = np.arange(route.max_seg_number+1) s = create_slice_from_string(sr._slice) return segs[s] if isinstance(s, slice) else [segs[s]] -def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG): - segs = parse_slice(sr) - route = Route(sr.route_name) +def comma_api_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): + segs = parse_slice(sr, route) log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() @@ -102,35 +100,33 @@ def comma_api_source(sr: SegmentRange, mode=ReadMode.RLOG): return [(log_paths[seg]) for seg in segs] -def internal_source(sr: SegmentRange, mode=ReadMode.RLOG): - segs = parse_slice(sr) +def internal_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): + segs = parse_slice(sr, route) return [f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2" for seg in segs] -def openpilotci_source(sr: SegmentRange, mode=ReadMode.RLOG): - segs = parse_slice(sr) +def openpilotci_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): + segs = parse_slice(sr, route) return [get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog') for seg in segs] def direct_source(file_or_url): return [file_or_url] +def check_source(source, *args): + try: + files = source(*args) + assert all(file_exists(f) for f in files) + return True, files + except Exception: + return False, None + def auto_source(*args): # Automatically determine viable source - - try: - identifiers = internal_source(*args) - _LogFileReader(identifiers[0]) - return internal_source(*args) - except Exception: - pass - - try: - identifiers = openpilotci_source(*args) - _LogFileReader(identifiers[0]) - return openpilotci_source(*args) - except Exception: - pass + for source in [internal_source, openpilotci_source]: + valid, ret = check_source(source, *args) + if valid: + return ret return comma_api_source(*args) @@ -173,10 +169,11 @@ class LogReader: return direct_source(identifier) sr = SegmentRange(parsed) + route = Route(sr.route_name) mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) source = self.default_source if source is None else source - return source(sr, mode) + return source(sr, route, mode) def __init__(self, identifier: str | List[str], default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False, only_union_types=False): self.default_mode = default_mode @@ -189,7 +186,6 @@ class LogReader: self.reset() def __iter__(self): - self.reset() for identifier in self.logreader_identifiers: yield from _LogFileReader(identifier) diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index a9769e5e63..74ee82d152 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -6,7 +6,7 @@ import pytest from parameterized import parameterized import requests from openpilot.tools.lib.logreader import LogReader, parse_indirect, parse_slice, ReadMode -from openpilot.tools.lib.route import SegmentRange +from openpilot.tools.lib.route import Route, SegmentRange NUM_SEGS = 17 # number of segments in the test route ALL_SEGS = list(np.arange(NUM_SEGS)) @@ -42,7 +42,8 @@ class TestLogReader(unittest.TestCase): def test_indirect_parsing(self, identifier, expected): parsed, _, _ = parse_indirect(identifier) sr = SegmentRange(parsed) - segs = parse_slice(sr) + route = Route(sr.route_name) + segs = parse_slice(sr, route) self.assertListEqual(list(segs), expected) def test_direct_parsing(self): @@ -91,6 +92,14 @@ class TestLogReader(unittest.TestCase): self.assertEqual(qlog_len*2, qlog_len_2) + @pytest.mark.slow + def test_multiple_iterations(self): + lr = LogReader(f"{TEST_ROUTE}/0/q") + qlog_len1 = len(list(lr)) + qlog_len2 = len(list(lr)) + + self.assertEqual(qlog_len1, qlog_len2) + if __name__ == "__main__": unittest.main() From b93382523120b0eec61e2e1cd3d4ee976619b033 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 17:30:11 -0800 Subject: [PATCH 469/724] fix typo --- .github/workflows/auto_pr_review.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 74911b9100..17c83de89d 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -48,7 +48,7 @@ jobs: script: | // Comment to add to the PR if no template has been used const NO_TEMPLATE_MESSAGE = - "It looks like you didn't use on of the Pull Request templates. Please check [the contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md). \ + "It looks like you didn't use one of the Pull Request templates. Please check [the contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md). \ Also make sure that you didn't modify any of the checkboxes or headings within the template."; // body data for future requests const body_data = { From 1bf8e04f54c50b47e1553e5290539bd88cc39f05 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 21:02:26 -0500 Subject: [PATCH 470/724] CI: only apply bot-review label when it doesn't pass (#31028) bot template --- .github/workflows/auto_pr_review.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 17c83de89d..0f82eaa789 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -89,12 +89,6 @@ jobs: return subset.every((item) => superset.includes(item)); }; - // Add an 'in-bot-review' label while this PR is under review - github.rest.issues.addLabels({ - ...body_data, - labels: ["in-bot-review"], - }); - // Get filenames of all currently checked-in PR templates const template_contents = await github.rest.repos.getContent({ owner: context.repo.owner, @@ -175,6 +169,12 @@ jobs: if (!template_found) { var comment_already_sent = false; + // Add an 'in-bot-review' label since this PR doesn't have the template + github.rest.issues.addLabels({ + ...body_data, + labels: ["in-bot-review"], + }); + if (existing_comments.length < 1) { github.rest.issues.createComment({ ...body_data, From 49586fc30e8f1b70b9bda4bdb4c682d414a635fc Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 16 Jan 2024 21:23:33 -0500 Subject: [PATCH 471/724] CI: auto_pr_review fix error when label doesn't exist (#31030) * bot template * catch error --- .github/workflows/auto_pr_review.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 0f82eaa789..820801a2c7 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -193,6 +193,8 @@ jobs: github.rest.issues.removeLabel({ ...body_data, name: "in-bot-review", + }).catch((error) => { + console.log("Label 'in-bot-review' not found, ignoring"); }); } From baa4e801fe6566f061dec258838b3aafa970bb47 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 16 Jan 2024 19:11:59 -0800 Subject: [PATCH 472/724] athena: remove MockParams from tests --- selfdrive/athena/tests/helpers.py | 29 -------------------------- selfdrive/athena/tests/test_athenad.py | 21 ++++++++++++++----- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/selfdrive/athena/tests/helpers.py b/selfdrive/athena/tests/helpers.py index 5aca899cc2..87202665aa 100644 --- a/selfdrive/athena/tests/helpers.py +++ b/selfdrive/athena/tests/helpers.py @@ -44,35 +44,6 @@ class MockApi(): return "fake-token" -class MockParams(): - default_params = { - "DongleId": b"0000000000000000", - "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501 - "GithubUsername": b"commaci", - "GsmMetered": True, - "AthenadUploadQueue": '[]', - } - params = default_params.copy() - - @staticmethod - def restore_defaults(): - MockParams.params = MockParams.default_params.copy() - - def get_bool(self, k): - return bool(MockParams.params.get(k)) - - def get(self, k, encoding=None): - ret = MockParams.params.get(k) - if ret is not None and encoding is not None: - ret = ret.decode(encoding) - return ret - - def put(self, k, v): - if k not in MockParams.params: - raise KeyError(f"key: {k} not in MockParams") - MockParams.params[k] = v - - class MockWebsocket(): def __init__(self, recv_queue, send_queue): self.recv_queue = recv_queue diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 8f6372ec84..9c7582a260 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -20,10 +20,11 @@ from websocket._exceptions import WebSocketConnectionClosedException from cereal import messaging +from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.selfdrive.athena import athenad from openpilot.selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher -from openpilot.selfdrive.athena.tests.helpers import MockWebsocket, MockParams, MockApi, EchoSocket, with_http_server +from openpilot.selfdrive.athena.tests.helpers import MockWebsocket, MockApi, EchoSocket, with_http_server from openpilot.system.hardware.hw import Paths from openpilot.selfdrive.athena.tests.helpers import HTTPRequestHandler @@ -45,12 +46,22 @@ class TestAthenadMethods(unittest.TestCase): @classmethod def setUpClass(cls): cls.SOCKET_PORT = 45454 - athenad.Params = MockParams athenad.Api = MockApi athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} def setUp(self): - MockParams.restore_defaults() + self.default_params = { + "DongleId": "0000000000000000", + "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501 + "GithubUsername": b"commaci", + "AthenadUploadQueue": '[]', + } + + self.params = Params() + for k, v in self.default_params.items(): + self.params.put(k, v) + self.params.put_bool("GsmMetered", True) + athenad.upload_queue = queue.Queue() athenad.cur_upload_items.clear() athenad.cancelled_uploads.clear() @@ -394,11 +405,11 @@ class TestAthenadMethods(unittest.TestCase): def test_getSshAuthorizedKeys(self): keys = dispatcher["getSshAuthorizedKeys"]() - self.assertEqual(keys, MockParams().params["GithubSshKeys"].decode('utf-8')) + self.assertEqual(keys, self.default_params["GithubSshKeys"].decode('utf-8')) def test_getGithubUsername(self): keys = dispatcher["getGithubUsername"]() - self.assertEqual(keys, MockParams().params["GithubUsername"].decode('utf-8')) + self.assertEqual(keys, self.default_params["GithubUsername"].decode('utf-8')) def test_getVersion(self): resp = dispatcher["getVersion"]() From 60a41028924858bfd94db3c49d217f298013ea15 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 16 Jan 2024 19:32:21 -0800 Subject: [PATCH 473/724] Fingerprints: automatically add missing FW versions from users [bot] (#31027) Export fingerprints --- selfdrive/car/chrysler/fingerprints.py | 40 ++++++++++++++++++++++++++ selfdrive/car/honda/fingerprints.py | 1 + 2 files changed, 41 insertions(+) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 4a1299d763..aeacc4cfcd 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -65,6 +65,7 @@ FW_VERSIONS = { CAR.JEEP_GRAND_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ b'68402971AD', + b'68454144AD', ], (Ecu.srs, 0x744, None): [ b'68355363AB', @@ -77,12 +78,15 @@ FW_VERSIONS = { ], (Ecu.eps, 0x75a, None): [ b'68453431AA', + b'68453433AA', ], (Ecu.engine, 0x7e0, None): [ b'05035674AB ', + b'68496223AA ', ], (Ecu.transmission, 0x7e1, None): [ b'05035707AA', + b'68495807AA', ], }, CAR.RAM_1500: { @@ -95,17 +99,26 @@ FW_VERSIONS = { b'68294063AI', b'68434846AC', b'68434858AC', + b'68434859AC', b'68434860AC', + b'68453483AC', + b'68453487AD', + b'68453491AC', + b'68453499AD', b'68453503AC', b'68453505AC', + b'68453505AD', b'68453511AC', b'68453513AD', b'68453514AD', b'68510280AG', + b'68510282AH', b'68510283AG', b'68527346AE', + b'68527361AD', b'68527375AD', b'68527382AE', + b'68527387AE', ], (Ecu.srs, 0x744, None): [ b'68428609AB', @@ -155,13 +168,18 @@ FW_VERSIONS = { b'68312176AE', b'68312176AG', b'68440789AC', + b'68466110AA', b'68466110AB', b'68469901AA', + b'68469907AA', + b'68522583AA', b'68522583AB', + b'68522584AA', b'68522585AB', b'68552788AA', b'68552789AA', b'68552790AA', + b'68552791AB', b'68585106AB', b'68585109AB', b'68585112AB', @@ -170,30 +188,52 @@ FW_VERSIONS = { b'05036065AE ', b'05036066AE ', b'05149591AD ', + b'05149591AE ', b'05149592AE ', b'05149846AA ', b'05149848AA ', + b'68378695AJ ', + b'68378696AJ ', b'68378701AI ', b'68378748AL ', b'68378758AM ', b'68448163AJ', b'68448165AK', + b'68455119AC ', + b'68455145AC ', + b'68455145AE ', + b'68455146AC ', b'68500630AD', b'68500630AE', + b'68502719AC ', + b'68502722AC ', + b'68502734AF ', + b'68502740AF ', + b'68502742AC ', b'68539650AD', + b'68539650AF', + b'68586101AA ', + b'68586105AB ', ], (Ecu.transmission, 0x7e1, None): [ + b'05036069AA', b'05149536AC', b'68360078AL', b'68360080AM', b'68360081AM', + b'68360085AJ', b'68360085AL', b'68384328AD', b'68384332AD', + b'68445531AC', b'68445533AB', + b'68445537AB', + b'68484466AC', b'68484467AC', + b'68484471AC', b'68502994AD', b'68520867AE', + b'68520867AF', b'68540431AB', ], }, diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index f28c85a076..e3e95483a0 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -1091,6 +1091,7 @@ FW_VERSIONS = { b'37805-5YF-C220\x00\x00', b'37805-5YF-C410\x00\x00', b'37805-5YF-C420\x00\x00', + b'37805-5YF-C430\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TJB-A030\x00\x00', From c6c6a441eb61c7ac28fb94b05b91fbb8837b9236 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 16 Jan 2024 19:54:07 -0800 Subject: [PATCH 474/724] uploader cleanup (#31029) * cleanup * startup cleanup * step * Update test_uploader.py --- system/loggerd/tests/loggerd_tests_common.py | 28 ++------ system/loggerd/tests/test_uploader.py | 4 +- system/loggerd/uploader.py | 67 ++++++-------------- 3 files changed, 28 insertions(+), 71 deletions(-) diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 8bfb571861..3aa9e40531 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -3,10 +3,12 @@ import random import unittest from pathlib import Path from typing import Optional -from openpilot.system.hardware.hw import Paths + import openpilot.system.loggerd.deleter as deleter import openpilot.system.loggerd.uploader as uploader +from openpilot.common.params import Params +from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import setxattr @@ -53,25 +55,6 @@ class MockApiIgnore(): def get_token(self): return "fake-token" -class MockParams(): - def __init__(self): - self.params = { - "DongleId": b"0000000000000000", - "IsOffroad": b"1", - } - - def get(self, k, block=False, encoding=None): - val = self.params[k] - - if encoding is not None: - return val.decode(encoding) - else: - return val - - def get_bool(self, k): - val = self.params[k] - return (val == b'1') - class UploaderTestCase(unittest.TestCase): f_type = "UNKNOWN" @@ -86,7 +69,6 @@ class UploaderTestCase(unittest.TestCase): def setUp(self): uploader.Api = MockApi - uploader.Params = MockParams uploader.fake_upload = True uploader.force_wifi = True uploader.allow_sleep = False @@ -95,6 +77,10 @@ class UploaderTestCase(unittest.TestCase): self.seg_format2 = "2019-05-18--11-22-33--{}" self.seg_dir = self.seg_format.format(self.seg_num) + self.params = Params() + self.params.put("IsOffroad", "1") + self.params.put("DongleId", "0000000000000000") + def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, upload_xattr: Optional[bytes] = None, preserve_xattr: Optional[bytes] = None) -> Path: file_path = Path(Paths.log_root()) / f_dir / fn diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index 538d99f66f..b674de5438 100755 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -10,7 +10,7 @@ from typing import List, Optional from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog -from openpilot.system.loggerd.uploader import uploader_fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE +from openpilot.system.loggerd.uploader import main, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE from openpilot.system.loggerd.tests.loggerd_tests_common import UploaderTestCase @@ -45,7 +45,7 @@ class TestUploader(UploaderTestCase): def start_thread(self): self.end_event = threading.Event() - self.up_thread = threading.Thread(target=uploader_fn, args=[self.end_event]) + self.up_thread = threading.Thread(target=main, args=[self.end_event]) self.up_thread.daemon = True self.up_thread.start() diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 8f27d4763d..aac5c07e53 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -77,9 +77,6 @@ class Uploader: self.last_resp: Optional[UploadResponse] = None self.last_exc: Optional[Tuple[Exception, str]] = None - self.immediate_size = 0 - self.immediate_count = 0 - # stats for last successfully uploaded file self.last_time = 0.0 self.last_speed = 0.0 @@ -88,18 +85,10 @@ class Uploader: self.immediate_folders = ["crash/", "boot/"] self.immediate_priority = {"qlog": 0, "qlog.bz2": 0, "qcamera.ts": 1} - def get_upload_sort(self, name: str) -> int: - if name in self.immediate_priority: - return self.immediate_priority[name] - return 1000 - def list_upload_files(self) -> Iterator[Tuple[str, str, str]]: if not os.path.isdir(self.root): return - self.immediate_size = 0 - self.immediate_count = 0 - for logname in listdir_by_creation(self.root): path = os.path.join(self.root, logname) try: @@ -110,7 +99,7 @@ class Uploader: if any(name.endswith(".lock") for name in names): continue - for name in sorted(names, key=self.get_upload_sort): + for name in sorted(names, key=lambda n: self.immediate_priority.get(n, 1000)): key = os.path.join(logname, name) fn = os.path.join(path, name) # skip files already uploaded @@ -122,13 +111,6 @@ class Uploader: if is_uploaded: continue - try: - if name in self.immediate_priority: - self.immediate_count += 1 - self.immediate_size += os.path.getsize(fn) - except OSError: - pass - yield name, key, fn def next_file_to_upload(self) -> Optional[Tuple[str, str, str]]: @@ -227,18 +209,25 @@ class Uploader: return success - def get_msg(self): - msg = messaging.new_message("uploaderState", valid=True) - us = msg.uploaderState - us.immediateQueueSize = int(self.immediate_size / 1e6) - us.immediateQueueCount = self.immediate_count - us.lastTime = self.last_time - us.lastSpeed = self.last_speed - us.lastFilename = self.last_filename - return msg + + def step(self, network_type: int, metered: bool) -> bool: + d = self.next_file_to_upload() + if d is None: + return True + + name, key, fn = d + + # qlogs and bootlogs need to be compressed before uploading + if key.endswith(('qlog', 'rlog')) or (key.startswith('boot/') and not key.endswith('.bz2')): + key += ".bz2" + + return self.upload(name, key, fn, network_type, metered) -def uploader_fn(exit_event: threading.Event) -> None: +def main(exit_event: Optional[threading.Event] = None) -> None: + if exit_event is None: + exit_event = threading.Event() + try: set_core_affinity([0, 1, 2, 3]) except Exception: @@ -257,7 +246,6 @@ def uploader_fn(exit_event: threading.Event) -> None: cloudlog.warning("NVME not mounted") sm = messaging.SubMaster(['deviceState']) - pm = messaging.PubMaster(['uploaderState']) uploader = Uploader(dongle_id, Paths.log_root()) backoff = 0.1 @@ -270,19 +258,8 @@ def uploader_fn(exit_event: threading.Event) -> None: time.sleep(60 if offroad else 5) continue - d = uploader.next_file_to_upload() - if d is None: # Nothing to upload - if allow_sleep: - time.sleep(60 if offroad else 5) - continue + success = uploader.step(sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) - name, key, fn = d - - # qlogs and bootlogs need to be compressed before uploading - if key.endswith(('qlog', 'rlog')) or (key.startswith('boot/') and not key.endswith('.bz2')): - key += ".bz2" - - success = uploader.upload(name, key, fn, sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) if success: backoff = 0.1 elif allow_sleep: @@ -290,12 +267,6 @@ def uploader_fn(exit_event: threading.Event) -> None: time.sleep(backoff + random.uniform(0, backoff)) backoff = min(backoff*2, 120) - pm.send("uploaderState", uploader.get_msg()) - - -def main() -> None: - uploader_fn(threading.Event()) - if __name__ == "__main__": main() From 86ac700805ef1ca913cfc859b6bfb1c528d83605 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 16 Jan 2024 20:22:05 -0800 Subject: [PATCH 475/724] update onnxruntime to 1.16.3 (#31024) --- poetry.lock | 42 ++++++++++++++++++------------------------ pyproject.toml | 4 ++-- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/poetry.lock b/poetry.lock index e58fe0f128..a098703d62 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2072,16 +2072,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2662,7 +2652,14 @@ files = [ ] [package.dependencies] -numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] [[package]] name = "opencv-python-headless" @@ -2681,7 +2678,14 @@ files = [ ] [package.dependencies] -numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] [[package]] name = "packaging" @@ -3805,7 +3809,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3813,15 +3816,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3838,7 +3834,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3846,7 +3841,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -4892,4 +4886,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "ddef5baa3e9a1f50713ddc370ead968173200467a56df6a718ef3d2ada95e832" +content-hash = "ad5e6e7b632c35edf6945774c6a4d0370177fd995c9b37efea68dd206a4ce1be" diff --git a/pyproject.toml b/pyproject.toml index da027ae95b..b12a21a962 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,8 +88,8 @@ json-rpc = "*" libusb1 = "*" numpy = "*" onnx = ">=1.14.0" -onnxruntime = { version = ">=1.15.1", platform = "linux", markers = "platform_machine == 'aarch64'" } -onnxruntime-gpu = { version = ">=1.15.1", platform = "linux", markers = "platform_machine == 'x86_64'" } +onnxruntime = { version = ">=1.16.3", platform = "linux", markers = "platform_machine == 'aarch64'" } +onnxruntime-gpu = { version = ">=1.16.3", platform = "linux", markers = "platform_machine == 'x86_64'" } psutil = "*" pyaudio = "*" pycapnp = "*" From f6cd009c77a560a87e31d8918bab0f08c567cc1c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 17 Jan 2024 13:36:47 -0500 Subject: [PATCH 476/724] Update PR templates + autogenerate (#31032) * update templates * same as before * no tuning * same as before * move up --- .github/PULL_REQUEST_TEMPLATE/car_port.md | 2 +- .github/PULL_REQUEST_TEMPLATE/fingerprint.md | 6 ++- .github/PULL_REQUEST_TEMPLATE/tuning.md | 3 +- .github/build.py | 30 +++++++++++++ .github/pull_request_template.md | 46 ++++++++++++++------ 5 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 .github/build.py diff --git a/.github/PULL_REQUEST_TEMPLATE/car_port.md b/.github/PULL_REQUEST_TEMPLATE/car_port.md index 4264363ba2..690c24c9b0 100644 --- a/.github/PULL_REQUEST_TEMPLATE/car_port.md +++ b/.github/PULL_REQUEST_TEMPLATE/car_port.md @@ -8,7 +8,7 @@ assignees: '' **Checklist** -- [ ] added to README +- [ ] added entry to CarInfo in selfdrive/car/*/values.py and ran `selfdrive/car/docs.py` to generate new docs - [ ] test route added to [routes.py](https://github.com/commaai/openpilot/blob/master/selfdrive/car/tests/routes.py) - [ ] route with openpilot: - [ ] route with stock system: diff --git a/.github/PULL_REQUEST_TEMPLATE/fingerprint.md b/.github/PULL_REQUEST_TEMPLATE/fingerprint.md index 466d4f98f4..b94f7dc53f 100644 --- a/.github/PULL_REQUEST_TEMPLATE/fingerprint.md +++ b/.github/PULL_REQUEST_TEMPLATE/fingerprint.md @@ -6,6 +6,8 @@ labels: 'fingerprint' assignees: '' --- -Discord username: [] +**Car** +Which car (make, model, year) this fingerprint is for -Route: [] +**Route** +A route with the fingerprint \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE/tuning.md b/.github/PULL_REQUEST_TEMPLATE/tuning.md index 05e4312699..4397e5ad20 100644 --- a/.github/PULL_REQUEST_TEMPLATE/tuning.md +++ b/.github/PULL_REQUEST_TEMPLATE/tuning.md @@ -28,5 +28,4 @@ Longitudinal: Lateral: * Straight driving at ~25, ~45 and ~65mph * Turns driving at ~25, ~45 and ~65mph - ---> +--> \ No newline at end of file diff --git a/.github/build.py b/.github/build.py new file mode 100644 index 0000000000..e141ea05df --- /dev/null +++ b/.github/build.py @@ -0,0 +1,30 @@ +import pathlib + +GITHUB_FOLDER = pathlib.Path(__file__).parent + +PULL_REQUEST_TEMPLATES = (GITHUB_FOLDER / "PULL_REQUEST_TEMPLATE") + +order = ["fingerprint", "car_bugfix", "bugfix", "car_port", "refactor"] + +def create_pull_request_template(): + with open(GITHUB_FOLDER / "pull_request_template.md", "w") as f: + f.write("\n\n") + + for t in order: + template = PULL_REQUEST_TEMPLATES / f"{t}.md" + text = template.read_text() + + # Remove metadata for GitHub + start = text.find("---") + end = text.find("---", start+1) + text = text[end + 4:] + + # Remove comments + text = text.replace("", "") + + f.write(f"\n\n") + +create_pull_request_template() diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6fbccfbdbb..3e3f42dcbc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,39 +10,59 @@ A route with the fingerprint --> - - - - + + + From 5e4df41b2f4c379157844dc4c19ef496ca658088 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 17 Jan 2024 14:07:38 -0500 Subject: [PATCH 477/724] Revert "uploader cleanup" (#31033) --- system/loggerd/tests/loggerd_tests_common.py | 28 ++++++-- system/loggerd/tests/test_uploader.py | 4 +- system/loggerd/uploader.py | 67 ++++++++++++++------ 3 files changed, 71 insertions(+), 28 deletions(-) diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 3aa9e40531..8bfb571861 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -3,12 +3,10 @@ import random import unittest from pathlib import Path from typing import Optional - +from openpilot.system.hardware.hw import Paths import openpilot.system.loggerd.deleter as deleter import openpilot.system.loggerd.uploader as uploader -from openpilot.common.params import Params -from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import setxattr @@ -55,6 +53,25 @@ class MockApiIgnore(): def get_token(self): return "fake-token" +class MockParams(): + def __init__(self): + self.params = { + "DongleId": b"0000000000000000", + "IsOffroad": b"1", + } + + def get(self, k, block=False, encoding=None): + val = self.params[k] + + if encoding is not None: + return val.decode(encoding) + else: + return val + + def get_bool(self, k): + val = self.params[k] + return (val == b'1') + class UploaderTestCase(unittest.TestCase): f_type = "UNKNOWN" @@ -69,6 +86,7 @@ class UploaderTestCase(unittest.TestCase): def setUp(self): uploader.Api = MockApi + uploader.Params = MockParams uploader.fake_upload = True uploader.force_wifi = True uploader.allow_sleep = False @@ -77,10 +95,6 @@ class UploaderTestCase(unittest.TestCase): self.seg_format2 = "2019-05-18--11-22-33--{}" self.seg_dir = self.seg_format.format(self.seg_num) - self.params = Params() - self.params.put("IsOffroad", "1") - self.params.put("DongleId", "0000000000000000") - def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, upload_xattr: Optional[bytes] = None, preserve_xattr: Optional[bytes] = None) -> Path: file_path = Path(Paths.log_root()) / f_dir / fn diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index b674de5438..538d99f66f 100755 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -10,7 +10,7 @@ from typing import List, Optional from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog -from openpilot.system.loggerd.uploader import main, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE +from openpilot.system.loggerd.uploader import uploader_fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE from openpilot.system.loggerd.tests.loggerd_tests_common import UploaderTestCase @@ -45,7 +45,7 @@ class TestUploader(UploaderTestCase): def start_thread(self): self.end_event = threading.Event() - self.up_thread = threading.Thread(target=main, args=[self.end_event]) + self.up_thread = threading.Thread(target=uploader_fn, args=[self.end_event]) self.up_thread.daemon = True self.up_thread.start() diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index aac5c07e53..8f27d4763d 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -77,6 +77,9 @@ class Uploader: self.last_resp: Optional[UploadResponse] = None self.last_exc: Optional[Tuple[Exception, str]] = None + self.immediate_size = 0 + self.immediate_count = 0 + # stats for last successfully uploaded file self.last_time = 0.0 self.last_speed = 0.0 @@ -85,10 +88,18 @@ class Uploader: self.immediate_folders = ["crash/", "boot/"] self.immediate_priority = {"qlog": 0, "qlog.bz2": 0, "qcamera.ts": 1} + def get_upload_sort(self, name: str) -> int: + if name in self.immediate_priority: + return self.immediate_priority[name] + return 1000 + def list_upload_files(self) -> Iterator[Tuple[str, str, str]]: if not os.path.isdir(self.root): return + self.immediate_size = 0 + self.immediate_count = 0 + for logname in listdir_by_creation(self.root): path = os.path.join(self.root, logname) try: @@ -99,7 +110,7 @@ class Uploader: if any(name.endswith(".lock") for name in names): continue - for name in sorted(names, key=lambda n: self.immediate_priority.get(n, 1000)): + for name in sorted(names, key=self.get_upload_sort): key = os.path.join(logname, name) fn = os.path.join(path, name) # skip files already uploaded @@ -111,6 +122,13 @@ class Uploader: if is_uploaded: continue + try: + if name in self.immediate_priority: + self.immediate_count += 1 + self.immediate_size += os.path.getsize(fn) + except OSError: + pass + yield name, key, fn def next_file_to_upload(self) -> Optional[Tuple[str, str, str]]: @@ -209,25 +227,18 @@ class Uploader: return success - - def step(self, network_type: int, metered: bool) -> bool: - d = self.next_file_to_upload() - if d is None: - return True - - name, key, fn = d - - # qlogs and bootlogs need to be compressed before uploading - if key.endswith(('qlog', 'rlog')) or (key.startswith('boot/') and not key.endswith('.bz2')): - key += ".bz2" - - return self.upload(name, key, fn, network_type, metered) + def get_msg(self): + msg = messaging.new_message("uploaderState", valid=True) + us = msg.uploaderState + us.immediateQueueSize = int(self.immediate_size / 1e6) + us.immediateQueueCount = self.immediate_count + us.lastTime = self.last_time + us.lastSpeed = self.last_speed + us.lastFilename = self.last_filename + return msg -def main(exit_event: Optional[threading.Event] = None) -> None: - if exit_event is None: - exit_event = threading.Event() - +def uploader_fn(exit_event: threading.Event) -> None: try: set_core_affinity([0, 1, 2, 3]) except Exception: @@ -246,6 +257,7 @@ def main(exit_event: Optional[threading.Event] = None) -> None: cloudlog.warning("NVME not mounted") sm = messaging.SubMaster(['deviceState']) + pm = messaging.PubMaster(['uploaderState']) uploader = Uploader(dongle_id, Paths.log_root()) backoff = 0.1 @@ -258,8 +270,19 @@ def main(exit_event: Optional[threading.Event] = None) -> None: time.sleep(60 if offroad else 5) continue - success = uploader.step(sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) + d = uploader.next_file_to_upload() + if d is None: # Nothing to upload + if allow_sleep: + time.sleep(60 if offroad else 5) + continue + name, key, fn = d + + # qlogs and bootlogs need to be compressed before uploading + if key.endswith(('qlog', 'rlog')) or (key.startswith('boot/') and not key.endswith('.bz2')): + key += ".bz2" + + success = uploader.upload(name, key, fn, sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) if success: backoff = 0.1 elif allow_sleep: @@ -267,6 +290,12 @@ def main(exit_event: Optional[threading.Event] = None) -> None: time.sleep(backoff + random.uniform(0, backoff)) backoff = min(backoff*2, 120) + pm.send("uploaderState", uploader.get_msg()) + + +def main() -> None: + uploader_fn(threading.Event()) + if __name__ == "__main__": main() From 3f0b56b36472b04dca653ac5870f10d4bd53bc01 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 17 Jan 2024 17:21:47 -0500 Subject: [PATCH 478/724] cleanup azure handling (#31034) * wip cleanup * Wip * fixes * fix * cleanup * keep this for now * dest --- selfdrive/test/update_ci_routes.py | 39 ++++++---------- tools/lib/azure_container.py | 74 ++++++++++++++++++++++++++++++ tools/lib/openpilotci.py | 70 ++++------------------------ tools/lib/openpilotcontainers.py | 6 +++ 4 files changed, 102 insertions(+), 87 deletions(-) create mode 100644 tools/lib/azure_container.py mode change 100755 => 100644 tools/lib/openpilotci.py create mode 100755 tools/lib/openpilotcontainers.py diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 2a841025e3..5ab5042b2b 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -3,33 +3,21 @@ import os import re import subprocess import sys -from functools import lru_cache -from typing import Iterable, Optional +from typing import Iterable, List, Optional -from azure.storage.blob import ContainerClient from tqdm import tqdm from openpilot.selfdrive.car.tests.routes import routes as test_car_models_routes from openpilot.selfdrive.test.process_replay.test_processes import source_segments as replay_segments -from openpilot.tools.lib.openpilotci import (DATA_CI_ACCOUNT, DATA_CI_ACCOUNT_URL, OPENPILOT_CI_CONTAINER, - DATA_CI_CONTAINER, get_azure_credential, get_container_sas, upload_file) +from openpilot.tools.lib.azure_container import AzureContainer +from openpilot.tools.lib.openpilotcontainers import DataCIContainer, DataProdContainer, OpenpilotCIContainer -DATA_PROD_ACCOUNT = "commadata2" -DATA_PROD_CONTAINER = "commadata2" - -SOURCES = [ - (DATA_PROD_ACCOUNT, DATA_PROD_CONTAINER), - (DATA_CI_ACCOUNT, DATA_CI_CONTAINER), +SOURCES: List[AzureContainer] = [ + DataProdContainer, + DataCIContainer ] - -@lru_cache -def get_azure_keys(): - dest_container = ContainerClient(DATA_CI_ACCOUNT_URL, OPENPILOT_CI_CONTAINER, credential=get_azure_credential()) - dest_key = get_container_sas(DATA_CI_ACCOUNT, OPENPILOT_CI_CONTAINER) - source_keys = [get_container_sas(*s) for s in SOURCES] - return dest_container, dest_key, source_keys - +DEST = OpenpilotCIContainer def upload_route(path: str, exclude_patterns: Optional[Iterable[str]] = None) -> None: if exclude_patterns is None: @@ -41,11 +29,11 @@ def upload_route(path: str, exclude_patterns: Optional[Iterable[str]] = None) -> for file in os.listdir(path): if any(re.search(pattern, file) for pattern in exclude_patterns): continue - upload_file(os.path.join(path, file), f"{destpath}/{file}") + DEST.upload_file(os.path.join(path, file), f"{destpath}/{file}") def sync_to_ci_public(route: str) -> bool: - dest_container, dest_key, source_keys = get_azure_keys() + dest_container, dest_key = DEST.get_client_and_key() key_prefix = route.replace('|', '/') dongle_id = key_prefix.split('/')[0] @@ -53,14 +41,15 @@ def sync_to_ci_public(route: str) -> bool: return True print(f"Uploading {route}") - for (source_account, source_bucket), source_key in zip(SOURCES, source_keys, strict=True): + for source_container in SOURCES: # assumes az login has been run - print(f"Trying {source_account}/{source_bucket}") + print(f"Trying {source_container.ACCOUNT}/{source_container.CONTAINER}") + _, source_key = source_container.get_client_and_key() cmd = [ "azcopy", "copy", - f"https://{source_account}.blob.core.windows.net/{source_bucket}/{key_prefix}?{source_key}", - f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net/{OPENPILOT_CI_CONTAINER}/{dongle_id}?{dest_key}", + f"{source_container.BASE_URL}{key_prefix}?{source_key}", + f"{DEST.BASE_URL}{dongle_id}?{dest_key}", "--recursive=true", "--overwrite=false", "--exclude-pattern=*/dcamera.hevc", diff --git a/tools/lib/azure_container.py b/tools/lib/azure_container.py new file mode 100644 index 0000000000..aa132a92f3 --- /dev/null +++ b/tools/lib/azure_container.py @@ -0,0 +1,74 @@ +import os +from datetime import datetime, timedelta +from functools import lru_cache +from pathlib import Path +from typing import IO, Union + +from azure.storage.blob import ContainerClient + +TOKEN_PATH = Path("/data/azure_token") + +@lru_cache +def get_azure_credential(): + if "AZURE_TOKEN" in os.environ: + return os.environ["AZURE_TOKEN"] + elif TOKEN_PATH.is_file(): + return TOKEN_PATH.read_text().strip() + else: + from azure.identity import AzureCliCredential + return AzureCliCredential() + +@lru_cache +def get_container_sas(account_name: str, container_name: str): + from azure.storage.blob import BlobServiceClient, ContainerSasPermissions, generate_container_sas + start_time = datetime.utcnow() + expiry_time = start_time + timedelta(hours=1) + blob_service = BlobServiceClient( + account_url=f"https://{account_name}.blob.core.windows.net", + credential=get_azure_credential(), + ) + return generate_container_sas( + account_name, + container_name, + user_delegation_key=blob_service.get_user_delegation_key(start_time, expiry_time), + permission=ContainerSasPermissions(read=True, write=True, list=True), + expiry=expiry_time, + ) + +class AzureContainer: + def __init__(self, account, container): + self.ACCOUNT = account + self.CONTAINER = container + + @property + def ACCOUNT_URL(self) -> str: + return f"https://{self.ACCOUNT}.blob.core.windows.net" + + @property + def BASE_URL(self) -> str: + return f"{self.ACCOUNT_URL}/{self.CONTAINER}/" + + def get_client_and_key(self): + client = ContainerClient(self.ACCOUNT_URL, self.CONTAINER, credential=get_azure_credential()) + key = get_container_sas(self.ACCOUNT, self.CONTAINER) + return client, key + + def get_url(self, route_name: str, segment_num, log_type="rlog") -> str: + ext = "hevc" if log_type.endswith('camera') else "bz2" + return self.BASE_URL + f"{route_name.replace('|', '/')}/{segment_num}/{log_type}.{ext}" + + def upload_bytes(self, data: Union[bytes, IO], blob_name: str) -> str: + from azure.storage.blob import BlobClient + blob = BlobClient( + account_url=self.ACCOUNT_URL, + container_name=self.CONTAINER, + blob_name=blob_name, + credential=get_azure_credential(), + overwrite=False, + ) + blob.upload_blob(data) + return self.BASE_URL + blob_name + + def upload_file(self, path: Union[str, os.PathLike], blob_name: str) -> str: + with open(path, "rb") as f: + return self.upload_bytes(f, blob_name) diff --git a/tools/lib/openpilotci.py b/tools/lib/openpilotci.py old mode 100755 new mode 100644 index 4e0da1bccf..1c1e1f171b --- a/tools/lib/openpilotci.py +++ b/tools/lib/openpilotci.py @@ -1,66 +1,12 @@ -#!/usr/bin/env python3 -import os -from datetime import datetime, timedelta -from functools import lru_cache -from pathlib import Path -from typing import IO, Union +from openpilot.tools.lib.openpilotcontainers import OpenpilotCIContainer -DATA_CI_ACCOUNT = "commadataci" -DATA_CI_ACCOUNT_URL = f"https://{DATA_CI_ACCOUNT}.blob.core.windows.net" -OPENPILOT_CI_CONTAINER = "openpilotci" -DATA_CI_CONTAINER = "commadataci" -BASE_URL = f"{DATA_CI_ACCOUNT_URL}/{OPENPILOT_CI_CONTAINER}/" +def get_url(*args, **kwargs): + return OpenpilotCIContainer.get_url(*args, **kwargs) -TOKEN_PATH = Path("/data/azure_token") +def upload_file(*args, **kwargs): + return OpenpilotCIContainer.upload_file(*args, **kwargs) +def upload_bytes(*args, **kwargs): + return OpenpilotCIContainer.upload_bytes(*args, **kwargs) -def get_url(route_name: str, segment_num, log_type="rlog") -> str: - ext = "hevc" if log_type.endswith('camera') else "bz2" - return BASE_URL + f"{route_name.replace('|', '/')}/{segment_num}/{log_type}.{ext}" - - -@lru_cache -def get_azure_credential(): - if "AZURE_TOKEN" in os.environ: - return os.environ["AZURE_TOKEN"] - elif TOKEN_PATH.is_file(): - return TOKEN_PATH.read_text().strip() - else: - from azure.identity import AzureCliCredential - return AzureCliCredential() - - -@lru_cache -def get_container_sas(account_name: str, container_name: str): - from azure.storage.blob import BlobServiceClient, ContainerSasPermissions, generate_container_sas - start_time = datetime.utcnow() - expiry_time = start_time + timedelta(hours=1) - blob_service = BlobServiceClient( - account_url=f"https://{account_name}.blob.core.windows.net", - credential=get_azure_credential(), - ) - return generate_container_sas( - account_name, - container_name, - user_delegation_key=blob_service.get_user_delegation_key(start_time, expiry_time), - permission=ContainerSasPermissions(read=True, write=True, list=True), - expiry=expiry_time, - ) - - -def upload_bytes(data: Union[bytes, IO], blob_name: str) -> str: - from azure.storage.blob import BlobClient - blob = BlobClient( - account_url=DATA_CI_ACCOUNT_URL, - container_name=OPENPILOT_CI_CONTAINER, - blob_name=blob_name, - credential=get_azure_credential(), - overwrite=False, - ) - blob.upload_blob(data) - return BASE_URL + blob_name - - -def upload_file(path: Union[str, os.PathLike], blob_name: str) -> str: - with open(path, "rb") as f: - return upload_bytes(f, blob_name) +BASE_URL = OpenpilotCIContainer.BASE_URL diff --git a/tools/lib/openpilotcontainers.py b/tools/lib/openpilotcontainers.py new file mode 100755 index 0000000000..9882461111 --- /dev/null +++ b/tools/lib/openpilotcontainers.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +from openpilot.tools.lib.azure_container import AzureContainer + +OpenpilotCIContainer = AzureContainer("commadataci", "openpilotci") +DataCIContainer = AzureContainer("commadataci", "commadataci") +DataProdContainer = AzureContainer("commadata2", "commadata2") From 0b5fd7287e715707e32b7e6978e296918dd34234 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Jan 2024 14:24:09 -0800 Subject: [PATCH 479/724] uploader cleanup (#31035) * Reapply "uploader cleanup" (#31033) This reverts commit 5e4df41b2f4c379157844dc4c19ef496ca658088. * always sleep * more cleanup * little more * fix linter * little more --- system/loggerd/tests/loggerd_tests_common.py | 28 +--- system/loggerd/tests/test_uploader.py | 4 +- system/loggerd/uploader.py | 159 +++++++------------ 3 files changed, 62 insertions(+), 129 deletions(-) diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 8bfb571861..3aa9e40531 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -3,10 +3,12 @@ import random import unittest from pathlib import Path from typing import Optional -from openpilot.system.hardware.hw import Paths + import openpilot.system.loggerd.deleter as deleter import openpilot.system.loggerd.uploader as uploader +from openpilot.common.params import Params +from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import setxattr @@ -53,25 +55,6 @@ class MockApiIgnore(): def get_token(self): return "fake-token" -class MockParams(): - def __init__(self): - self.params = { - "DongleId": b"0000000000000000", - "IsOffroad": b"1", - } - - def get(self, k, block=False, encoding=None): - val = self.params[k] - - if encoding is not None: - return val.decode(encoding) - else: - return val - - def get_bool(self, k): - val = self.params[k] - return (val == b'1') - class UploaderTestCase(unittest.TestCase): f_type = "UNKNOWN" @@ -86,7 +69,6 @@ class UploaderTestCase(unittest.TestCase): def setUp(self): uploader.Api = MockApi - uploader.Params = MockParams uploader.fake_upload = True uploader.force_wifi = True uploader.allow_sleep = False @@ -95,6 +77,10 @@ class UploaderTestCase(unittest.TestCase): self.seg_format2 = "2019-05-18--11-22-33--{}" self.seg_dir = self.seg_format.format(self.seg_num) + self.params = Params() + self.params.put("IsOffroad", "1") + self.params.put("DongleId", "0000000000000000") + def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, upload_xattr: Optional[bytes] = None, preserve_xattr: Optional[bytes] = None) -> Path: file_path = Path(Paths.log_root()) / f_dir / fn diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index 538d99f66f..b674de5438 100755 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -10,7 +10,7 @@ from typing import List, Optional from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog -from openpilot.system.loggerd.uploader import uploader_fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE +from openpilot.system.loggerd.uploader import main, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE from openpilot.system.loggerd.tests.loggerd_tests_common import UploaderTestCase @@ -45,7 +45,7 @@ class TestUploader(UploaderTestCase): def start_thread(self): self.end_event = threading.Event() - self.up_thread = threading.Thread(target=uploader_fn, args=[self.end_event]) + self.up_thread = threading.Thread(target=main, args=[self.end_event]) self.up_thread.daemon = True self.up_thread.start() diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 8f27d4763d..65259d1e45 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -8,15 +8,13 @@ import requests import threading import time import traceback -from pathlib import Path -from typing import BinaryIO, Iterator, List, Optional, Tuple, Union +from typing import BinaryIO, Iterator, List, Optional, Tuple from cereal import log import cereal.messaging as messaging from openpilot.common.api import Api from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity -from openpilot.system.hardware import TICI from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import getxattr, setxattr from openpilot.common.swaglog import cloudlog @@ -43,8 +41,6 @@ class FakeResponse: self.request = FakeRequest() -UploadResponse = Union[requests.Response, FakeResponse] - def get_directory_sort(d: str) -> List[str]: return [s.rjust(10, '0') for s in d.rsplit('--', 1)] @@ -74,32 +70,16 @@ class Uploader: self.api = Api(dongle_id) self.root = root - self.last_resp: Optional[UploadResponse] = None - self.last_exc: Optional[Tuple[Exception, str]] = None - - self.immediate_size = 0 - self.immediate_count = 0 - # stats for last successfully uploaded file - self.last_time = 0.0 - self.last_speed = 0.0 self.last_filename = "" self.immediate_folders = ["crash/", "boot/"] self.immediate_priority = {"qlog": 0, "qlog.bz2": 0, "qcamera.ts": 1} - def get_upload_sort(self, name: str) -> int: - if name in self.immediate_priority: - return self.immediate_priority[name] - return 1000 - def list_upload_files(self) -> Iterator[Tuple[str, str, str]]: if not os.path.isdir(self.root): return - self.immediate_size = 0 - self.immediate_count = 0 - for logname in listdir_by_creation(self.root): path = os.path.join(self.root, logname) try: @@ -110,25 +90,18 @@ class Uploader: if any(name.endswith(".lock") for name in names): continue - for name in sorted(names, key=self.get_upload_sort): + for name in sorted(names, key=lambda n: self.immediate_priority.get(n, 1000)): key = os.path.join(logname, name) fn = os.path.join(path, name) # skip files already uploaded try: is_uploaded = getxattr(fn, UPLOAD_ATTR_NAME) == UPLOAD_ATTR_VALUE except OSError: - cloudlog.event("uploader_getxattr_failed", exc=self.last_exc, key=key, fn=fn) + cloudlog.event("uploader_getxattr_failed", key=key, fn=fn) is_uploaded = True # deleter could have deleted if is_uploaded: continue - try: - if name in self.immediate_priority: - self.immediate_count += 1 - self.immediate_size += os.path.getsize(fn) - except OSError: - pass - yield name, key, fn def next_file_to_upload(self) -> Optional[Tuple[str, str, str]]: @@ -144,45 +117,28 @@ class Uploader: return None - def do_upload(self, key: str, fn: str) -> None: - try: - url_resp = self.api.get("v1.4/" + self.dongle_id + "/upload_url/", timeout=10, path=key, access_token=self.api.get_token()) - if url_resp.status_code == 412: - self.last_resp = url_resp - return + def do_upload(self, key: str, fn: str): + url_resp = self.api.get("v1.4/" + self.dongle_id + "/upload_url/", timeout=10, path=key, access_token=self.api.get_token()) + if url_resp.status_code == 412: + return url_resp - url_resp_json = json.loads(url_resp.text) - url = url_resp_json['url'] - headers = url_resp_json['headers'] - cloudlog.debug("upload_url v1.4 %s %s", url, str(headers)) + url_resp_json = json.loads(url_resp.text) + url = url_resp_json['url'] + headers = url_resp_json['headers'] + cloudlog.debug("upload_url v1.4 %s %s", url, str(headers)) - if fake_upload: - cloudlog.debug(f"*** WARNING, THIS IS A FAKE UPLOAD TO {url} ***") - self.last_resp = FakeResponse() + if fake_upload: + return FakeResponse() + + with open(fn, "rb") as f: + data: BinaryIO + if key.endswith('.bz2') and not fn.endswith('.bz2'): + compressed = bz2.compress(f.read()) + data = io.BytesIO(compressed) else: - with open(fn, "rb") as f: - data: BinaryIO - if key.endswith('.bz2') and not fn.endswith('.bz2'): - compressed = bz2.compress(f.read()) - data = io.BytesIO(compressed) - else: - data = f + data = f - self.last_resp = requests.put(url, data=data, headers=headers, timeout=10) - except Exception as e: - self.last_exc = (e, traceback.format_exc()) - raise - - def normal_upload(self, key: str, fn: str) -> Optional[UploadResponse]: - self.last_resp = None - self.last_exc = None - - try: - self.do_upload(key, fn) - except Exception: - pass - - return self.last_resp + return requests.put(url, data=data, headers=headers, timeout=10) def upload(self, name: str, key: str, fn: str, network_type: int, metered: bool) -> bool: try: @@ -201,44 +157,57 @@ class Uploader: success = True else: start_time = time.monotonic() - stat = self.normal_upload(key, fn) + + stat = None + last_exc = None + try: + stat = self.do_upload(key, fn) + except Exception as e: + last_exc = (e, traceback.format_exc()) + if stat is not None and stat.status_code in (200, 201, 401, 403, 412): self.last_filename = fn - self.last_time = time.monotonic() - start_time + dt = time.monotonic() - start_time if stat.status_code == 412: - self.last_speed = 0 cloudlog.event("upload_ignored", key=key, fn=fn, sz=sz, network_type=network_type, metered=metered) else: content_length = int(stat.request.headers.get("Content-Length", 0)) - self.last_speed = (content_length / 1e6) / self.last_time + speed = (content_length / 1e6) / dt cloudlog.event("upload_success", key=key, fn=fn, sz=sz, content_length=content_length, - network_type=network_type, metered=metered, speed=self.last_speed) + network_type=network_type, metered=metered, speed=speed) success = True else: success = False - cloudlog.event("upload_failed", stat=stat, exc=self.last_exc, key=key, fn=fn, sz=sz, network_type=network_type, metered=metered) + cloudlog.event("upload_failed", stat=stat, exc=last_exc, key=key, fn=fn, sz=sz, network_type=network_type, metered=metered) if success: # tag file as uploaded try: setxattr(fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE) except OSError: - cloudlog.event("uploader_setxattr_failed", exc=self.last_exc, key=key, fn=fn, sz=sz) + cloudlog.event("uploader_setxattr_failed", exc=last_exc, key=key, fn=fn, sz=sz) return success - def get_msg(self): - msg = messaging.new_message("uploaderState", valid=True) - us = msg.uploaderState - us.immediateQueueSize = int(self.immediate_size / 1e6) - us.immediateQueueCount = self.immediate_count - us.lastTime = self.last_time - us.lastSpeed = self.last_speed - us.lastFilename = self.last_filename - return msg + + def step(self, network_type: int, metered: bool) -> bool: + d = self.next_file_to_upload() + if d is None: + return True + + name, key, fn = d + + # qlogs and bootlogs need to be compressed before uploading + if key.endswith(('qlog', 'rlog')) or (key.startswith('boot/') and not key.endswith('.bz2')): + key += ".bz2" + + return self.upload(name, key, fn, network_type, metered) -def uploader_fn(exit_event: threading.Event) -> None: +def main(exit_event: Optional[threading.Event] = None) -> None: + if exit_event is None: + exit_event = threading.Event() + try: set_core_affinity([0, 1, 2, 3]) except Exception: @@ -253,11 +222,7 @@ def uploader_fn(exit_event: threading.Event) -> None: cloudlog.info("uploader missing dongle_id") raise Exception("uploader can't start without dongle id") - if TICI and not Path("/data/media").is_mount(): - cloudlog.warning("NVME not mounted") - sm = messaging.SubMaster(['deviceState']) - pm = messaging.PubMaster(['uploaderState']) uploader = Uploader(dongle_id, Paths.log_root()) backoff = 0.1 @@ -270,31 +235,13 @@ def uploader_fn(exit_event: threading.Event) -> None: time.sleep(60 if offroad else 5) continue - d = uploader.next_file_to_upload() - if d is None: # Nothing to upload - if allow_sleep: - time.sleep(60 if offroad else 5) - continue - - name, key, fn = d - - # qlogs and bootlogs need to be compressed before uploading - if key.endswith(('qlog', 'rlog')) or (key.startswith('boot/') and not key.endswith('.bz2')): - key += ".bz2" - - success = uploader.upload(name, key, fn, sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) + success = uploader.step(sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) if success: backoff = 0.1 elif allow_sleep: cloudlog.info("upload backoff %r", backoff) - time.sleep(backoff + random.uniform(0, backoff)) backoff = min(backoff*2, 120) - - pm.send("uploaderState", uploader.get_msg()) - - -def main() -> None: - uploader_fn(threading.Event()) + time.sleep(backoff + random.uniform(0, backoff)) if __name__ == "__main__": From 8c544d91392b6039c13b70657a0502ab1f993c76 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Jan 2024 14:39:31 -0800 Subject: [PATCH 480/724] Fix f-string in test_fw_query_on_routes.py --- selfdrive/debug/test_fw_query_on_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 20c5486c32..78f7a5d3e5 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -113,7 +113,7 @@ if __name__ == "__main__": for version in sorted(car_fw, key=lambda fw: fw.brand): subaddr = None if version.subAddress == 0 else hex(version.subAddress) print(f" Brand: {version.brand or UNKNOWN_BRAND:{padding}}, bus: {version.bus} - " + - "(Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],") + f"(Ecu.{version.ecu}, {hex(version.address)}, {subaddr}): [{version.fwVersion}],") print("Mismatches") found = False From 7ce29ef08e9ee9c930238f697bfe3b69f6be368e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Jan 2024 14:42:07 -0800 Subject: [PATCH 481/724] cleanup old params (#31039) * cleanup old params * one more --- common/params.cc | 6 ------ selfdrive/athena/registration.py | 1 - system/hardware/base.py | 4 ---- system/hardware/pc/hardware.py | 3 --- system/hardware/tici/hardware.py | 7 ------- 5 files changed, 21 deletions(-) diff --git a/common/params.cc b/common/params.cc index 7b14ac6955..b1fc15e4c5 100644 --- a/common/params.cc +++ b/common/params.cc @@ -144,16 +144,13 @@ std::unordered_map keys = { {"IsReleaseBranch", CLEAR_ON_MANAGER_START}, {"IsTakingSnapshot", CLEAR_ON_MANAGER_START}, {"IsTestedBranch", CLEAR_ON_MANAGER_START}, - {"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, - {"LaikadEphemerisV3", PERSISTENT | DONT_LOG}, {"LanguageSetting", PERSISTENT}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, {"LastManagerExitReason", CLEAR_ON_MANAGER_START}, {"LastOffroadStatusPacket", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"LastPowerDropDetected", CLEAR_ON_MANAGER_START}, - {"LastSystemShutdown", CLEAR_ON_MANAGER_START}, {"LastUpdateException", CLEAR_ON_MANAGER_START}, {"LastUpdateTime", PERSISTENT}, {"LiveParameters", PERSISTENT}, @@ -164,7 +161,6 @@ std::unordered_map keys = { {"NavPastDestinations", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, {"NavSettingTime24h", PERSISTENT}, - {"NavdRender", PERSISTENT}, {"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"Offroad_BadNvme", CLEAR_ON_MANAGER_START}, @@ -189,10 +185,8 @@ std::unordered_map keys = { {"RecordFront", PERSISTENT}, {"RecordFrontLock", PERSISTENT}, // for the internal fleet {"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, - {"ShouldDoUpdate", CLEAR_ON_MANAGER_START}, {"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"SshEnabled", PERSISTENT}, - {"SubscriberInfo", PERSISTENT}, {"TermsVersion", PERSISTENT}, {"Timezone", PERSISTENT}, {"TrainingVersion", PERSISTENT}, diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index c9a4b949ac..7db94c28c8 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -25,7 +25,6 @@ def is_registered_device() -> bool: def register(show_spinner=False) -> Optional[str]: params = Params() - params.put("SubscriberInfo", HARDWARE.get_subscriber_info()) IMEI = params.get("IMEI", encoding='utf8') HardwareSerial = params.get("HardwareSerial", encoding='utf8') diff --git a/system/hardware/base.py b/system/hardware/base.py index b432a41907..9c7a618337 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -54,10 +54,6 @@ class HardwareBase(ABC): def get_serial(self): pass - @abstractmethod - def get_subscriber_info(self): - pass - @abstractmethod def get_network_info(self): pass diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index 27c05f5904..4c2c104f94 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -29,9 +29,6 @@ class Pc(HardwareBase): def get_serial(self): return "cccccccc" - def get_subscriber_info(self): - return "" - def get_network_info(self): return None diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index d1ac52c8dc..3431718e22 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -206,9 +206,6 @@ class Tici(HardwareBase): 'data_connected': modem.Get(MM_MODEM, 'State', dbus_interface=DBUS_PROPS, timeout=TIMEOUT) == MM_MODEM_STATE.CONNECTED, } - def get_subscriber_info(self): - return "" - def get_imei(self, slot): if slot != 0: return "" @@ -337,10 +334,6 @@ class Tici(HardwareBase): pass return ret - def get_usb_present(self): - # Not sure if relevant on tici, but the file exists - return self.read_param_file("/sys/class/power_supply/usb/present", lambda x: bool(int(x)), False) - def get_current_power_draw(self): return (self.read_param_file("/sys/class/hwmon/hwmon1/power1_input", int) / 1e6) From 5f1e7649e3725667a7a753a89f31834aa28eb2b1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Jan 2024 15:08:36 -0800 Subject: [PATCH 482/724] update param in loggerd unit test --- system/loggerd/tests/test_loggerd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 9d7d3fa7bd..49d97505e8 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -116,7 +116,7 @@ class TestLoggerd(unittest.TestCase): params.clear_all() for k, _, v in fake_params: params.put(k, v) - params.put("LaikadEphemerisV3", "abc") + params.put("AccessToken", "abc") lr = list(LogReader(str(self._gen_bootlog()))) initData = lr[0].initData @@ -133,14 +133,14 @@ class TestLoggerd(unittest.TestCase): # check params logged_params = {entry.key: entry.value for entry in initData.params.entries} - expected_params = {k for k, _, __ in fake_params} | {'LaikadEphemerisV3'} + expected_params = {k for k, _, __ in fake_params} | {'AccessToken'} assert set(logged_params.keys()) == expected_params, set(logged_params.keys()) ^ expected_params - assert logged_params['LaikadEphemerisV3'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['LaikadEphemerisV3'])}" + assert logged_params['AccessToken'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['AccessToken'])}" for param_key, initData_key, v in fake_params: self.assertEqual(getattr(initData, initData_key), v) self.assertEqual(logged_params[param_key].decode(), v) - params.put("LaikadEphemerisV3", "") + params.put("AccessToken", "") def test_rotation(self): os.environ["LOGGERD_TEST"] = "1" From 840db1f1b70e5014f504908eb1beb6358c555cf2 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 17 Jan 2024 18:34:24 -0500 Subject: [PATCH 483/724] prevent typing issue in azure_container (#31044) prvent that issue --- tools/lib/azure_container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lib/azure_container.py b/tools/lib/azure_container.py index aa132a92f3..7d9550266d 100644 --- a/tools/lib/azure_container.py +++ b/tools/lib/azure_container.py @@ -4,7 +4,6 @@ from functools import lru_cache from pathlib import Path from typing import IO, Union -from azure.storage.blob import ContainerClient TOKEN_PATH = Path("/data/azure_token") @@ -49,6 +48,7 @@ class AzureContainer: return f"{self.ACCOUNT_URL}/{self.CONTAINER}/" def get_client_and_key(self): + from azure.storage.blob import ContainerClient client = ContainerClient(self.ACCOUNT_URL, self.CONTAINER, credential=get_azure_credential()) key = get_container_sas(self.ACCOUNT, self.CONTAINER) return client, key From d36103791c9432155413e1044bcca2ac37bfa852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 17 Jan 2024 16:00:50 -0800 Subject: [PATCH 484/724] No curv rate for lateral control (#31042) * No more Curvature rate for lat control * Update cereal * Update * Fix lat control test --- cereal | 2 +- selfdrive/controls/controlsd.py | 9 ++------- selfdrive/controls/lib/drive_helpers.py | 9 ++------- selfdrive/controls/lib/latcontrol.py | 2 +- selfdrive/controls/lib/latcontrol_angle.py | 2 +- selfdrive/controls/lib/latcontrol_pid.py | 2 +- selfdrive/controls/lib/latcontrol_torque.py | 2 +- selfdrive/controls/lib/tests/test_latcontrol.py | 2 +- 8 files changed, 10 insertions(+), 20 deletions(-) diff --git a/cereal b/cereal index d81d86e7cd..20b65eeb1f 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit d81d86e7cd83d1eb40314964a4d194231381d557 +Subproject commit 20b65eeb1f6c580cdd7d63e53639f4fc48bc2f56 diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 698c149515..78bcf8a9b0 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -172,7 +172,6 @@ class Controls: self.last_actuators = car.CarControl.Actuators.new_message() self.steer_limited = False self.desired_curvature = 0.0 - self.desired_curvature_rate = 0.0 self.experimental_mode = False self.v_cruise_helper = VCruiseHelper(self.CP) self.recalibrating_seen = False @@ -611,13 +610,10 @@ class Controls: actuators.accel = self.LoC.update(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan) # Steering PID loop and lateral MPC - self.desired_curvature, self.desired_curvature_rate = get_lag_adjusted_curvature(self.CP, CS.vEgo, - lat_plan.psis, - lat_plan.curvatures, - lat_plan.curvatureRates) + self.desired_curvature = get_lag_adjusted_curvature(self.CP, CS.vEgo, lat_plan.psis, lat_plan.curvatures) actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, self.steer_limited, self.desired_curvature, - self.desired_curvature_rate, self.sm['liveLocationKalman']) + self.sm['liveLocationKalman']) actuators.curvature = self.desired_curvature else: lac_log = log.ControlsState.LateralDebugState.new_message() @@ -787,7 +783,6 @@ class Controls: controlsState.active = self.active controlsState.curvature = curvature controlsState.desiredCurvature = self.desired_curvature - controlsState.desiredCurvatureRate = self.desired_curvature_rate controlsState.state = self.state controlsState.engageable = not self.events.contains(ET.NO_ENTRY) controlsState.longControlState = self.LoC.long_control_state diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index b728f1114c..9a5d1779fc 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -163,11 +163,10 @@ def rate_limit(new_value, last_value, dw_step, up_step): return clip(new_value, last_value + dw_step, last_value + up_step) -def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): +def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures): if len(psis) != CONTROL_N: psis = [0.0]*CONTROL_N curvatures = [0.0]*CONTROL_N - curvature_rates = [0.0]*CONTROL_N v_ego = max(MIN_SPEED, v_ego) # TODO this needs more thought, use .2s extra for now to estimate other delays @@ -182,16 +181,12 @@ def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): desired_curvature = 2 * average_curvature_desired - current_curvature_desired # This is the "desired rate of the setpoint" not an actual desired rate - desired_curvature_rate = curvature_rates[0] max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 - safe_desired_curvature_rate = clip(desired_curvature_rate, - -max_curvature_rate, - max_curvature_rate) safe_desired_curvature = clip(desired_curvature, current_curvature_desired - max_curvature_rate * DT_MDL, current_curvature_desired + max_curvature_rate * DT_MDL) - return safe_desired_curvature, safe_desired_curvature_rate + return safe_desired_curvature def get_friction(lateral_accel_error: float, lateral_accel_deadzone: float, friction_threshold: float, diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 723af7f806..fddb331ccb 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -17,7 +17,7 @@ class LatControl(ABC): self.steer_max = 1.0 @abstractmethod - def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk): pass def reset(self): diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index d363295f0c..329c486eb9 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -11,7 +11,7 @@ class LatControlAngle(LatControl): super().__init__(CP, CI) self.sat_check_min_speed = 5. - def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk): angle_log = log.ControlsState.LateralAngleState.new_message() if not active: diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index c673159ebb..9e6160838b 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -17,7 +17,7 @@ class LatControlPID(LatControl): super().reset() self.pid.reset() - def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk): pid_log = log.ControlsState.LateralPIDState.new_message() pid_log.steeringAngleDeg = float(CS.steeringAngleDeg) pid_log.steeringRateDeg = float(CS.steeringRateDeg) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index f46ab9eb6c..65fd1b51c5 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -36,7 +36,7 @@ class LatControlTorque(LatControl): self.torque_params.latAccelOffset = latAccelOffset self.torque_params.friction = friction - def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk): + def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk): pid_log = log.ControlsState.LateralTorqueState.new_message() if not active: diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index 5faad914cf..248abf1d7b 100755 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -34,7 +34,7 @@ class TestLatControl(unittest.TestCase): llk = gen_llk() for _ in range(1000): - _, _, lac_log = controller.update(True, CS, VM, params, False, 1, 0, llk) + _, _, lac_log = controller.update(True, CS, VM, params, False, 1, llk) self.assertTrue(lac_log.saturated) From 4c2bb9f380a93149864d11096002ada4d002a94f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Jan 2024 16:30:08 -0800 Subject: [PATCH 485/724] updated: only fetch on metered connection when necessary (#31041) * updated: only fetch on metered connection when necessary * button always fetches --- common/params.cc | 2 ++ selfdrive/thermald/thermald.py | 2 ++ selfdrive/updated.py | 48 ++++++++++++++++++++++------------ 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/common/params.cc b/common/params.cc index b1fc15e4c5..b416b801a8 100644 --- a/common/params.cc +++ b/common/params.cc @@ -161,6 +161,7 @@ std::unordered_map keys = { {"NavPastDestinations", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, {"NavSettingTime24h", PERSISTENT}, + {"NetworkMetered", PERSISTENT}, {"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"Offroad_BadNvme", CLEAR_ON_MANAGER_START}, @@ -201,6 +202,7 @@ std::unordered_map keys = { {"UpdaterNewReleaseNotes", CLEAR_ON_MANAGER_START}, {"UpdaterState", CLEAR_ON_MANAGER_START}, {"UpdaterTargetBranch", CLEAR_ON_MANAGER_START}, + {"UpdaterLastFetchTime", PERSISTENT}, {"Version", PERSISTENT}, {"VisionRadarToggle", PERSISTENT}, {"WheeledBody", PERSISTENT}, diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 7011ff0a99..75e091febb 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -447,6 +447,8 @@ def thermald_thread(end_event, hw_queue) -> None: except Exception: cloudlog.exception("failed to save offroad status") + params.put_bool_nonblocking("NetworkMetered", (msg.deviceState.networkType != NetworkType.wifi)) + count += 1 should_start_prev = should_start diff --git a/selfdrive/updated.py b/selfdrive/updated.py index a623aaefc8..8a46a11a78 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -35,26 +35,42 @@ OVERLAY_INIT = Path(os.path.join(BASEDIR, ".overlay_init")) DAYS_NO_CONNECTIVITY_MAX = 14 # do not allow to engage after this many days DAYS_NO_CONNECTIVITY_PROMPT = 10 # send an offroad prompt after this many days +class UserRequest: + NONE = 0 + CHECK = 1 + FETCH = 2 + class WaitTimeHelper: def __init__(self): self.ready_event = threading.Event() - self.only_check_for_update = False + self.user_request = UserRequest.NONE signal.signal(signal.SIGHUP, self.update_now) signal.signal(signal.SIGUSR1, self.check_now) def update_now(self, signum: int, frame) -> None: cloudlog.info("caught SIGHUP, attempting to downloading update") - self.only_check_for_update = False + self.user_request = UserRequest.FETCH self.ready_event.set() def check_now(self, signum: int, frame) -> None: cloudlog.info("caught SIGUSR1, checking for updates") - self.only_check_for_update = True + self.user_request = UserRequest.CHECK self.ready_event.set() def sleep(self, t: float) -> None: self.ready_event.wait(timeout=t) +def write_time_to_param(params, param) -> None: + t = datetime.datetime.utcnow() + params.put(param, t.isoformat().encode('utf8')) + +def read_time_from_param(params, param) -> Optional[datetime.datetime]: + t = params.get(param, encoding='utf8') + try: + return datetime.datetime.fromisoformat(t) + except (TypeError, ValueError): + pass + return None def run(cmd: List[str], cwd: Optional[str] = None) -> str: return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT, encoding='utf8') @@ -266,14 +282,11 @@ class Updater: last_update = datetime.datetime.utcnow() if update_success: - t = last_update.isoformat() - self.params.put("LastUpdateTime", t.encode('utf8')) + write_time_to_param(self.params, "LastUpdateTime") else: - try: - t = self.params.get("LastUpdateTime", encoding='utf8') - last_update = datetime.datetime.fromisoformat(t) - except (TypeError, ValueError): - pass + t = read_time_from_param(self.params, "LastUpdateTime") + if t is not None: + last_update = t if exception is None: self.params.remove("LastUpdateException") @@ -421,10 +434,7 @@ def main() -> None: updater = Updater() update_failed_count = 0 # TODO: Load from param? - - # no fetch on the first time wait_helper = WaitTimeHelper() - wait_helper.only_check_for_update = True # invalidate old finalized update set_consistent_flag(False) @@ -458,10 +468,16 @@ def main() -> None: updater.check_for_update() # download update - if wait_helper.only_check_for_update: - cloudlog.info("skipping fetch this cycle") + last_fetch = read_time_from_param(params, "UpdaterLastFetchTime") + timed_out = last_fetch is None or (datetime.datetime.utcnow() - last_fetch > datetime.timedelta(days=3)) + user_requested_fetch = wait_helper.user_request == UserRequest.FETCH + if params.get_bool("NetworkMetered") and not timed_out and not user_requested_fetch: + cloudlog.info("skipping fetch, connection metered") + elif wait_helper.user_request == UserRequest.CHECK: + cloudlog.info("skipping fetch, only checking") else: updater.fetch_update() + write_time_to_param(params, "UpdaterLastFetchTime") update_failed_count = 0 except subprocess.CalledProcessError as e: cloudlog.event( @@ -485,7 +501,7 @@ def main() -> None: cloudlog.exception("uncaught updated exception while setting params, shouldn't happen") # infrequent attempts if we successfully updated recently - wait_helper.only_check_for_update = False + wait_helper.user_request = UserRequest.NONE wait_helper.sleep(5*60 if update_failed_count > 0 else 1.5*60*60) From 19630c5af989fb62d1c920e54f838969c6158453 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Jan 2024 17:43:41 -0800 Subject: [PATCH 486/724] uploader: delay uploading crash and boot logs on metered connections (#31045) * uploader: delay uploading some files on metered connections * just crash and boot for now --- system/loggerd/uploader.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 65259d1e45..8b45627751 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -8,6 +8,7 @@ import requests import threading import time import traceback +import datetime from typing import BinaryIO, Iterator, List, Optional, Tuple from cereal import log @@ -45,17 +46,20 @@ def get_directory_sort(d: str) -> List[str]: return [s.rjust(10, '0') for s in d.rsplit('--', 1)] def listdir_by_creation(d: str) -> List[str]: + if not os.path.isdir(d): + return [] + try: paths = [f for f in os.listdir(d) if os.path.isdir(os.path.join(d, f))] paths = sorted(paths, key=get_directory_sort) return paths except OSError: cloudlog.exception("listdir_by_creation failed") - return list() + return [] def clear_locks(root: str) -> None: - for logname in os.listdir(root): - path = os.path.join(root, logname) + for logdir in os.listdir(root): + path = os.path.join(root, logdir) try: for fname in os.listdir(path): if fname.endswith(".lock"): @@ -76,12 +80,9 @@ class Uploader: self.immediate_folders = ["crash/", "boot/"] self.immediate_priority = {"qlog": 0, "qlog.bz2": 0, "qcamera.ts": 1} - def list_upload_files(self) -> Iterator[Tuple[str, str, str]]: - if not os.path.isdir(self.root): - return - - for logname in listdir_by_creation(self.root): - path = os.path.join(self.root, logname) + def list_upload_files(self, metered: bool) -> Iterator[Tuple[str, str, str]]: + for logdir in listdir_by_creation(self.root): + path = os.path.join(self.root, logdir) try: names = os.listdir(path) except OSError: @@ -91,21 +92,28 @@ class Uploader: continue for name in sorted(names, key=lambda n: self.immediate_priority.get(n, 1000)): - key = os.path.join(logname, name) + key = os.path.join(logdir, name) fn = os.path.join(path, name) # skip files already uploaded try: + ctime = os.path.getctime(fn) is_uploaded = getxattr(fn, UPLOAD_ATTR_NAME) == UPLOAD_ATTR_VALUE except OSError: cloudlog.event("uploader_getxattr_failed", key=key, fn=fn) - is_uploaded = True # deleter could have deleted + # deleter could have deleted, so skip + continue if is_uploaded: continue + # delay uploading crash and boot logs on metered connections + dt = datetime.timedelta(hours=12) + if metered and logdir in self.immediate_folders and (datetime.datetime.now() - datetime.datetime.fromtimestamp(ctime)) < dt: + continue + yield name, key, fn - def next_file_to_upload(self) -> Optional[Tuple[str, str, str]]: - upload_files = list(self.list_upload_files()) + def next_file_to_upload(self, metered: bool) -> Optional[Tuple[str, str, str]]: + upload_files = list(self.list_upload_files(metered)) for name, key, fn in upload_files: if any(f in fn for f in self.immediate_folders): @@ -191,7 +199,7 @@ class Uploader: def step(self, network_type: int, metered: bool) -> bool: - d = self.next_file_to_upload() + d = self.next_file_to_upload(metered) if d is None: return True From 005a6e4f17d88473440607bb4aa9218e44e906b4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Jan 2024 17:45:22 -0800 Subject: [PATCH 487/724] thermald: match metered param to packet --- selfdrive/thermald/thermald.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 75e091febb..96cf0f49f8 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -447,7 +447,7 @@ def thermald_thread(end_event, hw_queue) -> None: except Exception: cloudlog.exception("failed to save offroad status") - params.put_bool_nonblocking("NetworkMetered", (msg.deviceState.networkType != NetworkType.wifi)) + params.put_bool_nonblocking("NetworkMetered", msg.deviceState.networkMetered) count += 1 should_start_prev = should_start From f96aa247cc0834296a3f85ea58617ef700de596c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Jan 2024 19:05:31 -0800 Subject: [PATCH 488/724] FW query timing test: mock inconsistent, timing-based functions (#31048) * Revert "Revert "FW query timing test: mock get_data function for timeout" (#30696)" This reverts commit b1ecfd55ce73062f297a5ea7d99d0c55d57c58ab. * no thread * clean up a bit * no thread! * combine functions * re-enable total time check * back to 5 * minimal diff * minimal diff --- selfdrive/car/tests/test_fw_fingerprint.py | 91 ++++++++++++---------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 257a40f6a3..36619f4cde 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -5,10 +5,9 @@ import time import unittest from collections import defaultdict from parameterized import parameterized -import threading +from unittest import mock from cereal import car -from openpilot.common.params import Params from openpilot.selfdrive.car.car_helpers import interfaces from openpilot.selfdrive.car.fingerprints import FW_VERSIONS from openpilot.selfdrive.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, \ @@ -176,29 +175,36 @@ class TestFwFingerprint(unittest.TestCase): class TestFwFingerprintTiming(unittest.TestCase): N: int = 5 - TOL: float = 0.12 + TOL: float = 0.05 - @staticmethod - def _run_thread(thread: threading.Thread) -> float: - params = Params() - params.put_bool("ObdMultiplexingEnabled", True) - thread.start() - t = time.perf_counter() - while thread.is_alive(): - time.sleep(0.02) - if not params.get_bool("ObdMultiplexingChanged"): - params.put_bool("ObdMultiplexingChanged", True) - return time.perf_counter() - t + # for patched functions + current_obd_multiplexing: bool + total_time: float + + def fake_set_obd_multiplexing(self, _, obd_multiplexing): + """The 10Hz blocking params loop adds on average 50ms to the query time for each OBD multiplexing change""" + if obd_multiplexing != self.current_obd_multiplexing: + self.current_obd_multiplexing = obd_multiplexing + self.total_time += 0.1 / 2 + + def fake_get_data(self, timeout): + self.total_time += timeout + return {} def _benchmark_brand(self, brand, num_pandas): fake_socket = FakeSocket() - brand_time = 0 - for _ in range(self.N): - thread = threading.Thread(target=get_fw_versions, args=(fake_socket, fake_socket, brand), - kwargs=dict(num_pandas=num_pandas)) - brand_time += self._run_thread(thread) + self.total_time = 0 + with (mock.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing), + mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)): + for _ in range(self.N): + # Treat each brand as the most likely (aka, the first) brand with OBD multiplexing initially on + self.current_obd_multiplexing = True - return brand_time / self.N + t = time.perf_counter() + get_fw_versions(fake_socket, fake_socket, brand, num_pandas=num_pandas) + self.total_time += time.perf_counter() - t + + return self.total_time / self.N def _assert_timing(self, avg_time, ref_time): self.assertLess(avg_time, ref_time + self.TOL) @@ -207,44 +213,49 @@ class TestFwFingerprintTiming(unittest.TestCase): def test_startup_timing(self): # Tests worse-case VIN query time and typical present ECU query time vin_ref_time = 1.0 - present_ecu_ref_time = 0.8 + present_ecu_ref_time = 0.75 + + def fake_get_ecu_addrs(*_, timeout): + self.total_time += timeout + return set() fake_socket = FakeSocket() - present_ecu_time = 0.0 - for _ in range(self.N): - thread = threading.Thread(target=get_present_ecus, args=(fake_socket, fake_socket), - kwargs=dict(num_pandas=2)) - present_ecu_time += self._run_thread(thread) - self._assert_timing(present_ecu_time / self.N, present_ecu_ref_time) - print(f'get_present_ecus, query time={present_ecu_time / self.N} seconds') + self.total_time = 0.0 + with (mock.patch("openpilot.selfdrive.car.fw_versions.set_obd_multiplexing", self.fake_set_obd_multiplexing), + mock.patch("openpilot.selfdrive.car.fw_versions.get_ecu_addrs", fake_get_ecu_addrs)): + for _ in range(self.N): + self.current_obd_multiplexing = True + get_present_ecus(fake_socket, fake_socket, num_pandas=2) + self._assert_timing(self.total_time / self.N, present_ecu_ref_time) + print(f'get_present_ecus, query time={self.total_time / self.N} seconds') - vin_time = 0.0 - for _ in range(self.N): - thread = threading.Thread(target=get_vin, args=(fake_socket, fake_socket, 1)) - vin_time += self._run_thread(thread) - self._assert_timing(vin_time / self.N, vin_ref_time) - print(f'get_vin, query time={vin_time / self.N} seconds') + self.total_time = 0.0 + with (mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)): + for _ in range(self.N): + get_vin(fake_socket, fake_socket, 1) + self._assert_timing(self.total_time / self.N, vin_ref_time) + print(f'get_vin, query time={self.total_time / self.N} seconds') @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.41 + total_ref_time = 6.1 brand_ref_times = { 1: { - 'body': 0.11, + 'body': 0.1, 'chrysler': 0.3, 'ford': 0.2, - 'honda': 0.52, - 'hyundai': 0.72, + 'honda': 0.45, + 'hyundai': 0.65, 'mazda': 0.2, 'nissan': 0.4, - 'subaru': 0.52, + 'subaru': 0.45, 'tesla': 0.2, 'toyota': 1.6, 'volkswagen': 0.2, }, 2: { 'ford': 0.3, - 'hyundai': 1.12, + 'hyundai': 1.05, } } From 2280ac50b76264dadd170e8f23bf0c3bf68ef61c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 17 Jan 2024 21:41:58 -0800 Subject: [PATCH 489/724] uploader: restrict qcam upload on metered connctions (#31047) * uploader: restrict qcam upload on metered connctions * route requesting * rename * str * cleanup * strip dongle * upload bookmarked segments --------- Co-authored-by: Comma Device --- common/params.cc | 1 + selfdrive/athena/athenad.py | 15 +++++++++++++++ system/loggerd/loggerd.cc | 7 +++++++ system/loggerd/uploader.py | 17 +++++++++++++---- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/common/params.cc b/common/params.cc index b416b801a8..639de5322b 100644 --- a/common/params.cc +++ b/common/params.cc @@ -94,6 +94,7 @@ std::unordered_map keys = { {"AssistNowToken", PERSISTENT}, {"AthenadPid", PERSISTENT}, {"AthenadUploadQueue", PERSISTENT}, + {"AthenadRecentlyViewedRoutes", PERSISTENT}, {"CalibrationParams", PERSISTENT}, {"CameraDebugExpGain", CLEAR_ON_MANAGER_START}, {"CameraDebugExpTime", CLEAR_ON_MANAGER_START}, diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 2df44cb090..0a5c9b7999 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -432,6 +432,21 @@ def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]] cancelled_uploads.update(cancelled_ids) return {"success": 1} +@dispatcher.add_method +def setRouteViewed(route: str) -> Dict[str, Union[int, str]]: + # maintain a list of the last 10 routes viewed in connect + params = Params() + + r = params.get("AthenadRecentlyViewedRoutes", encoding="utf8") + routes = [] if r is None else r.split(",") + routes.append(route) + + # remove duplicates + routes = list(dict.fromkeys(routes)) + + params.put("AthenadRecentlyViewedRoutes", ",".join(routes[-10:])) + return {"success": 1} + def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> Dict[str, int]: try: diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 8d5fcb95ac..27dfa187c4 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -6,6 +6,7 @@ #include #include +#include "common/params.h" #include "system/loggerd/encoder/encoder.h" #include "system/loggerd/loggerd.h" #include "system/loggerd/video_writer.h" @@ -187,6 +188,12 @@ void handle_user_flag(LoggerdState *s) { if (ret) { LOGE("setxattr %s failed for %s: %s", PRESERVE_ATTR_NAME, s->logger.segmentPath().c_str(), strerror(errno)); } + + // mark route for uploading + Params params; + std::string routes = Params().get("AthenadRecentlyViewedRoutes"); + params.put("AthenadRecentlyViewedRoutes", routes + "," + s->logger.routeName()); + prev_segment = s->logger.segment(); } diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 8b45627751..105e830a4c 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -74,6 +74,8 @@ class Uploader: self.api = Api(dongle_id) self.root = root + self.params = Params() + # stats for last successfully uploaded file self.last_filename = "" @@ -81,6 +83,9 @@ class Uploader: self.immediate_priority = {"qlog": 0, "qlog.bz2": 0, "qcamera.ts": 1} def list_upload_files(self, metered: bool) -> Iterator[Tuple[str, str, str]]: + r = self.params.get("AthenadRecentlyViewedRoutes", encoding="utf8") + requested_routes = [] if r is None else r.split(",") + for logdir in listdir_by_creation(self.root): path = os.path.join(self.root, logdir) try: @@ -105,10 +110,14 @@ class Uploader: if is_uploaded: continue - # delay uploading crash and boot logs on metered connections - dt = datetime.timedelta(hours=12) - if metered and logdir in self.immediate_folders and (datetime.datetime.now() - datetime.datetime.fromtimestamp(ctime)) < dt: - continue + # limit uploading on metered connections + if metered: + dt = datetime.timedelta(hours=12) + if logdir in self.immediate_folders and (datetime.datetime.now() - datetime.datetime.fromtimestamp(ctime)) < dt: + continue + + if name == "qcamera.ts" and not any(logdir.startswith(r.split('|')[-1]) for r in requested_routes): + continue yield name, key, fn From 5791afda15db08448e770be9f7b03cb7daee7a39 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 11:50:17 -0800 Subject: [PATCH 490/724] update bolt bounty --- docs/BOUNTIES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BOUNTIES.md b/docs/BOUNTIES.md index a927a33b89..4fcfc1607c 100644 --- a/docs/BOUNTIES.md +++ b/docs/BOUNTIES.md @@ -59,4 +59,4 @@ We're contributing $5k to the [community-organized bounty](https://github.com/co #### Chevy Bolt with SuperCruise - $2500 -The Bolt is already supported on the trim with standard ACC. Get openpilot working on the trim with SuperCruise. It must be a normal install: no extra pandas or other hardware, no ECU reflashes, etc. +The Bolt is already supported on the trim with standard ACC. Get openpilot working on the trim with SuperCruise. It must be a normal install: no extra pandas or other hardware, no ECU reflashes, etc. The full bounty is for a port with lateral and longitudinal control. $1500 of the bounty can be claimed with a lateral-only port. From 292f2ea2884f52b0e2729c2b493521546197a5b3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 11:51:39 -0800 Subject: [PATCH 491/724] Update BOUNTIES.md --- docs/BOUNTIES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/BOUNTIES.md b/docs/BOUNTIES.md index 4fcfc1607c..5ff5dd939f 100644 --- a/docs/BOUNTIES.md +++ b/docs/BOUNTIES.md @@ -8,7 +8,7 @@ Get paid to improve openpilot! * bounty eligibility is solely at our discretion * once you open a PR, the bounty is locked to you until you stop working on it * open a ticket at [comma.ai/support](https://comma.ai/support/shop-order) with links to your PRs to claim -* get an extra 20% if you redeem your bounty in [comma shop](https://comma.ai/shop) credit +* get an extra 20% if you redeem your bounty in [comma shop](https://comma.ai/shop) credit (including refunds on previous orders) New bounties can be proposed in the [**#contributing**](https://discord.com/channels/469524606043160576/1183173332531687454) channel in Discord. From 1c09ae99eb4c961719bc5ca82df5d6b39bf25843 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 15:06:02 -0800 Subject: [PATCH 492/724] Bump submodules (#31054) bump submodules Co-authored-by: jnewb1 --- panda | 2 +- teleoprtc_repo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/panda b/panda index d66161966d..06feb083cf 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d66161966d8468223b645c8eba1324e9a49de916 +Subproject commit 06feb083cf9107a7c38147134bde10fe7df9e9e9 diff --git a/teleoprtc_repo b/teleoprtc_repo index ca10fb2410..3f9e8176d1 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit ca10fb2410ee14e207f30aadebeec707904dc559 +Subproject commit 3f9e8176d1be3d217528baee09fc418fa980a0c3 From d98b33adc8aaf10d0174d59e4065802c70a3413a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 18 Jan 2024 19:19:49 -0500 Subject: [PATCH 493/724] replace pytest-timeouts with pytest-timeout (#31053) * one timeout * timeout * higher timeout for now * bump --- .github/workflows/selfdrive_tests.yaml | 2 +- poetry.lock | 17 +---------------- pyproject.toml | 1 - 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 78502d3799..b9e80e9f74 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -181,7 +181,7 @@ jobs: run: | ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ - $PYTEST --timeout 30 -m 'not slow' && \ + $PYTEST --timeout 40 -m 'not slow' && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py" diff --git a/poetry.lock b/poetry.lock index a098703d62..41b677e5e0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3282,8 +3282,6 @@ files = [ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, - {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"}, - {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, @@ -3699,19 +3697,6 @@ files = [ [package.dependencies] pytest = ">=5.0.0" -[[package]] -name = "pytest-timeouts" -version = "1.2.1" -description = "Linux-only Pytest plugin to control durations of various test case execution phases" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pytest-timeouts-1.2.1.tar.gz", hash = "sha256:390351afc7ecb422ea0ec38081e0acd91cad416b383944a9a3358087de50c2fb"}, -] - -[package.dependencies] -pytest = ">=3.1" - [[package]] name = "pytest-xdist" version = "3.5.0" @@ -4886,4 +4871,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "ad5e6e7b632c35edf6945774c6a4d0370177fd995c9b37efea68dd206a4ce1be" +content-hash = "1976ee7795d5ac5b257cac8dd83e408ae6e75c48d8c349e3e8e56519727cf52f" diff --git a/pyproject.toml b/pyproject.toml index b12a21a962..5641a55101 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -145,7 +145,6 @@ pytest-cpp = "*" pytest-subtests = "*" pytest-xdist = "*" pytest-timeout = "*" -pytest-timeouts = "*" pytest-randomly = "*" ruff = "*" sphinx = "*" From 00b5424407be17dc1b193e2f52bc10d13a2c8794 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 16:38:46 -0800 Subject: [PATCH 494/724] time to remove this --- launch_chffrplus.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 7578c0296a..a91bd677aa 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -9,11 +9,6 @@ source "$BASEDIR/launch_env.sh" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" function agnos_init { - # wait longer for weston to come up - if [ -f "$BASEDIR/prebuilt" ]; then - sleep 5 - fi - # TODO: move this to agnos sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta From 9ee0a8a2b1abb01eeaca7aa9de5fa86ee60a59e9 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 18 Jan 2024 20:17:53 -0500 Subject: [PATCH 495/724] CI: use namespace.so (#31056) * namespace * mkdir * fix * restore only * restore * v3 --- .../workflows/auto-cache-restore/action.yaml | 34 +++++++++++++++++++ .../workflows/compile-openpilot/action.yaml | 8 +---- .github/workflows/selfdrive_tests.yaml | 17 +++++----- .../workflows/setup-with-retry/action.yaml | 7 ---- .github/workflows/setup/action.yaml | 25 +++----------- .github/workflows/tools_tests.yaml | 2 +- 6 files changed, 49 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/auto-cache-restore/action.yaml diff --git a/.github/workflows/auto-cache-restore/action.yaml b/.github/workflows/auto-cache-restore/action.yaml new file mode 100644 index 0000000000..0aab3b3eef --- /dev/null +++ b/.github/workflows/auto-cache-restore/action.yaml @@ -0,0 +1,34 @@ +name: 'automatically cache based on current runner' + +inputs: + path: + description: 'path to cache' + required: true + key: + description: 'key' + required: true + restore-keys: + description: 'restore-keys' + required: true + +runs: + using: "composite" + steps: + - name: setup namespace cache + if: ${{ contains(runner.name, 'nsc') }} + uses: namespacelabs/nscloud-cache-action@v1 + with: + path: ${{ inputs.path }} + + - name: setup github cache + if: ${{ !contains(runner.name, 'nsc') }} + uses: actions/cache/restore@v3 + with: + path: ${{ inputs.path }} + key: ${{ inputs.key }} + restore-keys: ${{ inputs.restore-keys }} + + # make the directory manually in case we didn't get a hit, so it doesn't fail on future steps + - id: scons-cache-setup + shell: bash + run: mkdir -p ${{ inputs.path }} diff --git a/.github/workflows/compile-openpilot/action.yaml b/.github/workflows/compile-openpilot/action.yaml index 8775c96262..2945b67d2e 100644 --- a/.github/workflows/compile-openpilot/action.yaml +++ b/.github/workflows/compile-openpilot/action.yaml @@ -1,11 +1,5 @@ name: 'compile openpilot' -inputs: - cache_key_prefix: - description: 'Prefix for caching key' - required: false - default: 'scons' - runs: using: "composite" steps: @@ -24,4 +18,4 @@ runs: if: github.ref == 'refs/heads/master' with: path: .ci_cache/scons_cache - key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index b9e80e9f74..dfd3570d4f 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -75,7 +75,7 @@ jobs: ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -83,18 +83,15 @@ jobs: - uses: ./.github/workflows/setup-with-retry with: docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} - cache_key_prefix: scons_${{ matrix.arch }} - uses: ./.github/workflows/compile-openpilot timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache - with: - cache_key_prefix: scons_${{ matrix.arch }} docker_push: name: docker push strategy: matrix: arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-20.04' }} if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v4 @@ -133,7 +130,9 @@ jobs: static_analysis: name: static analysis - runs-on: ubuntu-20.04 + runs-on: ${{ ((github.repository == 'commaai/openpilot') && + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -165,7 +164,7 @@ jobs: name: unit tests runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }} + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -181,7 +180,7 @@ jobs: run: | ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ - $PYTEST --timeout 40 -m 'not slow' && \ + $PYTEST --timeout 40 -m 'not slow' -n $(nproc) && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py" @@ -196,7 +195,7 @@ jobs: name: process replay runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }} + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/setup-with-retry/action.yaml b/.github/workflows/setup-with-retry/action.yaml index e0da746b44..369ac45c9a 100644 --- a/.github/workflows/setup-with-retry/action.yaml +++ b/.github/workflows/setup-with-retry/action.yaml @@ -5,10 +5,6 @@ inputs: description: 'Auth token for Docker Hub, required for BuildJet jobs' required: false default: '' - cache_key_prefix: - description: 'Prefix for caching key' - required: false - default: 'scons_x86_64' sleep_time: description: 'Time to sleep between retries' required: false @@ -22,7 +18,6 @@ runs: continue-on-error: true with: docker_hub_pat: ${{ inputs.docker_hub_pat }} - cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true - if: steps.setup1.outcome == 'failure' shell: bash @@ -33,7 +28,6 @@ runs: continue-on-error: true with: docker_hub_pat: ${{ inputs.docker_hub_pat }} - cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true - if: steps.setup2.outcome == 'failure' shell: bash @@ -43,5 +37,4 @@ runs: uses: ./.github/workflows/setup with: docker_hub_pat: ${{ inputs.docker_hub_pat }} - cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index 8bb1ccc376..ec1626c01b 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -5,10 +5,6 @@ inputs: description: 'Auth token for Docker Hub, required for BuildJet jobs' required: true default: '' - cache_key_prefix: - description: 'Prefix for caching key' - required: true - default: 'scons_x86_64' is_retried: description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly' required: false @@ -47,19 +43,14 @@ runs: run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - shell: bash run: echo "$CACHE_COMMIT_DATE" - - id: restore-scons-cache - uses: actions/cache/restore@v3 + - id: scons-cache + uses: ./.github/workflows/auto-cache-restore with: path: .ci_cache/scons_cache - key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | - ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}- - ${{ inputs.cache_key_prefix }}- - # if we didn't get a cache hit, make the directory manually so it doesn't fail on future steps - - id: scons-cache-setup - shell: bash - if: steps.restore-scons-cache.outputs.cache-hit != 'true' - run: mkdir -p $GITHUB_WORKSPACE/.ci_cache/scons_cache + scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }} + scons-${{ runner.arch }} # as suggested here: https://github.com/moby/moby/issues/32816#issuecomment-910030001 - id: normalize-file-permissions shell: bash @@ -67,12 +58,6 @@ runs: run: | find . -type f -executable -not -perm 755 -exec chmod 755 {} \; find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \; - - id: setup-buildx-action - if: contains(runner.name, 'buildjet') - name: Set up Docker Buildx on buildjet to ensure a consistent cache - uses: docker/setup-buildx-action@v2 - with: - driver: docker-container # build our docker image - shell: bash run: eval ${{ env.BUILD }} \ No newline at end of file diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index f549ece1b1..5cecfb7e4e 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -81,7 +81,7 @@ jobs: - name: Run dev container run: | mkdir -p /tmp/devcontainer_scons_cache/ - cp -r $GITHUB_WORKSPACE/.ci_cache/scons_cache/* /tmp/devcontainer_scons_cache/ + cp -r $GITHUB_WORKSPACE/.ci_cache/scons_cache/. /tmp/devcontainer_scons_cache/ devcontainer up --workspace-folder . - name: Test environment run: | From 23cbfe9eb3fc3fd3655e41d44a63a20e04bae1f1 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 18 Jan 2024 20:35:24 -0500 Subject: [PATCH 496/724] Revert "CI: use namespace.so" (#31060) --- .../workflows/auto-cache-restore/action.yaml | 34 ------------------- .../workflows/compile-openpilot/action.yaml | 8 ++++- .github/workflows/selfdrive_tests.yaml | 17 +++++----- .../workflows/setup-with-retry/action.yaml | 7 ++++ .github/workflows/setup/action.yaml | 25 +++++++++++--- .github/workflows/tools_tests.yaml | 2 +- 6 files changed, 44 insertions(+), 49 deletions(-) delete mode 100644 .github/workflows/auto-cache-restore/action.yaml diff --git a/.github/workflows/auto-cache-restore/action.yaml b/.github/workflows/auto-cache-restore/action.yaml deleted file mode 100644 index 0aab3b3eef..0000000000 --- a/.github/workflows/auto-cache-restore/action.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: 'automatically cache based on current runner' - -inputs: - path: - description: 'path to cache' - required: true - key: - description: 'key' - required: true - restore-keys: - description: 'restore-keys' - required: true - -runs: - using: "composite" - steps: - - name: setup namespace cache - if: ${{ contains(runner.name, 'nsc') }} - uses: namespacelabs/nscloud-cache-action@v1 - with: - path: ${{ inputs.path }} - - - name: setup github cache - if: ${{ !contains(runner.name, 'nsc') }} - uses: actions/cache/restore@v3 - with: - path: ${{ inputs.path }} - key: ${{ inputs.key }} - restore-keys: ${{ inputs.restore-keys }} - - # make the directory manually in case we didn't get a hit, so it doesn't fail on future steps - - id: scons-cache-setup - shell: bash - run: mkdir -p ${{ inputs.path }} diff --git a/.github/workflows/compile-openpilot/action.yaml b/.github/workflows/compile-openpilot/action.yaml index 2945b67d2e..8775c96262 100644 --- a/.github/workflows/compile-openpilot/action.yaml +++ b/.github/workflows/compile-openpilot/action.yaml @@ -1,5 +1,11 @@ name: 'compile openpilot' +inputs: + cache_key_prefix: + description: 'Prefix for caching key' + required: false + default: 'scons' + runs: using: "composite" steps: @@ -18,4 +24,4 @@ runs: if: github.ref == 'refs/heads/master' with: path: .ci_cache/scons_cache - key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index dfd3570d4f..b9e80e9f74 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -75,7 +75,7 @@ jobs: ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-20.04' }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -83,15 +83,18 @@ jobs: - uses: ./.github/workflows/setup-with-retry with: docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} + cache_key_prefix: scons_${{ matrix.arch }} - uses: ./.github/workflows/compile-openpilot timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache + with: + cache_key_prefix: scons_${{ matrix.arch }} docker_push: name: docker push strategy: matrix: arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-20.04' }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v4 @@ -130,9 +133,7 @@ jobs: static_analysis: name: static analysis - runs-on: ${{ ((github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 with: @@ -164,7 +165,7 @@ jobs: name: unit tests runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -180,7 +181,7 @@ jobs: run: | ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ - $PYTEST --timeout 40 -m 'not slow' -n $(nproc) && \ + $PYTEST --timeout 40 -m 'not slow' && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py" @@ -195,7 +196,7 @@ jobs: name: process replay runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/setup-with-retry/action.yaml b/.github/workflows/setup-with-retry/action.yaml index 369ac45c9a..e0da746b44 100644 --- a/.github/workflows/setup-with-retry/action.yaml +++ b/.github/workflows/setup-with-retry/action.yaml @@ -5,6 +5,10 @@ inputs: description: 'Auth token for Docker Hub, required for BuildJet jobs' required: false default: '' + cache_key_prefix: + description: 'Prefix for caching key' + required: false + default: 'scons_x86_64' sleep_time: description: 'Time to sleep between retries' required: false @@ -18,6 +22,7 @@ runs: continue-on-error: true with: docker_hub_pat: ${{ inputs.docker_hub_pat }} + cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true - if: steps.setup1.outcome == 'failure' shell: bash @@ -28,6 +33,7 @@ runs: continue-on-error: true with: docker_hub_pat: ${{ inputs.docker_hub_pat }} + cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true - if: steps.setup2.outcome == 'failure' shell: bash @@ -37,4 +43,5 @@ runs: uses: ./.github/workflows/setup with: docker_hub_pat: ${{ inputs.docker_hub_pat }} + cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index ec1626c01b..8bb1ccc376 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -5,6 +5,10 @@ inputs: description: 'Auth token for Docker Hub, required for BuildJet jobs' required: true default: '' + cache_key_prefix: + description: 'Prefix for caching key' + required: true + default: 'scons_x86_64' is_retried: description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly' required: false @@ -43,14 +47,19 @@ runs: run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - shell: bash run: echo "$CACHE_COMMIT_DATE" - - id: scons-cache - uses: ./.github/workflows/auto-cache-restore + - id: restore-scons-cache + uses: actions/cache/restore@v3 with: path: .ci_cache/scons_cache - key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | - scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }} - scons-${{ runner.arch }} + ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}- + ${{ inputs.cache_key_prefix }}- + # if we didn't get a cache hit, make the directory manually so it doesn't fail on future steps + - id: scons-cache-setup + shell: bash + if: steps.restore-scons-cache.outputs.cache-hit != 'true' + run: mkdir -p $GITHUB_WORKSPACE/.ci_cache/scons_cache # as suggested here: https://github.com/moby/moby/issues/32816#issuecomment-910030001 - id: normalize-file-permissions shell: bash @@ -58,6 +67,12 @@ runs: run: | find . -type f -executable -not -perm 755 -exec chmod 755 {} \; find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \; + - id: setup-buildx-action + if: contains(runner.name, 'buildjet') + name: Set up Docker Buildx on buildjet to ensure a consistent cache + uses: docker/setup-buildx-action@v2 + with: + driver: docker-container # build our docker image - shell: bash run: eval ${{ env.BUILD }} \ No newline at end of file diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 5cecfb7e4e..f549ece1b1 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -81,7 +81,7 @@ jobs: - name: Run dev container run: | mkdir -p /tmp/devcontainer_scons_cache/ - cp -r $GITHUB_WORKSPACE/.ci_cache/scons_cache/. /tmp/devcontainer_scons_cache/ + cp -r $GITHUB_WORKSPACE/.ci_cache/scons_cache/* /tmp/devcontainer_scons_cache/ devcontainer up --workspace-folder . - name: Test environment run: | From 29536324c1dceceb064c6b3ef6a3e0ef3dd0d578 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 Jan 2024 18:51:24 -0800 Subject: [PATCH 497/724] Toyota: combine more hybrid platforms (#31057) * Combine * migrate test models segs * fix highlander engine * no change --- docs/CARS.md | 2 +- selfdrive/car/fw_versions.py | 1 + selfdrive/car/tests/routes.py | 6 +- selfdrive/car/tests/test_models_segs.txt | 4 +- selfdrive/car/torque_data/params.toml | 6 +- selfdrive/car/torque_data/substitute.toml | 1 - selfdrive/car/toyota/carcontroller.py | 2 +- selfdrive/car/toyota/fingerprints.py | 115 ++++++---------------- selfdrive/car/toyota/interface.py | 9 +- selfdrive/car/toyota/tests/test_toyota.py | 11 ++- selfdrive/car/toyota/values.py | 74 +++++++------- 11 files changed, 89 insertions(+), 142 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 407c1d8cc7..b27fb1b943 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -155,7 +155,7 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index a4c8db6797..072c2ae4e4 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -123,6 +123,7 @@ def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True) -> Set[s found_versions = live_fw_versions.get(addr, set()) if not len(found_versions): # Some models can sometimes miss an ecu, or show on two different addresses + # FIXME: this logic can be improved to be more specific, should require one of the two addresses if candidate in config.non_essential_ecus.get(ecu_type, []): continue diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index c711f81929..69b39b3b2e 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -193,14 +193,14 @@ routes = [ CarTestRoute("7e34a988419b5307|2019-12-18--19-13-30", TOYOTA.RAV4_TSS2), # hybrid CarTestRoute("2475fb3eb2ffcc2e|2022-04-29--12-46-23", TOYOTA.RAV4_TSS2_2022), # hybrid CarTestRoute("7a31f030957b9c85|2023-04-01--14-12-51", TOYOTA.LEXUS_ES), + CarTestRoute("37041c500fd30100|2020-12-30--12-17-24", TOYOTA.LEXUS_ES), # hybrid CarTestRoute("e6a24be49a6cd46e|2019-10-29--10-52-42", TOYOTA.LEXUS_ES_TSS2), CarTestRoute("f49e8041283f2939|2019-05-30--11-51-51", TOYOTA.LEXUS_ES_TSS2), # hybrid CarTestRoute("da23c367491f53e2|2021-05-21--09-09-11", TOYOTA.LEXUS_CTH, segment=3), - CarTestRoute("37041c500fd30100|2020-12-30--12-17-24", TOYOTA.LEXUS_ESH), CarTestRoute("32696cea52831b02|2021-11-19--18-13-30", TOYOTA.LEXUS_RC), CarTestRoute("ab9b64a5e5960cba|2023-10-24--17-32-08", TOYOTA.LEXUS_GS_F), CarTestRoute("886fcd8408d570e9|2020-01-29--02-18-55", TOYOTA.LEXUS_RX), - CarTestRoute("d27ad752e9b08d4f|2021-05-26--19-39-51", TOYOTA.LEXUS_RXH), + CarTestRoute("d27ad752e9b08d4f|2021-05-26--19-39-51", TOYOTA.LEXUS_RX), # hybrid CarTestRoute("01b22eb2ed121565|2020-02-02--11-25-51", TOYOTA.LEXUS_RX_TSS2), CarTestRoute("b74758c690a49668|2020-05-20--15-58-57", TOYOTA.LEXUS_RX_TSS2), # hybrid CarTestRoute("964c09eb11ca8089|2020-11-03--22-04-00", TOYOTA.LEXUS_NX), @@ -210,7 +210,7 @@ routes = [ CarTestRoute("0a302ffddbb3e3d3|2020-02-08--16-19-08", TOYOTA.HIGHLANDER_TSS2), CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.HIGHLANDER_TSS2), # hybrid CarTestRoute("3183cd9b021e89ce|2021-05-25--10-34-44", TOYOTA.HIGHLANDER), - CarTestRoute("80d16a262e33d57f|2021-05-23--20-01-43", TOYOTA.HIGHLANDERH), + CarTestRoute("80d16a262e33d57f|2021-05-23--20-01-43", TOYOTA.HIGHLANDER), # hybrid CarTestRoute("eb6acd681135480d|2019-06-20--20-00-00", TOYOTA.SIENNA), CarTestRoute("2e07163a1ba9a780|2019-08-25--13-15-13", TOYOTA.LEXUS_IS), CarTestRoute("649bf2997ada6e3a|2023-08-08--18-04-22", TOYOTA.LEXUS_IS_TSS2), diff --git a/selfdrive/car/tests/test_models_segs.txt b/selfdrive/car/tests/test_models_segs.txt index 27a2104cf7..92835ae8ad 100644 --- a/selfdrive/car/tests/test_models_segs.txt +++ b/selfdrive/car/tests/test_models_segs.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8674d9a8304d501aee2bfaea2d668d746fd424b5b0872b83ab265d15d3a3d887 -size 125301 +oid sha256:8570e4144f2c17a1c804b18cea0d4358bea211f67c823fd3bd152b6af7cf207d +size 125147 diff --git a/selfdrive/car/torque_data/params.toml b/selfdrive/car/torque_data/params.toml index 2e04e1c6a0..564f50df5e 100644 --- a/selfdrive/car/torque_data/params.toml +++ b/selfdrive/car/torque_data/params.toml @@ -49,9 +49,8 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "LEXUS ES 2019" = [2.0357564999999997, 1.999082295195227, 0.101533] "LEXUS NX 2018" = [2.3525924753753613, 1.9731412277641067, 0.15168101064205927] "LEXUS NX 2020" = [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] -"LEXUS RX 2016" = [1.5876816543130423, 1.0427699298523752, 0.21334066732397142] +"LEXUS RX 2016" = [1.6430539050086406, 1.181960058934143, 0.19768806040843034] "LEXUS RX 2020" = [1.5375561442049257, 1.343166476215164, 0.1931062001527557] -"LEXUS RX HYBRID 2017" = [1.6984261557042386, 1.3211501880159107, 0.1820354534928893] "MAZDA CX-9 2021" = [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] "SKODA SUPERB 3RD GEN" = [1.166437404652981, 1.1686163012668165, 0.12194533036948708] "SUBARU FORESTER 2019" = [3.6617001649776793, 2.342197172531713, 0.11075960785398745] @@ -66,9 +65,8 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "TOYOTA CAMRY 2021" = [2.3548324999999997, 2.368900128946771, 0.118436] "TOYOTA COROLLA 2017" = [3.117154369115421, 1.8438132575043773, 0.12289685869250652] "TOYOTA COROLLA TSS2 2019" = [1.991132339206426, 1.868866242720403, 0.19570063298031432] -"TOYOTA HIGHLANDER 2017" = [1.8696367437248915, 1.626293990451463, 0.17485372210240796] +"TOYOTA HIGHLANDER 2017" = [1.8108348718624456, 1.6348421600679828, 0.15972686105120398] "TOYOTA HIGHLANDER 2020" = [1.9617570834136164, 1.8611643317268927, 0.14519673256119725] -"TOYOTA HIGHLANDER HYBRID 2018" = [1.752033, 1.6433903296845025, 0.144600] "TOYOTA MIRAI 2021" = [2.506899832157829, 1.7417213930750164, 0.20182618449440565] "TOYOTA PRIUS 2017" = [1.60, 1.5023147650693636, 0.151515] "TOYOTA PRIUS TSS2 2021" = [1.972600, 1.9104337425537743, 0.170968] diff --git a/selfdrive/car/torque_data/substitute.toml b/selfdrive/car/torque_data/substitute.toml index 2d3c4dc55c..2e4513a484 100644 --- a/selfdrive/car/torque_data/substitute.toml +++ b/selfdrive/car/torque_data/substitute.toml @@ -10,7 +10,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "LEXUS IS 2018" = "LEXUS NX 2018" "LEXUS CT HYBRID 2018" = "LEXUS NX 2018" "LEXUS ES 2018" = "TOYOTA CAMRY 2018" -"LEXUS ES HYBRID 2018" = "TOYOTA CAMRY 2018" "LEXUS RC 2020" = "LEXUS NX 2020" "KIA OPTIMA 4TH GEN" = "HYUNDAI SONATA 2020" diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index badfd33e0b..e8ed9cfcb2 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -102,7 +102,7 @@ class CarController: if self.CP.enableGasInterceptor and CC.longActive: MAX_INTERCEPTOR_GAS = 0.5 # RAV4 has very sensitive gas pedal - if self.CP.carFingerprint in (CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH): + if self.CP.carFingerprint in (CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER): PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0]) elif self.CP.carFingerprint in (CAR.COROLLA,): PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0]) diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index c72766f40a..2ecd780843 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -614,41 +614,27 @@ FW_VERSIONS = { b'\x01896630E88000\x00\x00\x00\x00', b'\x01896630EA0000\x00\x00\x00\x00', ], - (Ecu.eps, 0x7a1, None): [ - b'8965B48140\x00\x00\x00\x00\x00\x00', - b'8965B48150\x00\x00\x00\x00\x00\x00', - b'8965B48210\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F15260E011\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881510E01100\x00\x00\x00\x00', - b'881510E01200\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4702100\x00\x00\x00\x00', - b'8821F4702300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F0E01200\x00\x00\x00\x00', - b'8646F0E01300\x00\x00\x00\x00', - ], - }, - CAR.HIGHLANDERH: { - (Ecu.eps, 0x7a1, None): [ - b'8965B48160\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ - b'F152648541\x00\x00\x00\x00\x00\x00', - b'F152648542\x00\x00\x00\x00\x00\x00', - ], (Ecu.engine, 0x7e0, None): [ b'\x0230E40000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230E40100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230EA2000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230EA2100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', ], + (Ecu.eps, 0x7a1, None): [ + b'8965B48140\x00\x00\x00\x00\x00\x00', + b'8965B48150\x00\x00\x00\x00\x00\x00', + b'8965B48160\x00\x00\x00\x00\x00\x00', + b'8965B48210\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F15260E011\x00\x00\x00\x00\x00\x00', + b'F152648541\x00\x00\x00\x00\x00\x00', + b'F152648542\x00\x00\x00\x00\x00\x00', + ], + (Ecu.dsu, 0x791, None): [ + b'881510E01100\x00\x00\x00\x00', + b'881510E01200\x00\x00\x00\x00', + ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F4702100\x00\x00\x00\x00', b'8821F4702300\x00\x00\x00\x00', @@ -1273,39 +1259,24 @@ FW_VERSIONS = { }, CAR.LEXUS_ES: { (Ecu.engine, 0x7e0, None): [ + b'\x02333M4200\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02333R0000\x00\x00\x00\x00\x00\x00\x00\x00A0C01000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'F152606202\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881513309500\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B33502\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4701200\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F3302200\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_ESH: { - (Ecu.engine, 0x7e0, None): [ - b'\x02333M4200\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', - ], - (Ecu.abs, 0x7b0, None): [ b'F152633171\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ + b'881513309500\x00\x00\x00\x00', b'881513310400\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ + b'8965B33502\x00\x00\x00\x00\x00\x00', b'8965B33512\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F4701100\x00\x00\x00\x00', + b'8821F4701200\x00\x00\x00\x00', b'8821F4701300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ @@ -1463,43 +1434,6 @@ FW_VERSIONS = { b'\x018966348W1300\x00\x00\x00\x00', b'\x018966348W2300\x00\x00\x00\x00', ], - (Ecu.abs, 0x7b0, None): [ - b'F152648472\x00\x00\x00\x00\x00\x00', - b'F152648473\x00\x00\x00\x00\x00\x00', - b'F152648474\x00\x00\x00\x00\x00\x00', - b'F152648492\x00\x00\x00\x00\x00\x00', - b'F152648493\x00\x00\x00\x00\x00\x00', - b'F152648494\x00\x00\x00\x00\x00\x00', - b'F152648630\x00\x00\x00\x00\x00\x00', - ], - (Ecu.dsu, 0x791, None): [ - b'881514810300\x00\x00\x00\x00', - b'881514810500\x00\x00\x00\x00', - b'881514810700\x00\x00\x00\x00', - ], - (Ecu.eps, 0x7a1, None): [ - b'8965B0E011\x00\x00\x00\x00\x00\x00', - b'8965B0E012\x00\x00\x00\x00\x00\x00', - b'8965B48102\x00\x00\x00\x00\x00\x00', - b'8965B48111\x00\x00\x00\x00\x00\x00', - b'8965B48112\x00\x00\x00\x00\x00\x00', - ], - (Ecu.fwdRadar, 0x750, 0xf): [ - b'8821F4701000\x00\x00\x00\x00', - b'8821F4701100\x00\x00\x00\x00', - b'8821F4701200\x00\x00\x00\x00', - b'8821F4701300\x00\x00\x00\x00', - ], - (Ecu.fwdCamera, 0x750, 0x6d): [ - b'8646F4801100\x00\x00\x00\x00', - b'8646F4801200\x00\x00\x00\x00', - b'8646F4802001\x00\x00\x00\x00', - b'8646F4802100\x00\x00\x00\x00', - b'8646F4802200\x00\x00\x00\x00', - b'8646F4809000\x00\x00\x00\x00', - ], - }, - CAR.LEXUS_RXH: { (Ecu.engine, 0x7e0, None): [ b'\x02348J7000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348N0000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1513,13 +1447,23 @@ FW_VERSIONS = { ], (Ecu.abs, 0x7b0, None): [ b'F152648361\x00\x00\x00\x00\x00\x00', + b'F152648472\x00\x00\x00\x00\x00\x00', + b'F152648473\x00\x00\x00\x00\x00\x00', + b'F152648474\x00\x00\x00\x00\x00\x00', + b'F152648492\x00\x00\x00\x00\x00\x00', + b'F152648493\x00\x00\x00\x00\x00\x00', + b'F152648494\x00\x00\x00\x00\x00\x00', b'F152648501\x00\x00\x00\x00\x00\x00', b'F152648502\x00\x00\x00\x00\x00\x00', b'F152648504\x00\x00\x00\x00\x00\x00', + b'F152648630\x00\x00\x00\x00\x00\x00', b'F152648740\x00\x00\x00\x00\x00\x00', b'F152648A30\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ + b'881514810300\x00\x00\x00\x00', + b'881514810500\x00\x00\x00\x00', + b'881514810700\x00\x00\x00\x00', b'881514811300\x00\x00\x00\x00', b'881514811500\x00\x00\x00\x00', b'881514811700\x00\x00\x00\x00', @@ -1538,6 +1482,7 @@ FW_VERSIONS = { b'8821F4701300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F4801100\x00\x00\x00\x00', b'8646F4801200\x00\x00\x00\x00', b'8646F4802001\x00\x00\x00\x00', b'8646F4802100\x00\x00\x00\x00', diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index b5151f4cda..90b2b760fe 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -76,7 +76,7 @@ class CarInterface(CarInterfaceBase): ret.tireStiffnessFactor = 0.444 # not optimized yet ret.mass = 2860. * CV.LB_TO_KG # mean between normal and hybrid - elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RXH, CAR.LEXUS_RX_TSS2): + elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RX_TSS2): stop_and_go = True ret.wheelbase = 2.79 ret.steerRatio = 16. # 14.8 is spec end-to-end @@ -98,7 +98,8 @@ class CarInterface(CarInterfaceBase): ret.tireStiffnessFactor = 0.7933 ret.mass = 3400. * CV.LB_TO_KG # mean between normal and hybrid - elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.HIGHLANDER_TSS2): + elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDER_TSS2): + # TODO: TSS-P models can do stop and go, but unclear if it requires sDSU or unplugging DSU stop_and_go = True ret.wheelbase = 2.8194 # average of 109.8 and 112.2 in ret.steerRatio = 16.0 @@ -141,9 +142,7 @@ class CarInterface(CarInterfaceBase): ret.tireStiffnessFactor = 0.444 # not optimized yet ret.mass = 3060. * CV.LB_TO_KG - elif candidate in (CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_ES_TSS2): - if candidate not in (CAR.LEXUS_ES,): # TODO: LEXUS_ES may have sng - stop_and_go = True + elif candidate in (CAR.LEXUS_ES, CAR.LEXUS_ES_TSS2): ret.wheelbase = 2.8702 ret.steerRatio = 16.0 # not optimized ret.tireStiffnessFactor = 0.444 # not optimized yet diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py index f4bf18dc5b..d9d4fe087f 100755 --- a/selfdrive/car/toyota/tests/test_toyota.py +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -50,6 +50,15 @@ class TestToyotaInterfaces(unittest.TestCase): class TestToyotaFingerprint(unittest.TestCase): + def test_non_essential_ecus(self): + # Ensures only the cars that have multiple engine ECUs are in the engine non-essential ECU list + for car_model, ecus in FW_VERSIONS.items(): + with self.subTest(car_model=car_model.value): + engine_ecus = {ecu for ecu in ecus if ecu[0] == Ecu.engine} + self.assertEqual(len(engine_ecus) > 1, + car_model in FW_QUERY_CONFIG.non_essential_ecus[Ecu.engine], + f"Car model unexpectedly {'not ' if len(engine_ecus) > 1 else ''}in non-essential list") + # Tests for part numbers, platform codes, and sub-versions which Toyota will use to fuzzy # fingerprint in the absence of full FW matches: @settings(max_examples=100) @@ -144,7 +153,7 @@ class TestToyotaFingerprint(unittest.TestCase): self.assertEqual(list(matches)[0], platform) else: # If a platform has multiple matches, add it and its matches - platforms_with_shared_codes |= {platform, *matches} + platforms_with_shared_codes |= {str(platform), *matches} self.assertEqual(platforms_with_shared_codes, FUZZY_EXCLUDED_PLATFORMS, (len(platforms_with_shared_codes), len(FW_VERSIONS))) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index b3b6fd7377..a16864cce8 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -62,7 +62,6 @@ class CAR(StrEnum): COROLLA_TSS2 = "TOYOTA COROLLA TSS2 2019" HIGHLANDER = "TOYOTA HIGHLANDER 2017" HIGHLANDER_TSS2 = "TOYOTA HIGHLANDER 2020" - HIGHLANDERH = "TOYOTA HIGHLANDER HYBRID 2018" PRIUS = "TOYOTA PRIUS 2017" PRIUS_V = "TOYOTA PRIUS v 2017" PRIUS_TSS2 = "TOYOTA PRIUS TSS2 2021" @@ -77,7 +76,6 @@ class CAR(StrEnum): # Lexus LEXUS_CTH = "LEXUS CT HYBRID 2018" LEXUS_ES = "LEXUS ES 2018" - LEXUS_ESH = "LEXUS ES HYBRID 2018" LEXUS_ES_TSS2 = "LEXUS ES 2019" LEXUS_IS = "LEXUS IS 2018" LEXUS_IS_TSS2 = "LEXUS IS 2023" @@ -85,7 +83,6 @@ class CAR(StrEnum): LEXUS_NX_TSS2 = "LEXUS NX 2020" LEXUS_RC = "LEXUS RC 2020" LEXUS_RX = "LEXUS RX 2016" - LEXUS_RXH = "LEXUS RX HYBRID 2017" LEXUS_RX_TSS2 = "LEXUS RX 2020" LEXUS_GS_F = "LEXUS GS F 2016" @@ -147,12 +144,14 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5), ToyotaCarInfo("Lexus UX Hybrid 2019-23"), ], - CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), + CAR.HIGHLANDER: [ + ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), + ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"), + ], CAR.HIGHLANDER_TSS2: [ ToyotaCarInfo("Toyota Highlander 2020-23"), ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"), ], - CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"), CAR.PRIUS: [ ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), @@ -188,8 +187,10 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { # Lexus CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+"), - CAR.LEXUS_ES: ToyotaCarInfo("Lexus ES 2017-18"), - CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18"), + CAR.LEXUS_ES: [ + ToyotaCarInfo("Lexus ES 2017-18"), + ToyotaCarInfo("Lexus ES Hybrid 2017-18"), + ], CAR.LEXUS_ES_TSS2: [ ToyotaCarInfo("Lexus ES 2019-24"), ToyotaCarInfo("Lexus ES Hybrid 2019-23", video_link="https://youtu.be/BZ29osRVJeg?t=12"), @@ -209,8 +210,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.LEXUS_RX: [ ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+"), ToyotaCarInfo("Lexus RX 2017-19"), - ], - CAR.LEXUS_RXH: [ + # Hybrid platforms ToyotaCarInfo("Lexus RX Hybrid 2016", "Lexus Safety System+"), ToyotaCarInfo("Lexus RX Hybrid 2017-19"), ], @@ -222,33 +222,32 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { # (addr, cars, bus, 1/freq*100, vl) STATIC_DSU_MSGS = [ - (0x128, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.AVALON), 1, 3, b'\xf4\x01\x90\x83\x00\x37'), - (0x128, (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH), 1, 3, b'\x03\x00\x20\x00\x00\x52'), - (0x141, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 1, 2, b'\x00\x00\x00\x46'), - (0x160, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 1, 7, b'\x00\x00\x08\x12\x01\x31\x9c\x51'), - (0x161, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.AVALON, CAR.LEXUS_RX, CAR.PRIUS_V, CAR.LEXUS_ES), + (0x128, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.AVALON), 1, 3, b'\xf4\x01\x90\x83\x00\x37'), + (0x128, (CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES), 1, 3, b'\x03\x00\x20\x00\x00\x52'), + (0x141, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, + CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 1, 2, b'\x00\x00\x00\x46'), + (0x160, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, + CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 1, 7, b'\x00\x00\x08\x12\x01\x31\x9c\x51'), + (0x161, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.AVALON, CAR.PRIUS_V), 1, 7, b'\x00\x1e\x00\x00\x00\x80\x07'), - (0X161, (CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ESH), 1, 7, b'\x00\x1e\x00\xd4\x00\x00\x5b'), - (0x283, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 0, 3, b'\x00\x00\x00\x00\x00\x00\x8c'), - (0x2E6, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH), 0, 3, b'\xff\xf8\x00\x08\x7f\xe0\x00\x4e'), - (0x2E7, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH), 0, 3, b'\xa8\x9c\x31\x9c\x00\x00\x00\x02'), - (0x33E, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH), 0, 20, b'\x0f\xff\x26\x40\x00\x1f\x00'), - (0x344, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 0, 5, b'\x00\x00\x01\x00\x00\x00\x00\x50'), - (0x365, (CAR.PRIUS, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.HIGHLANDERH), 0, 20, b'\x00\x00\x00\x80\x03\x00\x08'), - (0x365, (CAR.RAV4, CAR.RAV4H, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, + (0X161, (CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES), 1, 7, b'\x00\x1e\x00\xd4\x00\x00\x5b'), + (0x283, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, + CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 0, 3, b'\x00\x00\x00\x00\x00\x00\x8c'), + (0x2E6, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX), 0, 3, b'\xff\xf8\x00\x08\x7f\xe0\x00\x4e'), + (0x2E7, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX), 0, 3, b'\xa8\x9c\x31\x9c\x00\x00\x00\x02'), + (0x33E, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX), 0, 20, b'\x0f\xff\x26\x40\x00\x1f\x00'), + (0x344, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, + CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 0, 5, b'\x00\x00\x01\x00\x00\x00\x00\x50'), + (0x365, (CAR.PRIUS, CAR.LEXUS_NX, CAR.HIGHLANDER), 0, 20, b'\x00\x00\x00\x80\x03\x00\x08'), + (0x365, (CAR.RAV4, CAR.RAV4H, CAR.COROLLA, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_RX, CAR.PRIUS_V), 0, 20, b'\x00\x00\x00\x80\xfc\x00\x08'), - (0x366, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.HIGHLANDERH), 0, 20, b'\x00\x00\x4d\x82\x40\x02\x00'), - (0x366, (CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), + (0x366, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.HIGHLANDER), 0, 20, b'\x00\x00\x4d\x82\x40\x02\x00'), + (0x366, (CAR.RAV4, CAR.COROLLA, CAR.AVALON, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 0, 20, b'\x00\x72\x07\xff\x09\xfe\x00'), - (0x366, (CAR.LEXUS_ES,), 0, 20, b'\x00\x95\x07\xfe\x08\x05\x00'), - (0x470, (CAR.PRIUS, CAR.LEXUS_RXH), 1, 100, b'\x00\x00\x02\x7a'), - (0x470, (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.RAV4H, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.PRIUS_V), 1, 100, b'\x00\x00\x01\x79'), - (0x4CB, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.AVALON, - CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.LEXUS_ESH, CAR.LEXUS_RX, CAR.PRIUS_V), 0, 100, b'\x0c\x00\x00\x00\x00\x00\x00\x00'), + (0x470, (CAR.PRIUS, CAR.LEXUS_RX), 1, 100, b'\x00\x00\x02\x7a'), + (0x470, (CAR.HIGHLANDER, CAR.RAV4H, CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 1, 100, b'\x00\x00\x01\x79'), + (0x4CB, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RX, CAR.LEXUS_NX, CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.AVALON, + CAR.SIENNA, CAR.LEXUS_CTH, CAR.LEXUS_ES, CAR.PRIUS_V), 0, 100, b'\x0c\x00\x00\x00\x00\x00\x00\x00'), ] @@ -395,8 +394,8 @@ FW_QUERY_CONFIG = FwQueryConfig( # FIXME: On some models, abs can sometimes be missing Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS, CAR.ALPHARD_TSS2], # On some models, the engine can show on two different addresses - Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.CHR_TSS2, CAR.LEXUS_IS, CAR.LEXUS_RC, - CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2, CAR.LEXUS_RX_TSS2], + Ecu.engine: [CAR.HIGHLANDER, CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.CHR_TSS2, CAR.LEXUS_IS, + CAR.LEXUS_RC, CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2, CAR.LEXUS_RX, CAR.LEXUS_RX_TSS2], }, extra_ecus=[ # All known ECUs on a late-model Toyota vehicle not queried here: @@ -444,7 +443,6 @@ DBC = { CAR.COROLLA: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), CAR.LEXUS_RC: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_RX: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), - CAR.LEXUS_RXH: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CHR_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), @@ -452,7 +450,6 @@ DBC = { CAR.CAMRY_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.HIGHLANDER: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.HIGHLANDER_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.HIGHLANDERH: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.AVALON: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.AVALON_2019: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.AVALON_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), @@ -462,7 +459,6 @@ DBC = { CAR.COROLLA_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.LEXUS_ES: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), CAR.LEXUS_ES_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), - CAR.LEXUS_ESH: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), CAR.SIENNA: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_IS: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_IS_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), @@ -495,4 +491,4 @@ RADAR_ACC_CAR = {CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2} ANGLE_CONTROL_CAR = {CAR.RAV4_TSS2_2023} # no resume button press required -NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH} +NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.PRIUS_V, CAR.RAV4H, CAR.HIGHLANDER, CAR.SIENNA} From c4362bd958d1473b97ae336fd0e9bd7b50fe4fb9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 20:04:08 -0800 Subject: [PATCH 498/724] bootlog doesn't block startup (#31061) * bootlog doesn't block startup * keep params * fix * cleanup --- selfdrive/manager/helpers.py | 19 +++++++++++++++++++ selfdrive/manager/manager.py | 6 ++---- system/hardware/hw.h | 4 ++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/selfdrive/manager/helpers.py b/selfdrive/manager/helpers.py index f8607fffc6..ba529910e5 100644 --- a/selfdrive/manager/helpers.py +++ b/selfdrive/manager/helpers.py @@ -3,7 +3,13 @@ import sys import fcntl import errno import signal +import shutil +import subprocess +import tempfile +import threading +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params def unblock_stdout() -> None: # get a non-blocking stdout @@ -41,3 +47,16 @@ def unblock_stdout() -> None: def write_onroad_params(started, params): params.put_bool("IsOnroad", started) params.put_bool("IsOffroad", not started) + + +def save_bootlog(): + # copy current params + tmp = tempfile.mkdtemp() + shutil.copytree(Params().get_param_path() + "/..", tmp, dirs_exist_ok=True) + + env = os.environ.copy() + env['PARAMS_ROOT'] = tmp + + t = threading.Thread(target=subprocess.call, args=("./bootlog", ), kwargs={'cwd': os.path.join(BASEDIR, "system/loggerd"), 'env': env}) + t.daemon = True + t.start() diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 8b9bba4a47..2bc14945fe 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -2,7 +2,6 @@ import datetime import os import signal -import subprocess import sys import traceback from typing import List, Tuple, Union @@ -10,12 +9,11 @@ from typing import List, Tuple, Union from cereal import log import cereal.messaging as messaging import openpilot.selfdrive.sentry as sentry -from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params, ParamKeyType from openpilot.common.text_window import TextWindow from openpilot.selfdrive.boardd.set_time import set_time from openpilot.system.hardware import HARDWARE, PC -from openpilot.selfdrive.manager.helpers import unblock_stdout, write_onroad_params +from openpilot.selfdrive.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog from openpilot.selfdrive.manager.process import ensure_running from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID @@ -31,7 +29,7 @@ def manager_init() -> None: set_time(cloudlog) # save boot log - subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "system/loggerd")) + save_bootlog() params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) diff --git a/system/hardware/hw.h b/system/hardware/hw.h index 2f6ccfffda..013a885a81 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -30,13 +30,13 @@ namespace Path { } inline std::string params() { - return Hardware::PC() ? util::getenv("PARAMS_ROOT", Path::comma_home() + "/params") : "/data/params"; + return util::getenv("PARAMS_ROOT", Hardware::PC ? (Path::comma_home() + "/params") : "/data/params"); } inline std::string rsa_file() { return Hardware::PC() ? Path::comma_home() + "/persist/comma/id_rsa" : "/persist/comma/id_rsa"; } - + inline std::string swaglog_ipc() { return "ipc:///tmp/logmessage" + Path::openpilot_prefix(); } From e2df83089fe0050d41ef3d7f9241c75b14cba134 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Thu, 18 Jan 2024 20:12:04 -0800 Subject: [PATCH 499/724] fix param path --- system/hardware/hw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/hardware/hw.h b/system/hardware/hw.h index 013a885a81..394807ccb5 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -30,7 +30,7 @@ namespace Path { } inline std::string params() { - return util::getenv("PARAMS_ROOT", Hardware::PC ? (Path::comma_home() + "/params") : "/data/params"); + return util::getenv("PARAMS_ROOT", Hardware::PC() ? (Path::comma_home() + "/params") : "/data/params"); } inline std::string rsa_file() { From ce947ffe9a1f31190b89b83e2266bf2a78f2d798 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 20:45:41 -0800 Subject: [PATCH 500/724] manager: merge prepare and init --- selfdrive/manager/manager.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 2bc14945fe..13b03c4dce 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -105,8 +105,7 @@ def manager_init() -> None: dirty=is_dirty(), device=HARDWARE.get_device_type()) - -def manager_prepare() -> None: + # preimport all processes for p in managed_processes.values(): p.prepare() @@ -186,17 +185,8 @@ def manager_thread() -> None: def main() -> None: - prepare_only = os.getenv("PREPAREONLY") is not None - manager_init() - - # Start UI early so prepare can happen in the background - if not prepare_only: - managed_processes['ui'].start() - - manager_prepare() - - if prepare_only: + if os.getenv("PREPAREONLY") is not None: return # SystemExit on sigterm From e3b17d10f35d6b2107d501c4e5d6b748f23b8900 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 21:02:18 -0800 Subject: [PATCH 501/724] speedup prebuilt check (#31065) * speedup prebuilt check * one place --- launch_chffrplus.sh | 9 +++++++-- selfdrive/manager/build.py | 3 +-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index a91bd677aa..8ebf3a53f2 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -72,14 +72,19 @@ function launch { export PYTHONPATH="$PWD" # hardware specific init - agnos_init + if [ -f /AGNOS ]; then + agnos_init + fi # write tmux scrollback to a file tmux capture-pane -pq -S-1000 > /tmp/launch_log # start manager cd selfdrive/manager - ./build.py && ./manager.py + if [ ! -f $DIR/prebuilt ]; then + ./build.py + fi + ./manager.py # if broken, keep on screen error while true; do sleep 1; done diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 836723e5a8..f2758cf32b 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -17,7 +17,6 @@ CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache") TOTAL_SCONS_NODES = 2560 MAX_BUILD_PROGRESS = 100 -PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: env = os.environ.copy() @@ -85,7 +84,7 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: f.unlink() -if __name__ == "__main__" and not PREBUILT: +if __name__ == "__main__": spinner = Spinner() spinner.update_progress(0, 100) build(spinner, is_dirty(), minimal = AGNOS) From 7a6463b2b4f8737101c8d78d5622f55f25d7c1b2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 21:05:57 -0800 Subject: [PATCH 502/724] remove early RTC time pull (#31066) --- launch_chffrplus.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 8ebf3a53f2..9271c15436 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -30,9 +30,6 @@ function launch { # Remove orphaned git lock if it exists on boot [ -f "$DIR/.git/index.lock" ] && rm -f $DIR/.git/index.lock - # Pull time from panda - $DIR/selfdrive/boardd/set_time.py - # Check to see if there's a valid overlay-based update available. Conditions # are as follows: # From b4ebba384b0d5e884f99a56caa8de92675cbd60f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 18 Jan 2024 22:09:55 -0800 Subject: [PATCH 503/724] cleanup startup params (#31068) --- Jenkinsfile | 1 + selfdrive/manager/helpers.py | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0ae5f5076d..4b0fe6a845 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -35,6 +35,7 @@ source ~/.bash_profile if [ -f /TICI ]; then source /etc/profile + rm -rf /tmp/tmp* rm -rf ~/.commacache if ! systemctl is-active --quiet systemd-resolved; then diff --git a/selfdrive/manager/helpers.py b/selfdrive/manager/helpers.py index ba529910e5..1b6f8df1ef 100644 --- a/selfdrive/manager/helpers.py +++ b/selfdrive/manager/helpers.py @@ -54,9 +54,11 @@ def save_bootlog(): tmp = tempfile.mkdtemp() shutil.copytree(Params().get_param_path() + "/..", tmp, dirs_exist_ok=True) - env = os.environ.copy() - env['PARAMS_ROOT'] = tmp - - t = threading.Thread(target=subprocess.call, args=("./bootlog", ), kwargs={'cwd': os.path.join(BASEDIR, "system/loggerd"), 'env': env}) + def fn(tmpdir): + env = os.environ.copy() + env['PARAMS_ROOT'] = tmpdir + subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "system/loggerd"), env=env) + shutil.rmtree(tmpdir) + t = threading.Thread(target=fn, args=(tmp, )) t.daemon = True t.start() From 35ed8a0886d8badf05f72edc597a536e1b03d08c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 19 Jan 2024 01:13:19 -0500 Subject: [PATCH 504/724] CI: use namespace.io (#31064) * namespace * mkdir * fix * restore only * restore * v3 * disable provenance --- .../workflows/auto-cache-restore/action.yaml | 34 +++++++++++++++++++ .../workflows/compile-openpilot/action.yaml | 8 +---- .github/workflows/selfdrive_tests.yaml | 17 +++++----- .../workflows/setup-with-retry/action.yaml | 7 ---- .github/workflows/setup/action.yaml | 25 +++----------- .github/workflows/tools_tests.yaml | 2 +- selfdrive/test/docker_build.sh | 2 +- 7 files changed, 50 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/auto-cache-restore/action.yaml diff --git a/.github/workflows/auto-cache-restore/action.yaml b/.github/workflows/auto-cache-restore/action.yaml new file mode 100644 index 0000000000..0aab3b3eef --- /dev/null +++ b/.github/workflows/auto-cache-restore/action.yaml @@ -0,0 +1,34 @@ +name: 'automatically cache based on current runner' + +inputs: + path: + description: 'path to cache' + required: true + key: + description: 'key' + required: true + restore-keys: + description: 'restore-keys' + required: true + +runs: + using: "composite" + steps: + - name: setup namespace cache + if: ${{ contains(runner.name, 'nsc') }} + uses: namespacelabs/nscloud-cache-action@v1 + with: + path: ${{ inputs.path }} + + - name: setup github cache + if: ${{ !contains(runner.name, 'nsc') }} + uses: actions/cache/restore@v3 + with: + path: ${{ inputs.path }} + key: ${{ inputs.key }} + restore-keys: ${{ inputs.restore-keys }} + + # make the directory manually in case we didn't get a hit, so it doesn't fail on future steps + - id: scons-cache-setup + shell: bash + run: mkdir -p ${{ inputs.path }} diff --git a/.github/workflows/compile-openpilot/action.yaml b/.github/workflows/compile-openpilot/action.yaml index 8775c96262..2945b67d2e 100644 --- a/.github/workflows/compile-openpilot/action.yaml +++ b/.github/workflows/compile-openpilot/action.yaml @@ -1,11 +1,5 @@ name: 'compile openpilot' -inputs: - cache_key_prefix: - description: 'Prefix for caching key' - required: false - default: 'scons' - runs: using: "composite" steps: @@ -24,4 +18,4 @@ runs: if: github.ref == 'refs/heads/master' with: path: .ci_cache/scons_cache - key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index b9e80e9f74..dfd3570d4f 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -75,7 +75,7 @@ jobs: ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -83,18 +83,15 @@ jobs: - uses: ./.github/workflows/setup-with-retry with: docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} - cache_key_prefix: scons_${{ matrix.arch }} - uses: ./.github/workflows/compile-openpilot timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 15 || 30) }} # allow more time when we missed the scons cache - with: - cache_key_prefix: scons_${{ matrix.arch }} docker_push: name: docker push strategy: matrix: arch: ${{ fromJson( (github.repository == 'commaai/openpilot') && '["x86_64", "aarch64"]' || '["x86_64"]' ) }} - runs-on: ${{ (matrix.arch == 'aarch64') && 'buildjet-2vcpu-ubuntu-2204-arm' || 'ubuntu-20.04' }} + runs-on: ${{ (matrix.arch == 'aarch64') && 'namespace-profile-arm64-2x8' || 'ubuntu-20.04' }} if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v4 @@ -133,7 +130,9 @@ jobs: static_analysis: name: static analysis - runs-on: ubuntu-20.04 + runs-on: ${{ ((github.repository == 'commaai/openpilot') && + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -165,7 +164,7 @@ jobs: name: unit tests runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }} + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: @@ -181,7 +180,7 @@ jobs: run: | ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ - $PYTEST --timeout 40 -m 'not slow' && \ + $PYTEST --timeout 40 -m 'not slow' -n $(nproc) && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py" @@ -196,7 +195,7 @@ jobs: name: process replay runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'buildjet-8vcpu-ubuntu-2004' || 'ubuntu-20.04' }} + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/setup-with-retry/action.yaml b/.github/workflows/setup-with-retry/action.yaml index e0da746b44..369ac45c9a 100644 --- a/.github/workflows/setup-with-retry/action.yaml +++ b/.github/workflows/setup-with-retry/action.yaml @@ -5,10 +5,6 @@ inputs: description: 'Auth token for Docker Hub, required for BuildJet jobs' required: false default: '' - cache_key_prefix: - description: 'Prefix for caching key' - required: false - default: 'scons_x86_64' sleep_time: description: 'Time to sleep between retries' required: false @@ -22,7 +18,6 @@ runs: continue-on-error: true with: docker_hub_pat: ${{ inputs.docker_hub_pat }} - cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true - if: steps.setup1.outcome == 'failure' shell: bash @@ -33,7 +28,6 @@ runs: continue-on-error: true with: docker_hub_pat: ${{ inputs.docker_hub_pat }} - cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true - if: steps.setup2.outcome == 'failure' shell: bash @@ -43,5 +37,4 @@ runs: uses: ./.github/workflows/setup with: docker_hub_pat: ${{ inputs.docker_hub_pat }} - cache_key_prefix: ${{ inputs.cache_key_prefix }} is_retried: true diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index 8bb1ccc376..ec1626c01b 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -5,10 +5,6 @@ inputs: description: 'Auth token for Docker Hub, required for BuildJet jobs' required: true default: '' - cache_key_prefix: - description: 'Prefix for caching key' - required: true - default: 'scons_x86_64' is_retried: description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly' required: false @@ -47,19 +43,14 @@ runs: run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - shell: bash run: echo "$CACHE_COMMIT_DATE" - - id: restore-scons-cache - uses: actions/cache/restore@v3 + - id: scons-cache + uses: ./.github/workflows/auto-cache-restore with: path: .ci_cache/scons_cache - key: ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | - ${{ inputs.cache_key_prefix }}-${{ env.CACHE_COMMIT_DATE }}- - ${{ inputs.cache_key_prefix }}- - # if we didn't get a cache hit, make the directory manually so it doesn't fail on future steps - - id: scons-cache-setup - shell: bash - if: steps.restore-scons-cache.outputs.cache-hit != 'true' - run: mkdir -p $GITHUB_WORKSPACE/.ci_cache/scons_cache + scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }} + scons-${{ runner.arch }} # as suggested here: https://github.com/moby/moby/issues/32816#issuecomment-910030001 - id: normalize-file-permissions shell: bash @@ -67,12 +58,6 @@ runs: run: | find . -type f -executable -not -perm 755 -exec chmod 755 {} \; find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \; - - id: setup-buildx-action - if: contains(runner.name, 'buildjet') - name: Set up Docker Buildx on buildjet to ensure a consistent cache - uses: docker/setup-buildx-action@v2 - with: - driver: docker-container # build our docker image - shell: bash run: eval ${{ env.BUILD }} \ No newline at end of file diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index f549ece1b1..5cecfb7e4e 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -81,7 +81,7 @@ jobs: - name: Run dev container run: | mkdir -p /tmp/devcontainer_scons_cache/ - cp -r $GITHUB_WORKSPACE/.ci_cache/scons_cache/* /tmp/devcontainer_scons_cache/ + cp -r $GITHUB_WORKSPACE/.ci_cache/scons_cache/. /tmp/devcontainer_scons_cache/ devcontainer up --workspace-folder . - name: Test environment run: | diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh index bd8c34c472..5f77ceb10d 100755 --- a/selfdrive/test/docker_build.sh +++ b/selfdrive/test/docker_build.sh @@ -17,7 +17,7 @@ fi source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" -DOCKER_BUILDKIT=1 docker buildx build --pull --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR +DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR if [ -n "$PUSH_IMAGE" ]; then docker push $REMOTE_TAG From dfb56abf9180e20fc30c8857229d69761c3e0f71 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 19 Jan 2024 01:40:28 -0500 Subject: [PATCH 505/724] silence xvfb (#31069) silence --- selfdrive/test/setup_xvfb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/setup_xvfb.sh b/selfdrive/test/setup_xvfb.sh index 2c79adec4a..79110fc4fb 100755 --- a/selfdrive/test/setup_xvfb.sh +++ b/selfdrive/test/setup_xvfb.sh @@ -5,7 +5,7 @@ DISP_ID=99 export DISPLAY=:$DISP_ID -sudo Xvfb $DISPLAY -screen 0 2160x1080x24 & +sudo Xvfb $DISPLAY -screen 0 2160x1080x24 2>/dev/null & # check for x11 socket for the specified display ID while [ ! -S /tmp/.X11-unix/X$DISP_ID ] From a6695e4489f8aaeccd246dba8aa5107393cf6565 Mon Sep 17 00:00:00 2001 From: ugtthis <142481257+ugtthis@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:38:11 -0800 Subject: [PATCH 506/724] UI/close_btn: Removed unused border code (#31073) --- selfdrive/ui/qt/offroad/settings.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 669de5eae9..eec9fede08 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -356,7 +356,6 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QPushButton { font-size: 140px; padding-bottom: 20px; - border 1px grey solid; border-radius: 100px; background-color: #292929; font-weight: 400; From f3689ef56108e4eafd00f17086e1f462451edefd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 19 Jan 2024 10:44:46 -0800 Subject: [PATCH 507/724] remove old ref to manager_prepare --- selfdrive/manager/test/test_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 0fa186baff..e97c4d14d7 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -53,7 +53,6 @@ class TestManager(unittest.TestCase): """ HARDWARE.set_power_save(False) manager.manager_init() - manager.manager_prepare() CP = car.CarParams.new_message() procs = ensure_running(managed_processes.values(), True, Params(), CP, not_run=BLACKLIST_PROCS) From 16cd20f4eb87acb1559b3d8e710b750b7eb0c2cc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 19 Jan 2024 11:51:58 -0800 Subject: [PATCH 508/724] don't block startup on time (#31074) * don't block startup on time * cleanup * unused --- selfdrive/thermald/thermald.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 96cf0f49f8..605cf51089 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import datetime import os import json import queue @@ -15,7 +14,6 @@ import cereal.messaging as messaging from cereal import log from cereal.services import SERVICE_LIST from openpilot.common.dict_helpers import strip_deprecated_keys -from openpilot.common.time import MIN_DATE from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.common.realtime import DT_TRML @@ -293,11 +291,6 @@ def thermald_thread(end_event, hw_queue) -> None: # **** starting logic **** - # Ensure date/time are valid - now = datetime.datetime.utcnow() - startup_conditions["time_valid"] = now > MIN_DATE - set_offroad_alert_if_changed("Offroad_InvalidTime", (not startup_conditions["time_valid"]) and peripheral_panda_present) - startup_conditions["up_to_date"] = params.get("Offroad_ConnectivityNeeded") is None or params.get_bool("DisableUpdates") or params.get_bool("SnoozeUpdate") startup_conditions["not_uninstalling"] = not params.get_bool("DoUninstall") startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version From bbbe94bc3efd9a6de32f4d806a025a734e215a6f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 19 Jan 2024 15:43:47 -0500 Subject: [PATCH 509/724] CI: auto-cache add option to save cache (#31076) * save cache option * inputs --- .../{auto-cache-restore => auto-cache}/action.yaml | 13 ++++++++++++- .github/workflows/setup/action.yaml | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) rename .github/workflows/{auto-cache-restore => auto-cache}/action.yaml (71%) diff --git a/.github/workflows/auto-cache-restore/action.yaml b/.github/workflows/auto-cache/action.yaml similarity index 71% rename from .github/workflows/auto-cache-restore/action.yaml rename to .github/workflows/auto-cache/action.yaml index 0aab3b3eef..523ce9fe8f 100644 --- a/.github/workflows/auto-cache-restore/action.yaml +++ b/.github/workflows/auto-cache/action.yaml @@ -10,6 +10,10 @@ inputs: restore-keys: description: 'restore-keys' required: true + save: + description: 'whether to save the cache' + default: 'false' + required: false runs: using: "composite" @@ -20,7 +24,7 @@ runs: with: path: ${{ inputs.path }} - - name: setup github cache + - name: restore github cache if: ${{ !contains(runner.name, 'nsc') }} uses: actions/cache/restore@v3 with: @@ -32,3 +36,10 @@ runs: - id: scons-cache-setup shell: bash run: mkdir -p ${{ inputs.path }} + + - name: save github cache + if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }} + uses: actions/cache/save@v3 + with: + path: ${{ inputs.path }} + key: ${{ inputs.key }} diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index ec1626c01b..970d62030d 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -44,7 +44,7 @@ runs: - shell: bash run: echo "$CACHE_COMMIT_DATE" - id: scons-cache - uses: ./.github/workflows/auto-cache-restore + uses: ./.github/workflows/auto-cache with: path: .ci_cache/scons_cache key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} From f0f7e86dcc1c1a5ab3fbf68854e6f609691eda25 Mon Sep 17 00:00:00 2001 From: Hoang Bui <47828508+bongbui321@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:06:02 -0500 Subject: [PATCH 510/724] Enable cache for pre-commit (#31071) * enable cache * test cache * commai cache * remove id * only save on master --------- Co-authored-by: Justin Newberry --- .github/workflows/selfdrive_tests.yaml | 8 +++++--- .github/workflows/setup-pre-commit/action.yaml | 12 ++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/setup-pre-commit/action.yaml diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index dfd3570d4f..c8864bd663 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -20,7 +20,7 @@ env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PRE_COMMIT_HOME=/tmp/pre-commit -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/pre-commit:/tmp/pre-commit -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c BUILD_CL: selfdrive/test/docker_build.sh cl @@ -42,6 +42,7 @@ jobs: - name: Build devel timeout-minutes: 1 run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh + - uses: ./.github/workflows/setup-pre-commit - uses: ./.github/workflows/setup-with-retry - name: Check submodules if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' @@ -66,7 +67,7 @@ jobs: cp pyproject.toml $STRIPPED_DIR cp poetry.lock $STRIPPED_DIR cd $STRIPPED_DIR - ${{ env.RUN }} "unset PYTHONWARNINGS && SKIP=check-added-large-files pre-commit run --all" + ${{ env.RUN }} "unset PYTHONWARNINGS && SKIP=check-added-large-files pre-commit run --all && chmod -R 777 /tmp/pre-commit" build: strategy: @@ -137,10 +138,11 @@ jobs: - uses: actions/checkout@v4 with: submodules: true + - uses: ./.github/workflows/setup-pre-commit - uses: ./.github/workflows/setup-with-retry - name: pre-commit timeout-minutes: 4 - run: ${{ env.RUN }} "unset PYTHONWARNINGS && pre-commit run --all" + run: ${{ env.RUN }} "unset PYTHONWARNINGS && pre-commit run --all && chmod -R 777 /tmp/pre-commit" valgrind: name: valgrind diff --git a/.github/workflows/setup-pre-commit/action.yaml b/.github/workflows/setup-pre-commit/action.yaml new file mode 100644 index 0000000000..1b3e16e73f --- /dev/null +++ b/.github/workflows/setup-pre-commit/action.yaml @@ -0,0 +1,12 @@ +name: 'set up pre-commit environment' + +runs: + using: "composite" + steps: + - uses: ./.github/workflows/auto-cache + with: + path: .ci_cache/pre-commit + key: pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }} + restore-keys: | + pre-commit- + save: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' }} From 8a26b4383f6dfa6a1fca7d4db5bfcc0d4b06deda Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 19 Jan 2024 13:19:41 -0800 Subject: [PATCH 511/724] bump that up --- .github/workflows/selfdrive_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index c8864bd663..f58a861235 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -182,7 +182,7 @@ jobs: run: | ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ - $PYTEST --timeout 40 -m 'not slow' -n $(nproc) && \ + $PYTEST --timeout 60 -m 'not slow' -n $(nproc) && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py" From c907dd9e7026d0ed5f7ab765b035714c341361cb Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 19 Jan 2024 16:25:41 -0500 Subject: [PATCH 512/724] CI: fix post run cache (#31078) * post cache only * fix * newline * really github??? --- .github/workflows/auto-cache/action.yaml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/auto-cache/action.yaml b/.github/workflows/auto-cache/action.yaml index 523ce9fe8f..ec929d3de2 100644 --- a/.github/workflows/auto-cache/action.yaml +++ b/.github/workflows/auto-cache/action.yaml @@ -24,9 +24,17 @@ runs: with: path: ${{ inputs.path }} - - name: restore github cache - if: ${{ !contains(runner.name, 'nsc') }} - uses: actions/cache/restore@v3 + - name: setup github cache + if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }} + uses: 'actions/cache@v3' + with: + path: ${{ inputs.path }} + key: ${{ inputs.key }} + restore-keys: ${{ inputs.restore-keys }} + + - name: setup github cache + if: ${{ !contains(runner.name, 'nsc') && inputs.save == 'false' }} + uses: 'actions/cache/restore@v3' with: path: ${{ inputs.path }} key: ${{ inputs.key }} @@ -36,10 +44,3 @@ runs: - id: scons-cache-setup shell: bash run: mkdir -p ${{ inputs.path }} - - - name: save github cache - if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }} - uses: actions/cache/save@v3 - with: - path: ${{ inputs.path }} - key: ${{ inputs.key }} From 28028aef174ddce0f1c8317f7fe2bc055db0b295 Mon Sep 17 00:00:00 2001 From: Andrei Radulescu Date: Fri, 19 Jan 2024 23:28:29 +0200 Subject: [PATCH 513/724] Support for newer versions of Qt (#31072) * Dynamic QtGui version * Fixed use of undeclared identifier 'va_start' * Fix for Qt deprecations * Fix for dynamic QtGui version * Update selfdrive/ui/SConscript --------- Co-authored-by: Adeeb Shihadeh --- SConstruct | 5 ++++- common/swaglog.cc | 1 + selfdrive/ui/SConscript | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 11b95702c3..a95a77817b 100644 --- a/SConstruct +++ b/SConstruct @@ -297,8 +297,11 @@ else: qt_env['QTDIR'] = qt_install_prefix qt_dirs = [ f"{qt_install_headers}", - f"{qt_install_headers}/QtGui/5.12.8/QtGui", ] + + qt_gui_path = os.path.join(qt_install_headers, "QtGui") + qt_gui_dirs = [d for d in os.listdir(qt_gui_path) if os.path.isdir(os.path.join(qt_gui_path, d))] + qt_dirs += f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui" if qt_gui_dirs else [] qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] qt_libs = [f"Qt5{m}" for m in qt_modules] diff --git a/common/swaglog.cc b/common/swaglog.cc index c22571dc9f..873836b725 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -10,6 +10,7 @@ #include #include +#include #include "third_party/json11/json11.hpp" #include "common/version.h" #include "system/hardware/hw.h" diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 7b570b93f8..03006e3b49 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -19,6 +19,9 @@ if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] qt_env['FRAMEWORKS'] += ['OpenCL'] +# FIXME: remove this once we're on 5.15 (24.04) +qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] + qt_util = qt_env.Library("qt_util", ["#selfdrive/ui/qt/api.cc", "#selfdrive/ui/qt/util.cc"], LIBS=base_libs) widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", "qt/widgets/ssh_keys.cc", "qt/widgets/toggle.cc", "qt/widgets/controls.cc", From 574fd03208013eda46fa9bbfef9a6ed0eb44f803 Mon Sep 17 00:00:00 2001 From: Comma Device Date: Fri, 19 Jan 2024 13:45:06 -0800 Subject: [PATCH 514/724] fix tici build --- SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index a95a77817b..3faa978087 100644 --- a/SConstruct +++ b/SConstruct @@ -301,7 +301,7 @@ else: qt_gui_path = os.path.join(qt_install_headers, "QtGui") qt_gui_dirs = [d for d in os.listdir(qt_gui_path) if os.path.isdir(os.path.join(qt_gui_path, d))] - qt_dirs += f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui" if qt_gui_dirs else [] + qt_dirs += [f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui", ] if qt_gui_dirs else [] qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] qt_libs = [f"Qt5{m}" for m in qt_modules] From 18467c8383dbfbd08cfc3bd6534dce068991c9aa Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 19 Jan 2024 16:57:10 -0500 Subject: [PATCH 515/724] fix cache permissions (#31084) * fix cache permissions * chown --- .github/workflows/auto-cache/action.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto-cache/action.yaml b/.github/workflows/auto-cache/action.yaml index ec929d3de2..173803f7f0 100644 --- a/.github/workflows/auto-cache/action.yaml +++ b/.github/workflows/auto-cache/action.yaml @@ -43,4 +43,7 @@ runs: # make the directory manually in case we didn't get a hit, so it doesn't fail on future steps - id: scons-cache-setup shell: bash - run: mkdir -p ${{ inputs.path }} + run: | + mkdir -p ${{ inputs.path }} + sudo chmod -R 777 ${{ inputs.path }} + sudo chown -R $USER ${{ inputs.path }} \ No newline at end of file From 7cd9568423a2e78187d16018b70a78f56c02027d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 19 Jan 2024 14:05:03 -0800 Subject: [PATCH 516/724] remove dashcam branch (#31081) * remove dashcam branch * fix * more cleanup * remove param * one more --- Jenkinsfile | 4 ++-- common/params.cc | 1 - launch_env.sh | 4 ---- launch_openpilot.sh | 2 -- release/build_release.sh | 7 ------- release/verify.sh | 2 +- scripts/build_small.sh | 2 +- selfdrive/controls/controlsd.py | 3 +-- selfdrive/controls/tests/test_startup.py | 1 - selfdrive/manager/manager.py | 7 ------- selfdrive/manager/test/test_manager.py | 1 - selfdrive/test/helpers.py | 2 -- selfdrive/test/process_replay/process_replay.py | 1 - selfdrive/thermald/thermald.py | 3 +-- selfdrive/ui/SConscript | 14 ++++---------- selfdrive/ui/installer/continue_dashcam.sh | 4 ---- selfdrive/ui/installer/installer.cc | 4 ++-- selfdrive/ui/qt/offroad/onboarding.cc | 2 +- selfdrive/ui/qt/offroad/settings.cc | 16 +++++++--------- selfdrive/ui/qt/setup/setup.cc | 6 +++--- selfdrive/ui/qt/util.cc | 2 +- selfdrive/ui/translations/main_ar.ts | 4 ---- selfdrive/ui/translations/main_de.ts | 4 ---- selfdrive/ui/translations/main_fr.ts | 4 ---- selfdrive/ui/translations/main_ja.ts | 4 ---- selfdrive/ui/translations/main_ko.ts | 4 ---- selfdrive/ui/translations/main_pt-BR.ts | 4 ---- selfdrive/ui/translations/main_th.ts | 4 ---- selfdrive/ui/translations/main_tr.ts | 4 ---- selfdrive/ui/translations/main_zh-CHS.ts | 4 ---- selfdrive/ui/translations/main_zh-CHT.ts | 4 ---- selfdrive/updated.py | 2 +- system/loggerd/logger.cc | 2 +- system/version.py | 2 +- tools/webcam/README.md | 2 +- 35 files changed, 28 insertions(+), 108 deletions(-) delete mode 100755 selfdrive/ui/installer/continue_dashcam.sh diff --git a/Jenkinsfile b/Jenkinsfile index 4b0fe6a845..1d724721d0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -168,7 +168,7 @@ node { env.GIT_COMMIT = checkout(scm).GIT_COMMIT def excludeBranches = ['master-ci', 'devel', 'devel-staging', 'release3', 'release3-staging', - 'dashcam3', 'dashcam3-staging', 'testing-closet*', 'hotfix-*'] + 'testing-closet*', 'hotfix-*'] def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*') if (env.BRANCH_NAME != 'master') { @@ -180,7 +180,7 @@ node { try { if (env.BRANCH_NAME == 'devel-staging') { deviceStage("build release3-staging", "tici-needs-can", [], [ - ["build release3-staging & dashcam3-staging", "RELEASE_BRANCH=release3-staging DASHCAM_BRANCH=dashcam3-staging $SOURCE_DIR/release/build_release.sh"], + ["build release3-staging", "RELEASE_BRANCH=release3-staging $SOURCE_DIR/release/build_release.sh"], ]) } diff --git a/common/params.cc b/common/params.cc index 639de5322b..386813efdd 100644 --- a/common/params.cc +++ b/common/params.cc @@ -182,7 +182,6 @@ std::unordered_map keys = { {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"PandaSomResetTriggered", CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION}, {"PandaSignatures", CLEAR_ON_MANAGER_START}, - {"Passive", PERSISTENT}, {"PrimeType", PERSISTENT}, {"RecordFront", PERSISTENT}, {"RecordFrontLock", PERSISTENT}, // for the internal fleet diff --git a/launch_env.sh b/launch_env.sh index dd8f431c63..c16f061912 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -10,8 +10,4 @@ if [ -z "$AGNOS_VERSION" ]; then export AGNOS_VERSION="9.1" fi -if [ -z "$PASSIVE" ]; then - export PASSIVE="1" -fi - export STAGING_ROOT="/data/safe_staging" diff --git a/launch_openpilot.sh b/launch_openpilot.sh index 1525e1715f..2888814c22 100755 --- a/launch_openpilot.sh +++ b/launch_openpilot.sh @@ -1,5 +1,3 @@ #!/usr/bin/bash -export PASSIVE="0" exec ./launch_chffrplus.sh - diff --git a/release/build_release.sh b/release/build_release.sh index b713876cd6..fc15cf6cdf 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -102,11 +102,4 @@ if [ ! -z "$RELEASE_BRANCH" ]; then git push -f origin $RELEASE_BRANCH:$RELEASE_BRANCH fi -if [ ! -z "$DASHCAM_BRANCH" ]; then - # Create dashcam - git rm selfdrive/car/*/carcontroller.py - git commit -m "create dashcam release from release" - git push -f origin $RELEASE_BRANCH:$DASHCAM_BRANCH -fi - echo "[-] done T=$SECONDS" diff --git a/release/verify.sh b/release/verify.sh index 56f21183f1..ec5266bd81 100755 --- a/release/verify.sh +++ b/release/verify.sh @@ -6,7 +6,7 @@ RED="\033[0;31m" GREEN="\033[0;32m" CLEAR="\033[0m" -BRANCHES="devel dashcam3 release3" +BRANCHES="devel release3" for b in $BRANCHES; do if git diff --quiet origin/$b origin/$b-staging && [ "$(git rev-parse origin/$b)" = "$(git rev-parse origin/$b-staging)" ]; then printf "%-10s $GREEN ok $CLEAR\n" "$b" diff --git a/scripts/build_small.sh b/scripts/build_small.sh index d53c7a6c78..90f55373e2 100755 --- a/scripts/build_small.sh +++ b/scripts/build_small.sh @@ -26,7 +26,7 @@ cd $OUT git tag -l | xargs git tag -d # remove non-master branches -BRANCHES="release2 dashcam dashcam3 release3 devel master-ci nightly" +BRANCHES="release2 release3 devel master-ci nightly" for branch in $BRANCHES; do git branch -D $branch git branch -D ${branch}-staging || true diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 78bcf8a9b0..a25b23469f 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -105,14 +105,13 @@ class Controls: self.is_metric = self.params.get_bool("IsMetric") self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled") openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle") - passive = self.params.get_bool("Passive") or not openpilot_enabled_toggle # detect sound card presence and ensure successful init sounds_available = HARDWARE.get_sound_card_online() car_recognized = self.CP.carName != 'mock' - controller_available = self.CI.CC is not None and not passive and not self.CP.dashcamOnly + controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly self.CP.passive = not car_recognized or not controller_available or self.CP.dashcamOnly if self.CP.passive: safety_config = car.CarParams.SafetyConfig.new_message() diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py index a93152e84e..02c0f486fe 100755 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -69,7 +69,6 @@ class TestStartup(unittest.TestCase): pm = messaging.PubMaster(['can', 'pandaStates']) params = Params() - params.put_bool("Passive", False) params.put_bool("OpenpilotEnabledToggle", True) # Build capnn version of FW array diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 13b03c4dce..5c0d5518be 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -58,13 +58,6 @@ def manager_init() -> None: if params.get(k) is None: params.put(k, v) - # is this dashcam? - if os.getenv("PASSIVE") is not None: - params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0")))) - - if params.get("Passive") is None: - raise Exception("Passive must be set to continue") - # Create folders needed for msgq try: os.mkdir("/dev/shm") diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index e97c4d14d7..9dc8bf1cda 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -21,7 +21,6 @@ BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] @pytest.mark.tici class TestManager(unittest.TestCase): def setUp(self): - os.environ['PASSIVE'] = '0' HARDWARE.set_power_save(False) # ensure clean CarParams diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 693604442d..af6390d749 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -12,7 +12,6 @@ from openpilot.tools.lib.logreader import LogIterable def set_params_enabled(): - os.environ['PASSIVE'] = "0" os.environ['REPLAY'] = "1" os.environ['FINGERPRINT'] = "TOYOTA COROLLA TSS2 2019" os.environ['LOGPRINT'] = "debug" @@ -21,7 +20,6 @@ def set_params_enabled(): params.put("HasAcceptedTerms", terms_version) params.put("CompletedTrainingVersion", training_version) params.put_bool("OpenpilotEnabledToggle", True) - params.put_bool("Passive", False) # valid calib msg = messaging.new_message('liveCalibration') diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 19f1c127a6..b95c75ddf7 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -725,7 +725,6 @@ def _replay_multi_process( def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=None) -> Dict[str, Any]: params_dict = { "OpenpilotEnabledToggle": True, - "Passive": False, "DisengageOnAccelerator": True, "DisableLogging": False, } diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 605cf51089..ab30b1579f 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -297,8 +297,7 @@ def thermald_thread(end_event, hw_queue) -> None: # with 2% left, we killall, otherwise the phone will take a long time to boot startup_conditions["free_space"] = msg.deviceState.freeSpacePercent > 2 - startup_conditions["completed_training"] = params.get("CompletedTrainingVersion") == training_version or \ - params.get_bool("Passive") + startup_conditions["completed_training"] = params.get("CompletedTrainingVersion") == training_version startup_conditions["not_driver_view"] = not params.get_bool("IsDriverViewEnabled") startup_conditions["not_taking_snapshot"] = not params.get_bool("IsTakingSnapshot") diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 03006e3b49..a3cba124fe 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -101,23 +101,17 @@ if GetOption('extras') and arch != "Darwin": senv['LINKFLAGS'].append('-Wl,-strip-debug') release = "release3" - dashcam = "dashcam3" installers = [ ("openpilot", release), ("openpilot_test", f"{release}-staging"), ("openpilot_nightly", "nightly"), ("openpilot_internal", "master"), - ("dashcam", dashcam), - ("dashcam_test", f"{dashcam}-staging"), ] - cont = {} - for brand in ("openpilot", "dashcam"): - cont[brand] = senv.Command(f"installer/continue_{brand}.o", f"installer/continue_{brand}.sh", - "ld -r -b binary -o $TARGET $SOURCE") + cont = senv.Command(f"installer/continue_openpilot.o", f"installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") for name, branch in installers: - brand = "dashcam" if "dashcam" in branch else "openpilot" - d = {'BRANCH': f"'\"{branch}\"'", 'BRAND': f"'\"{brand}\"'"} + d = {'BRANCH': f"'\"{branch}\"'"} if "internal" in name: d['INTERNAL'] = "1" @@ -126,7 +120,7 @@ if GetOption('extras') and arch != "Darwin": r.raise_for_status() d['SSH_KEYS'] = f'\\"{r.text.strip()}\\"' obj = senv.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = senv.Program(f"installer/installers/installer_{name}", [obj, cont[brand]], LIBS=qt_libs) + f = senv.Program(f"installer/installers/installer_{name}", [obj, cont], LIBS=qt_libs) # keep installers small assert f[0].get_size() < 350*1e3 diff --git a/selfdrive/ui/installer/continue_dashcam.sh b/selfdrive/ui/installer/continue_dashcam.sh deleted file mode 100755 index 25233fff11..0000000000 --- a/selfdrive/ui/installer/continue_dashcam.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/bash - -cd /data/openpilot -exec ./launch_chffrplus.sh diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 179ce60c63..d43ed37ae8 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -33,8 +33,8 @@ const QString CACHE_PATH = "/data/openpilot.cache"; #define INSTALL_PATH "/data/openpilot" #define TMP_INSTALL_PATH "/data/tmppilot" -extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_start"); -extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_end"); +extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start"); +extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end"); bool time_valid() { time_t rawtime; diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index c7c22f0ea3..d4fcee55ce 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -183,7 +183,7 @@ void DeclinePage::showEvent(QShowEvent *event) { void OnboardingWindow::updateActiveScreen() { if (!accepted_terms) { setCurrentIndex(0); - } else if (!training_done && !params.getBool("Passive")) { + } else if (!training_done) { setCurrentIndex(1); } else { emit onboardingDone(); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index eec9fede08..6d79aeb23d 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -215,15 +215,13 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { }); addItem(resetCalibBtn); - if (!params.getBool("Passive")) { - auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); - connect(retrainingBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { - emit reviewTrainingGuide(); - } - }); - addItem(retrainingBtn); - } + auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); + connect(retrainingBtn, &ButtonControl::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { + emit reviewTrainingGuide(); + } + }); + addItem(retrainingBtn); if (Hardware::TICI()) { auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), ""); diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 67f1c136d2..4f2e4f48cb 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -20,7 +20,7 @@ #include "selfdrive/ui/qt/widgets/input.h" const std::string USER_AGENT = "AGNOSSetup-"; -const QString DASHCAM_URL = "https://dashcam.comma.ai"; +const QString TEST_URL = "https://openpilot.comma.ai"; bool is_elf(char *fname) { FILE *fp = fopen(fname, "rb"); @@ -229,11 +229,11 @@ QWidget * Setup::network_setup() { } repaint(); }); - request->sendRequest(DASHCAM_URL); + request->sendRequest(TEST_URL); QTimer *timer = new QTimer(this); QObject::connect(timer, &QTimer::timeout, [=]() { if (!request->active() && cont->isVisible()) { - request->sendRequest(DASHCAM_URL); + request->sendRequest(TEST_URL); } }); timer->start(1000); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index c5222b865f..16d31ce304 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -25,7 +25,7 @@ QString getVersion() { } QString getBrand() { - return Params().getBool("Passive") ? QObject::tr("dashcam") : QObject::tr("openpilot"); + return QObject::tr("openpilot"); } QString getUserAgent() { diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index 2032807630..b7e35d7b6c 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -562,10 +562,6 @@ Exit إغلاق - - dashcam - dashcam - openpilot openpilot diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 782d65b06f..deab47e2cf 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -557,10 +557,6 @@ Exit Verlassen - - dashcam - dashcam - openpilot openpilot diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index f3aef3d43c..8cd5a4ea28 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -558,10 +558,6 @@ Exit Quitter - - dashcam - dashcam - openpilot openpilot diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index ba3ca8faf6..27cd7246d0 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -556,10 +556,6 @@ Exit 閉じる - - dashcam - ドライブレコーダー - openpilot openpilot diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index b2b76f3903..725e3c2f7b 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -557,10 +557,6 @@ Exit 종료 - - dashcam - 블랙박스 - openpilot openpilot diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index e89a3b1f5a..19e35b2c12 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -558,10 +558,6 @@ Exit Sair - - dashcam - dashcam - openpilot openpilot diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 0d0bda4abe..01d8a93ca1 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -557,10 +557,6 @@ Exit ปิด - - dashcam - กล้องติดรถยนต์ - openpilot openpilot diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index d14dd518a1..c4e661edfa 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -556,10 +556,6 @@ Exit Çık - - dashcam - araç yol kamerası - openpilot openpilot diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index ef0d78402e..68211b4b32 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -557,10 +557,6 @@ Exit 退出 - - dashcam - 行车记录仪 - openpilot openpilot diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 121bf58f1a..1bdd2d52ea 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -557,10 +557,6 @@ Exit 離開 - - dashcam - 行車記錄器 - openpilot openpilot diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 8a46a11a78..eb2759223b 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -342,7 +342,7 @@ class Updater: def check_for_update(self) -> None: cloudlog.info("checking for updates") - excluded_branches = ('release2', 'release2-staging', 'dashcam', 'dashcam-staging') + excluded_branches = ('release2', 'release2-staging') try: run(["git", "ls-remote", "origin", "HEAD"], OVERLAY_MERGED) diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index 3af2c50aa1..bc842f4a9a 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -43,7 +43,7 @@ kj::Array logger_build_init_data() { init.setGitCommit(params_map["GitCommit"]); init.setGitBranch(params_map["GitBranch"]); init.setGitRemote(params_map["GitRemote"]); - init.setPassive(params.getBool("Passive")); + init.setPassive(false); init.setDongleId(params_map["DongleId"]); auto lparams = init.initParams().initEntries(params_map.size()); diff --git a/system/version.py b/system/version.py index f9fd2bc847..6bcae5f3fa 100755 --- a/system/version.py +++ b/system/version.py @@ -7,7 +7,7 @@ from functools import lru_cache from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog -RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3', 'nightly'] +RELEASE_BRANCHES = ['release3-staging', 'release3', 'nightly'] TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] training_version: bytes = b"0.2.0" diff --git a/tools/webcam/README.md b/tools/webcam/README.md index bdf39b8145..709e8514c7 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -33,7 +33,7 @@ USE_WEBCAM=1 scons -j$(nproc) ## GO ``` cd ~/openpilot/selfdrive/manager -PASSIVE=0 NOSENSOR=1 USE_WEBCAM=1 ./manager.py +NOSENSOR=1 USE_WEBCAM=1 ./manager.py ``` - Start the car, then the UI should show the road webcam's view - Adjust and secure the webcams (you can run tools/webcam/front_mount_helper.py to help mount the driver camera) From 5c2b7882bf5cddac38b0a323c0d18a91e9d3bc04 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 19 Jan 2024 17:39:08 -0500 Subject: [PATCH 517/724] speedup replay test (#31086) * speedup replay test * better name * comment not required --- tools/replay/tests/test_replay.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index a01e0c2c5b..92510053ef 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -12,6 +12,8 @@ const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; const std::string TEST_RLOG_CHECKSUM = "5b966d4bb21a100a8c4e59195faeb741b975ccbe268211765efd1763d892bfb3"; +const int TEST_REPLAY_SEGMENTS = std::getenv("TEST_REPLAY_SEGMENTS") ? atoi(std::getenv("TEST_REPLAY_SEGMENTS")) : 1; + bool download_to_file(const std::string &url, const std::string &local_file, int chunk_size = 5 * 1024 * 1024, int retries = 3) { do { if (httpDownload(url, local_file, chunk_size)) { @@ -141,7 +143,7 @@ TEST_CASE("Local route") { Route route(DEMO_ROUTE, QString::fromStdString(data_dir)); REQUIRE(route.load()); REQUIRE(route.segments().size() == 2); - for (int i = 0; i < route.segments().size(); ++i) { + for (int i = 0; i < TEST_REPLAY_SEGMENTS; ++i) { read_segment(i, route.at(i), flags); } } @@ -151,7 +153,7 @@ TEST_CASE("Remote route") { Route route(DEMO_ROUTE); REQUIRE(route.load()); REQUIRE(route.segments().size() == 13); - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < TEST_REPLAY_SEGMENTS; ++i) { read_segment(i, route.at(i), flags); } } From dc9ffd2f100891554eaaad10139f233229390d56 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 19 Jan 2024 17:53:52 -0500 Subject: [PATCH 518/724] car tests on namespace (#31080) cars --- .github/workflows/selfdrive_tests.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index f58a861235..080051bdaa 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -307,7 +307,9 @@ jobs: test_cars: name: cars - runs-on: ubuntu-20.04 + runs-on: ${{ ((github.repository == 'commaai/openpilot') && + ((github.event_name != 'pull_request') || + (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-amd64-8x16' || 'ubuntu-20.04' }} strategy: fail-fast: false matrix: From 332a21965da4060d411fb65286cddc61b555ae48 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Jan 2024 15:24:34 -0800 Subject: [PATCH 519/724] jenkins: cleanup /dev/shm (#31095) --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index 1d724721d0..e45bcd16b2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -37,6 +37,7 @@ if [ -f /TICI ]; then rm -rf /tmp/tmp* rm -rf ~/.commacache + rm -rf /dev/shm/* if ! systemctl is-active --quiet systemd-resolved; then echo "restarting resolved" From e0d0896dcfbab482e10ef90794c746497b359321 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Jan 2024 17:20:21 -0800 Subject: [PATCH 520/724] test_power_draw: check msgs expect first for cleaner error --- system/hardware/tici/tests/test_power_draw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index c61d67e79d..337bbc9974 100755 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -91,8 +91,8 @@ class TestPowerDraw(unittest.TestCase): msgs_expected = int(sum(SAMPLE_TIME * SERVICE_LIST[msg].frequency for msg in proc.msgs)) tab.append([proc.name, round(expected, 2), round(cur, 2), msgs_expected, msgs_received]) with self.subTest(proc=proc.name): - np.testing.assert_allclose(cur, expected, rtol=proc.rtol, atol=proc.atol) np.testing.assert_allclose(msgs_expected, msgs_received, rtol=.02, atol=2) + np.testing.assert_allclose(cur, expected, rtol=proc.rtol, atol=proc.atol) print(tabulate(tab)) print(f"Baseline {baseline:.2f}W\n") From 2c86c023fe2d3072910ed33ff97cd6d3ddf2915d Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sun, 21 Jan 2024 12:22:32 -0500 Subject: [PATCH 521/724] sanitizer: sanitize vin number (#31090) --- selfdrive/car/tests/test_models.py | 3 ++- selfdrive/test/helpers.py | 9 --------- tools/lib/logreader.py | 5 +++-- tools/lib/sanitizer.py | 26 ++++++++++++++++++++++++++ 4 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 tools/lib/sanitizer.py diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index cce5979899..a66bd180b4 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -21,9 +21,10 @@ from openpilot.selfdrive.car.car_helpers import FRAME_FINGERPRINT, interfaces from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute from openpilot.selfdrive.controls.controlsd import Controls -from openpilot.selfdrive.test.helpers import read_segment_list, sanitize +from openpilot.selfdrive.test.helpers import read_segment_list from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.sanitizer import sanitize from openpilot.tools.lib.route import Route, SegmentName, RouteName from panda.tests.libpanda import libpanda_py diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index af6390d749..a8b7ca0c4d 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -8,7 +8,6 @@ from openpilot.common.params import Params from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.system.hardware import PC from openpilot.system.version import training_version, terms_version -from openpilot.tools.lib.logreader import LogIterable def set_params_enabled(): @@ -78,11 +77,3 @@ def read_segment_list(segment_list_path): seg_list = f.read().splitlines() return [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] - - -# Utilities for sanitizing routes of only essential data for testing car ports and doing validation. - -PRESERVE_SERVICES = ["can", "carParams", "pandaStates", "pandaStateDEPRECATED"] - -def sanitize(lr: LogIterable) -> LogIterable: - return filter(lambda msg: msg.which() in PRESERVE_SERVICES, lr) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 36d099de50..c7f9f9fae0 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -12,7 +12,7 @@ import sys import urllib.parse import warnings -from typing import Iterable, Iterator, List +from typing import Iterable, Iterator, List, Type from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log @@ -21,7 +21,8 @@ from openpilot.tools.lib.filereader import FileReader, file_exists from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.route import Route, SegmentRange -LogIterable = Iterable[capnp._DynamicStructReader] +LogMessage = Type[capnp._DynamicStructReader] +LogIterable = Iterable[LogMessage] class _LogFileReader: diff --git a/tools/lib/sanitizer.py b/tools/lib/sanitizer.py new file mode 100644 index 0000000000..b4a97a3792 --- /dev/null +++ b/tools/lib/sanitizer.py @@ -0,0 +1,26 @@ +# Utilities for sanitizing routes of only essential data for testing car ports and doing validation. + +from openpilot.tools.lib.logreader import LogIterable, LogMessage + + +def sanitize_vin(vin: str): + # (last 6 digits of vin are serial number https://en.wikipedia.org/wiki/Vehicle_identification_number) + VIN_SENSITIVE = 6 + return vin[:-VIN_SENSITIVE] + "X" * VIN_SENSITIVE + + +def sanitize_msg(msg: LogMessage) -> LogMessage: + if msg.which() == "carParams": + msg = msg.as_builder() + msg.carParams.carVin = sanitize_vin(msg.carParams.carVin) + msg = msg.as_reader() + return msg + + +PRESERVE_SERVICES = ["can", "carParams", "pandaStates", "pandaStateDEPRECATED"] + + +def sanitize(lr: LogIterable) -> LogIterable: + filtered = filter(lambda msg: msg.which() in PRESERVE_SERVICES, lr) + sanitized = map(sanitize_msg, filtered) + return sanitized From e6c97c384671b448f307a7ed91416886f2186d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sun, 21 Jan 2024 12:09:48 -0800 Subject: [PATCH 522/724] Delete lat planner (#31089) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit * Fixup * typo * ignore lateral plan * Update cereal * Remove lateralPlan * Fix release build * Fix release build * give car params * Add carParams to include_all_types * Write car param in powerdraw test * add demo mode * Update model regf * proc replay ref commit * Try * Move enum definition * Update cereal * typo * Write car param for modeld test * Update ref * Update model ref again --------- Co-authored-by: Kacper Rączy --- cereal | 2 +- release/files_common | 1 - selfdrive/car/car_helpers.py | 12 +++ selfdrive/car/interfaces.py | 1 - selfdrive/controls/controlsd.py | 37 +++++----- selfdrive/controls/lib/desire_helper.py | 34 ++++----- selfdrive/controls/lib/drive_helpers.py | 20 +++-- selfdrive/controls/lib/events.py | 8 -- selfdrive/controls/lib/lateral_planner.py | 74 ------------------- selfdrive/controls/plannerd.py | 28 ++----- selfdrive/debug/cycle_alerts.py | 2 +- selfdrive/locationd/torqued.py | 8 +- selfdrive/modeld/fill_model_msg.py | 14 +++- selfdrive/modeld/modeld | 2 +- selfdrive/modeld/modeld.py | 40 ++++++++-- selfdrive/modeld/tests/test_modeld.py | 4 +- selfdrive/test/process_replay/model_replay.py | 8 +- .../process_replay/model_replay_ref_commit | 2 +- .../test/process_replay/process_replay.py | 9 ++- selfdrive/test/process_replay/ref_commit | 2 +- selfdrive/test/test_onroad.py | 3 +- system/hardware/tici/tests/test_power_draw.py | 2 + tools/latencylogger/README.md | 2 - tools/latencylogger/latency_logger.py | 2 - tools/replay/ui.py | 2 +- tools/tuning/measure_steering_accuracy.py | 16 ++-- 26 files changed, 143 insertions(+), 192 deletions(-) delete mode 100644 selfdrive/controls/lib/lateral_planner.py diff --git a/cereal b/cereal index 20b65eeb1f..c2adb4f7cf 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 20b65eeb1f6c580cdd7d63e53639f4fc48bc2f56 +Subproject commit c2adb4f7cf30e53735ee43cc3a8a3698c5410819 diff --git a/release/files_common b/release/files_common index 71c096716b..4784b369e7 100644 --- a/release/files_common +++ b/release/files_common @@ -142,7 +142,6 @@ selfdrive/controls/lib/latcontrol_angle.py selfdrive/controls/lib/latcontrol_torque.py selfdrive/controls/lib/latcontrol_pid.py selfdrive/controls/lib/latcontrol.py -selfdrive/controls/lib/lateral_planner.py selfdrive/controls/lib/longcontrol.py selfdrive/controls/lib/longitudinal_planner.py selfdrive/controls/lib/pid.py diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index fa68313e86..c1a30094c4 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -207,3 +207,15 @@ def get_car(logcan, sendcan, experimental_long_allowed, num_pandas=1): CP.fuzzyFingerprint = not exact_match return CarInterface(CP, CarController, CarState), CP + +def write_car_param(fingerprint="mock"): + params = Params() + CarInterface, _, _ = interfaces[fingerprint] + CP = CarInterface.get_non_essential_params(fingerprint) + params.put("CarParams", CP.to_bytes()) + +def get_demo_car_params(): + fingerprint="mock" + CarInterface, _, _ = interfaces[fingerprint] + CP = CarInterface.get_non_essential_params(fingerprint) + return CP diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 44b198a421..59657c5437 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -125,7 +125,6 @@ class CarInterfaceBase(ABC): @staticmethod def get_steer_feedforward_default(desired_angle, v_ego): # Proportional to realigning tire momentum: lateral acceleration. - # TODO: something with lateralPlan.curvatureRates return desired_angle * (v_ego**2) def get_steer_feedforward_function(self): diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a25b23469f..2aae956090 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -17,8 +17,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_short_branch from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from openpilot.selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can -from openpilot.selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET -from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature +from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, clip_curvature from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED from openpilot.selfdrive.controls.lib.longcontrol import LongControl from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID @@ -32,6 +31,7 @@ from openpilot.system.hardware import HARDWARE SOFT_DISABLE_TIME = 3 # seconds LDW_MIN_SPEED = 31 * CV.MPH_TO_MS LANE_DEPARTURE_THRESHOLD = 0.1 +CAMERA_OFFSET = 0.04 REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ @@ -41,9 +41,9 @@ IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd"} ThermalStatus = log.DeviceState.ThermalStatus State = log.ControlsState.OpenpilotState PandaType = log.PandaState.PandaType -Desire = log.LateralPlan.Desire -LaneChangeState = log.LateralPlan.LaneChangeState -LaneChangeDirection = log.LateralPlan.LaneChangeDirection +Desire = log.Desire +LaneChangeState = log.LaneChangeState +LaneChangeDirection = log.LaneChangeDirection EventName = car.CarEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel @@ -77,7 +77,7 @@ class Controls: if SIMULATION: ignore += ['driverCameraState', 'managerState'] self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', - 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', + 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', 'testJoystick'] + self.camera_packets + self.sensor_packets, ignore_alive=ignore, ignore_avg_freq=['radarState', 'testJoystick'], ignore_valid=['testJoystick', ]) @@ -288,8 +288,8 @@ class Controls: self.events.add(EventName.calibrationInvalid) # Handle lane change - if self.sm['lateralPlan'].laneChangeState == LaneChangeState.preLaneChange: - direction = self.sm['lateralPlan'].laneChangeDirection + if self.sm['modelV2'].meta.laneChangeState == LaneChangeState.preLaneChange: + direction = self.sm['modelV2'].meta.laneChangeDirection if (CS.leftBlindspot and direction == LaneChangeDirection.left) or \ (CS.rightBlindspot and direction == LaneChangeDirection.right): self.events.add(EventName.laneChangeBlocked) @@ -298,7 +298,7 @@ class Controls: self.events.add(EventName.preLaneChangeLeft) else: self.events.add(EventName.preLaneChangeRight) - elif self.sm['lateralPlan'].laneChangeState in (LaneChangeState.laneChangeStarting, + elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting, LaneChangeState.laneChangeFinishing): self.events.add(EventName.laneChange) @@ -370,8 +370,6 @@ class Controls: self.logged_comm_issue = None if not (self.CP.notCar and self.joystick_mode): - if not self.sm['lateralPlan'].mpcSolutionValid: - self.events.add(EventName.plannerError) if not self.sm['liveLocationKalman'].posenetOK: self.events.add(EventName.posenetInvalid) if not self.sm['liveLocationKalman'].deviceStable: @@ -572,8 +570,8 @@ class Controls: self.LaC.update_live_torque_params(torque_params.latAccelFactorFiltered, torque_params.latAccelOffsetFiltered, torque_params.frictionCoefficientFiltered) - lat_plan = self.sm['lateralPlan'] long_plan = self.sm['longitudinalPlan'] + model_v2 = self.sm['modelV2'] CC = car.CarControl.new_message() CC.enabled = self.enabled @@ -588,9 +586,9 @@ class Controls: actuators.longControlState = self.LoC.long_control_state # Enable blinkers while lane changing - if self.sm['lateralPlan'].laneChangeState != LaneChangeState.off: - CC.leftBlinker = self.sm['lateralPlan'].laneChangeDirection == LaneChangeDirection.left - CC.rightBlinker = self.sm['lateralPlan'].laneChangeDirection == LaneChangeDirection.right + if model_v2.meta.laneChangeState != LaneChangeState.off: + CC.leftBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.left + CC.rightBlinker = model_v2.meta.laneChangeDirection == LaneChangeDirection.right if CS.leftBlinker or CS.rightBlinker: self.last_blinker_frame = self.sm.frame @@ -609,11 +607,11 @@ class Controls: actuators.accel = self.LoC.update(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan) # Steering PID loop and lateral MPC - self.desired_curvature = get_lag_adjusted_curvature(self.CP, CS.vEgo, lat_plan.psis, lat_plan.curvatures) + self.desired_curvature = clip_curvature(CS.vEgo, self.desired_curvature, model_v2.action.desiredCurvature) + actuators.curvature = self.desired_curvature actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, self.steer_limited, self.desired_curvature, self.sm['liveLocationKalman']) - actuators.curvature = self.desired_curvature else: lac_log = log.ControlsState.LateralDebugState.new_message() if self.sm.rcv_frame['testJoystick'] > 0: @@ -651,7 +649,8 @@ class Controls: if undershooting and turning and good_speed and max_torque: lac_log.active and self.events.add(EventName.steerSaturated) elif lac_log.saturated: - dpath_points = lat_plan.dPathPoints + # TODO probably should not use dpath_points but curvature + dpath_points = model_v2.position.y if len(dpath_points): # Check if we deviated from the path # TODO use desired vs actual curvature @@ -777,7 +776,7 @@ class Controls: controlsState.alertSound = current_alert.audible_alert controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan'] - controlsState.lateralPlanMonoTime = self.sm.logMonoTime['lateralPlan'] + controlsState.lateralPlanMonoTime = self.sm.logMonoTime['modelV2'] controlsState.enabled = self.enabled controlsState.active = self.active controlsState.curvature = curvature diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index d538035070..90b6858649 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -2,30 +2,30 @@ from cereal import log from openpilot.common.conversions import Conversions as CV from openpilot.common.realtime import DT_MDL -LaneChangeState = log.LateralPlan.LaneChangeState -LaneChangeDirection = log.LateralPlan.LaneChangeDirection +LaneChangeState = log.LaneChangeState +LaneChangeDirection = log.LaneChangeDirection LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS LANE_CHANGE_TIME_MAX = 10. DESIRES = { LaneChangeDirection.none: { - LaneChangeState.off: log.LateralPlan.Desire.none, - LaneChangeState.preLaneChange: log.LateralPlan.Desire.none, - LaneChangeState.laneChangeStarting: log.LateralPlan.Desire.none, - LaneChangeState.laneChangeFinishing: log.LateralPlan.Desire.none, + LaneChangeState.off: log.Desire.none, + LaneChangeState.preLaneChange: log.Desire.none, + LaneChangeState.laneChangeStarting: log.Desire.none, + LaneChangeState.laneChangeFinishing: log.Desire.none, }, LaneChangeDirection.left: { - LaneChangeState.off: log.LateralPlan.Desire.none, - LaneChangeState.preLaneChange: log.LateralPlan.Desire.none, - LaneChangeState.laneChangeStarting: log.LateralPlan.Desire.laneChangeLeft, - LaneChangeState.laneChangeFinishing: log.LateralPlan.Desire.laneChangeLeft, + LaneChangeState.off: log.Desire.none, + LaneChangeState.preLaneChange: log.Desire.none, + LaneChangeState.laneChangeStarting: log.Desire.laneChangeLeft, + LaneChangeState.laneChangeFinishing: log.Desire.laneChangeLeft, }, LaneChangeDirection.right: { - LaneChangeState.off: log.LateralPlan.Desire.none, - LaneChangeState.preLaneChange: log.LateralPlan.Desire.none, - LaneChangeState.laneChangeStarting: log.LateralPlan.Desire.laneChangeRight, - LaneChangeState.laneChangeFinishing: log.LateralPlan.Desire.laneChangeRight, + LaneChangeState.off: log.Desire.none, + LaneChangeState.preLaneChange: log.Desire.none, + LaneChangeState.laneChangeStarting: log.Desire.laneChangeRight, + LaneChangeState.laneChangeFinishing: log.Desire.laneChangeRight, }, } @@ -38,7 +38,7 @@ class DesireHelper: self.lane_change_ll_prob = 1.0 self.keep_pulse_timer = 0.0 self.prev_one_blinker = False - self.desire = log.LateralPlan.Desire.none + self.desire = log.Desire.none def update(self, carstate, lateral_active, lane_change_prob): v_ego = carstate.vEgo @@ -110,5 +110,5 @@ class DesireHelper: self.keep_pulse_timer += DT_MDL if self.keep_pulse_timer > 1.0: self.keep_pulse_timer = 0.0 - elif self.desire in (log.LateralPlan.Desire.keepLeft, log.LateralPlan.Desire.keepRight): - self.desire = log.LateralPlan.Desire.none + elif self.desire in (log.Desire.keepLeft, log.Desire.keepRight): + self.desire = log.Desire.none diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 9a5d1779fc..961b8069d4 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -3,7 +3,7 @@ import math from cereal import car, log from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import clip, interp -from openpilot.common.realtime import DT_MDL +from openpilot.common.realtime import DT_MDL, DT_CTRL from openpilot.selfdrive.modeld.constants import ModelConstants # WARNING: this value was determined based on the model's training distribution, @@ -163,21 +163,27 @@ def rate_limit(new_value, last_value, dw_step, up_step): return clip(new_value, last_value + dw_step, last_value + up_step) -def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures): +def clip_curvature(v_ego, prev_curvature, new_curvature): + v_ego = max(MIN_SPEED, v_ego) + max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 + safe_desired_curvature = clip(new_curvature, + prev_curvature - max_curvature_rate * DT_CTRL, + prev_curvature + max_curvature_rate * DT_CTRL) + return safe_desired_curvature + + +def get_lag_adjusted_curvature(steer_delay, v_ego, psis, curvatures): if len(psis) != CONTROL_N: psis = [0.0]*CONTROL_N curvatures = [0.0]*CONTROL_N v_ego = max(MIN_SPEED, v_ego) - # TODO this needs more thought, use .2s extra for now to estimate other delays - delay = CP.steerActuatorDelay + .2 - # MPC can plan to turn the wheel and turn back before t_delay. This means # in high delay cases some corrections never even get commanded. So just use # psi to calculate a simple linearization of desired curvature current_curvature_desired = curvatures[0] - psi = interp(delay, ModelConstants.T_IDXS[:CONTROL_N], psis) - average_curvature_desired = psi / (v_ego * delay) + psi = interp(steer_delay, ModelConstants.T_IDXS[:CONTROL_N], psis) + average_curvature_desired = psi / (v_ego * steer_delay) desired_curvature = 2 * average_curvature_desired - current_curvature_desired # This is the "desired rate of the setpoint" not an actual desired rate diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 79bead3ac8..7df41927cf 100755 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -912,14 +912,6 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Is Off"), }, - # For planning the trajectory Model Predictive Control (MPC) is used. This is - # an optimization algorithm that is not guaranteed to find a feasible solution. - # If no solution is found or the solution has a very high cost this alert is thrown. - EventName.plannerError: { - ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Planner Solution Error"), - ET.NO_ENTRY: NoEntryAlert("Planner Solution Error"), - }, - # When the relay in the harness box opens the CAN bus between the LKAS camera # and the rest of the car is separated. When messages from the LKAS camera # are received on the car side this usually means the relay hasn't opened correctly diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py deleted file mode 100644 index 2417eb3c68..0000000000 --- a/selfdrive/controls/lib/lateral_planner.py +++ /dev/null @@ -1,74 +0,0 @@ -import numpy as np -from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, MIN_SPEED, get_speed_error -from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper -import cereal.messaging as messaging -from cereal import log - -TRAJECTORY_SIZE = 33 -CAMERA_OFFSET = 0.04 - -class LateralPlanner: - def __init__(self, CP, debug=False): - self.DH = DesireHelper() - - # Vehicle model parameters used to calculate lateral movement of car - self.factor1 = CP.wheelbase - CP.centerToFront - self.factor2 = (CP.centerToFront * CP.mass) / (CP.wheelbase * CP.tireStiffnessRear) - self.last_cloudlog_t = 0 - self.solution_invalid_cnt = 0 - - self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) - self.velocity_xyz = np.zeros((TRAJECTORY_SIZE, 3)) - self.v_plan = np.zeros((TRAJECTORY_SIZE,)) - self.x_sol = np.zeros((TRAJECTORY_SIZE, 4), dtype=np.float32) - self.v_ego = MIN_SPEED - self.l_lane_change_prob = 0.0 - self.r_lane_change_prob = 0.0 - - self.debug_mode = debug - - def update(self, sm): - v_ego_car = sm['carState'].vEgo - - # Parse model predictions - md = sm['modelV2'] - if len(md.position.x) == TRAJECTORY_SIZE and len(md.velocity.x) == TRAJECTORY_SIZE and len(md.lateralPlannerSolution.x) == TRAJECTORY_SIZE: - self.path_xyz = np.column_stack([md.position.x, md.position.y, md.position.z]) - self.velocity_xyz = np.column_stack([md.velocity.x, md.velocity.y, md.velocity.z]) - car_speed = np.linalg.norm(self.velocity_xyz, axis=1) - get_speed_error(md, v_ego_car) - self.v_plan = np.clip(car_speed, MIN_SPEED, np.inf) - self.v_ego = self.v_plan[0] - self.x_sol = np.column_stack([md.lateralPlannerSolution.x, md.lateralPlannerSolution.y, md.lateralPlannerSolution.yaw, md.lateralPlannerSolution.yawRate]) - - # Lane change logic - desire_state = md.meta.desireState - if len(desire_state): - self.l_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeLeft] - self.r_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeRight] - lane_change_prob = self.l_lane_change_prob + self.r_lane_change_prob - self.DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob) - - def publish(self, sm, pm): - plan_send = messaging.new_message('lateralPlan') - plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'modelV2']) - - lateralPlan = plan_send.lateralPlan - lateralPlan.modelMonoTime = sm.logMonoTime['modelV2'] - lateralPlan.dPathPoints = self.path_xyz[:,1].tolist() - lateralPlan.psis = self.x_sol[0:CONTROL_N, 2].tolist() - - lateralPlan.curvatures = (self.x_sol[0:CONTROL_N, 3]/self.v_ego).tolist() - lateralPlan.curvatureRates = [float(0) for _ in range(CONTROL_N-1)] # TODO: unused - - lateralPlan.mpcSolutionValid = bool(1) - lateralPlan.solverExecutionTime = 0.0 - if self.debug_mode: - lateralPlan.solverState = log.LateralPlan.SolverState.new_message() - lateralPlan.solverState.x = self.x_sol.tolist() - - lateralPlan.desire = self.DH.desire - lateralPlan.useLaneLines = False - lateralPlan.laneChangeState = self.DH.lane_change_state - lateralPlan.laneChangeDirection = self.DH.lane_change_direction - - pm.send('lateralPlan', plan_send) diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 46d10ef2f5..5ab4894424 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -1,29 +1,19 @@ #!/usr/bin/env python3 -import os -import numpy as np from cereal import car from openpilot.common.params import Params from openpilot.common.realtime import Priority, config_realtime_process from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner -from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner import cereal.messaging as messaging -def cumtrapz(x, t): - return np.concatenate([[0], np.cumsum(((x[0:-1] + x[1:])/2) * np.diff(t))]) - -def publish_ui_plan(sm, pm, lateral_planner, longitudinal_planner): - plan_odo = cumtrapz(longitudinal_planner.v_desired_trajectory_full, ModelConstants.T_IDXS) - model_odo = cumtrapz(lateral_planner.v_plan, ModelConstants.T_IDXS) - +def publish_ui_plan(sm, pm, longitudinal_planner): ui_send = messaging.new_message('uiPlan') ui_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'modelV2']) uiPlan = ui_send.uiPlan uiPlan.frameId = sm['modelV2'].frameId - uiPlan.position.x = np.interp(plan_odo, model_odo, lateral_planner.x_sol[:,0]).tolist() - uiPlan.position.y = np.interp(plan_odo, model_odo, lateral_planner.x_sol[:,1]).tolist() - uiPlan.position.z = np.interp(plan_odo, model_odo, lateral_planner.path_xyz[:,2]).tolist() + uiPlan.position.x = list(sm['modelV2'].position.x) + uiPlan.position.y = list(sm['modelV2'].position.y) + uiPlan.position.z = list(sm['modelV2'].position.z) uiPlan.accel = longitudinal_planner.a_desired_trajectory_full.tolist() pm.send('uiPlan', ui_send) @@ -36,12 +26,8 @@ def plannerd_thread(): CP = msg cloudlog.info("plannerd got CarParams: %s", CP.carName) - debug_mode = bool(int(os.getenv("DEBUG", "0"))) - longitudinal_planner = LongitudinalPlanner(CP) - lateral_planner = LateralPlanner(CP, debug=debug_mode) - - pm = messaging.PubMaster(['longitudinalPlan', 'lateralPlan', 'uiPlan']) + pm = messaging.PubMaster(['longitudinalPlan', 'uiPlan']) sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2'], poll=['radarState', 'modelV2'], ignore_avg_freq=['radarState']) @@ -49,11 +35,9 @@ def plannerd_thread(): sm.update() if sm.updated['modelV2']: - lateral_planner.update(sm) - lateral_planner.publish(sm, pm) longitudinal_planner.update(sm) longitudinal_planner.publish(sm, pm) - publish_ui_plan(sm, pm, lateral_planner, longitudinal_planner) + publish_ui_plan(sm, pm, longitudinal_planner) def main(): plannerd_thread() diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index fcecb3d9d3..42561f70f0 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -54,7 +54,7 @@ def cycle_alerts(duration=200, is_metric=False): CS = car.CarState.new_message() CP = CarInterface.get_non_essential_params("HONDA CIVIC 2016") sm = messaging.SubMaster(['deviceState', 'pandaStates', 'roadCameraState', 'modelV2', 'liveCalibration', - 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', + 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman', 'managerState'] + cameras) pm = messaging.PubMaster(['controlsState', 'pandaStates', 'deviceState']) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 21a5ebca5f..f2c0248afa 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -214,7 +214,7 @@ class TorqueEstimator(ParameterEstimator): return msg -def main(): +def main(demo=False): config_realtime_process([0, 1, 2, 3], 5) pm = messaging.PubMaster(['liveTorqueParameters']) @@ -242,4 +242,8 @@ def main(): params.put_nonblocking("LiveTorqueParameters", msg.to_bytes()) if __name__ == "__main__": - main() + import argparse + parser = argparse.ArgumentParser(description='Process the --demo argument.') + parser.add_argument('--demo', action='store_true', help='A boolean for demo mode.') + args = parser.parse_args() + main(demo=args.demo) diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 7434e94287..4a47571be7 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -3,6 +3,7 @@ import capnp import numpy as np from typing import Dict from cereal import log +from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_lag_adjusted_curvature, MIN_SPEED from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') @@ -45,7 +46,7 @@ def fill_xyvat(builder, t, x, y, v, a, x_std=None, y_std=None, v_std=None, a_std def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, np.ndarray], publish_state: PublishState, vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float, timestamp_eof: int, timestamp_llk: int, model_execution_time: float, - nav_enabled: bool, valid: bool) -> None: + nav_enabled: bool, v_ego: float, steer_delay: float, valid: bool) -> None: frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0 msg.valid = valid @@ -72,9 +73,14 @@ def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) # lateral planning - solution = modelV2.lateralPlannerSolution - solution.x, solution.y, solution.yaw, solution.yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)] - solution.xStd, solution.yStd, solution.yawStd, solution.yawRateStd = [net_output_data['lat_planner_solution_stds'][0,:,i].tolist() for i in range(4)] + x, y, yaw, yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)] + x_sol = np.column_stack([x, y, yaw, yawRate]) + v_ego = max(MIN_SPEED, v_ego) + psis = x_sol[0:CONTROL_N, 2].tolist() + curvatures = (x_sol[0:CONTROL_N, 3]/v_ego).tolist() + + action = modelV2.action + action.desiredCurvature = get_lag_adjusted_curvature(steer_delay, v_ego, psis, curvatures) # times at X_IDXS according to model plan PLAN_T_IDXS = [np.nan] * ModelConstants.IDX_N diff --git a/selfdrive/modeld/modeld b/selfdrive/modeld/modeld index 14048ec9fd..e1cef4dcc3 100755 --- a/selfdrive/modeld/modeld +++ b/selfdrive/modeld/modeld @@ -7,4 +7,4 @@ if [ -f "$DIR/libthneed.so" ]; then export LD_PRELOAD="$DIR/libthneed.so" fi -exec "$DIR/modeld.py" +exec "$DIR/modeld.py" "$@" diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 9cc238530a..0b57228a7a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -4,6 +4,7 @@ import time import pickle import numpy as np import cereal.messaging as messaging +from cereal import car, log from pathlib import Path from typing import Dict, Optional from setproctitle import setproctitle @@ -17,6 +18,8 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive import sentry +from openpilot.selfdrive.car.car_helpers import get_demo_car_params +from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState @@ -93,7 +96,6 @@ class ModelState: self.inputs['traffic_convention'][:] = inputs['traffic_convention'] self.inputs['nav_features'][:] = inputs['nav_features'] self.inputs['nav_instructions'][:] = inputs['nav_instructions'] - # self.inputs['driving_style'][:] = inputs['driving_style'] # if getCLBuffer is not None, frame will be None self.model.setInputBuffer("input_imgs", self.frame.prepare(buf, transform.flatten(), self.model.getCLBuffer("input_imgs"))) @@ -113,7 +115,7 @@ class ModelState: return outputs -def main(): +def main(demo=False): sentry.set_tag("daemon", PROCESS_NAME) cloudlog.bind(daemon=PROCESS_NAME) setproctitle(PROCESS_NAME) @@ -148,7 +150,7 @@ def main(): # messaging pm = PubMaster(["modelV2", "cameraOdometry"]) - sm = SubMaster(["lateralPlan", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction"]) + sm = SubMaster(["carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction", "carControl"]) publish_state = PublishState() params = Params() @@ -162,13 +164,23 @@ def main(): model_transform_main = np.zeros((3, 3), dtype=np.float32) model_transform_extra = np.zeros((3, 3), dtype=np.float32) live_calib_seen = False - driving_style = np.array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], dtype=np.float32) nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) buf_main, buf_extra = None, None meta_main = FrameMeta() meta_extra = FrameMeta() + + if demo: + CP = get_demo_car_params() + with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg: + CP = msg + cloudlog.info("plannerd got CarParams: %s", CP.carName) + # TODO this needs more thought, use .2s extra for now to estimate other delays + steer_delay = CP.steerActuatorDelay + .2 + DH = DesireHelper() + + while True: # Keep receiving frames until we are at least 1 frame ahead of previous extra frame while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: @@ -205,7 +217,8 @@ def main(): # TODO: path planner timeout? sm.update(0) - desire = sm["lateralPlan"].desire.raw + desire = DH.desire + v_ego = sm["carState"].vEgo is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId if sm.updated["liveCalibration"]: @@ -261,7 +274,6 @@ def main(): inputs:Dict[str, np.ndarray] = { 'desire': vec_desire, 'traffic_convention': traffic_convention, - 'driving_style': driving_style, 'nav_features': nav_features, 'nav_instructions': nav_instructions} @@ -274,7 +286,15 @@ def main(): modelv2_send = messaging.new_message('modelV2') posenet_send = messaging.new_message('cameraOdometry') fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, - meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, live_calib_seen) + meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, v_ego, steer_delay, live_calib_seen) + + desire_state = modelv2_send.modelV2.meta.desireState + l_lane_change_prob = desire_state[log.Desire.laneChangeLeft] + r_lane_change_prob = desire_state[log.Desire.laneChangeRight] + lane_change_prob = l_lane_change_prob + r_lane_change_prob + DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob) + modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state + modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen) pm.send('modelV2', modelv2_send) @@ -285,7 +305,11 @@ def main(): if __name__ == "__main__": try: - main() + import argparse + parser = argparse.ArgumentParser() + parser.add_argument('--demo', action='store_true', help='A boolean for demo mode.') + args = parser.parse_args() + main(demo=args.demo) except KeyboardInterrupt: cloudlog.warning(f"child {PROCESS_NAME} got SIGINT") except Exception: diff --git a/selfdrive/modeld/tests/test_modeld.py b/selfdrive/modeld/tests/test_modeld.py index e010f6cfd6..257a9bc878 100755 --- a/selfdrive/modeld/tests/test_modeld.py +++ b/selfdrive/modeld/tests/test_modeld.py @@ -7,6 +7,7 @@ import cereal.messaging as messaging from cereal.visionipc import VisionIpcServer, VisionStreamType from openpilot.common.transformations.camera import tici_f_frame_size from openpilot.common.realtime import DT_MDL +from openpilot.selfdrive.car.car_helpers import write_car_param from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state @@ -22,9 +23,10 @@ class TestModeld(unittest.TestCase): self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *tici_f_frame_size) self.vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *tici_f_frame_size) self.vipc_server.start_listener() + write_car_param() self.sm = messaging.SubMaster(['modelV2', 'cameraOdometry']) - self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'liveCalibration', 'lateralPlan']) + self.pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'liveCalibration']) managed_processes['modeld'].start() self.pm.wait_for_readers_to_update("roadCameraState", 10) diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index a96dd18503..97b7c7c46c 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -105,11 +105,11 @@ def nav_model_replay(lr): def model_replay(lr, frs): # modeld is using frame pairs - modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"}, {"roadEncodeIdx", "wideRoadEncodeIdx"}) - dmodeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"driverCameraState"}, {"driverEncodeIdx"}) + modeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"roadCameraState", "wideRoadCameraState"}, {"roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}) + dmodeld_logs = trim_logs_to_max_frames(lr, MAX_FRAMES, {"driverCameraState"}, {"driverEncodeIdx", "carParams"}) if not SEND_EXTRA_INPUTS: - modeld_logs = [msg for msg in modeld_logs if msg.which() not in ["liveCalibration", "lateralPlan"]] - dmodeld_logs = [msg for msg in dmodeld_logs if msg.which() not in ["liveCalibration", "lateralPlan"]] + modeld_logs = [msg for msg in modeld_logs if msg.which() not in ["liveCalibration",]] + dmodeld_logs = [msg for msg in dmodeld_logs if msg.which() not in ["liveCalibration",]] # initial calibration cal_msg = next(msg for msg in lr if msg.which() == "liveCalibration").as_builder() cal_msg.logMonoTime = lr[0].logMonoTime diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 1be0aaf8c3..3f65f36e18 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -ad64b6f38c1362e9d184f3fc95299284eacb56d4 +0513d29764980f512710cc2ebd7c14f91ae0351d diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index b95c75ddf7..a6b2771668 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -461,7 +461,7 @@ CONFIGS = [ proc_name="controlsd", pubs=[ "can", "deviceState", "pandaStates", "peripheralState", "liveCalibration", "driverMonitoringState", - "longitudinalPlan", "lateralPlan", "liveLocationKalman", "liveParameters", "radarState", + "longitudinalPlan", "liveLocationKalman", "liveParameters", "radarState", "modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope" ], @@ -486,8 +486,8 @@ CONFIGS = [ ProcessConfig( proc_name="plannerd", pubs=["modelV2", "carControl", "carState", "controlsState", "radarState"], - subs=["lateralPlan", "longitudinalPlan", "uiPlan"], - ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime", "lateralPlan.solverExecutionTime"], + subs=["longitudinalPlan", "uiPlan"], + ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime"], init_callback=get_car_params_callback, should_recv_callback=FrequencyBasedRcvCallback("modelV2"), tolerance=NUMPY_TOLERANCE, @@ -545,7 +545,7 @@ CONFIGS = [ ), ProcessConfig( proc_name="modeld", - pubs=["lateralPlan", "roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState"], + pubs=["roadCameraState", "wideRoadCameraState", "liveCalibration", "driverMonitoringState"], subs=["modelV2", "cameraOdometry"], ignore=["logMonoTime", "modelV2.frameDropPerc", "modelV2.modelExecutionTime"], should_recv_callback=ModeldCameraSyncRcvCallback(), @@ -555,6 +555,7 @@ CONFIGS = [ main_pub_drained=False, vision_pubs=["roadCameraState", "wideRoadCameraState"], ignore_alive_pubs=["wideRoadCameraState"], + init_callback=get_car_params_callback, ), ProcessConfig( proc_name="dmonitoringmodeld", diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 9163bcae75..a8d0976b57 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -1b981ce7f817974d4a7a28b06f01f727a5a7ea7b \ No newline at end of file +0dffa4e5634108f41d140c74052c38059038abd0 diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index bc12234c9c..0161259356 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -82,7 +82,6 @@ TIMINGS = { "carState": [2.5, 0.35], "carControl": [2.5, 0.35], "controlsState": [2.5, 0.35], - "lateralPlan": [2.5, 0.5], "longitudinalPlan": [2.5, 0.5], "roadCameraState": [2.5, 0.35], "driverCameraState": [2.5, 0.35], @@ -344,7 +343,7 @@ class TestOnroad(unittest.TestCase): result += "----------------- MPC Timing ------------------\n" result += "------------------------------------------------\n" - cfgs = [("lateralPlan", 0.05, 0.05), ("longitudinalPlan", 0.05, 0.05)] + cfgs = [("longitudinalPlan", 0.05, 0.05),] for (s, instant_max, avg_max) in cfgs: ts = [getattr(m, s).solverExecutionTime for m in self.service_msgs[s]] self.assertLess(max(ts), instant_max, f"high '{s}' execution time: {max(ts)}") diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index 337bbc9974..eed2ce231b 100755 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -10,6 +10,7 @@ from typing import List import cereal.messaging as messaging from cereal.services import SERVICE_LIST +from openpilot.selfdrive.car.car_helpers import write_car_param from openpilot.system.hardware import HARDWARE from openpilot.system.hardware.tici.power_monitor import get_power from openpilot.selfdrive.manager.process_config import managed_processes @@ -51,6 +52,7 @@ class TestPowerDraw(unittest.TestCase): def setUp(self): HARDWARE.initialize_hardware() HARDWARE.set_power_save(False) + write_car_param() # wait a bit for power save to disable time.sleep(5) diff --git a/tools/latencylogger/README.md b/tools/latencylogger/README.md index c40ec1b9ed..a961f83619 100644 --- a/tools/latencylogger/README.md +++ b/tools/latencylogger/README.md @@ -53,9 +53,7 @@ Frame ID: 1202 modelV2.modelExecutionTime 23.62649142742157 modelV2.gpuExecutionTime 0.0 plannerd - lateralPlan published 66.915049 longitudinalPlan published 69.715999 - lateralPlan.solverExecutionTime 0.8170719956979156 longitudinalPlan.solverExecutionTime 0.5619999719783664 controlsd Data sampled 70.217763 diff --git a/tools/latencylogger/latency_logger.py b/tools/latencylogger/latency_logger.py index b561b65105..19c0a86bf4 100755 --- a/tools/latencylogger/latency_logger.py +++ b/tools/latencylogger/latency_logger.py @@ -13,13 +13,11 @@ from openpilot.tools.lib.logreader import LogReader DEMO_ROUTE = "9f583b1d93915c31|2022-05-18--10-49-51--0" SERVICES = ['camerad', 'modeld', 'plannerd', 'controlsd', 'boardd'] -# Retrieve controlsd frameId from lateralPlan, mismatch with longitudinalPlan will be ignored MONOTIME_KEYS = ['modelMonoTime', 'lateralPlanMonoTime'] MSGQ_TO_SERVICE = { 'roadCameraState': 'camerad', 'wideRoadCameraState': 'camerad', 'modelV2': 'modeld', - 'lateralPlan': 'plannerd', 'longitudinalPlan': 'plannerd', 'sendcan': 'controlsd', 'controlsState': 'controlsd' diff --git a/tools/replay/ui.py b/tools/replay/ui.py index e47aa0416e..7c95a75f8b 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -55,7 +55,7 @@ def ui_thread(addr): top_down_surface = pygame.surface.Surface((UP.lidar_x, UP.lidar_y), 0, 8) sm = messaging.SubMaster(['carState', 'longitudinalPlan', 'carControl', 'radarState', 'liveCalibration', 'controlsState', - 'liveTracks', 'modelV2', 'liveParameters', 'lateralPlan'], addr=addr) + 'liveTracks', 'modelV2', 'liveParameters'], addr=addr) img = np.zeros((480, 640, 3), dtype='uint8') imgff = None diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index 06523497a5..f804b328de 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -51,8 +51,8 @@ class SteeringAccuracyTool: standstill = sm['carState'].standstill steer_limited = abs(sm['carControl'].actuators.steer - sm['carControl'].actuatorsOutput.steer) > 1e-2 overriding = sm['carState'].steeringPressed - changing_lanes = sm['lateralPlan'].laneChangeState != 0 - d_path_points = sm['lateralPlan'].dPathPoints + changing_lanes = sm['modelV2'].meta.laneChangeState != 0 + model_points = sm['modelV2'].position.y # must be engaged, not at standstill, not overriding steering, and not changing lanes if active and not standstill and not overriding and not changing_lanes: self.cnt += 1 @@ -75,8 +75,8 @@ class SteeringAccuracyTool: self.speed_group_stats[group][angle_abs]["cnt"] += 1 self.speed_group_stats[group][angle_abs]["err"] += angle_error self.speed_group_stats[group][angle_abs]["steer"] += abs(steer) - if len(d_path_points): - self.speed_group_stats[group][angle_abs]["dpp"] += abs(d_path_points[0]) + if len(model_points): + self.speed_group_stats[group][angle_abs]["dpp"] += abs(model_points[0]) if steer_limited: self.speed_group_stats[group][angle_abs]["limited"] += 1 if control_state.saturated: @@ -138,10 +138,10 @@ if __name__ == "__main__": sm['carControl'] = msg.carControl elif msg.which() == 'controlsState': sm['controlsState'] = msg.controlsState - elif msg.which() == 'lateralPlan': - sm['lateralPlan'] = msg.lateralPlan + elif msg.which() == 'modelV2': + sm['modelV2'] = msg.modelV2 - if msg.which() == 'carControl' and 'carState' in sm and 'controlsState' in sm and 'lateralPlan' in sm: + if msg.which() == 'carControl' and 'carState' in sm and 'controlsState' in sm and 'modelV2' in sm: tool.update(sm) else: @@ -150,7 +150,7 @@ if __name__ == "__main__": messaging.context = messaging.Context() carControl = messaging.sub_sock('carControl', addr=args.addr, conflate=True) - sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'lateralPlan'], addr=args.addr) + sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'modelV2'], addr=args.addr) time.sleep(1) # Make sure all submaster data is available before going further print("waiting for messages...") From ec72cdca00840ecc653fb9c3f5813c66d73de2e4 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Sun, 21 Jan 2024 12:10:55 -0800 Subject: [PATCH 523/724] Bump cereal --- cereal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cereal b/cereal index c2adb4f7cf..fd1843960f 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit c2adb4f7cf30e53735ee43cc3a8a3698c5410819 +Subproject commit fd1843960f4ba55bb2857593857d05ca463ac377 From 78e20cff3906ee76c356e8bdef920397e60389b4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Jan 2024 14:49:57 -0800 Subject: [PATCH 524/724] cleanup tests that are now useless (#31098) * disable that onoe * e2e tests catch this * rm more useless checks --- common/SConscript | 2 +- common/tests/test_ratekeeper.cc | 23 ----------------------- selfdrive/navd/tests/test_map_renderer.py | 1 + system/proclogd/tests/test_proclog.cc | 14 +------------- 4 files changed, 3 insertions(+), 37 deletions(-) delete mode 100644 common/tests/test_ratekeeper.cc diff --git a/common/SConscript b/common/SConscript index 52b3adf1d3..829db6eeec 100644 --- a/common/SConscript +++ b/common/SConscript @@ -23,7 +23,7 @@ Export('_common', '_gpucommon') if GetOption('extras'): env.Program('tests/test_common', - ['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc', 'tests/test_ratekeeper.cc'], + ['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'], LIBS=[_common, 'json11', 'zmq', 'pthread']) # Cython bindings diff --git a/common/tests/test_ratekeeper.cc b/common/tests/test_ratekeeper.cc deleted file mode 100644 index 32c7dfe58c..0000000000 --- a/common/tests/test_ratekeeper.cc +++ /dev/null @@ -1,23 +0,0 @@ -#include "catch2/catch.hpp" -#include "common/ratekeeper.h" -#include "common/timing.h" -#include "common/util.h" - -TEST_CASE("RateKeeper") { - float freq = GENERATE(10, 50, 100); - RateKeeper rk("Test RateKeeper", freq); - - int lags = 0; - int bad_keep_times = 0; - for (int i = 0; i < freq; ++i) { - double begin = seconds_since_boot(); - util::sleep_for(util::random_int(0, 1000.0 / freq - 1)); - bool lagged = rk.keepTime(); - lags += lagged; - bad_keep_times += (seconds_since_boot() - begin - (1 / freq)) > 1e-3; - } - - // need a tolerance here due to scheduling - REQUIRE(lags < 5); - REQUIRE(bad_keep_times < 5); -} diff --git a/selfdrive/navd/tests/test_map_renderer.py b/selfdrive/navd/tests/test_map_renderer.py index d336ffb651..d056198bec 100755 --- a/selfdrive/navd/tests/test_map_renderer.py +++ b/selfdrive/navd/tests/test_map_renderer.py @@ -181,6 +181,7 @@ class TestMapRenderer(unittest.TestCase): self._run_test(False) @with_processes(["mapsd"]) + @pytest.mark.skip(reason="slow, flaky, and unlikely to break") def test_recover_from_no_internet(self): self._setup_test() self._run_test(True) diff --git a/system/proclogd/tests/test_proclog.cc b/system/proclogd/tests/test_proclog.cc index 613c326166..b86229a499 100644 --- a/system/proclogd/tests/test_proclog.cc +++ b/system/proclogd/tests/test_proclog.cc @@ -109,7 +109,7 @@ TEST_CASE("Parser::cmdline") { test_cmdline(std::string("a\0b\0c\0\0\0", 9), {"a", "b", "c"}); } -TEST_CASE("buildProcLogerMessage") { +TEST_CASE("buildProcLoggerMessage") { MessageBuilder msg; buildProcLogMessage(msg); @@ -137,18 +137,6 @@ TEST_CASE("buildProcLogerMessage") { REQUIRE(p.getState() == 'R'); REQUIRE_THAT(p.getExe().cStr(), Catch::Matchers::Contains("test_proclog")); REQUIRE_THAT(p.getCmdline()[0], Catch::Matchers::Contains("test_proclog")); - } else { - std::string cmd_path = "/proc/" + std::to_string(p.getPid()) + "/cmdline"; - if (util::file_exists(cmd_path)) { - std::ifstream stream(cmd_path); - auto cmdline = Parser::cmdline(stream); - REQUIRE(cmdline.size() == p.getCmdline().size()); - for (int i = 0; i < p.getCmdline().size(); ++i) { - // do not check the cmdline of pytest as it will change. - if (cmdline[i].find("[pytest") || std::string(p.getCmdline()[i]).find("[pytest")) continue; - REQUIRE(cmdline[i] == p.getCmdline()[i].cStr()); - } - } } } } From b1f457bdc819a868591749ced9dad3e836876e91 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Jan 2024 16:39:52 -0800 Subject: [PATCH 525/724] pytest: cleanup manager procs on teardown (#31101) --- conftest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conftest.py b/conftest.py index 6792bd0c3d..d8cbe25b0a 100644 --- a/conftest.py +++ b/conftest.py @@ -3,6 +3,7 @@ import pytest import random from openpilot.common.prefix import OpenpilotPrefix +from openpilot.selfdrive.manager import manager from openpilot.system.hardware import TICI @@ -41,6 +42,8 @@ def openpilot_function_fixture(): os.environ.clear() os.environ.update(starting_env) + # cleanup any started processes + manager.manager_cleanup() # If you use setUpClass, the environment variables won't be cleared properly, # so we need to hook both the function and class pytest fixtures From 74a2e3178fb16fe498a84147c14732e6fa3f7417 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Jan 2024 16:40:12 -0800 Subject: [PATCH 526/724] pre-commit: only check translations on update (#31102) --- .pre-commit-config.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4bae3294ab..a3c13c77c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -81,6 +81,7 @@ repos: entry: selfdrive/ui/tests/test_translations.py language: script pass_filenames: false + files: 'selfdrive/ui/translations/*' - repo: https://github.com/python-poetry/poetry rev: '1.7.0' hooks: @@ -92,10 +93,3 @@ repos: rev: 0.27.3 hooks: - id: check-github-workflows -# - repo: local -# hooks: -# - id: format-fingerprints -# name: format-fingerprints -# entry: selfdrive/debug/format_fingerprints.py -# language: system -# types: [python] From 139b0ae3b696c16056c065dec626c65ec8282622 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Jan 2024 16:55:47 -0800 Subject: [PATCH 527/724] fix test_startup (#31103) * fix test_startup * real fix --- selfdrive/controls/tests/test_startup.py | 143 +++++++++++------------ 1 file changed, 67 insertions(+), 76 deletions(-) mode change 100755 => 100644 selfdrive/controls/tests/test_startup.py diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py old mode 100755 new mode 100644 index 02c0f486fe..34d14fbb39 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 import os -import unittest from parameterized import parameterized from cereal import log, car @@ -36,94 +34,87 @@ CX5_FW_VERSIONS = [ (Ecu.transmission, 0x7e1, None, b'PYNC-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'), ] -class TestStartup(unittest.TestCase): - def tearDown(self): - managed_processes['controlsd'].stop() +@parameterized.expand([ + # TODO: test EventName.startup for release branches - @parameterized.expand([ - # TODO: test EventName.startup for release branches + # officially supported car + (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"), + (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"), - # officially supported car - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"), - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS, "toyota"), + # dashcamOnly car + (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"), + (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"), - # dashcamOnly car - (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"), - (EventName.startupNoControl, MAZDA.CX5, CX5_FW_VERSIONS, "mazda"), + # unrecognized car with no fw + (EventName.startupNoFw, None, None, ""), + (EventName.startupNoFw, None, None, ""), - # unrecognized car with no fw - (EventName.startupNoFw, None, None, ""), - (EventName.startupNoFw, None, None, ""), + # unrecognized car + (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"), + (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"), - # unrecognized car - (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"), - (EventName.startupNoCar, None, COROLLA_FW_VERSIONS[:1], "toyota"), + # fuzzy match + (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), + (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), +]) +def test_startup_alert(expected_event, car_model, fw_versions, brand): + controls_sock = messaging.sub_sock("controlsState") + pm = messaging.PubMaster(['can', 'pandaStates']) - # fuzzy match - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), - (EventName.startupMaster, TOYOTA.COROLLA, COROLLA_FW_VERSIONS_FUZZY, "toyota"), - ]) - def test_startup_alert(self, expected_event, car_model, fw_versions, brand): - controls_sock = messaging.sub_sock("controlsState") - pm = messaging.PubMaster(['can', 'pandaStates']) + params = Params() + params.put_bool("OpenpilotEnabledToggle", True) - params = Params() - params.put_bool("OpenpilotEnabledToggle", True) + # Build capnn version of FW array + if fw_versions is not None: + car_fw = [] + cp = car.CarParams.new_message() + for ecu, addr, subaddress, version in fw_versions: + f = car.CarParams.CarFw.new_message() + f.ecu = ecu + f.address = addr + f.fwVersion = version + f.brand = brand - # Build capnn version of FW array - if fw_versions is not None: - car_fw = [] - cp = car.CarParams.new_message() - for ecu, addr, subaddress, version in fw_versions: - f = car.CarParams.CarFw.new_message() - f.ecu = ecu - f.address = addr - f.fwVersion = version - f.brand = brand + if subaddress is not None: + f.subAddress = subaddress - if subaddress is not None: - f.subAddress = subaddress + car_fw.append(f) + cp.carVin = "1" * 17 + cp.carFw = car_fw + params.put("CarParamsCache", cp.to_bytes()) + else: + os.environ['SKIP_FW_QUERY'] = '1' - car_fw.append(f) - cp.carVin = "1" * 17 - cp.carFw = car_fw - params.put("CarParamsCache", cp.to_bytes()) - else: - os.environ['SKIP_FW_QUERY'] = '1' + managed_processes['controlsd'].start() - managed_processes['controlsd'].start() + assert pm.wait_for_readers_to_update('can', 5) + pm.send('can', can_list_to_can_capnp([[0, 0, b"", 0]])) - assert pm.wait_for_readers_to_update('can', 5) - pm.send('can', can_list_to_can_capnp([[0, 0, b"", 0]])) + assert pm.wait_for_readers_to_update('pandaStates', 5) + msg = messaging.new_message('pandaStates', 1) + msg.pandaStates[0].pandaType = log.PandaState.PandaType.uno + pm.send('pandaStates', msg) - msg = messaging.new_message('pandaStates', 1) - msg.pandaStates[0].pandaType = log.PandaState.PandaType.uno - pm.send('pandaStates', msg) + # fingerprint + if (car_model is None) or (fw_versions is not None): + finger = {addr: 1 for addr in range(1, 100)} + else: + finger = _FINGERPRINTS[car_model][0] - # fingerprint - if (car_model is None) or (fw_versions is not None): - finger = {addr: 1 for addr in range(1, 100)} - else: - finger = _FINGERPRINTS[car_model][0] + msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()] + for _ in range(1000): + # controlsd waits for boardd to echo back that it has changed the multiplexing mode + if not params.get_bool("ObdMultiplexingChanged"): + params.put_bool("ObdMultiplexingChanged", True) - msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()] - for _ in range(1000): - # controlsd waits for boardd to echo back that it has changed the multiplexing mode - if not params.get_bool("ObdMultiplexingChanged"): - params.put_bool("ObdMultiplexingChanged", True) + pm.send('can', can_list_to_can_capnp(msgs)) + assert pm.wait_for_readers_to_update('can', 5, dt=0.001), f"step: {_}" - pm.send('can', can_list_to_can_capnp(msgs)) - assert pm.wait_for_readers_to_update('can', 2, dt=0.001) - - ctrls = messaging.drain_sock(controls_sock) - if len(ctrls): - event_name = ctrls[0].controlsState.alertType.split("/")[0] - self.assertEqual(EVENT_NAME[expected_event], event_name, - f"expected {EVENT_NAME[expected_event]} for '{car_model}', got {event_name}") - break - else: - self.fail(f"failed to fingerprint {car_model}") - -if __name__ == "__main__": - unittest.main() + ctrls = messaging.drain_sock(controls_sock) + if len(ctrls): + event_name = ctrls[0].controlsState.alertType.split("/")[0] + assert EVENT_NAME[expected_event] == event_name, f"expected {EVENT_NAME[expected_event]} for '{car_model}', got {event_name}" + break + else: + raise Exception(f"failed to fingerprint {car_model}") From 599325718ebdecba993c3a93e4c416e47b72c343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sun, 21 Jan 2024 17:10:40 -0800 Subject: [PATCH 528/724] Refactor modeld (#31099) * First commit * Allow * Just camerastream helper --- selfdrive/modeld/modeld.py | 134 ++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 60 deletions(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 0b57228a7a..beef14b816 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -115,38 +115,84 @@ class ModelState: return outputs + + +class CameraStreams: + def __init__(self, cl_context: CLContext): + while True: + available_streams = VisionIpcClient.available_streams("camerad", block=False) + if available_streams: + self.use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams + self.main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams + break + time.sleep(.1) + + vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if self.main_wide_camera else VisionStreamType.VISION_STREAM_ROAD + self.vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) + self.vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + cloudlog.warning(f"vision stream set up, main_wide_camera: {self.main_wide_camera}, use_extra_client: {self.use_extra_client}") + + while not self.vipc_client_main.connect(False): + time.sleep(0.1) + while self.use_extra_client and not self.vipc_client_extra.connect(False): + time.sleep(0.1) + + cloudlog.warning(f"connected main cam with buffer size: {self.vipc_client_main.buffer_len} \ + ({self.vipc_client_main.width} x {self.vipc_client_main.height})") + if self.use_extra_client: + cloudlog.warning(f"connected extra cam with buffer size: {self.vipc_client_extra.buffer_len} \ + ({self.vipc_client_extra.width} x {self.vipc_client_extra.height})") + + def receive_frames(self): + buf_main, buf_extra = None, None + meta_main = FrameMeta() + meta_extra = FrameMeta() + # Keep receiving frames until we are at least 1 frame ahead of previous extra frame + while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: + buf_main = self.vipc_client_main.recv() + meta_main = FrameMeta(self.vipc_client_main) + if buf_main is None: + break + + if buf_main is None: + cloudlog.error("vipc_client_main no frame") + return None + + if self.use_extra_client: + # Keep receiving extra frames until frame id matches main camera + while True: + buf_extra = self.vipc_client_extra.recv() + meta_extra = FrameMeta(self.vipc_client_extra) + if buf_extra is None or meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: + break + + if buf_extra is None: + cloudlog.error("vipc_client_extra no frame") + return None + + if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000: + cloudlog.error("frames out of sync! main: {} ({:.5f}), extra: {} ({:.5f})".format( + meta_main.frame_id, meta_main.timestamp_sof / 1e9, + meta_extra.frame_id, meta_extra.timestamp_sof / 1e9)) + + else: + # Use single camera + buf_extra = buf_main + meta_extra = meta_main + return meta_main, meta_extra, buf_main, buf_extra + + def main(demo=False): sentry.set_tag("daemon", PROCESS_NAME) cloudlog.bind(daemon=PROCESS_NAME) setproctitle(PROCESS_NAME) config_realtime_process(7, 54) + cl_context = CLContext() model = ModelState(cl_context) cloudlog.warning("models loaded, modeld starting") - - # visionipc clients - while True: - available_streams = VisionIpcClient.available_streams("camerad", block=False) - if available_streams: - use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams - main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams - break - time.sleep(.1) - - vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) - cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") - - while not vipc_client_main.connect(False): - time.sleep(0.1) - while use_extra_client and not vipc_client_extra.connect(False): - time.sleep(0.1) - - cloudlog.warning(f"connected main cam with buffer size: {vipc_client_main.buffer_len} ({vipc_client_main.width} x {vipc_client_main.height})") - if use_extra_client: - cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})") + camera_streams = CameraStreams(cl_context) # messaging pm = PubMaster(["modelV2", "cameraOdometry"]) @@ -166,9 +212,6 @@ def main(demo=False): live_calib_seen = False nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) - buf_main, buf_extra = None, None - meta_main = FrameMeta() - meta_extra = FrameMeta() if demo: @@ -182,40 +225,11 @@ def main(demo=False): while True: - # Keep receiving frames until we are at least 1 frame ahead of previous extra frame - while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: - buf_main = vipc_client_main.recv() - meta_main = FrameMeta(vipc_client_main) - if buf_main is None: - break + frames = None + while frames is None: + frames = camera_streams.receive_frames() + meta_main, meta_extra, buf_main, buf_extra = frames - if buf_main is None: - cloudlog.error("vipc_client_main no frame") - continue - - if use_extra_client: - # Keep receiving extra frames until frame id matches main camera - while True: - buf_extra = vipc_client_extra.recv() - meta_extra = FrameMeta(vipc_client_extra) - if buf_extra is None or meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: - break - - if buf_extra is None: - cloudlog.error("vipc_client_extra no frame") - continue - - if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000: - cloudlog.error("frames out of sync! main: {} ({:.5f}), extra: {} ({:.5f})".format( - meta_main.frame_id, meta_main.timestamp_sof / 1e9, - meta_extra.frame_id, meta_extra.timestamp_sof / 1e9)) - - else: - # Use single camera - buf_extra = buf_main - meta_extra = meta_main - - # TODO: path planner timeout? sm.update(0) desire = DH.desire v_ego = sm["carState"].vEgo @@ -223,7 +237,7 @@ def main(demo=False): frame_id = sm["roadCameraState"].frameId if sm.updated["liveCalibration"]: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) - model_transform_main = get_warp_matrix(device_from_calib_euler, main_wide_camera, False).astype(np.float32) + model_transform_main = get_warp_matrix(device_from_calib_euler, camera_streams.main_wide_camera, False).astype(np.float32) model_transform_extra = get_warp_matrix(device_from_calib_euler, True, True).astype(np.float32) live_calib_seen = True From 7ebd841a0b227da03c7a68d2914e06e4e4364247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sun, 21 Jan 2024 17:10:49 -0800 Subject: [PATCH 529/724] Deprecate lat planner (#31097) * Deprecate lat planner * Bump cereal --- cereal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cereal b/cereal index fd1843960f..775e7f84da 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit fd1843960f4ba55bb2857593857d05ca463ac377 +Subproject commit 775e7f84da5eb283d604e94f9bf4582ffa741c22 From 3b01733900a531c7080f3b2b8d16d633cb18edb7 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Sun, 21 Jan 2024 17:29:32 -0800 Subject: [PATCH 530/724] Revert "Refactor modeld (#31099)" This reverts commit 599325718ebdecba993c3a93e4c416e47b72c343. --- selfdrive/modeld/modeld.py | 134 +++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 74 deletions(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index beef14b816..0b57228a7a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -115,84 +115,38 @@ class ModelState: return outputs - - -class CameraStreams: - def __init__(self, cl_context: CLContext): - while True: - available_streams = VisionIpcClient.available_streams("camerad", block=False) - if available_streams: - self.use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams - self.main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams - break - time.sleep(.1) - - vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if self.main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - self.vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - self.vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) - cloudlog.warning(f"vision stream set up, main_wide_camera: {self.main_wide_camera}, use_extra_client: {self.use_extra_client}") - - while not self.vipc_client_main.connect(False): - time.sleep(0.1) - while self.use_extra_client and not self.vipc_client_extra.connect(False): - time.sleep(0.1) - - cloudlog.warning(f"connected main cam with buffer size: {self.vipc_client_main.buffer_len} \ - ({self.vipc_client_main.width} x {self.vipc_client_main.height})") - if self.use_extra_client: - cloudlog.warning(f"connected extra cam with buffer size: {self.vipc_client_extra.buffer_len} \ - ({self.vipc_client_extra.width} x {self.vipc_client_extra.height})") - - def receive_frames(self): - buf_main, buf_extra = None, None - meta_main = FrameMeta() - meta_extra = FrameMeta() - # Keep receiving frames until we are at least 1 frame ahead of previous extra frame - while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: - buf_main = self.vipc_client_main.recv() - meta_main = FrameMeta(self.vipc_client_main) - if buf_main is None: - break - - if buf_main is None: - cloudlog.error("vipc_client_main no frame") - return None - - if self.use_extra_client: - # Keep receiving extra frames until frame id matches main camera - while True: - buf_extra = self.vipc_client_extra.recv() - meta_extra = FrameMeta(self.vipc_client_extra) - if buf_extra is None or meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: - break - - if buf_extra is None: - cloudlog.error("vipc_client_extra no frame") - return None - - if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000: - cloudlog.error("frames out of sync! main: {} ({:.5f}), extra: {} ({:.5f})".format( - meta_main.frame_id, meta_main.timestamp_sof / 1e9, - meta_extra.frame_id, meta_extra.timestamp_sof / 1e9)) - - else: - # Use single camera - buf_extra = buf_main - meta_extra = meta_main - return meta_main, meta_extra, buf_main, buf_extra - - def main(demo=False): sentry.set_tag("daemon", PROCESS_NAME) cloudlog.bind(daemon=PROCESS_NAME) setproctitle(PROCESS_NAME) config_realtime_process(7, 54) - cl_context = CLContext() model = ModelState(cl_context) cloudlog.warning("models loaded, modeld starting") - camera_streams = CameraStreams(cl_context) + + # visionipc clients + while True: + available_streams = VisionIpcClient.available_streams("camerad", block=False) + if available_streams: + use_extra_client = VisionStreamType.VISION_STREAM_WIDE_ROAD in available_streams and VisionStreamType.VISION_STREAM_ROAD in available_streams + main_wide_camera = VisionStreamType.VISION_STREAM_ROAD not in available_streams + break + time.sleep(.1) + + vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") + + while not vipc_client_main.connect(False): + time.sleep(0.1) + while use_extra_client and not vipc_client_extra.connect(False): + time.sleep(0.1) + + cloudlog.warning(f"connected main cam with buffer size: {vipc_client_main.buffer_len} ({vipc_client_main.width} x {vipc_client_main.height})") + if use_extra_client: + cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})") # messaging pm = PubMaster(["modelV2", "cameraOdometry"]) @@ -212,6 +166,9 @@ def main(demo=False): live_calib_seen = False nav_features = np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32) nav_instructions = np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32) + buf_main, buf_extra = None, None + meta_main = FrameMeta() + meta_extra = FrameMeta() if demo: @@ -225,11 +182,40 @@ def main(demo=False): while True: - frames = None - while frames is None: - frames = camera_streams.receive_frames() - meta_main, meta_extra, buf_main, buf_extra = frames + # Keep receiving frames until we are at least 1 frame ahead of previous extra frame + while meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: + buf_main = vipc_client_main.recv() + meta_main = FrameMeta(vipc_client_main) + if buf_main is None: + break + if buf_main is None: + cloudlog.error("vipc_client_main no frame") + continue + + if use_extra_client: + # Keep receiving extra frames until frame id matches main camera + while True: + buf_extra = vipc_client_extra.recv() + meta_extra = FrameMeta(vipc_client_extra) + if buf_extra is None or meta_main.timestamp_sof < meta_extra.timestamp_sof + 25000000: + break + + if buf_extra is None: + cloudlog.error("vipc_client_extra no frame") + continue + + if abs(meta_main.timestamp_sof - meta_extra.timestamp_sof) > 10000000: + cloudlog.error("frames out of sync! main: {} ({:.5f}), extra: {} ({:.5f})".format( + meta_main.frame_id, meta_main.timestamp_sof / 1e9, + meta_extra.frame_id, meta_extra.timestamp_sof / 1e9)) + + else: + # Use single camera + buf_extra = buf_main + meta_extra = meta_main + + # TODO: path planner timeout? sm.update(0) desire = DH.desire v_ego = sm["carState"].vEgo @@ -237,7 +223,7 @@ def main(demo=False): frame_id = sm["roadCameraState"].frameId if sm.updated["liveCalibration"]: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) - model_transform_main = get_warp_matrix(device_from_calib_euler, camera_streams.main_wide_camera, False).astype(np.float32) + model_transform_main = get_warp_matrix(device_from_calib_euler, main_wide_camera, False).astype(np.float32) model_transform_extra = get_warp_matrix(device_from_calib_euler, True, True).astype(np.float32) live_calib_seen = True From 81516216b154bbf8c713aa6b4b538d3a2e0ae0b5 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sun, 21 Jan 2024 17:34:26 -0800 Subject: [PATCH 531/724] Simulator: fix metadrive (#31104) fix metadrive --- tools/sim/bridge/metadrive/metadrive_process.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_process.py b/tools/sim/bridge/metadrive/metadrive_process.py index 8040d0d050..aa6ae57976 100644 --- a/tools/sim/bridge/metadrive/metadrive_process.py +++ b/tools/sim/bridge/metadrive/metadrive_process.py @@ -34,12 +34,13 @@ def apply_metadrive_patches(): EngineCore.add_image_sensor = add_image_sensor_patched # we aren't going to use the built-in observation stack, so disable it to save time - def observe_patched(self, vehicle): + def observe_patched(self, *args, **kwargs): return self.state ImageObservation.observe = observe_patched - def arrive_destination_patch(self, vehicle): + # disable destination, we want to loop forever + def arrive_destination_patch(self, *args, **kwargs): return False MetaDriveEnv._is_arrive_destination = arrive_destination_patch From 0067cf3eb1fce18a4d9c548a0c136027c87d43aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 22 Jan 2024 00:58:06 -0800 Subject: [PATCH 532/724] Los Angeles Model (#31037) * 1c888f5b-c213-4c1c-9eba-c587afd047fc/700 * Move to action * Updates * Add steer delay * Update curvature grep * clip speed * No car params for now * Add delay back * Update * fix lint * fix lint * update model regf --- selfdrive/controls/lib/drive_helpers.py | 26 +------------------ selfdrive/modeld/constants.py | 5 ++-- selfdrive/modeld/fill_model_msg.py | 9 +------ selfdrive/modeld/modeld.py | 17 ++++++++---- selfdrive/modeld/models/navmodel.onnx | 4 +-- selfdrive/modeld/models/navmodel_q.dlc | 2 +- selfdrive/modeld/models/supercombo.onnx | 4 +-- selfdrive/modeld/parse_model_outputs.py | 2 +- .../process_replay/model_replay_ref_commit | 2 +- 9 files changed, 24 insertions(+), 47 deletions(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index 961b8069d4..6a5b22f686 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -3,8 +3,7 @@ import math from cereal import car, log from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import clip, interp -from openpilot.common.realtime import DT_MDL, DT_CTRL -from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.common.realtime import DT_CTRL # WARNING: this value was determined based on the model's training distribution, # model predictions above this speed can be unpredictable @@ -22,7 +21,6 @@ CAR_ROTATION_RADIUS = 0.0 # EU guidelines MAX_LATERAL_JERK = 5.0 - MAX_VEL_ERR = 5.0 ButtonEvent = car.CarState.ButtonEvent @@ -169,28 +167,6 @@ def clip_curvature(v_ego, prev_curvature, new_curvature): safe_desired_curvature = clip(new_curvature, prev_curvature - max_curvature_rate * DT_CTRL, prev_curvature + max_curvature_rate * DT_CTRL) - return safe_desired_curvature - - -def get_lag_adjusted_curvature(steer_delay, v_ego, psis, curvatures): - if len(psis) != CONTROL_N: - psis = [0.0]*CONTROL_N - curvatures = [0.0]*CONTROL_N - v_ego = max(MIN_SPEED, v_ego) - - # MPC can plan to turn the wheel and turn back before t_delay. This means - # in high delay cases some corrections never even get commanded. So just use - # psi to calculate a simple linearization of desired curvature - current_curvature_desired = curvatures[0] - psi = interp(steer_delay, ModelConstants.T_IDXS[:CONTROL_N], psis) - average_curvature_desired = psi / (v_ego * steer_delay) - desired_curvature = 2 * average_curvature_desired - current_curvature_desired - - # This is the "desired rate of the setpoint" not an actual desired rate - max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 - safe_desired_curvature = clip(desired_curvature, - current_curvature_desired - max_curvature_rate * DT_MDL, - current_curvature_desired + max_curvature_rate * DT_MDL) return safe_desired_curvature diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py index 4d3af51635..e513922c72 100644 --- a/selfdrive/modeld/constants.py +++ b/selfdrive/modeld/constants.py @@ -21,7 +21,8 @@ class ModelConstants: NAV_FEATURE_LEN = 256 NAV_INSTRUCTION_LEN = 150 DRIVING_STYLE_LEN = 12 - LAT_PLANNER_STATE_LEN = 4 + LATERAL_CONTROL_PARAMS_LEN = 2 + PREV_DESIRED_CURVS_LEN = 20 # model outputs constants FCW_THRESHOLDS_5MS2 = np.array([.05, .05, .15, .15, .15], dtype=np.float32) @@ -38,7 +39,7 @@ class ModelConstants: ROAD_EDGES_WIDTH = 2 PLAN_WIDTH = 15 DESIRE_PRED_WIDTH = 8 - LAT_PLANNER_SOLUTION_WIDTH = 4 + DESIRED_CURV_WIDTH = 1 NUM_LANE_LINES = 4 NUM_ROAD_EDGES = 2 diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 4a47571be7..93d1c7e77b 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -3,7 +3,6 @@ import capnp import numpy as np from typing import Dict from cereal import log -from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_lag_adjusted_curvature, MIN_SPEED from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') @@ -73,14 +72,8 @@ def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, fill_xyzt(orientation_rate, ModelConstants.T_IDXS, *net_output_data['plan'][0,:,Plan.ORIENTATION_RATE].T) # lateral planning - x, y, yaw, yawRate = [net_output_data['lat_planner_solution'][0,:,i].tolist() for i in range(4)] - x_sol = np.column_stack([x, y, yaw, yawRate]) - v_ego = max(MIN_SPEED, v_ego) - psis = x_sol[0:CONTROL_N, 2].tolist() - curvatures = (x_sol[0:CONTROL_N, 3]/v_ego).tolist() - action = modelV2.action - action.desiredCurvature = get_lag_adjusted_curvature(steer_delay, v_ego, psis, curvatures) + action.desiredCurvature = float(net_output_data['desired_curvature'][0,0]) # times at X_IDXS according to model plan PLAN_T_IDXS = [np.nan] * ModelConstants.IDX_N diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 0b57228a7a..00c46c3bce 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -12,8 +12,6 @@ from cereal.messaging import PubMaster, SubMaster from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params -from openpilot.common.realtime import DT_MDL -from openpilot.common.numpy_fast import interp from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import get_warp_matrix @@ -59,7 +57,8 @@ class ModelState: self.inputs = { 'desire': np.zeros(ModelConstants.DESIRE_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), 'traffic_convention': np.zeros(ModelConstants.TRAFFIC_CONVENTION_LEN, dtype=np.float32), - 'lat_planner_state': np.zeros(ModelConstants.LAT_PLANNER_STATE_LEN, dtype=np.float32), + 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), + 'prev_desired_curvs': np.zeros(ModelConstants.PREV_DESIRED_CURVS_LEN, dtype=np.float32), 'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32), 'features_buffer': np.zeros(ModelConstants.HISTORY_BUFFER_LEN * ModelConstants.FEATURE_LEN, dtype=np.float32), @@ -94,6 +93,7 @@ class ModelState: self.prev_desire[:] = inputs['desire'] self.inputs['traffic_convention'][:] = inputs['traffic_convention'] + self.inputs['lateral_control_params'][:] = inputs['lateral_control_params'] self.inputs['nav_features'][:] = inputs['nav_features'] self.inputs['nav_instructions'][:] = inputs['nav_instructions'] @@ -110,8 +110,8 @@ class ModelState: self.inputs['features_buffer'][:-ModelConstants.FEATURE_LEN] = self.inputs['features_buffer'][ModelConstants.FEATURE_LEN:] self.inputs['features_buffer'][-ModelConstants.FEATURE_LEN:] = outputs['hidden_state'][0, :] - self.inputs['lat_planner_state'][2] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 2]) - self.inputs['lat_planner_state'][3] = interp(DT_MDL, ModelConstants.T_IDXS, outputs['lat_planner_solution'][0, :, 3]) + self.inputs['prev_desired_curvs'][:-1] = self.inputs['prev_desired_curvs'][1:] + self.inputs['prev_desired_curvs'][-1] = outputs['desired_curvature'][0, 0] return outputs @@ -152,8 +152,12 @@ def main(demo=False): pm = PubMaster(["modelV2", "cameraOdometry"]) sm = SubMaster(["carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction", "carControl"]) + publish_state = PublishState() params = Params() + with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg: + steer_delay = msg.steerActuatorDelay + .2 + #steer_delay = 0.4 # setup filter to track dropped frames frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ) @@ -221,6 +225,8 @@ def main(demo=False): v_ego = sm["carState"].vEgo is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId + # TODO add lag + lateral_control_params = np.array([sm["carState"].vEgo, steer_delay], dtype=np.float32) if sm.updated["liveCalibration"]: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) model_transform_main = get_warp_matrix(device_from_calib_euler, main_wide_camera, False).astype(np.float32) @@ -274,6 +280,7 @@ def main(demo=False): inputs:Dict[str, np.ndarray] = { 'desire': vec_desire, 'traffic_convention': traffic_convention, + 'lateral_control_params': lateral_control_params, 'nav_features': nav_features, 'nav_instructions': nav_instructions} diff --git a/selfdrive/modeld/models/navmodel.onnx b/selfdrive/modeld/models/navmodel.onnx index 93edb593cc..3b687b960a 100644 --- a/selfdrive/modeld/models/navmodel.onnx +++ b/selfdrive/modeld/models/navmodel.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4971931accb5ba2e534bb3e0c591826ee507e2988df2eccf1fe862c303ddf9c5 -size 14221074 +oid sha256:8254b569878b7472e3f63ed9f3527a87bde785c9037aee3ed66f972e072b5899 +size 14166696 diff --git a/selfdrive/modeld/models/navmodel_q.dlc b/selfdrive/modeld/models/navmodel_q.dlc index cb6e55b75f..d5e43abcfb 100644 --- a/selfdrive/modeld/models/navmodel_q.dlc +++ b/selfdrive/modeld/models/navmodel_q.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fa346ada6f8c6326a5ee5fcd27e45e3e710049358079413c6a4624b20c6e1e47 +oid sha256:89fda8380efa3e421fbcdb6bb204c36a4991f137ee01d47f3d0380895aa7c036 size 3630942 diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index ca8642093d..e91940c728 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae44fe832fe48b89998f09cebb1bcd129864a8f51497b636cd38e66e46d69a89 -size 48457850 +oid sha256:4b085c1ba231bc381f78462bda136172787371d5d83b6e1bcd340aad17290ebc +size 48197170 diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 9d37a5fad4..07d5e0a921 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -93,7 +93,7 @@ class Parser: self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) - self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH)) + self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,)) for k in ['lead_prob', 'lane_lines_prob', 'meta']: self.parse_binary_crossentropy(k, outs) self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 3f65f36e18..85bcf123e7 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -0513d29764980f512710cc2ebd7c14f91ae0351d +cfdad3a695e3562ca32accce043b358291f0eef2 From eb04935b95e424b0cad526c0067542401e64c892 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 22 Jan 2024 09:15:22 -0800 Subject: [PATCH 533/724] Update Python packages and pre-commit hooks (#31108) Co-authored-by: jnewb1 --- .pre-commit-config.yaml | 2 +- poetry.lock | 430 +++++++++++++++++++--------------------- 2 files changed, 207 insertions(+), 225 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3c13c77c5..fa05b5b8a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: - --explicit-package-bases exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)' - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.13 + rev: v0.1.14 hooks: - id: ruff exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' diff --git a/poetry.lock b/poetry.lock index 41b677e5e0..b7e7f6deaf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -180,26 +180,6 @@ files = [ {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, ] -[[package]] -name = "anyio" -version = "4.2.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - [[package]] name = "attrs" version = "23.2.0" @@ -275,17 +255,16 @@ files = [ [[package]] name = "azure-core" -version = "1.29.6" +version = "1.29.7" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "azure-core-1.29.6.tar.gz", hash = "sha256:13b485252ecd9384ae624894fe51cfa6220966207264c360beada239f88b738a"}, - {file = "azure_core-1.29.6-py3-none-any.whl", hash = "sha256:604a005bce6a49ba661bb7b2be84a9b169047e52fcfcd0a4e4770affab4178f7"}, + {file = "azure-core-1.29.7.tar.gz", hash = "sha256:2944faf1a7ff1558b1f457cabf60f279869cabaeef86b353bed8eb032c7d8c5e"}, + {file = "azure_core-1.29.7-py3-none-any.whl", hash = "sha256:95a7b41b4af102e5fcdfac9500fcc82ff86e936c7145a099b7848b9ac0501250"}, ] [package.dependencies] -anyio = ">=3.0,<5.0" requests = ">=2.21.0" six = ">=1.11.0" typing-extensions = ">=4.6.0" @@ -1001,22 +980,23 @@ files = [ [[package]] name = "dnspython" -version = "2.4.2" +version = "2.5.0" description = "DNS toolkit" optional = false -python-versions = ">=3.8,<4.0" +python-versions = ">=3.8" files = [ - {file = "dnspython-2.4.2-py3-none-any.whl", hash = "sha256:57c6fbaaeaaf39c891292012060beb141791735dbb4004798328fc2c467402d8"}, - {file = "dnspython-2.4.2.tar.gz", hash = "sha256:8dcfae8c7460a2f84b4072e26f1c9f4101ca20c071649cb7c34e8b6a93d58984"}, + {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"}, + {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"}, ] [package.extras] -dnssec = ["cryptography (>=2.6,<42.0)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.24.1)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"] doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1,<4.0)"] -trio = ["trio (>=0.14,<0.23)"] -wmi = ["wmi (>=1.5.1,<2.0.0)"] +idna = ["idna (>=2.1)"] +trio = ["trio (>=0.14)"] +wmi = ["wmi (>=1.5.1)"] [[package]] name = "docutils" @@ -2047,61 +2027,71 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, + {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, + {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, + {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, + {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, + {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, + {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, + {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, ] [[package]] @@ -2184,7 +2174,7 @@ files = [ [[package]] name = "metadrive-simulator" -version = "0.4.1.2" +version = "0.4.2.2" description = "An open-ended driving simulator with infinite scenes" optional = false python-versions = ">=3.6, <3.12" @@ -2224,7 +2214,7 @@ ros = ["zmq"] type = "git" url = "https://github.com/metadriverse/metadrive.git" reference = "main" -resolved_reference = "9276a2cc3a149614d528e7e2a36c9d7a1307fb50" +resolved_reference = "bc162e1b423bd194a58ef2269103f1bcea5f53c7" [[package]] name = "mpld3" @@ -2652,14 +2642,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "opencv-python-headless" @@ -2678,14 +2661,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "packaging" @@ -2818,67 +2794,71 @@ test = ["pylint (>=3.0.0,<3.1.0)", "pytest", "pytest-pylint"] [[package]] name = "pandas" -version = "2.1.4" +version = "2.2.0" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdec823dc6ec53f7a6339a0e34c68b144a7a1fd28d80c260534c39c62c5bf8c9"}, - {file = "pandas-2.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:294d96cfaf28d688f30c918a765ea2ae2e0e71d3536754f4b6de0ea4a496d034"}, - {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b728fb8deba8905b319f96447a27033969f3ea1fea09d07d296c9030ab2ed1d"}, - {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00028e6737c594feac3c2df15636d73ace46b8314d236100b57ed7e4b9ebe8d9"}, - {file = "pandas-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:426dc0f1b187523c4db06f96fb5c8d1a845e259c99bda74f7de97bd8a3bb3139"}, - {file = "pandas-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:f237e6ca6421265643608813ce9793610ad09b40154a3344a088159590469e46"}, - {file = "pandas-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b7d852d16c270e4331f6f59b3e9aa23f935f5c4b0ed2d0bc77637a8890a5d092"}, - {file = "pandas-2.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7d5f2f54f78164b3d7a40f33bf79a74cdee72c31affec86bfcabe7e0789821"}, - {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa6e92e639da0d6e2017d9ccff563222f4eb31e4b2c3cf32a2a392fc3103c0d"}, - {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d797591b6846b9db79e65dc2d0d48e61f7db8d10b2a9480b4e3faaddc421a171"}, - {file = "pandas-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2d3e7b00f703aea3945995ee63375c61b2e6aa5aa7871c5d622870e5e137623"}, - {file = "pandas-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc9bf7ade01143cddc0074aa6995edd05323974e6e40d9dbde081021ded8510e"}, - {file = "pandas-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:482d5076e1791777e1571f2e2d789e940dedd927325cc3cb6d0800c6304082f6"}, - {file = "pandas-2.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a706cfe7955c4ca59af8c7a0517370eafbd98593155b48f10f9811da440248b"}, - {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0513a132a15977b4a5b89aabd304647919bc2169eac4c8536afb29c07c23540"}, - {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9f17f2b6fc076b2a0078862547595d66244db0f41bf79fc5f64a5c4d635bead"}, - {file = "pandas-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:45d63d2a9b1b37fa6c84a68ba2422dc9ed018bdaa668c7f47566a01188ceeec1"}, - {file = "pandas-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f69b0c9bb174a2342818d3e2778584e18c740d56857fc5cdb944ec8bbe4082cf"}, - {file = "pandas-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f06bda01a143020bad20f7a85dd5f4a1600112145f126bc9e3e42077c24ef34"}, - {file = "pandas-2.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab5796839eb1fd62a39eec2916d3e979ec3130509930fea17fe6f81e18108f6a"}, - {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbaf9e8d3a63a9276d707b4d25930a262341bca9874fcb22eff5e3da5394732"}, - {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ebfd771110b50055712b3b711b51bee5d50135429364d0498e1213a7adc2be8"}, - {file = "pandas-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ea107e0be2aba1da619cc6ba3f999b2bfc9669a83554b1904ce3dd9507f0860"}, - {file = "pandas-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:d65148b14788b3758daf57bf42725caa536575da2b64df9964c563b015230984"}, - {file = "pandas-2.1.4.tar.gz", hash = "sha256:fcb68203c833cc735321512e13861358079a96c174a61f5116a1de89c58c0ef7"}, + {file = "pandas-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8108ee1712bb4fa2c16981fba7e68b3f6ea330277f5ca34fa8d557e986a11670"}, + {file = "pandas-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:736da9ad4033aeab51d067fc3bd69a0ba36f5a60f66a527b3d72e2030e63280a"}, + {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e0b4fc3ddceb56ec8a287313bc22abe17ab0eb184069f08fc6a9352a769b18"}, + {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20404d2adefe92aed3b38da41d0847a143a09be982a31b85bc7dd565bdba0f4e"}, + {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ea3ee3f125032bfcade3a4cf85131ed064b4f8dd23e5ce6fa16473e48ebcaf5"}, + {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9670b3ac00a387620489dfc1bca66db47a787f4e55911f1293063a78b108df1"}, + {file = "pandas-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a946f210383c7e6d16312d30b238fd508d80d927014f3b33fb5b15c2f895430"}, + {file = "pandas-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a1b438fa26b208005c997e78672f1aa8138f67002e833312e6230f3e57fa87d5"}, + {file = "pandas-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ce2fbc8d9bf303ce54a476116165220a1fedf15985b09656b4b4275300e920b"}, + {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2707514a7bec41a4ab81f2ccce8b382961a29fbe9492eab1305bb075b2b1ff4f"}, + {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85793cbdc2d5bc32620dc8ffa715423f0c680dacacf55056ba13454a5be5de88"}, + {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfd6c2491dc821b10c716ad6776e7ab311f7df5d16038d0b7458bc0b67dc10f3"}, + {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a146b9dcacc3123aa2b399df1a284de5f46287a4ab4fbfc237eac98a92ebcb71"}, + {file = "pandas-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbc1b53c0e1fdf16388c33c3cca160f798d38aea2978004dd3f4d3dec56454c9"}, + {file = "pandas-2.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a41d06f308a024981dcaa6c41f2f2be46a6b186b902c94c2674e8cb5c42985bc"}, + {file = "pandas-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:159205c99d7a5ce89ecfc37cb08ed179de7783737cea403b295b5eda8e9c56d1"}, + {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1e1f3861ea9132b32f2133788f3b14911b68102d562715d71bd0013bc45440"}, + {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:761cb99b42a69005dec2b08854fb1d4888fdf7b05db23a8c5a099e4b886a2106"}, + {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a20628faaf444da122b2a64b1e5360cde100ee6283ae8effa0d8745153809a2e"}, + {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f5be5d03ea2073627e7111f61b9f1f0d9625dc3c4d8dda72cc827b0c58a1d042"}, + {file = "pandas-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:a626795722d893ed6aacb64d2401d017ddc8a2341b49e0384ab9bf7112bdec30"}, + {file = "pandas-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9f66419d4a41132eb7e9a73dcec9486cf5019f52d90dd35547af11bc58f8637d"}, + {file = "pandas-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57abcaeda83fb80d447f28ab0cc7b32b13978f6f733875ebd1ed14f8fbc0f4ab"}, + {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60f1f7dba3c2d5ca159e18c46a34e7ca7247a73b5dd1a22b6d59707ed6b899a"}, + {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb61dc8567b798b969bcc1fc964788f5a68214d333cade8319c7ab33e2b5d88a"}, + {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52826b5f4ed658fa2b729264d63f6732b8b29949c7fd234510d57c61dbeadfcd"}, + {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bde2bc699dbd80d7bc7f9cab1e23a95c4375de615860ca089f34e7c64f4a8de7"}, + {file = "pandas-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:3de918a754bbf2da2381e8a3dcc45eede8cd7775b047b923f9006d5f876802ae"}, + {file = "pandas-2.2.0.tar.gz", hash = "sha256:30b83f7c3eb217fb4d1b494a57a2fda5444f17834f5df2de6b2ffff68dc3c8e2"}, ] [package.dependencies] numpy = {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""} python-dateutil = ">=2.8.2" pytz = ">=2020.1" -tzdata = ">=2022.1" +tzdata = ">=2022.7" [package.extras] -all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"] -aws = ["s3fs (>=2022.05.0)"] -clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"] -compression = ["zstandard (>=0.17.0)"] -computation = ["scipy (>=1.8.1)", "xarray (>=2022.03.0)"] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pyxlsb (>=1.0.9)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)"] -feather = ["pyarrow (>=7.0.0)"] -fss = ["fsspec (>=2022.05.0)"] -gcp = ["gcsfs (>=2022.05.0)", "pandas-gbq (>=0.17.5)"] -hdf5 = ["tables (>=3.7.0)"] -html = ["beautifulsoup4 (>=4.11.1)", "html5lib (>=1.1)", "lxml (>=4.8.0)"] -mysql = ["SQLAlchemy (>=1.4.36)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.8.10)"] -parquet = ["pyarrow (>=7.0.0)"] -performance = ["bottleneck (>=1.3.4)", "numba (>=0.55.2)", "numexpr (>=2.8.0)"] -plot = ["matplotlib (>=3.6.1)"] -postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"] -spss = ["pyreadstat (>=1.1.5)"] -sql-other = ["SQLAlchemy (>=1.4.36)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.8.0)"] +xml = ["lxml (>=4.9.2)"] [[package]] name = "parameterized" @@ -3103,27 +3083,27 @@ files = [ [[package]] name = "psutil" -version = "5.9.7" +version = "5.9.8" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"}, - {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:fe361f743cb3389b8efda21980d93eb55c1f1e3898269bc9a2a1d0bb7b1f6508"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e469990e28f1ad738f65a42dcfc17adaed9d0f325d55047593cb9033a0ab63df"}, - {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3c4747a3e2ead1589e647e64aad601981f01b68f9398ddf94d01e3dc0d1e57c7"}, - {file = "psutil-5.9.7-cp27-none-win32.whl", hash = "sha256:1d4bc4a0148fdd7fd8f38e0498639ae128e64538faa507df25a20f8f7fb2341c"}, - {file = "psutil-5.9.7-cp27-none-win_amd64.whl", hash = "sha256:4c03362e280d06bbbfcd52f29acd79c733e0af33d707c54255d21029b8b32ba6"}, - {file = "psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284"}, - {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe"}, - {file = "psutil-5.9.7-cp36-cp36m-win32.whl", hash = "sha256:b27f8fdb190c8c03914f908a4555159327d7481dac2f01008d483137ef3311a9"}, - {file = "psutil-5.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:44969859757f4d8f2a9bd5b76eba8c3099a2c8cf3992ff62144061e39ba8568e"}, - {file = "psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68"}, - {file = "psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414"}, - {file = "psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340"}, - {file = "psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c"}, + {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, + {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, + {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, + {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, + {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, + {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, + {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, + {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, + {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, + {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, + {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, + {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, + {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, ] [package.extras] @@ -3282,6 +3262,8 @@ files = [ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, @@ -3794,6 +3776,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3801,8 +3784,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3819,6 +3810,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3826,6 +3818,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3959,71 +3952,71 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.1.13" +version = "0.1.14" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e3fd36e0d48aeac672aa850045e784673449ce619afc12823ea7868fcc41d8ba"}, - {file = "ruff-0.1.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9fb6b3b86450d4ec6a6732f9f60c4406061b6851c4b29f944f8c9d91c3611c7a"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b13ba5d7156daaf3fd08b6b993360a96060500aca7e307d95ecbc5bb47a69296"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9ebb40442f7b531e136d334ef0851412410061e65d61ca8ce90d894a094feb22"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226b517f42d59a543d6383cfe03cccf0091e3e0ed1b856c6824be03d2a75d3b6"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5f0312ba1061e9b8c724e9a702d3c8621e3c6e6c2c9bd862550ab2951ac75c16"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f59bcf5217c661254bd6bc42d65a6fd1a8b80c48763cb5c2293295babd945dd"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6894b00495e00c27b6ba61af1fc666f17de6140345e5ef27dd6e08fb987259d"}, - {file = "ruff-0.1.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1600942485c6e66119da294c6294856b5c86fd6df591ce293e4a4cc8e72989"}, - {file = "ruff-0.1.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ee3febce7863e231a467f90e681d3d89210b900d49ce88723ce052c8761be8c7"}, - {file = "ruff-0.1.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dcaab50e278ff497ee4d1fe69b29ca0a9a47cd954bb17963628fa417933c6eb1"}, - {file = "ruff-0.1.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f57de973de4edef3ad3044d6a50c02ad9fc2dff0d88587f25f1a48e3f72edf5e"}, - {file = "ruff-0.1.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a36fa90eb12208272a858475ec43ac811ac37e91ef868759770b71bdabe27b6"}, - {file = "ruff-0.1.13-py3-none-win32.whl", hash = "sha256:a623349a505ff768dad6bd57087e2461be8db58305ebd5577bd0e98631f9ae69"}, - {file = "ruff-0.1.13-py3-none-win_amd64.whl", hash = "sha256:f988746e3c3982bea7f824c8fa318ce7f538c4dfefec99cd09c8770bd33e6539"}, - {file = "ruff-0.1.13-py3-none-win_arm64.whl", hash = "sha256:6bbbc3042075871ec17f28864808540a26f0f79a4478c357d3e3d2284e832998"}, - {file = "ruff-0.1.13.tar.gz", hash = "sha256:e261f1baed6291f434ffb1d5c6bd8051d1c2a26958072d38dfbec39b3dda7352"}, + {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:96f76536df9b26622755c12ed8680f159817be2f725c17ed9305b472a757cdbb"}, + {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab3f71f64498c7241123bb5a768544cf42821d2a537f894b22457a543d3ca7a9"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060156ecc572b8f984fd20fd8b0fcb692dd5d837b7606e968334ab7ff0090ab"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a53d8e35313d7b67eb3db15a66c08434809107659226a90dcd7acb2afa55faea"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bea9be712b8f5b4ebed40e1949379cfb2a7d907f42921cf9ab3aae07e6fba9eb"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2270504d629a0b064247983cbc495bed277f372fb9eaba41e5cf51f7ba705a6a"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80258bb3b8909b1700610dfabef7876423eed1bc930fe177c71c414921898efa"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:653230dd00aaf449eb5ff25d10a6e03bc3006813e2cb99799e568f55482e5cae"}, + {file = "ruff-0.1.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b3acc6c4e6928459ba9eb7459dd4f0c4bf266a053c863d72a44c33246bfdbf"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b3dadc9522d0eccc060699a9816e8127b27addbb4697fc0c08611e4e6aeb8b5"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c8eca1a47b4150dc0fbec7fe68fc91c695aed798532a18dbb1424e61e9b721f"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:62ce2ae46303ee896fc6811f63d6dabf8d9c389da0f3e3f2bce8bc7f15ef5488"}, + {file = "ruff-0.1.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2027dde79d217b211d725fc833e8965dc90a16d0d3213f1298f97465956661b"}, + {file = "ruff-0.1.14-py3-none-win32.whl", hash = "sha256:722bafc299145575a63bbd6b5069cb643eaa62546a5b6398f82b3e4403329cab"}, + {file = "ruff-0.1.14-py3-none-win_amd64.whl", hash = "sha256:e3d241aa61f92b0805a7082bd89a9990826448e4d0398f0e2bc8f05c75c63d99"}, + {file = "ruff-0.1.14-py3-none-win_arm64.whl", hash = "sha256:269302b31ade4cde6cf6f9dd58ea593773a37ed3f7b97e793c8594b262466b67"}, + {file = "ruff-0.1.14.tar.gz", hash = "sha256:ad3f8088b2dfd884820289a06ab718cde7d38b94972212cc4ba90d5fbc9955f3"}, ] [[package]] name = "scipy" -version = "1.11.4" +version = "1.12.0" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.9" files = [ - {file = "scipy-1.11.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc9a714581f561af0848e6b69947fda0614915f072dfd14142ed1bfe1b806710"}, - {file = "scipy-1.11.4-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cf00bd2b1b0211888d4dc75656c0412213a8b25e80d73898083f402b50f47e41"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9999c008ccf00e8fbcce1236f85ade5c569d13144f77a1946bef8863e8f6eb4"}, - {file = "scipy-1.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:933baf588daa8dc9a92c20a0be32f56d43faf3d1a60ab11b3f08c356430f6e56"}, - {file = "scipy-1.11.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8fce70f39076a5aa62e92e69a7f62349f9574d8405c0a5de6ed3ef72de07f446"}, - {file = "scipy-1.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:6550466fbeec7453d7465e74d4f4b19f905642c89a7525571ee91dd7adabb5a3"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b39a7e94f296025e3cffc2c567618174c0b1dde173960cf23808f9fae4be"}, - {file = "scipy-1.11.4-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1b7c3dca977f30a739e0409fb001056484661cb2541a01aba0bb0029f7b68db8"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00150c5eae7b610c32589dda259eacc7c4f1665aedf25d921907f4d08a951b1c"}, - {file = "scipy-1.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:530f9ad26440e85766509dbf78edcfe13ffd0ab7fec2560ee5c36ff74d6269ff"}, - {file = "scipy-1.11.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5e347b14fe01003d3b78e196e84bd3f48ffe4c8a7b8a1afbcb8f5505cb710993"}, - {file = "scipy-1.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:acf8ed278cc03f5aff035e69cb511741e0418681d25fbbb86ca65429c4f4d9cd"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:028eccd22e654b3ea01ee63705681ee79933652b2d8f873e7949898dda6d11b6"}, - {file = "scipy-1.11.4-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c6ff6ef9cc27f9b3db93a6f8b38f97387e6e0591600369a297a50a8e96e835d"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b030c6674b9230d37c5c60ab456e2cf12f6784596d15ce8da9365e70896effc4"}, - {file = "scipy-1.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad669df80528aeca5f557712102538f4f37e503f0c5b9541655016dd0932ca79"}, - {file = "scipy-1.11.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce7fff2e23ab2cc81ff452a9444c215c28e6305f396b2ba88343a567feec9660"}, - {file = "scipy-1.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:36750b7733d960d7994888f0d148d31ea3017ac15eef664194b4ef68d36a4a97"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e619aba2df228a9b34718efb023966da781e89dd3d21637b27f2e54db0410d7"}, - {file = "scipy-1.11.4-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:f3cd9e7b3c2c1ec26364856f9fbe78695fe631150f94cd1c22228456404cf1ec"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d10e45a6c50211fe256da61a11c34927c68f277e03138777bdebedd933712fea"}, - {file = "scipy-1.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91af76a68eeae0064887a48e25c4e616fa519fa0d38602eda7e0f97d65d57937"}, - {file = "scipy-1.11.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6df1468153a31cf55ed5ed39647279beb9cfb5d3f84369453b49e4b8502394fd"}, - {file = "scipy-1.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:ee410e6de8f88fd5cf6eadd73c135020bfbbbdfcd0f6162c36a7638a1ea8cc65"}, - {file = "scipy-1.11.4.tar.gz", hash = "sha256:90a2b78e7f5733b9de748f589f09225013685f9b218275257f8a8168ededaeaa"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78e4402e140879387187f7f25d91cc592b3501a2e51dfb320f48dfb73565f10b"}, + {file = "scipy-1.12.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5f00ebaf8de24d14b8449981a2842d404152774c1a1d880c901bf454cb8e2a1"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e53958531a7c695ff66c2e7bb7b79560ffdc562e2051644c5576c39ff8efb563"}, + {file = "scipy-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e32847e08da8d895ce09d108a494d9eb78974cf6de23063f93306a3e419960c"}, + {file = "scipy-1.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c1020cad92772bf44b8e4cdabc1df5d87376cb219742549ef69fc9fd86282dd"}, + {file = "scipy-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:75ea2a144096b5e39402e2ff53a36fecfd3b960d786b7efd3c180e29c39e53f2"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:408c68423f9de16cb9e602528be4ce0d6312b05001f3de61fe9ec8b1263cad08"}, + {file = "scipy-1.12.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5adfad5dbf0163397beb4aca679187d24aec085343755fcdbdeb32b3679f254c"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3003652496f6e7c387b1cf63f4bb720951cfa18907e998ea551e6de51a04467"}, + {file = "scipy-1.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b8066bce124ee5531d12a74b617d9ac0ea59245246410e19bca549656d9a40a"}, + {file = "scipy-1.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8bee4993817e204d761dba10dbab0774ba5a8612e57e81319ea04d84945375ba"}, + {file = "scipy-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a24024d45ce9a675c1fb8494e8e5244efea1c7a09c60beb1eeb80373d0fecc70"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e7e76cc48638228212c747ada851ef355c2bb5e7f939e10952bc504c11f4e372"}, + {file = "scipy-1.12.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f7ce148dffcd64ade37b2df9315541f9adad6efcaa86866ee7dd5db0c8f041c3"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c39f92041f490422924dfdb782527a4abddf4707616e07b021de33467f917bc"}, + {file = "scipy-1.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ebda398f86e56178c2fa94cad15bf457a218a54a35c2a7b4490b9f9cb2676c"}, + {file = "scipy-1.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:95e5c750d55cf518c398a8240571b0e0782c2d5a703250872f36eaf737751338"}, + {file = "scipy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e646d8571804a304e1da01040d21577685ce8e2db08ac58e543eaca063453e1c"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:913d6e7956c3a671de3b05ccb66b11bc293f56bfdef040583a7221d9e22a2e35"}, + {file = "scipy-1.12.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba1b0c7256ad75401c73e4b3cf09d1f176e9bd4248f0d3112170fb2ec4db067"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:730badef9b827b368f351eacae2e82da414e13cf8bd5051b4bdfd720271a5371"}, + {file = "scipy-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6546dc2c11a9df6926afcbdd8a3edec28566e4e785b915e849348c6dd9f3f490"}, + {file = "scipy-1.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc"}, + {file = "scipy-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:b360f1b6b2f742781299514e99ff560d1fe9bd1bff2712894b52abe528d1fd1e"}, + {file = "scipy-1.12.0.tar.gz", hash = "sha256:4bf5abab8a36d20193c698b0f1fc282c1d083c94723902c447e5d2f1780936a3"}, ] [package.dependencies] -numpy = ">=1.21.6,<1.28.0" +numpy = ">=1.22.4,<1.29.0" [package.extras] dev = ["click", "cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] doc = ["jupytext", "matplotlib (>2)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-design (>=0.2.0)"] -test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +test = ["asv", "gmpy2", "hypothesis", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "scons" @@ -4302,17 +4295,6 @@ docs = ["sphinx (>=1.5.3)"] qa = ["flake8"] test = ["mock", "nose"] -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] - [[package]] name = "snowballstemmer" version = "2.2.0" From 77d051f5ec0fa95b35b44a26793ef7e6df260234 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 22 Jan 2024 09:15:58 -0800 Subject: [PATCH 534/724] Bump submodules (#31107) bump submodules Co-authored-by: jnewb1 --- opendbc | 2 +- panda | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc b/opendbc index 40d9c723d4..f4b1061ae0 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 40d9c723d48496229fecc436046538a53af19c11 +Subproject commit f4b1061ae03994013809dc4a03ac571bfeda5714 diff --git a/panda b/panda index 06feb083cf..c5e79403a8 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 06feb083cf9107a7c38147134bde10fe7df9e9e9 +Subproject commit c5e79403a8c49f5a36e3ede44fcda65fabe263bf From 95d33164fd98246c2d80afa78732352f97b83bf9 Mon Sep 17 00:00:00 2001 From: Brad Slavin Date: Mon, 22 Jan 2024 12:09:17 -0800 Subject: [PATCH 535/724] Hyundai: add missing Ioniq 5 2024 RWD Limited FW (#30864) Update fingerprints.py Added 2024 RWD Limited --- selfdrive/car/hyundai/fingerprints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index fa6b56507c..3c5d4b9a51 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -1548,6 +1548,7 @@ FW_VERSIONS = { b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.06 99211-GI010 230110', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI020 230719', ], }, CAR.IONIQ_6: { From 7c9530f7431f66df186aa4855fbd481592d94198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 22 Jan 2024 14:44:48 -0800 Subject: [PATCH 536/724] Code for running legacy models (#31111) * Code for running legacy models * Optional outputs --- selfdrive/modeld/constants.py | 2 ++ selfdrive/modeld/parse_model_outputs.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py index e513922c72..e1741cbdcf 100644 --- a/selfdrive/modeld/constants.py +++ b/selfdrive/modeld/constants.py @@ -21,6 +21,7 @@ class ModelConstants: NAV_FEATURE_LEN = 256 NAV_INSTRUCTION_LEN = 150 DRIVING_STYLE_LEN = 12 + LAT_PLANNER_STATE_LEN = 4 LATERAL_CONTROL_PARAMS_LEN = 2 PREV_DESIRED_CURVS_LEN = 20 @@ -39,6 +40,7 @@ class ModelConstants: ROAD_EDGES_WIDTH = 2 PLAN_WIDTH = 15 DESIRE_PRED_WIDTH = 8 + LAT_PLANNER_SOLUTION_WIDTH = 4 DESIRED_CURV_WIDTH = 1 NUM_LANE_LINES = 4 diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 07d5e0a921..01cba29d1c 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -93,7 +93,10 @@ class Parser: self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION, out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH)) - self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,)) + if 'lat_planner_solution' in outs: + self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH)) + if 'desired_curvature' in outs: + self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,)) for k in ['lead_prob', 'lane_lines_prob', 'meta']: self.parse_binary_crossentropy(k, outs) self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) From 3846130d8e15ea71e9576ae62b69918757edc7b8 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 22 Jan 2024 16:30:59 -0800 Subject: [PATCH 537/724] pytest: add marker for sharing the download cache (#31082) * fix cache * with a marker --- common/prefix.py | 10 ++++++++-- conftest.py | 7 +++++-- selfdrive/car/tests/test_models.py | 4 ++++ selfdrive/locationd/test/test_locationd_scenarios.py | 1 + system/hardware/hw.py | 6 ++++-- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/common/prefix.py b/common/prefix.py index c1744e8ff7..d027e3e5a1 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -6,12 +6,14 @@ from typing import Optional from openpilot.common.params import Params from openpilot.system.hardware.hw import Paths +from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT class OpenpilotPrefix: - def __init__(self, prefix: Optional[str] = None, clean_dirs_on_exit: bool = True): + def __init__(self, prefix: Optional[str] = None, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) self.msgq_path = os.path.join('/dev/shm', self.prefix) self.clean_dirs_on_exit = clean_dirs_on_exit + self.shared_download_cache = shared_download_cache def __enter__(self): self.original_prefix = os.environ.get('OPENPILOT_PREFIX', None) @@ -22,6 +24,9 @@ class OpenpilotPrefix: pass os.makedirs(Paths.log_root(), exist_ok=True) + if self.shared_download_cache: + os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT + return self def __exit__(self, exc_type, exc_obj, exc_tb): @@ -42,5 +47,6 @@ class OpenpilotPrefix: os.remove(symlink_path) shutil.rmtree(self.msgq_path, ignore_errors=True) shutil.rmtree(Paths.log_root(), ignore_errors=True) - shutil.rmtree(Paths.download_cache_root(), ignore_errors=True) + if not os.environ.get("COMMA_CACHE", False): + shutil.rmtree(Paths.download_cache_root(), ignore_errors=True) shutil.rmtree(Paths.comma_home(), ignore_errors=True) diff --git a/conftest.py b/conftest.py index d8cbe25b0a..c4eb259a35 100644 --- a/conftest.py +++ b/conftest.py @@ -25,13 +25,13 @@ def pytest_runtest_call(item): @pytest.fixture(scope="function", autouse=True) -def openpilot_function_fixture(): +def openpilot_function_fixture(request): starting_env = dict(os.environ) random.seed(0) # setup a clean environment for each test - with OpenpilotPrefix(): + with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix: prefix = os.environ["OPENPILOT_PREFIX"] yield @@ -77,3 +77,6 @@ def pytest_configure(config): config_line = "nocapture: don't capture test output" config.addinivalue_line("markers", config_line) + + config_line = "shared_download_cache: share download cache between tests" + config.addinivalue_line("markers", config_line) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index a66bd180b4..973101d4ca 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -22,6 +22,7 @@ from openpilot.selfdrive.car.honda.values import CAR as HONDA, HONDA_BOSCH from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTestRoute from openpilot.selfdrive.controls.controlsd import Controls from openpilot.selfdrive.test.helpers import read_segment_list +from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.sanitizer import sanitize @@ -64,6 +65,7 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: @pytest.mark.slow +@pytest.mark.shared_download_cache class TestCarModelBase(unittest.TestCase): car_model: Optional[str] = None test_route: Optional[CarTestRoute] = None @@ -183,6 +185,8 @@ class TestCarModelBase(unittest.TestCase): assert cls.CP assert cls.CP.carFingerprint == cls.car_model + os.environ["COMMA_CACHE"] = DEFAULT_DOWNLOAD_CACHE_ROOT + @classmethod def tearDownClass(cls): del cls.can_msgs diff --git a/selfdrive/locationd/test/test_locationd_scenarios.py b/selfdrive/locationd/test/test_locationd_scenarios.py index 4cfbd6f8a9..f48c83ce46 100755 --- a/selfdrive/locationd/test/test_locationd_scenarios.py +++ b/selfdrive/locationd/test/test_locationd_scenarios.py @@ -97,6 +97,7 @@ def run_scenarios(scenario, logs): @pytest.mark.xdist_group("test_locationd_scenarios") +@pytest.mark.shared_download_cache class TestLocationdScenarios(unittest.TestCase): """ Test locationd with different scenarios. In all these scenarios, we expect the following: diff --git a/system/hardware/hw.py b/system/hardware/hw.py index a8967520e3..694299d72e 100644 --- a/system/hardware/hw.py +++ b/system/hardware/hw.py @@ -3,6 +3,8 @@ from pathlib import Path from openpilot.system.hardware import PC +DEFAULT_DOWNLOAD_CACHE_ROOT = "/tmp/comma_download_cache" + class Paths: @staticmethod def comma_home() -> str: @@ -31,8 +33,8 @@ class Paths: @staticmethod def download_cache_root() -> str: if os.environ.get('COMMA_CACHE', False): - return os.environ['COMMA_CACHE'] - return "/tmp/comma_download_cache" + os.environ.get("OPENPILOT_PREFIX", "") + "/" + return os.environ['COMMA_CACHE'] + "/" + return DEFAULT_DOWNLOAD_CACHE_ROOT + os.environ.get("OPENPILOT_PREFIX", "") + "/" @staticmethod def persist_root() -> str: From 694fc378dd1b4dc4b26847efb25af538133d0685 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 22 Jan 2024 17:24:42 -0800 Subject: [PATCH 538/724] loggerd: speedup unit tests (#31115) * first speed it up * pytestify * no sleep --- system/loggerd/tests/test_loggerd.py | 53 ++++++++++++---------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 49d97505e8..dde12b646d 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -5,7 +5,6 @@ import random import string import subprocess import time -import unittest from collections import defaultdict from pathlib import Path from typing import Dict, List @@ -31,10 +30,7 @@ CEREAL_SERVICES = [f for f in log.Event.schema.union_fields if f in SERVICE_LIST and SERVICE_LIST[f].should_log and "encode" not in f.lower()] -class TestLoggerd(unittest.TestCase): - def setUp(self): - os.environ.pop("LOG_ROOT", None) - +class TestLoggerd: def _get_latest_log_dir(self): log_dirs = sorted(Path(Paths.log_root()).iterdir(), key=lambda f: f.stat().st_mtime) return log_dirs[-1] @@ -68,21 +64,21 @@ class TestLoggerd(unittest.TestCase): def _check_init_data(self, msgs): msg = msgs[0] - self.assertEqual(msg.which(), 'initData') + assert msg.which() == 'initData' def _check_sentinel(self, msgs, route): start_type = SentinelType.startOfRoute if route else SentinelType.startOfSegment - self.assertTrue(msgs[1].sentinel.type == start_type) + assert msgs[1].sentinel.type == start_type end_type = SentinelType.endOfRoute if route else SentinelType.endOfSegment - self.assertTrue(msgs[-1].sentinel.type == end_type) + assert msgs[-1].sentinel.type == end_type def _publish_random_messages(self, services: List[str]) -> Dict[str, list]: pm = messaging.PubMaster(services) managed_processes["loggerd"].start() for s in services: - self.assertTrue(pm.wait_for_readers_to_update(s, timeout=5)) + assert pm.wait_for_readers_to_update(s, timeout=5) sent_msgs = defaultdict(list) for _ in range(random.randint(2, 10) * 100): @@ -93,10 +89,9 @@ class TestLoggerd(unittest.TestCase): m = messaging.new_message(s, random.randint(2, 10)) pm.send(s, m) sent_msgs[s].append(m) - time.sleep(0.01) for s in services: - self.assertTrue(pm.wait_for_readers_to_update(s, timeout=5)) + assert pm.wait_for_readers_to_update(s, timeout=5) managed_processes["loggerd"].stop() return sent_msgs @@ -121,15 +116,15 @@ class TestLoggerd(unittest.TestCase): lr = list(LogReader(str(self._gen_bootlog()))) initData = lr[0].initData - self.assertTrue(initData.dirty != bool(os.environ["CLEAN"])) - self.assertEqual(initData.version, get_version()) + assert initData.dirty != bool(os.environ["CLEAN"]) + assert initData.version == get_version() if os.path.isfile("/proc/cmdline"): with open("/proc/cmdline") as f: - self.assertEqual(list(initData.kernelArgs), f.read().strip().split(" ")) + assert list(initData.kernelArgs) == f.read().strip().split(" ") with open("/proc/version") as f: - self.assertEqual(initData.kernelVersion, f.read()) + assert initData.kernelVersion == f.read() # check params logged_params = {entry.key: entry.value for entry in initData.params.entries} @@ -137,8 +132,8 @@ class TestLoggerd(unittest.TestCase): assert set(logged_params.keys()) == expected_params, set(logged_params.keys()) ^ expected_params assert logged_params['AccessToken'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['AccessToken'])}" for param_key, initData_key, v in fake_params: - self.assertEqual(getattr(initData, initData_key), v) - self.assertEqual(logged_params[param_key].decode(), v) + assert getattr(initData, initData_key) == v + assert logged_params[param_key].decode() == v params.put("AccessToken", "") @@ -162,11 +157,10 @@ class TestLoggerd(unittest.TestCase): os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length) managed_processes["loggerd"].start() managed_processes["encoderd"].start() - time.sleep(1) + assert pm.wait_for_readers_to_update("roadCameraState", timeout=5) fps = 20.0 for n in range(1, int(num_segs*length*fps)+1): - time_start = time.monotonic() for stream_type, frame_spec, state in streams: dat = np.empty(frame_spec[2], dtype=np.uint8) vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps) @@ -175,7 +169,9 @@ class TestLoggerd(unittest.TestCase): frame = getattr(camera_state, state) frame.frameId = n pm.send(state, camera_state) - time.sleep(max((1.0/fps) - (time.monotonic() - time_start), 0)) + + for _, _, state in streams: + assert pm.wait_for_readers_to_update(state, timeout=5, dt=0.001) managed_processes["loggerd"].stop() managed_processes["encoderd"].stop() @@ -185,7 +181,7 @@ class TestLoggerd(unittest.TestCase): p = Path(f"{route_path}--{n}") logged = {f.name for f in p.iterdir() if f.is_file()} diff = logged ^ expected_files - self.assertEqual(len(diff), 0, f"didn't get all expected files. run={_} seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}") + assert len(diff) == 0, f"didn't get all expected files. run={_} seg={n} {route_path=}, {diff=}\n{logged=} {expected_files=}" def test_bootlog(self): # generate bootlog with fake launch log @@ -216,7 +212,7 @@ class TestLoggerd(unittest.TestCase): with open(path, "rb") as f: expected_val = f.read() bootlog_val = [e.value for e in boot.pstore.entries if e.key == fn][0] - self.assertEqual(expected_val, bootlog_val) + assert expected_val == bootlog_val def test_qlog(self): qlog_services = [s for s in CEREAL_SERVICES if SERVICE_LIST[s].decimation is not None] @@ -242,11 +238,11 @@ class TestLoggerd(unittest.TestCase): if s in no_qlog_services: # check services with no specific decimation aren't in qlog - self.assertEqual(recv_cnt, 0, f"got {recv_cnt} {s} msgs in qlog") + assert recv_cnt == 0, f"got {recv_cnt} {s} msgs in qlog" else: # check logged message count matches decimation expected_cnt = (len(msgs) - 1) // SERVICE_LIST[s].decimation + 1 - self.assertEqual(recv_cnt, expected_cnt, f"expected {expected_cnt} msgs for {s}, got {recv_cnt}") + assert recv_cnt == expected_cnt, f"expected {expected_cnt} msgs for {s}, got {recv_cnt}" def test_rlog(self): services = random.sample(CEREAL_SERVICES, random.randint(5, 10)) @@ -263,22 +259,19 @@ class TestLoggerd(unittest.TestCase): for m in lr: sent = sent_msgs[m.which()].pop(0) sent.clear_write_flag() - self.assertEqual(sent.to_bytes(), m.as_builder().to_bytes()) + assert sent.to_bytes() == m.as_builder().to_bytes() def test_preserving_flagged_segments(self): services = set(random.sample(CEREAL_SERVICES, random.randint(5, 10))) | {"userFlag"} self._publish_random_messages(services) segment_dir = self._get_latest_log_dir() - self.assertEqual(getxattr(segment_dir, PRESERVE_ATTR_NAME), PRESERVE_ATTR_VALUE) + assert getxattr(segment_dir, PRESERVE_ATTR_NAME) == PRESERVE_ATTR_VALUE def test_not_preserving_unflagged_segments(self): services = set(random.sample(CEREAL_SERVICES, random.randint(5, 10))) - {"userFlag"} self._publish_random_messages(services) segment_dir = self._get_latest_log_dir() - self.assertIsNone(getxattr(segment_dir, PRESERVE_ATTR_NAME)) + assert getxattr(segment_dir, PRESERVE_ATTR_NAME) is None - -if __name__ == "__main__": - unittest.main() From caa9153974c21fd827d0e991f1789cf3e81b5c3b Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 22 Jan 2024 17:33:03 -0800 Subject: [PATCH 539/724] CommaCarSegments: public database of segments for each platform (#31114) * comma car segments * comma car segments for test_models * oneliner Co-authored-by: Shane Smiskol * better name * not used here * sort * remove print * better comment --------- Co-authored-by: Shane Smiskol --- selfdrive/car/tests/test_models.py | 4 +- tools/lib/comma_car_segments.py | 82 ++++++++++++++++++++++ tools/lib/logreader.py | 4 ++ tools/lib/tests/test_comma_car_segments.py | 41 +++++++++++ 4 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 tools/lib/comma_car_segments.py create mode 100644 tools/lib/tests/test_comma_car_segments.py diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 973101d4ca..537214d14c 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -23,9 +23,8 @@ from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTes from openpilot.selfdrive.controls.controlsd import Controls from openpilot.selfdrive.test.helpers import read_segment_list from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT -from openpilot.tools.lib.openpilotci import get_url +from openpilot.tools.lib.comma_car_segments import get_url from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.sanitizer import sanitize from openpilot.tools.lib.route import Route, SegmentName, RouteName from panda.tests.libpanda import libpanda_py @@ -86,7 +85,6 @@ class TestCarModelBase(unittest.TestCase): @classmethod def get_testing_data_from_logreader(cls, lr): - lr = sanitize(lr) car_fw = [] can_msgs = [] cls.elm_frame = None diff --git a/tools/lib/comma_car_segments.py b/tools/lib/comma_car_segments.py new file mode 100644 index 0000000000..995b26d8e6 --- /dev/null +++ b/tools/lib/comma_car_segments.py @@ -0,0 +1,82 @@ +import os +import requests + +# Forks with additional car support can fork the commaCarSegments repo on huggingface or host the LFS files themselves +COMMA_CAR_SEGMENTS_REPO = os.environ.get("COMMA_CAR_SEGMENTS_REPO", "https://huggingface.co/datasets/commaai/commaCarSegments") +COMMA_CAR_SEGMENTS_BRANCH = os.environ.get("COMMA_CAR_SEGMENTS_BRANCH", "main") + +COMMA_CAR_SEGMENTS_LFS_INSTANCE = os.environ.get("COMMA_CAR_SEGMENTS_LFS_INSTANCE", "https://huggingface.co/datasets/commaai/commaCarSegments") + +def get_comma_car_segments_database(): + return requests.get(get_repo_raw_url("database.json")).json() + + +# Helpers related to interfacing with the openpilot-data repository, which contains a collection of public segments for users to perform validation on. + +def parse_lfs_pointer(text): + header, lfs_version = text.splitlines()[0].split(" ") + assert header == "version" + assert lfs_version == "https://git-lfs.github.com/spec/v1" + + header, oid_raw = text.splitlines()[1].split(" ") + assert header == "oid" + header, oid = oid_raw.split(":") + assert header == "sha256" + + header, size = text.splitlines()[2].split(" ") + assert header == "size" + + return oid, size + +def get_lfs_file_url(oid, size): + data = { + "operation": "download", + "transfers": [ "basic" ], + "objects": [ + { + "oid": oid, + "size": int(size) + } + ], + "hash_algo": "sha256" + } + + headers = { + "Accept": "application/vnd.git-lfs+json", + "Content-Type": "application/vnd.git-lfs+json" + } + + response = requests.post(f"{COMMA_CAR_SEGMENTS_LFS_INSTANCE}.git/info/lfs/objects/batch", json=data, headers=headers) + + assert response.ok + + obj = response.json()["objects"][0] + + assert "error" not in obj, obj + + return obj["actions"]["download"]["href"] + +def get_repo_raw_url(path): + if "huggingface" in COMMA_CAR_SEGMENTS_REPO: + return f"{COMMA_CAR_SEGMENTS_REPO}/raw/{COMMA_CAR_SEGMENTS_BRANCH}/{path}" + +def get_repo_url(path): + # Automatically switch to LFS if we are requesting a file that is stored in LFS + + response = requests.head(get_repo_raw_url(path)) + + if "text/plain" in response.headers.get("content-type"): + # This is an LFS pointer, so download the raw data from lfs + response = requests.get(get_repo_raw_url(path)) + assert response.status_code == 200 + oid, size = parse_lfs_pointer(response.text) + + return get_lfs_file_url(oid, size) + else: + # File has not been uploaded to LFS yet + # (either we are on a fork where the data hasn't been pushed to LFS yet, or the CI job to push hasn't finished) + return get_repo_raw_url(path) + + +def get_url(route, segment, file="rlog.bz2"): + return get_repo_url(f"segments/{route.replace('|', '/')}/{segment}/{file}") diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index c7f9f9fae0..9334f542f6 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -209,6 +209,10 @@ class LogReader: return _LogFileReader("", dat=dat) +def get_first_message(lr: LogIterable, msg_type): + return next(filter(lambda m: m.which() == msg_type, lr), None) + + if __name__ == "__main__": import codecs # capnproto <= 0.8.0 throws errors converting byte data to string diff --git a/tools/lib/tests/test_comma_car_segments.py b/tools/lib/tests/test_comma_car_segments.py new file mode 100644 index 0000000000..1d6088d821 --- /dev/null +++ b/tools/lib/tests/test_comma_car_segments.py @@ -0,0 +1,41 @@ + + +import unittest + +import requests +from openpilot.tools.lib.comma_car_segments import get_comma_car_segments_database, get_url +from openpilot.tools.lib.logreader import LogReader, get_first_message +from openpilot.tools.lib.route import SegmentRange + + +class TestCommaCarSegments(unittest.TestCase): + def test_database(self): + database = get_comma_car_segments_database() + + platforms = database.keys() + + assert len(platforms) > 100 + + def test_download_segment(self): + database = get_comma_car_segments_database() + + fp = "SUBARU FORESTER 2019" + + segment = database[fp][0] + + sr = SegmentRange(segment) + + url = get_url(sr.route_name, sr._slice) + + resp = requests.get(url) + self.assertEqual(resp.status_code, 200) + + lr = LogReader(url) + + CP = get_first_message(lr, "carParams").carParams + + self.assertEqual(CP.carFingerprint, fp) + + +if __name__ == "__main__": + unittest.main() From 389b8ca30d62f81ae40a7d026a299ce3930183da Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 22 Jan 2024 18:23:07 -0800 Subject: [PATCH 540/724] add example of using the comma car segments database (#31117) * add vin fingerprinting example * perhaps! * remove segment * easier --- tools/car_porting/README.md | 24 ++- .../examples/ford_vin_fingerprint.ipynb | 140 ++++++++++++++++++ tools/lib/logreader.py | 6 + 3 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 tools/car_porting/examples/ford_vin_fingerprint.ipynb diff --git a/tools/car_porting/README.md b/tools/car_porting/README.md index 4197e3ab76..8db17b0976 100644 --- a/tools/car_porting/README.md +++ b/tools/car_porting/README.md @@ -71,4 +71,26 @@ An example of plotting the response of an actuator when it is active. ![brake pressure example](https://github.com/commaai/openpilot/assets/9648890/8f32cf1d-8fc0-4407-b540-70625ebbf082) -*a plot of the brake_pressure vs acceleration, where we can see it is a fairly linear response.* \ No newline at end of file +*a plot of the brake_pressure vs acceleration, where we can see it is a fairly linear response.* + +### [tools/car_porting/examples/ford_vin_fingerprint.ipynb](/tools/car_porting/examples/ford_vin_fingerprint.ipynb) + +In this example, we use the public comma car segments database to check if vin fingerprinting is feasible for ford. + +``` +vin: 1FM5K8GC7LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False +vin: 00000000000XXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False +vin: 3FTTW8F98NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False +vin: 1FTVW1EL4NWXXXXXX real platform: FORD F-150 LIGHTNING 1ST GEN determined platform: FORD F-150 LIGHTNING 1ST GEN correct: True +vin: 1FM5K7LC0MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False +vin: WF0NXXGCHNJXXXXXX real platform: FORD FOCUS 4TH GEN determined platform: mock correct: False +vin: 1FMCU9J94MUXXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False +vin: 5LM5J7XC9LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False +vin: 3FMCR9B69NRXXXXXX real platform: FORD BRONCO SPORT 1ST GEN determined platform: mock correct: False +vin: 3FMTK3SU0MMXXXXXX real platform: FORD MUSTANG MACH-E 1ST GEN determined platform: FORD MUSTANG MACH-E 1ST GEN correct: True +vin: 1FM5K8HC7MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False +vin: 1FM5K8GC7NGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False +vin: 5LM5J7XC8MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False +vin: 3FTTW8E31PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False +vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False +``` \ No newline at end of file diff --git a/tools/car_porting/examples/ford_vin_fingerprint.ipynb b/tools/car_porting/examples/ford_vin_fingerprint.ipynb new file mode 100644 index 0000000000..bc689a5aad --- /dev/null +++ b/tools/car_porting/examples/ford_vin_fingerprint.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"In this example, we use the public comma car segments database to check if vin fingerprinting is feasible for ford.\"\"\"\n", + "\n", + "from openpilot.tools.lib.logreader import LogReader\n", + "from openpilot.tools.lib.comma_car_segments import get_comma_car_segments_database\n", + "from openpilot.selfdrive.car.ford.values import CAR\n", + "\n", + "database = get_comma_car_segments_database()\n", + "\n", + "platforms = [c.value for c in CAR]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Adapted from https://github.com/commaai/openpilot/issues/31052#issuecomment-1902690083\n", + "\n", + "MODEL_YEAR_CODES = {'M': 2021, 'N': 2022, 'P': 2023, 'R': 2024, 'S': 2025}\n", + "\n", + "\n", + "F150_CODES = ['F1C', 'F1E', 'W1C', 'W1E', 'X1C', 'X1E', 'W1R', 'W1P', 'W1S', 'W1T']\n", + "LIGHTNING_CODES = ['L', 'V']\n", + "MACHE_CODES = ['K1R', 'K1S', 'K2S', 'K3R', 'K3S', 'K4S']\n", + "\n", + "FORD_VIN_START = ['1FT', '3FM', '5LM']\n", + "\n", + "def ford_vin_fingerprint(vin): # Check if it's a Ford vehicle and determine the model\n", + " vin_positions_567 = vin[4:7]\n", + "\n", + " if vin.startswith('1FT'):\n", + " if vin_positions_567 in F150_CODES:\n", + " if vin[7] in LIGHTNING_CODES:\n", + " return f\"FORD F-150 LIGHTNING 1ST GEN\"\n", + " else:\n", + " return f\"FORD F-150 14TH GEN\"\n", + " elif vin.startswith('3FM'):\n", + " if vin_positions_567 in MACHE_CODES:\n", + " return f\"FORD MUSTANG MACH-E 1ST GEN\"\n", + " elif vin.startswith('5LM'):\n", + " pass\n", + "\n", + " return \"mock\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Skipping platform: FORD F-150 14TH GEN, no data available\n" + ] + } + ], + "source": [ + "from openpilot.tools.lib.logreader import comma_car_segments_source, get_first_message\n", + "\n", + "\n", + "VINS_TO_CHECK = set()\n", + "\n", + "for platform in platforms:\n", + " if platform not in database:\n", + " print(f\"Skipping platform: {platform}, no data available\")\n", + " continue\n", + "\n", + " for segment in database[platform]:\n", + " lr = LogReader(segment, default_source=comma_car_segments_source)\n", + " CP = get_first_message(lr, \"carParams\").carParams\n", + " VINS_TO_CHECK.add((CP.carVin, CP.carFingerprint))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "vin: 1FM5K8GC7LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 00000000000XXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n", + "vin: 3FTTW8F98NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", + "vin: 1FTVW1EL4NWXXXXXX real platform: FORD F-150 LIGHTNING 1ST GEN determined platform: FORD F-150 LIGHTNING 1ST GEN correct: True\n", + "vin: 1FM5K7LC0MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: WF0NXXGCHNJXXXXXX real platform: FORD FOCUS 4TH GEN determined platform: mock correct: False\n", + "vin: 1FMCU9J94MUXXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n", + "vin: 5LM5J7XC9LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 3FMCR9B69NRXXXXXX real platform: FORD BRONCO SPORT 1ST GEN determined platform: mock correct: False\n", + "vin: 3FMTK3SU0MMXXXXXX real platform: FORD MUSTANG MACH-E 1ST GEN determined platform: FORD MUSTANG MACH-E 1ST GEN correct: True\n", + "vin: 1FM5K8HC7MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 1FM5K8GC7NGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 5LM5J7XC8MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 3FTTW8E31PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", + "vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n" + ] + } + ], + "source": [ + "for vin, real_fingerprint in VINS_TO_CHECK:\n", + " determined_fingerprint = ford_vin_fingerprint(vin)\n", + " print(f\"vin: {vin} real platform: {real_fingerprint: <30} determined platform: {determined_fingerprint: <30} correct: {real_fingerprint == determined_fingerprint}\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 9334f542f6..c1f4f0f4a9 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -16,6 +16,7 @@ from typing import Iterable, Iterator, List, Type from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log +from openpilot.tools.lib.comma_car_segments import get_url as get_comma_segments_url from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.filereader import FileReader, file_exists from openpilot.tools.lib.helpers import RE @@ -111,6 +112,11 @@ def openpilotci_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): return [get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog') for seg in segs] +def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): + segs = parse_slice(sr, route) + + return [get_comma_segments_url(sr.route_name, seg) for seg in segs] + def direct_source(file_or_url): return [file_or_url] From 827aa2e4fad3334e594f8a259444f9461579fa2d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 22 Jan 2024 18:35:11 -0800 Subject: [PATCH 541/724] timed: always pull time and timezone from GPS (#31112) * timed * rm timezoned * just gps * ok * little more * fix * datetime * sleep a little --------- Co-authored-by: Comma Device --- release/files_common | 1 + release/files_tici | 2 - selfdrive/manager/process_config.py | 2 +- selfdrive/test/test_onroad.py | 2 +- system/timed.py | 94 +++++++++++++++++++++++++++++ system/timezoned.py | 70 --------------------- 6 files changed, 97 insertions(+), 74 deletions(-) create mode 100755 system/timed.py delete mode 100755 system/timezoned.py diff --git a/release/files_common b/release/files_common index 4784b369e7..cf985f682d 100644 --- a/release/files_common +++ b/release/files_common @@ -153,6 +153,7 @@ selfdrive/controls/lib/lateral_mpc_lib/* selfdrive/controls/lib/longitudinal_mpc_lib/* system/__init__.py +system/*.py system/hardware/__init__.py system/hardware/base.h diff --git a/release/files_tici b/release/files_tici index 1e5adab498..5d65ef458a 100644 --- a/release/files_tici +++ b/release/files_tici @@ -4,8 +4,6 @@ third_party/snpe/aarch64-ubuntu-gcc7.5/* third_party/mapbox-gl-native-qt/include/* third_party/acados/larch64/** -system/timezoned.py - system/camerad/cameras/camera_qcom2.cc system/camerad/cameras/camera_qcom2.h system/camerad/cameras/camera_util.cc diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 36d69b03bb..cb6dd8883f 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -49,7 +49,7 @@ procs = [ NativeProcess("proclogd", "system/proclogd", ["./proclogd"], only_onroad), PythonProcess("logmessaged", "system.logmessaged", always_run), PythonProcess("micd", "system.micd", iscar), - PythonProcess("timezoned", "system.timezoned", always_run, enabled=not PC), + PythonProcess("timed", "system.timed", always_run, enabled=not PC), PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(not PC or WEBCAM)), NativeProcess("encoderd", "system/loggerd", ["./encoderd"], only_onroad), diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 0161259356..335da73232 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -53,7 +53,7 @@ PROCS = { "selfdrive.tombstoned": 0, "./logcatd": 0, "system.micd": 6.0, - "system.timezoned": 0, + "system.timed": 0, "selfdrive.boardd.pandad": 0, "selfdrive.statsd": 0.4, "selfdrive.navd.navd": 0.4, diff --git a/system/timed.py b/system/timed.py new file mode 100755 index 0000000000..21fb47b680 --- /dev/null +++ b/system/timed.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +import datetime +import os +import subprocess +import time +from typing import NoReturn + +from timezonefinder import TimezoneFinder + +import cereal.messaging as messaging +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog +from openpilot.system.hardware import AGNOS + + +def set_timezone(timezone): + valid_timezones = subprocess.check_output('timedatectl list-timezones', shell=True, encoding='utf8').strip().split('\n') + if timezone not in valid_timezones: + cloudlog.error(f"Timezone not supported {timezone}") + return + + cloudlog.debug(f"Setting timezone to {timezone}") + try: + if AGNOS: + tzpath = os.path.join("/usr/share/zoneinfo/", timezone) + subprocess.check_call(f'sudo su -c "ln -snf {tzpath} /data/etc/tmptime && \ + mv /data/etc/tmptime /data/etc/localtime"', shell=True) + subprocess.check_call(f'sudo su -c "echo \"{timezone}\" > /data/etc/timezone"', shell=True) + else: + subprocess.check_call(f'sudo timedatectl set-timezone {timezone}', shell=True) + except subprocess.CalledProcessError: + cloudlog.exception(f"Error setting timezone to {timezone}") + + +def set_time(new_time): + diff = datetime.datetime.now() - new_time + if diff < datetime.timedelta(seconds=10): + cloudlog.debug(f"Time diff too small: {diff}") + return + + cloudlog.debug(f"Setting time to {new_time}") + try: + subprocess.run(f"TZ=UTC date -s '{new_time}'", shell=True, check=True) + except subprocess.CalledProcessError: + cloudlog.exception("timed.failed_setting_time") + + +def main() -> NoReturn: + """ + timed has two responsibilities: + - getting the current time + - getting the current timezone + + GPS directly gives time, and timezone is looked up from GPS position. + AGNOS will also use NTP to update the time. + """ + + params = Params() + tf = TimezoneFinder() + + # Restore timezone from param + tz = params.get("Timezone", encoding='utf8') + tf = TimezoneFinder() + if tz is not None: + cloudlog.debug("Restoring timezone from param") + set_timezone(tz) + + sm = messaging.SubMaster(['liveLocationKalman']) + while True: + sm.update(1000) + + llk = sm['liveLocationKalman'] + if not llk.gpsOK or (time.monotonic() - sm.logMonoTime['liveLocationKalman']/1e9) > 0.2: + continue + + # set time + # TODO: account for unixTimesatmpMillis being a (usually short) time in the past + gps_time = datetime.datetime.fromtimestamp(llk.unixTimestampMillis / 1000.) + set_time(gps_time) + + # set timezone + pos = llk.positionGeodetic.value + if len(pos) == 3: + gps_timezone = tf.timezone_at(lat=pos[0], lng=pos[1]) + if gps_timezone is None: + cloudlog.critical(f"No timezone found based on {pos=}") + else: + set_timezone(gps_timezone) + params.put_nonblocking("Timezone", gps_timezone) + + time.sleep(10) + +if __name__ == "__main__": + main() diff --git a/system/timezoned.py b/system/timezoned.py deleted file mode 100755 index 2cfc0076e9..0000000000 --- a/system/timezoned.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -import json -import os -import time -import subprocess -from typing import NoReturn - -from timezonefinder import TimezoneFinder - -from openpilot.common.params import Params -from openpilot.system.hardware import AGNOS -from openpilot.common.swaglog import cloudlog -from openpilot.system.version import get_version - -REQUEST_HEADERS = {'User-Agent': "openpilot-" + get_version()} - - -def set_timezone(valid_timezones, timezone): - if timezone not in valid_timezones: - cloudlog.error(f"Timezone not supported {timezone}") - return - - cloudlog.info(f"Setting timezone to {timezone}") - try: - if AGNOS: - tzpath = os.path.join("/usr/share/zoneinfo/", timezone) - subprocess.check_call(f'sudo su -c "ln -snf {tzpath} /data/etc/tmptime && \ - mv /data/etc/tmptime /data/etc/localtime"', shell=True) - subprocess.check_call(f'sudo su -c "echo \"{timezone}\" > /data/etc/timezone"', shell=True) - else: - subprocess.check_call(f'sudo timedatectl set-timezone {timezone}', shell=True) - except subprocess.CalledProcessError: - cloudlog.exception(f"Error setting timezone to {timezone}") - - -def main() -> NoReturn: - params = Params() - tf = TimezoneFinder() - - # Get allowed timezones - valid_timezones = subprocess.check_output('timedatectl list-timezones', shell=True, encoding='utf8').strip().split('\n') - - timezone = params.get("Timezone", encoding='utf8') - if timezone is not None: - cloudlog.debug("Setting timezone based on param") - set_timezone(valid_timezones, timezone) - - while True: - time.sleep(60) - - location = params.get("LastGPSPosition", encoding='utf8') - - # Find timezone by reverse geocoding the last known gps location - if location is not None: - cloudlog.debug("Setting timezone based on GPS location") - try: - location = json.loads(location) - except Exception: - cloudlog.exception("Error parsing location") - continue - - timezone = tf.timezone_at(lng=location['longitude'], lat=location['latitude']) - if timezone is None: - cloudlog.error(f"No timezone found based on location, {location}") - continue - set_timezone(valid_timezones, timezone) - - -if __name__ == "__main__": - main() From ee036482b8152f0e7650895c6f4bac5d4f00e00f Mon Sep 17 00:00:00 2001 From: mtribiere Date: Tue, 23 Jan 2024 07:35:47 +0100 Subject: [PATCH 542/724] Switch from json11 to nlohmann/json (#31093) * Initial commit * fix release/file_common * Update common/swaglog.cc * Swtiched serialize cc * move to lfs --------- Co-authored-by: Adeeb Shihadeh --- .gitattributes | 1 + SConstruct | 4 +- common/SConscript | 6 +- common/swaglog.cc | 16 +- common/tests/test_swaglog.cc | 25 +- release/files_common | 3 +- selfdrive/modeld/thneed/serialize.cc | 79 ++- selfdrive/modeld/thneed/thneed.h | 4 - selfdrive/navd/SConscript | 2 +- selfdrive/ui/SConscript | 4 +- system/logcatd/SConscript | 2 +- system/logcatd/logcatd_systemd.cc | 4 +- system/proclogd/SConscript | 2 +- third_party/SConscript | 3 - third_party/json11/json11.cpp | 784 --------------------------- third_party/json11/json11.hpp | 232 -------- third_party/nlohmann/json.hpp | 3 + 17 files changed, 76 insertions(+), 1098 deletions(-) delete mode 100644 third_party/json11/json11.cpp delete mode 100644 third_party/json11/json11.hpp create mode 100644 third_party/nlohmann/json.hpp diff --git a/.gitattributes b/.gitattributes index 8781a7371f..51f4625fb6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,3 +22,4 @@ third_party/acados/*/t_renderer filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lrelease filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lupdate filter=lfs diff=lfs merge=lfs -text third_party/catch2/include/catch2/catch.hpp filter=lfs diff=lfs merge=lfs -text +third_party/nlohmann/json.hpp filter=lfs diff=lfs merge=lfs -text diff --git a/SConstruct b/SConstruct index 3faa978087..5fa3c6f4fc 100644 --- a/SConstruct +++ b/SConstruct @@ -205,7 +205,7 @@ env = Environment( "#third_party/acados/include/hpipm/include", "#third_party/catch2/include", "#third_party/libyuv/include", - "#third_party/json11", + "#third_party/nlohmann", "#third_party/linux/include", "#third_party/snpe/include", "#third_party/mapbox-gl-native-qt/include", @@ -351,7 +351,7 @@ Export('env', 'qt_env', 'arch', 'real_arch') SConscript(['common/SConscript']) Import('_common', '_gpucommon') -common = [_common, 'json11'] +common = [_common] gpucommon = [_gpucommon] Export('common', 'gpucommon') diff --git a/common/SConscript b/common/SConscript index 829db6eeec..a8300a90e7 100644 --- a/common/SConscript +++ b/common/SConscript @@ -12,7 +12,7 @@ common_libs = [ if arch != "Darwin": common_libs.append('gpio.cc') -_common = env.Library('common', common_libs, LIBS="json11") +_common = env.Library('common', common_libs) files = [ 'clutil.cc', @@ -24,10 +24,10 @@ Export('_common', '_gpucommon') if GetOption('extras'): env.Program('tests/test_common', ['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'], - LIBS=[_common, 'json11', 'zmq', 'pthread']) + LIBS=[_common, 'zmq', 'pthread']) # Cython bindings -params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) +params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq']) SConscript([ 'transformations/SConscript', diff --git a/common/swaglog.cc b/common/swaglog.cc index 873836b725..270e866565 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -11,7 +11,7 @@ #include #include -#include "third_party/json11/json11.hpp" +#include "nlohmann/json.hpp" #include "common/version.h" #include "system/hardware/hw.h" @@ -37,7 +37,7 @@ public: } } - ctx_j = json11::Json::object{}; + ctx_j = nlohmann::json(); if (char* dongle_id = getenv("DONGLE_ID")) { ctx_j["dongle_id"] = dongle_id; } @@ -66,17 +66,17 @@ public: void* zctx = nullptr; void* sock = nullptr; int print_level; - json11::Json::object ctx_j; + nlohmann::json ctx_j; }; bool LOG_TIMESTAMPS = getenv("LOG_TIMESTAMPS"); uint32_t NO_FRAME_ID = std::numeric_limits::max(); static void cloudlog_common(int levelnum, const char* filename, int lineno, const char* func, - char* msg_buf, const json11::Json::object &msg_j={}) { + char* msg_buf, const nlohmann::json &msg_j={}) { static SwaglogState s; - json11::Json::object log_j = json11::Json::object { + nlohmann::json log_j = nlohmann::json { {"ctx", s.ctx_j}, {"levelnum", levelnum}, {"filename", filename}, @@ -92,7 +92,7 @@ static void cloudlog_common(int levelnum, const char* filename, int lineno, cons std::string log_s; log_s += (char)levelnum; - ((json11::Json)log_j).dump(log_s); + log_s += log_j.dump(); s.log(levelnum, filename, lineno, func, msg_buf, log_s); free(msg_buf); @@ -115,14 +115,14 @@ void cloudlog_t_common(int levelnum, const char* filename, int lineno, const cha char* msg_buf = nullptr; int ret = vasprintf(&msg_buf, fmt, args); if (ret <= 0 || !msg_buf) return; - json11::Json::object tspt_j = json11::Json::object{ + nlohmann::json tspt_j = nlohmann::json{ {"event", msg_buf}, {"time", std::to_string(nanos_since_boot())} }; if (frame_id < NO_FRAME_ID) { tspt_j["frame_id"] = std::to_string(frame_id); } - tspt_j = json11::Json::object{{"timestamp", tspt_j}}; + tspt_j = nlohmann::json{{"timestamp", tspt_j}}; cloudlog_common(levelnum, filename, lineno, func, msg_buf, tspt_j); } diff --git a/common/tests/test_swaglog.cc b/common/tests/test_swaglog.cc index 09bc4c3795..be5ce51c2d 100644 --- a/common/tests/test_swaglog.cc +++ b/common/tests/test_swaglog.cc @@ -7,7 +7,7 @@ #include "common/util.h" #include "common/version.h" #include "system/hardware/hw.h" -#include "third_party/json11/json11.hpp" +#include "nlohmann/json.hpp" std::string daemon_name = "testy"; std::string dongle_id = "test_dongle_id"; @@ -39,26 +39,25 @@ void recv_log(int thread_cnt, int thread_msg_cnt) { REQUIRE(buf[0] == CLOUDLOG_DEBUG); std::string err; - auto msg = json11::Json::parse(buf + 1, err); - REQUIRE(!msg.is_null()); + nlohmann::json msg = nlohmann::json::parse(buf + 1); - REQUIRE(msg["levelnum"].int_value() == CLOUDLOG_DEBUG); - REQUIRE_THAT(msg["filename"].string_value(), Catch::Contains("test_swaglog.cc")); - REQUIRE(msg["funcname"].string_value() == "log_thread"); - REQUIRE(msg["lineno"].int_value() == LINE_NO); + REQUIRE(msg["levelnum"].template get() == CLOUDLOG_DEBUG); + REQUIRE_THAT(msg["filename"].template get(), Catch::Contains("test_swaglog.cc")); + REQUIRE(msg["funcname"].template get() == "log_thread"); + REQUIRE(msg["lineno"].template get() == LINE_NO); auto ctx = msg["ctx"]; - REQUIRE(ctx["daemon"].string_value() == daemon_name); - REQUIRE(ctx["dongle_id"].string_value() == dongle_id); - REQUIRE(ctx["dirty"].bool_value() == true); + REQUIRE(ctx["daemon"].template get() == daemon_name); + REQUIRE(ctx["dongle_id"].template get() == dongle_id); + REQUIRE(ctx["dirty"].template get() == true); - REQUIRE(ctx["version"].string_value() == COMMA_VERSION); + REQUIRE(ctx["version"].template get() == COMMA_VERSION); std::string device = Hardware::get_name(); - REQUIRE(ctx["device"].string_value() == device); + REQUIRE(ctx["device"].template get() == device); - int thread_id = atoi(msg["msg"].string_value().c_str()); + int thread_id = atoi(msg["msg"].template get().c_str()); REQUIRE((thread_id >= 0 && thread_id < thread_cnt)); thread_msgs[thread_id]++; total_count++; diff --git a/release/files_common b/release/files_common index cf985f682d..342d5ff489 100644 --- a/release/files_common +++ b/release/files_common @@ -374,8 +374,7 @@ third_party/SConscript third_party/linux/** third_party/opencl/** -third_party/json11/json11.cpp -third_party/json11/json11.hpp +third_party/nlohmann/json.hpp third_party/qrcode/*.cc third_party/qrcode/*.hpp diff --git a/selfdrive/modeld/thneed/serialize.cc b/selfdrive/modeld/thneed/serialize.cc index 6ed5c08e81..56e0907ecc 100644 --- a/selfdrive/modeld/thneed/serialize.cc +++ b/selfdrive/modeld/thneed/serialize.cc @@ -1,11 +1,11 @@ #include #include -#include "third_party/json11/json11.hpp" +#include "nlohmann/json.hpp" #include "common/util.h" #include "common/clutil.h" #include "selfdrive/modeld/thneed/thneed.h" -using namespace json11; +using namespace nlohmann; extern map g_program_source; @@ -14,25 +14,24 @@ void Thneed::load(const char *filename) { string buf = util::read_file(filename); int jsz = *(int *)buf.data(); - string jsonerr; string jj(buf.data() + sizeof(int), jsz); - Json jdat = Json::parse(jj, jsonerr); + json jdat = json::parse(jj); map real_mem; real_mem[NULL] = NULL; int ptr = sizeof(int)+jsz; - for (auto &obj : jdat["objects"].array_items()) { - auto mobj = obj.object_items(); - int sz = mobj["size"].int_value(); + for (auto &obj : jdat["objects"]) { + auto mobj = obj; + int sz = mobj["size"].template get(); cl_mem clbuf = NULL; - if (mobj["buffer_id"].string_value().size() > 0) { + if (mobj["buffer_id"].template get().size() > 0) { // image buffer must already be allocated - clbuf = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; - assert(mobj["needs_load"].bool_value() == false); + clbuf = real_mem[*(cl_mem*)(mobj["buffer_id"].template get().data())]; + assert(mobj["needs_load"].template get() == false); } else { - if (mobj["needs_load"].bool_value()) { + if (mobj["needs_load"].template get()) { clbuf = clCreateBuffer(context, CL_MEM_COPY_HOST_PTR | CL_MEM_READ_WRITE, sz, &buf[ptr], NULL); if (debug >= 1) printf("loading %p %d @ 0x%X\n", clbuf, sz, ptr); ptr += sz; @@ -48,9 +47,9 @@ void Thneed::load(const char *filename) { if (mobj["arg_type"] == "image2d_t" || mobj["arg_type"] == "image1d_t") { cl_image_desc desc = {0}; desc.image_type = (mobj["arg_type"] == "image2d_t") ? CL_MEM_OBJECT_IMAGE2D : CL_MEM_OBJECT_IMAGE1D_BUFFER; - desc.image_width = mobj["width"].int_value(); - desc.image_height = mobj["height"].int_value(); - desc.image_row_pitch = mobj["row_pitch"].int_value(); + desc.image_width = mobj["width"].template get(); + desc.image_height = mobj["height"].template get(); + desc.image_row_pitch = mobj["row_pitch"].template get(); assert(sz == desc.image_height*desc.image_row_pitch); #ifdef QCOM2 desc.buffer = clbuf; @@ -60,12 +59,12 @@ void Thneed::load(const char *filename) { #endif cl_image_format format = {0}; format.image_channel_order = CL_RGBA; - format.image_channel_data_type = mobj["float32"].bool_value() ? CL_FLOAT : CL_HALF_FLOAT; + format.image_channel_data_type = mobj["float32"].template get() ? CL_FLOAT : CL_HALF_FLOAT; cl_int errcode; #ifndef QCOM2 - if (mobj["needs_load"].bool_value()) { + if (mobj["needs_load"].template get()) { clbuf = clCreateImage(context, CL_MEM_COPY_HOST_PTR | CL_MEM_READ_WRITE, &format, &desc, &buf[ptr-sz], &errcode); } else { clbuf = clCreateImage(context, CL_MEM_READ_WRITE, &format, &desc, NULL, &errcode); @@ -80,22 +79,22 @@ void Thneed::load(const char *filename) { assert(clbuf != NULL); } - real_mem[*(cl_mem*)(mobj["id"].string_value().data())] = clbuf; + real_mem[*(cl_mem*)(mobj["id"].template get().data())] = clbuf; } map g_programs; - for (const auto &[name, source] : jdat["programs"].object_items()) { - if (debug >= 1) printf("building %s with size %zu\n", name.c_str(), source.string_value().size()); - g_programs[name] = cl_program_from_source(context, device_id, source.string_value()); + for (const auto &[name, source] : jdat["programs"].items()) { + if (debug >= 1) printf("building %s with size %zu\n", name.c_str(), source.template get().size()); + g_programs[name] = cl_program_from_source(context, device_id, source.template get()); } - for (auto &obj : jdat["inputs"].array_items()) { - auto mobj = obj.object_items(); - int sz = mobj["size"].int_value(); - cl_mem aa = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; + for (auto &obj : jdat["inputs"]) { + auto mobj = obj; + int sz = mobj["size"].template get(); + cl_mem aa = real_mem[*(cl_mem*)(mobj["buffer_id"].template get().data())]; input_clmem.push_back(aa); input_sizes.push_back(sz); - printf("Thneed::load: adding input %s with size %d\n", mobj["name"].string_value().data(), sz); + printf("Thneed::load: adding input %s with size %d\n", mobj["name"].template get().data(), sz); cl_int cl_err; void *ret = clEnqueueMapBuffer(command_queue, aa, CL_TRUE, CL_MAP_WRITE, 0, sz, 0, NULL, NULL, &cl_err); @@ -104,39 +103,39 @@ void Thneed::load(const char *filename) { inputs.push_back(ret); } - for (auto &obj : jdat["outputs"].array_items()) { - auto mobj = obj.object_items(); - int sz = mobj["size"].int_value(); + for (auto &obj : jdat["outputs"]) { + auto mobj = obj; + int sz = mobj["size"].template get(); printf("Thneed::save: adding output with size %d\n", sz); // TODO: support multiple outputs - output = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; + output = real_mem[*(cl_mem*)(mobj["buffer_id"].template get().data())]; assert(output != NULL); } - for (auto &obj : jdat["binaries"].array_items()) { - string name = obj["name"].string_value(); - size_t length = obj["length"].int_value(); + for (auto &obj : jdat["binaries"]) { + string name = obj["name"].template get(); + size_t length = obj["length"].template get(); if (debug >= 1) printf("binary %s with size %zu\n", name.c_str(), length); g_programs[name] = cl_program_from_binary(context, device_id, (const uint8_t*)&buf[ptr], length); ptr += length; } - for (auto &obj : jdat["kernels"].array_items()) { + for (auto &obj : jdat["kernels"]) { auto gws = obj["global_work_size"]; auto lws = obj["local_work_size"]; auto kk = shared_ptr(new CLQueuedKernel(this)); - kk->name = obj["name"].string_value(); + kk->name = obj["name"].template get(); kk->program = g_programs[kk->name]; - kk->work_dim = obj["work_dim"].int_value(); + kk->work_dim = obj["work_dim"].template get(); for (int i = 0; i < kk->work_dim; i++) { - kk->global_work_size[i] = gws[i].int_value(); - kk->local_work_size[i] = lws[i].int_value(); + kk->global_work_size[i] = gws[i].template get(); + kk->local_work_size[i] = lws[i].template get(); } - kk->num_args = obj["num_args"].int_value(); + kk->num_args = obj["num_args"].template get(); for (int i = 0; i < kk->num_args; i++) { - string arg = obj["args"].array_items()[i].string_value(); - int arg_size = obj["args_size"].array_items()[i].int_value(); + string arg = obj["args"][i].template get(); + int arg_size = obj["args_size"][i].template get(); kk->args_size.push_back(arg_size); if (arg_size == 8) { cl_mem val = *(cl_mem*)(arg.data()); diff --git a/selfdrive/modeld/thneed/thneed.h b/selfdrive/modeld/thneed/thneed.h index 47e18e0be3..c5436a3847 100644 --- a/selfdrive/modeld/thneed/thneed.h +++ b/selfdrive/modeld/thneed/thneed.h @@ -18,9 +18,6 @@ using namespace std; cl_int thneed_clSetKernelArg(cl_kernel kernel, cl_uint arg_index, size_t arg_size, const void *arg_value); -namespace json11 { - class Json; -} class Thneed; class GPUMalloc { @@ -52,7 +49,6 @@ class CLQueuedKernel { vector args; vector args_size; cl_kernel kernel = NULL; - json11::Json to_json() const; cl_uint work_dim; size_t global_work_size[3] = {0}; diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript index c116ef1535..dfa6941c7f 100644 --- a/selfdrive/navd/SConscript +++ b/selfdrive/navd/SConscript @@ -2,7 +2,7 @@ Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'transfor map_env = qt_env.Clone() libs = ['qt_widgets', 'qt_util', 'qmapboxgl', common, messaging, cereal, visionipc, transformations, - 'zmq', 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread', 'json11'] + map_env["LIBS"] + 'zmq', 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + map_env["LIBS"] if arch == 'larch64': libs.append(':libEGL_mesa.so.0') diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a3cba124fe..53d9cdcc42 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -91,7 +91,7 @@ if GetOption('extras') and arch != "Darwin": # setup and factory resetter qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs) qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc", asset_obj], - LIBS=qt_libs + ['curl', 'common', 'json11']) + LIBS=qt_libs + ['curl', 'common']) # build updater UI qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) @@ -126,4 +126,4 @@ if GetOption('extras') and arch != "Darwin": # build watch3 if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'): - qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'zmq', 'visionipc', 'messaging']) + qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'zmq', 'visionipc', 'messaging']) diff --git a/system/logcatd/SConscript b/system/logcatd/SConscript index 6bd7c6ff3e..aaba60270a 100644 --- a/system/logcatd/SConscript +++ b/system/logcatd/SConscript @@ -1,3 +1,3 @@ Import('env', 'cereal', 'messaging', 'common') -env.Program('logcatd', 'logcatd_systemd.cc', LIBS=[cereal, messaging, common, 'zmq', 'capnp', 'kj', 'systemd', 'json11']) +env.Program('logcatd', 'logcatd_systemd.cc', LIBS=[cereal, messaging, common, 'zmq', 'capnp', 'kj', 'systemd']) diff --git a/system/logcatd/logcatd_systemd.cc b/system/logcatd/logcatd_systemd.cc index 54b3782132..4f98ac0d67 100644 --- a/system/logcatd/logcatd_systemd.cc +++ b/system/logcatd/logcatd_systemd.cc @@ -5,7 +5,7 @@ #include #include -#include "third_party/json11/json11.hpp" +#include "nlohmann/json.hpp" #include "cereal/messaging/messaging.h" #include "common/timing.h" @@ -62,7 +62,7 @@ int main(int argc, char *argv[]) { // Build message auto androidEntry = msg.initEvent().initAndroidLog(); androidEntry.setTs(timestamp); - androidEntry.setMessage(json11::Json(kv).dump()); + androidEntry.setMessage(nlohmann::json(kv).dump()); if (kv.count("_PID")) androidEntry.setPid(std::atoi(kv["_PID"].c_str())); if (kv.count("PRIORITY")) androidEntry.setPriority(std::atoi(kv["PRIORITY"].c_str())); if (kv.count("SYSLOG_IDENTIFIER")) androidEntry.setTag(kv["SYSLOG_IDENTIFIER"]); diff --git a/system/proclogd/SConscript b/system/proclogd/SConscript index 1f4b767011..e662991a48 100644 --- a/system/proclogd/SConscript +++ b/system/proclogd/SConscript @@ -1,5 +1,5 @@ Import('env', 'cereal', 'messaging', 'common') -libs = [cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj', 'common', 'zmq', 'json11'] +libs = [cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj', 'common', 'zmq'] env.Program('proclogd', ['main.cc', 'proclog.cc'], LIBS=libs) if GetOption('extras'): diff --git a/third_party/SConscript b/third_party/SConscript index e5bbfaa07a..14def0611c 100644 --- a/third_party/SConscript +++ b/third_party/SConscript @@ -1,6 +1,3 @@ Import('env') -env.Library('json11', ['json11/json11.cpp'], CCFLAGS=env['CCFLAGS'] + ['-Wno-unqualified-std-cast-call']) -env.Append(CPPPATH=[Dir('json11')]) - env.Library('kaitai', ['kaitai/kaitaistream.cpp'], CPPDEFINES=['KS_STR_ENCODING_NONE']) diff --git a/third_party/json11/json11.cpp b/third_party/json11/json11.cpp deleted file mode 100644 index bc4045f07d..0000000000 --- a/third_party/json11/json11.cpp +++ /dev/null @@ -1,784 +0,0 @@ -/* Copyright (c) 2013 Dropbox, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "json11.hpp" -#include -#include -#include -#include -#include - -namespace json11 { - -static const int max_depth = 200; - -using std::string; -using std::vector; -using std::map; -using std::make_shared; -using std::initializer_list; -using std::move; - -/* Helper for representing null - just a do-nothing struct, plus comparison - * operators so the helpers in JsonValue work. We can't use nullptr_t because - * it may not be orderable. - */ -struct NullStruct { - bool operator==(NullStruct) const { return true; } - bool operator<(NullStruct) const { return false; } -}; - -/* * * * * * * * * * * * * * * * * * * * - * Serialization - */ - -static void dump(NullStruct, string &out) { - out += "null"; -} - -static void dump(double value, string &out) { - if (std::isfinite(value)) { - char buf[32]; - snprintf(buf, sizeof buf, "%.17g", value); - out += buf; - } else { - out += "null"; - } -} - -static void dump(int value, string &out) { - char buf[32]; - snprintf(buf, sizeof buf, "%d", value); - out += buf; -} - -static void dump(bool value, string &out) { - out += value ? "true" : "false"; -} - -static void dump(const string &value, string &out) { - out += '"'; - for (size_t i = 0; i < value.length(); i++) { - const char ch = value[i]; - if (ch == '\\') { - out += "\\\\"; - } else if (ch == '"') { - out += "\\\""; - } else if (ch == '\b') { - out += "\\b"; - } else if (ch == '\f') { - out += "\\f"; - } else if (ch == '\n') { - out += "\\n"; - } else if (ch == '\r') { - out += "\\r"; - } else if (ch == '\t') { - out += "\\t"; - } else if (static_cast(ch) <= 0x1f) { - char buf[8]; - snprintf(buf, sizeof buf, "\\u%04x", ch); - out += buf; - } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 - && static_cast(value[i+2]) == 0xa8) { - out += "\\u2028"; - i += 2; - } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 - && static_cast(value[i+2]) == 0xa9) { - out += "\\u2029"; - i += 2; - } else { - out += ch; - } - } - out += '"'; -} - -static void dump(const Json::array &values, string &out) { - bool first = true; - out += "["; - for (const auto &value : values) { - if (!first) - out += ", "; - value.dump(out); - first = false; - } - out += "]"; -} - -static void dump(const Json::object &values, string &out) { - bool first = true; - out += "{"; - for (const auto &kv : values) { - if (!first) - out += ", "; - dump(kv.first, out); - out += ": "; - kv.second.dump(out); - first = false; - } - out += "}"; -} - -void Json::dump(string &out) const { - m_ptr->dump(out); -} - -/* * * * * * * * * * * * * * * * * * * * - * Value wrappers - */ - -template -class Value : public JsonValue { -protected: - - // Constructors - explicit Value(const T &value) : m_value(value) {} - explicit Value(T &&value) : m_value(move(value)) {} - - // Get type tag - Json::Type type() const override { - return tag; - } - - // Comparisons - bool equals(const JsonValue * other) const override { - return m_value == static_cast *>(other)->m_value; - } - bool less(const JsonValue * other) const override { - return m_value < static_cast *>(other)->m_value; - } - - const T m_value; - void dump(string &out) const override { json11::dump(m_value, out); } -}; - -class JsonDouble final : public Value { - double number_value() const override { return m_value; } - int int_value() const override { return static_cast(m_value); } - bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } - bool less(const JsonValue * other) const override { return m_value < other->number_value(); } -public: - explicit JsonDouble(double value) : Value(value) {} -}; - -class JsonInt final : public Value { - double number_value() const override { return m_value; } - int int_value() const override { return m_value; } - bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } - bool less(const JsonValue * other) const override { return m_value < other->number_value(); } -public: - explicit JsonInt(int value) : Value(value) {} -}; - -class JsonBoolean final : public Value { - bool bool_value() const override { return m_value; } -public: - explicit JsonBoolean(bool value) : Value(value) {} -}; - -class JsonString final : public Value { - const string &string_value() const override { return m_value; } -public: - explicit JsonString(const string &value) : Value(value) {} - explicit JsonString(string &&value) : Value(move(value)) {} -}; - -class JsonArray final : public Value { - const Json::array &array_items() const override { return m_value; } - const Json & operator[](size_t i) const override; -public: - explicit JsonArray(const Json::array &value) : Value(value) {} - explicit JsonArray(Json::array &&value) : Value(move(value)) {} -}; - -class JsonObject final : public Value { - const Json::object &object_items() const override { return m_value; } - const Json & operator[](const string &key) const override; -public: - explicit JsonObject(const Json::object &value) : Value(value) {} - explicit JsonObject(Json::object &&value) : Value(move(value)) {} -}; - -class JsonNull final : public Value { -public: - JsonNull() : Value({}) {} -}; - -/* * * * * * * * * * * * * * * * * * * * - * Static globals - static-init-safe - */ -struct Statics { - const std::shared_ptr null = make_shared(); - const std::shared_ptr t = make_shared(true); - const std::shared_ptr f = make_shared(false); - const string empty_string; - const vector empty_vector; - const map empty_map; - Statics() {} -}; - -static const Statics & statics() { - static const Statics s {}; - return s; -} - -static const Json & static_null() { - // This has to be separate, not in Statics, because Json() accesses statics().null. - static const Json json_null; - return json_null; -} - -/* * * * * * * * * * * * * * * * * * * * - * Constructors - */ - -Json::Json() noexcept : m_ptr(statics().null) {} -Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} -Json::Json(double value) : m_ptr(make_shared(value)) {} -Json::Json(int value) : m_ptr(make_shared(value)) {} -Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} -Json::Json(const string &value) : m_ptr(make_shared(value)) {} -Json::Json(string &&value) : m_ptr(make_shared(move(value))) {} -Json::Json(const char * value) : m_ptr(make_shared(value)) {} -Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} -Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {} -Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} -Json::Json(Json::object &&values) : m_ptr(make_shared(move(values))) {} - -/* * * * * * * * * * * * * * * * * * * * - * Accessors - */ - -Json::Type Json::type() const { return m_ptr->type(); } -double Json::number_value() const { return m_ptr->number_value(); } -int Json::int_value() const { return m_ptr->int_value(); } -bool Json::bool_value() const { return m_ptr->bool_value(); } -const string & Json::string_value() const { return m_ptr->string_value(); } -const vector & Json::array_items() const { return m_ptr->array_items(); } -const map & Json::object_items() const { return m_ptr->object_items(); } -const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } -const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } - -double JsonValue::number_value() const { return 0; } -int JsonValue::int_value() const { return 0; } -bool JsonValue::bool_value() const { return false; } -const string & JsonValue::string_value() const { return statics().empty_string; } -const vector & JsonValue::array_items() const { return statics().empty_vector; } -const map & JsonValue::object_items() const { return statics().empty_map; } -const Json & JsonValue::operator[] (size_t) const { return static_null(); } -const Json & JsonValue::operator[] (const string &) const { return static_null(); } - -const Json & JsonObject::operator[] (const string &key) const { - auto iter = m_value.find(key); - return (iter == m_value.end()) ? static_null() : iter->second; -} -const Json & JsonArray::operator[] (size_t i) const { - if (i >= m_value.size()) return static_null(); - else return m_value[i]; -} - -/* * * * * * * * * * * * * * * * * * * * - * Comparison - */ - -bool Json::operator== (const Json &other) const { - if (m_ptr->type() != other.m_ptr->type()) - return false; - - return m_ptr->equals(other.m_ptr.get()); -} - -bool Json::operator< (const Json &other) const { - if (m_ptr->type() != other.m_ptr->type()) - return m_ptr->type() < other.m_ptr->type(); - - return m_ptr->less(other.m_ptr.get()); -} - -/* * * * * * * * * * * * * * * * * * * * - * Parsing - */ - -/* esc(c) - * - * Format char c suitable for printing in an error message. - */ -static inline string esc(char c) { - char buf[12]; - if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { - snprintf(buf, sizeof buf, "'%c' (%d)", c, c); - } else { - snprintf(buf, sizeof buf, "(%d)", c); - } - return string(buf); -} - -static inline bool in_range(long x, long lower, long upper) { - return (x >= lower && x <= upper); -} - -namespace { -/* JsonParser - * - * Object that tracks all state of an in-progress parse. - */ -struct JsonParser final { - - /* State - */ - const string &str; - size_t i; - string &err; - bool failed; - const JsonParse strategy; - - /* fail(msg, err_ret = Json()) - * - * Mark this parse as failed. - */ - Json fail(string &&msg) { - return fail(move(msg), Json()); - } - - template - T fail(string &&msg, const T err_ret) { - if (!failed) - err = std::move(msg); - failed = true; - return err_ret; - } - - /* consume_whitespace() - * - * Advance until the current character is non-whitespace. - */ - void consume_whitespace() { - while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') - i++; - } - - /* consume_comment() - * - * Advance comments (c-style inline and multiline). - */ - bool consume_comment() { - bool comment_found = false; - if (str[i] == '/') { - i++; - if (i == str.size()) - return fail("unexpected end of input after start of comment", false); - if (str[i] == '/') { // inline comment - i++; - // advance until next line, or end of input - while (i < str.size() && str[i] != '\n') { - i++; - } - comment_found = true; - } - else if (str[i] == '*') { // multiline comment - i++; - if (i > str.size()-2) - return fail("unexpected end of input inside multi-line comment", false); - // advance until closing tokens - while (!(str[i] == '*' && str[i+1] == '/')) { - i++; - if (i > str.size()-2) - return fail( - "unexpected end of input inside multi-line comment", false); - } - i += 2; - comment_found = true; - } - else - return fail("malformed comment", false); - } - return comment_found; - } - - /* consume_garbage() - * - * Advance until the current character is non-whitespace and non-comment. - */ - void consume_garbage() { - consume_whitespace(); - if(strategy == JsonParse::COMMENTS) { - bool comment_found = false; - do { - comment_found = consume_comment(); - if (failed) return; - consume_whitespace(); - } - while(comment_found); - } - } - - /* get_next_token() - * - * Return the next non-whitespace character. If the end of the input is reached, - * flag an error and return 0. - */ - char get_next_token() { - consume_garbage(); - if (failed) return (char)0; - if (i == str.size()) - return fail("unexpected end of input", (char)0); - - return str[i++]; - } - - /* encode_utf8(pt, out) - * - * Encode pt as UTF-8 and add it to out. - */ - void encode_utf8(long pt, string & out) { - if (pt < 0) - return; - - if (pt < 0x80) { - out += static_cast(pt); - } else if (pt < 0x800) { - out += static_cast((pt >> 6) | 0xC0); - out += static_cast((pt & 0x3F) | 0x80); - } else if (pt < 0x10000) { - out += static_cast((pt >> 12) | 0xE0); - out += static_cast(((pt >> 6) & 0x3F) | 0x80); - out += static_cast((pt & 0x3F) | 0x80); - } else { - out += static_cast((pt >> 18) | 0xF0); - out += static_cast(((pt >> 12) & 0x3F) | 0x80); - out += static_cast(((pt >> 6) & 0x3F) | 0x80); - out += static_cast((pt & 0x3F) | 0x80); - } - } - - /* parse_string() - * - * Parse a string, starting at the current position. - */ - string parse_string() { - string out; - long last_escaped_codepoint = -1; - while (true) { - if (i == str.size()) - return fail("unexpected end of input in string", ""); - - char ch = str[i++]; - - if (ch == '"') { - encode_utf8(last_escaped_codepoint, out); - return out; - } - - if (in_range(ch, 0, 0x1f)) - return fail("unescaped " + esc(ch) + " in string", ""); - - // The usual case: non-escaped characters - if (ch != '\\') { - encode_utf8(last_escaped_codepoint, out); - last_escaped_codepoint = -1; - out += ch; - continue; - } - - // Handle escapes - if (i == str.size()) - return fail("unexpected end of input in string", ""); - - ch = str[i++]; - - if (ch == 'u') { - // Extract 4-byte escape sequence - string esc = str.substr(i, 4); - // Explicitly check length of the substring. The following loop - // relies on std::string returning the terminating NUL when - // accessing str[length]. Checking here reduces brittleness. - if (esc.length() < 4) { - return fail("bad \\u escape: " + esc, ""); - } - for (size_t j = 0; j < 4; j++) { - if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') - && !in_range(esc[j], '0', '9')) - return fail("bad \\u escape: " + esc, ""); - } - - long codepoint = strtol(esc.data(), nullptr, 16); - - // JSON specifies that characters outside the BMP shall be encoded as a pair - // of 4-hex-digit \u escapes encoding their surrogate pair components. Check - // whether we're in the middle of such a beast: the previous codepoint was an - // escaped lead (high) surrogate, and this is a trail (low) surrogate. - if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) - && in_range(codepoint, 0xDC00, 0xDFFF)) { - // Reassemble the two surrogate pairs into one astral-plane character, per - // the UTF-16 algorithm. - encode_utf8((((last_escaped_codepoint - 0xD800) << 10) - | (codepoint - 0xDC00)) + 0x10000, out); - last_escaped_codepoint = -1; - } else { - encode_utf8(last_escaped_codepoint, out); - last_escaped_codepoint = codepoint; - } - - i += 4; - continue; - } - - encode_utf8(last_escaped_codepoint, out); - last_escaped_codepoint = -1; - - if (ch == 'b') { - out += '\b'; - } else if (ch == 'f') { - out += '\f'; - } else if (ch == 'n') { - out += '\n'; - } else if (ch == 'r') { - out += '\r'; - } else if (ch == 't') { - out += '\t'; - } else if (ch == '"' || ch == '\\' || ch == '/') { - out += ch; - } else { - return fail("invalid escape character " + esc(ch), ""); - } - } - } - - /* parse_number() - * - * Parse a double. - */ - Json parse_number() { - size_t start_pos = i; - - if (str[i] == '-') - i++; - - // Integer part - if (str[i] == '0') { - i++; - if (in_range(str[i], '0', '9')) - return fail("leading 0s not permitted in numbers"); - } else if (in_range(str[i], '1', '9')) { - i++; - while (in_range(str[i], '0', '9')) - i++; - } else { - return fail("invalid " + esc(str[i]) + " in number"); - } - - if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' - && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) { - return std::atoi(str.c_str() + start_pos); - } - - // Decimal part - if (str[i] == '.') { - i++; - if (!in_range(str[i], '0', '9')) - return fail("at least one digit required in fractional part"); - - while (in_range(str[i], '0', '9')) - i++; - } - - // Exponent part - if (str[i] == 'e' || str[i] == 'E') { - i++; - - if (str[i] == '+' || str[i] == '-') - i++; - - if (!in_range(str[i], '0', '9')) - return fail("at least one digit required in exponent"); - - while (in_range(str[i], '0', '9')) - i++; - } - - return std::strtod(str.c_str() + start_pos, nullptr); - } - - /* expect(str, res) - * - * Expect that 'str' starts at the character that was just read. If it does, advance - * the input and return res. If not, flag an error. - */ - Json expect(const string &expected, Json res) { - assert(i != 0); - i--; - if (str.compare(i, expected.length(), expected) == 0) { - i += expected.length(); - return res; - } else { - return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); - } - } - - /* parse_json() - * - * Parse a JSON object. - */ - Json parse_json(int depth) { - if (depth > max_depth) { - return fail("exceeded maximum nesting depth"); - } - - char ch = get_next_token(); - if (failed) - return Json(); - - if (ch == '-' || (ch >= '0' && ch <= '9')) { - i--; - return parse_number(); - } - - if (ch == 't') - return expect("true", true); - - if (ch == 'f') - return expect("false", false); - - if (ch == 'n') - return expect("null", Json()); - - if (ch == '"') - return parse_string(); - - if (ch == '{') { - map data; - ch = get_next_token(); - if (ch == '}') - return data; - - while (1) { - if (ch != '"') - return fail("expected '\"' in object, got " + esc(ch)); - - string key = parse_string(); - if (failed) - return Json(); - - ch = get_next_token(); - if (ch != ':') - return fail("expected ':' in object, got " + esc(ch)); - - data[std::move(key)] = parse_json(depth + 1); - if (failed) - return Json(); - - ch = get_next_token(); - if (ch == '}') - break; - if (ch != ',') - return fail("expected ',' in object, got " + esc(ch)); - - ch = get_next_token(); - } - return data; - } - - if (ch == '[') { - vector data; - ch = get_next_token(); - if (ch == ']') - return data; - - while (1) { - i--; - data.push_back(parse_json(depth + 1)); - if (failed) - return Json(); - - ch = get_next_token(); - if (ch == ']') - break; - if (ch != ',') - return fail("expected ',' in list, got " + esc(ch)); - - ch = get_next_token(); - (void)ch; - } - return data; - } - - return fail("expected value, got " + esc(ch)); - } -}; -}//namespace { - -Json Json::parse(const string &in, string &err, JsonParse strategy) { - JsonParser parser { in, 0, err, false, strategy }; - Json result = parser.parse_json(0); - - // Check for any trailing garbage - parser.consume_garbage(); - if (parser.failed) - return Json(); - if (parser.i != in.size()) - return parser.fail("unexpected trailing " + esc(in[parser.i])); - - return result; -} - -// Documented in json11.hpp -vector Json::parse_multi(const string &in, - std::string::size_type &parser_stop_pos, - string &err, - JsonParse strategy) { - JsonParser parser { in, 0, err, false, strategy }; - parser_stop_pos = 0; - vector json_vec; - while (parser.i != in.size() && !parser.failed) { - json_vec.push_back(parser.parse_json(0)); - if (parser.failed) - break; - - // Check for another object - parser.consume_garbage(); - if (parser.failed) - break; - parser_stop_pos = parser.i; - } - return json_vec; -} - -/* * * * * * * * * * * * * * * * * * * * - * Shape-checking - */ - -bool Json::has_shape(const shape & types, string & err) const { - if (!is_object()) { - err = "expected JSON object, got " + dump(); - return false; - } - - for (auto & item : types) { - if ((*this)[item.first].type() != item.second) { - err = "bad type for " + item.first + " in " + dump(); - return false; - } - } - - return true; -} - -} // namespace json11 diff --git a/third_party/json11/json11.hpp b/third_party/json11/json11.hpp deleted file mode 100644 index 5202ef9323..0000000000 --- a/third_party/json11/json11.hpp +++ /dev/null @@ -1,232 +0,0 @@ -/* json11 - * - * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. - * - * The core object provided by the library is json11::Json. A Json object represents any JSON - * value: null, bool, number (int or double), string (std::string), array (std::vector), or - * object (std::map). - * - * Json objects act like values: they can be assigned, copied, moved, compared for equality or - * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and - * Json::parse (static) to parse a std::string as a Json object. - * - * Internally, the various types of Json object are represented by the JsonValue class - * hierarchy. - * - * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, - * so some JSON implementations distinguish between integers and floating-point numbers, while - * some don't. In json11, we choose the latter. Because some JSON implementations (namely - * Javascript itself) treat all numbers as the same type, distinguishing the two leads - * to JSON that will be *silently* changed by a round-trip through those implementations. - * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also - * provides integer helpers. - * - * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the - * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 - * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch - * will be exact for +/- 275 years.) - */ - -/* Copyright (c) 2013 Dropbox, Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#ifdef _MSC_VER - #if _MSC_VER <= 1800 // VS 2013 - #ifndef noexcept - #define noexcept throw() - #endif - - #ifndef snprintf - #define snprintf _snprintf_s - #endif - #endif -#endif - -namespace json11 { - -enum JsonParse { - STANDARD, COMMENTS -}; - -class JsonValue; - -class Json final { -public: - // Types - enum Type { - NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT - }; - - // Array and object typedefs - typedef std::vector array; - typedef std::map object; - - // Constructors for the various types of JSON value. - Json() noexcept; // NUL - Json(std::nullptr_t) noexcept; // NUL - Json(double value); // NUMBER - Json(int value); // NUMBER - Json(bool value); // BOOL - Json(const std::string &value); // STRING - Json(std::string &&value); // STRING - Json(const char * value); // STRING - Json(const array &values); // ARRAY - Json(array &&values); // ARRAY - Json(const object &values); // OBJECT - Json(object &&values); // OBJECT - - // Implicit constructor: anything with a to_json() function. - template - Json(const T & t) : Json(t.to_json()) {} - - // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) - template ::value - && std::is_constructible::value, - int>::type = 0> - Json(const M & m) : Json(object(m.begin(), m.end())) {} - - // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) - template ::value, - int>::type = 0> - Json(const V & v) : Json(array(v.begin(), v.end())) {} - - // This prevents Json(some_pointer) from accidentally producing a bool. Use - // Json(bool(some_pointer)) if that behavior is desired. - Json(void *) = delete; - - // Accessors - Type type() const; - - bool is_null() const { return type() == NUL; } - bool is_number() const { return type() == NUMBER; } - bool is_bool() const { return type() == BOOL; } - bool is_string() const { return type() == STRING; } - bool is_array() const { return type() == ARRAY; } - bool is_object() const { return type() == OBJECT; } - - // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not - // distinguish between integer and non-integer numbers - number_value() and int_value() - // can both be applied to a NUMBER-typed object. - double number_value() const; - int int_value() const; - - // Return the enclosed value if this is a boolean, false otherwise. - bool bool_value() const; - // Return the enclosed string if this is a string, "" otherwise. - const std::string &string_value() const; - // Return the enclosed std::vector if this is an array, or an empty vector otherwise. - const array &array_items() const; - // Return the enclosed std::map if this is an object, or an empty map otherwise. - const object &object_items() const; - - // Return a reference to arr[i] if this is an array, Json() otherwise. - const Json & operator[](size_t i) const; - // Return a reference to obj[key] if this is an object, Json() otherwise. - const Json & operator[](const std::string &key) const; - - // Serialize. - void dump(std::string &out) const; - std::string dump() const { - std::string out; - dump(out); - return out; - } - - // Parse. If parse fails, return Json() and assign an error message to err. - static Json parse(const std::string & in, - std::string & err, - JsonParse strategy = JsonParse::STANDARD); - static Json parse(const char * in, - std::string & err, - JsonParse strategy = JsonParse::STANDARD) { - if (in) { - return parse(std::string(in), err, strategy); - } else { - err = "null input"; - return nullptr; - } - } - // Parse multiple objects, concatenated or separated by whitespace - static std::vector parse_multi( - const std::string & in, - std::string::size_type & parser_stop_pos, - std::string & err, - JsonParse strategy = JsonParse::STANDARD); - - static inline std::vector parse_multi( - const std::string & in, - std::string & err, - JsonParse strategy = JsonParse::STANDARD) { - std::string::size_type parser_stop_pos; - return parse_multi(in, parser_stop_pos, err, strategy); - } - - bool operator== (const Json &rhs) const; - bool operator< (const Json &rhs) const; - bool operator!= (const Json &rhs) const { return !(*this == rhs); } - bool operator<= (const Json &rhs) const { return !(rhs < *this); } - bool operator> (const Json &rhs) const { return (rhs < *this); } - bool operator>= (const Json &rhs) const { return !(*this < rhs); } - - /* has_shape(types, err) - * - * Return true if this is a JSON object and, for each item in types, has a field of - * the given type. If not, return false and set err to a descriptive message. - */ - typedef std::initializer_list> shape; - bool has_shape(const shape & types, std::string & err) const; - -private: - std::shared_ptr m_ptr; -}; - -// Internal class hierarchy - JsonValue objects are not exposed to users of this API. -class JsonValue { -protected: - friend class Json; - friend class JsonInt; - friend class JsonDouble; - virtual Json::Type type() const = 0; - virtual bool equals(const JsonValue * other) const = 0; - virtual bool less(const JsonValue * other) const = 0; - virtual void dump(std::string &out) const = 0; - virtual double number_value() const; - virtual int int_value() const; - virtual bool bool_value() const; - virtual const std::string &string_value() const; - virtual const Json::array &array_items() const; - virtual const Json &operator[](size_t i) const; - virtual const Json::object &object_items() const; - virtual const Json &operator[](const std::string &key) const; - virtual ~JsonValue() {} -}; - -} // namespace json11 diff --git a/third_party/nlohmann/json.hpp b/third_party/nlohmann/json.hpp new file mode 100644 index 0000000000..74986f9249 --- /dev/null +++ b/third_party/nlohmann/json.hpp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bea4c8066ef4a1c206b2be5a36302f8926f7fdc6087af5d20b417d0cf103ea6 +size 919975 From 88757c12b3371a1ba5b3ebabc9e85faad0a19d56 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 22 Jan 2024 22:41:59 -0800 Subject: [PATCH 543/724] Revert "Switch from json11 to nlohmann/json (#31093)" This reverts commit ee036482b8152f0e7650895c6f4bac5d4f00e00f. --- .gitattributes | 1 - SConstruct | 4 +- common/SConscript | 6 +- common/swaglog.cc | 16 +- common/tests/test_swaglog.cc | 25 +- release/files_common | 3 +- selfdrive/modeld/thneed/serialize.cc | 79 +-- selfdrive/modeld/thneed/thneed.h | 4 + selfdrive/navd/SConscript | 2 +- selfdrive/ui/SConscript | 4 +- system/logcatd/SConscript | 2 +- system/logcatd/logcatd_systemd.cc | 4 +- system/proclogd/SConscript | 2 +- third_party/SConscript | 3 + third_party/json11/json11.cpp | 784 +++++++++++++++++++++++++++ third_party/json11/json11.hpp | 232 ++++++++ third_party/nlohmann/json.hpp | 3 - 17 files changed, 1098 insertions(+), 76 deletions(-) create mode 100644 third_party/json11/json11.cpp create mode 100644 third_party/json11/json11.hpp delete mode 100644 third_party/nlohmann/json.hpp diff --git a/.gitattributes b/.gitattributes index 51f4625fb6..8781a7371f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -22,4 +22,3 @@ third_party/acados/*/t_renderer filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lrelease filter=lfs diff=lfs merge=lfs -text third_party/qt5/larch64/bin/lupdate filter=lfs diff=lfs merge=lfs -text third_party/catch2/include/catch2/catch.hpp filter=lfs diff=lfs merge=lfs -text -third_party/nlohmann/json.hpp filter=lfs diff=lfs merge=lfs -text diff --git a/SConstruct b/SConstruct index 5fa3c6f4fc..3faa978087 100644 --- a/SConstruct +++ b/SConstruct @@ -205,7 +205,7 @@ env = Environment( "#third_party/acados/include/hpipm/include", "#third_party/catch2/include", "#third_party/libyuv/include", - "#third_party/nlohmann", + "#third_party/json11", "#third_party/linux/include", "#third_party/snpe/include", "#third_party/mapbox-gl-native-qt/include", @@ -351,7 +351,7 @@ Export('env', 'qt_env', 'arch', 'real_arch') SConscript(['common/SConscript']) Import('_common', '_gpucommon') -common = [_common] +common = [_common, 'json11'] gpucommon = [_gpucommon] Export('common', 'gpucommon') diff --git a/common/SConscript b/common/SConscript index a8300a90e7..829db6eeec 100644 --- a/common/SConscript +++ b/common/SConscript @@ -12,7 +12,7 @@ common_libs = [ if arch != "Darwin": common_libs.append('gpio.cc') -_common = env.Library('common', common_libs) +_common = env.Library('common', common_libs, LIBS="json11") files = [ 'clutil.cc', @@ -24,10 +24,10 @@ Export('_common', '_gpucommon') if GetOption('extras'): env.Program('tests/test_common', ['tests/test_runner.cc', 'tests/test_params.cc', 'tests/test_util.cc', 'tests/test_swaglog.cc'], - LIBS=[_common, 'zmq', 'pthread']) + LIBS=[_common, 'json11', 'zmq', 'pthread']) # Cython bindings -params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq']) +params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) SConscript([ 'transformations/SConscript', diff --git a/common/swaglog.cc b/common/swaglog.cc index 270e866565..873836b725 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -11,7 +11,7 @@ #include #include -#include "nlohmann/json.hpp" +#include "third_party/json11/json11.hpp" #include "common/version.h" #include "system/hardware/hw.h" @@ -37,7 +37,7 @@ public: } } - ctx_j = nlohmann::json(); + ctx_j = json11::Json::object{}; if (char* dongle_id = getenv("DONGLE_ID")) { ctx_j["dongle_id"] = dongle_id; } @@ -66,17 +66,17 @@ public: void* zctx = nullptr; void* sock = nullptr; int print_level; - nlohmann::json ctx_j; + json11::Json::object ctx_j; }; bool LOG_TIMESTAMPS = getenv("LOG_TIMESTAMPS"); uint32_t NO_FRAME_ID = std::numeric_limits::max(); static void cloudlog_common(int levelnum, const char* filename, int lineno, const char* func, - char* msg_buf, const nlohmann::json &msg_j={}) { + char* msg_buf, const json11::Json::object &msg_j={}) { static SwaglogState s; - nlohmann::json log_j = nlohmann::json { + json11::Json::object log_j = json11::Json::object { {"ctx", s.ctx_j}, {"levelnum", levelnum}, {"filename", filename}, @@ -92,7 +92,7 @@ static void cloudlog_common(int levelnum, const char* filename, int lineno, cons std::string log_s; log_s += (char)levelnum; - log_s += log_j.dump(); + ((json11::Json)log_j).dump(log_s); s.log(levelnum, filename, lineno, func, msg_buf, log_s); free(msg_buf); @@ -115,14 +115,14 @@ void cloudlog_t_common(int levelnum, const char* filename, int lineno, const cha char* msg_buf = nullptr; int ret = vasprintf(&msg_buf, fmt, args); if (ret <= 0 || !msg_buf) return; - nlohmann::json tspt_j = nlohmann::json{ + json11::Json::object tspt_j = json11::Json::object{ {"event", msg_buf}, {"time", std::to_string(nanos_since_boot())} }; if (frame_id < NO_FRAME_ID) { tspt_j["frame_id"] = std::to_string(frame_id); } - tspt_j = nlohmann::json{{"timestamp", tspt_j}}; + tspt_j = json11::Json::object{{"timestamp", tspt_j}}; cloudlog_common(levelnum, filename, lineno, func, msg_buf, tspt_j); } diff --git a/common/tests/test_swaglog.cc b/common/tests/test_swaglog.cc index be5ce51c2d..09bc4c3795 100644 --- a/common/tests/test_swaglog.cc +++ b/common/tests/test_swaglog.cc @@ -7,7 +7,7 @@ #include "common/util.h" #include "common/version.h" #include "system/hardware/hw.h" -#include "nlohmann/json.hpp" +#include "third_party/json11/json11.hpp" std::string daemon_name = "testy"; std::string dongle_id = "test_dongle_id"; @@ -39,25 +39,26 @@ void recv_log(int thread_cnt, int thread_msg_cnt) { REQUIRE(buf[0] == CLOUDLOG_DEBUG); std::string err; - nlohmann::json msg = nlohmann::json::parse(buf + 1); + auto msg = json11::Json::parse(buf + 1, err); + REQUIRE(!msg.is_null()); - REQUIRE(msg["levelnum"].template get() == CLOUDLOG_DEBUG); - REQUIRE_THAT(msg["filename"].template get(), Catch::Contains("test_swaglog.cc")); - REQUIRE(msg["funcname"].template get() == "log_thread"); - REQUIRE(msg["lineno"].template get() == LINE_NO); + REQUIRE(msg["levelnum"].int_value() == CLOUDLOG_DEBUG); + REQUIRE_THAT(msg["filename"].string_value(), Catch::Contains("test_swaglog.cc")); + REQUIRE(msg["funcname"].string_value() == "log_thread"); + REQUIRE(msg["lineno"].int_value() == LINE_NO); auto ctx = msg["ctx"]; - REQUIRE(ctx["daemon"].template get() == daemon_name); - REQUIRE(ctx["dongle_id"].template get() == dongle_id); - REQUIRE(ctx["dirty"].template get() == true); + REQUIRE(ctx["daemon"].string_value() == daemon_name); + REQUIRE(ctx["dongle_id"].string_value() == dongle_id); + REQUIRE(ctx["dirty"].bool_value() == true); - REQUIRE(ctx["version"].template get() == COMMA_VERSION); + REQUIRE(ctx["version"].string_value() == COMMA_VERSION); std::string device = Hardware::get_name(); - REQUIRE(ctx["device"].template get() == device); + REQUIRE(ctx["device"].string_value() == device); - int thread_id = atoi(msg["msg"].template get().c_str()); + int thread_id = atoi(msg["msg"].string_value().c_str()); REQUIRE((thread_id >= 0 && thread_id < thread_cnt)); thread_msgs[thread_id]++; total_count++; diff --git a/release/files_common b/release/files_common index 342d5ff489..cf985f682d 100644 --- a/release/files_common +++ b/release/files_common @@ -374,7 +374,8 @@ third_party/SConscript third_party/linux/** third_party/opencl/** -third_party/nlohmann/json.hpp +third_party/json11/json11.cpp +third_party/json11/json11.hpp third_party/qrcode/*.cc third_party/qrcode/*.hpp diff --git a/selfdrive/modeld/thneed/serialize.cc b/selfdrive/modeld/thneed/serialize.cc index 56e0907ecc..6ed5c08e81 100644 --- a/selfdrive/modeld/thneed/serialize.cc +++ b/selfdrive/modeld/thneed/serialize.cc @@ -1,11 +1,11 @@ #include #include -#include "nlohmann/json.hpp" +#include "third_party/json11/json11.hpp" #include "common/util.h" #include "common/clutil.h" #include "selfdrive/modeld/thneed/thneed.h" -using namespace nlohmann; +using namespace json11; extern map g_program_source; @@ -14,24 +14,25 @@ void Thneed::load(const char *filename) { string buf = util::read_file(filename); int jsz = *(int *)buf.data(); + string jsonerr; string jj(buf.data() + sizeof(int), jsz); - json jdat = json::parse(jj); + Json jdat = Json::parse(jj, jsonerr); map real_mem; real_mem[NULL] = NULL; int ptr = sizeof(int)+jsz; - for (auto &obj : jdat["objects"]) { - auto mobj = obj; - int sz = mobj["size"].template get(); + for (auto &obj : jdat["objects"].array_items()) { + auto mobj = obj.object_items(); + int sz = mobj["size"].int_value(); cl_mem clbuf = NULL; - if (mobj["buffer_id"].template get().size() > 0) { + if (mobj["buffer_id"].string_value().size() > 0) { // image buffer must already be allocated - clbuf = real_mem[*(cl_mem*)(mobj["buffer_id"].template get().data())]; - assert(mobj["needs_load"].template get() == false); + clbuf = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; + assert(mobj["needs_load"].bool_value() == false); } else { - if (mobj["needs_load"].template get()) { + if (mobj["needs_load"].bool_value()) { clbuf = clCreateBuffer(context, CL_MEM_COPY_HOST_PTR | CL_MEM_READ_WRITE, sz, &buf[ptr], NULL); if (debug >= 1) printf("loading %p %d @ 0x%X\n", clbuf, sz, ptr); ptr += sz; @@ -47,9 +48,9 @@ void Thneed::load(const char *filename) { if (mobj["arg_type"] == "image2d_t" || mobj["arg_type"] == "image1d_t") { cl_image_desc desc = {0}; desc.image_type = (mobj["arg_type"] == "image2d_t") ? CL_MEM_OBJECT_IMAGE2D : CL_MEM_OBJECT_IMAGE1D_BUFFER; - desc.image_width = mobj["width"].template get(); - desc.image_height = mobj["height"].template get(); - desc.image_row_pitch = mobj["row_pitch"].template get(); + desc.image_width = mobj["width"].int_value(); + desc.image_height = mobj["height"].int_value(); + desc.image_row_pitch = mobj["row_pitch"].int_value(); assert(sz == desc.image_height*desc.image_row_pitch); #ifdef QCOM2 desc.buffer = clbuf; @@ -59,12 +60,12 @@ void Thneed::load(const char *filename) { #endif cl_image_format format = {0}; format.image_channel_order = CL_RGBA; - format.image_channel_data_type = mobj["float32"].template get() ? CL_FLOAT : CL_HALF_FLOAT; + format.image_channel_data_type = mobj["float32"].bool_value() ? CL_FLOAT : CL_HALF_FLOAT; cl_int errcode; #ifndef QCOM2 - if (mobj["needs_load"].template get()) { + if (mobj["needs_load"].bool_value()) { clbuf = clCreateImage(context, CL_MEM_COPY_HOST_PTR | CL_MEM_READ_WRITE, &format, &desc, &buf[ptr-sz], &errcode); } else { clbuf = clCreateImage(context, CL_MEM_READ_WRITE, &format, &desc, NULL, &errcode); @@ -79,22 +80,22 @@ void Thneed::load(const char *filename) { assert(clbuf != NULL); } - real_mem[*(cl_mem*)(mobj["id"].template get().data())] = clbuf; + real_mem[*(cl_mem*)(mobj["id"].string_value().data())] = clbuf; } map g_programs; - for (const auto &[name, source] : jdat["programs"].items()) { - if (debug >= 1) printf("building %s with size %zu\n", name.c_str(), source.template get().size()); - g_programs[name] = cl_program_from_source(context, device_id, source.template get()); + for (const auto &[name, source] : jdat["programs"].object_items()) { + if (debug >= 1) printf("building %s with size %zu\n", name.c_str(), source.string_value().size()); + g_programs[name] = cl_program_from_source(context, device_id, source.string_value()); } - for (auto &obj : jdat["inputs"]) { - auto mobj = obj; - int sz = mobj["size"].template get(); - cl_mem aa = real_mem[*(cl_mem*)(mobj["buffer_id"].template get().data())]; + for (auto &obj : jdat["inputs"].array_items()) { + auto mobj = obj.object_items(); + int sz = mobj["size"].int_value(); + cl_mem aa = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; input_clmem.push_back(aa); input_sizes.push_back(sz); - printf("Thneed::load: adding input %s with size %d\n", mobj["name"].template get().data(), sz); + printf("Thneed::load: adding input %s with size %d\n", mobj["name"].string_value().data(), sz); cl_int cl_err; void *ret = clEnqueueMapBuffer(command_queue, aa, CL_TRUE, CL_MAP_WRITE, 0, sz, 0, NULL, NULL, &cl_err); @@ -103,39 +104,39 @@ void Thneed::load(const char *filename) { inputs.push_back(ret); } - for (auto &obj : jdat["outputs"]) { - auto mobj = obj; - int sz = mobj["size"].template get(); + for (auto &obj : jdat["outputs"].array_items()) { + auto mobj = obj.object_items(); + int sz = mobj["size"].int_value(); printf("Thneed::save: adding output with size %d\n", sz); // TODO: support multiple outputs - output = real_mem[*(cl_mem*)(mobj["buffer_id"].template get().data())]; + output = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; assert(output != NULL); } - for (auto &obj : jdat["binaries"]) { - string name = obj["name"].template get(); - size_t length = obj["length"].template get(); + for (auto &obj : jdat["binaries"].array_items()) { + string name = obj["name"].string_value(); + size_t length = obj["length"].int_value(); if (debug >= 1) printf("binary %s with size %zu\n", name.c_str(), length); g_programs[name] = cl_program_from_binary(context, device_id, (const uint8_t*)&buf[ptr], length); ptr += length; } - for (auto &obj : jdat["kernels"]) { + for (auto &obj : jdat["kernels"].array_items()) { auto gws = obj["global_work_size"]; auto lws = obj["local_work_size"]; auto kk = shared_ptr(new CLQueuedKernel(this)); - kk->name = obj["name"].template get(); + kk->name = obj["name"].string_value(); kk->program = g_programs[kk->name]; - kk->work_dim = obj["work_dim"].template get(); + kk->work_dim = obj["work_dim"].int_value(); for (int i = 0; i < kk->work_dim; i++) { - kk->global_work_size[i] = gws[i].template get(); - kk->local_work_size[i] = lws[i].template get(); + kk->global_work_size[i] = gws[i].int_value(); + kk->local_work_size[i] = lws[i].int_value(); } - kk->num_args = obj["num_args"].template get(); + kk->num_args = obj["num_args"].int_value(); for (int i = 0; i < kk->num_args; i++) { - string arg = obj["args"][i].template get(); - int arg_size = obj["args_size"][i].template get(); + string arg = obj["args"].array_items()[i].string_value(); + int arg_size = obj["args_size"].array_items()[i].int_value(); kk->args_size.push_back(arg_size); if (arg_size == 8) { cl_mem val = *(cl_mem*)(arg.data()); diff --git a/selfdrive/modeld/thneed/thneed.h b/selfdrive/modeld/thneed/thneed.h index c5436a3847..47e18e0be3 100644 --- a/selfdrive/modeld/thneed/thneed.h +++ b/selfdrive/modeld/thneed/thneed.h @@ -18,6 +18,9 @@ using namespace std; cl_int thneed_clSetKernelArg(cl_kernel kernel, cl_uint arg_index, size_t arg_size, const void *arg_value); +namespace json11 { + class Json; +} class Thneed; class GPUMalloc { @@ -49,6 +52,7 @@ class CLQueuedKernel { vector args; vector args_size; cl_kernel kernel = NULL; + json11::Json to_json() const; cl_uint work_dim; size_t global_work_size[3] = {0}; diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript index dfa6941c7f..c116ef1535 100644 --- a/selfdrive/navd/SConscript +++ b/selfdrive/navd/SConscript @@ -2,7 +2,7 @@ Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'transfor map_env = qt_env.Clone() libs = ['qt_widgets', 'qt_util', 'qmapboxgl', common, messaging, cereal, visionipc, transformations, - 'zmq', 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + map_env["LIBS"] + 'zmq', 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread', 'json11'] + map_env["LIBS"] if arch == 'larch64': libs.append(':libEGL_mesa.so.0') diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 53d9cdcc42..a3cba124fe 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -91,7 +91,7 @@ if GetOption('extras') and arch != "Darwin": # setup and factory resetter qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs) qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc", asset_obj], - LIBS=qt_libs + ['curl', 'common']) + LIBS=qt_libs + ['curl', 'common', 'json11']) # build updater UI qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) @@ -126,4 +126,4 @@ if GetOption('extras') and arch != "Darwin": # build watch3 if arch in ['x86_64', 'aarch64', 'Darwin'] or GetOption('extras'): - qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'zmq', 'visionipc', 'messaging']) + qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'zmq', 'visionipc', 'messaging']) diff --git a/system/logcatd/SConscript b/system/logcatd/SConscript index aaba60270a..6bd7c6ff3e 100644 --- a/system/logcatd/SConscript +++ b/system/logcatd/SConscript @@ -1,3 +1,3 @@ Import('env', 'cereal', 'messaging', 'common') -env.Program('logcatd', 'logcatd_systemd.cc', LIBS=[cereal, messaging, common, 'zmq', 'capnp', 'kj', 'systemd']) +env.Program('logcatd', 'logcatd_systemd.cc', LIBS=[cereal, messaging, common, 'zmq', 'capnp', 'kj', 'systemd', 'json11']) diff --git a/system/logcatd/logcatd_systemd.cc b/system/logcatd/logcatd_systemd.cc index 4f98ac0d67..54b3782132 100644 --- a/system/logcatd/logcatd_systemd.cc +++ b/system/logcatd/logcatd_systemd.cc @@ -5,7 +5,7 @@ #include #include -#include "nlohmann/json.hpp" +#include "third_party/json11/json11.hpp" #include "cereal/messaging/messaging.h" #include "common/timing.h" @@ -62,7 +62,7 @@ int main(int argc, char *argv[]) { // Build message auto androidEntry = msg.initEvent().initAndroidLog(); androidEntry.setTs(timestamp); - androidEntry.setMessage(nlohmann::json(kv).dump()); + androidEntry.setMessage(json11::Json(kv).dump()); if (kv.count("_PID")) androidEntry.setPid(std::atoi(kv["_PID"].c_str())); if (kv.count("PRIORITY")) androidEntry.setPriority(std::atoi(kv["PRIORITY"].c_str())); if (kv.count("SYSLOG_IDENTIFIER")) androidEntry.setTag(kv["SYSLOG_IDENTIFIER"]); diff --git a/system/proclogd/SConscript b/system/proclogd/SConscript index e662991a48..1f4b767011 100644 --- a/system/proclogd/SConscript +++ b/system/proclogd/SConscript @@ -1,5 +1,5 @@ Import('env', 'cereal', 'messaging', 'common') -libs = [cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj', 'common', 'zmq'] +libs = [cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj', 'common', 'zmq', 'json11'] env.Program('proclogd', ['main.cc', 'proclog.cc'], LIBS=libs) if GetOption('extras'): diff --git a/third_party/SConscript b/third_party/SConscript index 14def0611c..e5bbfaa07a 100644 --- a/third_party/SConscript +++ b/third_party/SConscript @@ -1,3 +1,6 @@ Import('env') +env.Library('json11', ['json11/json11.cpp'], CCFLAGS=env['CCFLAGS'] + ['-Wno-unqualified-std-cast-call']) +env.Append(CPPPATH=[Dir('json11')]) + env.Library('kaitai', ['kaitai/kaitaistream.cpp'], CPPDEFINES=['KS_STR_ENCODING_NONE']) diff --git a/third_party/json11/json11.cpp b/third_party/json11/json11.cpp new file mode 100644 index 0000000000..bc4045f07d --- /dev/null +++ b/third_party/json11/json11.cpp @@ -0,0 +1,784 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "json11.hpp" +#include +#include +#include +#include +#include + +namespace json11 { + +static const int max_depth = 200; + +using std::string; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ +struct NullStruct { + bool operator==(NullStruct) const { return true; } + bool operator<(NullStruct) const { return false; } +}; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(NullStruct, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (size_t i = 0; i < value.length(); i++) { + const char ch = value[i]; + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1]) == 0x80 + && static_cast(value[i+2]) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { json11::dump(m_value, out); } +}; + +class JsonDouble final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(move(value)) {} +}; + +class JsonArray final : public Value { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](size_t i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(move(value)) {} +}; + +class JsonObject final : public Value { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(move(value)) {} +}; + +class JsonNull final : public Value { +public: + JsonNull() : Value({}) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr null = make_shared(); + const std::shared_ptr t = make_shared(true); + const std::shared_ptr f = make_shared(false); + const string empty_string; + const vector empty_vector; + const map empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared(value)) {} +Json::Json(int value) : m_ptr(make_shared(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared(value)) {} +Json::Json(string &&value) : m_ptr(make_shared(move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared(move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector & Json::array_items() const { return m_ptr->array_items(); } +const map & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector & JsonValue::array_items() const { return statics().empty_vector; } +const map & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (size_t) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (size_t i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + size_t i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(move(msg), Json()); + } + + template + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input after start of comment", false); + if (str[i] == '/') { // inline comment + i++; + // advance until next line, or end of input + while (i < str.size() && str[i] != '\n') { + i++; + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + if (failed) return; + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (failed) return (char)0; + if (i == str.size()) + return fail("unexpected end of input", (char)0); + + return str[i++]; + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast(pt); + } else if (pt < 0x800) { + out += static_cast((pt >> 6) | 0xC0); + out += static_cast((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast((pt >> 12) | 0xE0); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } else { + out += static_cast((pt >> 18) | 0xF0); + out += static_cast(((pt >> 12) & 0x3F) | 0x80); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++]; + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail("unescaped " + esc(ch) + " in string", ""); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++]; + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.substr(i, 4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail("bad \\u escape: " + esc, ""); + } + for (size_t j = 0; j < 4; j++) { + if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') + && !in_range(esc[j], '0', '9')) + return fail("bad \\u escape: " + esc, ""); + } + + long codepoint = strtol(esc.data(), nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail("invalid escape character " + esc(ch), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + size_t start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i], '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i], '1', '9')) { + i++; + while (in_range(str[i], '0', '9')) + i++; + } else { + return fail("invalid " + esc(str[i]) + " in number"); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) { + return std::atoi(str.c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i], '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i], '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i], '0', '9')) + i++; + } + + return std::strtod(str.c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str.compare(i, expected.length(), expected) == 0) { + i += expected.length(); + return res; + } else { + return fail("parse error: expected " + expected + ", got " + str.substr(i, expected.length())); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail("expected '\"' in object, got " + esc(ch)); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail("expected ':' in object, got " + esc(ch)); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail("expected ',' in object, got " + esc(ch)); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail("expected ',' in list, got " + esc(ch)); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail("expected value, got " + esc(ch)); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.failed) + return Json(); + if (parser.i != in.size()) + return parser.fail("unexpected trailing " + esc(in[parser.i])); + + return result; +} + +// Documented in json11.hpp +vector Json::parse_multi(const string &in, + std::string::size_type &parser_stop_pos, + string &err, + JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + parser_stop_pos = 0; + vector json_vec; + while (parser.i != in.size() && !parser.failed) { + json_vec.push_back(parser.parse_json(0)); + if (parser.failed) + break; + + // Check for another object + parser.consume_garbage(); + if (parser.failed) + break; + parser_stop_pos = parser.i; + } + return json_vec; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & err) const { + if (!is_object()) { + err = "expected JSON object, got " + dump(); + return false; + } + + for (auto & item : types) { + if ((*this)[item.first].type() != item.second) { + err = "bad type for " + item.first + " in " + dump(); + return false; + } + } + + return true; +} + +} // namespace json11 diff --git a/third_party/json11/json11.hpp b/third_party/json11/json11.hpp new file mode 100644 index 0000000000..5202ef9323 --- /dev/null +++ b/third_party/json11/json11.hpp @@ -0,0 +1,232 @@ +/* json11 + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace json11 { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector array; + typedef std::map object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const std::string &value); // STRING + Json(std::string &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template ::value + && std::is_constructible::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template ::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that json11 does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const std::string &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](size_t i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const std::string &key) const; + + // Serialize. + void dump(std::string &out) const; + std::string dump() const { + std::string out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(std::string(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + // Parse multiple objects, concatenated or separated by whitespace + static std::vector parse_multi( + const std::string & in, + std::string::size_type & parser_stop_pos, + std::string & err, + JsonParse strategy = JsonParse::STANDARD); + + static inline std::vector parse_multi( + const std::string & in, + std::string & err, + JsonParse strategy = JsonParse::STANDARD) { + std::string::size_type parser_stop_pos; + return parse_multi(in, parser_stop_pos, err, strategy); + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + bool operator> (const Json &rhs) const { return (rhs < *this); } + bool operator>= (const Json &rhs) const { return !(*this < rhs); } + + /* has_shape(types, err) + * + * Return true if this is a JSON object and, for each item in types, has a field of + * the given type. If not, return false and set err to a descriptive message. + */ + typedef std::initializer_list> shape; + bool has_shape(const shape & types, std::string & err) const; + +private: + std::shared_ptr m_ptr; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(std::string &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const std::string &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](size_t i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const std::string &key) const; + virtual ~JsonValue() {} +}; + +} // namespace json11 diff --git a/third_party/nlohmann/json.hpp b/third_party/nlohmann/json.hpp deleted file mode 100644 index 74986f9249..0000000000 --- a/third_party/nlohmann/json.hpp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9bea4c8066ef4a1c206b2be5a36302f8926f7fdc6087af5d20b417d0cf103ea6 -size 919975 From 403610eed968823aff507d44e4945be774c6fb37 Mon Sep 17 00:00:00 2001 From: Hoang Bui <47828508+bongbui321@users.noreply.github.com> Date: Tue, 23 Jan 2024 02:10:17 -0500 Subject: [PATCH 544/724] CI: enable virtual audio sink for soundd (#31120) * enable virtual sound * mv install to dockerfile * cleaner --------- Co-authored-by: Justin Newberry --- Dockerfile.openpilot_base | 2 +- selfdrive/test/setup_vsound.sh | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100755 selfdrive/test/setup_vsound.sh diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 028d877a93..5d3d26c981 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -4,7 +4,7 @@ ENV PYTHONUNBUFFERED 1 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && \ - apt-get install -y --no-install-recommends sudo tzdata locales ssh && \ + apt-get install -y --no-install-recommends sudo tzdata locales ssh pulseaudio && \ rm -rf /var/lib/apt/lists/* RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen diff --git a/selfdrive/test/setup_vsound.sh b/selfdrive/test/setup_vsound.sh new file mode 100755 index 0000000000..a6601d0a61 --- /dev/null +++ b/selfdrive/test/setup_vsound.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +{ + #start pulseaudio daemon + sudo pulseaudio -D + + # create a virtual null audio and set it to default device + sudo pactl load-module module-null-sink sink_name=virtual_audio + sudo pactl set-default-sink virtual_audio +} > /dev/null 2>&1 From ea57336005f4cf9c587dfbe221662b4de73bd508 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 23 Jan 2024 02:19:32 -0800 Subject: [PATCH 545/724] Fingerprints: automatically add missing FW versions from users [bot] (#31116) Export fingerprints --- selfdrive/car/chrysler/fingerprints.py | 5 +++++ selfdrive/car/honda/fingerprints.py | 3 +++ selfdrive/car/hyundai/fingerprints.py | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index aeacc4cfcd..1a04e79c7d 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -109,6 +109,7 @@ FW_VERSIONS = { b'68453505AC', b'68453505AD', b'68453511AC', + b'68453513AC', b'68453513AD', b'68453514AD', b'68510280AG', @@ -185,6 +186,7 @@ FW_VERSIONS = { b'68585112AB', ], (Ecu.engine, 0x7e0, None): [ + b'05035699AG ', b'05036065AE ', b'05036066AE ', b'05149591AD ', @@ -198,6 +200,7 @@ FW_VERSIONS = { b'68378748AL ', b'68378758AM ', b'68448163AJ', + b'68448163AL', b'68448165AK', b'68455119AC ', b'68455145AC ', @@ -209,6 +212,7 @@ FW_VERSIONS = { b'68502722AC ', b'68502734AF ', b'68502740AF ', + b'68502741AF ', b'68502742AC ', b'68539650AD', b'68539650AF', @@ -216,6 +220,7 @@ FW_VERSIONS = { b'68586105AB ', ], (Ecu.transmission, 0x7e1, None): [ + b'05035706AD', b'05036069AA', b'05149536AC', b'68360078AL', diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index e3e95483a0..0d174f91fb 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -38,6 +38,7 @@ FW_VERSIONS = { b'37805-6B2-A820\x00\x00', b'37805-6B2-A920\x00\x00', b'37805-6B2-C520\x00\x00', + b'37805-6B2-C540\x00\x00', b'37805-6B2-M520\x00\x00', b'37805-6B2-Y810\x00\x00', b'37805-6M4-B730\x00\x00', @@ -946,6 +947,7 @@ FW_VERSIONS = { b'37805-RLV-B220\x00\x00', b'37805-RLV-B420\x00\x00', b'37805-RLV-B430\x00\x00', + b'37805-RLV-B710\x00\x00', b'37805-RLV-B720\x00\x00', b'37805-RLV-C430\x00\x00', b'37805-RLV-C510\x00\x00', @@ -957,6 +959,7 @@ FW_VERSIONS = { b'37805-RLV-L160\x00\x00', b'37805-RLV-L180\x00\x00', b'37805-RLV-L410\x00\x00', + b'37805-RLV-L430\x00\x00', b'37805-RLV-L850\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 3c5d4b9a51..0c5925c744 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -1543,12 +1543,12 @@ FW_VERSIONS = { b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007', b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI020 230719', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.06 99211-GI010 230110', - b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI020 230719', ], }, CAR.IONIQ_6: { From 35e1e8ecd2e2a8a458467ecb3ec18730610da18a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 11:40:22 -0800 Subject: [PATCH 546/724] SegmentRange: __str__ get preferred format (#31126) get canonical --- tools/lib/route.py | 3 +++ tools/lib/tests/test_logreader.py | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/tools/lib/route.py b/tools/lib/route.py index d3df93ccda..09eb7f5426 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -255,3 +255,6 @@ class SegmentRange: @property def selector(self): return self.m.group("selector") + + def __str__(self): + return f"{self.dongle_id}/{self.timestamp}" + (f"/{self._slice}" if self._slice else "") + (f"/{self.selector}" if self.selector else "") diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index 74ee82d152..6ec3edc9ac 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -46,6 +46,18 @@ class TestLogReader(unittest.TestCase): segs = parse_slice(sr, route) self.assertListEqual(list(segs), expected) + @parameterized.expand([ + (f"{TEST_ROUTE}", f"{TEST_ROUTE}"), + (f"{TEST_ROUTE.replace('/', '|')}", f"{TEST_ROUTE}"), + (f"{TEST_ROUTE}--5", f"{TEST_ROUTE}/5"), + (f"{TEST_ROUTE}/0/q", f"{TEST_ROUTE}/0/q"), + (f"{TEST_ROUTE}/5:6/r", f"{TEST_ROUTE}/5:6/r"), + (f"{TEST_ROUTE}/5", f"{TEST_ROUTE}/5"), + ]) + def test_canonical_name(self, identifier, expected): + sr = SegmentRange(identifier) + self.assertEqual(str(sr), expected) + def test_direct_parsing(self): qlog = tempfile.NamedTemporaryFile(mode='wb', delete=False) From 65490bb3c55fa81d5144eba2c09f054b73179cc4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 12:53:19 -0800 Subject: [PATCH 547/724] plotjuggler: auto fallback to qlogs with prompt (#31110) * qlog fallback * show a warning for that * qlog fallback * fix * wording --- tools/lib/helpers.py | 2 +- tools/lib/logreader.py | 64 +++++++++++++++++++++++++++++-------- tools/plotjuggler/juggle.py | 4 +-- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index c184efe6a2..e2c8d07dd8 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -11,7 +11,7 @@ class RE: SEGMENT_NAME = r'{}(?:--|/)(?P[0-9]+)'.format(ROUTE_NAME) INDEX = r'-?[0-9]+' SLICE = r'(?P{})?:?(?P{})?:?(?P{})?'.format(INDEX, INDEX, INDEX) - SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?/?(?P([qr]))?'.format(ROUTE_NAME, SLICE) + SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?/?(?P([qra]))?'.format(ROUTE_NAME, SLICE) BOOTLOG_NAME = ROUTE_NAME EXPLORER_FILE = r'^(?P{})--(?P[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index c1f4f0f4a9..e28f41341c 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -16,6 +16,7 @@ from typing import Iterable, Iterator, List, Type from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log +from openpilot.common.swaglog import cloudlog from openpilot.tools.lib.comma_car_segments import get_url as get_comma_segments_url from openpilot.tools.lib.openpilotci import get_url from openpilot.tools.lib.filereader import FileReader, file_exists @@ -71,8 +72,8 @@ class _LogFileReader: class ReadMode(enum.StrEnum): RLOG = "r" # only read rlogs QLOG = "q" # only read qlogs - #AUTO = "a" # default to rlogs, fallback to qlogs, not supported yet - + AUTO = "a" # default to rlogs, fallback to qlogs + AUTO_INTERACIVE = "i" # default to rlogs, fallback to qlogs with a prompt from the user def create_slice_from_string(s: str): m = re.fullmatch(RE.SLICE, s) @@ -86,31 +87,60 @@ def create_slice_from_string(s: str): return start return slice(start, end, step) +def auto_strategy(rlog_paths, qlog_paths, interactive): + # auto select logs based on availability + if any(rlog is None or not file_exists(rlog) for rlog in rlog_paths): + if interactive: + if input("Some rlogs were not found, would you like to fallback to qlogs for those segments? (y/n) ").lower() != "y": + return rlog_paths + else: + cloudlog.warning("Some rlogs were not found, falling back to qlogs for those segments...") + + return [rlog if (rlog is not None and file_exists(rlog)) else (qlog if (qlog is not None and file_exists(qlog)) else None) + for (rlog, qlog) in zip(rlog_paths, qlog_paths, strict=True)] + return rlog_paths + +def apply_strategy(mode: ReadMode, rlog_paths, qlog_paths): + if mode == ReadMode.RLOG: + return rlog_paths + elif mode == ReadMode.QLOG: + return qlog_paths + elif mode == ReadMode.AUTO: + return auto_strategy(rlog_paths, qlog_paths, False) + elif mode == ReadMode.AUTO_INTERACIVE: + return auto_strategy(rlog_paths, qlog_paths, True) + def parse_slice(sr: SegmentRange, route: Route): segs = np.arange(route.max_seg_number+1) s = create_slice_from_string(sr._slice) return segs[s] if isinstance(s, slice) else [segs[s]] -def comma_api_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): +def comma_api_source(sr: SegmentRange, route: Route, mode: ReadMode): segs = parse_slice(sr, route) - log_paths = route.log_paths() if mode == ReadMode.RLOG else route.qlog_paths() + rlog_paths = [route.log_paths()[seg] for seg in segs] + qlog_paths = [route.log_paths()[seg] for seg in segs] - invalid_segs = [seg for seg in segs if log_paths[seg] is None] + return apply_strategy(mode, rlog_paths, qlog_paths) - assert not len(invalid_segs), f"Some of the requested segments are not available: {invalid_segs}" - - return [(log_paths[seg]) for seg in segs] - -def internal_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): +def internal_source(sr: SegmentRange, route: Route, mode: ReadMode): segs = parse_slice(sr, route) - return [f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{'rlog' if mode == ReadMode.RLOG else 'qlog'}.bz2" for seg in segs] + def get_internal_url(sr: SegmentRange, seg, file): + return f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{file}.bz2" -def openpilotci_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): + rlog_paths = [get_internal_url(sr, seg, "rlog") for seg in segs] + qlog_paths = [get_internal_url(sr, seg, "qlog") for seg in segs] + + return apply_strategy(mode, rlog_paths, qlog_paths) + +def openpilotci_source(sr: SegmentRange, route: Route, mode: ReadMode): segs = parse_slice(sr, route) - return [get_url(sr.route_name, seg, 'rlog' if mode == ReadMode.RLOG else 'qlog') for seg in segs] + rlog_paths = [get_url(sr.route_name, seg, "rlog") for seg in segs] + qlog_paths = [get_url(sr.route_name, seg, "qlog") for seg in segs] + + return apply_strategy(mode, rlog_paths, qlog_paths) def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): segs = parse_slice(sr, route) @@ -120,10 +150,13 @@ def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG def direct_source(file_or_url): return [file_or_url] +def get_invalid_files(files): + return [f for f in files if f is None or not file_exists(f)] + def check_source(source, *args): try: files = source(*args) - assert all(file_exists(f) for f in files) + assert len(get_invalid_files(files)) == 0 return True, files except Exception: return False, None @@ -209,6 +242,9 @@ class LogReader: def reset(self): self.logreader_identifiers = self._parse_identifiers(self.identifier) + invalid_count = len(get_invalid_files(self.logreader_identifiers)) + assert invalid_count == 0, f"{invalid_count}/{len(self.logreader_identifiers)} invalid log(s) found, please ensure all logs \ +are uploaded or auto fallback to qlogs with '/a' selector at the end of the route name." @staticmethod def from_bytes(dat): diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 292db5a50a..cc21095414 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -13,7 +13,7 @@ from functools import partial from openpilot.common.basedir import BASEDIR from openpilot.tools.lib.helpers import save_log -from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.logreader import LogReader, ReadMode juggle_dir = os.path.dirname(os.path.realpath(__file__)) @@ -73,7 +73,7 @@ def process(can, lr): return [d for d in lr if can or d.which() not in ['can', 'sendcan']] def juggle_route(route_or_segment_name, can, layout, dbc=None): - sr = LogReader(route_or_segment_name) + sr = LogReader(route_or_segment_name, default_mode=ReadMode.AUTO_INTERACIVE) all_data = sr.run_across_segments(24, partial(process, can)) From 0234cadec3aefdca2e71c251ff6f8885a247c34a Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 12:55:50 -0800 Subject: [PATCH 548/724] LogReader: remove redirect message for internal data (#31088) fix logging --- tools/lib/url_file.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 97c0a639a7..5c6f187eee 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -1,3 +1,4 @@ +import logging import os import time import threading @@ -12,6 +13,7 @@ from openpilot.system.hardware.hw import Paths K = 1000 CHUNK_SIZE = 1000 * K +logging.getLogger("urllib3").setLevel(logging.WARNING) def hash_256(link): hsh = str(sha256((link.split("?")[0]).encode('utf-8')).hexdigest()) From ebc916e25c2136d9cadbb98672442cfbc5e84ea5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 23 Jan 2024 14:23:11 -0800 Subject: [PATCH 549/724] Export fingerprints --- selfdrive/car/chrysler/fingerprints.py | 14 ++++++++++++++ selfdrive/car/honda/fingerprints.py | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 1a04e79c7d..9ef991e8ed 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -66,6 +66,7 @@ FW_VERSIONS = { (Ecu.combinationMeter, 0x742, None): [ b'68402971AD', b'68454144AD', + b'68454152AB', ], (Ecu.srs, 0x744, None): [ b'68355363AB', @@ -87,6 +88,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'05035707AA', b'68495807AA', + b'68495807AB', ], }, CAR.RAM_1500: { @@ -98,6 +100,7 @@ FW_VERSIONS = { b'68294063AH', b'68294063AI', b'68434846AC', + b'68434849AC', b'68434858AC', b'68434859AC', b'68434860AC', @@ -112,7 +115,10 @@ FW_VERSIONS = { b'68453513AC', b'68453513AD', b'68453514AD', + b'68505633AB', + b'68510277AG', b'68510280AG', + b'68510282AG', b'68510282AH', b'68510283AG', b'68527346AE', @@ -120,6 +126,7 @@ FW_VERSIONS = { b'68527375AD', b'68527382AE', b'68527387AE', + b'68631942AA', ], (Ecu.srs, 0x744, None): [ b'68428609AB', @@ -192,15 +199,19 @@ FW_VERSIONS = { b'05149591AD ', b'05149591AE ', b'05149592AE ', + b'05149600AD ', b'05149846AA ', b'05149848AA ', + b'05190341AD', b'68378695AJ ', b'68378696AJ ', b'68378701AI ', b'68378748AL ', b'68378758AM ', b'68448163AJ', + b'68448163AK', b'68448163AL', + b'68448165AG', b'68448165AK', b'68455119AC ', b'68455145AC ', @@ -218,11 +229,13 @@ FW_VERSIONS = { b'68539650AF', b'68586101AA ', b'68586105AB ', + b'68629926AC ', ], (Ecu.transmission, 0x7e1, None): [ b'05035706AD', b'05036069AA', b'05149536AC', + b'05149537AC', b'68360078AL', b'68360080AM', b'68360081AM', @@ -240,6 +253,7 @@ FW_VERSIONS = { b'68520867AE', b'68520867AF', b'68540431AB', + b'68629936AC', ], }, CAR.RAM_HD: { diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 0d174f91fb..25fb88aa39 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -928,6 +928,8 @@ FW_VERSIONS = { (Ecu.transmission, 0x18da1ef1, None): [ b'28101-5EY-A050\x00\x00', b'28101-5EY-A100\x00\x00', + b'28101-5EY-A430\x00\x00', + b'28101-5EY-A500\x00\x00', b'28101-5EZ-A050\x00\x00', b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A100\x00\x00', @@ -947,6 +949,7 @@ FW_VERSIONS = { b'37805-RLV-B220\x00\x00', b'37805-RLV-B420\x00\x00', b'37805-RLV-B430\x00\x00', + b'37805-RLV-B620\x00\x00', b'37805-RLV-B710\x00\x00', b'37805-RLV-B720\x00\x00', b'37805-RLV-C430\x00\x00', @@ -958,6 +961,7 @@ FW_VERSIONS = { b'37805-RLV-L090\x00\x00', b'37805-RLV-L160\x00\x00', b'37805-RLV-L180\x00\x00', + b'37805-RLV-L350\x00\x00', b'37805-RLV-L410\x00\x00', b'37805-RLV-L430\x00\x00', b'37805-RLV-L850\x00\x00', @@ -987,6 +991,7 @@ FW_VERSIONS = { b'36161-TG7-D520\x00\x00', b'36161-TG7-D630\x00\x00', b'36161-TG7-Y630\x00\x00', + b'36161-TG8-A410\x00\x00', b'36161-TG8-A520\x00\x00', b'36161-TG8-A630\x00\x00', b'36161-TG8-A720\x00\x00', @@ -1031,6 +1036,7 @@ FW_VERSIONS = { b'78109-TG8-AJ10\x00\x00', b'78109-TG8-AJ20\x00\x00', b'78109-TG8-AK20\x00\x00', + b'78109-TG8-AS20\x00\x00', b'78109-TGS-AB10\x00\x00', b'78109-TGS-AC10\x00\x00', b'78109-TGS-AD10\x00\x00', From a2028cbfd55712ef23cefa31d909892592ab2e8d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 23 Jan 2024 14:29:31 -0800 Subject: [PATCH 550/724] New MY! --- docs/CARS.md | 2 +- selfdrive/car/chrysler/values.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index b27fb1b943..55098a39a3 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -181,7 +181,7 @@ A supported vehicle is one that just works when you install a comma device. All |Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ram|1500 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 055efba829..2a3bceec96 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -74,7 +74,7 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { ], CAR.JEEP_GRAND_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), CAR.JEEP_GRAND_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), - CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-23", car_parts=CarParts.common([CarHarness.ram])), + CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-24", car_parts=CarParts.common([CarHarness.ram])), CAR.RAM_HD: [ ChryslerCarInfo("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])), ChryslerCarInfo("Ram 3500 2019-22", car_parts=CarParts.common([CarHarness.ram])), From d574013d7bda94a43b29095887e176b9723d13cb Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 14:51:45 -0800 Subject: [PATCH 551/724] LogReader: don't use route for max_segment (#31129) * dont use route * fix and cleanup --- tools/lib/logreader.py | 35 +++++++++++++++++++------------ tools/lib/route.py | 13 ++++++++++++ tools/lib/tests/test_logreader.py | 5 ++--- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index e28f41341c..c29662dfbc 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -110,21 +110,31 @@ def apply_strategy(mode: ReadMode, rlog_paths, qlog_paths): elif mode == ReadMode.AUTO_INTERACIVE: return auto_strategy(rlog_paths, qlog_paths, True) -def parse_slice(sr: SegmentRange, route: Route): - segs = np.arange(route.max_seg_number+1) +def parse_slice(sr: SegmentRange): s = create_slice_from_string(sr._slice) - return segs[s] if isinstance(s, slice) else [segs[s]] + if isinstance(s, slice): + if s.stop is None or s.stop < 0 or (s.start is not None and s.start < 0): # we need the number of segments in order to parse this slice + segs = np.arange(sr.get_max_seg_number()+1) + else: + segs = np.arange(s.stop + 1) + return segs[s] + else: + if s < 0: + s = sr.get_max_seg_number() + s + 1 + return [s] -def comma_api_source(sr: SegmentRange, route: Route, mode: ReadMode): - segs = parse_slice(sr, route) +def comma_api_source(sr: SegmentRange, mode: ReadMode): + segs = parse_slice(sr) + + route = Route(sr.route_name) rlog_paths = [route.log_paths()[seg] for seg in segs] qlog_paths = [route.log_paths()[seg] for seg in segs] return apply_strategy(mode, rlog_paths, qlog_paths) -def internal_source(sr: SegmentRange, route: Route, mode: ReadMode): - segs = parse_slice(sr, route) +def internal_source(sr: SegmentRange, mode: ReadMode): + segs = parse_slice(sr) def get_internal_url(sr: SegmentRange, seg, file): return f"cd:/{sr.dongle_id}/{sr.timestamp}/{seg}/{file}.bz2" @@ -134,16 +144,16 @@ def internal_source(sr: SegmentRange, route: Route, mode: ReadMode): return apply_strategy(mode, rlog_paths, qlog_paths) -def openpilotci_source(sr: SegmentRange, route: Route, mode: ReadMode): - segs = parse_slice(sr, route) +def openpilotci_source(sr: SegmentRange, mode: ReadMode): + segs = parse_slice(sr) rlog_paths = [get_url(sr.route_name, seg, "rlog") for seg in segs] qlog_paths = [get_url(sr.route_name, seg, "qlog") for seg in segs] return apply_strategy(mode, rlog_paths, qlog_paths) -def comma_car_segments_source(sr: SegmentRange, route: Route, mode=ReadMode.RLOG): - segs = parse_slice(sr, route) +def comma_car_segments_source(sr: SegmentRange, mode=ReadMode.RLOG): + segs = parse_slice(sr) return [get_comma_segments_url(sr.route_name, seg) for seg in segs] @@ -209,11 +219,10 @@ class LogReader: return direct_source(identifier) sr = SegmentRange(parsed) - route = Route(sr.route_name) mode = self.default_mode if sr.selector is None else ReadMode(sr.selector) source = self.default_source if source is None else source - return source(sr, route, mode) + return source(sr, mode) def __init__(self, identifier: str | List[str], default_mode=ReadMode.RLOG, default_source=auto_source, sort_by_time=False, only_union_types=False): self.default_mode = default_mode diff --git a/tools/lib/route.py b/tools/lib/route.py index 09eb7f5426..f7c3c432c8 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -1,5 +1,6 @@ import os import re +from functools import cache from urllib.parse import urlparse from collections import defaultdict from itertools import chain @@ -231,11 +232,23 @@ class SegmentName: def __str__(self) -> str: return self._canonical_name +@cache +def get_max_seg_number_cached(sr: 'SegmentRange'): + try: + api = CommaApi(get_token()) + return api.get("/v1/route/" + sr.route_name.replace("/", "|"))["segment_numbers"][-1] + except Exception as e: + raise Exception("unable to get max_segment_number. ensure you have access to this route or the route is public.") from e + + class SegmentRange: def __init__(self, segment_range: str): self.m = re.fullmatch(RE.SEGMENT_RANGE, segment_range) assert self.m, f"Segment range is not valid {segment_range}" + def get_max_seg_number(self): + return get_max_seg_number_cached(self) + @property def route_name(self): return self.m.group("route_name") diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index 6ec3edc9ac..f7874a3fb3 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -6,7 +6,7 @@ import pytest from parameterized import parameterized import requests from openpilot.tools.lib.logreader import LogReader, parse_indirect, parse_slice, ReadMode -from openpilot.tools.lib.route import Route, SegmentRange +from openpilot.tools.lib.route import SegmentRange NUM_SEGS = 17 # number of segments in the test route ALL_SEGS = list(np.arange(NUM_SEGS)) @@ -42,8 +42,7 @@ class TestLogReader(unittest.TestCase): def test_indirect_parsing(self, identifier, expected): parsed, _, _ = parse_indirect(identifier) sr = SegmentRange(parsed) - route = Route(sr.route_name) - segs = parse_slice(sr, route) + segs = parse_slice(sr) self.assertListEqual(list(segs), expected) @parameterized.expand([ From f22b6681d67a0ef11d0446edfb40f50a5c1f8417 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 14:57:31 -0800 Subject: [PATCH 552/724] CI: test the car_porting example notebooks (#31131) * dont use route * fix and cleanup * test the example notebooks --- .github/workflows/tools_tests.yaml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 5cecfb7e4e..e141071d09 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -88,4 +88,22 @@ jobs: devcontainer exec --workspace-folder . scons -j$(nproc) cereal/ common/ devcontainer exec --workspace-folder . pip install pip-install-test devcontainer exec --workspace-folder . touch /home/batman/.comma/auth.json - devcontainer exec --workspace-folder . sudo touch /root/test.txt \ No newline at end of file + devcontainer exec --workspace-folder . sudo touch /root/test.txt + + notebooks: + name: notebooks + runs-on: ubuntu-20.04 + if: github.repository == 'commaai/openpilot' + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: ./.github/workflows/setup-with-retry + - name: Build openpilot + timeout-minutes: 5 + run: ${{ env.RUN }} "scons -j$(nproc)" + - name: Test notebooks + timeout-minutes: 2 + run: | + ${{ env.RUN }} "pip install nbmake && pytest --nbmake tools/car_porting/examples/" \ No newline at end of file From 780f1222adeef28cd8d3057fa94c6dc65cbee876 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 23 Jan 2024 17:21:48 -0800 Subject: [PATCH 553/724] fw_versions: add extra_fw_versions param (#31132) * add param * fix * fix --- selfdrive/car/fw_versions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 072c2ae4e4..547904ee47 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -105,11 +105,14 @@ def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude= return set() -def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True) -> Set[str]: +def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True, extra_fw_versions=None) -> Set[str]: """Do an exact FW match. Returns all cars that match the given FW versions for a list of "essential" ECUs. If an ECU is not considered essential the FW version can be missing to get a fingerprint, but if it's present it needs to match the database.""" + if extra_fw_versions is None: + extra_fw_versions = {} + invalid = set() candidates = {c: f for c, f in FW_VERSIONS.items() if is_brand(MODEL_TO_BRAND[c], match_brand)} @@ -117,6 +120,7 @@ def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True) -> Set[s for candidate, fws in candidates.items(): config = FW_QUERY_CONFIGS[MODEL_TO_BRAND[candidate]] for ecu, expected_versions in fws.items(): + expected_versions = expected_versions + extra_fw_versions.get(candidate, {}).get(ecu, []) ecu_type = ecu[0] addr = ecu[1:] From 998eb8cde2c81964c883595ee84eeab4172c99ae Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 17:41:43 -0800 Subject: [PATCH 554/724] test_fw_query_on_routes: use new LogReader (#31134) a --- selfdrive/debug/test_fw_query_on_routes.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 78f7a5d3e5..dd6243a44c 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -6,8 +6,8 @@ import argparse import os import traceback from tqdm import tqdm -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.route import Route +from openpilot.tools.lib.logreader import LogReader, ReadMode +from openpilot.tools.lib.route import SegmentRange from openpilot.selfdrive.car.car_helpers import interface_names from openpilot.selfdrive.car.fw_versions import VERSIONS, match_fw_to_car @@ -44,23 +44,14 @@ if __name__ == "__main__": dongles = [] for route in tqdm(routes): - route = route.rstrip() - dongle_id, time = route.split('|') + dongle_id = SegmentRange(route).dongle_id if dongle_id in dongles: continue - if NO_API: - qlog_path = f"cd:/{dongle_id}/{time}/0/qlog.bz2" - else: - route = Route(route) - qlog_path = next((p for p in route.qlog_paths() if p is not None), None) - - if qlog_path is None: - continue + lr = LogReader(route, default_mode=ReadMode.QLOG) try: - lr = LogReader(qlog_path) dongles.append(dongle_id) CP = None @@ -98,13 +89,11 @@ if __name__ == "__main__": if len(fuzzy_matches) == 1: if list(fuzzy_matches)[0] != live_fingerprint: wrong_fuzzy += 1 - print(f"{dongle_id}|{time}") print("Fuzzy match wrong! Fuzzy:", fuzzy_matches, "Live:", live_fingerprint) else: good_fuzzy += 1 break - print(f"{dongle_id}|{time}") print("Old style:", live_fingerprint, "Vin", CP.carVin) print("New style (exact):", exact_matches) print("New style (fuzzy):", fuzzy_matches) From 3d9b483e107e119fd5322e00494a769ef0e081bc Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 20:51:15 -0800 Subject: [PATCH 555/724] comma_api_source: fix qlogs (#31136) fix qlogs --- tools/lib/logreader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index c29662dfbc..4d557a4d91 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -129,7 +129,7 @@ def comma_api_source(sr: SegmentRange, mode: ReadMode): route = Route(sr.route_name) rlog_paths = [route.log_paths()[seg] for seg in segs] - qlog_paths = [route.log_paths()[seg] for seg in segs] + qlog_paths = [route.qlog_paths()[seg] for seg in segs] return apply_strategy(mode, rlog_paths, qlog_paths) From abf2801122a8e92e486a8ca88c946949d3149bae Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 20:59:32 -0800 Subject: [PATCH 556/724] use get_first_message for auto_fingerprint --- tools/car_porting/auto_fingerprint.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tools/car_porting/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py index fce7e33182..e14a018917 100755 --- a/tools/car_porting/auto_fingerprint.py +++ b/tools/car_porting/auto_fingerprint.py @@ -7,7 +7,7 @@ from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versio from openpilot.selfdrive.car.fw_versions import match_fw_to_car from openpilot.selfdrive.car.interfaces import get_interface_attr -from openpilot.tools.lib.logreader import LogReader, ReadMode +from openpilot.tools.lib.logreader import LogReader, ReadMode, get_first_message ALL_FW_VERSIONS = get_interface_attr("FW_VERSIONS") @@ -32,16 +32,15 @@ if __name__ == "__main__": platform: Optional[str] = None - for msg in lr: - if msg.which() == "carParams": - carFw = msg.carParams.carFw - carVin = msg.carParams.carVin - carPlatform = msg.carParams.carFingerprint - break + CP = get_first_message(lr, "carParams") - if carFw is None: + if CP is None: raise Exception("No fw versions in the provided route...") + carFw = CP.carParams.carFw + carVin = CP.carParams.carVin + carPlatform = CP.carParams.carFingerprint + if args.platform is None: # attempt to auto-determine platform with other fuzzy fingerprints _, possible_platforms = match_fw_to_car(carFw, log=False) From ac71c30ad5d8a5f303ac7a08cc73b8a4b0174498 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 21:40:04 -0800 Subject: [PATCH 557/724] auto_source: add commaCarSegments source (#31137) add source --- .../examples/ford_vin_fingerprint.ipynb | 42 +++++++++++-------- tools/lib/logreader.py | 2 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/tools/car_porting/examples/ford_vin_fingerprint.ipynb b/tools/car_porting/examples/ford_vin_fingerprint.ipynb index bc689a5aad..c4f2088b38 100644 --- a/tools/car_porting/examples/ford_vin_fingerprint.ipynb +++ b/tools/car_porting/examples/ford_vin_fingerprint.ipynb @@ -2,9 +2,17 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "kj/filesystem-disk-unix.c++:1703: warning: PWD environment variable doesn't match current directory; pwd = /mnt/c/Users/jnewb/AppData/Local/Programs/Microsoft VS Code\n" + ] + } + ], "source": [ "\"\"\"In this example, we use the public comma car segments database to check if vin fingerprinting is feasible for ford.\"\"\"\n", "\n", @@ -19,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -54,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -66,7 +74,7 @@ } ], "source": [ - "from openpilot.tools.lib.logreader import comma_car_segments_source, get_first_message\n", + "from openpilot.tools.lib.logreader import get_first_message\n", "\n", "\n", "VINS_TO_CHECK = set()\n", @@ -77,35 +85,35 @@ " continue\n", "\n", " for segment in database[platform]:\n", - " lr = LogReader(segment, default_source=comma_car_segments_source)\n", + " lr = LogReader(segment)\n", " CP = get_first_message(lr, \"carParams\").carParams\n", " VINS_TO_CHECK.add((CP.carVin, CP.carFingerprint))" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ + "vin: 3FTTW8E31PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", "vin: 1FM5K8GC7LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", + "vin: 1FM5K8GC7NGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 1FM5K7LC0MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 1FM5K8HC7MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 3FMTK3SU0MMXXXXXX real platform: FORD MUSTANG MACH-E 1ST GEN determined platform: FORD MUSTANG MACH-E 1ST GEN correct: True\n", + "vin: WF0NXXGCHNJXXXXXX real platform: FORD FOCUS 4TH GEN determined platform: mock correct: False\n", + "vin: 1FTVW1EL4NWXXXXXX real platform: FORD F-150 LIGHTNING 1ST GEN determined platform: FORD F-150 LIGHTNING 1ST GEN correct: True\n", "vin: 00000000000XXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n", "vin: 3FTTW8F98NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", - "vin: 1FTVW1EL4NWXXXXXX real platform: FORD F-150 LIGHTNING 1ST GEN determined platform: FORD F-150 LIGHTNING 1ST GEN correct: True\n", - "vin: 1FM5K7LC0MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", - "vin: WF0NXXGCHNJXXXXXX real platform: FORD FOCUS 4TH GEN determined platform: mock correct: False\n", - "vin: 1FMCU9J94MUXXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n", "vin: 5LM5J7XC9LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", - "vin: 3FMCR9B69NRXXXXXX real platform: FORD BRONCO SPORT 1ST GEN determined platform: mock correct: False\n", - "vin: 3FMTK3SU0MMXXXXXX real platform: FORD MUSTANG MACH-E 1ST GEN determined platform: FORD MUSTANG MACH-E 1ST GEN correct: True\n", - "vin: 1FM5K8HC7MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", - "vin: 1FM5K8GC7NGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", "vin: 5LM5J7XC8MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", - "vin: 3FTTW8E31PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", - "vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n" + "vin: 3FMCR9B69NRXXXXXX real platform: FORD BRONCO SPORT 1ST GEN determined platform: mock correct: False\n", + "vin: 1FMCU9J94MUXXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n" ] } ], diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 4d557a4d91..29e3acc076 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -173,7 +173,7 @@ def check_source(source, *args): def auto_source(*args): # Automatically determine viable source - for source in [internal_source, openpilotci_source]: + for source in [comma_car_segments_source, internal_source, openpilotci_source]: valid, ret = check_source(source, *args) if valid: return ret From b9ad854451ce642a05c487d96a1a9bc2181bb3ba Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 23 Jan 2024 23:12:48 -0800 Subject: [PATCH 558/724] Nissan: log FW responses on bus 0 (#31138) * nissan: log FW responses on bus 0 * update refs --- selfdrive/car/nissan/values.py | 12 ++++++++++-- selfdrive/car/tests/test_fw_fingerprint.py | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index 4ee23c32c3..c013b2056f 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -59,27 +59,35 @@ NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83' NISSAN_RX_OFFSET = 0x20 FW_QUERY_CONFIG = FwQueryConfig( - requests=[ + requests=[request for bus, logging in ((0, True), (1, False)) for request in [ Request( [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP], [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP], + bus=bus, + logging=logging, ), Request( [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP], [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP], rx_offset=NISSAN_RX_OFFSET, + bus=bus, + logging=logging, ), # Rogue's engine solely responds to this Request( [NISSAN_DIAGNOSTIC_REQUEST_KWP_2, NISSAN_VERSION_REQUEST_KWP], [NISSAN_DIAGNOSTIC_RESPONSE_KWP_2, NISSAN_VERSION_RESPONSE_KWP], + bus=bus, + logging=logging, ), Request( [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], rx_offset=NISSAN_RX_OFFSET, + bus=bus, + logging=logging, ), - ], + ]], ) DBC = { diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 36619f4cde..893c79c797 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -238,7 +238,7 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.1 + total_ref_time = 6.5 brand_ref_times = { 1: { 'body': 0.1, @@ -247,7 +247,7 @@ class TestFwFingerprintTiming(unittest.TestCase): 'honda': 0.45, 'hyundai': 0.65, 'mazda': 0.2, - 'nissan': 0.4, + 'nissan': 0.8, 'subaru': 0.45, 'tesla': 0.2, 'toyota': 1.6, From 88dcaa51c4d1fcc338d44f55134593760334ae23 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Tue, 23 Jan 2024 23:54:11 -0800 Subject: [PATCH 559/724] LogReader: no redownloading on multiple iterations (#31141) * no redownload * sort --- tools/lib/logreader.py | 21 +++++++++++++-------- tools/lib/tests/test_logreader.py | 11 +++++++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 29e3acc076..58883c6865 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -12,7 +12,7 @@ import sys import urllib.parse import warnings -from typing import Iterable, Iterator, List, Type +from typing import Dict, Iterable, Iterator, List, Type from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log @@ -232,20 +232,25 @@ class LogReader: self.sort_by_time = sort_by_time self.only_union_types = only_union_types + self.__lrs: Dict[int, _LogFileReader] = {} self.reset() - def __iter__(self): - for identifier in self.logreader_identifiers: - yield from _LogFileReader(identifier) + def _get_lr(self, i): + if i not in self.__lrs: + self.__lrs[i] = _LogFileReader(self.logreader_identifiers[i]) + return self.__lrs[i] - def _run_on_segment(self, func, identifier): - lr = _LogFileReader(identifier) - return func(lr) + def __iter__(self): + for i in range(len(self.logreader_identifiers)): + yield from self._get_lr(i) + + def _run_on_segment(self, func, i): + return func(self._get_lr(i)) def run_across_segments(self, num_processes, func): with multiprocessing.Pool(num_processes) as pool: ret = [] - for p in pool.map(partial(self._run_on_segment, func), self.logreader_identifiers): + for p in pool.map(partial(self._run_on_segment, func), range(len(self.logreader_identifiers))): ret.extend(p) return ret diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index f7874a3fb3..d8a9c14088 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -3,8 +3,11 @@ import tempfile import numpy as np import unittest import pytest -from parameterized import parameterized import requests + +from parameterized import parameterized +from unittest import mock + from openpilot.tools.lib.logreader import LogReader, parse_indirect, parse_slice, ReadMode from openpilot.tools.lib.route import SegmentRange @@ -104,11 +107,15 @@ class TestLogReader(unittest.TestCase): self.assertEqual(qlog_len*2, qlog_len_2) @pytest.mark.slow - def test_multiple_iterations(self): + @mock.patch("openpilot.tools.lib.logreader._LogFileReader") + def test_multiple_iterations(self, init_mock): lr = LogReader(f"{TEST_ROUTE}/0/q") qlog_len1 = len(list(lr)) qlog_len2 = len(list(lr)) + # ensure we don't create multiple instances of _LogFileReader, which means downloading the files twice + self.assertEqual(init_mock.call_count, 1) + self.assertEqual(qlog_len1, qlog_len2) From 1b82e77ed9ae2016a23034e7fc01deef199fba7a Mon Sep 17 00:00:00 2001 From: Kenny K Date: Wed, 24 Jan 2024 03:01:15 -0600 Subject: [PATCH 560/724] Chrysler: add Pacifica 2023 FW versions (#30934) * Add Pacifica 2023 fingerprint * add FW * remove can --------- Co-authored-by: Shane Smiskol --- selfdrive/car/chrysler/fingerprints.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 9ef991e8ed..46a5549d7c 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -62,6 +62,29 @@ FINGERPRINTS = { } FW_VERSIONS = { + CAR.PACIFICA_2019_HYBRID: { + (Ecu.combinationMeter, 0x742, None): [ + b'68594993AB', + ], + (Ecu.srs, 0x744, None): [ + b'68526663AB', + ], + (Ecu.abs, 0x747, None): [ + b'68593395AA', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'68598670AB', + ], + (Ecu.eps, 0x75a, None): [ + b'68594340AB', + ], + (Ecu.engine, 0x7e0, None): [ + b'68700306AB ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'68586231AD', + ], + }, CAR.JEEP_GRAND_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ b'68402971AD', From ee716a6d9d79a2276cc63c5fd6751e2cfbbc7b9b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 24 Jan 2024 01:29:05 -0800 Subject: [PATCH 561/724] Revert "Chrysler: add Pacifica 2023 FW versions (#30934)" (#31143) Revert "Add Pacifica 2023 fingerprint" --- selfdrive/car/chrysler/fingerprints.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 46a5549d7c..9ef991e8ed 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -62,29 +62,6 @@ FINGERPRINTS = { } FW_VERSIONS = { - CAR.PACIFICA_2019_HYBRID: { - (Ecu.combinationMeter, 0x742, None): [ - b'68594993AB', - ], - (Ecu.srs, 0x744, None): [ - b'68526663AB', - ], - (Ecu.abs, 0x747, None): [ - b'68593395AA', - ], - (Ecu.fwdRadar, 0x753, None): [ - b'68598670AB', - ], - (Ecu.eps, 0x75a, None): [ - b'68594340AB', - ], - (Ecu.engine, 0x7e0, None): [ - b'68700306AB ', - ], - (Ecu.transmission, 0x7e1, None): [ - b'68586231AD', - ], - }, CAR.JEEP_GRAND_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ b'68402971AD', From e5fe1d2885761028dfa61d95972a8d346809c446 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 24 Jan 2024 01:56:33 -0800 Subject: [PATCH 562/724] Chrysler: add Pacifica 2023 FW versions (#30934) (#31144) * Revert "Revert "Chrysler: add Pacifica 2023 FW versions (#30934)"" * ICE --- docs/CARS.md | 2 +- selfdrive/car/chrysler/fingerprints.py | 23 +++++++++++++++++++++++ selfdrive/car/chrysler/values.py | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 55098a39a3..bb55f9e0b7 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -28,7 +28,7 @@ A supported vehicle is one that just works when you install a comma device. All |Chevrolet|Volt 2017-18[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica Hybrid 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|| diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 9ef991e8ed..28f590b23a 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -62,6 +62,29 @@ FINGERPRINTS = { } FW_VERSIONS = { + CAR.PACIFICA_2020: { + (Ecu.combinationMeter, 0x742, None): [ + b'68594993AB', + ], + (Ecu.srs, 0x744, None): [ + b'68526663AB', + ], + (Ecu.abs, 0x747, None): [ + b'68593395AA', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'68598670AB', + ], + (Ecu.eps, 0x75a, None): [ + b'68594340AB', + ], + (Ecu.engine, 0x7e0, None): [ + b'68700306AB ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'68586231AD', + ], + }, CAR.JEEP_GRAND_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ b'68402971AD', diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 2a3bceec96..8d873705fa 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -70,7 +70,7 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"), CAR.PACIFICA_2020: [ ChryslerCarInfo("Chrysler Pacifica 2019-20"), - ChryslerCarInfo("Chrysler Pacifica 2021", package="All"), + ChryslerCarInfo("Chrysler Pacifica 2021-23", package="All"), ], CAR.JEEP_GRAND_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), CAR.JEEP_GRAND_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), From dd0c474e6cc09d17cd07b51098d826dc40bc5403 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 24 Jan 2024 10:21:57 -0800 Subject: [PATCH 563/724] LogReader: replace get_first_message with LogReader.first (#31146) * first * str --- tools/car_porting/auto_fingerprint.py | 10 +++++----- tools/car_porting/examples/ford_vin_fingerprint.ipynb | 9 +++------ tools/lib/logreader.py | 6 +++--- tools/lib/tests/test_comma_car_segments.py | 4 ++-- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/tools/car_porting/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py index e14a018917..7dae9ce048 100755 --- a/tools/car_porting/auto_fingerprint.py +++ b/tools/car_porting/auto_fingerprint.py @@ -7,7 +7,7 @@ from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versio from openpilot.selfdrive.car.fw_versions import match_fw_to_car from openpilot.selfdrive.car.interfaces import get_interface_attr -from openpilot.tools.lib.logreader import LogReader, ReadMode, get_first_message +from openpilot.tools.lib.logreader import LogReader, ReadMode ALL_FW_VERSIONS = get_interface_attr("FW_VERSIONS") @@ -32,14 +32,14 @@ if __name__ == "__main__": platform: Optional[str] = None - CP = get_first_message(lr, "carParams") + CP = lr.first("carParams") if CP is None: raise Exception("No fw versions in the provided route...") - carFw = CP.carParams.carFw - carVin = CP.carParams.carVin - carPlatform = CP.carParams.carFingerprint + carFw = CP.carFw + carVin = CP.carVin + carPlatform = CP.carFingerprint if args.platform is None: # attempt to auto-determine platform with other fuzzy fingerprints _, possible_platforms = match_fw_to_car(carFw, log=False) diff --git a/tools/car_porting/examples/ford_vin_fingerprint.ipynb b/tools/car_porting/examples/ford_vin_fingerprint.ipynb index c4f2088b38..b020b1100b 100644 --- a/tools/car_porting/examples/ford_vin_fingerprint.ipynb +++ b/tools/car_porting/examples/ford_vin_fingerprint.ipynb @@ -9,7 +9,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "kj/filesystem-disk-unix.c++:1703: warning: PWD environment variable doesn't match current directory; pwd = /mnt/c/Users/jnewb/AppData/Local/Programs/Microsoft VS Code\n" + "kj/filesystem-disk-unix.c++:1703: warning: PWD environment variable doesn't match current directory; pwd = /home/batman\n" ] } ], @@ -74,9 +74,6 @@ } ], "source": [ - "from openpilot.tools.lib.logreader import get_first_message\n", - "\n", - "\n", "VINS_TO_CHECK = set()\n", "\n", "for platform in platforms:\n", @@ -86,13 +83,13 @@ "\n", " for segment in database[platform]:\n", " lr = LogReader(segment)\n", - " CP = get_first_message(lr, \"carParams\").carParams\n", + " CP = lr.first(\"carParams\")\n", " VINS_TO_CHECK.add((CP.carVin, CP.carFingerprint))" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [ { diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 58883c6865..6c1174611b 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -264,9 +264,9 @@ are uploaded or auto fallback to qlogs with '/a' selector at the end of the rout def from_bytes(dat): return _LogFileReader("", dat=dat) - -def get_first_message(lr: LogIterable, msg_type): - return next(filter(lambda m: m.which() == msg_type, lr), None) + def first(self, msg_type: str): + m = next(filter(lambda m: m.which() == msg_type, self), None) + return None if m is None else getattr(m, msg_type) if __name__ == "__main__": diff --git a/tools/lib/tests/test_comma_car_segments.py b/tools/lib/tests/test_comma_car_segments.py index 1d6088d821..484a4aae08 100644 --- a/tools/lib/tests/test_comma_car_segments.py +++ b/tools/lib/tests/test_comma_car_segments.py @@ -4,7 +4,7 @@ import unittest import requests from openpilot.tools.lib.comma_car_segments import get_comma_car_segments_database, get_url -from openpilot.tools.lib.logreader import LogReader, get_first_message +from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import SegmentRange @@ -32,7 +32,7 @@ class TestCommaCarSegments(unittest.TestCase): lr = LogReader(url) - CP = get_first_message(lr, "carParams").carParams + CP = lr.first("carParams") self.assertEqual(CP.carFingerprint, fp) From 1bade14fdaf433a4e115874f9cc74e4fd6190481 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 24 Jan 2024 10:23:59 -0800 Subject: [PATCH 564/724] car porting examples: subaru fuzzy fingerprinting (#31139) initial commit --- .../examples/subaru_fuzzy_fingerprint.ipynb | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb diff --git a/tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb b/tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb new file mode 100644 index 0000000000..fbd88a769e --- /dev/null +++ b/tools/car_porting/examples/subaru_fuzzy_fingerprint.ipynb @@ -0,0 +1,259 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 148, + "metadata": {}, + "outputs": [], + "source": [ + "from cereal import car\n", + "from openpilot.selfdrive.car.subaru.values import CAR, PREGLOBAL_CARS\n", + "from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS\n", + "\n", + "TEST_PLATFORMS = set(c.value for c in CAR) - PREGLOBAL_CARS # preglobal cars seem to have a different format for fingerprints, ignore for now\n", + "\n", + "Ecu = car.CarParams.Ecu\n", + "\n", + "FW_BY_ECU = {platform: {ecu: versions for (ecu, addr, sub_addr), versions in fw_versions.items()} for platform, fw_versions in FW_VERSIONS.items()}" + ] + }, + { + "cell_type": "code", + "execution_count": 149, + "metadata": {}, + "outputs": [], + "source": [ + "from openpilot.selfdrive.car.subaru.values import CAR_INFO\n", + "\n", + "def get_carinfo(model: CAR):\n", + " c = CAR_INFO[model]\n", + " if isinstance(c, list):\n", + " c = c[0]\n", + " return c" + ] + }, + { + "cell_type": "code", + "execution_count": 150, + "metadata": {}, + "outputs": [], + "source": [ + "PLATFORM_CODES = {\n", + " Ecu.abs: {\n", + " 0: {\n", + " b'\\xa5': [CAR.ASCENT, CAR.ASCENT_2023],\n", + " b'\\xa2': [CAR.IMPREZA, CAR.IMPREZA_2020, CAR.CROSSTREK_HYBRID],\n", + " b'\\xa1': [CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023],\n", + " b'\\xa3': [CAR.FORESTER, CAR.FORESTER_HYBRID, CAR.FORESTER_2022],\n", + " b'z': [CAR.IMPREZA],\n", + " }\n", + " }\n", + "}\n", + "\n", + "YEAR_CODES = {\n", + " Ecu.abs: {\n", + " 2: {\n", + " b'\\x18': 2018,\n", + " b'\\x19': 2019,\n", + " b'\\x20': 2020,\n", + " b'\\x21': 2021,\n", + " b'\\x22': 2022,\n", + " b'\\x23': 2023,\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 151, + "metadata": {}, + "outputs": [], + "source": [ + "def get_codes(platforms, codes):\n", + " results = []\n", + " for platform in platforms:\n", + " for ecu in codes:\n", + " for i in codes[ecu]:\n", + " if isinstance(i, tuple):\n", + " j = slice(i[0], i[1])\n", + " else:\n", + " j = slice(i, i+1)\n", + " for version in FW_BY_ECU[platform][ecu]:\n", + " code = version[j]\n", + " if code not in codes[ecu][i]:\n", + " print(f\"{platform} {code.hex()} not in {codes[ecu][i].keys()}\")\n", + " else:\n", + " results.append((platform, codes[ecu][i][code]))\n", + " return results" + ] + }, + { + "cell_type": "code", + "execution_count": 152, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SUBARU IMPREZA LIMITED 2019 08 not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", + "SUBARU IMPREZA LIMITED 2019 08 not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", + "SUBARU IMPREZA LIMITED 2019 0c not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", + "SUBARU IMPREZA LIMITED 2019 0c not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", + "SUBARU IMPREZA LIMITED 2019 2e not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", + "SUBARU IMPREZA LIMITED 2019 3f not in dict_keys([b'\\x18', b'\\x19', b' ', b'!', b'\"', b'#'])\n", + "correct_year=True platform=SUBARU ASCENT LIMITED 2019 year=2019 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU ASCENT LIMITED 2019 year=2021 years=[2019, 2020, 2021]\n", + "correct_year=False platform=SUBARU IMPREZA SPORT 2020 year=2019 years=[2020, 2021, 2022]\n", + "correct_year=False platform=SUBARU IMPREZA SPORT 2020 year=2019 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU IMPREZA SPORT 2020 year=2021 years=[2020, 2021, 2022]\n", + "correct_year=False platform=SUBARU FORESTER 2019 year=2018 years=[2019, 2020, 2021]\n", + "correct_year=False platform=SUBARU FORESTER 2019 year=2018 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU FORESTER 2019 year=2019 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU FORESTER 2019 year=2019 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU FORESTER 2019 year=2020 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU FORESTER 2019 year=2020 years=[2019, 2020, 2021]\n", + "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", + "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", + "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2018 years=[2017, 2018, 2019]\n", + "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", + "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", + "correct_year=True platform=SUBARU IMPREZA LIMITED 2019 year=2019 years=[2017, 2018, 2019]\n", + "correct_year=True platform=SUBARU OUTBACK 7TH GEN year=2023 years=[2023]\n", + "correct_year=True platform=SUBARU OUTBACK 7TH GEN year=2023 years=[2023]\n", + "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU LEGACY 7TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=False platform=SUBARU FORESTER 2022 year=2021 years=[2022, 2023]\n", + "correct_year=False platform=SUBARU FORESTER 2022 year=2021 years=[2022, 2023]\n", + "correct_year=True platform=SUBARU FORESTER 2022 year=2022 years=[2022, 2023]\n", + "correct_year=True platform=SUBARU FORESTER 2022 year=2022 years=[2022, 2023]\n", + "correct_year=False platform=SUBARU CROSSTREK HYBRID 2020 year=2019 years=[2020]\n", + "correct_year=False platform=SUBARU CROSSTREK HYBRID 2020 year=2021 years=[2020]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2020 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2022 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU OUTBACK 6TH GEN year=2022 years=[2020, 2021, 2022]\n", + "correct_year=True platform=SUBARU ASCENT 2023 year=2023 years=[2023]\n", + "correct_year=False platform=SUBARU FORESTER HYBRID 2020 year=2019 years=[2020]\n" + ] + } + ], + "source": [ + "def test_year_code(platform, year):\n", + " years = [int(y) for y in get_carinfo(platform).year_list]\n", + " correct_year = year in years\n", + " print(f\"{correct_year=!s: <6} {platform=: <32} {year=: <5} {years=}\")\n", + "\n", + "codes = get_codes(TEST_PLATFORMS, YEAR_CODES)\n", + "for platform, year in codes:\n", + " test_year_code(platform, year)" + ] + }, + { + "cell_type": "code", + "execution_count": 153, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "in_possible_platforms=True platform=SUBARU ASCENT LIMITED 2019 platforms=['SUBARU ASCENT LIMITED 2019', 'SUBARU ASCENT 2023']\n", + "in_possible_platforms=True platform=SUBARU ASCENT LIMITED 2019 platforms=['SUBARU ASCENT LIMITED 2019', 'SUBARU ASCENT 2023']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA SPORT 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2019 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU IMPREZA LIMITED 2019 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU LEGACY 7TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU FORESTER 2022 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n", + "in_possible_platforms=True platform=SUBARU CROSSTREK HYBRID 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU CROSSTREK HYBRID 2020 platforms=['SUBARU IMPREZA LIMITED 2019', 'SUBARU IMPREZA SPORT 2020', 'SUBARU CROSSTREK HYBRID 2020']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU OUTBACK 6TH GEN platforms=['SUBARU OUTBACK 6TH GEN', 'SUBARU LEGACY 7TH GEN', 'SUBARU OUTBACK 7TH GEN']\n", + "in_possible_platforms=True platform=SUBARU ASCENT 2023 platforms=['SUBARU ASCENT LIMITED 2019', 'SUBARU ASCENT 2023']\n", + "in_possible_platforms=True platform=SUBARU FORESTER HYBRID 2020 platforms=['SUBARU FORESTER 2019', 'SUBARU FORESTER HYBRID 2020', 'SUBARU FORESTER 2022']\n" + ] + } + ], + "source": [ + "def test_platform_code(platform, platforms):\n", + " platforms = [str(p) for p in platforms]\n", + " in_possible_platforms = platform in platforms\n", + " print(f\"{in_possible_platforms=!s: <6} {platform=: <32} {platforms=}\")\n", + "\n", + "codes = get_codes(TEST_PLATFORMS, PLATFORM_CODES)\n", + "for platform, possible_platforms in codes:\n", + " test_platform_code(platform, possible_platforms)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 7f35d0a909d65d647008e74df56c51e603971012 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 24 Jan 2024 11:53:03 -0800 Subject: [PATCH 565/724] disable TestManager.test_clean_exit for now --- selfdrive/manager/test/test_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 9dc8bf1cda..fbeb932a86 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -46,6 +46,7 @@ class TestManager(unittest.TestCase): t = time.monotonic() - start assert t < MAX_STARTUP_TIME, f"startup took {t}s, expected <{MAX_STARTUP_TIME}s" + @unittest.skip("this test is flaky the way it's currently written, should be moved to test_onroad") def test_clean_exit(self): """ Ensure all processes exit cleanly when stopped. From e2ec5be6eec73f6455727313ba2b9c5bb01cce8c Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 24 Jan 2024 13:54:31 -0800 Subject: [PATCH 566/724] test_ui: create test ui screenshots and html report (#31147) * test ui * report + common * in ci * fix * dont enable in ci yet --- .github/workflows/selfdrive_tests.yaml | 22 + poetry.lock | 2972 +++++++++++++++++++++- pyproject.toml | 2 + selfdrive/test/setup_xvfb.sh | 2 +- selfdrive/ui/tests/.gitignore | 1 + selfdrive/ui/tests/test_ui/run.py | 121 + selfdrive/ui/tests/test_ui/template.html | 34 + 7 files changed, 3136 insertions(+), 18 deletions(-) create mode 100644 selfdrive/ui/tests/test_ui/run.py create mode 100644 selfdrive/ui/tests/test_ui/template.html diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 080051bdaa..2daa895de9 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -395,3 +395,25 @@ jobs: repo: context.repo.repo, comment_id: ${{ steps.fc.outputs.comment-id }} }) + +# need to figure out some stuff with tkinter before enabling this + + # create_ui_report: + # name: Create UI Report + # runs-on: ubuntu-20.04 + # steps: + # - uses: actions/checkout@v4 + # with: + # submodules: true + # - uses: ./.github/workflows/setup-with-retry + # - name: Build openpilot + # run: ${{ env.RUN }} "scons -j$(nproc)" + # - name: Create Test Report + # run: ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ + # export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ + # python selfdrive/ui/tests/test_ui/run.py" + # - name: Upload Test Report + # uses: actions/upload-artifact@v2 + # with: + # name: report + # path: selfdrive/ui/tests/test_ui \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index b7e7f6deaf..ecfdac4497 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2216,6 +2216,21 @@ url = "https://github.com/metadriverse/metadrive.git" reference = "main" resolved_reference = "bc162e1b423bd194a58ef2269103f1bcea5f53c7" +[[package]] +name = "mouseinfo" +version = "0.1.3" +description = "An application to display XY position and RGB color information for the pixel currently under the mouse. Works on Python 2 and 3." +optional = false +python-versions = "*" +files = [ + {file = "MouseInfo-0.1.3.tar.gz", hash = "sha256:2c62fb8885062b8e520a3cce0a297c657adcc08c60952eb05bc8256ef6f7f6e7"}, +] + +[package.dependencies] +pyperclip = "*" +python3-Xlib = {version = "*", markers = "platform_system == \"Linux\" and python_version >= \"3.0\""} +rubicon-objc = {version = "*", markers = "platform_system == \"Darwin\""} + [[package]] name = "mpld3" version = "0.5.10" @@ -2642,7 +2657,14 @@ files = [ ] [package.dependencies] -numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] [[package]] name = "opencv-python-headless" @@ -2661,7 +2683,14 @@ files = [ ] [package.dependencies] -numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, + {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, + {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, + {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] [[package]] name = "packaging" @@ -3132,6 +3161,26 @@ files = [ [package.extras] test = ["numpy"] +[[package]] +name = "pyautogui" +version = "0.9.54" +description = "PyAutoGUI lets Python control the mouse and keyboard, and other GUI automation tasks. For Windows, macOS, and Linux, on Python 3 and 2." +optional = false +python-versions = "*" +files = [ + {file = "PyAutoGUI-0.9.54.tar.gz", hash = "sha256:dd1d29e8fd118941cb193f74df57e5c6ff8e9253b99c7b04f39cfc69f3ae04b2"}, +] + +[package.dependencies] +mouseinfo = "*" +pygetwindow = ">=0.0.5" +pymsgbox = "*" +pyobjc-core = {version = "*", markers = "platform_system == \"Darwin\""} +pyobjc-framework-quartz = {version = "*", markers = "platform_system == \"Darwin\""} +pyscreeze = ">=0.1.21" +python3-Xlib = {version = "*", markers = "platform_system == \"Linux\" and python_version >= \"3.0\""} +pytweening = ">=1.0.4" + [[package]] name = "pycapnp" version = "1.3.0" @@ -3262,8 +3311,6 @@ files = [ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, - {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"}, - {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, @@ -3307,6 +3354,19 @@ files = [ {file = "pygame-2.5.2.tar.gz", hash = "sha256:c1b89eb5d539e7ac5cf75513125fb5f2f0a2d918b1fd6e981f23bf0ac1b1c24a"}, ] +[[package]] +name = "pygetwindow" +version = "0.0.9" +description = "A simple, cross-platform module for obtaining GUI information on application's windows." +optional = false +python-versions = "*" +files = [ + {file = "PyGetWindow-0.0.9.tar.gz", hash = "sha256:17894355e7d2b305cd832d717708384017c1698a90ce24f6f7fbf0242dd0a688"}, +] + +[package.dependencies] +pyrect = "*" + [[package]] name = "pygments" version = "2.17.2" @@ -3380,6 +3440,2773 @@ cffi = ">=1.0.0" [package.extras] dev = ["coverage[toml] (>=7.2.2)"] +[[package]] +name = "pymonctl" +version = "0.7" +description = "Cross-Platform toolkit to get info on and control monitors connected" +optional = false +python-versions = "*" +files = [ + {file = "PyMonCtl-0.7-py3-none-any.whl", hash = "sha256:20c1f7f4cf46ff3349fa60d4e63c4ac300a2b907c0d984a1dd64d452b85ea781"}, +] + +[package.dependencies] +pyobjc = {version = ">=8.1", markers = "sys_platform == \"darwin\""} +python-xlib = {version = ">=0.21", markers = "sys_platform == \"linux\""} +pywin32 = {version = ">=302", markers = "sys_platform == \"win32\""} +typing-extensions = ">=4.4.0" + +[package.extras] +dev = ["mypy (>=0.990)", "types-python-xlib (>=0.32)", "types-pywin32 (>=305.0.0.3)", "types-setuptools (>=65.5)"] + +[[package]] +name = "pymsgbox" +version = "1.0.9" +description = "A simple, cross-platform, pure Python module for JavaScript-like message boxes." +optional = false +python-versions = "*" +files = [ + {file = "PyMsgBox-1.0.9.tar.gz", hash = "sha256:2194227de8bff7a3d6da541848705a155dcbb2a06ee120d9f280a1d7f51263ff"}, +] + +[[package]] +name = "pyobjc" +version = "10.1" +description = "Python<->ObjC Interoperability Module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-10.1-py3-none-any.whl", hash = "sha256:2687ff02217e7b2aba52c6b948bccea864a8f034af6c90528564d496b343c418"}, + {file = "pyobjc-10.1.tar.gz", hash = "sha256:f54baff4c40d53c3fb3812816ebd130d3186805936628cc1f212f95979af5b98"}, +] + +[package.dependencies] +pyobjc-core = "10.1" +pyobjc-framework-Accessibility = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-Accounts = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-AddressBook = "10.1" +pyobjc-framework-AdServices = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-AdSupport = {version = "10.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-AppleScriptKit = "10.1" +pyobjc-framework-AppleScriptObjC = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-ApplicationServices = "10.1" +pyobjc-framework-AppTrackingTransparency = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-AudioVideoBridging = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-AuthenticationServices = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-AutomaticAssessmentConfiguration = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Automator = "10.1" +pyobjc-framework-AVFoundation = {version = "10.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-AVKit = {version = "10.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-AVRouting = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-BackgroundAssets = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-BusinessChat = {version = "10.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-CalendarStore = {version = "10.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-CallKit = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-CFNetwork = "10.1" +pyobjc-framework-Cinematic = {version = "10.1", markers = "platform_release >= \"23.0\""} +pyobjc-framework-ClassKit = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-CloudKit = {version = "10.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-Cocoa = "10.1" +pyobjc-framework-Collaboration = {version = "10.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-ColorSync = {version = "10.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-Contacts = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-ContactsUI = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-CoreAudio = "10.1" +pyobjc-framework-CoreAudioKit = "10.1" +pyobjc-framework-CoreBluetooth = {version = "10.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-CoreData = "10.1" +pyobjc-framework-CoreHaptics = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-CoreLocation = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-CoreMedia = {version = "10.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-CoreMediaIO = {version = "10.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-CoreMIDI = "10.1" +pyobjc-framework-CoreML = {version = "10.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-CoreMotion = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-CoreServices = "10.1" +pyobjc-framework-CoreSpotlight = {version = "10.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-CoreText = "10.1" +pyobjc-framework-CoreWLAN = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-CryptoTokenKit = {version = "10.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-DataDetection = {version = "10.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-DeviceCheck = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-DictionaryServices = {version = "10.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-DiscRecording = "10.1" +pyobjc-framework-DiscRecordingUI = "10.1" +pyobjc-framework-DiskArbitration = "10.1" +pyobjc-framework-DVDPlayback = "10.1" +pyobjc-framework-EventKit = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-ExceptionHandling = "10.1" +pyobjc-framework-ExecutionPolicy = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ExtensionKit = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ExternalAccessory = {version = "10.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-FileProvider = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-FileProviderUI = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-FinderSync = {version = "10.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-FSEvents = {version = "10.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-GameCenter = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-GameController = {version = "10.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-GameKit = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-GameplayKit = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-HealthKit = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ImageCaptureCore = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-InputMethodKit = {version = "10.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-InstallerPlugins = "10.1" +pyobjc-framework-InstantMessage = {version = "10.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-Intents = {version = "10.1", markers = "platform_release >= \"16.0\""} +pyobjc-framework-IntentsUI = {version = "10.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-IOBluetooth = "10.1" +pyobjc-framework-IOBluetoothUI = "10.1" +pyobjc-framework-IOSurface = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-iTunesLibrary = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-KernelManagement = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-LatentSemanticMapping = "10.1" +pyobjc-framework-LaunchServices = "10.1" +pyobjc-framework-libdispatch = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-libxpc = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-LinkPresentation = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-LocalAuthentication = {version = "10.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-LocalAuthenticationEmbeddedUI = {version = "10.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MailKit = {version = "10.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MapKit = {version = "10.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaAccessibility = {version = "10.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaLibrary = {version = "10.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-MediaPlayer = {version = "10.1", markers = "platform_release >= \"16.0\""} +pyobjc-framework-MediaToolbox = {version = "10.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-Metal = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MetalFX = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-MetalKit = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MetalPerformanceShaders = {version = "10.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-MetalPerformanceShadersGraph = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-MetricKit = {version = "10.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-MLCompute = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-ModelIO = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-MultipeerConnectivity = {version = "10.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-NaturalLanguage = {version = "10.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-NetFS = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-Network = {version = "10.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-NetworkExtension = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-NotificationCenter = {version = "10.1", markers = "platform_release >= \"14.0\""} +pyobjc-framework-OpenDirectory = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-OSAKit = "10.1" +pyobjc-framework-OSLog = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-PassKit = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-PencilKit = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-PHASE = {version = "10.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-Photos = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-PhotosUI = {version = "10.1", markers = "platform_release >= \"15.0\""} +pyobjc-framework-PreferencePanes = "10.1" +pyobjc-framework-PubSub = {version = "10.1", markers = "platform_release >= \"9.0\" and platform_release < \"18.0\""} +pyobjc-framework-PushKit = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Quartz = "10.1" +pyobjc-framework-QuickLookThumbnailing = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ReplayKit = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-SafariServices = {version = "10.1", markers = "platform_release >= \"16.0\""} +pyobjc-framework-SafetyKit = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-SceneKit = {version = "10.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-ScreenCaptureKit = {version = "10.1", markers = "platform_release >= \"21.4\""} +pyobjc-framework-ScreenSaver = "10.1" +pyobjc-framework-ScreenTime = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-ScriptingBridge = {version = "10.1", markers = "platform_release >= \"9.0\""} +pyobjc-framework-SearchKit = "10.1" +pyobjc-framework-Security = "10.1" +pyobjc-framework-SecurityFoundation = "10.1" +pyobjc-framework-SecurityInterface = "10.1" +pyobjc-framework-SensitiveContentAnalysis = {version = "10.1", markers = "platform_release >= \"23.0\""} +pyobjc-framework-ServiceManagement = {version = "10.1", markers = "platform_release >= \"10.0\""} +pyobjc-framework-SharedWithYou = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-SharedWithYouCore = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-ShazamKit = {version = "10.1", markers = "platform_release >= \"21.0\""} +pyobjc-framework-Social = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-SoundAnalysis = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-Speech = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-SpriteKit = {version = "10.1", markers = "platform_release >= \"13.0\""} +pyobjc-framework-StoreKit = {version = "10.1", markers = "platform_release >= \"11.0\""} +pyobjc-framework-Symbols = {version = "10.1", markers = "platform_release >= \"23.0\""} +pyobjc-framework-SyncServices = "10.1" +pyobjc-framework-SystemConfiguration = "10.1" +pyobjc-framework-SystemExtensions = {version = "10.1", markers = "platform_release >= \"19.0\""} +pyobjc-framework-ThreadNetwork = {version = "10.1", markers = "platform_release >= \"22.0\""} +pyobjc-framework-UniformTypeIdentifiers = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-UserNotifications = {version = "10.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-UserNotificationsUI = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-VideoSubscriberAccount = {version = "10.1", markers = "platform_release >= \"18.0\""} +pyobjc-framework-VideoToolbox = {version = "10.1", markers = "platform_release >= \"12.0\""} +pyobjc-framework-Virtualization = {version = "10.1", markers = "platform_release >= \"20.0\""} +pyobjc-framework-Vision = {version = "10.1", markers = "platform_release >= \"17.0\""} +pyobjc-framework-WebKit = "10.1" + +[package.extras] +allbindings = ["pyobjc-core (==10.1)", "pyobjc-framework-AVFoundation (==10.1)", "pyobjc-framework-AVKit (==10.1)", "pyobjc-framework-AVRouting (==10.1)", "pyobjc-framework-Accessibility (==10.1)", "pyobjc-framework-Accounts (==10.1)", "pyobjc-framework-AdServices (==10.1)", "pyobjc-framework-AdSupport (==10.1)", "pyobjc-framework-AddressBook (==10.1)", "pyobjc-framework-AppTrackingTransparency (==10.1)", "pyobjc-framework-AppleScriptKit (==10.1)", "pyobjc-framework-AppleScriptObjC (==10.1)", "pyobjc-framework-ApplicationServices (==10.1)", "pyobjc-framework-AudioVideoBridging (==10.1)", "pyobjc-framework-AuthenticationServices (==10.1)", "pyobjc-framework-AutomaticAssessmentConfiguration (==10.1)", "pyobjc-framework-Automator (==10.1)", "pyobjc-framework-BackgroundAssets (==10.1)", "pyobjc-framework-BusinessChat (==10.1)", "pyobjc-framework-CFNetwork (==10.1)", "pyobjc-framework-CalendarStore (==10.1)", "pyobjc-framework-CallKit (==10.1)", "pyobjc-framework-Cinematic (==10.1)", "pyobjc-framework-ClassKit (==10.1)", "pyobjc-framework-CloudKit (==10.1)", "pyobjc-framework-Cocoa (==10.1)", "pyobjc-framework-Collaboration (==10.1)", "pyobjc-framework-ColorSync (==10.1)", "pyobjc-framework-Contacts (==10.1)", "pyobjc-framework-ContactsUI (==10.1)", "pyobjc-framework-CoreAudio (==10.1)", "pyobjc-framework-CoreAudioKit (==10.1)", "pyobjc-framework-CoreBluetooth (==10.1)", "pyobjc-framework-CoreData (==10.1)", "pyobjc-framework-CoreHaptics (==10.1)", "pyobjc-framework-CoreLocation (==10.1)", "pyobjc-framework-CoreMIDI (==10.1)", "pyobjc-framework-CoreML (==10.1)", "pyobjc-framework-CoreMedia (==10.1)", "pyobjc-framework-CoreMediaIO (==10.1)", "pyobjc-framework-CoreMotion (==10.1)", "pyobjc-framework-CoreServices (==10.1)", "pyobjc-framework-CoreSpotlight (==10.1)", "pyobjc-framework-CoreText (==10.1)", "pyobjc-framework-CoreWLAN (==10.1)", "pyobjc-framework-CryptoTokenKit (==10.1)", "pyobjc-framework-DVDPlayback (==10.1)", "pyobjc-framework-DataDetection (==10.1)", "pyobjc-framework-DeviceCheck (==10.1)", "pyobjc-framework-DictionaryServices (==10.1)", "pyobjc-framework-DiscRecording (==10.1)", "pyobjc-framework-DiscRecordingUI (==10.1)", "pyobjc-framework-DiskArbitration (==10.1)", "pyobjc-framework-EventKit (==10.1)", "pyobjc-framework-ExceptionHandling (==10.1)", "pyobjc-framework-ExecutionPolicy (==10.1)", "pyobjc-framework-ExtensionKit (==10.1)", "pyobjc-framework-ExternalAccessory (==10.1)", "pyobjc-framework-FSEvents (==10.1)", "pyobjc-framework-FileProvider (==10.1)", "pyobjc-framework-FileProviderUI (==10.1)", "pyobjc-framework-FinderSync (==10.1)", "pyobjc-framework-GameCenter (==10.1)", "pyobjc-framework-GameController (==10.1)", "pyobjc-framework-GameKit (==10.1)", "pyobjc-framework-GameplayKit (==10.1)", "pyobjc-framework-HealthKit (==10.1)", "pyobjc-framework-IOBluetooth (==10.1)", "pyobjc-framework-IOBluetoothUI (==10.1)", "pyobjc-framework-IOSurface (==10.1)", "pyobjc-framework-ImageCaptureCore (==10.1)", "pyobjc-framework-InputMethodKit (==10.1)", "pyobjc-framework-InstallerPlugins (==10.1)", "pyobjc-framework-InstantMessage (==10.1)", "pyobjc-framework-Intents (==10.1)", "pyobjc-framework-IntentsUI (==10.1)", "pyobjc-framework-KernelManagement (==10.1)", "pyobjc-framework-LatentSemanticMapping (==10.1)", "pyobjc-framework-LaunchServices (==10.1)", "pyobjc-framework-LinkPresentation (==10.1)", "pyobjc-framework-LocalAuthentication (==10.1)", "pyobjc-framework-LocalAuthenticationEmbeddedUI (==10.1)", "pyobjc-framework-MLCompute (==10.1)", "pyobjc-framework-MailKit (==10.1)", "pyobjc-framework-MapKit (==10.1)", "pyobjc-framework-MediaAccessibility (==10.1)", "pyobjc-framework-MediaLibrary (==10.1)", "pyobjc-framework-MediaPlayer (==10.1)", "pyobjc-framework-MediaToolbox (==10.1)", "pyobjc-framework-Metal (==10.1)", "pyobjc-framework-MetalFX (==10.1)", "pyobjc-framework-MetalKit (==10.1)", "pyobjc-framework-MetalPerformanceShaders (==10.1)", "pyobjc-framework-MetalPerformanceShadersGraph (==10.1)", "pyobjc-framework-MetricKit (==10.1)", "pyobjc-framework-ModelIO (==10.1)", "pyobjc-framework-MultipeerConnectivity (==10.1)", "pyobjc-framework-NaturalLanguage (==10.1)", "pyobjc-framework-NetFS (==10.1)", "pyobjc-framework-Network (==10.1)", "pyobjc-framework-NetworkExtension (==10.1)", "pyobjc-framework-NotificationCenter (==10.1)", "pyobjc-framework-OSAKit (==10.1)", "pyobjc-framework-OSLog (==10.1)", "pyobjc-framework-OpenDirectory (==10.1)", "pyobjc-framework-PHASE (==10.1)", "pyobjc-framework-PassKit (==10.1)", "pyobjc-framework-PencilKit (==10.1)", "pyobjc-framework-Photos (==10.1)", "pyobjc-framework-PhotosUI (==10.1)", "pyobjc-framework-PreferencePanes (==10.1)", "pyobjc-framework-PubSub (==10.1)", "pyobjc-framework-PushKit (==10.1)", "pyobjc-framework-Quartz (==10.1)", "pyobjc-framework-QuickLookThumbnailing (==10.1)", "pyobjc-framework-ReplayKit (==10.1)", "pyobjc-framework-SafariServices (==10.1)", "pyobjc-framework-SafetyKit (==10.1)", "pyobjc-framework-SceneKit (==10.1)", "pyobjc-framework-ScreenCaptureKit (==10.1)", "pyobjc-framework-ScreenSaver (==10.1)", "pyobjc-framework-ScreenTime (==10.1)", "pyobjc-framework-ScriptingBridge (==10.1)", "pyobjc-framework-SearchKit (==10.1)", "pyobjc-framework-Security (==10.1)", "pyobjc-framework-SecurityFoundation (==10.1)", "pyobjc-framework-SecurityInterface (==10.1)", "pyobjc-framework-SensitiveContentAnalysis (==10.1)", "pyobjc-framework-ServiceManagement (==10.1)", "pyobjc-framework-SharedWithYou (==10.1)", "pyobjc-framework-SharedWithYouCore (==10.1)", "pyobjc-framework-ShazamKit (==10.1)", "pyobjc-framework-Social (==10.1)", "pyobjc-framework-SoundAnalysis (==10.1)", "pyobjc-framework-Speech (==10.1)", "pyobjc-framework-SpriteKit (==10.1)", "pyobjc-framework-StoreKit (==10.1)", "pyobjc-framework-Symbols (==10.1)", "pyobjc-framework-SyncServices (==10.1)", "pyobjc-framework-SystemConfiguration (==10.1)", "pyobjc-framework-SystemExtensions (==10.1)", "pyobjc-framework-ThreadNetwork (==10.1)", "pyobjc-framework-UniformTypeIdentifiers (==10.1)", "pyobjc-framework-UserNotifications (==10.1)", "pyobjc-framework-UserNotificationsUI (==10.1)", "pyobjc-framework-VideoSubscriberAccount (==10.1)", "pyobjc-framework-VideoToolbox (==10.1)", "pyobjc-framework-Virtualization (==10.1)", "pyobjc-framework-Vision (==10.1)", "pyobjc-framework-WebKit (==10.1)", "pyobjc-framework-iTunesLibrary (==10.1)", "pyobjc-framework-libdispatch (==10.1)", "pyobjc-framework-libxpc (==10.1)"] + +[[package]] +name = "pyobjc-core" +version = "10.1" +description = "Python<->ObjC Interoperability Module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-core-10.1.tar.gz", hash = "sha256:1844f1c8e282839e6fdcb9a9722396c1c12fb1e9331eb68828a26f28a3b2b2b1"}, + {file = "pyobjc_core-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2a72a88222539ad07b5c8be411edc52ff9147d7cef311a2c849869d7bb9603fd"}, + {file = "pyobjc_core-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe1b9987b7b0437685fb529832876c2a8463500114960d4e76bb8ae96b6bf208"}, + {file = "pyobjc_core-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9f628779c345d3abd0e20048fb0e256d894c22254577a81a6dcfdb92c3647682"}, + {file = "pyobjc_core-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25a9e5a2de19238787d24cfa7def6b7fbb94bbe89c0e3109f71c1cb108e8ab44"}, + {file = "pyobjc_core-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2d43205d3a784aa87055b84c0ec0dfa76498e5f18d1ad16bdc58a3dcf5a7d5d0"}, + {file = "pyobjc_core-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0aa9799b5996a893944999a2f1afcf1de119cab3551c169ad9f54d12e1d38c99"}, +] + +[[package]] +name = "pyobjc-framework-accessibility" +version = "10.1" +description = "Wrappers for the framework Accessibility on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Accessibility-10.1.tar.gz", hash = "sha256:70b812cf2b04b57a520c3fde9df4184c2783795fb355b416a8058114e52ad24a"}, + {file = "pyobjc_framework_Accessibility-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ef8524f2b67240fb3c3f7928f2d73e9050a8b80e18db8336e7ba4d4ba1d368df"}, + {file = "pyobjc_framework_Accessibility-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d351d7799b197524a200c54bebe450e87f9c52812f6162811b7e84823e8946df"}, + {file = "pyobjc_framework_Accessibility-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1dc4e0acedaa0232103714dd2daa3244a628426bee6e933078c89e8eb86b7961"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-accounts" +version = "10.1" +description = "Wrappers for the framework Accounts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Accounts-10.1.tar.gz", hash = "sha256:cb436af5b60f1d6a8a59f94d84ea6decba663598d5624fce29a0babf6fad0a89"}, + {file = "pyobjc_framework_Accounts-10.1-py2.py3-none-any.whl", hash = "sha256:30da31a76f2cfd0a4021eff4d4ff69e0a70b2f293290372f5909ae267d15a010"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-addressbook" +version = "10.1" +description = "Wrappers for the framework AddressBook on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AddressBook-10.1.tar.gz", hash = "sha256:9b8e01da07703990f0e745945b01cc75c59ade41913edbd6824194e21522efff"}, + {file = "pyobjc_framework_AddressBook-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2c6cb2278161ed55fba8b47515ff777a95265e484c51ad7a1c952747d8a411ee"}, + {file = "pyobjc_framework_AddressBook-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:10236b9112c8e5d83526804ca734a5f176bba435c2451c4b43c1247e76d9f73d"}, + {file = "pyobjc_framework_AddressBook-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e4305cf6366fa2e01040f490360283f572103be0a45d190774869915c2707c54"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-adservices" +version = "10.1" +description = "Wrappers for the framework AdServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AdServices-10.1.tar.gz", hash = "sha256:54d2dd3084374213e31760bae9df1e6e9da3b3f1cc04787dae3ad53f8fc12f69"}, + {file = "pyobjc_framework_AdServices-10.1-py2.py3-none-any.whl", hash = "sha256:79ec6eb744635b72ffd0bdd5e55cb5ec57603633716861bbf40b236d8dba0dfd"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-adsupport" +version = "10.1" +description = "Wrappers for the framework AdSupport on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AdSupport-10.1.tar.gz", hash = "sha256:df6b2d1cc1202905dcf6bcdbf35121acc45c346a57b1048f5f4d1ea15bc29c9c"}, + {file = "pyobjc_framework_AdSupport-10.1-py2.py3-none-any.whl", hash = "sha256:d61f2e44f6c2ed5c33b6520754ef8ea22470f8ac3154912aa44bee4fb792255c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-applescriptkit" +version = "10.1" +description = "Wrappers for the framework AppleScriptKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppleScriptKit-10.1.tar.gz", hash = "sha256:e41cd0037cbe0af4ffecc42339d1b6255f2539dfb6dedf4f2ae00ac1a260eecf"}, + {file = "pyobjc_framework_AppleScriptKit-10.1-py2.py3-none-any.whl", hash = "sha256:b88bc8ae9e000d382c3e1d72b3c4f39499323fbe88cc84af259925448c187387"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-applescriptobjc" +version = "10.1" +description = "Wrappers for the framework AppleScriptObjC on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppleScriptObjC-10.1.tar.gz", hash = "sha256:cfcec31b25a4c201188936347697ff3eb1f79885a43af26559a572391c50cdf9"}, + {file = "pyobjc_framework_AppleScriptObjC-10.1-py2.py3-none-any.whl", hash = "sha256:500ed0e39bf2a4f2413d8d6dc398bb58f233ca3670f6946aa5c6d14d1b563465"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-applicationservices" +version = "10.1" +description = "Wrappers for the framework ApplicationServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ApplicationServices-10.1.tar.gz", hash = "sha256:bb780eabadad0fbf36a128041dccfd71e30bbeb6b110852d37fd5c98f4a2ec04"}, + {file = "pyobjc_framework_ApplicationServices-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a74a0922b48ad5ac4e402a1ac5dda5d6ee0d177870b7e244be61bc95d639ba85"}, + {file = "pyobjc_framework_ApplicationServices-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff352c33cad3f7bf8dd9b955ebb5db02d451d88eb04478d83edf0edd0cc8bf5d"}, + {file = "pyobjc_framework_ApplicationServices-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6d0706d5d9436298c8d619a1bb5be11a1f4ff9f4733797a393c6a706568de110"}, + {file = "pyobjc_framework_ApplicationServices-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:95bd111583c3bf20656393c2a056a457b0cf08c76c0ab27cfcaedf92f707e8a9"}, + {file = "pyobjc_framework_ApplicationServices-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:90a2350ddae3d9fb7b2e35e3b672b64854edae497fda8d7d4d798679c8280fed"}, + {file = "pyobjc_framework_ApplicationServices-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8bc830ac60b73a4cab24d1b1fdd8b044f25fe02e0af63a92cd96c43a51808c96"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreText = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-apptrackingtransparency" +version = "10.1" +description = "Wrappers for the framework AppTrackingTransparency on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AppTrackingTransparency-10.1.tar.gz", hash = "sha256:7d75a1d2c07b4d60e79c014b509f7a217b8e43ffb856b05aac5e12dfb03aa662"}, + {file = "pyobjc_framework_AppTrackingTransparency-10.1-py2.py3-none-any.whl", hash = "sha256:5dee7e163a6b325315410ca4929f1e07162403fc0f62d7d6a8dd504b544e1626"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-audiovideobridging" +version = "10.1" +description = "Wrappers for the framework AudioVideoBridging on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AudioVideoBridging-10.1.tar.gz", hash = "sha256:73d049a9d203541c12a672af37676c8dddf68217a3e9212510544cb457e77db0"}, + {file = "pyobjc_framework_AudioVideoBridging-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:574ead9349db4d37ec6db865cf71d8fdf74d5b4d4b577aa5c56c77a5c17f4fff"}, + {file = "pyobjc_framework_AudioVideoBridging-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e9ea894545e5ed1fa9b772dcea876bdb16dac9300e021a81f8b92ec8ed876efb"}, + {file = "pyobjc_framework_AudioVideoBridging-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:be025276db7bf431361f908c45af631c5c97a138069127ca43e679640fd2b935"}, + {file = "pyobjc_framework_AudioVideoBridging-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a8d470904288e9ea7a9fb758cb704cbbebaec941c1e11d358c5260f117cbcad6"}, + {file = "pyobjc_framework_AudioVideoBridging-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a4ad2cb237ffaa3868eed2ed8869488cb44a8a85a63b2dfe6421be2cb5cbde9e"}, + {file = "pyobjc_framework_AudioVideoBridging-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:41e9ed25b14b30e5eb488a8278fd86cb0973a5677698b534a18c4917b7ec9f9d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-authenticationservices" +version = "10.1" +description = "Wrappers for the framework AuthenticationServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AuthenticationServices-10.1.tar.gz", hash = "sha256:2d686019564f18390ac16d3b225c6c8fead03d929e8cee16942fc532599e15be"}, + {file = "pyobjc_framework_AuthenticationServices-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:364b4f94171c78d5da9172fdf30ef71958da010d923f6fc8f673f8d2e3c8e9ef"}, + {file = "pyobjc_framework_AuthenticationServices-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:09636bd6614d440e0475ba05beba42aa79a73c4fe310e9e79dea4821e57685ae"}, + {file = "pyobjc_framework_AuthenticationServices-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:dd03dc0c4ad5c40a688ceca813b5c05ae99b72e6201a5a700d1d2722eee8fba3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-automaticassessmentconfiguration" +version = "10.1" +description = "Wrappers for the framework AutomaticAssessmentConfiguration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AutomaticAssessmentConfiguration-10.1.tar.gz", hash = "sha256:c8f32f5586f7d7f9fd12343714c7439a1dfad5b5393f403aee440b5f91ef9f7d"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf009cddaa8d62eedaeb4878cc7acab80f4e9bb0bd83a5dff79590bab08b81ce"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c0ea6fa98e07a9cbcd90b7482022be5e1dc99e3170dcf2d4937ab17c5c2879dd"}, + {file = "pyobjc_framework_AutomaticAssessmentConfiguration-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4d47b95c515781fa5553443f38782f5e9b1aa7c1938449bcbb2377776441c54c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-automator" +version = "10.1" +description = "Wrappers for the framework Automator on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Automator-10.1.tar.gz", hash = "sha256:0e95fc90a2930d108d38b61b4365f3678edd5aa25d26598fe39924c890813e80"}, + {file = "pyobjc_framework_Automator-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5c46ca5a97f6193432ad5195f6dfc261d66f70aea8371aa04f5c0ef85eb959f9"}, + {file = "pyobjc_framework_Automator-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e16540ca8f432de665997566e1cf1b43e8c7bea90a3460ab0aaccdb51bdac13c"}, + {file = "pyobjc_framework_Automator-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:0a8b26890e1b0728c7150cd81dfb7c3d091752e71550a5a8db27c703915b7f40"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-avfoundation" +version = "10.1" +description = "Wrappers for the framework AVFoundation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVFoundation-10.1.tar.gz", hash = "sha256:07e065c6904fbd6afc434a79888461cdd4097b4153dd592dcbe9c8bef01ee701"}, + {file = "pyobjc_framework_AVFoundation-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3475f2a5c18cab80a23266470bc7014a88c8e1e8894e96f9f75e960b82679723"}, + {file = "pyobjc_framework_AVFoundation-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fad5c9190d633f51193a62c4354f2fb7be3511c31a0c58f17e351bb30bfadad3"}, + {file = "pyobjc_framework_AVFoundation-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:6ee76be15a6ad7caf9db71c682fb677d29df6c1bb2972ed2f21283f1b3e99f45"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreAudio = ">=10.1" +pyobjc-framework-CoreMedia = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-avkit" +version = "10.1" +description = "Wrappers for the framework AVKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVKit-10.1.tar.gz", hash = "sha256:15779995d4bb3b231a09d9032cf42e8f2681e4271ee677076a08c60a1b45fac7"}, + {file = "pyobjc_framework_AVKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e73da23397f0d9397a5f78223b06a49873d11cce71f06d486316a006220b587"}, + {file = "pyobjc_framework_AVKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2ea71aa0c9230c37da6dab710b237ea67ea16a5ed2cd5f6123a562c8c6b6fa20"}, + {file = "pyobjc_framework_AVKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:3c00b4448d0480e92d7a0dfe62d92d42554ddb45c7183c256931e47dafca1dce"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-avrouting" +version = "10.1" +description = "Wrappers for the framework AVRouting on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-AVRouting-10.1.tar.gz", hash = "sha256:148fc29d0d5e73fb23ed64edede3f74d902ec41b7a7869435816a7a1b37aa038"}, + {file = "pyobjc_framework_AVRouting-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2a2d6524ac870a0b022beb33f0a9ec8870dfb62524d778b7cb54b7946705a3ac"}, + {file = "pyobjc_framework_AVRouting-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:643412674490719dc05c3e4c010e464d50b51e834428e97739510f513ecc008d"}, + {file = "pyobjc_framework_AVRouting-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:103e99db20099331afe637d4bcc39ec7c5d8fe3edefa2dd0a865d6f5d15b0f65"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-backgroundassets" +version = "10.1" +description = "Wrappers for the framework BackgroundAssets on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-BackgroundAssets-10.1.tar.gz", hash = "sha256:0a770f77f7fe6d715cf02e95a5efb70895ee19736cf0fa0ecbb3c320f4fa3430"}, + {file = "pyobjc_framework_BackgroundAssets-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:82bfc758b8c542e0b155a922e0dc901fdcd6b6a7574f4575725cfadb8d248825"}, + {file = "pyobjc_framework_BackgroundAssets-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a4f6e2dea9f2cb507e94e0c3c621e2e6af613770a8595ff17aedb34dc2fa56b4"}, + {file = "pyobjc_framework_BackgroundAssets-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4dfe00a649dd7c7aee0f25daf96c8c35438ed69ec324bcad81d5a87110759a72"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-businesschat" +version = "10.1" +description = "Wrappers for the framework BusinessChat on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-BusinessChat-10.1.tar.gz", hash = "sha256:f361139464532d84bb29d520f2b045a4a63e960d07a0dd574c6c15dd67f890ed"}, + {file = "pyobjc_framework_BusinessChat-10.1-py2.py3-none-any.whl", hash = "sha256:60df5660a9a90a461c68a6cb49326c25e81f3412e951e84be7ccc98b62eb5404"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-calendarstore" +version = "10.1" +description = "Wrappers for the framework CalendarStore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CalendarStore-10.1.tar.gz", hash = "sha256:6274d7eb94353813aefca236276c5b6dc6445a48fff39e832478db17c47e34c1"}, + {file = "pyobjc_framework_CalendarStore-10.1-py2.py3-none-any.whl", hash = "sha256:cbd8ec495d9b13cc986b018d8740e25a4e18a25732ee19de1311f0c30ab53120"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-callkit" +version = "10.1" +description = "Wrappers for the framework CallKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CallKit-10.1.tar.gz", hash = "sha256:9a5165f35e31d98b7d1539c9b979cabd01064926903389fc558cbc71bf86ddd4"}, + {file = "pyobjc_framework_CallKit-10.1-py2.py3-none-any.whl", hash = "sha256:f82e791b2dbae4adfcc596949975573309a0127ba02d4c35743501f6665ec610"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-cfnetwork" +version = "10.1" +description = "Wrappers for the framework CFNetwork on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CFNetwork-10.1.tar.gz", hash = "sha256:898fa3ec863b9d72b3262135e1b0a24bc73879b65c69a2a7b213fe840e2a11de"}, + {file = "pyobjc_framework_CFNetwork-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:82f6fa09d67e25ef1cd92596b25328a6c295341c40a572e899c9e858ce949a1d"}, + {file = "pyobjc_framework_CFNetwork-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c598f18e50480a92df3c69c22cd1752844eb487176ada5e1c1b80670fb05e4eb"}, + {file = "pyobjc_framework_CFNetwork-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:90697ae10c7fb83d81f25d3800f33846329121bedefd495b45d47a0f0d996a73"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-cinematic" +version = "10.1" +description = "Wrappers for the framework Cinematic on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Cinematic-10.1.tar.gz", hash = "sha256:a1210338de5a739b00304555ce15b70b36deebdbd3c6940f8e9531253219edce"}, + {file = "pyobjc_framework_Cinematic-10.1-py2.py3-none-any.whl", hash = "sha256:73408d3bfd9b08389eb6787b0b5df4fe9c351c936fa9b1f95a9c723951e9a988"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-AVFoundation = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreMedia = ">=10.1" +pyobjc-framework-Metal = ">=10.1" + +[[package]] +name = "pyobjc-framework-classkit" +version = "10.1" +description = "Wrappers for the framework ClassKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ClassKit-10.1.tar.gz", hash = "sha256:baf79b1296662525d0fa486d4488720cceebe63595765cfeade61aeb78a4216f"}, + {file = "pyobjc_framework_ClassKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:502949573701363947bf64f7ac9dedab7247037c0e53c7db080c871f3ca52aa8"}, + {file = "pyobjc_framework_ClassKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e6d7c2e8b87b285ce21582c602be23960349e23111c8d02bcc3b9192090b437e"}, + {file = "pyobjc_framework_ClassKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5ac34c1d491e15f81df83b406a281d3176fff8476e053bb8476cad7e4fa102e7"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-cloudkit" +version = "10.1" +description = "Wrappers for the framework CloudKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CloudKit-10.1.tar.gz", hash = "sha256:8f0109f29ac6554c22cc21c06f6fd0a23e3e49556b0ab2532eb1d69ac2a7cd96"}, + {file = "pyobjc_framework_CloudKit-10.1-py2.py3-none-any.whl", hash = "sha256:ffdedaaa8384a64df6b30d45c834dffa002a63b8e74578012b6261780f31c28c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Accounts = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreData = ">=10.1" +pyobjc-framework-CoreLocation = ">=10.1" + +[[package]] +name = "pyobjc-framework-cocoa" +version = "10.1" +description = "Wrappers for the Cocoa frameworks on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Cocoa-10.1.tar.gz", hash = "sha256:8faaf1292a112e488b777d0c19862d993f3f384f3927dc6eca0d8d2221906a14"}, + {file = "pyobjc_framework_Cocoa-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e82c2e20b89811d92a7e6e487b6980f360b7c142e2576e90f0e7569caf8202b"}, + {file = "pyobjc_framework_Cocoa-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0860a9beb7e5c72a1f575679a6d1428a398fa19ad710fb116df899972912e304"}, + {file = "pyobjc_framework_Cocoa-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:34b791ea740e1afce211f19334e45469fea9a48d8fce5072e146199fd19ff49f"}, + {file = "pyobjc_framework_Cocoa-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1398c1a9bebad1a0f2549980e20f4aade00c341b9bac56b4493095a65917d34a"}, + {file = "pyobjc_framework_Cocoa-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:22be21226e223d26c9e77645564225787f2b12a750dd17c7ad99c36f428eda14"}, + {file = "pyobjc_framework_Cocoa-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0280561f4fb98a864bd23f2c480d907b0edbffe1048654f5dfab160cea8198e6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" + +[[package]] +name = "pyobjc-framework-collaboration" +version = "10.1" +description = "Wrappers for the framework Collaboration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Collaboration-10.1.tar.gz", hash = "sha256:e85c6bd8b74b1707f66847ed71de077565d5e9fe6e7ed4db3cdafc2408723da5"}, + {file = "pyobjc_framework_Collaboration-10.1-py2.py3-none-any.whl", hash = "sha256:9a2137aaed1ad71bf6c92c7c275253c2dc6f0062af9d2d8a1590d00bf30c1ecb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-colorsync" +version = "10.1" +description = "Wrappers for the framework ColorSync on Mac OS X" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ColorSync-10.1.tar.gz", hash = "sha256:2c6ee65dfca6bc41f0e9dffaf1adebc78a7fb5cee63740b092ade226710c1c32"}, + {file = "pyobjc_framework_ColorSync-10.1-py2.py3-none-any.whl", hash = "sha256:58596365b270453c3ce10bb168383c615321fa377a983eba3021f664c98f852a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-contacts" +version = "10.1" +description = "Wrappers for the framework Contacts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Contacts-10.1.tar.gz", hash = "sha256:949d61ff7f4f07956949f8945ad627ffa89cce3d10af9442591e519791a25cc4"}, + {file = "pyobjc_framework_Contacts-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b10c068b5a79fcb0240ea4cd1048162277f36567a84333a0bd0168f851168f99"}, + {file = "pyobjc_framework_Contacts-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a3bb6fb24deae41a0879ac321e6401b43e5fbedba0a75ced67b2048a4852c3ff"}, + {file = "pyobjc_framework_Contacts-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:16556f06202b1b4fd9da8e3186b6140b582a4032437cdab2f5f8b32b24f3e3ed"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-contactsui" +version = "10.1" +description = "Wrappers for the framework ContactsUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ContactsUI-10.1.tar.gz", hash = "sha256:0b97e4c5599ab269f53597dd8f47a45599434c833e72185d5d3a257413a6faf4"}, + {file = "pyobjc_framework_ContactsUI-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5c70ff6b07e48331f25138bc159f7215d9b5d6825da844fec26ba403aad53f52"}, + {file = "pyobjc_framework_ContactsUI-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:876c1280fcb13c89a5fd89e7c3ace04bfd3c3b418cb64b6579dcbee1e9156377"}, + {file = "pyobjc_framework_ContactsUI-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a5ee22f1e893eb79633ed425972e50c5ec9b0a1d20cf6fbf21bf68d1bbfec436"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Contacts = ">=10.1" + +[[package]] +name = "pyobjc-framework-coreaudio" +version = "10.1" +description = "Wrappers for the framework CoreAudio on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreAudio-10.1.tar.gz", hash = "sha256:713ca82fc363ea6cf373d2db0b183f39058bcadceb8229d9e8839b783104f8e2"}, + {file = "pyobjc_framework_CoreAudio-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9348f613a1f35bbeb7d1d899e2ee3876881cd0433e59f584f30ba96e179d960a"}, + {file = "pyobjc_framework_CoreAudio-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0192dddd2f99db51cdb0959e80f29f9f531ba8bd0421e06ae9212f34a05c48a"}, + {file = "pyobjc_framework_CoreAudio-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b1a4612ce87dfcca3c939ec5885d4578955f5ff4d017f95d4459d5fb3bdc8970"}, + {file = "pyobjc_framework_CoreAudio-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:678d2b916850daf7fe38a95af0f73b4dd39b463ea87ec36fe287d81d050c31f7"}, + {file = "pyobjc_framework_CoreAudio-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:807fa54de91d53ff64537e50aa123c5b262952c57eea6928ecb3d526078229c2"}, + {file = "pyobjc_framework_CoreAudio-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db149cabae1b91ea437536e1741b6e7573a71ec2aae4274318172936a5ac7190"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coreaudiokit" +version = "10.1" +description = "Wrappers for the framework CoreAudioKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreAudioKit-10.1.tar.gz", hash = "sha256:85472aaee6360940f679a5e068b5a21160f8cee676d9fd0937b43b39c447d78e"}, + {file = "pyobjc_framework_CoreAudioKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bde3012be239328fdc928d0ff9da9f4627e6ab4832e05faaa0c0ea4e11078d14"}, + {file = "pyobjc_framework_CoreAudioKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:43e9643ce390e36c64dca98a1bbcb0c2c282c527d31eb52aa2b7a18e2f7c97d1"}, + {file = "pyobjc_framework_CoreAudioKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:65bb2c5870b1739703fce056cdc4daddcdcf644c1ddcb590e4b88b5ed2fc45a4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreAudio = ">=10.1" + +[[package]] +name = "pyobjc-framework-corebluetooth" +version = "10.1" +description = "Wrappers for the framework CoreBluetooth on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreBluetooth-10.1.tar.gz", hash = "sha256:81f50fcd9ee24332f1ad85798d489cfc05be739fcc1389caa6d682e034215efd"}, + {file = "pyobjc_framework_CoreBluetooth-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:145540ae4f35992774e559840a778554f3d3d29b359ff6d7f450c954cacccf0f"}, + {file = "pyobjc_framework_CoreBluetooth-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1ef97e8479895048fa96d5afa2f88139a8432158d6b0fb80ad1db03666c1d4ad"}, + {file = "pyobjc_framework_CoreBluetooth-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e8bce7f425caa55a87b7519eff03eaa7d08ff5e5e09e9318706d3f5087b63b08"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coredata" +version = "10.1" +description = "Wrappers for the framework CoreData on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreData-10.1.tar.gz", hash = "sha256:01dfbf2bfdaa4e0aa3e636025dc868ddb62aedf710890e6af94106278f1659aa"}, + {file = "pyobjc_framework_CoreData-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ff280c893c6d472168696fa0732690809af2694167081b5db87395c25cdf6e27"}, + {file = "pyobjc_framework_CoreData-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c51d8e4723ed113684d0ddd4240900a937682859a9e75d830f35783098f04e95"}, + {file = "pyobjc_framework_CoreData-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a8a8d509ff17f65f4cec7fb35d77a21937f2c8232b5ce357e783cbf971616ad9"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-corehaptics" +version = "10.1" +description = "Wrappers for the framework CoreHaptics on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreHaptics-10.1.tar.gz", hash = "sha256:87c1913078963b2d7dba231839566f831f47499c4c029f8beaa46209630e75e1"}, + {file = "pyobjc_framework_CoreHaptics-10.1-py2.py3-none-any.whl", hash = "sha256:ae6143c041b0846a58199826c0094cfb2fb9080f139c93e6b63f51a6b2766552"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-corelocation" +version = "10.1" +description = "Wrappers for the framework CoreLocation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreLocation-10.1.tar.gz", hash = "sha256:f43637443c4386233c52b0af3131a545968229543f7b0050764298cac1604fd8"}, + {file = "pyobjc_framework_CoreLocation-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:66bad050f37966526017763e5a8c424e257a0974cfbe0c8875fa149bdc1d41c2"}, + {file = "pyobjc_framework_CoreLocation-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:eaa4ff8e3cc388f045a6c15f3ee5a950860164f6fb5a13aed29e37b6cb481607"}, + {file = "pyobjc_framework_CoreLocation-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f4dcf52c4934e99b20056f2ebc8398c9f8f8a61ceac0e5de1e2bb719b0f844c2"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coremedia" +version = "10.1" +description = "Wrappers for the framework CoreMedia on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMedia-10.1.tar.gz", hash = "sha256:8210d03ff9533d5cef3244515c1aa4bb54abaeb93dfc20be6d87e3a6b3377b36"}, + {file = "pyobjc_framework_CoreMedia-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7bd43d614f0427732ce1690e0640f1d7a40a73dd90142ac08c5dab2ba0d49e8d"}, + {file = "pyobjc_framework_CoreMedia-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5c1f849ce7de96da6fc81dc8975ecf04444c7179129976b3fe064d9f85a91082"}, + {file = "pyobjc_framework_CoreMedia-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ab9e44e0d3ce584a119c3b3d539de9d228b635cb98bf60f1e1a221f8aa20681e"}, + {file = "pyobjc_framework_CoreMedia-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fffc355c038dfaa83f7d7c01497fb20590f9090421564b275cd8fd12e8e10e8e"}, + {file = "pyobjc_framework_CoreMedia-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e4e9221c5a4c0b7ff7484eb8a21e06be0cafc1c95b9bcc27a57c139b64692dbe"}, + {file = "pyobjc_framework_CoreMedia-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b61e986b686d4e928208c26a4a2163210e101fcec56eeb61d62b969802eaa8ca"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coremediaio" +version = "10.1" +description = "Wrappers for the framework CoreMediaIO on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMediaIO-10.1.tar.gz", hash = "sha256:c07177c58c88b6de229f88f3b88b4d97bfc59d2406f751b5aff6bed5cac4d938"}, + {file = "pyobjc_framework_CoreMediaIO-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d44781e21632f3af8eab86194b2fe32ce235b6c6a03ff87f09e0ba034a1e7a73"}, + {file = "pyobjc_framework_CoreMediaIO-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c97bd803a17204a2ec2d9f22c14176009067359efe80b9df69e8ec197783091c"}, + {file = "pyobjc_framework_CoreMediaIO-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d137b0eef1533af244c70ab02f1ed5716dcc8739b0ba6b6c703d36a61d9bab2e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coremidi" +version = "10.1" +description = "Wrappers for the framework CoreMIDI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMIDI-10.1.tar.gz", hash = "sha256:e2e407dcb9d5ed53e0a8ed4622429a56c9770c26e2e4455dcb76a6620a12eba6"}, + {file = "pyobjc_framework_CoreMIDI-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b632773bea0c943a1f733166aece7560f32237a42706124d1f001b10620c4bcc"}, + {file = "pyobjc_framework_CoreMIDI-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:065cf89ee58b01700780fbbed0c00e1c5f5f383ac3d54f31642ee6d59e3c03c2"}, + {file = "pyobjc_framework_CoreMIDI-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8984ad04837efc8bc7cbf1d48ff3433eb7fea1d298ed8b72344ec1641826df48"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coreml" +version = "10.1" +description = "Wrappers for the framework CoreML on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreML-10.1.tar.gz", hash = "sha256:4cda220d5ad5b677a95d4d29256b076b411b692b64219c2dcb81c702fc34d57d"}, + {file = "pyobjc_framework_CoreML-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:fb85e653a0f7984fff908890b7988d9d5ac42ff92b213cd9371bb255982ee787"}, + {file = "pyobjc_framework_CoreML-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a361d35c75e749a975330f7647084a58c2166f076ecc5573491542b96bc84c28"}, + {file = "pyobjc_framework_CoreML-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4c3b43da4afb279b02bbb6a57a3c5fb4d24ad6d48ef40c20efcda783e41077d2"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coremotion" +version = "10.1" +description = "Wrappers for the framework CoreMotion on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreMotion-10.1.tar.gz", hash = "sha256:907a2f6da592f61d49f06559b34fc5addd8c0f2b85f9f277c5e4ea5d95247b67"}, + {file = "pyobjc_framework_CoreMotion-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:630487c14e22c0c05ddc33a149db673d8a28a876b59a78ed672f1a4825ebf40e"}, + {file = "pyobjc_framework_CoreMotion-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2219096ceb41aea91819df747c08059885f94ca14c66a078d3161ba49c1cb56e"}, + {file = "pyobjc_framework_CoreMotion-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:de694971994df2791047c2a1039556ea54683fd09cdc30c23ee5891c63414232"}, + {file = "pyobjc_framework_CoreMotion-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81b138a672519ecbea8a2f2392a7f015f3d7caf150368f83b3b278cb60743e8c"}, + {file = "pyobjc_framework_CoreMotion-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b6cbbee7d26ef1837e382566316cb5d5fae6bca10418437608ebc312f396f898"}, + {file = "pyobjc_framework_CoreMotion-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9968b07199532b1c4ff56d1d6a6195e8ce8bc2beabbf55dc53193f473b3741f9"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coreservices" +version = "10.1" +description = "Wrappers for the framework CoreServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreServices-10.1.tar.gz", hash = "sha256:43d507f2b3d84a5aab808c3f67bf21fb6a7d1367d506e2e9877bf1cac9bad2eb"}, + {file = "pyobjc_framework_CoreServices-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:73162798c506f6d767e2684d7c739907c96a5547045d42e01bee47639130b848"}, + {file = "pyobjc_framework_CoreServices-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:eba7abba8f5ba194a5ef1ffeb5f9d3c0daa751e07ef0d3662e35e27e75a24d73"}, + {file = "pyobjc_framework_CoreServices-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:924bca5a67e9046e8c6146dbc1301fe22c2a1bd49bef92358fd6330ef19cfa65"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-FSEvents = ">=10.1" + +[[package]] +name = "pyobjc-framework-corespotlight" +version = "10.1" +description = "Wrappers for the framework CoreSpotlight on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreSpotlight-10.1.tar.gz", hash = "sha256:b50e13d55d90b88800c2cc2955c000ea6b1de6481ff6e0092c7b7bf94fceea69"}, + {file = "pyobjc_framework_CoreSpotlight-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:421e18ded971c212fd3f2878658c209c81d1f8859eb62dd6a965abcb19a4ce5a"}, + {file = "pyobjc_framework_CoreSpotlight-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c775e0d42ee21f7d6b9374b01df1a0d4ece0b765e99c7011cb2ea74a2c2ef275"}, + {file = "pyobjc_framework_CoreSpotlight-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7dc11f607429e21c2aee18f950cdde141a414c874369dbb66920930802cfd0fa"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-coretext" +version = "10.1" +description = "Wrappers for the framework CoreText on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreText-10.1.tar.gz", hash = "sha256:b6a112e2ae8720be42af19e0fe9b866b43d7e9196726caa366d61d18294e6248"}, + {file = "pyobjc_framework_CoreText-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6ea2920c126a8a39e8a13b6de731b78b391300cec242812c9fbcf65a66ae40cf"}, + {file = "pyobjc_framework_CoreText-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:37b203d832dd82bd9566c72eea815eb89f00f128a4c9a2f352843914da4effec"}, + {file = "pyobjc_framework_CoreText-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:083700483b18f337b0c43bdfaafc43467846f8555075669d4962d460d9d6cd00"}, + {file = "pyobjc_framework_CoreText-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59472cd1a33e83803fa62b3db20ac0e899f5ed706d22704ea81129d3f49ff0c7"}, + {file = "pyobjc_framework_CoreText-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:cc1a63ca2c6921768b1c794ce60e2a278e0177731aa4bf8f620fdde857e4835e"}, + {file = "pyobjc_framework_CoreText-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fbbdde4ce747bcad45c2aded36167ad00fead309a265d89ab22289c221038e57"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-corewlan" +version = "10.1" +description = "Wrappers for the framework CoreWLAN on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CoreWLAN-10.1.tar.gz", hash = "sha256:a4316e992521878fb75ccff6bd633ee9c9a9bf72d5e2741e8804b43e8eeef8ac"}, + {file = "pyobjc_framework_CoreWLAN-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e5374ebd6e258d6cdaa9fdeb21c10830c50fc1c00eaa91b2293833b0182479f7"}, + {file = "pyobjc_framework_CoreWLAN-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:57cb3fbb69500df92a111655c129b0f658ec16e14e57b08b9c1ef400f33f3bb5"}, + {file = "pyobjc_framework_CoreWLAN-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:eb2360b20ab14a0f6cc7d06dc7bf2b0832d0c95892d9f364e03c6ecf77dfc328"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-cryptotokenkit" +version = "10.1" +description = "Wrappers for the framework CryptoTokenKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-CryptoTokenKit-10.1.tar.gz", hash = "sha256:ad8fb3c4f314cc5f35cd26a5e3fdd68dd71ea0f7b063f31cffb9d78050ce76f0"}, + {file = "pyobjc_framework_CryptoTokenKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e691877b2e96c8f257873943147315561cda79b63c911afa8d0103d6b351a88f"}, + {file = "pyobjc_framework_CryptoTokenKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5a4ce0650ad70eedadc46091d61878e28a4cf491d1c2e8da32feab2f661a4ee5"}, + {file = "pyobjc_framework_CryptoTokenKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c0013c7795d208547d9f92c0539bc7fec09ca049d791458b62c177585552abc4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-datadetection" +version = "10.1" +description = "Wrappers for the framework DataDetection on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DataDetection-10.1.tar.gz", hash = "sha256:f81d1ca971aa8034faeb6e457144df0832f870d7e19905886593bafe4cbfe21f"}, + {file = "pyobjc_framework_DataDetection-10.1-py2.py3-none-any.whl", hash = "sha256:f23fa282297ed385c9df384a0765e4f9743b8916de8a58137f981ab0425b80f5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-devicecheck" +version = "10.1" +description = "Wrappers for the framework DeviceCheck on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DeviceCheck-10.1.tar.gz", hash = "sha256:f89e3acd15ec48134f95bf027778ca1d7e3088b9c2204e48f8c6e3bdcb28cf82"}, + {file = "pyobjc_framework_DeviceCheck-10.1-py2.py3-none-any.whl", hash = "sha256:31d5a83d85a4d95e238f432ac66cbf110a7b70afa82fd230ab4b911a5e2b9cb4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-dictionaryservices" +version = "10.1" +description = "Wrappers for the framework DictionaryServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DictionaryServices-10.1.tar.gz", hash = "sha256:03b3b19a9b911beb3bdc8809f5d02356a497a75dbefa0f355825ec610c050a3e"}, + {file = "pyobjc_framework_DictionaryServices-10.1-py2.py3-none-any.whl", hash = "sha256:7ace031cc3df1fa9a4eb663ff55eee0a4c7c8c34653aa1529988d579d273150b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-CoreServices = ">=10.1" + +[[package]] +name = "pyobjc-framework-discrecording" +version = "10.1" +description = "Wrappers for the framework DiscRecording on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiscRecording-10.1.tar.gz", hash = "sha256:321c69b6494c57d75d4a0ecf5d90ceac3800441bf877eac8196ab25dcf15ebde"}, + {file = "pyobjc_framework_DiscRecording-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:581141bd645436d009cc6b42ca6255322de9a3a36052e7dcf497e90959c7bc77"}, + {file = "pyobjc_framework_DiscRecording-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2c4144c4d1d7bf7ad537c6159cefb490876b7eff62ec95d4af7bc857813b95cd"}, + {file = "pyobjc_framework_DiscRecording-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:1c52f7ace5936edbe160aa4d6cb456a49e7bc1a43a5e34572d48cd65dee22d1e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-discrecordingui" +version = "10.1" +description = "Wrappers for the framework DiscRecordingUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiscRecordingUI-10.1.tar.gz", hash = "sha256:2a44278b19738e8d4f2180433df37b59a0b645d9ddc0f3c3a6c81e506afc1953"}, + {file = "pyobjc_framework_DiscRecordingUI-10.1-py2.py3-none-any.whl", hash = "sha256:684925119e4c8f8ea43cfede1a3e39f785b5aa94a48f1aa7a98fd4cdc4c1d2e3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-DiscRecording = ">=10.1" + +[[package]] +name = "pyobjc-framework-diskarbitration" +version = "10.1" +description = "Wrappers for the framework DiskArbitration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DiskArbitration-10.1.tar.gz", hash = "sha256:c3ab3dc91375dabaf4d3470e01358e4acfecf6b425abf9ad95a26a7a56398f56"}, + {file = "pyobjc_framework_DiskArbitration-10.1-py2.py3-none-any.whl", hash = "sha256:a3bd1883b60aa1d8cff3bc18957f81ed14e5d0406d18a4a9095539ddf51dd72e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-dvdplayback" +version = "10.1" +description = "Wrappers for the framework DVDPlayback on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-DVDPlayback-10.1.tar.gz", hash = "sha256:2af92907a50a47c44a8dd1521217a564ad9a3dd9e9056f0a76b13275d380bee1"}, + {file = "pyobjc_framework_DVDPlayback-10.1-py2.py3-none-any.whl", hash = "sha256:bcbfb832a3f04e47aef03606a21fd58458bc28e25e1a444e7a9388bfee2f9dd3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-eventkit" +version = "10.1" +description = "Wrappers for the framework Accounts on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-EventKit-10.1.tar.gz", hash = "sha256:53473df48000b39dec276e3b8ead88b153c66cf0fdc07b016f759b42f0b2ec50"}, + {file = "pyobjc_framework_EventKit-10.1-py2.py3-none-any.whl", hash = "sha256:3265ef0bfab38508c50febfa922b4abf6ebc55a44f105d499e19231c225a027c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-exceptionhandling" +version = "10.1" +description = "Wrappers for the framework ExceptionHandling on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExceptionHandling-10.1.tar.gz", hash = "sha256:ac75ac230249d6f26f750beb406c133a49d4a284e7ee62ba1139e9d9bc5ec44d"}, + {file = "pyobjc_framework_ExceptionHandling-10.1-py2.py3-none-any.whl", hash = "sha256:b8eb9142f141361982e498610bfd33803acb4f471f80b5cd9df8d382142449e9"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-executionpolicy" +version = "10.1" +description = "Wrappers for the framework ExecutionPolicy on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExecutionPolicy-10.1.tar.gz", hash = "sha256:5ab7da37722b468a5e230354fa45a6a96e545c6c2aab5a76029e2227b1bae326"}, + {file = "pyobjc_framework_ExecutionPolicy-10.1-py2.py3-none-any.whl", hash = "sha256:556aa28220438b64e6f75f539f133616a343abe3e2565f0d016091f4dc4a9c3d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-extensionkit" +version = "10.1" +description = "Wrappers for the framework ExtensionKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExtensionKit-10.1.tar.gz", hash = "sha256:4f0a5256149502eeb1b4e1af1455de629a3c3326aaf4d766937212e56355ad58"}, + {file = "pyobjc_framework_ExtensionKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:82eece8d6d807bafb5cf8a220c58f2b42b350a0bc9131cb0cdfd29e90294858d"}, + {file = "pyobjc_framework_ExtensionKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9936cfe8a17c09457aa10c21f90f77151328596bd72b55fd9b6c3e78a11384"}, + {file = "pyobjc_framework_ExtensionKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:94cdc24e19ed554bc1d8e9d11139839033b26997f5b29a381ed4be8633ad2569"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-externalaccessory" +version = "10.1" +description = "Wrappers for the framework ExternalAccessory on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ExternalAccessory-10.1.tar.gz", hash = "sha256:1c206f2e27aedb0258a3cf425ed89cbea0657521829f061362b4fca586e033a8"}, + {file = "pyobjc_framework_ExternalAccessory-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:519c36e63011a797f1747306132957168eed53456e801973c38c52b06b265a0e"}, + {file = "pyobjc_framework_ExternalAccessory-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8c54367b52cc74231df057c9bbf722896d98efd91f6d6a7415e0ca7227f311b9"}, + {file = "pyobjc_framework_ExternalAccessory-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:64facb48377840e72e459f9ae88d482d0d17a1726733b2d79205de4e4449eb89"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-fileprovider" +version = "10.1" +description = "Wrappers for the framework FileProvider on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FileProvider-10.1.tar.gz", hash = "sha256:617811be28bd5d9b0cc87389073ade7593f89ee342a5d6d5ce619912748d8e00"}, + {file = "pyobjc_framework_FileProvider-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9e98efd27f69c8275dc8220dfb2bb41a486400c1fb839940cd298b8d1e44adca"}, + {file = "pyobjc_framework_FileProvider-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7046144d86b94707fea6d8bb00b2850f99e0ebaef136ee2b3db884516b585529"}, + {file = "pyobjc_framework_FileProvider-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2f4c816b87237ab2ddfb0314296e5824411cec11f9c1b5919f8b4e8c02069ff1"}, + {file = "pyobjc_framework_FileProvider-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8476daf2c291f6bc1c9f4a26f4492236a2e427774fd02a03c561c667e9ec0931"}, + {file = "pyobjc_framework_FileProvider-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:de3dcbe70943b3bf057f634be4003fdcc112e3d7296f1631be1bf20f494db212"}, + {file = "pyobjc_framework_FileProvider-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a53ebe7e4a6ef24cf3ade1c936a96a1cb0c40dd7639899e3e238e050d4813417"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-fileproviderui" +version = "10.1" +description = "Wrappers for the framework FileProviderUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FileProviderUI-10.1.tar.gz", hash = "sha256:e8f40b41f63d51401fb2aa5881dbf57ef6eacaa6c4d95f3dd1d9eb1b392a2d84"}, + {file = "pyobjc_framework_FileProviderUI-10.1-py2.py3-none-any.whl", hash = "sha256:ef85cead617c3e9b851589505503d201197bbc0ee27117a77243a1a4e99fad7d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-FileProvider = ">=10.1" + +[[package]] +name = "pyobjc-framework-findersync" +version = "10.1" +description = "Wrappers for the framework FinderSync on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FinderSync-10.1.tar.gz", hash = "sha256:5b1fb13c10d0f9bf8bccdacd0ecd894d79376747bd13aca5a410f65306bcbc29"}, + {file = "pyobjc_framework_FinderSync-10.1-py2.py3-none-any.whl", hash = "sha256:e5a5ff1e7d55edb5208ce04868fcf2f92611053476fbbf8f48daa3064d56deb5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-fsevents" +version = "10.1" +description = "Wrappers for the framework FSEvents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-FSEvents-10.1.tar.gz", hash = "sha256:f5dee8cfbd878006814db264c5f70aeb1a43c06620e98f628ca6c0008beb1b1d"}, + {file = "pyobjc_framework_FSEvents-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3c2bf6ffd49fd21df73a39e61b81d7c6651e1063f72b62b2218c6ab4bf91dc02"}, + {file = "pyobjc_framework_FSEvents-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e57dccf14720c0789811580cb99e325353259cc96514e2622ca512e70f392c2"}, + {file = "pyobjc_framework_FSEvents-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f026f417b25f804c6994d49af0743177ca7119d5d9e885a80d71c624e12a5d47"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-gamecenter" +version = "10.1" +description = "Wrappers for the framework GameCenter on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameCenter-10.1.tar.gz", hash = "sha256:0d124b3f18bb1b3e134268c99bf92c29791e8c62a97095c1fb1eb912ebe495e0"}, + {file = "pyobjc_framework_GameCenter-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5d6cf2405e5107c8befcb61a5339c0766fbab9448a2c4e8f5dd4401a7ef380ab"}, + {file = "pyobjc_framework_GameCenter-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ede397f1ca31022e7507cb1cf801617094e407300ee29c19415fd32f64fa758"}, + {file = "pyobjc_framework_GameCenter-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:db4e36a573e91715d8ed5250f6784fe5b03c8b2e769b97f8cf836eb7111c3777"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-gamecontroller" +version = "10.1" +description = "Wrappers for the framework GameController on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameController-10.1.tar.gz", hash = "sha256:50a17fdd151f31b3a5eae9ae811f6f48680316a5c2686413b9a607c25b6be4bc"}, + {file = "pyobjc_framework_GameController-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ec7e84c2dbc90065db8d0293c29e34d95b4fa14beeb3fb3c818fa3bcdf24d89a"}, + {file = "pyobjc_framework_GameController-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:672d99f849b803c6c6b8e89445e2c446379ae23f1f0f7e355a2a94f91d591fea"}, + {file = "pyobjc_framework_GameController-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:c56232fc2f0e95901e66534d18a008857c59363078ac75fedb2d18dcbd5dda63"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-gamekit" +version = "10.1" +description = "Wrappers for the framework GameKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameKit-10.1.tar.gz", hash = "sha256:6fa87a29556cdaf78c86851fc61edb6d384f1a7370a75a66bdd208ed1250899f"}, + {file = "pyobjc_framework_GameKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bb00618d256f67b6f784b49db78bde80677a2004af4558266009de30e8804660"}, + {file = "pyobjc_framework_GameKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:38bcce65b781c5967eb7985b551ca015cda89903d18f29eab74518a52f626fec"}, + {file = "pyobjc_framework_GameKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:773e6f645731dac7c2b6e55ec7ecd92928b070f7a33b4c5ce33a3a52565ecd49"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-gameplaykit" +version = "10.1" +description = "Wrappers for the framework GameplayKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-GameplayKit-10.1.tar.gz", hash = "sha256:12a5d1dc59668df155436250476af029b1765ca68e7a1e2d440158e7130232a3"}, + {file = "pyobjc_framework_GameplayKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f877d2c449aea09187856540b3d5a3e6a98573673a09af6163b1217040d93e5f"}, + {file = "pyobjc_framework_GameplayKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:86e320c79707ab7a3de2f23d0d32bd151b685865f43d13fb58daa2963b4da5cc"}, + {file = "pyobjc_framework_GameplayKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:93a1e62c2875d7705d1aa70115f646258ecffc4d4702ed940a5214dc0ea580f5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-SpriteKit = ">=10.1" + +[[package]] +name = "pyobjc-framework-healthkit" +version = "10.1" +description = "Wrappers for the framework HealthKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-HealthKit-10.1.tar.gz", hash = "sha256:9479c467514c506f9083889f11da6b8f34d705f716ffe9cbbb5a3157000d24de"}, + {file = "pyobjc_framework_HealthKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b32171f6d4ee6fa37718f5b299c6b866a4ae395ff8764ccc040b9d1263a3e74f"}, + {file = "pyobjc_framework_HealthKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a628c777c02df6c5dbbc5f26576f52239dab79ac1afe5ca53d40d561d55adb52"}, + {file = "pyobjc_framework_HealthKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:4d44c5ace78dce1f0c76b96010d9446b90a9474946a25bfb33d373a152e22524"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-imagecapturecore" +version = "10.1" +description = "Wrappers for the framework ImageCaptureCore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ImageCaptureCore-10.1.tar.gz", hash = "sha256:29b85ee9af77bba7e1ea9191bf84edad39d07681b9bd267c8f5057db3b0cdd64"}, + {file = "pyobjc_framework_ImageCaptureCore-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8d2dbc09aed984f7d92e7b835e87608d356f5f4b6dd03e84258963391791ae5"}, + {file = "pyobjc_framework_ImageCaptureCore-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5eef95798d340000ddfb9c59c9468b75bb4cd389d311fd27078c3f4a4a3af29a"}, + {file = "pyobjc_framework_ImageCaptureCore-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:832cbe0d957935553183586556d2036cfcc9aae593defe71e6a0726e5c63abf6"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-inputmethodkit" +version = "10.1" +description = "Wrappers for the framework InputMethodKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InputMethodKit-10.1.tar.gz", hash = "sha256:b995f43a8859016474098c894c966718afe9fbcc18996ce3c6bebfc6a64cfad7"}, + {file = "pyobjc_framework_InputMethodKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5288d12d1a2a6da9261c0cadbee03f31c80a0a3bb77645b4e7c2836864f54533"}, + {file = "pyobjc_framework_InputMethodKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e04ac004ac848492242fda193e63322abce87ecdef081f1b7268cac7f2af8ad"}, + {file = "pyobjc_framework_InputMethodKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:538d5955a8ab3a9c7a7286c72dba87634ba0babe7cd0a4cec335100df8789c01"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-installerplugins" +version = "10.1" +description = "Wrappers for the framework InstallerPlugins on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InstallerPlugins-10.1.tar.gz", hash = "sha256:4c9f8b46d43f1277aad3bea648f84754e9f48251a6fb385ad8119c1b44dffe9b"}, + {file = "pyobjc_framework_InstallerPlugins-10.1-py2.py3-none-any.whl", hash = "sha256:195e7d559421bf36479b085bf74d56f8549fff715596fc21e0e0c95989a3149a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-instantmessage" +version = "10.1" +description = "Wrappers for the framework InstantMessage on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-InstantMessage-10.1.tar.gz", hash = "sha256:8e8e7e2c64a3a6b0aa67cace58f7cea1971bb93de57be40b7ba285e305fab0b5"}, + {file = "pyobjc_framework_InstantMessage-10.1-py2.py3-none-any.whl", hash = "sha256:c03a9a99faaa14ff0a477114b691d628117422a15995523deb25ff2d1d07a36d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-intents" +version = "10.1" +description = "Wrappers for the framework Intents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Intents-10.1.tar.gz", hash = "sha256:85a84a5912b8d8a876767ca8fa220dc24bf1c075ed81b58c386d25c835cec804"}, + {file = "pyobjc_framework_Intents-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:64a3cef4af536de1153937d99a4cb8d0568ca20ee5c74458dca4f270b01a3c1a"}, + {file = "pyobjc_framework_Intents-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:89f49f82245d4facb329dd65434a602506246e6585f544ab78b0ab4bd151f4f7"}, + {file = "pyobjc_framework_Intents-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:bc6f93d151c4474150b6c76fe43067d2d0d06446851d66df3bb9968682a2d225"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-intentsui" +version = "10.1" +description = "Wrappers for the framework Intents on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IntentsUI-10.1.tar.gz", hash = "sha256:01948fbd8f956a79d3c2e27f75bc9954ad12cb4113982f58654122cfa8095ebb"}, + {file = "pyobjc_framework_IntentsUI-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e829659751ff47e3b85980075897ddebbf62d5644478c1bb2ff1dcdc116b8897"}, + {file = "pyobjc_framework_IntentsUI-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0d6ac451433ec0602c661b32216cd3c44b1c99b9f41781b3af79b7941118552"}, + {file = "pyobjc_framework_IntentsUI-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1806e6189cf09c0b031594ad445da1a93c30c399298c6fce2369a49bac7eade4"}, + {file = "pyobjc_framework_IntentsUI-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:33518f549b6c501d7c6c36542154ae5d2255d7223804470e14cd76b325676a48"}, + {file = "pyobjc_framework_IntentsUI-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:91498d3cf4098fe412ea66c01b080919906dd23d53653d49addc7a26c50e570f"}, + {file = "pyobjc_framework_IntentsUI-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c73626bfad3f098eed4a446cee90154dec39d9a9c0775532980c5266bc91a4c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Intents = ">=10.1" + +[[package]] +name = "pyobjc-framework-iobluetooth" +version = "10.1" +description = "Wrappers for the framework IOBluetooth on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOBluetooth-10.1.tar.gz", hash = "sha256:9a30554f1c9229ba120b2626d420fb44059e4aa7013c11f36ab9637dc3aba21f"}, + {file = "pyobjc_framework_IOBluetooth-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5fd71294464b9d891d3a7ebb674bcc462feb6fbdf33ebd08c1411ef104460f7f"}, + {file = "pyobjc_framework_IOBluetooth-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:88e44f1bb3495c3104d9b0a73b2155e4326366c5f08a6e31ef431ab17f342b24"}, + {file = "pyobjc_framework_IOBluetooth-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:15c1a049e1431996188352784defe54a37181da38a7e5a378fcda77fdc007aea"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-iobluetoothui" +version = "10.1" +description = "Wrappers for the framework IOBluetoothUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOBluetoothUI-10.1.tar.gz", hash = "sha256:979c0d9638c0f31e62afe90d8089e61a912d08e0db893a47d3e423b9b23e0db2"}, + {file = "pyobjc_framework_IOBluetoothUI-10.1-py2.py3-none-any.whl", hash = "sha256:809eeb98ce71d0d4a7538fb77f14d1e7cd2c2b91c10605fb8c0d69dbac205de5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-IOBluetooth = ">=10.1" + +[[package]] +name = "pyobjc-framework-iosurface" +version = "10.1" +description = "Wrappers for the framework IOSurface on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-IOSurface-10.1.tar.gz", hash = "sha256:e41c635c5e259019df243da8910675db10480a36d0c316539a8ab3fa0d941000"}, + {file = "pyobjc_framework_IOSurface-10.1-py2.py3-none-any.whl", hash = "sha256:46239320148b82c1f2364d5468999b48fa9c94fc404aff6c5451d23896ece694"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-ituneslibrary" +version = "10.1" +description = "Wrappers for the framework iTunesLibrary on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-iTunesLibrary-10.1.tar.gz", hash = "sha256:18766b2fb016d33cde8ec2c81b05ddfb77d65cb8c92e16864d0c288edd02e812"}, + {file = "pyobjc_framework_iTunesLibrary-10.1-py2.py3-none-any.whl", hash = "sha256:043a2ede182f41a3ca70be50bf95f18641e2945f0077797ff2bb42a3e1984e37"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-kernelmanagement" +version = "10.1" +description = "Wrappers for the framework KernelManagement on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-KernelManagement-10.1.tar.gz", hash = "sha256:da8ac0e6a2de33b823e07ce0462a64340cfebd04f24426b1022374933bbd8d0a"}, + {file = "pyobjc_framework_KernelManagement-10.1-py2.py3-none-any.whl", hash = "sha256:923ff2bbab35a92b9becd9762348f6f690fa463ef07a0e5c4a2b8eb1d3e096af"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-latentsemanticmapping" +version = "10.1" +description = "Wrappers for the framework LatentSemanticMapping on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LatentSemanticMapping-10.1.tar.gz", hash = "sha256:46e95532c71083d1e63bcfa4b89a56fcf860288f8fb04fc0313e4c40685d1916"}, + {file = "pyobjc_framework_LatentSemanticMapping-10.1-py2.py3-none-any.whl", hash = "sha256:f0b14a1a2a6d6b25b902a2cc5949f0145926f0b0a3132d17210b1a580dc7f0f5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-launchservices" +version = "10.1" +description = "Wrappers for the framework LaunchServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LaunchServices-10.1.tar.gz", hash = "sha256:c0ef72f7cee77556c81e620ae8c511e73bdea4f644a233c8a5e3a333f5cd3d7d"}, + {file = "pyobjc_framework_LaunchServices-10.1-py2.py3-none-any.whl", hash = "sha256:b792a863427a2c59c884952737041e25ef05bdb541471ce94fb26a05b48abbbc"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-CoreServices = ">=10.1" + +[[package]] +name = "pyobjc-framework-libdispatch" +version = "10.1" +description = "Wrappers for libdispatch on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-libdispatch-10.1.tar.gz", hash = "sha256:444ca20e348cbdd2963523b89661d67743a6c87a57caf9e5d546665baf384a5b"}, + {file = "pyobjc_framework_libdispatch-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da0fa1e63b7e72010c69341bcd2f523ade827c7f30e0ef5c901a2536f43a1262"}, + {file = "pyobjc_framework_libdispatch-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd72ff7f399079eaf8135503c3658b3ce967076a9e3fdcd155c8a589134e476a"}, + {file = "pyobjc_framework_libdispatch-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ec061cba47247a5fd5788c3b9d5eba30936df3328f91fea63a565d09c53a0a02"}, + {file = "pyobjc_framework_libdispatch-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c71c8c9dca56b0e89ac7c4aff4b53bc74f64a2290e48c31cc77d87771c5203bd"}, + {file = "pyobjc_framework_libdispatch-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:b24a76fe2de4422685323e4f533b7bfd11a27edf06094c0f730f3f243f94a8bd"}, + {file = "pyobjc_framework_libdispatch-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:606954514e5747b05f9c608614f1affa44512888d18805452fade5d9b7938c14"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-libxpc" +version = "10.1" +description = "Wrappers for xpc on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-libxpc-10.1.tar.gz", hash = "sha256:8e768bb3052b30ef3938c41c9b9a52ad9d454c105d2011f5247f9ffb151e3702"}, + {file = "pyobjc_framework_libxpc-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5a3efe43b370fdc4d166ddfd8d1f74b5c3ae5f9b273e5738253c3d9a2bebf27"}, + {file = "pyobjc_framework_libxpc-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc16190cbcaf8639f4783058ec63b1aa5d03e3586311f171177b9275ed5725d8"}, + {file = "pyobjc_framework_libxpc-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3f0756945995da4cb503dc9ca31b0633b7044722b08348a240ebe6f594d43c0c"}, + {file = "pyobjc_framework_libxpc-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8d5a7f06f437c6a23c469299a3a15b62f8b4661563499b0f04d9fe8ea5e75a95"}, + {file = "pyobjc_framework_libxpc-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9341237ffaedb3169037a666564615fefd921e190e6ec3e951dc75384169a320"}, + {file = "pyobjc_framework_libxpc-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:57eaa7242ef4afe3e8d1fbe48f259613322549353250400c8d508afff251dde4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-linkpresentation" +version = "10.1" +description = "Wrappers for the framework LinkPresentation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LinkPresentation-10.1.tar.gz", hash = "sha256:d35f9436f6a72c0877479083118f57a42c0d01879df41ee832378bebef37e93c"}, + {file = "pyobjc_framework_LinkPresentation-10.1-py2.py3-none-any.whl", hash = "sha256:077c28c038b1aac0e5cd158cbf8b80863627f1254f0a1884440fabf95d46d62f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-localauthentication" +version = "10.1" +description = "Wrappers for the framework LocalAuthentication on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LocalAuthentication-10.1.tar.gz", hash = "sha256:e2b06bf7af1b6f8ba08bd59e1a3616732d801284362dd5482181b0b1488eca2d"}, + {file = "pyobjc_framework_LocalAuthentication-10.1-py2.py3-none-any.whl", hash = "sha256:3df6ac268f79f28e5b5e76b4fd6e095bdc9a200e1908f24cc33e805fa789f716"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Security = ">=10.1" + +[[package]] +name = "pyobjc-framework-localauthenticationembeddedui" +version = "10.1" +description = "Wrappers for the framework LocalAuthenticationEmbeddedUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-LocalAuthenticationEmbeddedUI-10.1.tar.gz", hash = "sha256:5a201e26c7710f8e8a6507dbb861baa545dc5abcbe0f3f6a19b2e270562c7bec"}, + {file = "pyobjc_framework_LocalAuthenticationEmbeddedUI-10.1-py2.py3-none-any.whl", hash = "sha256:a8e8101ca74441a862ffb8e2309fe382789c759d0951fb7b7b4e46652b4cb068"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-LocalAuthentication = ">=10.1" + +[[package]] +name = "pyobjc-framework-mailkit" +version = "10.1" +description = "Wrappers for the framework MailKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MailKit-10.1.tar.gz", hash = "sha256:a4589b13361a49ff0b3e9be43cd6f935a35acfc7a9f0da8b5db64283401da181"}, + {file = "pyobjc_framework_MailKit-10.1-py2.py3-none-any.whl", hash = "sha256:498d743e56d876d6d128970e6c0674470d9a4bcf9c021f0b115aa0c6ade1f5ae"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-mapkit" +version = "10.1" +description = "Wrappers for the framework MapKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MapKit-10.1.tar.gz", hash = "sha256:4e5b295ce1e94ed38888a0c4e3a5a92004e63e6d2ba9a86b5a277bbe658ddf05"}, + {file = "pyobjc_framework_MapKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:7628e7d8e4181f06fc3138b7426593d09fe3d49a056e7e3d5853f7bbcc62b240"}, + {file = "pyobjc_framework_MapKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:d5d78c56b2806148f7b4a2975e580bc039f1898ca8953041405683ba6c22f19b"}, + {file = "pyobjc_framework_MapKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b9c942b3a705021561de2a4e1590c58212131c2c7dc721290f68371558a42f35"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreLocation = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-mediaaccessibility" +version = "10.1" +description = "Wrappers for the framework MediaAccessibility on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaAccessibility-10.1.tar.gz", hash = "sha256:f487d83f948c12679c1ce06caabaedade1f4aee1f35163f835213c251a4639c7"}, + {file = "pyobjc_framework_MediaAccessibility-10.1-py2.py3-none-any.whl", hash = "sha256:2301cc554396efe449b079f99a0b5812e8e3dc364195dfcd2cc2b8a9c8915f11"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-medialibrary" +version = "10.1" +description = "Wrappers for the framework MediaLibrary on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaLibrary-10.1.tar.gz", hash = "sha256:cd4815cb270aa2f28acdad8185a4b9d4b76a6177f70e8ed62a610484f4bd44a9"}, + {file = "pyobjc_framework_MediaLibrary-10.1-py2.py3-none-any.whl", hash = "sha256:420d5006c624aaaf583058666fd5900a5619ff1f230e99cdd3acb76c12351a37"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-mediaplayer" +version = "10.1" +description = "Wrappers for the framework MediaPlayer on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaPlayer-10.1.tar.gz", hash = "sha256:394acea4fb61f8c4605494c7c64c52a105760aa2ec7e2c34db484e576ed10ad6"}, + {file = "pyobjc_framework_MediaPlayer-10.1-py2.py3-none-any.whl", hash = "sha256:10e25a8682cd0d1d8fc0041f0a34e8acf785b541d8c1ebe493c2d17caeef5648"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-AVFoundation = ">=10.1" + +[[package]] +name = "pyobjc-framework-mediatoolbox" +version = "10.1" +description = "Wrappers for the framework MediaToolbox on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MediaToolbox-10.1.tar.gz", hash = "sha256:56906cd90e1f969656db1fecd5874c6345e160044f54596c288fb0ffdb35cdc5"}, + {file = "pyobjc_framework_MediaToolbox-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:23b02872d910481a75db8eeb9c053a16b9a3cff1e030ca29d855ba8291c9501a"}, + {file = "pyobjc_framework_MediaToolbox-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c96557db9540ed18748f47d4cd0b2ba7273acb756bf7e91d8b2a943211850614"}, + {file = "pyobjc_framework_MediaToolbox-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:17ee7f045f39e3f11945bf951dfb4238c695ca49938e8b43c78fe12a8eb05628"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-metal" +version = "10.1" +description = "Wrappers for the framework Metal on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Metal-10.1.tar.gz", hash = "sha256:bde76fe5d285c9c875d411a7cf6cdd7617559eabf4fb9a588f90762a0634148c"}, + {file = "pyobjc_framework_Metal-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0701fe5e5aaa5471843fa0d5fe8fe3279313ff83c8bf5230ab6e11f7cba79a78"}, + {file = "pyobjc_framework_Metal-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5f542aaab62b9803e7e644b86dd82718aa9f1bcfc11cb4a666a59f248b3ae2e0"}, + {file = "pyobjc_framework_Metal-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:90ba5118ebf56a152e2404336ad7732dc60f252cd2414d34c9b32c07897f4512"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-metalfx" +version = "10.1" +description = "Wrappers for the framework MetalFX on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalFX-10.1.tar.gz", hash = "sha256:64c96595c2489e41d93a1c75d1eace70619d973e5c9e90e7cfca29c934fc5d06"}, + {file = "pyobjc_framework_MetalFX-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3d456581a76fde824a9109f9dfdd3fc4819e81ae27527b23d2855656ed0ab6ed"}, + {file = "pyobjc_framework_MetalFX-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a3fd4384c83c0818484a3a90120131114a0866b2004309cda24ce873e4ff1e50"}, + {file = "pyobjc_framework_MetalFX-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:76a9ef5abf114c1e2d60b1e971619183f87918eafeb0719a281d1475c88592ad"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Metal = ">=10.1" + +[[package]] +name = "pyobjc-framework-metalkit" +version = "10.1" +description = "Wrappers for the framework MetalKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalKit-10.1.tar.gz", hash = "sha256:da0357868824e6ec506ff92d18d729f8462c4c5ca8f26ecc86e8c031d78fa80d"}, + {file = "pyobjc_framework_MetalKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8723c6b009bf0ce7eb77aa7bdc54f1ee6d0450a3bc2f8ce85523170e92a62152"}, + {file = "pyobjc_framework_MetalKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e94303a78a883e3aa53115c4ebb8329989fcf36be353e908252bba3ba3dc807d"}, + {file = "pyobjc_framework_MetalKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:184922a11f273e604f2c5af2e14ce1f4ef2dce0f5c09aadda857c5a5ca6acab1"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Metal = ">=10.1" + +[[package]] +name = "pyobjc-framework-metalperformanceshaders" +version = "10.1" +description = "Wrappers for the framework MetalPerformanceShaders on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalPerformanceShaders-10.1.tar.gz", hash = "sha256:335a49c69ac95e412c581592a148a32c0fcf434097e50da378f21fe09be13738"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a410b6dce52f7a2adebdb66891bfc33939ffe24bd75691fc30c1f7539521df86"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:7218e89bccadc451f5ba040d84b049fe8b4a4bf7d9a4fdfb20fe6851e433cd49"}, + {file = "pyobjc_framework_MetalPerformanceShaders-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:9452e07d38c7a199c2eebb1280227285f918b97d3d597940e1e6e6471636b44a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Metal = ">=10.1" + +[[package]] +name = "pyobjc-framework-metalperformanceshadersgraph" +version = "10.1" +description = "Wrappers for the framework MetalPerformanceShadersGraph on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetalPerformanceShadersGraph-10.1.tar.gz", hash = "sha256:f587a0728e5240669d23a649f50bb25c10577f9775ba4f2576b19423575464a0"}, + {file = "pyobjc_framework_MetalPerformanceShadersGraph-10.1-py2.py3-none-any.whl", hash = "sha256:467e84983c5ded8cfaea8cb425872d5069eda8c4cc1f803ca3afaed0e184c763"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-MetalPerformanceShaders = ">=10.1" + +[[package]] +name = "pyobjc-framework-metrickit" +version = "10.1" +description = "Wrappers for the framework MetricKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MetricKit-10.1.tar.gz", hash = "sha256:6887cec4b7aa489ec16af2f77f7c447bc0a0493456fe1c4910d95a5b3e587fcd"}, + {file = "pyobjc_framework_MetricKit-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:02c8775000effbb00f6dde0a493b9ae955e54be4f9e72c4d0f2350d0864b46ac"}, + {file = "pyobjc_framework_MetricKit-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:49f6d9d9c46eb6605799fe0d898945cb62fc5c2d2fea1f7e51950765bbf7b03b"}, + {file = "pyobjc_framework_MetricKit-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6494dac683181dd1ca55b2fc912f693f51483a4e468a3fac05543539a643ca40"}, + {file = "pyobjc_framework_MetricKit-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cb7b318a2e2f4bb841a5427ab53448c827de0f2617123b804c41e6d595581321"}, + {file = "pyobjc_framework_MetricKit-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:5484980ef21e68389ed452f8a4d357f00dabf8711cb5268efe683f758441f23f"}, + {file = "pyobjc_framework_MetricKit-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7dc5a4da156e7f8724969ae329c8bae4fab58c2d7376ae96f62e2d646cc1175c"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-mlcompute" +version = "10.1" +description = "Wrappers for the framework MLCompute on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MLCompute-10.1.tar.gz", hash = "sha256:02e947ddb90c236acb2cb34f41838e6c78cb070886ddfb98bb71a8f02f991fd6"}, + {file = "pyobjc_framework_MLCompute-10.1-py2.py3-none-any.whl", hash = "sha256:25ed4d3002bd33c039f4ad9bf05b4830d53f67282a8399df7c65bd1430a01183"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-modelio" +version = "10.1" +description = "Wrappers for the framework ModelIO on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ModelIO-10.1.tar.gz", hash = "sha256:75fe5405165264a5059c16bfd492593e3becba50811a47dedbfc699ff73d4bfb"}, + {file = "pyobjc_framework_ModelIO-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:6911dfa7821e1b97cf48b593d3ccd6c9f2401ed1715a677df3cdfdfeec7dad14"}, + {file = "pyobjc_framework_ModelIO-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e1a1f3b276999032ff13b1f985ae06a95b5ffc9c53771b10ea3496a70e809d58"}, + {file = "pyobjc_framework_ModelIO-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8c6d43cca1c952858b2f31cab7b9ef01daae5aa1322240895d2e965cc72230cd"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-multipeerconnectivity" +version = "10.1" +description = "Wrappers for the framework MultipeerConnectivity on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-MultipeerConnectivity-10.1.tar.gz", hash = "sha256:ab83e57953bb3f3476c77ed863e1138ab58a0711a77a1a11924b9d22e90f116b"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:e5770351b75484bbb4f6b0f0a20e0a0197a0b192a35228b087bc06f149242b0f"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:a06db4ee86ee85bd085659a965f1448b27bf0018f1842ae3fb6ec1c195b5352c"}, + {file = "pyobjc_framework_MultipeerConnectivity-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:becab88974b1669f5ca9c76dddb5591d4ed9acb4176980d763e22d298b6ba83d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-naturallanguage" +version = "10.1" +description = "Wrappers for the framework NaturalLanguage on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NaturalLanguage-10.1.tar.gz", hash = "sha256:b017a75d7275606f1732fef891198e916743871ca7663ddbb1ffae7d4d93a855"}, + {file = "pyobjc_framework_NaturalLanguage-10.1-py2.py3-none-any.whl", hash = "sha256:02bb4df955ecf329cf6da77ca6952777e5b2a10aee67452ea6314ec632cbc475"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-netfs" +version = "10.1" +description = "Wrappers for the framework NetFS on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NetFS-10.1.tar.gz", hash = "sha256:0bd9c58a0939df29729c0ab5c5fe3e7eb7fc066a15bd885ddbbbfc6d6b1524b6"}, + {file = "pyobjc_framework_NetFS-10.1-py2.py3-none-any.whl", hash = "sha256:a317c30a367af22f94858ca73cfe38a0dc4b63d0783f93532cb33780cd98a942"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-network" +version = "10.1" +description = "Wrappers for the framework Network on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Network-10.1.tar.gz", hash = "sha256:39c02fdcac4e487e14296f5d60458b9a0cd58c2a830591a7cfacc0bca191e03f"}, + {file = "pyobjc_framework_Network-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:5a31831cd9949efbc82bcea854ea3c22dcb5dc85072f909710cde666efd5cfb6"}, + {file = "pyobjc_framework_Network-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6704cbbf5f50d73208e7c9d92a1212f581280430da2606e07e88669120c82a36"}, + {file = "pyobjc_framework_Network-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:fe52bac9aa16429f7a138aad4cbb1e95a2be5c052c1cfda7e8c4dd16d1147dec"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-networkextension" +version = "10.1" +description = "Wrappers for the framework NetworkExtension on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NetworkExtension-10.1.tar.gz", hash = "sha256:f49a3bd117ca40a1ea8ae751aca630f7b7e7d7305aa5dfa969beb07299eb2784"}, + {file = "pyobjc_framework_NetworkExtension-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfc5a7b402c8daced465c6b18683930a2ece91e98134cc1801657ad0a9256b1e"}, + {file = "pyobjc_framework_NetworkExtension-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:dfd808295c3b68a2f225410a67645b184187848be86abc2e6ba90db27e5c470f"}, + {file = "pyobjc_framework_NetworkExtension-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a925cfdabb4d882e8b9c3524a729c3b683e7a7ca18e291509625d3e63d3840cb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-notificationcenter" +version = "10.1" +description = "Wrappers for the framework NotificationCenter on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-NotificationCenter-10.1.tar.gz", hash = "sha256:697e29da4fdd5899e5db5bda7bdb5afc97f4a6e4d959caf2316aef3b300c5575"}, + {file = "pyobjc_framework_NotificationCenter-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f9078ba52e1cfa77c797a9aed972c182acfcc79cc2eb083c7b06ba76738b5f6d"}, + {file = "pyobjc_framework_NotificationCenter-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:25bf6f521f99ccb057d0df063242d5d28223672525706317134caa887ffd6b07"}, + {file = "pyobjc_framework_NotificationCenter-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7a6876f4b25023562ddf2558fba5e52d72a7ce3ec41c8e96533779d25e2b7722"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-opendirectory" +version = "10.1" +description = "Wrappers for the framework OpenDirectory on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OpenDirectory-10.1.tar.gz", hash = "sha256:5d25c254876378966ce58e0de9e3d3594ca25922773d6526235b5e2f2c4103e1"}, + {file = "pyobjc_framework_OpenDirectory-10.1-py2.py3-none-any.whl", hash = "sha256:83601f3b5694b1087616566019c300aa38b2a15b59d215e96c5dae18430e8c96"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-osakit" +version = "10.1" +description = "Wrappers for the framework OSAKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OSAKit-10.1.tar.gz", hash = "sha256:59ad344fed2ddbc24c5dad3345f596cd6ecb73e4c97a05051e3680709f66a42f"}, + {file = "pyobjc_framework_OSAKit-10.1-py2.py3-none-any.whl", hash = "sha256:af34b4dccc17a772d80c283c9356bdb5a5a300bd54c2557c26671aca4f2f86cb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-oslog" +version = "10.1" +description = "Wrappers for the framework OSLog on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-OSLog-10.1.tar.gz", hash = "sha256:bfce0067351115ae273489768f93692dcda88bd5b54f28bb741c08855c114dfe"}, + {file = "pyobjc_framework_OSLog-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:57b2920c5c9393fb4fe9e1d5d87eabead32ebe821853d06d577bdb5503327a49"}, + {file = "pyobjc_framework_OSLog-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:e94a3ce153fe72f7fe463e468d94e3657db54b133aaf5a313fb31b6b52ed60f2"}, + {file = "pyobjc_framework_OSLog-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:09e64565e4a293f3a9d486e1376f2c9651d5cec500b2c2245de9ae0baecfb29e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreMedia = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-passkit" +version = "10.1" +description = "Wrappers for the framework PassKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PassKit-10.1.tar.gz", hash = "sha256:bc19a46fad004137f207a5a9643d3f9a3602ea3f1d75c57841de986019a3c805"}, + {file = "pyobjc_framework_PassKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:011e2f32bc465b634d171a2500e6a153b479b807a50659cc164883bbeec74e59"}, + {file = "pyobjc_framework_PassKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:791f7d4317164130e8232e75e40ba565633a01bc5777dc3d0ba0a8b5f4aeab92"}, + {file = "pyobjc_framework_PassKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:58115c31a2e8b8a57ca048de408444cc4ba94da386656e0eeeac919b157f03a4"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-pencilkit" +version = "10.1" +description = "Wrappers for the framework PencilKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PencilKit-10.1.tar.gz", hash = "sha256:712654dc9373014aa5472b10ba269d95f455c722ebb7504caa04dfae209ed63a"}, + {file = "pyobjc_framework_PencilKit-10.1-py2.py3-none-any.whl", hash = "sha256:baf856d274653d74d66099ae81005ddb3923f7128d36d2f87100481cbb8b2b27"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-phase" +version = "10.1" +description = "Wrappers for the framework PHASE on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PHASE-10.1.tar.gz", hash = "sha256:b7e0ef3567359a4f8ab3e5f8319f2201e775e3db6d7498409701664568c8c7c6"}, + {file = "pyobjc_framework_PHASE-10.1-py2.py3-none-any.whl", hash = "sha256:329cd6dd040a7ef8091dda9d8e57d9613bc9c8edf3cfd3af549f5cd9d64a0941"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-AVFoundation = ">=10.1" + +[[package]] +name = "pyobjc-framework-photos" +version = "10.1" +description = "Wrappers for the framework Photos on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Photos-10.1.tar.gz", hash = "sha256:eb0e83d01c8eb0652fac8382e68fd9643b7530f6580c2a51846444cae09ec094"}, + {file = "pyobjc_framework_Photos-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:d91ead1ec33e05bf9e42b7df3f8fe7e3d4cf2814482f6878060c259453491d65"}, + {file = "pyobjc_framework_Photos-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:f8415346689213b30488eb023d699c0512fcddeb7e1e4aa833860c312dddf780"}, + {file = "pyobjc_framework_Photos-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:61174248e07025d4696b6164346b43147250d03ae8378f70a458c0583e003656"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-photosui" +version = "10.1" +description = "Wrappers for the framework PhotosUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PhotosUI-10.1.tar.gz", hash = "sha256:4b90755c6c62a0668782cf05d92fca6277485f2cb3473981760c0dc0e40de1d8"}, + {file = "pyobjc_framework_PhotosUI-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:206641f2f7f3169fecc0014b9b93c89b5503841014e911d4686684de137c79f9"}, + {file = "pyobjc_framework_PhotosUI-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:02c1861bcce433294b00f6c614559addc87fcf57aaa1ede2b6dfea50a3795378"}, + {file = "pyobjc_framework_PhotosUI-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:8df716fb2e9994bfc2d716d6930defb3e3911a0337788ef36eea0c2eb0f899ad"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-preferencepanes" +version = "10.1" +description = "Wrappers for the framework PreferencePanes on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PreferencePanes-10.1.tar.gz", hash = "sha256:3a26cd8959dac30203410eb432a361caf2a0b8552c74edd3d7406d722ecc1014"}, + {file = "pyobjc_framework_PreferencePanes-10.1-py2.py3-none-any.whl", hash = "sha256:9b16c93d7f684cbe097932c8260a0b6460ad9fc68230648981340b7e3ee7053e"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-pubsub" +version = "10.1" +description = "Wrappers for the framework PubSub on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PubSub-10.1.tar.gz", hash = "sha256:7992344ae82d9566d300b3d2c92ff9fa9a28e060bd42d0988df351f5fb729156"}, + {file = "pyobjc_framework_PubSub-10.1-py2.py3-none-any.whl", hash = "sha256:af0b1ed0328f06d7d96390a4b95bfb4a790d5b38142825a222923f908dc46db9"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-pushkit" +version = "10.1" +description = "Wrappers for the framework PushKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-PushKit-10.1.tar.gz", hash = "sha256:12798ad9ae87f7e78690d2bce2ea46f0714d30dd938f5b288717660120a00795"}, + {file = "pyobjc_framework_PushKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:99edcd057d5cc7e015fe6b464b83f07c843fba878f5b0636ff30cd6377ec2915"}, + {file = "pyobjc_framework_PushKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:986216b9021ed6aff3a528c2b6a3885426e8acac9a744397ede998d2e7a83d06"}, + {file = "pyobjc_framework_PushKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:cf27a49a3b9eadde0bc518b54f7b38fd5d0e1c2203350d1286527b6177afa3c3"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-quartz" +version = "10.1" +description = "Wrappers for the Quartz frameworks on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Quartz-10.1.tar.gz", hash = "sha256:b7439c0a3be9590d261cd2d340ba8dd24a75877b0be3ebce56e022a19cc05738"}, + {file = "pyobjc_framework_Quartz-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:69db14ac9814839471e3cf5a8d81fb5edd1b762739ad806d3cf244836dac0154"}, + {file = "pyobjc_framework_Quartz-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ddcd18e96511e618ce43e288a043e25524c131f5e6d58775db7aaf15553d849"}, + {file = "pyobjc_framework_Quartz-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c4257a2fb5580e5ebe927a66cf36a11749685a4681a30f90e954a3f08894cb62"}, + {file = "pyobjc_framework_Quartz-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28315ca6e04a08ae9e4eaf35b364ee77e081605d5865021018217626097c5e80"}, + {file = "pyobjc_framework_Quartz-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:9cb859a2fd7e15f2de85c16b028148dea06002d1a4142922b3441d3802fab372"}, + {file = "pyobjc_framework_Quartz-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:993c71009e6374e57205e6aeaa577b7af2df245a5d1d2feff0f88ca0fa7b8626"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-quicklookthumbnailing" +version = "10.1" +description = "Wrappers for the framework QuickLookThumbnailing on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-QuickLookThumbnailing-10.1.tar.gz", hash = "sha256:b6c4ea3701cb18abaffcceb65adc9dcfd6bb28811af7a99148c71e71d538a3a6"}, + {file = "pyobjc_framework_QuickLookThumbnailing-10.1-py2.py3-none-any.whl", hash = "sha256:984e4e92727caa5b2ebbe8c91fcde6acc416f15dd8e7aef94cb3999c4a7028ec"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-replaykit" +version = "10.1" +description = "Wrappers for the framework ReplayKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ReplayKit-10.1.tar.gz", hash = "sha256:c74d092afd8da7448e3b96a28d9cde09ad11269b345a5df21ce971c87671e421"}, + {file = "pyobjc_framework_ReplayKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:592cd22d78a92691d3dce98cd526e95fbb692142541a62c99d989c8941ec9f55"}, + {file = "pyobjc_framework_ReplayKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:05358af8daef82de6fa40fb5e04639d0f29d3f22f34b0901d5a224f8d2a7da69"}, + {file = "pyobjc_framework_ReplayKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:771af451363b7c81c920a1290f673501762da6f691f54920d0866028098390dd"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-safariservices" +version = "10.1" +description = "Wrappers for the framework SafariServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SafariServices-10.1.tar.gz", hash = "sha256:5a9105d3aea43cd214583acd06609f56ed704ce816cb103916324e8ed8388fce"}, + {file = "pyobjc_framework_SafariServices-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:f672434748e7d9b303535969bcb03d99cdbf79162292ad439c0347455f38f1db"}, + {file = "pyobjc_framework_SafariServices-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:64d37455a8bd541bd799604ee9e41120cc7c9c19f26776b6d8e16f1902738b70"}, + {file = "pyobjc_framework_SafariServices-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a5aa2fb6333ec0929f6b9689992b76eb6442e5ef4bad8b5a72c7796f24898868"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-safetykit" +version = "10.1" +description = "Wrappers for the framework SafetyKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SafetyKit-10.1.tar.gz", hash = "sha256:f1ac7201d32129c9c1a200724a5d3e75c6da8793f9c8a628be206cdebcd548e5"}, + {file = "pyobjc_framework_SafetyKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:3499fd9d85c8c93ae7be2949c1b2e91e0f74b9a0d39be9c66440c40195ef4633"}, + {file = "pyobjc_framework_SafetyKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ab7d47dbcdeafb56f0c2c6e1be847e74840038c19fecbbaf883e68cd44511eb9"}, + {file = "pyobjc_framework_SafetyKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:5c68ab2994c21bd32540595ec92922b0234e48fbb6998fcb22bacee46286e999"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-scenekit" +version = "10.1" +description = "Wrappers for the framework SceneKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SceneKit-10.1.tar.gz", hash = "sha256:f6565f3dba3bacf6099677ef713f9c95bcb9d8c4ea755c1866d113f95f110fc9"}, + {file = "pyobjc_framework_SceneKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:70d3a7f78238255bf62fab33a3e9ac20e13ec228eafd1aa0ef579b3792e5d9b9"}, + {file = "pyobjc_framework_SceneKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1bbdee819b638530c53271a4f302357cf8c829dbfc6e40b062335c900816bb01"}, + {file = "pyobjc_framework_SceneKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:e179e37613814661be86c8316dd92497012cec48bb4febdc3d432ac5e7594a3f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-screencapturekit" +version = "10.1" +description = "Wrappers for the framework ScreenCaptureKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenCaptureKit-10.1.tar.gz", hash = "sha256:a00d85c97bf0cdd454d57181c371f372b8549c4edd949e2b66f42782f426f855"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc05248b9ae9ed4aa474b4e54927216046c284a4c6c27d30db9df659887b7b1d"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98eaec608bd9858a265541b14d6708bcc0b8c8276c2a5b41b80d828c0c2a8c64"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ff8657865f6280a942d175b87933ff0f1b6064e672a7f1efb5e66d864b435c27"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:30615c2a9f0a04cca41afe0cee21e3179f72f055e9cac94fe1e4f31fcccb0919"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2cbd9957e9823615a494b2fd6815688eb0ad2eed7df007b25e3f7d83261653a9"}, + {file = "pyobjc_framework_ScreenCaptureKit-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3b732bad05c973844ea3b25ccabf0d41b4c3eec4f7b5d78650337685cb43142"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreMedia = ">=10.1" + +[[package]] +name = "pyobjc-framework-screensaver" +version = "10.1" +description = "Wrappers for the framework ScreenSaver on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenSaver-10.1.tar.gz", hash = "sha256:d1b890c7cae9e5c43582fe834aebcb6a1ecf52467a8ed7a28ba9d873bbf582d5"}, + {file = "pyobjc_framework_ScreenSaver-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:b464de6398ef3a700c4ac19ed92b25cf2d30900b574533a659967446fddede3b"}, + {file = "pyobjc_framework_ScreenSaver-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cc8f81b2073920ca84d8e83435b95731e798ad422e0a3d67b09feb208a3920c6"}, + {file = "pyobjc_framework_ScreenSaver-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:96868cd9dc6613144821bb4db50ca68efa3ae8e07c31a626ce02d78b4eeaaeff"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-screentime" +version = "10.1" +description = "Wrappers for the framework ScreenTime on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScreenTime-10.1.tar.gz", hash = "sha256:6221e0f5122042b280212a6555f72d94020c2fd62319c4562cdfc7b58960dd07"}, + {file = "pyobjc_framework_ScreenTime-10.1-py2.py3-none-any.whl", hash = "sha256:734e090debb954a890a564869f2af20b55b9f7b7875d360795c9875279d09bd9"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-scriptingbridge" +version = "10.1" +description = "Wrappers for the framework ScriptingBridge on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ScriptingBridge-10.1.tar.gz", hash = "sha256:7dce35a25d1f3b125e4b68a07c7f9eaa33fc9f00dde32356d0f7f73eb09429a3"}, + {file = "pyobjc_framework_ScriptingBridge-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:15e60d3783d7611f4d35e6b2905921a01162cfa04eb1a6426135585c84806d19"}, + {file = "pyobjc_framework_ScriptingBridge-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:30c0aac8623d0e96442801219004c32d527d4b4bbbf5c271517d73c5eeae85a3"}, + {file = "pyobjc_framework_ScriptingBridge-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:ca0dc8ccb443f5a3ab9afb500d6c730723faf38c5880a243a65b4e44be64fa55"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-searchkit" +version = "10.1" +description = "Wrappers for the framework SearchKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SearchKit-10.1.tar.gz", hash = "sha256:75234ee6e8490cf792453bf9a9854d7b5f1cebd65e81903d5ce0ecc3e65fc277"}, + {file = "pyobjc_framework_SearchKit-10.1-py2.py3-none-any.whl", hash = "sha256:2e42e7cacb0a7f9b327d1c770e52fe13dfaaac377cb4e413b609e478993552e0"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-CoreServices = ">=10.1" + +[[package]] +name = "pyobjc-framework-security" +version = "10.1" +description = "Wrappers for the framework Security on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Security-10.1.tar.gz", hash = "sha256:33becccea5488a4044792034d8cf4faf1913f8ca9ba912dceeaa54db311bd284"}, + {file = "pyobjc_framework_Security-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:72955f4faf503e6a41076fcaa3ec138eb1cc794f483db77104acf2ee480f8a04"}, + {file = "pyobjc_framework_Security-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b02075026d78feda8c1af9462199c2cde65b87e4adde65b90ca6965f06cb422"}, + {file = "pyobjc_framework_Security-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1d19785d8531a6cdcdbb4c545b560f63306ff947592e7fad27811f87ee64854c"}, + {file = "pyobjc_framework_Security-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:569a9243d4044e3e433335ded891dc357880787df647de8220659f022a03f467"}, + {file = "pyobjc_framework_Security-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d8b8c402c395ac3868727f04e98b2ded675534de1349df8f5813b3c483b50a2c"}, + {file = "pyobjc_framework_Security-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:aaca360a28b6333a8a93b091426daa5ffd22006bbb1122d3d6a78d33177f612a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-securityfoundation" +version = "10.1" +description = "Wrappers for the framework SecurityFoundation on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SecurityFoundation-10.1.tar.gz", hash = "sha256:11def85a7a4ea490fa24df79d01ea137f378534fedf1da248068ddf137f38c7e"}, + {file = "pyobjc_framework_SecurityFoundation-10.1-py2.py3-none-any.whl", hash = "sha256:bbd67737afec25f2e3d41c8c2e7b4a6f9aae4231242e215b82a950eef6432ce0"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Security = ">=10.1" + +[[package]] +name = "pyobjc-framework-securityinterface" +version = "10.1" +description = "Wrappers for the framework SecurityInterface on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SecurityInterface-10.1.tar.gz", hash = "sha256:444a0dc7d50390750c28185b6496ee913011ac886d9e634bfc9a0856372d0a94"}, + {file = "pyobjc_framework_SecurityInterface-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c0e52408e25845a960b0fe339c274650fd211f9fee5944c643d9ba16861e45ac"}, + {file = "pyobjc_framework_SecurityInterface-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bef4a63d31808531f5806006945d1f9b5650221e4adc973302387ab7b2e1b349"}, + {file = "pyobjc_framework_SecurityInterface-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:479e555df16ff7f79bf7622ab3341b0ef176fbd85ef3f7301931a57d2def682f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Security = ">=10.1" + +[[package]] +name = "pyobjc-framework-sensitivecontentanalysis" +version = "10.1" +description = "Wrappers for the framework SensitiveContentAnalysis on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SensitiveContentAnalysis-10.1.tar.gz", hash = "sha256:435906a3fcc6cba50cd7c5bfd693368c6042c17c5f64bcd560a3761d947425de"}, + {file = "pyobjc_framework_SensitiveContentAnalysis-10.1-py2.py3-none-any.whl", hash = "sha256:472c0fb0f1ad9c370cbc7cf636bb5888cbcf0ee8c9ecb9c5f6de25e2587771e5"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-servicemanagement" +version = "10.1" +description = "Wrappers for the framework ServiceManagement on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ServiceManagement-10.1.tar.gz", hash = "sha256:ebe38b80ed74112fdd356e19165c365f6281baad83818774a0da6d790fd13044"}, + {file = "pyobjc_framework_ServiceManagement-10.1-py2.py3-none-any.whl", hash = "sha256:d05289948558cf4c7fbc101946f6ccadcc33826b2056c14d5494a8ae7f136936"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-sharedwithyou" +version = "10.1" +description = "Wrappers for the framework SharedWithYou on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SharedWithYou-10.1.tar.gz", hash = "sha256:bcac8ffa2642589a416c62ff436148586db9c41f92419a0164b1e9d6f6c73e38"}, + {file = "pyobjc_framework_SharedWithYou-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:05fceedcd7b6e8753cb8dc5f09a947686dd454c304965c959bc101cfd7349fcd"}, + {file = "pyobjc_framework_SharedWithYou-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6f4f9fb6d335b54eb0a02b277ca8a2cb87962a579bafdc9df5f94c8af1063ee4"}, + {file = "pyobjc_framework_SharedWithYou-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:a1c7c688c15117f1c6ea638e83285ce1b2fbd9d8c76ee405e43b24fa4fea766d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-SharedWithYouCore = ">=10.1" + +[[package]] +name = "pyobjc-framework-sharedwithyoucore" +version = "10.1" +description = "Wrappers for the framework SharedWithYouCore on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SharedWithYouCore-10.1.tar.gz", hash = "sha256:2b4f62b0df4bd44198f6d3a3aae4d054592261d36fc2af71f9dd81744aa99815"}, + {file = "pyobjc_framework_SharedWithYouCore-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a7f41415a3ca40d4ee18955155a4141e0d2d55e713b513aa567305ae54716cb7"}, + {file = "pyobjc_framework_SharedWithYouCore-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc133f07a71cb828073dc671cb1e8ffa5bde714b376a8eba0a8110ac41927ae9"}, + {file = "pyobjc_framework_SharedWithYouCore-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:d7169a2492ed4fd7d45ad0eafbecebffec0b22f08e756f2e251eda62cd5ba42a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-shazamkit" +version = "10.1" +description = "Wrappers for the framework ShazamKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ShazamKit-10.1.tar.gz", hash = "sha256:d091c5104adda8d54e65463862550e59f86646fdafcdcd234c9a7a2624584f1d"}, + {file = "pyobjc_framework_ShazamKit-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6670ed380dacc6aa86f571a18d9e321bd075da11bf144cba2802b19bb0868a21"}, + {file = "pyobjc_framework_ShazamKit-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d4efd57dac3f50621cc23be38cefbd6c80b4b55f0339312b4f2a340cd6ffde9b"}, + {file = "pyobjc_framework_ShazamKit-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:07e58fc6b70bf961f230044cf46324ab4239864955299957e231ba7cda8fafa9"}, + {file = "pyobjc_framework_ShazamKit-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be2da82d9a58c2605a1a17a88fbc389931b8fd8ad7d60926755b50316fe5e04f"}, + {file = "pyobjc_framework_ShazamKit-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:e500c6794f2b7cea57dea6f64c1fc9e067a14ddb9446e9d7739dcb57683b5a8a"}, + {file = "pyobjc_framework_ShazamKit-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4289a1109148a1a314c6bae9b33e90eca6d18a06a767b431cdff1178024f3310"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-social" +version = "10.1" +description = "Wrappers for the framework Social on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Social-10.1.tar.gz", hash = "sha256:50757d982c712090e93b6ee4bd97ed3f6acfe7005f981f060433e94b7aca818b"}, + {file = "pyobjc_framework_Social-10.1-py2.py3-none-any.whl", hash = "sha256:81363d9d06c9c8ede16d96ec1d3cdba6deef195ef54cc64618e58c7fc1f574df"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-soundanalysis" +version = "10.1" +description = "Wrappers for the framework SoundAnalysis on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SoundAnalysis-10.1.tar.gz", hash = "sha256:42e0ae24f11ef8cf097c71e5b2378eaba26f66cb39959fec4ca79812bc0ed417"}, + {file = "pyobjc_framework_SoundAnalysis-10.1-py2.py3-none-any.whl", hash = "sha256:a33bc8a1ecee11387beb9db06aaf9c362f7dc171d60da913277ac482d67beabb"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-speech" +version = "10.1" +description = "Wrappers for the framework Speech on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Speech-10.1.tar.gz", hash = "sha256:a9eddebd4e4bcdb9c165129bea510c5e9f1353528a8211cc38c22711792bf30d"}, + {file = "pyobjc_framework_Speech-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:0cc26aca43d738d25f615def32eb8ce27675fc616584c009e57f6b82dec75cc5"}, + {file = "pyobjc_framework_Speech-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9a448c58149f5bbf0128f2c6492ea281b51f50bdc4f0ecd52bea43c80f7e2063"}, + {file = "pyobjc_framework_Speech-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:76a528fc587c8eb86cdba61bf6b94ddb7e3fb38a41f1a46217e2ce7fc21d6c26"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-spritekit" +version = "10.1" +description = "Wrappers for the framework SpriteKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SpriteKit-10.1.tar.gz", hash = "sha256:998be1d6c7fd5cc66bd54bae37c45cf3394df7bc689b5d0c813f0449c8eee53f"}, + {file = "pyobjc_framework_SpriteKit-10.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:23f6657e48f7d8cb434bcf6a76b2c336eb29be69ade933f88299465a0c83cb3b"}, + {file = "pyobjc_framework_SpriteKit-10.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b5556d8469b20fe35a0ec5f9e493c30ebc531bce3be4e48fc99cb87338ba5cfb"}, + {file = "pyobjc_framework_SpriteKit-10.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aa51855f7bfed3dc1bcda95b140d71c4dc1e21c3480216df19f6fddc7dc7ce39"}, + {file = "pyobjc_framework_SpriteKit-10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2210920a4f9a39dc3bea9287e012cdfb740a0748faa6ab13bf8a58d07da913cc"}, + {file = "pyobjc_framework_SpriteKit-10.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:66df1436d17bf0c17432d2d66ebeef8efee012240297e5aabc1118b014947375"}, + {file = "pyobjc_framework_SpriteKit-10.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5564ed8648afba01f9877062204ed03d3fef8a980b6b4155c69d3662e4732947"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-storekit" +version = "10.1" +description = "Wrappers for the framework StoreKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-StoreKit-10.1.tar.gz", hash = "sha256:4e91d77d1b9745eca6730ddf6cde964e2bd956fafad303591f671ebd1d4de64b"}, + {file = "pyobjc_framework_StoreKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:456641cbe97eab4bb68dccec6f8bf3bc435adaa0b2ae6a7a4a3da0adc84a9405"}, + {file = "pyobjc_framework_StoreKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:356966d260bd1e19c7cdba7551b3e477078d3d4b0df04b7f38013dd044913727"}, + {file = "pyobjc_framework_StoreKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:652657006d3c8fefcdbb662f8f33ef6ee8e01ba30a0b4d6e2fcd2e4046951766"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-symbols" +version = "10.1" +description = "Wrappers for the framework Symbols on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Symbols-10.1.tar.gz", hash = "sha256:63f5345fa90b31ea017c01ffd39e6e0289ef0258c6af7941263083d2289f5d3d"}, + {file = "pyobjc_framework_Symbols-10.1-py2.py3-none-any.whl", hash = "sha256:88b48102ba33ac3d8bc5c047cc892ab21e8e102c3b25b4186b77c5d1f5c1bc40"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-syncservices" +version = "10.1" +description = "Wrappers for the framework SyncServices on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SyncServices-10.1.tar.gz", hash = "sha256:644d394b84468fa6178b5aa609771252ca416ca2be2bac5501222b3c5151846d"}, + {file = "pyobjc_framework_SyncServices-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:edf7d5de135ec44b8ecf260265cb7bd9bf938d3fcc2204282aea674a86918c60"}, + {file = "pyobjc_framework_SyncServices-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:53ef6096359d182952fdb40734f17302edf35757578c0c52314f703322d855cb"}, + {file = "pyobjc_framework_SyncServices-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:b9d7ec3f784fc89847ad136bb3d67d159310a2e072a724d4ffddccf0ee5dec2b"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreData = ">=10.1" + +[[package]] +name = "pyobjc-framework-systemconfiguration" +version = "10.1" +description = "Wrappers for the framework SystemConfiguration on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SystemConfiguration-10.1.tar.gz", hash = "sha256:7e125872d4b54c8d04f15d83e7f7f706c18bd87960b3873c797e6a71b95030b0"}, + {file = "pyobjc_framework_SystemConfiguration-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ab80e4272937643de8569a711e5adee8afca2bf071b6cfc6b7fc4143010d258"}, + {file = "pyobjc_framework_SystemConfiguration-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:431c32557bde3dad18fb245bf1e5ce80963f28caa4d2691b5a82e6db2b5efc2f"}, + {file = "pyobjc_framework_SystemConfiguration-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:7e8ae510b11ceca8800bc7b4b0c7735cf26de803771199d6c2d8f24fbb5467df"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-systemextensions" +version = "10.1" +description = "Wrappers for the framework SystemExtensions on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-SystemExtensions-10.1.tar.gz", hash = "sha256:3eb7ad8f1a6901294b02cd6d6581bd6960a48fcfd82475f5970d1c909f12670d"}, + {file = "pyobjc_framework_SystemExtensions-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:8dc306dd07f9ff071759bb2237b7f7fddd0d2624966bdb0801dc5a70b026f431"}, + {file = "pyobjc_framework_SystemExtensions-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c76c6d7dbf253abe11ebfa83bbbfa7f2fc4c700db052771075c26dabbd5ee1e9"}, + {file = "pyobjc_framework_SystemExtensions-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2586323fbe9382edebd7ca5dfe50b432c842b7ef45ef26444edcb7238bcf006f"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-threadnetwork" +version = "10.1" +description = "Wrappers for the framework ThreadNetwork on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-ThreadNetwork-10.1.tar.gz", hash = "sha256:72694dce8b10937f4d8fef67d14e15fa65ba590dec8298df439fd0cc953a83fa"}, + {file = "pyobjc_framework_ThreadNetwork-10.1-py2.py3-none-any.whl", hash = "sha256:720d4a14619598431a22be2a720bf877f996d65cee430b96c5d7ec833b676b68"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-uniformtypeidentifiers" +version = "10.1" +description = "Wrappers for the framework UniformTypeIdentifiers on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UniformTypeIdentifiers-10.1.tar.gz", hash = "sha256:e8a6e8d4c3c6d8213d18fab44704055a5fca91e0a74891b4f1bfe6574cd51d97"}, + {file = "pyobjc_framework_UniformTypeIdentifiers-10.1-py2.py3-none-any.whl", hash = "sha256:4c867b298956d74398d2b6354bd932dc109431d9726c8ea2fc9c83e6946a2a7d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-usernotifications" +version = "10.1" +description = "Wrappers for the framework UserNotifications on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UserNotifications-10.1.tar.gz", hash = "sha256:eca638b04b60d5d8f5efecafc1fd021a1b55d4a6d1ebd22e65771eddb3dd478f"}, + {file = "pyobjc_framework_UserNotifications-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a44b89659eae1015da9148fc24f931108ff7a05ba61509bfab34af50806beb0c"}, + {file = "pyobjc_framework_UserNotifications-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:00aa84f29bcbe8f302d20c96ef51fb48af519d83e0b72d22bd075ea1af86629f"}, + {file = "pyobjc_framework_UserNotifications-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:fe9170b5c4da8e75288ada553cc821b9e3fc1279eb56fa9e3d4278b35a26c5ce"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-usernotificationsui" +version = "10.1" +description = "Wrappers for the framework UserNotificationsUI on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-UserNotificationsUI-10.1.tar.gz", hash = "sha256:09df08f47a7e605642a6c6846e365cab8b8631a7f87b41a65c35c52e484b9f8a"}, + {file = "pyobjc_framework_UserNotificationsUI-10.1-py2.py3-none-any.whl", hash = "sha256:6640c6d04f459b6927096696dac98ce5fcb702a507a757d6d1b909b341bb8a0d"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-UserNotifications = ">=10.1" + +[[package]] +name = "pyobjc-framework-videosubscriberaccount" +version = "10.1" +description = "Wrappers for the framework VideoSubscriberAccount on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-VideoSubscriberAccount-10.1.tar.gz", hash = "sha256:6410c68ea37a4ba4667b8c71fbfd3c011bf6ecdb9f1d6adf3c9a35584b7c8804"}, + {file = "pyobjc_framework_VideoSubscriberAccount-10.1-py2.py3-none-any.whl", hash = "sha256:f32716070f849989e3ff052effb54f951b89a538208651426848d9d924ac1625"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-videotoolbox" +version = "10.1" +description = "Wrappers for the framework VideoToolbox on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-VideoToolbox-10.1.tar.gz", hash = "sha256:56c9d4b74965fe79f050884ffa560ff71ffe709c24923d3d0b34459fb626eb11"}, + {file = "pyobjc_framework_VideoToolbox-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a4115690b8ed4266e52a4d200c870e68dd03119993280020a1a4d6a9d4764fcf"}, + {file = "pyobjc_framework_VideoToolbox-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:64874d253c2996216c6d56e03e848cf845c3f0eac84d06ba97d83871dbf19490"}, + {file = "pyobjc_framework_VideoToolbox-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:977b2981532442c4c99fff75ffcc2b5a4b0f8108abcabdafcda2addf8b2ffa21"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreMedia = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-virtualization" +version = "10.1" +description = "Wrappers for the framework Virtualization on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Virtualization-10.1.tar.gz", hash = "sha256:48f2484a7627caa246f55daf203927f10600e615e620a2d9ca22e483ed0bb9b4"}, + {file = "pyobjc_framework_Virtualization-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:a434c40038c0c1acd31805795f28f959ea231252dc3ab34ed5a268c21227682c"}, + {file = "pyobjc_framework_Virtualization-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8ad3e40ec5970e881f92af337354be68c1f2512690545a2da826684daeaa3535"}, + {file = "pyobjc_framework_Virtualization-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:2aba907617075394718bc8883c650197e21b2ea0d284ca51811229386114040a"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + +[[package]] +name = "pyobjc-framework-vision" +version = "10.1" +description = "Wrappers for the framework Vision on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-Vision-10.1.tar.gz", hash = "sha256:ff50fb7577be8d8862a076a6cde5ebdc9ef07d9045e2158faaf0f04b5b051208"}, + {file = "pyobjc_framework_Vision-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:c6330d8b22f75f1e7d9a5456f3e2c7299d05d575b2e9b2f1e50230b18f17abed"}, + {file = "pyobjc_framework_Vision-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:91b4d740b6943f6b228915ece2e027555f28ccf49c8d063a580b8f9e5af56fd0"}, + {file = "pyobjc_framework_Vision-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:bb2d7334b4b725c5e5346a8cce2a0064259a09e90ec189b0c776304d5fc01e49"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" +pyobjc-framework-CoreML = ">=10.1" +pyobjc-framework-Quartz = ">=10.1" + +[[package]] +name = "pyobjc-framework-webkit" +version = "10.1" +description = "Wrappers for the framework WebKit on macOS" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyobjc-framework-WebKit-10.1.tar.gz", hash = "sha256:311974b626facee73cab5a7e53da4cc8966cbe60b606ba11fd0f3547e0ba1762"}, + {file = "pyobjc_framework_WebKit-10.1-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:ad9e1bd2fa9885818e1228c60e0d95100df69252f230ea8bb451fae73fcace61"}, + {file = "pyobjc_framework_WebKit-10.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c901fc6977b3298de789002a76a34c353ed38faedfc5ba63ef94a149ec9e5b02"}, + {file = "pyobjc_framework_WebKit-10.1-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:f2d45dfc2c41792a5a983263d5b06c4fe70bf2f24943e2bf3097e4c9449a4516"}, +] + +[package.dependencies] +pyobjc-core = ">=10.1" +pyobjc-framework-Cocoa = ">=10.1" + [[package]] name = "pyopencl" version = "2023.1.4" @@ -3473,6 +6300,16 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyperclip" +version = "1.8.2" +description = "A cross-platform clipboard module for Python. (Only handles plain text for now.)" +optional = false +python-versions = "*" +files = [ + {file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"}, +] + [[package]] name = "pyprof2calltree" version = "1.4.5" @@ -3569,6 +6406,29 @@ files = [ {file = "PyQt5_sip-12.13.0.tar.gz", hash = "sha256:7f321daf84b9c9dbca61b80e1ef37bdaffc0e93312edae2cd7da25b953971d91"}, ] +[[package]] +name = "pyrect" +version = "0.2.0" +description = "PyRect is a simple module with a Rect class for Pygame-like rectangular areas." +optional = false +python-versions = "*" +files = [ + {file = "PyRect-0.2.0.tar.gz", hash = "sha256:f65155f6df9b929b67caffbd57c0947c5ae5449d3b580d178074bffb47a09b78"}, +] + +[[package]] +name = "pyscreeze" +version = "0.1.30" +description = "A simple, cross-platform screenshot module for Python 2 and 3." +optional = false +python-versions = "*" +files = [ + {file = "PyScreeze-0.1.30.tar.gz", hash = "sha256:74098ad048e76a6231dcfa6243343af94459b8c829f9ccb7a44a5d3b147a67d1"}, +] + +[package.dependencies] +Pillow = {version = ">=9.3.0", markers = "python_version == \"3.11\""} + [[package]] name = "pyserial" version = "3.5" @@ -3713,6 +6573,30 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-xlib" +version = "0.33" +description = "Python X Library" +optional = false +python-versions = "*" +files = [ + {file = "python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32"}, + {file = "python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398"}, +] + +[package.dependencies] +six = ">=1.10.0" + +[[package]] +name = "python3-xlib" +version = "0.15" +description = "Python3 X Library" +optional = false +python-versions = "*" +files = [ + {file = "python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8"}, +] + [[package]] name = "pytools" version = "2023.1.1" @@ -3730,6 +6614,16 @@ platformdirs = ">=2.2.0" [package.extras] numpy = ["numpy (>=1.6.0)"] +[[package]] +name = "pytweening" +version = "1.0.7" +description = "A collection of tweening / easing functions." +optional = false +python-versions = "*" +files = [ + {file = "pytweening-1.0.7.tar.gz", hash = "sha256:767134f1bf57b76c1ce9f692dd1cfc776d9a279de6724e8d04854508fd7ededb"}, +] + [[package]] name = "pytz" version = "2023.3.post1" @@ -3764,6 +6658,46 @@ files = [ {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, ] +[[package]] +name = "pywinbox" +version = "0.6" +description = "Cross-Platform and multi-monitor toolkit to handle rectangular areas and windows box" +optional = false +python-versions = "*" +files = [ + {file = "PyWinBox-0.6-py3-none-any.whl", hash = "sha256:3547e459caf466d3beb2230b02208a348478428ebca61ce3e004fb5468976f33"}, +] + +[package.dependencies] +pyobjc = {version = ">=8.1", markers = "sys_platform == \"darwin\""} +python-xlib = {version = ">=0.21", markers = "sys_platform == \"linux\""} +pywin32 = {version = ">=302", markers = "sys_platform == \"win32\""} +typing-extensions = ">=4.4.0" + +[package.extras] +dev = ["mypy (>=0.990)", "pywinctl (>=0.1)", "types-python-xlib (>=0.32)", "types-pywin32 (>=305.0.0.3)", "types-setuptools (>=65.5)"] + +[[package]] +name = "pywinctl" +version = "0.3" +description = "Cross-Platform toolkit to get info on and control windows on screen" +optional = false +python-versions = "*" +files = [ + {file = "PyWinCtl-0.3-py3-none-any.whl", hash = "sha256:3603981c87b0c64987e7be857d89450f98792b01f49006a17dac758e11141dd7"}, +] + +[package.dependencies] +pymonctl = ">=0.6" +pyobjc = {version = ">=8.1", markers = "sys_platform == \"darwin\""} +python-xlib = {version = ">=0.21", markers = "sys_platform == \"linux\""} +pywin32 = {version = ">=302", markers = "sys_platform == \"win32\""} +pywinbox = ">=0.6" +typing-extensions = ">=4.4.0" + +[package.extras] +dev = ["mypy (>=0.990)", "types-python-xlib (>=0.32)", "types-pywin32 (>=305.0.0.3)", "types-setuptools (>=65.5)"] + [[package]] name = "pyyaml" version = "6.0.1" @@ -3776,7 +6710,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -3784,16 +6717,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -3810,7 +6735,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -3818,7 +6742,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3950,6 +6873,21 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rubicon-objc" +version = "0.4.7" +description = "A bridge between an Objective C runtime environment and Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "rubicon-objc-0.4.7.tar.gz", hash = "sha256:be937d864bd1229f860defabb89b40c53634eedc36448d89ad3c14eb3286e509"}, + {file = "rubicon_objc-0.4.7-py3-none-any.whl", hash = "sha256:f37108e35d5da1a78ab3eed2d03b095934f5f618329a939e4bd2ada9894eff6e"}, +] + +[package.extras] +dev = ["pre-commit (==3.5.0)", "pytest (==7.4.2)", "pytest-tldr (==0.2.5)", "setuptools-scm[toml] (==8.0.4)", "tox (==4.11.3)"] +docs = ["furo (==2023.9.10)", "pyenchant (==3.2.2)", "sphinx (==7.1.2)", "sphinx (==7.2.6)", "sphinx-autobuild (==2021.3.14)", "sphinx-copybutton (==0.5.2)", "sphinx-tabs (==3.4.1)", "sphinxcontrib-spelling (==8.0.0)"] + [[package]] name = "ruff" version = "0.1.14" @@ -4853,4 +7791,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "1976ee7795d5ac5b257cac8dd83e408ae6e75c48d8c349e3e8e56519727cf52f" +content-hash = "80980e271850e99a5b9005dda4b25a822a4d2b15ba152697c5fb692be4b3bb13" diff --git a/pyproject.toml b/pyproject.toml index 5641a55101..6930193242 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -136,8 +136,10 @@ parameterized = "^0.8" pprofile = "*" polyline = "*" pre-commit = "*" +pyautogui = "*" pyopencl = "*" pygame = "*" +pywinctl = "*" pyprof2calltree = "*" pytest = "*" pytest-cov = "*" diff --git a/selfdrive/test/setup_xvfb.sh b/selfdrive/test/setup_xvfb.sh index 79110fc4fb..806515b0f9 100755 --- a/selfdrive/test/setup_xvfb.sh +++ b/selfdrive/test/setup_xvfb.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Sets up a virtual display for running map renderer and simulator without an X11 display diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index 94ba9a3a97..6c624b66d3 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -3,3 +3,4 @@ playsound test_sound test_translations ui_snapshot +test_ui/report \ No newline at end of file diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py new file mode 100644 index 0000000000..f943786350 --- /dev/null +++ b/selfdrive/ui/tests/test_ui/run.py @@ -0,0 +1,121 @@ +import pathlib +import shutil +import jinja2 +import matplotlib.pyplot as plt +import numpy as np +import os +import pyautogui +import pywinctl +import time +import unittest + +from parameterized import parameterized +from cereal import messaging, log + +from cereal.messaging import SubMaster, PubMaster +from openpilot.common.params import Params +from openpilot.selfdrive.test.helpers import with_processes + +UI_DELAY = 0.5 # may be slower on CI? + +NetworkType = log.DeviceState.NetworkType +NetworkStrength = log.DeviceState.NetworkStrength + +def setup_common(click, pm: PubMaster): + Params().put("DongleId", "123456789012345") + dat = messaging.new_message('deviceState') + dat.deviceState.networkType = NetworkType.cell4G + dat.deviceState.networkStrength = NetworkStrength.moderate + + pm.send("deviceState", dat) + + time.sleep(UI_DELAY) + +def setup_homescreen(click, pm: PubMaster): + setup_common(click, pm) + +def setup_settings_device(click, pm: PubMaster): + setup_common(click, pm) + + click(100, 100) + time.sleep(UI_DELAY) + +def setup_settings_network(click, pm: PubMaster): + setup_common(click, pm) + + setup_settings_device(click, pm) + click(300, 600) + time.sleep(UI_DELAY) + +CASES = { + "homescreen": setup_homescreen, + "settings_device": setup_settings_device, + "settings_network": setup_settings_network, +} + + +TEST_DIR = pathlib.Path(__file__).parent + +TEST_OUTPUT_DIR = TEST_DIR / "report" +SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" + + +class TestUI(unittest.TestCase): + @classmethod + def setUpClass(cls): + os.environ["SCALE"] = "1" + + def setup(self): + self.sm = SubMaster(["uiDebug"]) + self.pm = PubMaster(["deviceState"]) + while not self.sm.valid["uiDebug"]: + self.sm.update(1) + time.sleep(UI_DELAY) # wait a bit more for the UI to finish rendering + self.ui = pywinctl.getWindowsWithTitle("ui")[0] + + def screenshot(self): + im = pyautogui.screenshot(region=(self.ui.left, self.ui.top, self.ui.width, self.ui.height)) + self.assertEqual(im.width, 2160) + self.assertEqual(im.height, 1080) + img = np.array(im) + im.close() + return img + + def click(self, x, y, *args, **kwargs): + pyautogui.click(self.ui.left + x, self.ui.top + y, *args, **kwargs) + + @parameterized.expand(CASES.items()) + @with_processes(["ui"]) + def test_ui(self, name, setup_case): + self.setup() + + setup_case(self.click, self.pm) + + im = self.screenshot() + plt.imsave(SCREENSHOTS_DIR / f"{name}.png", im) + + +def create_html_report(): + OUTPUT_FILE = TEST_OUTPUT_DIR / "index.html" + + with open(TEST_DIR / "template.html") as f: + template = jinja2.Template(f.read()) + + cases = {f.stem: (str(f.relative_to(TEST_OUTPUT_DIR)), "reference.png") for f in SCREENSHOTS_DIR.glob("*.png")} + + with open(OUTPUT_FILE, "w") as f: + f.write(template.render(cases=cases)) + +def create_screenshots(): + if TEST_OUTPUT_DIR.exists(): + shutil.rmtree(TEST_OUTPUT_DIR) + + SCREENSHOTS_DIR.mkdir(parents=True) + unittest.main(exit=False) + +if __name__ == "__main__": + print("creating test screenshots") + create_screenshots() + + print("creating html report") + create_html_report() diff --git a/selfdrive/ui/tests/test_ui/template.html b/selfdrive/ui/tests/test_ui/template.html new file mode 100644 index 0000000000..68df5879e6 --- /dev/null +++ b/selfdrive/ui/tests/test_ui/template.html @@ -0,0 +1,34 @@ + + + + +{% for name, (image, ref_image) in cases.items() %} + +

{{name}}

+
+
+ +
+
+ +
+ +{% endfor %} + \ No newline at end of file From f55e6a3b81bf08bce9cb9d2357db5bbf529d1d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 24 Jan 2024 15:26:22 -0800 Subject: [PATCH 567/724] Los Angeles Model 2 (#31135) * 7045fb53-6739-4338-8426-20cbd4ad7832/600 * Update constants * Update parse * Typos * Update ref * 26057144-130f-48e1-8a98-00fae53ae7db/700 * Update ref --- selfdrive/modeld/constants.py | 2 +- selfdrive/modeld/modeld.py | 6 +++--- selfdrive/modeld/models/supercombo.onnx | 4 ++-- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/modeld/constants.py b/selfdrive/modeld/constants.py index e1741cbdcf..dda1ff5e33 100644 --- a/selfdrive/modeld/constants.py +++ b/selfdrive/modeld/constants.py @@ -23,7 +23,7 @@ class ModelConstants: DRIVING_STYLE_LEN = 12 LAT_PLANNER_STATE_LEN = 4 LATERAL_CONTROL_PARAMS_LEN = 2 - PREV_DESIRED_CURVS_LEN = 20 + PREV_DESIRED_CURV_LEN = 1 # model outputs constants FCW_THRESHOLDS_5MS2 = np.array([.05, .05, .15, .15, .15], dtype=np.float32) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 00c46c3bce..5b227d08e9 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -58,7 +58,7 @@ class ModelState: 'desire': np.zeros(ModelConstants.DESIRE_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), 'traffic_convention': np.zeros(ModelConstants.TRAFFIC_CONVENTION_LEN, dtype=np.float32), 'lateral_control_params': np.zeros(ModelConstants.LATERAL_CONTROL_PARAMS_LEN, dtype=np.float32), - 'prev_desired_curvs': np.zeros(ModelConstants.PREV_DESIRED_CURVS_LEN, dtype=np.float32), + 'prev_desired_curv': np.zeros(ModelConstants.PREV_DESIRED_CURV_LEN * (ModelConstants.HISTORY_BUFFER_LEN+1), dtype=np.float32), 'nav_features': np.zeros(ModelConstants.NAV_FEATURE_LEN, dtype=np.float32), 'nav_instructions': np.zeros(ModelConstants.NAV_INSTRUCTION_LEN, dtype=np.float32), 'features_buffer': np.zeros(ModelConstants.HISTORY_BUFFER_LEN * ModelConstants.FEATURE_LEN, dtype=np.float32), @@ -110,8 +110,8 @@ class ModelState: self.inputs['features_buffer'][:-ModelConstants.FEATURE_LEN] = self.inputs['features_buffer'][ModelConstants.FEATURE_LEN:] self.inputs['features_buffer'][-ModelConstants.FEATURE_LEN:] = outputs['hidden_state'][0, :] - self.inputs['prev_desired_curvs'][:-1] = self.inputs['prev_desired_curvs'][1:] - self.inputs['prev_desired_curvs'][-1] = outputs['desired_curvature'][0, 0] + self.inputs['prev_desired_curv'][:-ModelConstants.PREV_DESIRED_CURV_LEN] = self.inputs['prev_desired_curv'][ModelConstants.PREV_DESIRED_CURV_LEN:] + self.inputs['prev_desired_curv'][-ModelConstants.PREV_DESIRED_CURV_LEN:] = outputs['desired_curvature'][0, :] return outputs diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index e91940c728..dc93d84135 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b085c1ba231bc381f78462bda136172787371d5d83b6e1bcd340aad17290ebc -size 48197170 +oid sha256:763821410b35b06b598cacfa5bc3e312610b3f8de2729e0d5954d7571b6794be +size 48219112 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 85bcf123e7..2e78b1e608 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -cfdad3a695e3562ca32accce043b358291f0eef2 +4b983afeba5ec953f0d37e7b3499bde5209728eb From 1a202a55c1db9e8db3d69a30fcfb9f978cf20a53 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 24 Jan 2024 23:37:32 +0000 Subject: [PATCH 568/724] Ford: add missing Mach-E fw (#31145) From the route 83a4e056c7072678/2023-11-13--16-51-33 (which is already in selfdrive/car/tests/routes.py, added in #30691). --- selfdrive/car/ford/fingerprints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py index 8594ab95df..0cd812bc6b 100644 --- a/selfdrive/car/ford/fingerprints.py +++ b/selfdrive/car/ford/fingerprints.py @@ -145,6 +145,7 @@ FW_VERSIONS = { b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ + b'MJ98-14C204-BBP\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MJ98-14C204-BBS\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], From ae25a135947a1e2b06245cb63cc0f9cf4c44dd41 Mon Sep 17 00:00:00 2001 From: kovat74 <93383675+kovat74@users.noreply.github.com> Date: Thu, 25 Jan 2024 01:15:20 +0100 Subject: [PATCH 569/724] Added firmware fingerprint IONIQ 2020 EV (#31049) --- selfdrive/car/hyundai/fingerprints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 0c5925c744..56b4217403 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -199,6 +199,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7200 ', b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7000 ', + b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7500 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7310 4APEC101', @@ -209,6 +210,7 @@ FW_VERSIONS = { b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.03 95740-G2500 190516', b'\xf1\x00AEE MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2700 201027', ], }, CAR.IONIQ_EV_LTD: { From 3676ae4da3dc4fbc2ea6aa84369a5f460662f6e4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 24 Jan 2024 16:51:06 -0800 Subject: [PATCH 570/724] test_ui: add onroad state (#31148) add onroad --- selfdrive/ui/tests/test_ui/run.py | 70 +++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index f943786350..48223d3fe8 100644 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -10,22 +10,37 @@ import time import unittest from parameterized import parameterized -from cereal import messaging, log +from cereal import messaging, car, log +from cereal.visionipc import VisionIpcServer, VisionStreamType from cereal.messaging import SubMaster, PubMaster from openpilot.common.params import Params +from openpilot.common.realtime import DT_MDL +from openpilot.common.transformations.camera import tici_f_frame_size from openpilot.selfdrive.test.helpers import with_processes +from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state +from openpilot.tools.webcam.camera import Camera UI_DELAY = 0.5 # may be slower on CI? NetworkType = log.DeviceState.NetworkType NetworkStrength = log.DeviceState.NetworkStrength +EventName = car.CarEvent.EventName +EVENTS_BY_NAME = {v: k for k, v in EventName.schema.enumerants.items()} + + def setup_common(click, pm: PubMaster): Params().put("DongleId", "123456789012345") dat = messaging.new_message('deviceState') + dat.deviceState.started = True dat.deviceState.networkType = NetworkType.cell4G dat.deviceState.networkStrength = NetworkStrength.moderate + dat.deviceState.freeSpacePercent = 80 + dat.deviceState.memoryUsagePercent = 2 + dat.deviceState.cpuTempC = [2,]*3 + dat.deviceState.gpuTempC = [2,]*3 + dat.deviceState.cpuUsagePercent = [2,]*8 pm.send("deviceState", dat) @@ -47,13 +62,61 @@ def setup_settings_network(click, pm: PubMaster): click(300, 600) time.sleep(UI_DELAY) +def setup_onroad(click, pm: PubMaster): + setup_common(click, pm) + + dat = messaging.new_message('pandaStates', 1) + dat.pandaStates[0].ignitionLine = True + dat.pandaStates[0].pandaType = log.PandaState.PandaType.uno + + pm.send("pandaStates", dat) + + server = VisionIpcServer("camerad") + server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, *tici_f_frame_size) + server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *tici_f_frame_size) + server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *tici_f_frame_size) + server.start_listener() + + time.sleep(UI_DELAY) + + IMG = Camera.bgr2nv12(np.random.randint(0, 255, (*tici_f_frame_size,3), dtype=np.uint8)) + IMG_BYTES = IMG.flatten().tobytes() + + cams = ('roadCameraState', 'wideRoadCameraState') + + frame_id = 0 + for cam in cams: + msg = messaging.new_message(cam) + cs = getattr(msg, cam) + cs.frameId = frame_id + cs.timestampSof = int((frame_id * DT_MDL) * 1e9) + cs.timestampEof = int((frame_id * DT_MDL) * 1e9) + cam_meta = meta_from_camera_state(cam) + + pm.send(msg.which(), msg) + server.send(cam_meta.stream, IMG_BYTES, cs.frameId, cs.timestampSof, cs.timestampEof) + + time.sleep(UI_DELAY) + +def setup_onroad_map(click, pm: PubMaster): + setup_onroad(click, pm) + click(500, 500) + time.sleep(UI_DELAY) + +def setup_onroad_sidebar(click, pm: PubMaster): + setup_onroad_map(click, pm) + click(500, 500) + time.sleep(UI_DELAY) + CASES = { "homescreen": setup_homescreen, "settings_device": setup_settings_device, "settings_network": setup_settings_network, + "onroad": setup_onroad, + "onroad_map": setup_onroad_map, + "onroad_map_sidebar": setup_onroad_sidebar } - TEST_DIR = pathlib.Path(__file__).parent TEST_OUTPUT_DIR = TEST_DIR / "report" @@ -67,7 +130,7 @@ class TestUI(unittest.TestCase): def setup(self): self.sm = SubMaster(["uiDebug"]) - self.pm = PubMaster(["deviceState"]) + self.pm = PubMaster(["deviceState", "pandaStates", "controlsState", 'roadCameraState', 'wideRoadCameraState']) while not self.sm.valid["uiDebug"]: self.sm.update(1) time.sleep(UI_DELAY) # wait a bit more for the UI to finish rendering @@ -102,6 +165,7 @@ def create_html_report(): template = jinja2.Template(f.read()) cases = {f.stem: (str(f.relative_to(TEST_OUTPUT_DIR)), "reference.png") for f in SCREENSHOTS_DIR.glob("*.png")} + cases = dict(sorted(cases.items())) with open(OUTPUT_FILE, "w") as f: f.write(template.render(cases=cases)) From 951d7e9cd0f7a12af81e3224771e2dec079ca22e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 24 Jan 2024 21:29:18 -0800 Subject: [PATCH 571/724] Chrysler: add FW from CAN fingerprinting cars last 90 days on 0.9.5-release (#31142) * print * name * add missing * ----- * add list so can fingerprint * transmission doesn't exist on these * fix abs addr! * apparantly abs isn't on these cars * 2018 too * clean up * rm abs * add missing versions! * Revert "add missing versions!" This reverts commit 45d36a49363a9c540841e6618c1a73cd3948c754. * need this to fingerprint! * Add FW from CAN fingerprinting cars last 90 days on 0.9.5-release * need this temporarily * Add rest of dongles * update docs * remove FINGERPRINTS * Apply suggestions from code review * Update selfdrive/car/fw_versions.py --- docs/CARS.md | 5 +- selfdrive/car/chrysler/fingerprints.py | 356 +++++++++++++++++++++---- selfdrive/car/chrysler/values.py | 7 +- 3 files changed, 304 insertions(+), 64 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index bb55f9e0b7..92b8dd952d 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 274 Supported Cars +# 275 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -29,7 +29,8 @@ A supported vehicle is one that just works when you install a comma device. All |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Chrysler|Pacifica Hybrid 2017|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Chrysler|Pacifica Hybrid 2018|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica Hybrid 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|| |Ford|Bronco Sport 2021-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 28f590b23a..47be008735 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -1,117 +1,357 @@ -# ruff: noqa: E501 from cereal import car from openpilot.selfdrive.car.chrysler.values import CAR Ecu = car.CarParams.Ecu -# Unique CAN messages: -# Only the hybrids have 270: 8 -# Only the gas have 55: 8, 416: 7 -# For 564, All 2017 have length 4, whereas 2018-19 have length 8. -# For 924, Pacifica 2017 has length 3, whereas all 2018-19 have length 8. -# For 560, All 2019 have length 8, whereas all 2017-18 have length 4. -# -# Jeep Grand Cherokee unique messages: -# 2017 Trailhawk: 618: 8 -# For 924, Trailhawk 2017 has length 3, whereas 2018 V6 has length 8. - - -FINGERPRINTS = { - CAR.PACIFICA_2017_HYBRID: [{ - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 840: 8, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 3, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1892: 8, 2016: 8, 2024: 8 - }], - CAR.PACIFICA_2018: [{ - 55: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 8, 926: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 - }, - { - 55: 8, 58: 6, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 926: 3, 937: 8, 947: 8, 948: 8, 956: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 - }], - CAR.PACIFICA_2020: [{ - 55: 8, 179: 8, 181: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 536: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 776: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 886: 8, 897: 8, 906: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 7, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1543: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], - CAR.PACIFICA_2018_HYBRID: [{ - 68: 8, 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8 - }, - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2016: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 - }], - CAR.PACIFICA_2019_HYBRID: [{ - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1538: 8 - }, - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1537: 8 - }, - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 528: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1562: 8, 1570: 8 - }, - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1258: 8, 1259: 8, 1260: 8, 1262: 8, 1284: 8, 1536: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 1899: 8, 1900: 8, 1902: 8, 2015: 8, 2016: 8, 2017: 8, 2018: 8, 2019: 8, 2020: 8, 2023: 8, 2024: 8, 2026: 8, 2027: 8, 2028: 8, 2031: 8 - }, - { - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 450: 8, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 653: 8, 654: 8, 655: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 683: 8, 701: 8, 703: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 796: 8, 797: 8, 798: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 805: 8, 807: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 886: 8, 897: 8, 906: 8, 908: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 958: 8, 959: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 2018: 8, 2020: 8, 2026: 8, 2028: 8 - }], - CAR.JEEP_GRAND_CHEROKEE: [{ - 55: 8, 168: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 975: 8, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1543: 8, 1562: 8, 1576: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }, - { - 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 658: 6, 660: 8, 671: 8, 672: 8, 678: 8, 680: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 752: 2, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 783: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 844: 5, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1235: 8, 1242: 8, 1252: 8, 1792: 8, 1798: 8, 1799: 8, 1810: 8, 1813: 8, 1824: 8, 1825: 8, 1840: 8, 1856: 8, 1858: 8, 1859: 8, 1860: 8, 1862: 8, 1863: 8, 1872: 8, 1875: 8, 1879: 8, 1882: 8, 1888: 8, 1892: 8, 1927: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 2000: 8, 2001: 8, 2004: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], - CAR.JEEP_GRAND_CHEROKEE_2019: [{ - 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1538: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], -} - FW_VERSIONS = { + CAR.PACIFICA_2017_HYBRID: { + (Ecu.combinationMeter, 0x742, None): [ + b'68239262AH', + b'68239262AI', + b'68239262AJ', + b'68239263AH', + b'68239263AJ', + ], + (Ecu.srs, 0x744, None): [ + b'68238840AH', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'68226356AI', + ], + (Ecu.eps, 0x75a, None): [ + b'68288309AC', + b'68288309AD', + ], + (Ecu.engine, 0x7e0, None): [ + b'68277480AV ', + b'68277480AX ', + b'68277480AZ ', + ], + (Ecu.hybrid, 0x7e2, None): [ + b'05190175BF', + b'05190175BH', + b'05190226AK', + ], + }, + CAR.PACIFICA_2018: { + (Ecu.combinationMeter, 0x742, None): [ + b'68227902AF', + b'68227902AG', + b'68227902AH', + b'68360252AC', + ], + (Ecu.srs, 0x744, None): [ + b'68211617AF', + b'68211617AG', + b'68358974AC', + b'68405937AA', + ], + (Ecu.abs, 0x747, None): [ + b'68222747AG', + b'68330876AA', + b'68330876AB', + b'68352227AA', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'04672758AA', + b'68226356AF', + b'68226356AH', + b'68226356AI', + ], + (Ecu.eps, 0x75a, None): [ + b'68288891AE', + b'68378884AA', + b'68525338AA', + b'68525338AB', + ], + (Ecu.engine, 0x7e0, None): [ + b'68267018AO ', + b'68267020AJ ', + b'68340762AD ', + b'68340764AD ', + b'68352652AE ', + b'68366851AH ', + b'68366853AE ', + b'68372861AF ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'68277370AJ', + b'68277370AM', + b'68277372AD', + b'68277372AN', + b'68277374AA', + b'68277374AB', + b'68277374AD', + b'68277374AN', + b'68367471AC', + b'68380571AB', + ], + }, CAR.PACIFICA_2020: { (Ecu.combinationMeter, 0x742, None): [ + b'68405327AC', + b'68436233AB', + b'68436233AC', + b'68436250AE', + b'68529067AA', b'68594993AB', ], (Ecu.srs, 0x744, None): [ + b'68405565AB', + b'68405565AC', + b'68444299AC', + b'68480708AC', b'68526663AB', ], (Ecu.abs, 0x747, None): [ + b'68397394AA', + b'68433480AB', + b'68453575AF', + b'68577676AA', b'68593395AA', ], (Ecu.fwdRadar, 0x753, None): [ + b'04672758AA', + b'04672758AB', + b'68417813AF', + b'68540436AA', + b'68540436AC', + b'68540436AD', b'68598670AB', ], (Ecu.eps, 0x75a, None): [ + b'68416742AA', + b'68460393AA', + b'68460393AB', + b'68494461AB', + b'68524936AA', + b'68524936AB', + b'68525338AB', b'68594340AB', ], (Ecu.engine, 0x7e0, None): [ + b'68413871AD ', + b'68413871AE ', + b'68413871AH ', + b'68413871AI ', + b'68413873AI ', + b'68443120AE ', + b'68443123AC ', + b'68443125AC ', + b'68526752AD ', + b'68526752AE ', + b'68526754AE ', + b'68536264AE ', b'68700306AB ', ], (Ecu.transmission, 0x7e1, None): [ + b'68414271AC', + b'68414271AD', + b'68414275AC', + b'68443154AB', + b'68443155AC', + b'68443158AB', + b'68501050AD', + b'68527221AB', + b'68527223AB', b'68586231AD', ], }, + CAR.PACIFICA_2018_HYBRID: { + (Ecu.combinationMeter, 0x742, None): [ + b'68358439AE', + b'68358439AG', + ], + (Ecu.srs, 0x744, None): [ + b'68358990AC', + b'68405939AA', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'04672758AA', + ], + (Ecu.eps, 0x75a, None): [ + b'68288309AD', + b'68525339AA', + ], + (Ecu.engine, 0x7e0, None): [ + b'68366580AI ', + b'68366580AK ', + b'68366580AM ', + ], + (Ecu.hybrid, 0x7e2, None): [ + b'05190226AI', + b'05190226AK', + b'05190226AM', + ], + }, + CAR.PACIFICA_2019_HYBRID: { + (Ecu.combinationMeter, 0x742, None): [ + b'68405292AC', + b'68434956AC', + b'68434956AD', + b'68434960AE', + b'68434960AF', + b'68529064AB', + b'68594990AB', + ], + (Ecu.srs, 0x744, None): [ + b'68405567AB', + b'68405567AC', + b'68453076AD', + b'68480710AC', + b'68526665AB', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'04672758AB', + b'68417813AF', + b'68540436AA', + b'68540436AB', + b'68540436AC', + b'68540436AD', + b'68598670AB', + b'68598670AC', + ], + (Ecu.eps, 0x75a, None): [ + b'68416741AA', + b'68460392AA', + b'68525339AA', + b'68525339AB', + b'68594341AB', + ], + (Ecu.engine, 0x7e0, None): [ + b'68416680AE ', + b'68416680AF ', + b'68416680AG ', + b'68444228AD ', + b'68444228AE ', + b'68444228AF ', + b'68499122AD ', + b'68499122AE ', + b'68499122AF ', + b'68526772AD ', + b'68526772AH ', + b'68599493AC ', + ], + (Ecu.hybrid, 0x7e2, None): [ + b'05185116AF', + b'05185116AJ', + b'05185116AK', + b'05190240AP', + b'05190240AQ', + b'05190240AR', + b'05190265AG', + b'05190265AH', + b'05190289AE', + b'68540977AH', + b'68540977AK', + b'68597647AE', + ], + }, + CAR.JEEP_GRAND_CHEROKEE: { + (Ecu.combinationMeter, 0x742, None): [ + b'68302211AC', + b'68302212AD', + b'68302246AC', + b'68331511AC', + b'68331574AC', + b'68331687AC', + b'68340272AD', + ], + (Ecu.srs, 0x744, None): [ + b'68316742AB', + b'68355363AB', + ], + (Ecu.abs, 0x747, None): [ + b'68306178AD', + b'68336276AB', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'04672627AB', + b'68332015AB', + ], + (Ecu.eps, 0x75a, None): [ + b'68321644AB', + b'68321644AC', + b'68321646AC', + b'68321648AC', + ], + (Ecu.engine, 0x7e0, None): [ + b'05035920AE ', + b'68284455AI ', + b'68284456AI ', + b'68284477AF ', + b'68325564AH ', + b'68325565AH ', + b'68325565AI ', + b'68325618AD ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'05035517AH', + b'68311218AC', + b'68311223AF', + b'68311223AG', + b'68361911AE', + b'68361911AF', + b'68361911AH', + b'68361916AD', + ], + }, CAR.JEEP_GRAND_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ + b'68402703AB', + b'68402708AB', b'68402971AD', b'68454144AD', b'68454152AB', + b'68454156AB', + b'68516650AB', + b'68516651AB', + b'68516669AB', + b'68516671AB', + b'68516683AB', ], (Ecu.srs, 0x744, None): [ b'68355363AB', + b'68355364AB', ], (Ecu.abs, 0x747, None): [ + b'68408639AC', b'68408639AD', + b'68499978AB', ], (Ecu.fwdRadar, 0x753, None): [ + b'04672788AA', b'68456722AC', ], (Ecu.eps, 0x75a, None): [ + b'68417279AA', + b'68417280AA', b'68453431AA', b'68453433AA', + b'68453435AA', + b'68499171AA', + b'68499171AB', + b'68501183AA', ], (Ecu.engine, 0x7e0, None): [ b'05035674AB ', + b'68412635AG ', + b'68422860AB', + b'68449435AE ', b'68496223AA ', + b'68504959AD ', + b'68504960AD ', + b'68504993AC ', ], (Ecu.transmission, 0x7e1, None): [ b'05035707AA', + b'68419672AC', + b'68423905AB', + b'68449258AC', b'68495807AA', b'68495807AB', + b'68503641AC', + b'68503664AC', ], }, CAR.RAM_1500: { diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 8d873705fa..34e602562a 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -64,8 +64,8 @@ class ChryslerCarInfo(CarInfo): CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { - CAR.PACIFICA_2017_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2017-18"), - CAR.PACIFICA_2018_HYBRID: None, # same platforms + CAR.PACIFICA_2017_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2017"), + CAR.PACIFICA_2018_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2018"), CAR.PACIFICA_2019_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2019-23"), CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"), CAR.PACIFICA_2020: [ @@ -117,8 +117,7 @@ FW_QUERY_CONFIG = FwQueryConfig( ), ], extra_ecus=[ - (Ecu.hybrid, 0x7e2, None), # manages transmission on hybrids - (Ecu.abs, 0x7e4, None), # alt address for abs on hybrids + (Ecu.abs, 0x7e4, None), # alt address for abs on hybrids, NOTE: not on all hybrid platforms ], ) From 8472ae5317d98a41dae28b2cd1e72ac05b5cae04 Mon Sep 17 00:00:00 2001 From: Ruben Medina Date: Wed, 24 Jan 2024 23:35:39 -0800 Subject: [PATCH 572/724] add size to system alt image manifest entry (#31154) --- system/hardware/tici/agnos.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 080fa2cf8e..3fe3942305 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -70,7 +70,8 @@ "has_ab": true, "alt": { "hash": "2fb81e58f4bc6c4e5e71c8e7ac7553f85082c430627d7a5cc54a6bbc82862500", - "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98.img.xz" + "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98.img.xz", + "size": 4543090376 } } ] \ No newline at end of file From be15b31696af43accbffc8d59b3a5acbb4725682 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 02:02:00 -0800 Subject: [PATCH 573/724] Toyota: use radar in place of abs for platform code ECUs (#31043) * use radar instead of abs * str the strenum * Lexus ES: non-TSS2 probably don't have stop and go without DSU-pull and/or pedal * remove from NO_STOP_TIMER_CAR. this set is so confusing * Combine ES & ESH (preferring ESH's DSU msgs) * ES: forgot substitute * comment * Highlander: combine, preferring the hybrid for DSU msg mismatches (0x365 and 0x366) * remove duplicates and sort * update docs * toyota non_essential ECU test * Lexus RX: combine, preferring hybrid's msgs (they all exist on ICE) * sort and remove dups * parameterize so we can test with notebook * this too * clean up * clean up * tuple makes more sense * update comments * update * update * update --- selfdrive/car/toyota/values.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index a16864cce8..4f6e55b5d3 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -348,13 +348,16 @@ FW_LEN_CODE = re.compile(b'^[\x01-\x03]') # highest seen is 3 chunks, 16 bytes FW_CHUNK_LEN = 16 # List of ECUs that are most unique across openpilot platforms -# TODO: use hybrid ECU, splits similar ICE and hybrid variants # - fwdCamera: describes actual features related to ADAS. For example, on the Avalon it describes # when TSS-P became standard, whether the car supports stop and go, and whether it's TSS2. # On the RAV4, it describes the move to the radar doing ACC, and the use of LTA for lane keeping. -# - abs: differentiates hybrid/ICE on most cars (Corolla TSS2 is an exception) +# Note that the platform codes & major versions do not describe features in plain text, only with +# matching against other seen FW versions in the database they can describe features. +# - fwdRadar: sanity check against fwdCamera, commonly shares a platform code. +# For example the RAV4 2022's new radar architecture is shown for both with platform code. +# - abs: differentiates hybrid/ICE on most cars (Corolla TSS2 is an exception, not used due to hybrid platform combination) # - eps: describes lateral API changes for the EPS, such as using LTA for lane keeping and rejecting LKA messages -PLATFORM_CODE_ECUS = [Ecu.fwdCamera, Ecu.abs, Ecu.eps] +PLATFORM_CODE_ECUS = (Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps) # These platforms have at least one platform code for all ECUs shared with another platform. FUZZY_EXCLUDED_PLATFORMS: set[CAR] = set() From edd2428b6067381ffaa25420d6fd9f0f142038a8 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 25 Jan 2024 11:09:09 -0800 Subject: [PATCH 574/724] LogReader: fail-fast on invalid segments (#31152) * don't check all * is --- tools/lib/logreader.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 6c1174611b..d6c1cc90c2 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -161,12 +161,14 @@ def direct_source(file_or_url): return [file_or_url] def get_invalid_files(files): - return [f for f in files if f is None or not file_exists(f)] + for f in files: + if f is None or not file_exists(f): + yield f def check_source(source, *args): try: files = source(*args) - assert len(get_invalid_files(files)) == 0 + assert next(get_invalid_files(files), None) is None return True, files except Exception: return False, None @@ -177,7 +179,6 @@ def auto_source(*args): valid, ret = check_source(source, *args) if valid: return ret - return comma_api_source(*args) def parse_useradmin(identifier): @@ -256,7 +257,7 @@ class LogReader: def reset(self): self.logreader_identifiers = self._parse_identifiers(self.identifier) - invalid_count = len(get_invalid_files(self.logreader_identifiers)) + invalid_count = len(list(get_invalid_files(self.logreader_identifiers))) assert invalid_count == 0, f"{invalid_count}/{len(self.logreader_identifiers)} invalid log(s) found, please ensure all logs \ are uploaded or auto fallback to qlogs with '/a' selector at the end of the route name." From 6f2f966a2f03335da693f278825becb7f7c841bb Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 25 Jan 2024 11:28:09 -0800 Subject: [PATCH 575/724] CI: add bot labels to repo_maintenance (#31159) * add bot lables * prefix --- .github/workflows/repo-maintenance.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 1f14895e21..26b1192efa 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -29,7 +29,8 @@ jobs: branch: auto-bump-submodules base: master delete-branch: true - body: 'Automatic PR from repo-maintenance -> bump_submodules' + body: '[bot] Automatic PR from repo-maintenance -> bump_submodules' + labels: bot package_updates: name: package_updates runs-on: ubuntu-20.04 @@ -54,4 +55,5 @@ jobs: branch: auto-package-updates base: master delete-branch: true - body: 'Automatic PR from repo-maintenance -> package_updates' + body: '[bot] Automatic PR from repo-maintenance -> package_updates' + labels: bot From 35374f4d577d0bf835ae146954010ef61a3c2fa4 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 25 Jan 2024 12:13:43 -0800 Subject: [PATCH 576/724] test_loggerd: make rotation test more robust (#31160) * no randomness * lots * Revert "lots" This reverts commit 2052fd6f7b8dc4c53fbb7c726026070918d5de00. --- system/loggerd/tests/test_loggerd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index dde12b646d..0f2fc918b7 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -152,8 +152,8 @@ class TestLoggerd: vipc_server.create_buffers_with_sizes(stream_type, 40, False, *(frame_spec)) vipc_server.start_listener() - num_segs = random.randint(2, 5) - length = random.randint(1, 3) + num_segs = 3 + length = 2 os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length) managed_processes["loggerd"].start() managed_processes["encoderd"].start() From 3b618c4dfd81238c8e76524cbedb073d70d69794 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 25 Jan 2024 12:44:01 -0800 Subject: [PATCH 577/724] Simulator: pin metadrive + use pip version (#31161) * pin metadrive * pin * not on git --- poetry.lock | 14 +++++--------- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index ecfdac4497..694486a75e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2178,8 +2178,10 @@ version = "0.4.2.2" description = "An open-ended driving simulator with infinite scenes" optional = false python-versions = ">=3.6, <3.12" -files = [] -develop = false +files = [ + {file = "metadrive-simulator-0.4.2.2.tar.gz", hash = "sha256:dcce9f9c73b6055e70480af8543058b95e8c4f68d2595107f3ef36c9be03f6bb"}, + {file = "metadrive_simulator-0.4.2.2-py3-none-any.whl", hash = "sha256:165ba0b5275313a71090ba0e73d51b7b5e05391b071d3a2114d999ac9287d150"}, +] [package.dependencies] filelock = "*" @@ -2210,12 +2212,6 @@ cuda = ["PyOpenGL (==3.1.6)", "PyOpenGL-accelerate (==3.1.6)", "cuda-python (==1 gym = ["gym (>=0.19.0,<=0.26.0)"] ros = ["zmq"] -[package.source] -type = "git" -url = "https://github.com/metadriverse/metadrive.git" -reference = "main" -resolved_reference = "bc162e1b423bd194a58ef2269103f1bcea5f53c7" - [[package]] name = "mouseinfo" version = "0.1.3" @@ -7791,4 +7787,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "80980e271850e99a5b9005dda4b25a822a4d2b15ba152697c5fb692be4b3bb13" +content-hash = "4f771435666b64f677d7dd2b4c9b4ec4bbe6fcbb004d13f19e0e79ae138c92b9" diff --git a/pyproject.toml b/pyproject.toml index 6930193242..0d9772f695 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,7 +126,7 @@ inputs = "*" Jinja2 = "*" lru-dict = "*" matplotlib = "*" -metadrive-simulator = { git = "https://github.com/metadriverse/metadrive.git", rev ="main", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies +metadrive-simulator = { version = "0.4.2.2", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies mpld3 = "*" mypy = "*" myst-parser = "*" From 410db2cd9fafdf17700179fe14999775551edc78 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 25 Jan 2024 12:45:43 -0800 Subject: [PATCH 578/724] Simulator: add comment about mapbox tokens (#31162) * add to readme --- tools/sim/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sim/README.md b/tools/sim/README.md index f6ddde2580..ff95d16de6 100644 --- a/tools/sim/README.md +++ b/tools/sim/README.md @@ -4,7 +4,7 @@ openpilot in simulator openpilot implements a [bridge](run_bridge.py) that allows it to run in the [MetaDrive simulator](https://github.com/metadriverse/metadrive). ## Launching openpilot -First, start openpilot. +First, start openpilot. Note that you will either need a [mapbox token](https://docs.mapbox.com/help/getting-started/access-tokens/#how-access-tokens-work) (set with ```export MAPBOX_TOKEN="1234"```), or to disable mapsd with ```export BLOCK=mapsd``` ``` bash # Run locally ./tools/sim/launch_openpilot.sh From 6901aada14df6aa68b5e3970da0487b8d2b4df8c Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Thu, 25 Jan 2024 13:26:12 -0800 Subject: [PATCH 579/724] filereader should use connection pool for file_exists (#31158) * filereader should use connection pool for file_exists * remove debug var --- tools/lib/filereader.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/lib/filereader.py b/tools/lib/filereader.py index 15e618f649..1db3207e4b 100644 --- a/tools/lib/filereader.py +++ b/tools/lib/filereader.py @@ -1,5 +1,4 @@ import os -import requests from openpilot.tools.lib.url_file import URLFile @@ -13,7 +12,7 @@ def resolve_name(fn): def file_exists(fn): fn = resolve_name(fn) if fn.startswith(("http://", "https://")): - return requests.head(fn, allow_redirects=True).status_code == 200 + return URLFile(fn).get_length_online() != -1 return os.path.exists(fn) def FileReader(fn, debug=False): From 80ea4db7bb19c7758aae6dda1997b5bf5b27dc9b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 25 Jan 2024 13:33:47 -0800 Subject: [PATCH 580/724] update factory reset --- selfdrive/ui/qt/setup/reset.cc | 9 +-------- selfdrive/ui/qt/setup/reset.h | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/selfdrive/ui/qt/setup/reset.cc b/selfdrive/ui/qt/setup/reset.cc index 7999dd640b..c9e0525784 100644 --- a/selfdrive/ui/qt/setup/reset.cc +++ b/selfdrive/ui/qt/setup/reset.cc @@ -57,7 +57,7 @@ Reset::Reset(ResetMode mode, QWidget *parent) : QWidget(parent) { main_layout->addSpacing(60); - body = new QLabel(tr("Press confirm to erase all content and settings. Press cancel to resume boot.")); + body = new QLabel(tr("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot.")); body->setWordWrap(true); body->setStyleSheet("font-size: 80px; font-weight: light;"); main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft); @@ -97,11 +97,6 @@ Reset::Reset(ResetMode mode, QWidget *parent) : QWidget(parent) { body->setText(tr("Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device.")); } - // automatically start if we're just finishing up an ABL reset - if (mode == ResetMode::FORMAT) { - startReset(); - } - setStyleSheet(R"( * { font-family: Inter; @@ -129,8 +124,6 @@ int main(int argc, char *argv[]) { if (argc > 1) { if (strcmp(argv[1], "--recover") == 0) { mode = ResetMode::RECOVER; - } else if (strcmp(argv[1], "--format") == 0) { - mode = ResetMode::FORMAT; } } diff --git a/selfdrive/ui/qt/setup/reset.h b/selfdrive/ui/qt/setup/reset.h index 04a191d829..8bf368e3e8 100644 --- a/selfdrive/ui/qt/setup/reset.h +++ b/selfdrive/ui/qt/setup/reset.h @@ -5,7 +5,6 @@ enum ResetMode { USER_RESET, // user initiated a factory reset from openpilot RECOVER, // userdata is corrupt for some reason, give a chance to recover - FORMAT, // finish up an ABL factory reset }; class Reset : public QWidget { From 4ecaa6da4595b2c82d4d17358717536e6f28e0da Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 25 Jan 2024 13:44:26 -0800 Subject: [PATCH 581/724] update translations --- selfdrive/ui/translations/main_ar.ts | 8 ++++---- selfdrive/ui/translations/main_de.ts | 6 +++--- selfdrive/ui/translations/main_fr.ts | 8 ++++---- selfdrive/ui/translations/main_ja.ts | 6 +++--- selfdrive/ui/translations/main_ko.ts | 8 ++++---- selfdrive/ui/translations/main_pt-BR.ts | 8 ++++---- selfdrive/ui/translations/main_th.ts | 8 ++++---- selfdrive/ui/translations/main_tr.ts | 4 ++-- selfdrive/ui/translations/main_zh-CHS.ts | 8 ++++---- selfdrive/ui/translations/main_zh-CHT.ts | 8 ++++---- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index b7e35d7b6c..eb6a580c01 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -648,14 +648,14 @@ This may take up to a minute. يتم إعادة ضبط الجهاز... قد يستغرق الأمر حوالي الدقيقة.
- - Press confirm to erase all content and settings. Press cancel to resume boot. - اضغط على تأكيد لمسح جميع المحتويات والإعدادات. اضغط على إلغاء لمتابعة التشغيل. - Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. غير قادر على تحميل جزء البيانات. قد يكون الجزء تالفاً. اضغط على تأكيد لمسح جهازك وإعادة ضبطه. + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + +
SettingsWindow diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index deab47e2cf..6d814b9625 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -630,12 +630,12 @@ - Press confirm to erase all content and settings. Press cancel to resume boot. + Resetting device... +This may take up to a minute. - Resetting device... -This may take up to a minute. + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 8cd5a4ea28..cd96c466e9 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -620,10 +620,6 @@ Cela peut prendre jusqu'à une minute. System Reset Réinitialisation du système - - Press confirm to erase all content and settings. Press cancel to resume boot. - Appuyez sur confirmer pour effacer tout le contenu et les paramètres. Appuyez sur annuler pour reprendre le démarrage. - Cancel Annuler @@ -640,6 +636,10 @@ Cela peut prendre jusqu'à une minute. Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. Impossible de monter la partition data. La partition peut être corrompue. Appuyez sur confirmer pour effacer et réinitialiser votre appareil. + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 27cd7246d0..d07c7d525a 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -626,12 +626,12 @@ - Press confirm to erase all content and settings. Press cancel to resume boot. + Resetting device... +This may take up to a minute. - Resetting device... -This may take up to a minute. + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 725e3c2f7b..a903978329 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -626,16 +626,16 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. 데이터 파티션을 마운트할 수 없습니다. 파티션이 손상되었을 수 있습니다. 모든 설정을 삭제하고 장치를 초기화하려면 확인을 누르세요. - - Press confirm to erase all content and settings. Press cancel to resume boot. - 모든 콘텐츠와 설정을 삭제하려면 확인을 누르세요. 계속 부팅하려면 취소를 누르세요. - Resetting device... This may take up to a minute. 장치를 초기화하는 중... 최대 1분이 소요될 수 있습니다. + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 19e35b2c12..d683891e44 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -630,16 +630,16 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. Não é possível montar a partição de dados. Partição corrompida. Confirme para apagar e redefinir o dispositivo. - - Press confirm to erase all content and settings. Press cancel to resume boot. - Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancelar para voltar. - Resetting device... This may take up to a minute. Redefinindo o dispositivo Isso pode levar até um minuto. + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 01d8a93ca1..f4d097b2a2 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -628,14 +628,14 @@ This may take up to a minute. กำลังรีเซ็ตอุปกรณ์... อาจใช้เวลาถึงหนึ่งนาที - - Press confirm to erase all content and settings. Press cancel to resume boot. - กดยืนยันเพื่อลบข้อมูลและการตั้งค่าทั้งหมด กดยกเลิกเพื่อบูตต่อ - Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. ไม่สามารถเมานต์พาร์ติชั่นข้อมูลได้ พาร์ติชั่นอาจเสียหาย กดยืนยันเพื่อลบและรีเซ็ตอุปกรณ์ของคุณ + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index c4e661edfa..ec6f71f5b0 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -627,11 +627,11 @@ This may take up to a minute. - Press confirm to erase all content and settings. Press cancel to resume boot. + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. - Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 68211b4b32..91f55c6e94 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -626,16 +626,16 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. 无法挂载数据分区。分区可能已经损坏。请确认是否要删除并重新设置。 - - Press confirm to erase all content and settings. Press cancel to resume boot. - 按下确认以删除所有内容及设置。按下取消来继续开机。 - Resetting device... This may take up to a minute. 设备重置中… 这可能需要一分钟的时间。 + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 1bdd2d52ea..e8c8854d0e 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -626,16 +626,16 @@ Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. 無法掛載資料分割區。分割區可能已經毀損。請確認是否要刪除並重新設定。 - - Press confirm to erase all content and settings. Press cancel to resume boot. - 按下確認以刪除所有內容及設定。按下取消來繼續開機。 - Resetting device... This may take up to a minute. 設備重設中… 這可能需要一分鐘的時間。 + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + + SettingsWindow From 9ae8830a435816e1a5758c6c61aa1c567c651dfd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 15:28:02 -0800 Subject: [PATCH 582/724] Fingerprints: add missing FW versions from users for Toyota, Honda, Chrysler [bot] (#31156) Export fingerprints --- selfdrive/car/chrysler/fingerprints.py | 15 +++++++++++++++ selfdrive/car/honda/fingerprints.py | 7 +++++++ selfdrive/car/hyundai/fingerprints.py | 4 ++-- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 47be008735..394f9c5132 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -363,7 +363,9 @@ FW_VERSIONS = { b'68294063AH', b'68294063AI', b'68434846AC', + b'68434847AC', b'68434849AC', + b'68434856AC', b'68434858AC', b'68434859AC', b'68434860AC', @@ -380,6 +382,7 @@ FW_VERSIONS = { b'68453514AD', b'68505633AB', b'68510277AG', + b'68510277AH', b'68510280AG', b'68510282AG', b'68510282AH', @@ -388,6 +391,7 @@ FW_VERSIONS = { b'68527361AD', b'68527375AD', b'68527382AE', + b'68527383AD', b'68527387AE', b'68631942AA', ], @@ -452,6 +456,7 @@ FW_VERSIONS = { b'68552790AA', b'68552791AB', b'68585106AB', + b'68585108AB', b'68585109AB', b'68585112AB', ], @@ -463,12 +468,14 @@ FW_VERSIONS = { b'05149591AE ', b'05149592AE ', b'05149600AD ', + b'05149605AE ', b'05149846AA ', b'05149848AA ', b'05190341AD', b'68378695AJ ', b'68378696AJ ', b'68378701AI ', + b'68378710AL ', b'68378748AL ', b'68378758AM ', b'68448163AJ', @@ -476,10 +483,12 @@ FW_VERSIONS = { b'68448163AL', b'68448165AG', b'68448165AK', + b'68455111AC ', b'68455119AC ', b'68455145AC ', b'68455145AE ', b'68455146AC ', + b'68467915AC ', b'68500630AD', b'68500630AE', b'68502719AC ', @@ -490,6 +499,7 @@ FW_VERSIONS = { b'68502742AC ', b'68539650AD', b'68539650AF', + b'68539651AD', b'68586101AA ', b'68586105AB ', b'68629926AC ', @@ -499,7 +509,9 @@ FW_VERSIONS = { b'05036069AA', b'05149536AC', b'05149537AC', + b'05149543AC', b'68360078AL', + b'68360080AL', b'68360080AM', b'68360081AM', b'68360085AJ', @@ -508,7 +520,9 @@ FW_VERSIONS = { b'68384332AD', b'68445531AC', b'68445533AB', + b'68445536AB', b'68445537AB', + b'68466087AB', b'68484466AC', b'68484467AC', b'68484471AC', @@ -516,6 +530,7 @@ FW_VERSIONS = { b'68520867AE', b'68520867AF', b'68540431AB', + b'68540433AB', b'68629936AC', ], }, diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 25fb88aa39..df9a7658f7 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -255,6 +255,7 @@ FW_VERSIONS = { b'37805-5BA-A760\x00\x00', b'37805-5BA-A930\x00\x00', b'37805-5BA-A960\x00\x00', + b'37805-5BA-C640\x00\x00', b'37805-5BA-C860\x00\x00', b'37805-5BA-L410\x00\x00', b'37805-5BA-L760\x00\x00', @@ -298,6 +299,7 @@ FW_VERSIONS = { b'78109-TBA-A510\x00\x00', b'78109-TBA-A520\x00\x00', b'78109-TBA-A530\x00\x00', + b'78109-TBA-C310\x00\x00', b'78109-TBA-C520\x00\x00', b'78109-TBC-A310\x00\x00', b'78109-TBC-A320\x00\x00', @@ -593,6 +595,7 @@ FW_VERSIONS = { b'37805-5PA-AD10\x00\x00', b'37805-5PA-AF20\x00\x00', b'37805-5PA-AH20\x00\x00', + b'37805-5PA-BF10\x00\x00', b'37805-5PA-C680\x00\x00', b'37805-5PD-Q630\x00\x00', b'37805-5PF-F730\x00\x00', @@ -817,6 +820,7 @@ FW_VERSIONS = { ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-5MR-4080\x00\x00', + b'37805-5MR-4180\x00\x00', b'37805-5MR-A240\x00\x00', b'37805-5MR-A250\x00\x00', b'37805-5MR-A310\x00\x00', @@ -855,6 +859,7 @@ FW_VERSIONS = { b'28102-5MX-A001\x00\x00', b'28102-5MX-A600\x00\x00', b'28102-5MX-A610\x00\x00', + b'28102-5MX-A700\x00\x00', b'28102-5MX-A710\x00\x00', b'28102-5MX-A900\x00\x00', b'28102-5MX-A910\x00\x00', @@ -875,6 +880,7 @@ FW_VERSIONS = { b'78109-THR-A420\x00\x00', b'78109-THR-A430\x00\x00', b'78109-THR-A720\x00\x00', + b'78109-THR-A730\x00\x00', b'78109-THR-A820\x00\x00', b'78109-THR-A830\x00\x00', b'78109-THR-AB20\x00\x00', @@ -892,6 +898,7 @@ FW_VERSIONS = { b'78109-THR-AL10\x00\x00', b'78109-THR-AN10\x00\x00', b'78109-THR-C220\x00\x00', + b'78109-THR-C320\x00\x00', b'78109-THR-C330\x00\x00', b'78109-THR-CE20\x00\x00', b'78109-THR-DA20\x00\x00', diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 56b4217403..585a2ec732 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -198,8 +198,8 @@ FW_VERSIONS = { CAR.IONIQ_EV_2020: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7200 ', - b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7000 ', b'\xf1\x00AEev SCC F-CUP 1.00 1.00 99110-G7500 ', + b'\xf1\x00AEev SCC F-CUP 1.00 1.01 99110-G7000 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.01 56310/G7310 4APEC101', @@ -207,10 +207,10 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2600 190730', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2700 201027', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.03 95740-G2500 190516', b'\xf1\x00AEE MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', - b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2700 201027', ], }, CAR.IONIQ_EV_LTD: { From f21ccfbf790b9591043494d1612c5c6e0207f935 Mon Sep 17 00:00:00 2001 From: Alexandre Nobuharu Sato <66435071+AlexandreSato@users.noreply.github.com> Date: Thu, 25 Jan 2024 20:55:58 -0300 Subject: [PATCH 583/724] Multilang: Update pt-BR translations (#31167) * add instructions to my fork * add paypall.png to gitignore * Revert "add instructions to my fork" This reverts commit a6a6f93533add212e321e8472a78a8a69bf932f4. * translate * revert this * why? --------- Co-authored-by: Comma Device --- selfdrive/ui/translations/main_pt-BR.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index d683891e44..fce5a8a8ff 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -638,7 +638,7 @@ Isso pode levar até um minuto. System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - + Reinicialização do sistema acionada. Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancel para retomar a inicialização. From f5732f9c5b266e4e992aa75fb7dc1770759731e9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 16:44:25 -0800 Subject: [PATCH 584/724] Fingerprints: add missing FW versions from users for Toyota, Honda, Chrysler [bot] (#31166) --- selfdrive/car/honda/fingerprints.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index df9a7658f7..ff2320188e 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -27,7 +27,9 @@ FW_VERSIONS = { b'37805-6A0-A930\x00\x00', b'37805-6A0-AF30\x00\x00', b'37805-6A0-AG30\x00\x00', + b'37805-6A0-AJ10\x00\x00', b'37805-6A0-C540\x00\x00', + b'37805-6A0-CG20\x00\x00', b'37805-6A1-H650\x00\x00', b'37805-6B2-A550\x00\x00', b'37805-6B2-A560\x00\x00', @@ -37,6 +39,7 @@ FW_VERSIONS = { b'37805-6B2-A810\x00\x00', b'37805-6B2-A820\x00\x00', b'37805-6B2-A920\x00\x00', + b'37805-6B2-AA10\x00\x00', b'37805-6B2-C520\x00\x00', b'37805-6B2-C540\x00\x00', b'37805-6B2-M520\x00\x00', @@ -123,6 +126,7 @@ FW_VERSIONS = { b'78109-TVA-A230\x00\x00', b'78109-TVA-A310\x00\x00', b'78109-TVA-C010\x00\x00', + b'78109-TVA-C130\x00\x00', b'78109-TVA-L010\x00\x00', b'78109-TVA-L210\x00\x00', b'78109-TVA-R310\x00\x00', @@ -183,6 +187,7 @@ FW_VERSIONS = { b'57114-TWA-A050\x00\x00', b'57114-TWA-A530\x00\x00', b'57114-TWA-B520\x00\x00', + b'57114-TWA-C510\x00\x00', b'57114-TWB-H030\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ @@ -200,6 +205,7 @@ FW_VERSIONS = { b'78109-TWA-A210\x00\x00', b'78109-TWA-A220\x00\x00', b'78109-TWA-A230\x00\x00', + b'78109-TWA-A610\x00\x00', b'78109-TWA-H210\x00\x00', b'78109-TWA-L010\x00\x00', b'78109-TWA-L210\x00\x00', @@ -353,6 +359,7 @@ FW_VERSIONS = { b'37805-5AN-AK20\x00\x00', b'37805-5AN-AR10\x00\x00', b'37805-5AN-AR20\x00\x00', + b'37805-5AN-C650\x00\x00', b'37805-5AN-CH20\x00\x00', b'37805-5AN-E630\x00\x00', b'37805-5AN-E720\x00\x00', @@ -373,6 +380,7 @@ FW_VERSIONS = { b'37805-5AZ-G840\x00\x00', b'37805-5BB-A530\x00\x00', b'37805-5BB-A540\x00\x00', + b'37805-5BB-A620\x00\x00', b'37805-5BB-A630\x00\x00', b'37805-5BB-A640\x00\x00', b'37805-5BB-C540\x00\x00', @@ -422,6 +430,7 @@ FW_VERSIONS = { b'39990-TBA-C120\x00\x00', b'39990-TEA-T820\x00\x00', b'39990-TEZ-T020\x00\x00', + b'39990-TGG,A020\x00\x00', b'39990-TGG-A020\x00\x00', b'39990-TGG-A120\x00\x00', b'39990-TGG-J510\x00\x00', @@ -461,6 +470,7 @@ FW_VERSIONS = { b'78109-TGG-A620\x00\x00', b'78109-TGG-A810\x00\x00', b'78109-TGG-A820\x00\x00', + b'78109-TGG-C010\x00\x00', b'78109-TGG-C220\x00\x00', b'78109-TGG-E110\x00\x00', b'78109-TGG-G030\x00\x00', @@ -479,6 +489,7 @@ FW_VERSIONS = { b'36802-TFJ-G060\x00\x00', b'36802-TGG-A050\x00\x00', b'36802-TGG-A060\x00\x00', + b'36802-TGG-A070\x00\x00', b'36802-TGG-A130\x00\x00', b'36802-TGG-G040\x00\x00', b'36802-TGG-G130\x00\x00', @@ -946,10 +957,13 @@ FW_VERSIONS = { b'28101-5EZ-A500\x00\x00', b'28101-5EZ-A600\x00\x00', b'28101-5EZ-A700\x00\x00', + b'28103-5EY-A110\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-RLV-4060\x00\x00', b'37805-RLV-4070\x00\x00', + b'37805-RLV-5140\x00\x00', + b'37805-RLV-5230\x00\x00', b'37805-RLV-A830\x00\x00', b'37805-RLV-A840\x00\x00', b'37805-RLV-B210\x00\x00', From faf99ba711cb12e33e0e1b6034ec146b65f7a854 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 25 Jan 2024 16:49:38 -0800 Subject: [PATCH 585/724] CI: generate test_ui report (#31151) * add test * simpler * simpler * false * move these here * faster * map takes logner to render --- .github/workflows/selfdrive_tests.yaml | 42 +++++++++++------------ Dockerfile.openpilot_base | 2 +- selfdrive/navd/tests/test_map_renderer.py | 3 ++ selfdrive/test/setup_xvfb.sh | 6 +++- selfdrive/ui/tests/test_ui/run.py | 42 +++++++++++++++-------- tools/install_ubuntu_dependencies.sh | 1 - 6 files changed, 58 insertions(+), 38 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 2daa895de9..3aeebd702b 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -396,24 +396,24 @@ jobs: comment_id: ${{ steps.fc.outputs.comment-id }} }) -# need to figure out some stuff with tkinter before enabling this - - # create_ui_report: - # name: Create UI Report - # runs-on: ubuntu-20.04 - # steps: - # - uses: actions/checkout@v4 - # with: - # submodules: true - # - uses: ./.github/workflows/setup-with-retry - # - name: Build openpilot - # run: ${{ env.RUN }} "scons -j$(nproc)" - # - name: Create Test Report - # run: ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ - # export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ - # python selfdrive/ui/tests/test_ui/run.py" - # - name: Upload Test Report - # uses: actions/upload-artifact@v2 - # with: - # name: report - # path: selfdrive/ui/tests/test_ui \ No newline at end of file + create_ui_report: + name: Create UI Report + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: ./.github/workflows/setup-with-retry + - name: Build openpilot + run: ${{ env.RUN }} "scons -j$(nproc)" + - name: Create Test Report + run: > + ${{ env.RUN }} "PYTHONWARNINGS=ignore && + source selfdrive/test/setup_xvfb.sh && + export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && + python selfdrive/ui/tests/test_ui/run.py" + - name: Upload Test Report + uses: actions/upload-artifact@v2 + with: + name: report + path: selfdrive/ui/tests/test_ui/report \ No newline at end of file diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 5d3d26c981..d280d2c9ec 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -4,7 +4,7 @@ ENV PYTHONUNBUFFERED 1 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && \ - apt-get install -y --no-install-recommends sudo tzdata locales ssh pulseaudio && \ + apt-get install -y --no-install-recommends sudo tzdata locales ssh pulseaudio xvfb x11-xserver-utils gnome-screenshot && \ rm -rf /var/lib/apt/lists/* RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen diff --git a/selfdrive/navd/tests/test_map_renderer.py b/selfdrive/navd/tests/test_map_renderer.py index d056198bec..a7289d02d9 100755 --- a/selfdrive/navd/tests/test_map_renderer.py +++ b/selfdrive/navd/tests/test_map_renderer.py @@ -28,8 +28,11 @@ LOCATION2_REPEATED = [LOCATION2] * DEFAULT_ITERATIONS def gen_llk(location=LOCATION1): msg = messaging.new_message('liveLocationKalman') msg.liveLocationKalman.positionGeodetic = {'value': [*location, 0], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.positionECEF = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} msg.liveLocationKalman.calibratedOrientationNED = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.velocityCalibrated = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} msg.liveLocationKalman.status = 'valid' + msg.liveLocationKalman.gpsOK = True return msg diff --git a/selfdrive/test/setup_xvfb.sh b/selfdrive/test/setup_xvfb.sh index 806515b0f9..692b84d65f 100755 --- a/selfdrive/test/setup_xvfb.sh +++ b/selfdrive/test/setup_xvfb.sh @@ -12,4 +12,8 @@ while [ ! -S /tmp/.X11-unix/X$DISP_ID ] do echo "Waiting for Xvfb..." sleep 1 -done \ No newline at end of file +done + +touch ~/.Xauthority +export XDG_SESSION_TYPE="x11" +xset -q \ No newline at end of file diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index 48223d3fe8..1fea759efb 100644 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -1,10 +1,11 @@ +from collections import namedtuple import pathlib import shutil +import sys import jinja2 import matplotlib.pyplot as plt import numpy as np import os -import pyautogui import pywinctl import time import unittest @@ -17,6 +18,7 @@ from cereal.messaging import SubMaster, PubMaster from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.common.transformations.camera import tici_f_frame_size +from openpilot.selfdrive.navd.tests.test_map_renderer import gen_llk from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state from openpilot.tools.webcam.camera import Camera @@ -44,8 +46,6 @@ def setup_common(click, pm: PubMaster): pm.send("deviceState", dat) - time.sleep(UI_DELAY) - def setup_homescreen(click, pm: PubMaster): setup_common(click, pm) @@ -53,14 +53,12 @@ def setup_settings_device(click, pm: PubMaster): setup_common(click, pm) click(100, 100) - time.sleep(UI_DELAY) def setup_settings_network(click, pm: PubMaster): setup_common(click, pm) setup_settings_device(click, pm) click(300, 600) - time.sleep(UI_DELAY) def setup_onroad(click, pm: PubMaster): setup_common(click, pm) @@ -77,7 +75,7 @@ def setup_onroad(click, pm: PubMaster): server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *tici_f_frame_size) server.start_listener() - time.sleep(UI_DELAY) + time.sleep(0.5) # give time for vipc server to start IMG = Camera.bgr2nv12(np.random.randint(0, 255, (*tici_f_frame_size,3), dtype=np.uint8)) IMG_BYTES = IMG.flatten().tobytes() @@ -96,17 +94,19 @@ def setup_onroad(click, pm: PubMaster): pm.send(msg.which(), msg) server.send(cam_meta.stream, IMG_BYTES, cs.frameId, cs.timestampSof, cs.timestampEof) - time.sleep(UI_DELAY) - def setup_onroad_map(click, pm: PubMaster): setup_onroad(click, pm) + + dat = gen_llk() + pm.send("liveLocationKalman", dat) + click(500, 500) - time.sleep(UI_DELAY) + + time.sleep(UI_DELAY) # give time for the map to render def setup_onroad_sidebar(click, pm: PubMaster): setup_onroad_map(click, pm) click(500, 500) - time.sleep(UI_DELAY) CASES = { "homescreen": setup_homescreen, @@ -114,7 +114,7 @@ CASES = { "settings_network": setup_settings_network, "onroad": setup_onroad, "onroad_map": setup_onroad_map, - "onroad_map_sidebar": setup_onroad_sidebar + "onroad_sidebar": setup_onroad_sidebar } TEST_DIR = pathlib.Path(__file__).parent @@ -127,16 +127,26 @@ class TestUI(unittest.TestCase): @classmethod def setUpClass(cls): os.environ["SCALE"] = "1" + sys.modules["mouseinfo"] = False + + @classmethod + def tearDownClass(cls): + del sys.modules["mouseinfo"] def setup(self): self.sm = SubMaster(["uiDebug"]) - self.pm = PubMaster(["deviceState", "pandaStates", "controlsState", 'roadCameraState', 'wideRoadCameraState']) + self.pm = PubMaster(["deviceState", "pandaStates", "controlsState", 'roadCameraState', 'wideRoadCameraState', 'liveLocationKalman']) while not self.sm.valid["uiDebug"]: self.sm.update(1) - time.sleep(UI_DELAY) # wait a bit more for the UI to finish rendering - self.ui = pywinctl.getWindowsWithTitle("ui")[0] + time.sleep(UI_DELAY) # wait a bit more for the UI to start rendering + try: + self.ui = pywinctl.getWindowsWithTitle("ui")[0] + except Exception as e: + print(f"failed to find ui window, assuming that it's in the top left (for Xvfb) {e}") + self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0,0,2160,1080) def screenshot(self): + import pyautogui im = pyautogui.screenshot(region=(self.ui.left, self.ui.top, self.ui.width, self.ui.height)) self.assertEqual(im.width, 2160) self.assertEqual(im.height, 1080) @@ -145,7 +155,9 @@ class TestUI(unittest.TestCase): return img def click(self, x, y, *args, **kwargs): + import pyautogui pyautogui.click(self.ui.left + x, self.ui.top + y, *args, **kwargs) + time.sleep(UI_DELAY) # give enough time for the UI to react @parameterized.expand(CASES.items()) @with_processes(["ui"]) @@ -154,6 +166,8 @@ class TestUI(unittest.TestCase): setup_case(self.click, self.pm) + time.sleep(UI_DELAY) # wait a bit more for the UI to finish rendering + im = self.screenshot() plt.imsave(SCREENSHOTS_DIR / f"{name}.png", im) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index e1bd30aa39..78a01f2b75 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -77,7 +77,6 @@ function install_ubuntu_common_requirements() { libqt5x11extras5-dev \ libreadline-dev \ libdw1 \ - xvfb \ valgrind } From 979dba33cda2c54686e0e07f439ce06aa2598b1f Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 25 Jan 2024 17:11:03 -0800 Subject: [PATCH 586/724] Revert "test_loggerd: make rotation test more robust" (#31168) --- system/loggerd/tests/test_loggerd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 0f2fc918b7..dde12b646d 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -152,8 +152,8 @@ class TestLoggerd: vipc_server.create_buffers_with_sizes(stream_type, 40, False, *(frame_spec)) vipc_server.start_listener() - num_segs = 3 - length = 2 + num_segs = random.randint(2, 5) + length = random.randint(1, 3) os.environ["LOGGERD_SEGMENT_LENGTH"] = str(length) managed_processes["loggerd"].start() managed_processes["encoderd"].start() From 076810d5bd62f357e594212be665d0e2c06a739f Mon Sep 17 00:00:00 2001 From: Comma Device Date: Thu, 25 Jan 2024 17:13:35 -0800 Subject: [PATCH 587/724] allow a couple retries --- system/camerad/test/test_camerad.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index ad3a9fdc91..c6dc3be99c 100755 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 import pytest import time -import unittest import numpy as np +from flaky import flaky from collections import defaultdict import cereal.messaging as messaging @@ -18,11 +18,12 @@ FRAME_DELTA_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 1.0, CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') - +# TODO: this shouldn't be needed +@flaky(max_runs=3) @pytest.mark.tici -class TestCamerad(unittest.TestCase): +class TestCamerad: @classmethod - def setUpClass(cls): + def setup_class(cls): # run camerad and record logs managed_processes['camerad'].start() time.sleep(3) @@ -85,6 +86,3 @@ class TestCamerad(unittest.TestCase): print("TODO: handle camera out of sync") else: assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" - -if __name__ == "__main__": - unittest.main() From fe4abaa6f693b000056d2ce8c2492fb64c3e313d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 25 Jan 2024 17:31:51 -0800 Subject: [PATCH 588/724] add flaky package --- poetry.lock | 45 +++++++++++++++++------------ pyproject.toml | 2 +- system/camerad/test/test_camerad.py | 31 +++++++++----------- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/poetry.lock b/poetry.lock index 694486a75e..00e8784118 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -1098,6 +1098,17 @@ calc = ["shapely"] s3 = ["boto3 (>=1.3.1)"] test = ["Fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] +[[package]] +name = "flaky" +version = "3.7.0" +description = "Plugin for nose or pytest that automatically reruns flaky tests." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "flaky-3.7.0-py2.py3-none-any.whl", hash = "sha256:d6eda73cab5ae7364504b7c44670f70abed9e75f77dd116352f662817592ec9c"}, + {file = "flaky-3.7.0.tar.gz", hash = "sha256:3ad100780721a1911f57a165809b7ea265a7863305acb66708220820caf8aa0d"}, +] + [[package]] name = "flatbuffers" version = "23.5.26" @@ -2653,14 +2664,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "opencv-python-headless" @@ -2679,14 +2683,7 @@ files = [ ] [package.dependencies] -numpy = [ - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, - {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\""}, - {version = ">=1.23.5", markers = "python_version >= \"3.11\""}, - {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\" or python_version >= \"3.9\""}, - {version = ">=1.17.0", markers = "python_version >= \"3.7\""}, - {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, -] +numpy = {version = ">=1.23.5", markers = "python_version >= \"3.11\""} [[package]] name = "packaging" @@ -3307,6 +3304,8 @@ files = [ {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"}, + {file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, @@ -6706,6 +6705,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -6713,8 +6713,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -6731,6 +6738,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -6738,6 +6746,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -7787,4 +7796,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "4f771435666b64f677d7dd2b4c9b4ec4bbe6fcbb004d13f19e0e79ae138c92b9" +content-hash = "31a962516d63b49e14cc615807e067ec833f76d5aa1157a4dcc9408f23c28ad1" diff --git a/pyproject.toml b/pyproject.toml index 0d9772f695..647f3df6b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,6 @@ markdown-it-py = "*" timezonefinder = "*" setproctitle = "*" - [tool.poetry.group.dev.dependencies] av = "*" azure-identity = "*" @@ -121,6 +120,7 @@ control = "*" coverage = "*" dictdiffer = "*" ft4222 = "*" +flaky = "*" hypothesis = "~6.47" inputs = "*" Jinja2 = "*" diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index c6dc3be99c..408115607e 100755 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -22,44 +22,39 @@ CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') @flaky(max_runs=3) @pytest.mark.tici class TestCamerad: - @classmethod - def setup_class(cls): + def setup_method(self): # run camerad and record logs managed_processes['camerad'].start() time.sleep(3) socks = {c: messaging.sub_sock(c, conflate=False, timeout=100) for c in CAMERAS} - cls.logs = defaultdict(list) + self.logs = defaultdict(list) start_time = time.monotonic() while time.monotonic()- start_time < TEST_TIMESPAN: for cam, s in socks.items(): - cls.logs[cam] += messaging.drain_sock(s) + self.logs[cam] += messaging.drain_sock(s) time.sleep(0.2) managed_processes['camerad'].stop() - cls.log_by_frame_id = defaultdict(list) - cls.sensor_type = None - for cam, msgs in cls.logs.items(): - if cls.sensor_type is None: - cls.sensor_type = getattr(msgs[0], msgs[0].which()).sensor.raw + self.log_by_frame_id = defaultdict(list) + self.sensor_type = None + for cam, msgs in self.logs.items(): + if self.sensor_type is None: + self.sensor_type = getattr(msgs[0], msgs[0].which()).sensor.raw expected_frames = SERVICE_LIST[cam].frequency * TEST_TIMESPAN assert expected_frames*0.95 < len(msgs) < expected_frames*1.05, f"unexpected frame count {cam}: {expected_frames=}, got {len(msgs)}" dts = np.abs(np.diff([getattr(m, m.which()).timestampSof/1e6 for m in msgs]) - 1000/SERVICE_LIST[cam].frequency) - assert (dts < FRAME_DELTA_TOLERANCE[cls.sensor_type]).all(), f"{cam} dts(ms) out of spec: max diff {dts.max()}, 99 percentile {np.percentile(dts, 99)}" + assert (dts < FRAME_DELTA_TOLERANCE[self.sensor_type]).all(), f"{cam} dts(ms) out of spec: max diff {dts.max()}, 99 percentile {np.percentile(dts, 99)}" for m in msgs: - cls.log_by_frame_id[getattr(m, m.which()).frameId].append(m) + self.log_by_frame_id[getattr(m, m.which()).frameId].append(m) # strip beginning and end for _ in range(3): - mn, mx = min(cls.log_by_frame_id.keys()), max(cls.log_by_frame_id.keys()) - del cls.log_by_frame_id[mn] - del cls.log_by_frame_id[mx] - - @classmethod - def tearDownClass(cls): - managed_processes['camerad'].stop() + mn, mx = min(self.log_by_frame_id.keys()), max(self.log_by_frame_id.keys()) + del self.log_by_frame_id[mn] + del self.log_by_frame_id[mx] def test_frame_skips(self): skips = {} From 289868297eb2646acb8c62cec27ce77027d09ac7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 18:01:49 -0800 Subject: [PATCH 589/724] VIN: query on bus 0 to log data (#31165) * query on bus 0 * fix * log vin_rx_bus * fix! --- selfdrive/car/car_helpers.py | 14 +++---- selfdrive/car/fw_versions.py | 4 +- selfdrive/car/tests/test_fw_fingerprint.py | 4 +- selfdrive/car/vin.py | 43 +++++++++++----------- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index c1a30094c4..8c3552a403 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -127,9 +127,6 @@ def fingerprint(logcan, sendcan, num_pandas): start_time = time.monotonic() if not skip_fw_query: - # Vin query only reliably works through OBDII - bus = 1 - cached_params = params.get("CarParamsCache") if cached_params is not None: with car.CarParams.from_bytes(cached_params) as cached_params: @@ -139,20 +136,21 @@ def fingerprint(logcan, sendcan, num_pandas): if cached_params is not None and len(cached_params.carFw) > 0 and \ cached_params.carVin is not VIN_UNKNOWN and not disable_fw_cache: cloudlog.warning("Using cached CarParams") - vin, vin_rx_addr = cached_params.carVin, 0 + vin_rx_addr, vin_rx_bus, vin = -1, -1, cached_params.carVin car_fw = list(cached_params.carFw) cached = True else: cloudlog.warning("Getting VIN & FW versions") set_obd_multiplexing(params, True) - vin_rx_addr, vin = get_vin(logcan, sendcan, bus) + # Vin query only reliably works through OBDII + vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (1, 0)) ecu_rx_addrs = get_present_ecus(logcan, sendcan, num_pandas=num_pandas) car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas) cached = False exact_fw_match, fw_candidates = match_fw_to_car(car_fw) else: - vin, vin_rx_addr = VIN_UNKNOWN, 0 + vin_rx_addr, vin_rx_bus, vin = -1, -1, VIN_UNKNOWN exact_fw_match, fw_candidates, car_fw = True, set(), [] cached = False @@ -187,8 +185,8 @@ def fingerprint(logcan, sendcan, num_pandas): source = car.CarParams.FingerprintSource.fixed cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, cached=cached, - fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, fingerprints=finger, - fw_query_time=fw_query_time, error=True) + fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, vin_rx_bus=vin_rx_bus, + fingerprints=finger, fw_query_time=fw_query_time, error=True) return car_fingerprint, finger, vin, car_fw, source, exact_match diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 547904ee47..085131118a 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -370,8 +370,8 @@ if __name__ == "__main__": t = time.time() print("Getting vin...") - vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) - print(f'RX: {hex(vin_rx_addr)}, VIN: {vin}') + vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (1, 0), retry=10, debug=args.debug) + print(f'RX: {hex(vin_rx_addr)}, BUS: {vin_rx_bus}, VIN: {vin}') print(f"Getting VIN took {time.time() - t:.3f} s") print() diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 893c79c797..98546b4a22 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -212,7 +212,7 @@ class TestFwFingerprintTiming(unittest.TestCase): def test_startup_timing(self): # Tests worse-case VIN query time and typical present ECU query time - vin_ref_time = 1.0 + vin_ref_time = 2.0 present_ecu_ref_time = 0.75 def fake_get_ecu_addrs(*_, timeout): @@ -232,7 +232,7 @@ class TestFwFingerprintTiming(unittest.TestCase): self.total_time = 0.0 with (mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)): for _ in range(self.N): - get_vin(fake_socket, fake_socket, 1) + get_vin(fake_socket, fake_socket, (0, 1)) self._assert_timing(self.total_time / self.N, vin_ref_time) print(f'get_vin, query time={self.total_time / self.N} seconds') diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index e2709cc842..18bfcb5174 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -15,33 +15,34 @@ def is_valid_vin(vin: str): return re.fullmatch(VIN_RE, vin) is not None -def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False): +def get_vin(logcan, sendcan, buses, timeout=0.1, retry=5, debug=False): addrs = list(range(0x7e0, 0x7e8)) + list(range(0x18DA00F1, 0x18DB00F1, 0x100)) # addrs to process/wait for valid_vin_addrs = [0x7e0, 0x7e2, 0x18da10f1, 0x18da0ef1] # engine, VMCU, 29-bit engine, PGM-FI for i in range(retry): - for request, response in ((StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE), (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE)): - try: - query = IsoTpParallelQuery(sendcan, logcan, bus, addrs, [request, ], [response, ], functional_addrs=FUNCTIONAL_ADDRS, debug=debug) - results = query.get_data(timeout) + for bus in buses: + for request, response in ((StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE), (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE)): + try: + query = IsoTpParallelQuery(sendcan, logcan, bus, addrs, [request, ], [response, ], functional_addrs=FUNCTIONAL_ADDRS, debug=debug) + results = query.get_data(timeout) - for addr in valid_vin_addrs: - vin = results.get((addr, None)) - if vin is not None: - # Ford pads with null bytes - if len(vin) == 24: - vin = re.sub(b'\x00*$', b'', vin) + for addr in valid_vin_addrs: + vin = results.get((addr, None)) + if vin is not None: + # Ford pads with null bytes + if len(vin) == 24: + vin = re.sub(b'\x00*$', b'', vin) - # Honda Bosch response starts with a length, trim to correct length - if vin.startswith(b'\x11'): - vin = vin[1:18] + # Honda Bosch response starts with a length, trim to correct length + if vin.startswith(b'\x11'): + vin = vin[1:18] - return get_rx_addr_for_tx_addr(addr), vin.decode() + return get_rx_addr_for_tx_addr(addr), bus, vin.decode() - cloudlog.error(f"vin query retry ({i+1}) ...") - except Exception: - cloudlog.exception("VIN query exception") + cloudlog.error(f"vin query retry ({i+1}) ...") + except Exception: + cloudlog.exception("VIN query exception") - return 0, VIN_UNKNOWN + return -1, -1, VIN_UNKNOWN if __name__ == "__main__": @@ -59,5 +60,5 @@ if __name__ == "__main__": logcan = messaging.sub_sock('can') time.sleep(1) - vin_rx_addr, vin = get_vin(logcan, sendcan, args.bus, args.timeout, args.retry, debug=args.debug) - print(f'RX: {hex(vin_rx_addr)}, VIN: {vin}') + vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (args.bus,), args.timeout, args.retry, debug=args.debug) + print(f'RX: {hex(vin_rx_addr)}, BUS: {vin_rx_bus}, VIN: {vin}') From 1612ec6d93deecf888b5739569a95be7e7c68bb1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 19:09:53 -0800 Subject: [PATCH 590/724] vin: try bus 0 first --- selfdrive/car/car_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 8c3552a403..167366cf6a 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -143,7 +143,7 @@ def fingerprint(logcan, sendcan, num_pandas): cloudlog.warning("Getting VIN & FW versions") set_obd_multiplexing(params, True) # Vin query only reliably works through OBDII - vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (1, 0)) + vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1)) ecu_rx_addrs = get_present_ecus(logcan, sendcan, num_pandas=num_pandas) car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas) cached = False From 6262de44190ffd02bf6dc7db418599885db800f3 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Thu, 25 Jan 2024 20:37:25 -0800 Subject: [PATCH 591/724] CI: use auto-cache for test_models (#31171) use auto cache --- .github/workflows/selfdrive_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 3aeebd702b..964c36776e 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -321,7 +321,7 @@ jobs: - uses: ./.github/workflows/setup-with-retry - name: Cache test routes id: dependency-cache - uses: actions/cache@v3 + uses: ./.github/workflows/auto-cache with: path: .ci_cache/comma_download_cache key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }} From ca2ed59a643c5fe0e0530e60c06b064fadbccbad Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 22:27:18 -0800 Subject: [PATCH 592/724] add comment about sendcan --- selfdrive/car/car_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 167366cf6a..47290c89ee 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -141,6 +141,7 @@ def fingerprint(logcan, sendcan, num_pandas): cached = True else: cloudlog.warning("Getting VIN & FW versions") + # enable OBD multiplexing for Vin query, also allows time for sendcan subscriber to connect set_obd_multiplexing(params, True) # Vin query only reliably works through OBDII vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1)) From c99cd3251b6a283be43fd301273baf78553c9c43 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 22:40:39 -0800 Subject: [PATCH 593/724] vin: reduce retries (#31175) reduce retries --- selfdrive/car/tests/test_fw_fingerprint.py | 2 +- selfdrive/car/vin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 98546b4a22..e1967e9ec8 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -212,7 +212,7 @@ class TestFwFingerprintTiming(unittest.TestCase): def test_startup_timing(self): # Tests worse-case VIN query time and typical present ECU query time - vin_ref_time = 2.0 + vin_ref_time = 1.2 present_ecu_ref_time = 0.75 def fake_get_ecu_addrs(*_, timeout): diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 18bfcb5174..ba081764b5 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -15,7 +15,7 @@ def is_valid_vin(vin: str): return re.fullmatch(VIN_RE, vin) is not None -def get_vin(logcan, sendcan, buses, timeout=0.1, retry=5, debug=False): +def get_vin(logcan, sendcan, buses, timeout=0.1, retry=3, debug=False): addrs = list(range(0x7e0, 0x7e8)) + list(range(0x18DA00F1, 0x18DB00F1, 0x100)) # addrs to process/wait for valid_vin_addrs = [0x7e0, 0x7e2, 0x18da10f1, 0x18da0ef1] # engine, VMCU, 29-bit engine, PGM-FI for i in range(retry): From d1a8ec0cadfe298e614809c0c791758cccea68dc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 23:13:17 -0800 Subject: [PATCH 594/724] Fingerprints: add missing FW versions from users for Toyota, Honda, Chrysler [bot] (#31173) Export fingerprints --- selfdrive/car/honda/fingerprints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index ff2320188e..0f217afb3c 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -431,6 +431,7 @@ FW_VERSIONS = { b'39990-TEA-T820\x00\x00', b'39990-TEZ-T020\x00\x00', b'39990-TGG,A020\x00\x00', + b'39990-TGG,A120\x00\x00', b'39990-TGG-A020\x00\x00', b'39990-TGG-A120\x00\x00', b'39990-TGG-J510\x00\x00', From 52d7cfa68952ab30ac7577b4cdeb20c75e2c8745 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 25 Jan 2024 23:58:27 -0800 Subject: [PATCH 595/724] Honda: add logging request for radarless Civic 2022+ camera (#31176) * try tester present * radar * rm whitelist * refs --- selfdrive/car/honda/values.py | 7 +++++++ selfdrive/car/tests/test_fw_fingerprint.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index a10080ec0e..fefce7ff9c 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -169,6 +169,13 @@ FW_QUERY_CONFIG = FwQueryConfig( ), # Data collection requests: + # Attempt to get the radarless Civic 2022+ camera FW + Request( + [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], + bus=0, + logging=True + ), # Log extra identifiers for current ECUs Request( [HONDA_VERSION_REQUEST], diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index e1967e9ec8..d640b32ac6 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -244,7 +244,7 @@ class TestFwFingerprintTiming(unittest.TestCase): 'body': 0.1, 'chrysler': 0.3, 'ford': 0.2, - 'honda': 0.45, + 'honda': 0.55, 'hyundai': 0.65, 'mazda': 0.2, 'nissan': 0.8, From 14e83ac9cc0f08126bca4553e403dcfa6fc3bb1e Mon Sep 17 00:00:00 2001 From: ugtthis <142481257+ugtthis@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:16:54 -0800 Subject: [PATCH 596/724] UI/sidebar_layout: Removed redundant piece of margin code (#31106) --- selfdrive/ui/qt/offroad/settings.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 6d79aeb23d..85393cabc0 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -345,7 +345,6 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { // setup two main layouts sidebar_widget = new QWidget; QVBoxLayout *sidebar_layout = new QVBoxLayout(sidebar_widget); - sidebar_layout->setMargin(0); panel_widget = new QStackedWidget(); // close button From 9518b66c9e49f744591f8da858b201342a8c096d Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Fri, 26 Jan 2024 13:20:25 -0800 Subject: [PATCH 597/724] commaCarSegments cleanup (#31182) * cleanup * no space --- tools/lib/comma_car_segments.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/lib/comma_car_segments.py b/tools/lib/comma_car_segments.py index 995b26d8e6..9027fec637 100644 --- a/tools/lib/comma_car_segments.py +++ b/tools/lib/comma_car_segments.py @@ -4,14 +4,13 @@ import requests # Forks with additional car support can fork the commaCarSegments repo on huggingface or host the LFS files themselves COMMA_CAR_SEGMENTS_REPO = os.environ.get("COMMA_CAR_SEGMENTS_REPO", "https://huggingface.co/datasets/commaai/commaCarSegments") COMMA_CAR_SEGMENTS_BRANCH = os.environ.get("COMMA_CAR_SEGMENTS_BRANCH", "main") - -COMMA_CAR_SEGMENTS_LFS_INSTANCE = os.environ.get("COMMA_CAR_SEGMENTS_LFS_INSTANCE", "https://huggingface.co/datasets/commaai/commaCarSegments") +COMMA_CAR_SEGMENTS_LFS_INSTANCE = os.environ.get("COMMA_CAR_SEGMENTS_LFS_INSTANCE", COMMA_CAR_SEGMENTS_REPO) def get_comma_car_segments_database(): return requests.get(get_repo_raw_url("database.json")).json() -# Helpers related to interfacing with the openpilot-data repository, which contains a collection of public segments for users to perform validation on. +# Helpers related to interfacing with the commaCarSegments repository, which contains a collection of public segments for users to perform validation on. def parse_lfs_pointer(text): header, lfs_version = text.splitlines()[0].split(" ") From 97488c190ed2dc406e3ba9995516309f83535ecc Mon Sep 17 00:00:00 2001 From: Alexandre Nobuharu Sato <66435071+AlexandreSato@users.noreply.github.com> Date: Fri, 26 Jan 2024 18:20:35 -0300 Subject: [PATCH 598/724] Fingerprint vw taos eduardo (#31113) * add instructions to my fork * add braziliam TAOS fingerprint * Revert "add instructions to my fork" This reverts commit 46fbee3ebe7ecbb211d855e63b2d4c8919c31b63. --- selfdrive/car/volkswagen/fingerprints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/volkswagen/fingerprints.py b/selfdrive/car/volkswagen/fingerprints.py index f98a822f1e..dcdb2e62bf 100644 --- a/selfdrive/car/volkswagen/fingerprints.py +++ b/selfdrive/car/volkswagen/fingerprints.py @@ -484,6 +484,7 @@ FW_VERSIONS = { }, CAR.TAOS_MK1: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704E906025CK\xf1\x892228', b'\xf1\x8704E906027NJ\xf1\x891445', b'\xf1\x8704E906027NP\xf1\x891286', b'\xf1\x8705E906013BD\xf1\x892496', @@ -492,6 +493,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158EM\xf1\x893812', b'\xf1\x8709S927158BL\xf1\x893791', + b'\xf1\x8709S927158CR\xf1\x893924', b'\xf1\x8709S927158DN\xf1\x893946', b'\xf1\x8709S927158FF\xf1\x893876', ], From 1001becfa59fb7208ecd69e98a3f60f5efa37a1d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 26 Jan 2024 14:33:59 -0800 Subject: [PATCH 599/724] unpin sentry-sdk (#31183) * unpin sentry-sdk * update --- poetry.lock | 385 ++++++++++++++++++++++---------------------- pyproject.toml | 2 +- selfdrive/sentry.py | 3 +- 3 files changed, 194 insertions(+), 196 deletions(-) diff --git a/poetry.lock b/poetry.lock index 00e8784118..6d19a563d8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -112,45 +112,45 @@ ifaddr = ">=0.2.0" [[package]] name = "aiortc" -version = "1.6.0" +version = "1.7.0" description = "An implementation of WebRTC and ORTC" optional = false python-versions = ">=3.8" files = [ - {file = "aiortc-1.6.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4675e04d8441797fef6c8a669b3a67d750670d1b897f08886072a084d743e07d"}, - {file = "aiortc-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:836c0686fca67f142c52e5af8043206c2bb702ad0ddcdc94ef19caf1c22f8d54"}, - {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33f846abd753935881158751994a51f14e345762130688b19c26cab42c01266f"}, - {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4bd45397945a5323bd077d43c702a3a991d75023f23649c1d18df5d80c221d"}, - {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f93561b515ff478b068bb9b047d8b1e896c747dcf3ee465463047c51a7bea24d"}, - {file = "aiortc-1.6.0-cp38-abi3-win32.whl", hash = "sha256:325f847397af2892aa051dc2880a75e9bd79f535cc05ec8f4538b5ed098b3c5d"}, - {file = "aiortc-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:98b118d53ae874126b2e9ec6bb1397ea169b85550c4bd5453e279507ff7f0cf9"}, - {file = "aiortc-1.6.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6677018833a5d648754c99c70e6c6f6d4f3942682cda07ed5afa73422f8a009a"}, - {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:090936c719225e50cd4d66f476e6c17293a8062cf7687a1baa5080f3c90ec8b1"}, - {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e60d5bc269d4d12f1f5f47e2c17aa3799f3b5c8b73fd6d8d246ddc11bb29776"}, - {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b9496b4a3c8cd9d32afb6d5bce07f9170831ec44a20ab8af54d53879aafc8"}, - {file = "aiortc-1.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0a41f4c0b31e45548e7c7397ef1aecc4be49ab68afd8fa134c07581fe0b3a9c2"}, - {file = "aiortc-1.6.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:64a9939016edbc8f300de6189983c983753827813ac9acd9b5be8ce61cc32684"}, - {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:813e7985665c94a0e3387b66e39dba6c751e5e588aedbca06d7e52068c6e37fb"}, - {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:672d0b4ad35c4d8f014f44a142aa55529ec82cfe2809226e1275e35a71fd4422"}, - {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1526a1904174bb11958b8f7e93f01f37f80df2190e5089f0501984bdef79595e"}, - {file = "aiortc-1.6.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cecfa5f462e73218cadc9acd8013cb3a0d9007a4515bceba6e7755d77bb80061"}, - {file = "aiortc-1.6.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:febca6773de18a6bb9e5569ae87c8be55ed184695f1f9fc99aa4744a7b0375f8"}, - {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5ba865be9708713397ec584ed1baeb2f15d2fa9c32594ce19a41ffa6e2517cb"}, - {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9759a3c00e46ba1c3499dbf5a8513ae37ba65b940a56b0e7fa5070478e9379f"}, - {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cff7868663d9d1c74e237b86e45126022466240439a5f63c3440e3acdf0305b"}, - {file = "aiortc-1.6.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9f20479d0dd06116ac81d332850fab874d83b561a73fd7252d218f55c6bd5b79"}, - {file = "aiortc-1.6.0.tar.gz", hash = "sha256:08cffbcde3401b33731b1d4169b9ff860b0aaaca200b62e10ce5978238671ad7"}, + {file = "aiortc-1.7.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aba47eac61ee7fb7e89876f40e4132aa66a13ed2a730dff003342e57219f34c0"}, + {file = "aiortc-1.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:169abaaa0c11a1695942b3eeea9d9032ea4992c6e84958c1b31c6ba22fcf4b0e"}, + {file = "aiortc-1.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5323a347d02d53989e61944eead19e550d930afbb872dd0fb51b3d065aaa833"}, + {file = "aiortc-1.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71c18762ebfeb239352705672e598c57f0e56e5c1b7955dba27651c801c56ea2"}, + {file = "aiortc-1.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:817526c2e429a1ef1226ca9cdb4ff3c5d03857eb31de0f5a00433dc4cb5569f3"}, + {file = "aiortc-1.7.0-cp38-abi3-win32.whl", hash = "sha256:a63c4da5c4a9d96ef6e3948c1f4675e02b0b908605eff4cea8b5e2fa5a34da4e"}, + {file = "aiortc-1.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:e60f19f810a552690bf6e969429c624df39af2b5079ee0d95fb75d110b978e20"}, + {file = "aiortc-1.7.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7407d7065cbc649adf866927c007d84f94eeb3fcaa65f44eb94def8c2c5bbca"}, + {file = "aiortc-1.7.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35e1dce2697762841dd3cc5a6e23ef8a0d96207e3fd33b834b5a8686748f6143"}, + {file = "aiortc-1.7.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f77cc2d757d7c3c37c6157cce36fe13fc161c5cb5aea62759c8b0d3e6d7f45f9"}, + {file = "aiortc-1.7.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80465daa66f89e4fd22b6afd3a6ae71ffd506343cf30025dbc36eb5453f95330"}, + {file = "aiortc-1.7.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8fcf352df6fabad32fd337dc51b629060de80d06987a8544c3c842ecc04254f8"}, + {file = "aiortc-1.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b1427673e3ef8889dbd1c4f05d3d2aa7895cbfdc985532d54892ad6f96fc08c"}, + {file = "aiortc-1.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec2d018bd01c7532188be5842e11de252a1346156880ff3387d4f879c9f163d2"}, + {file = "aiortc-1.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f21e0024c0c7b07fec87e1ffcc30b6dbd57d1b822324d9c0128731388a82f08"}, + {file = "aiortc-1.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd267c338bd6578b46fb8f664418f9a48ad5d1582895eb029b4c5087e105fc89"}, + {file = "aiortc-1.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:919b6edbc1e462cb00e313d44368798066c3ebe81525ec6fb6008e0cad572c97"}, + {file = "aiortc-1.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:573314501d4aa2ef5e8826abe7e675742c92d25908f5f6b48ea2f5fababdfb4b"}, + {file = "aiortc-1.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:791f525577033572ee937853159cff2c63121d1e30d9c93df4ef3a4c94eec5ec"}, + {file = "aiortc-1.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52e945b2ba536e4e78c106e6c923f73b1838bf0c35592d729ea9b3ba6791b108"}, + {file = "aiortc-1.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89a8aef4067c68ccbc97872d506b9b80d5ed39e0cc41a46d641b66c518e00240"}, + {file = "aiortc-1.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0b7248716706c52e3bc1f21a89a1a9ca205e95a74c0b4aa4b2863d2162fd2552"}, + {file = "aiortc-1.7.0.tar.gz", hash = "sha256:4fd900797b419a9189443b7c95a2ce4bf5aa0d9542d8d19edfabf30aa5fbc296"}, ] [package.dependencies] aioice = ">=0.9.0,<1.0.0" av = ">=9.0.0,<12.0.0" cffi = ">=1.0.0" -cryptography = ">=2.2" +cryptography = ">=42.0.0" google-crc32c = ">=1.1" pyee = ">=9.0.0" -pylibsrtp = ">=0.5.6" -pyopenssl = ">=23.1.0" +pylibsrtp = ">=0.10.0" +pyopenssl = ">=24.0.0" [package.extras] dev = ["aiohttp (>=3.7.0)", "coverage[toml] (>=7.2.2)", "numpy (>=1.19.0)"] @@ -751,63 +751,63 @@ test = ["pytest", "pytest-timeout"] [[package]] name = "coverage" -version = "7.4.0" +version = "7.4.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"}, - {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"}, - {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"}, - {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"}, - {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"}, - {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"}, - {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"}, - {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"}, - {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"}, - {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"}, - {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"}, - {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"}, - {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"}, - {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"}, - {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"}, - {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"}, - {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"}, - {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"}, - {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"}, - {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"}, - {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"}, - {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"}, - {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"}, - {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"}, - {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"}, - {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"}, - {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"}, - {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"}, - {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"}, - {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"}, - {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"}, - {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, + {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, + {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, + {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, + {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, + {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, + {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, + {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, + {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, + {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, + {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, + {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, + {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, + {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, + {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, + {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, + {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, + {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, + {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, + {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, + {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, + {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, + {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, + {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, + {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, + {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, + {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, + {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, ] [package.extras] @@ -825,47 +825,56 @@ files = [ [[package]] name = "cryptography" -version = "41.0.7" +version = "42.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, - {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, - {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, - {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, + {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77"}, + {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009"}, + {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407"}, + {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa"}, + {file = "cryptography-42.0.1-cp37-abi3-win32.whl", hash = "sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453"}, + {file = "cryptography-42.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302"}, + {file = "cryptography-42.0.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca"}, + {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323"}, + {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49"}, + {file = "cryptography-42.0.1-cp39-abi3-win32.whl", hash = "sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881"}, + {file = "cryptography-42.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6"}, + {file = "cryptography-42.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f"}, + {file = "cryptography-42.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04"}, + {file = "cryptography-42.0.1.tar.gz", hash = "sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -2998,13 +3007,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.3.0" +version = "1.4.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, ] [package.extras] @@ -3399,34 +3408,34 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] name = "pylibsrtp" -version = "0.9.1" +version = "0.10.0" description = "Python wrapper around the libsrtp library" optional = false python-versions = ">=3.8" files = [ - {file = "pylibsrtp-0.9.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed9e4babf175f5d3eb3814ea4c4481fff31156050697ca771b292e93da5ff666"}, - {file = "pylibsrtp-0.9.1-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e9e4ee8f82cca3d6a5d79da653ce727cb3c3a65ad1837aaa0a8c2c8bc10e763"}, - {file = "pylibsrtp-0.9.1-cp38-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54da85cff840b0b7dfb6ee7f62bc90741e489dae2b4cc524dbf6702a91ccd2ad"}, - {file = "pylibsrtp-0.9.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d7da7d6c9f420ef4cf44044ac0543a2584704ee13a48c6169abba2b27624b51"}, - {file = "pylibsrtp-0.9.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ebf704c1e9d827d63cfde4999cdb954247ddf97e47b8f768c1ea47cc20ec51c"}, - {file = "pylibsrtp-0.9.1-cp38-abi3-win32.whl", hash = "sha256:7f5bbd06b823e412deeacd617f09fbdcff484f9dd0d85fd377acb674b35b903f"}, - {file = "pylibsrtp-0.9.1-cp38-abi3-win_amd64.whl", hash = "sha256:4034d8eee4b0d6df018585717329698591492d0762055478dc46169516639f78"}, - {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d5ae5225d6555cc7e1fc3d80c4c802d938080259c0cabacc88572ddacb143512"}, - {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8f866e5564ea714dec283cd0064d4770eb3099ca993eea211a6b6d34f98aeb1"}, - {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11e84d0eb0c912b737202bcbe4c406105d488c61553479a3a66eeef130ff92e0"}, - {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b1cffede6a3e90c19bbbe44a2ca5eee857de1483eb749cd532e0208bf53be6f"}, - {file = "pylibsrtp-0.9.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:83d240572bfb35de7c93dc024722556b62b35d89813f40d4a9f7d0f3bf84554e"}, - {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9ee457fc74eb75ba78c5144e94ab8aa0be8447d74ab8b95d802c2c34f07a787b"}, - {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94b3dd175c9c1177f420cfabdbd425dc76499855d9defd1b33591d0f50465acf"}, - {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58f7a0bdfdc40d4fa95446b639e683e969d56a53b3255eae1ea3cdb785cc396d"}, - {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d3a736189dc08ef2e6abdde2e7a94cc52c23b88030956023b4b92a946eccb57"}, - {file = "pylibsrtp-0.9.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4927b4672041c0200d5ccb69d9a6f9934b340b7035b6e2ddd9c5e0069b22e9f8"}, - {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f51c91b89a692784fea909d8e3a64222ad5145d056d21fdaec92112efaa6c60"}, - {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:215bcd54f9072be8a8985bdb60bdd432bf63173ce4cc5bd417209724524b4d53"}, - {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41ffbe0cdcba5014bdbd080abccdc79066d2860a499791dd9f049ab6aec6a3ec"}, - {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9550f2b0313a7efb34762b6df444a75f1021ac8e0b824448173eafb7537018bd"}, - {file = "pylibsrtp-0.9.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ccc5147887bf9eec58fc8c54e1fe200a3041b61143024a7b38389d932ee7ebd2"}, - {file = "pylibsrtp-0.9.1.tar.gz", hash = "sha256:3a51d7a18aa2338e10f54c1bd03e94fd760fabb69c2f5bf1dc590305b7ec2402"}, + {file = "pylibsrtp-0.10.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6a1121ceea3339e0a84842a4a9da0fcf57cc8f99eb60dbf31a46d978b4170e7c"}, + {file = "pylibsrtp-0.10.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ca1994e73c6857b0a695fdde94cc5ac846c1b0d5d8766255a1dc2db40857f667"}, + {file = "pylibsrtp-0.10.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb7640b524544603d07bd4373b04c9582c8cfe41d9789d3f492081f053bed9c1"}, + {file = "pylibsrtp-0.10.0-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f13aa945e1dcf8c138bf3d4a6e34056c4c2f69bf9934bc53b320ef14c7317ccc"}, + {file = "pylibsrtp-0.10.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b2ef1c32d1145239dd0fe7b7fbe083334d345df6b4597fc66faf914a32682d9"}, + {file = "pylibsrtp-0.10.0-cp38-abi3-win32.whl", hash = "sha256:8c6fe2576b2ab13942b47db6c2ffe71f5eb1edc1dc3bdd7283169fecd5249e74"}, + {file = "pylibsrtp-0.10.0-cp38-abi3-win_amd64.whl", hash = "sha256:cd965d4b0e9a77b362526cab119f4d9ce39b83f1f20f46c6af8e694b86fa19a7"}, + {file = "pylibsrtp-0.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:582e9771be7ffd060faea215cb4248afdad1356da473df1b8f35c7e382ca3871"}, + {file = "pylibsrtp-0.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70111eeb87e5d3ffb9623e1ea036329dc81fed1282aa93c1f32377862ca0a0d8"}, + {file = "pylibsrtp-0.10.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eda06947ab42fd3737f01a7b98537a5d5908434d37c70488d10e7bd2ff0d520c"}, + {file = "pylibsrtp-0.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:511158499309c3f7e97e1ebeffbf3dd939e641ea553de43cfc02d3576aad5c15"}, + {file = "pylibsrtp-0.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4033481f332331bf14b9705dca69efd09d3809ba4a2ff69914c53dddf39c20c1"}, + {file = "pylibsrtp-0.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1990afa3b1acc2ded0bcb015e38d28eb707af32b4bb0a08ba4bc187148f36a9e"}, + {file = "pylibsrtp-0.10.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2115f91e39cf3718591fac3202df76d606441cabc9057e4a1b97b7e7a3328d9"}, + {file = "pylibsrtp-0.10.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5f971f1f9e4849c5ce38671628b3d1558763941cb4b54a74497026e82b5f68a"}, + {file = "pylibsrtp-0.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ca0e351c15d2d085665a1103d967fc90c9074d4ba60a0b3ed529ef075fd6e9e"}, + {file = "pylibsrtp-0.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea95747ce124f7a476fb71e0929d122ce6ddf35ae7d6f4eb372707a078c4b40"}, + {file = "pylibsrtp-0.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba342e60035dcdbb04368c95cea7ed2c9639ea34efb68db42cde3cfd6c489e71"}, + {file = "pylibsrtp-0.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95a31a776d993cdc486a6435268a6facac8c92cff560b0b53ccc3a1fd4714f6a"}, + {file = "pylibsrtp-0.10.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e519f9f73cd89404db9008bf7e628f8be4957000f61cee2841b62fcffe267c5"}, + {file = "pylibsrtp-0.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f33dced976a0039b4873d2468fb864795df0b79cdecf9b9a4286fe76099dfb1b"}, + {file = "pylibsrtp-0.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e4d9625a9667d2c0843bf9cfa731f815d8b9a04ed9ffe5b98442483b8b236f13"}, + {file = "pylibsrtp-0.10.0.tar.gz", hash = "sha256:d8001912d7f51bd05b4ea3551747930631777fd37892cf3bfe0e541a742e699f"}, ] [package.dependencies] @@ -6204,53 +6213,40 @@ pyobjc-framework-Cocoa = ">=10.1" [[package]] name = "pyopencl" -version = "2023.1.4" +version = "2024.1" description = "Python wrapper for OpenCL" optional = false python-versions = "~=3.8" files = [ - {file = "pyopencl-2023.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce71196cee0171a923d9ef6a8c21ce26fd7342ddaee88a29aa372ae3295f5257"}, - {file = "pyopencl-2023.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7bdf472b8e36f81145ac526d51f550ba539b765199da9da16732a222c7b85bee"}, - {file = "pyopencl-2023.1.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e99bb71267ce5223814177ff8c73e98057a70d20fc6555050d77d98b01bba5"}, - {file = "pyopencl-2023.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d8cbe0d2121babf7daf0bcd6ded687c191c6d2effbac85c5421659fbc26947"}, - {file = "pyopencl-2023.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08ebc3d5776ec2a8853c11234d605754bdf0d5af8322a08d558d661eb85c6fa3"}, - {file = "pyopencl-2023.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f199eff64be353f5badac62576d14cb40cc137c9f92f5260452de097dd9fe7a0"}, - {file = "pyopencl-2023.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:38a0a3c8389f44b51c0a916dc8e532d77ea37ab5a11f10e1ba97cc8f9a634695"}, - {file = "pyopencl-2023.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a1158956a59e73a57e98acc681ff01a64574d0b3715634703437aa8b7c785c"}, - {file = "pyopencl-2023.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55f09e9d12a036830d71f60af9233ff493c83ff2f99b472baa1f779688c816b7"}, - {file = "pyopencl-2023.1.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:742fdb18bcc933f20b795c787fe513c69b83f074626bc62ff3c9cd1c8b243022"}, - {file = "pyopencl-2023.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4102eb3f5b9fe4b08c7900820a908cec3004e9a6990cc7202162ae46e07869d"}, - {file = "pyopencl-2023.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:36d79cd6445a86b4db7399488c301b49bda5fde6c1455c36b1ce58e03c690916"}, - {file = "pyopencl-2023.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a561e42ee3030b2966e7dc5cc764e705d9ff1bb5aa1124fa7d6ba4009ebfb96"}, - {file = "pyopencl-2023.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:0f7889dce94dba10738225fabec929fa977bfe8777a64f5699f6f2fe2b00742c"}, - {file = "pyopencl-2023.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ab31b35875cecd2b1c10ac47ea06e224c5881cca942fba94387317357d73c0b9"}, - {file = "pyopencl-2023.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:230c63354fe6a18043e67042769ddc5e329002fb55fb99f692e4f5c8fcf3007c"}, - {file = "pyopencl-2023.1.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef091d4a4802b267b0aa5ec46c4ebc00fd664d9178ad9866b485006eeff180b4"}, - {file = "pyopencl-2023.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c11fc7445e21dcd97bd8f5534531ed7bbd09ded853b520157623f48fad5b739"}, - {file = "pyopencl-2023.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0b6a0ebbf67b2ef7098bdf3632177b71c7430883b2a48b2b09b84a02d8cbb4b0"}, - {file = "pyopencl-2023.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1767616f795c598531cd19d89f451db7e25393add300204e7d1a7dd2a017709f"}, - {file = "pyopencl-2023.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:8bbb02f230b969109bdf00f36e463fed1de6e1c70e088f2f2f9b41fab128f20c"}, - {file = "pyopencl-2023.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0e444320491744fe52e49f87674c931219a5be254a8a129175db61378f5b6f18"}, - {file = "pyopencl-2023.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0649a9d6249612e79cffef46618cccaaa9eac7b0c3f1833a3576ea0bd985d887"}, - {file = "pyopencl-2023.1.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f790dda26812cad5288fe1831158f55c48e6ede46ae4a37db66394645b07ef8e"}, - {file = "pyopencl-2023.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:571c6430c6fb046643040d4a6d99ee677e4c6c99b09577dbb51177001e93c21c"}, - {file = "pyopencl-2023.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0ca2d4f14e1fc716b1928679c3182595a4862f577fa0f4a5d8edf37ef8db059b"}, - {file = "pyopencl-2023.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6ac9e8e84dc11b82df51916cf41f34a9ca327adbaf9f4a03a8d1f4f1e1f35382"}, - {file = "pyopencl-2023.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:802e5eb27fd311af42133bb2da83b5777a84f5c7c11e0a4b8d1d50d265b22e37"}, - {file = "pyopencl-2023.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4b7f34aa56a4dc5ce5b4d795dc872f9a1aac66f15ded575aeabdfd15da0bc"}, - {file = "pyopencl-2023.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3f5861de88a7d5054cfc8a0f78c42c7b7cd7c65c43a1426a72411111b024658"}, - {file = "pyopencl-2023.1.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c658eb9198235e8866afedb9b32bad4c6a4988c7dff2103e61794cd9ea261b2a"}, - {file = "pyopencl-2023.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d148b21de9f7aa542b576c09ba3c68106658c8a3429f41c0120c7cd4cb55970f"}, - {file = "pyopencl-2023.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:63be569b566ad627e7a1331db6cfda3eb82a2076872549f1c89f4e24ee12601a"}, - {file = "pyopencl-2023.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d61a6ca8c2d8f2d7bcf106abff6ac58cb79f335303b02b90b66591b25d1af4aa"}, - {file = "pyopencl-2023.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:daeff57a66c7a2be03345dd919507f2a2b2ed4ce64c3d8416fc01fa947807e59"}, - {file = "pyopencl-2023.1.4-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c059f15d71c680e35704650bc02d7026b5566687fd45ca9f4c789567d0731cfc"}, - {file = "pyopencl-2023.1.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38c1ab4ed770eb2b2f9c34bced444fc81e96dddd188848f028d36cd16fe9fcb9"}, - {file = "pyopencl-2023.1.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d195dad3d3a0473373bbf173671900d4519662824b5a81ced5b491cfae5c5e23"}, - {file = "pyopencl-2023.1.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4917db2d0ec5ea7dd3c0cc66dc6a5acadc39a577a6b532293dde57ba3b23fb"}, - {file = "pyopencl-2023.1.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c926fb5886a561fe01be907575e396096a75fd35a2a6df5fc080c19959ae8c5c"}, - {file = "pyopencl-2023.1.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35fc3161b2f609018b6e1b22f4aa79fb571a388b61e90177c1d474975af15ce8"}, - {file = "pyopencl-2023.1.4.tar.gz", hash = "sha256:220174efca900e9d5de5aef2aa1b77a6f2550501de92b035a91013aeae4d4c5e"}, + {file = "pyopencl-2024.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fa9ed708955c1f2cf8d7fb07b639663ef9fc16376250476d12017eb848e907"}, + {file = "pyopencl-2024.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:229c79af0b0aa0980f632d0545ffd5c6e59d4b1d1ed5e1ccb548e2635ac1a700"}, + {file = "pyopencl-2024.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753693b81b8c51992a7cf5a18acf039b5e2def9362db349f52954cc4367e9b8e"}, + {file = "pyopencl-2024.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4b7be6175510f56c0451b43a46687bf1c3a9b1890ed107b49e7069a112bc4cc8"}, + {file = "pyopencl-2024.1-cp310-cp310-win_amd64.whl", hash = "sha256:e3a94aa8fa92e9abea2dccf2950c38480ebf936bc6c07bb617e3b1d7e0f9d0cf"}, + {file = "pyopencl-2024.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa2cf8f4c05245de8360151107357f765408421d1e4e0b785ec9d3b106d0f13f"}, + {file = "pyopencl-2024.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba574103bad5338fa6cf0dfe5f53edf0abb3d4b963fc7c69f347f69419fed419"}, + {file = "pyopencl-2024.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad5c462e8e32b4004fd1507d2c7359873453bed8734198c8d6dfd89a066be721"}, + {file = "pyopencl-2024.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69161052f2bcf05fecceec0efed306e5231e96da536affad4203645e836db59f"}, + {file = "pyopencl-2024.1-cp311-cp311-win_amd64.whl", hash = "sha256:c989f4d40e1dbaaa713349a6112fd3f022918c75f1466c62685f30b874ff2ede"}, + {file = "pyopencl-2024.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:382b996d9928afe12d45a559b43cc1d6f90a08066b684c6d004d978abd05015e"}, + {file = "pyopencl-2024.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1fc08f0f1e940b59d5768959d38972d2d6d41776acb85036678057502ad21997"}, + {file = "pyopencl-2024.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2aadbd401862396a3ad5fb21df516c04e952c707c6c42dfb014a79b8726f046"}, + {file = "pyopencl-2024.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:83ddcd972fc77f1e434a4f024bb3d4a09099105ebae1751cc1c0724fb891f7da"}, + {file = "pyopencl-2024.1-cp312-cp312-win_amd64.whl", hash = "sha256:e3477c703ee2537e80425b09f2b3306128b0be7b8d5ab3792636b8828e4f2abb"}, + {file = "pyopencl-2024.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26ca61c0646ddadf9e49e3caafb14f321d16c2b7f92fe8d465acb98d4594c94d"}, + {file = "pyopencl-2024.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f38ce6f501e4f23bf3fe211811e22bc82101f926480f2f5cd3f367366cfc9cbe"}, + {file = "pyopencl-2024.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3b3eb473af74018b1a0ace6ce71b3e2469a7588764ef816382ef0fdf8182797"}, + {file = "pyopencl-2024.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:828f67033ce15dd987e910e9130b864d4645792c712dd22b7e18ac5d13364190"}, + {file = "pyopencl-2024.1-cp38-cp38-win_amd64.whl", hash = "sha256:af251fb69e13f891c7c42752204b8525ce984b4a057defefb8af40ce80100b82"}, + {file = "pyopencl-2024.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6908ab02f20cb472dc3ceadeb4e0534ef75d6f150535d436711350bbafd5e3b8"}, + {file = "pyopencl-2024.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8c554086f3dc2b35b448e5f6e5a2651f637c9627d3464c66393d0b6daf18df9"}, + {file = "pyopencl-2024.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e290df49c2cef63b16338017543770f0af247d19b4d52ddf58991a83c97acd4"}, + {file = "pyopencl-2024.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b82cb17a714f1155b67734e2416fe6ed3e5899f46475efc233ff3b25acabb3cf"}, + {file = "pyopencl-2024.1-cp39-cp39-win_amd64.whl", hash = "sha256:4753e7b960471f67d9eb4a2ea9cb14f84d1e37bcfb28e3e1e3f747151ac78d15"}, + {file = "pyopencl-2024.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b39c710b512e0c33e3553b175e71d6f46f80577f4b6afa731da295406b2f85"}, + {file = "pyopencl-2024.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:237a80e48d5ea31b4ec734011effab4bb33d4447f8f65d7b9e0d9c716df1cefa"}, + {file = "pyopencl-2024.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9293f9d2fdea419d014534a5f66798bd439617dcc2ae21847ea99a9fac1dd3a"}, + {file = "pyopencl-2024.1.tar.gz", hash = "sha256:ecd572ee940ad8bda1639c3a7beb68834fc9a98ad7eb3f6e01aac4f7d9d4bac1"}, ] [package.dependencies] @@ -6265,17 +6261,17 @@ test = ["Mako", "pytest (>=7.0.0)"] [[package]] name = "pyopenssl" -version = "23.3.0" +version = "24.0.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.7" files = [ - {file = "pyOpenSSL-23.3.0-py3-none-any.whl", hash = "sha256:6756834481d9ed5470f4a9393455154bc92fe7a64b7bc6ee2c804e78c52099b2"}, - {file = "pyOpenSSL-23.3.0.tar.gz", hash = "sha256:6b2cba5cc46e822750ec3e5a81ee12819850b11303630d575e98108a079c2b12"}, + {file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"}, + {file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"}, ] [package.dependencies] -cryptography = ">=41.0.5,<42" +cryptography = ">=41.0.5,<43" [package.extras] docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] @@ -6977,13 +6973,13 @@ setuptools = "*" [[package]] name = "seaborn" -version = "0.13.1" +version = "0.13.2" description = "Statistical data visualization" optional = false python-versions = ">=3.8" files = [ - {file = "seaborn-0.13.1-py3-none-any.whl", hash = "sha256:6baa69b6d1169ae59037971491c450c0b73332b42bd4b23570b62a546bc61cb8"}, - {file = "seaborn-0.13.1.tar.gz", hash = "sha256:bfad65e9c5989e5e1897e61bdbd2f22e62455940ca76fd49eca3ed69345b9179"}, + {file = "seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987"}, + {file = "seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7"}, ] [package.dependencies] @@ -6998,13 +6994,13 @@ stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] [[package]] name = "sentry-sdk" -version = "1.28.1" +version = "1.39.2" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.28.1.tar.gz", hash = "sha256:dcd88c68aa64dae715311b5ede6502fd684f70d00a7cd4858118f0ba3153a3ae"}, - {file = "sentry_sdk-1.28.1-py2.py3-none-any.whl", hash = "sha256:6bdb25bd9092478d3a817cb0d01fa99e296aea34d404eac3ca0037faa5c2aa0a"}, + {file = "sentry-sdk-1.39.2.tar.gz", hash = "sha256:24c83b0b41c887d33328a9166f5950dc37ad58f01c9f2fbff6b87a6f1094170c"}, + {file = "sentry_sdk-1.39.2-py2.py3-none-any.whl", hash = "sha256:acaf597b30258fc7663063b291aa99e58f3096e91fe1e6634f4b79f9c1943e8e"}, ] [package.dependencies] @@ -7014,10 +7010,12 @@ urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} [package.extras] aiohttp = ["aiohttp (>=3.5)"] arq = ["arq (>=0.23)"] +asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] bottle = ["bottle (>=0.12.13)"] celery = ["celery (>=3)"] chalice = ["chalice (>=1.16.0)"] +clickhouse-driver = ["clickhouse-driver (>=0.2.0)"] django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] @@ -7027,7 +7025,8 @@ httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] loguru = ["loguru (>=0.5)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -pure-eval = ["asttokens", "executing", "pure-eval"] +opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] +pure-eval = ["asttokens", "executing", "pure_eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -7562,13 +7561,13 @@ telegram = ["requests"] [[package]] name = "types-requests" -version = "2.31.0.20240106" +version = "2.31.0.20240125" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.20240106.tar.gz", hash = "sha256:0e1c731c17f33618ec58e022b614a1a2ecc25f7dc86800b36ef341380402c612"}, - {file = "types_requests-2.31.0.20240106-py3-none-any.whl", hash = "sha256:da997b3b6a72cc08d09f4dba9802fdbabc89104b35fe24ee588e674037689354"}, + {file = "types-requests-2.31.0.20240125.tar.gz", hash = "sha256:03a28ce1d7cd54199148e043b2079cdded22d6795d19a2c2a6791a4b2b5e2eb5"}, + {file = "types_requests-2.31.0.20240125-py3-none-any.whl", hash = "sha256:9592a9a4cb92d6d75d9b491a41477272b710e021011a2a3061157e2fb1f1a5d1"}, ] [package.dependencies] @@ -7796,4 +7795,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "31a962516d63b49e14cc615807e067ec833f76d5aa1157a4dcc9408f23c28ad1" +content-hash = "94851b6ca3418abfc2061c31a2631026a24cf380b76738db45b917079e9f9583" diff --git a/pyproject.toml b/pyproject.toml index 647f3df6b9..ebc413de12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,7 +99,7 @@ pyserial = "*" pyzmq = "*" requests = "*" scons = "*" -sentry-sdk = "==1.28.1" # needs to be updated with AGNOS +sentry-sdk = "*" smbus2 = "*" sounddevice = "*" spidev = { version = "*", platform = "linux" } diff --git a/selfdrive/sentry.py b/selfdrive/sentry.py index f4bb1fbce8..6b14b6bbd6 100644 --- a/selfdrive/sentry.py +++ b/selfdrive/sentry.py @@ -54,14 +54,13 @@ def init(project: SentryProject) -> bool: integrations = [] if project == SentryProject.SELFDRIVE: integrations.append(ThreadingIntegration(propagate_hub=True)) - else: - sentry_sdk.utils.MAX_STRING_LENGTH = 8192 sentry_sdk.init(project.value, default_integrations=False, release=get_version(), integrations=integrations, traces_sample_rate=1.0, + max_value_length=8192, environment=env) sentry_sdk.set_user({"id": dongle_id}) From 6fbd8442164fbc4dde8949d63c67a9a600a3f896 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 26 Jan 2024 14:37:22 -0800 Subject: [PATCH 600/724] mark as flaky for now --- system/loggerd/tests/test_loggerd.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index dde12b646d..625fa22f6d 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -8,6 +8,7 @@ import time from collections import defaultdict from pathlib import Path from typing import Dict, List +from flaky import flaky import cereal.messaging as messaging from cereal import log @@ -137,6 +138,7 @@ class TestLoggerd: params.put("AccessToken", "") + @flaky(max_runs=3) def test_rotation(self): os.environ["LOGGERD_TEST"] = "1" Params().put("RecordFront", "1") From eada51ec8ed570a4efe70909760460c976cd4f04 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 26 Jan 2024 16:00:33 -0800 Subject: [PATCH 601/724] pin pyopencl to fix aarch64 build (#31184) * revert pyopencl * revert that --- poetry.lock | 75 +++++++++++++++++++++++++++++--------------------- pyproject.toml | 2 +- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6d19a563d8..a289470d47 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6213,40 +6213,53 @@ pyobjc-framework-Cocoa = ">=10.1" [[package]] name = "pyopencl" -version = "2024.1" +version = "2023.1.4" description = "Python wrapper for OpenCL" optional = false python-versions = "~=3.8" files = [ - {file = "pyopencl-2024.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fa9ed708955c1f2cf8d7fb07b639663ef9fc16376250476d12017eb848e907"}, - {file = "pyopencl-2024.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:229c79af0b0aa0980f632d0545ffd5c6e59d4b1d1ed5e1ccb548e2635ac1a700"}, - {file = "pyopencl-2024.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753693b81b8c51992a7cf5a18acf039b5e2def9362db349f52954cc4367e9b8e"}, - {file = "pyopencl-2024.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4b7be6175510f56c0451b43a46687bf1c3a9b1890ed107b49e7069a112bc4cc8"}, - {file = "pyopencl-2024.1-cp310-cp310-win_amd64.whl", hash = "sha256:e3a94aa8fa92e9abea2dccf2950c38480ebf936bc6c07bb617e3b1d7e0f9d0cf"}, - {file = "pyopencl-2024.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa2cf8f4c05245de8360151107357f765408421d1e4e0b785ec9d3b106d0f13f"}, - {file = "pyopencl-2024.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba574103bad5338fa6cf0dfe5f53edf0abb3d4b963fc7c69f347f69419fed419"}, - {file = "pyopencl-2024.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad5c462e8e32b4004fd1507d2c7359873453bed8734198c8d6dfd89a066be721"}, - {file = "pyopencl-2024.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:69161052f2bcf05fecceec0efed306e5231e96da536affad4203645e836db59f"}, - {file = "pyopencl-2024.1-cp311-cp311-win_amd64.whl", hash = "sha256:c989f4d40e1dbaaa713349a6112fd3f022918c75f1466c62685f30b874ff2ede"}, - {file = "pyopencl-2024.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:382b996d9928afe12d45a559b43cc1d6f90a08066b684c6d004d978abd05015e"}, - {file = "pyopencl-2024.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1fc08f0f1e940b59d5768959d38972d2d6d41776acb85036678057502ad21997"}, - {file = "pyopencl-2024.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2aadbd401862396a3ad5fb21df516c04e952c707c6c42dfb014a79b8726f046"}, - {file = "pyopencl-2024.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:83ddcd972fc77f1e434a4f024bb3d4a09099105ebae1751cc1c0724fb891f7da"}, - {file = "pyopencl-2024.1-cp312-cp312-win_amd64.whl", hash = "sha256:e3477c703ee2537e80425b09f2b3306128b0be7b8d5ab3792636b8828e4f2abb"}, - {file = "pyopencl-2024.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26ca61c0646ddadf9e49e3caafb14f321d16c2b7f92fe8d465acb98d4594c94d"}, - {file = "pyopencl-2024.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f38ce6f501e4f23bf3fe211811e22bc82101f926480f2f5cd3f367366cfc9cbe"}, - {file = "pyopencl-2024.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3b3eb473af74018b1a0ace6ce71b3e2469a7588764ef816382ef0fdf8182797"}, - {file = "pyopencl-2024.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:828f67033ce15dd987e910e9130b864d4645792c712dd22b7e18ac5d13364190"}, - {file = "pyopencl-2024.1-cp38-cp38-win_amd64.whl", hash = "sha256:af251fb69e13f891c7c42752204b8525ce984b4a057defefb8af40ce80100b82"}, - {file = "pyopencl-2024.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6908ab02f20cb472dc3ceadeb4e0534ef75d6f150535d436711350bbafd5e3b8"}, - {file = "pyopencl-2024.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8c554086f3dc2b35b448e5f6e5a2651f637c9627d3464c66393d0b6daf18df9"}, - {file = "pyopencl-2024.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e290df49c2cef63b16338017543770f0af247d19b4d52ddf58991a83c97acd4"}, - {file = "pyopencl-2024.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b82cb17a714f1155b67734e2416fe6ed3e5899f46475efc233ff3b25acabb3cf"}, - {file = "pyopencl-2024.1-cp39-cp39-win_amd64.whl", hash = "sha256:4753e7b960471f67d9eb4a2ea9cb14f84d1e37bcfb28e3e1e3f747151ac78d15"}, - {file = "pyopencl-2024.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b39c710b512e0c33e3553b175e71d6f46f80577f4b6afa731da295406b2f85"}, - {file = "pyopencl-2024.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:237a80e48d5ea31b4ec734011effab4bb33d4447f8f65d7b9e0d9c716df1cefa"}, - {file = "pyopencl-2024.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9293f9d2fdea419d014534a5f66798bd439617dcc2ae21847ea99a9fac1dd3a"}, - {file = "pyopencl-2024.1.tar.gz", hash = "sha256:ecd572ee940ad8bda1639c3a7beb68834fc9a98ad7eb3f6e01aac4f7d9d4bac1"}, + {file = "pyopencl-2023.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce71196cee0171a923d9ef6a8c21ce26fd7342ddaee88a29aa372ae3295f5257"}, + {file = "pyopencl-2023.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7bdf472b8e36f81145ac526d51f550ba539b765199da9da16732a222c7b85bee"}, + {file = "pyopencl-2023.1.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e99bb71267ce5223814177ff8c73e98057a70d20fc6555050d77d98b01bba5"}, + {file = "pyopencl-2023.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11d8cbe0d2121babf7daf0bcd6ded687c191c6d2effbac85c5421659fbc26947"}, + {file = "pyopencl-2023.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08ebc3d5776ec2a8853c11234d605754bdf0d5af8322a08d558d661eb85c6fa3"}, + {file = "pyopencl-2023.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f199eff64be353f5badac62576d14cb40cc137c9f92f5260452de097dd9fe7a0"}, + {file = "pyopencl-2023.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:38a0a3c8389f44b51c0a916dc8e532d77ea37ab5a11f10e1ba97cc8f9a634695"}, + {file = "pyopencl-2023.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46a1158956a59e73a57e98acc681ff01a64574d0b3715634703437aa8b7c785c"}, + {file = "pyopencl-2023.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55f09e9d12a036830d71f60af9233ff493c83ff2f99b472baa1f779688c816b7"}, + {file = "pyopencl-2023.1.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:742fdb18bcc933f20b795c787fe513c69b83f074626bc62ff3c9cd1c8b243022"}, + {file = "pyopencl-2023.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4102eb3f5b9fe4b08c7900820a908cec3004e9a6990cc7202162ae46e07869d"}, + {file = "pyopencl-2023.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:36d79cd6445a86b4db7399488c301b49bda5fde6c1455c36b1ce58e03c690916"}, + {file = "pyopencl-2023.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4a561e42ee3030b2966e7dc5cc764e705d9ff1bb5aa1124fa7d6ba4009ebfb96"}, + {file = "pyopencl-2023.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:0f7889dce94dba10738225fabec929fa977bfe8777a64f5699f6f2fe2b00742c"}, + {file = "pyopencl-2023.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ab31b35875cecd2b1c10ac47ea06e224c5881cca942fba94387317357d73c0b9"}, + {file = "pyopencl-2023.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:230c63354fe6a18043e67042769ddc5e329002fb55fb99f692e4f5c8fcf3007c"}, + {file = "pyopencl-2023.1.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef091d4a4802b267b0aa5ec46c4ebc00fd664d9178ad9866b485006eeff180b4"}, + {file = "pyopencl-2023.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c11fc7445e21dcd97bd8f5534531ed7bbd09ded853b520157623f48fad5b739"}, + {file = "pyopencl-2023.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0b6a0ebbf67b2ef7098bdf3632177b71c7430883b2a48b2b09b84a02d8cbb4b0"}, + {file = "pyopencl-2023.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1767616f795c598531cd19d89f451db7e25393add300204e7d1a7dd2a017709f"}, + {file = "pyopencl-2023.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:8bbb02f230b969109bdf00f36e463fed1de6e1c70e088f2f2f9b41fab128f20c"}, + {file = "pyopencl-2023.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0e444320491744fe52e49f87674c931219a5be254a8a129175db61378f5b6f18"}, + {file = "pyopencl-2023.1.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0649a9d6249612e79cffef46618cccaaa9eac7b0c3f1833a3576ea0bd985d887"}, + {file = "pyopencl-2023.1.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f790dda26812cad5288fe1831158f55c48e6ede46ae4a37db66394645b07ef8e"}, + {file = "pyopencl-2023.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:571c6430c6fb046643040d4a6d99ee677e4c6c99b09577dbb51177001e93c21c"}, + {file = "pyopencl-2023.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0ca2d4f14e1fc716b1928679c3182595a4862f577fa0f4a5d8edf37ef8db059b"}, + {file = "pyopencl-2023.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6ac9e8e84dc11b82df51916cf41f34a9ca327adbaf9f4a03a8d1f4f1e1f35382"}, + {file = "pyopencl-2023.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:802e5eb27fd311af42133bb2da83b5777a84f5c7c11e0a4b8d1d50d265b22e37"}, + {file = "pyopencl-2023.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ae4b7f34aa56a4dc5ce5b4d795dc872f9a1aac66f15ded575aeabdfd15da0bc"}, + {file = "pyopencl-2023.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3f5861de88a7d5054cfc8a0f78c42c7b7cd7c65c43a1426a72411111b024658"}, + {file = "pyopencl-2023.1.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c658eb9198235e8866afedb9b32bad4c6a4988c7dff2103e61794cd9ea261b2a"}, + {file = "pyopencl-2023.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d148b21de9f7aa542b576c09ba3c68106658c8a3429f41c0120c7cd4cb55970f"}, + {file = "pyopencl-2023.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:63be569b566ad627e7a1331db6cfda3eb82a2076872549f1c89f4e24ee12601a"}, + {file = "pyopencl-2023.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d61a6ca8c2d8f2d7bcf106abff6ac58cb79f335303b02b90b66591b25d1af4aa"}, + {file = "pyopencl-2023.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:daeff57a66c7a2be03345dd919507f2a2b2ed4ce64c3d8416fc01fa947807e59"}, + {file = "pyopencl-2023.1.4-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c059f15d71c680e35704650bc02d7026b5566687fd45ca9f4c789567d0731cfc"}, + {file = "pyopencl-2023.1.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38c1ab4ed770eb2b2f9c34bced444fc81e96dddd188848f028d36cd16fe9fcb9"}, + {file = "pyopencl-2023.1.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d195dad3d3a0473373bbf173671900d4519662824b5a81ced5b491cfae5c5e23"}, + {file = "pyopencl-2023.1.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4917db2d0ec5ea7dd3c0cc66dc6a5acadc39a577a6b532293dde57ba3b23fb"}, + {file = "pyopencl-2023.1.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c926fb5886a561fe01be907575e396096a75fd35a2a6df5fc080c19959ae8c5c"}, + {file = "pyopencl-2023.1.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35fc3161b2f609018b6e1b22f4aa79fb571a388b61e90177c1d474975af15ce8"}, + {file = "pyopencl-2023.1.4.tar.gz", hash = "sha256:220174efca900e9d5de5aef2aa1b77a6f2550501de92b035a91013aeae4d4c5e"}, ] [package.dependencies] @@ -7795,4 +7808,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "94851b6ca3418abfc2061c31a2631026a24cf380b76738db45b917079e9f9583" +content-hash = "8af315a175ab43dbc5a777f68fca6d76af1443b5b574ab8570ef5dad59f288fc" diff --git a/pyproject.toml b/pyproject.toml index ebc413de12..116c034bc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -137,7 +137,7 @@ pprofile = "*" polyline = "*" pre-commit = "*" pyautogui = "*" -pyopencl = "*" +pyopencl = "==2023.1.4" # 2024.1 is broken on arm64 pygame = "*" pywinctl = "*" pyprof2calltree = "*" From 52c7514452f984db5036994c20be652ec0b44a6c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 26 Jan 2024 16:44:12 -0800 Subject: [PATCH 602/724] tools: add LOG_ID regex pattern (#31186) --- tools/lib/helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index e2c8d07dd8..029ecb7966 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -7,11 +7,14 @@ TIME_FMT = "%Y-%m-%d--%H-%M-%S" class RE: DONGLE_ID = r'(?P[a-z0-9]{16})' TIMESTAMP = r'(?P[0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2})' - ROUTE_NAME = r'(?P{}[|_/]{})'.format(DONGLE_ID, TIMESTAMP) + LOG_ID = r"(?P{})".format(TIMESTAMP) + ROUTE_NAME = r'(?P{}[|_/]{})'.format(DONGLE_ID, LOG_ID) SEGMENT_NAME = r'{}(?:--|/)(?P[0-9]+)'.format(ROUTE_NAME) + INDEX = r'-?[0-9]+' SLICE = r'(?P{})?:?(?P{})?:?(?P{})?'.format(INDEX, INDEX, INDEX) SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?/?(?P([qra]))?'.format(ROUTE_NAME, SLICE) + BOOTLOG_NAME = ROUTE_NAME EXPLORER_FILE = r'^(?P{})--(?P[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME) From 25043410705f2a7a275e69ec513c8889465d4ba3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 26 Jan 2024 19:17:38 -0800 Subject: [PATCH 603/724] bootlog: remove timestamp from filename (#31187) * bootlog: remove timestamp from filename * revert route * test * revert that * fix loggerd test --- common/params.cc | 1 + system/loggerd/bootlog.cc | 6 +++--- system/loggerd/logger.cc | 27 +++++++++++++++++++++++++++ system/loggerd/logger.h | 1 + system/loggerd/tests/test_loggerd.py | 13 +++++++++---- tools/lib/bootlog.py | 19 +++++++------------ tools/lib/helpers.py | 3 ++- 7 files changed, 50 insertions(+), 20 deletions(-) diff --git a/common/params.cc b/common/params.cc index 386813efdd..3ce2505243 100644 --- a/common/params.cc +++ b/common/params.cc @@ -95,6 +95,7 @@ std::unordered_map keys = { {"AthenadPid", PERSISTENT}, {"AthenadUploadQueue", PERSISTENT}, {"AthenadRecentlyViewedRoutes", PERSISTENT}, + {"BootCount", PERSISTENT}, {"CalibrationParams", PERSISTENT}, {"CameraDebugExpGain", CLEAR_ON_MANAGER_START}, {"CameraDebugExpTime", CLEAR_ON_MANAGER_START}, diff --git a/system/loggerd/bootlog.cc b/system/loggerd/bootlog.cc index 771594d20c..b8257b6d69 100644 --- a/system/loggerd/bootlog.cc +++ b/system/loggerd/bootlog.cc @@ -49,8 +49,8 @@ static kj::Array build_boot_log() { } int main(int argc, char** argv) { - const std::string timestr = logger_get_route_name(); - const std::string path = Path::log_root() + "/boot/" + timestr; + const std::string id = logger_get_identifier("BootCount"); + const std::string path = Path::log_root() + "/boot/" + id; LOGW("bootlog to %s", path.c_str()); // Open bootlog @@ -64,7 +64,7 @@ int main(int argc, char** argv) { file.write(build_boot_log().asBytes()); // Write out bootlog param to match routes with bootlog - Params().put("CurrentBootlog", timestr.c_str()); + Params().put("CurrentBootlog", id.c_str()); return 0; } diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index bc842f4a9a..bb829df6ed 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include "common/params.h" #include "common/swaglog.h" @@ -94,6 +97,30 @@ std::string logger_get_route_name() { return route_name; } +std::string logger_get_identifier(std::string key) { + // a log identifier is a 32 bit counter, plus a 10 character unique ID. + // e.g. 000001a3--c20ba54385 + + Params params; + uint32_t cnt; + try { + cnt = std::stol(params.get(key)); + } catch (std::exception &e) { + cnt = 0; + } + params.put(key, std::to_string(cnt + 1)); + + std::stringstream ss; + std::random_device rd; + std::mt19937 mt(rd()); + std::uniform_int_distribution dist(0, 15); + for (int i = 0; i < 10; ++i) { + ss << std::hex << dist(mt); + } + + return util::string_format("%08x--%s", cnt, ss.str().c_str()); +} + static void log_sentinel(LoggerState *log, SentinelType type, int eixt_signal = 0) { MessageBuilder msg; auto sen = msg.initEvent().initSentinel(); diff --git a/system/loggerd/logger.h b/system/loggerd/logger.h index 76a12b9e87..dd3bee150c 100644 --- a/system/loggerd/logger.h +++ b/system/loggerd/logger.h @@ -53,3 +53,4 @@ protected: kj::Array logger_build_init_data(); std::string logger_get_route_name(); +std::string logger_get_identifier(std::string key); diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 625fa22f6d..0cd8548809 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import numpy as np import os +import re import random import string import subprocess @@ -21,6 +22,7 @@ from openpilot.system.loggerd.xattr_cache import getxattr from openpilot.system.loggerd.deleter import PRESERVE_ATTR_NAME, PRESERVE_ATTR_VALUE from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.system.version import get_version +from openpilot.tools.lib.helpers import RE from openpilot.tools.lib.logreader import LogReader from cereal.visionipc import VisionIpcServer, VisionStreamType from openpilot.common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size @@ -109,7 +111,6 @@ class TestLoggerd: ("GitRemote", "gitRemote", "remote"), ] params = Params() - params.clear_all() for k, _, v in fake_params: params.put(k, v) params.put("AccessToken", "abc") @@ -129,15 +130,13 @@ class TestLoggerd: # check params logged_params = {entry.key: entry.value for entry in initData.params.entries} - expected_params = {k for k, _, __ in fake_params} | {'AccessToken'} + expected_params = {k for k, _, __ in fake_params} | {'AccessToken', 'BootCount'} assert set(logged_params.keys()) == expected_params, set(logged_params.keys()) ^ expected_params assert logged_params['AccessToken'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['AccessToken'])}" for param_key, initData_key, v in fake_params: assert getattr(initData, initData_key) == v assert logged_params[param_key].decode() == v - params.put("AccessToken", "") - @flaky(max_runs=3) def test_rotation(self): os.environ["LOGGERD_TEST"] = "1" @@ -216,6 +215,12 @@ class TestLoggerd: bootlog_val = [e.value for e in boot.pstore.entries if e.key == fn][0] assert expected_val == bootlog_val + # next one should increment by one + bl1 = re.match(RE.LOG_ID_V2, bootlog_path.name) + bl2 = re.match(RE.LOG_ID_V2, self._gen_bootlog().name) + assert bl1.group('uid') != bl2.group('uid') + assert int(bl1.group('count')) == 0 and int(bl2.group('count')) == 1 + def test_qlog(self): qlog_services = [s for s in CEREAL_SERVICES if SERVICE_LIST[s].decimation is not None] no_qlog_services = [s for s in CEREAL_SERVICES if SERVICE_LIST[s].decimation is None] diff --git a/tools/lib/bootlog.py b/tools/lib/bootlog.py index 01756bb5e9..827ef1eefc 100644 --- a/tools/lib/bootlog.py +++ b/tools/lib/bootlog.py @@ -1,11 +1,10 @@ -import datetime import functools import re from typing import List, Optional from openpilot.tools.lib.auth_config import get_token from openpilot.tools.lib.api import CommaApi -from openpilot.tools.lib.helpers import RE, timestamp_to_datetime +from openpilot.tools.lib.helpers import RE @functools.total_ordering @@ -17,8 +16,8 @@ class Bootlog: if not r: raise Exception(f"Unable to parse: {url}") + self._id = r.group('log_id') self._dongle_id = r.group('dongle_id') - self._timestamp = r.group('timestamp') @property def url(self) -> str: @@ -29,25 +28,21 @@ class Bootlog: return self._dongle_id @property - def timestamp(self) -> str: - return self._timestamp - - @property - def datetime(self) -> datetime.datetime: - return timestamp_to_datetime(self._timestamp) + def id(self) -> str: + return self._id def __str__(self): - return f"{self._dongle_id}|{self._timestamp}" + return f"{self._dongle_id}/{self._id}" def __eq__(self, b) -> bool: if not isinstance(b, Bootlog): return False - return self.datetime == b.datetime + return self.id == b.id def __lt__(self, b) -> bool: if not isinstance(b, Bootlog): return False - return self.datetime < b.datetime + return self.id < b.id def get_bootlog_from_id(bootlog_id: str) -> Optional[Bootlog]: # TODO: implement an API endpoint for this diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index 029ecb7966..37bbaf0c4a 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -7,7 +7,8 @@ TIME_FMT = "%Y-%m-%d--%H-%M-%S" class RE: DONGLE_ID = r'(?P[a-z0-9]{16})' TIMESTAMP = r'(?P[0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2})' - LOG_ID = r"(?P{})".format(TIMESTAMP) + LOG_ID_V2 = r'(?P[a-z0-9]{8})--(?P[a-z0-9]{10})' + LOG_ID = r'(?P(?:{}|{}))'.format(TIMESTAMP, LOG_ID_V2) ROUTE_NAME = r'(?P{}[|_/]{})'.format(DONGLE_ID, LOG_ID) SEGMENT_NAME = r'{}(?:--|/)(?P[0-9]+)'.format(ROUTE_NAME) From 824ddffec8c67758f3ea080c75e805d4b27f38ef Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sat, 27 Jan 2024 01:07:29 -0800 Subject: [PATCH 604/724] CommaCarSegments: BIG Database! (#31169) * sanetized selector * god damn * add to re * slog branch * slog * source * big * not slog * cleanup * no space * works! * trigger * main! --- .../examples/ford_vin_fingerprint.ipynb | 65 +++++++++++-------- tools/lib/helpers.py | 2 +- tools/lib/logreader.py | 24 ++++--- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/tools/car_porting/examples/ford_vin_fingerprint.ipynb b/tools/car_porting/examples/ford_vin_fingerprint.ipynb index b020b1100b..21d1cb62c0 100644 --- a/tools/car_porting/examples/ford_vin_fingerprint.ipynb +++ b/tools/car_porting/examples/ford_vin_fingerprint.ipynb @@ -2,17 +2,9 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "kj/filesystem-disk-unix.c++:1703: warning: PWD environment variable doesn't match current directory; pwd = /home/batman\n" - ] - } - ], + "outputs": [], "source": [ "\"\"\"In this example, we use the public comma car segments database to check if vin fingerprinting is feasible for ford.\"\"\"\n", "\n", @@ -27,7 +19,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -62,55 +54,74 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Skipping platform: FORD F-150 14TH GEN, no data available\n" + "Got 287 segments for platform FORD BRONCO SPORT 1ST GEN, sampling 5 segments\n", + "Got 137 segments for platform FORD ESCAPE 4TH GEN, sampling 5 segments\n", + "Got 1041 segments for platform FORD EXPLORER 6TH GEN, sampling 5 segments\n", + "Got 5 segments for platform FORD F-150 14TH GEN, sampling 5 segments\n", + "Got 56 segments for platform FORD FOCUS 4TH GEN, sampling 5 segments\n", + "Got 637 segments for platform FORD MAVERICK 1ST GEN, sampling 5 segments\n", + "Got 3 segments for platform FORD F-150 LIGHTNING 1ST GEN, sampling 3 segments\n", + "Got 3 segments for platform FORD MUSTANG MACH-E 1ST GEN, sampling 3 segments\n" ] } ], "source": [ + "import random\n", + "\n", + "MAX_SEGS_PER_PLATFORM = 5\n", + "\n", "VINS_TO_CHECK = set()\n", "\n", "for platform in platforms:\n", " if platform not in database:\n", " print(f\"Skipping platform: {platform}, no data available\")\n", " continue\n", + " \n", + " all_segments = database[platform]\n", "\n", - " for segment in database[platform]:\n", + " NUM_SEGMENTS = min(len(all_segments), MAX_SEGS_PER_PLATFORM)\n", + "\n", + " print(f\"Got {len(all_segments)} segments for platform {platform}, sampling {NUM_SEGMENTS} segments\")\n", + "\n", + " segments = random.sample(all_segments, NUM_SEGMENTS)\n", + "\n", + " for segment in segments:\n", " lr = LogReader(segment)\n", " CP = lr.first(\"carParams\")\n", + " if \"FORD\" not in CP.carFingerprint:\n", + " print(segment, CP.carFingerprint)\n", " VINS_TO_CHECK.add((CP.carVin, CP.carFingerprint))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "vin: 3FTTW8E31PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", - "vin: 1FM5K8GC7LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", - "vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", - "vin: 1FM5K8GC7NGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", - "vin: 1FM5K7LC0MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", - "vin: 1FM5K8HC7MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 3FTTW8E34PRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", + "vin: 00000000000XXXXXX real platform: FORD F-150 14TH GEN determined platform: mock correct: False\n", "vin: 3FMTK3SU0MMXXXXXX real platform: FORD MUSTANG MACH-E 1ST GEN determined platform: FORD MUSTANG MACH-E 1ST GEN correct: True\n", - "vin: WF0NXXGCHNJXXXXXX real platform: FORD FOCUS 4TH GEN determined platform: mock correct: False\n", "vin: 1FTVW1EL4NWXXXXXX real platform: FORD F-150 LIGHTNING 1ST GEN determined platform: FORD F-150 LIGHTNING 1ST GEN correct: True\n", - "vin: 00000000000XXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n", - "vin: 3FTTW8F98NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", - "vin: 5LM5J7XC9LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", - "vin: 5LM5J7XC8MGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: WF0NXXGCHNJXXXXXX real platform: FORD FOCUS 4TH GEN determined platform: mock correct: False\n", + "vin: 1FMCU9J94MUXXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n", + "vin: 3FTTW8E33NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", "vin: 3FMCR9B69NRXXXXXX real platform: FORD BRONCO SPORT 1ST GEN determined platform: mock correct: False\n", - "vin: 1FMCU9J94MUXXXXXX real platform: FORD ESCAPE 4TH GEN determined platform: mock correct: False\n" + "vin: 1FM5K8GC7LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 5LM5J7XC9LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 5LM5J7XC1LGXXXXXX real platform: FORD EXPLORER 6TH GEN determined platform: mock correct: False\n", + "vin: 3FTTW8F97NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n", + "vin: 3FTTW8E99NRXXXXXX real platform: FORD MAVERICK 1ST GEN determined platform: mock correct: False\n" ] } ], diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index 37bbaf0c4a..423f207b4d 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -14,7 +14,7 @@ class RE: INDEX = r'-?[0-9]+' SLICE = r'(?P{})?:?(?P{})?:?(?P{})?'.format(INDEX, INDEX, INDEX) - SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?/?(?P([qra]))?'.format(ROUTE_NAME, SLICE) + SEGMENT_RANGE = r'{}(?:--|/)?(?P({}))?/?(?P([qras]))?'.format(ROUTE_NAME, SLICE) BOOTLOG_NAME = ROUTE_NAME diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index d6c1cc90c2..e86690f3c3 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -72,6 +72,7 @@ class _LogFileReader: class ReadMode(enum.StrEnum): RLOG = "r" # only read rlogs QLOG = "q" # only read qlogs + SANITIZED = "s" # read from the commaCarSegments database AUTO = "a" # default to rlogs, fallback to qlogs AUTO_INTERACIVE = "i" # default to rlogs, fallback to qlogs with a prompt from the user @@ -169,17 +170,24 @@ def check_source(source, *args): try: files = source(*args) assert next(get_invalid_files(files), None) is None - return True, files - except Exception: - return False, None + return None, files + except Exception as e: + return e, None -def auto_source(*args): +def auto_source(sr: SegmentRange, mode=ReadMode.RLOG): + if mode == ReadMode.SANITIZED: + return comma_car_segments_source(sr, mode) + + exceptions = [] # Automatically determine viable source - for source in [comma_car_segments_source, internal_source, openpilotci_source]: - valid, ret = check_source(source, *args) - if valid: + for source in [internal_source, openpilotci_source, comma_api_source, comma_car_segments_source]: + exception, ret = check_source(source, sr, mode) + if exception is None: return ret - return comma_api_source(*args) + else: + exceptions.append(exception) + + raise Exception(f"auto_source could not find any valid source, exceptions for sources: {exceptions}") def parse_useradmin(identifier): if "useradmin.comma.ai" in identifier: From aff3d5ae091eba2b2ef09f004c6a2b70ee33db06 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Sat, 27 Jan 2024 09:07:14 -0800 Subject: [PATCH 605/724] Subaru: move non-obd requests out of logging (#31174) enable --- selfdrive/car/subaru/values.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 80251e7e7e..9fb4b45686 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -162,14 +162,12 @@ FW_QUERY_CONFIG = FwQueryConfig( [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission], bus=0, - logging=True, ), Request( [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission], bus=1, - logging=True, obd_multiplexing=False, ), ], From be4107dc8a79ea017baf05d5055a0d59d3a965e8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 27 Jan 2024 20:31:50 -0600 Subject: [PATCH 606/724] Fingerprints: add missing FW versions from users for Toyota, Honda, Chrysler [bot] (#31193) Export fingerprints --- selfdrive/car/honda/fingerprints.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 0f217afb3c..437c9b0381 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -156,6 +156,7 @@ FW_VERSIONS = { b'36802-TVA-A150\x00\x00', b'36802-TVA-A160\x00\x00', b'36802-TVA-A170\x00\x00', + b'36802-TVA-A180\x00\x00', b'36802-TVA-A330\x00\x00', b'36802-TVC-A330\x00\x00', b'36802-TVE-H070\x00\x00', @@ -831,6 +832,7 @@ FW_VERSIONS = { b'38897-THR-A020\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-5MR-3250\x00\x00', b'37805-5MR-4080\x00\x00', b'37805-5MR-4180\x00\x00', b'37805-5MR-A240\x00\x00', @@ -980,6 +982,7 @@ FW_VERSIONS = { b'37805-RLV-C530\x00\x00', b'37805-RLV-C910\x00\x00', b'37805-RLV-F120\x00\x00', + b'37805-RLV-L080\x00\x00', b'37805-RLV-L090\x00\x00', b'37805-RLV-L160\x00\x00', b'37805-RLV-L180\x00\x00', @@ -1102,6 +1105,7 @@ FW_VERSIONS = { (Ecu.combinationMeter, 0x18da60f1, None): [ b'78109-TX4-A210\x00\x00', b'78109-TX4-A310\x00\x00', + b'78109-TX5-A210\x00\x00', b'78109-TX5-A310\x00\x00', ], }, From 58ef40eb2264a9d62a1715486677c342d5d113ce Mon Sep 17 00:00:00 2001 From: garrettpall <76917194+garrettpall@users.noreply.github.com> Date: Sat, 27 Jan 2024 23:56:10 -0500 Subject: [PATCH 607/724] GM: Add BSM from Powertrain Bus (#30861) * Add GM BSM * Move BSM check to fwdCam only * Detect if BSM message if present * Remove hardcoding * Apply Commit Suggestion Co-authored-by: Shane Smiskol * Cleanup Suggestions * bump opendbc * test out bsm * BCMBlindSpotMonitors -> BCMBlindSpotMonitor * Revert "test out bsm" This reverts commit 0e166530e60165269c1be200f48ead9918d5fe96. * up here --------- Co-authored-by: Shane Smiskol --- opendbc | 2 +- selfdrive/car/gm/carstate.py | 7 +++++++ selfdrive/car/gm/interface.py | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/opendbc b/opendbc index f4b1061ae0..3cfd0bf4eb 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit f4b1061ae03994013809dc4a03ac571bfeda5714 +Subproject commit 3cfd0bf4eb73953f3d179dddc1ba2c92e317188c diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 89c1a3596a..f7309939b1 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -113,6 +113,10 @@ class CarState(CarStateBase): if self.CP.pcmCruise: ret.cruiseState.nonAdaptive = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCruiseState"] not in (2, 3) + if self.CP.enableBsm: + ret.leftBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["LeftBSM"] == 1 + ret.rightBlindspot = pt_cp.vl["BCMBlindSpotMonitor"]["RightBSM"] == 1 + return ret @staticmethod @@ -146,6 +150,9 @@ class CarState(CarStateBase): ("ECMAcceleratorPos", 80), ] + if CP.enableBsm: + messages.append(("BCMBlindSpotMonitor", 10)) + # Used to read back last counter sent to PT by camera if CP.networkLocation == NetworkLocation.fwdCamera: messages += [ diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index a0defb7cfb..af34100d74 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -72,6 +72,7 @@ class CarInterface(CarInterfaceBase): ret.carName = "gm" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)] ret.autoResumeSng = False + ret.enableBsm = 0x142 in fingerprint[CanBus.POWERTRAIN] if candidate in EV_CAR: ret.transmissionType = TransmissionType.direct From a3eb61b7a3262bc48cf7d94cbd80ae64c99a654a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 27 Jan 2024 23:25:14 -0800 Subject: [PATCH 608/724] update refs --- selfdrive/test/process_replay/ref_commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index a8d0976b57..803f4e96ee 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -0dffa4e5634108f41d140c74052c38059038abd0 +58ef40eb2264a9d62a1715486677c342d5d113ce \ No newline at end of file From 2a006f59a52f462b0c49b174a16dd4d09cc8bd70 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 28 Jan 2024 17:53:55 -0600 Subject: [PATCH 609/724] Fingerprints: add missing FW versions from users for Toyota, Honda, Chrysler [bot] (#31196) Export fingerprints --- selfdrive/car/toyota/fingerprints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 2ecd780843..90ad19ee4a 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -1267,6 +1267,7 @@ FW_VERSIONS = { b'F152633171\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ + b'881513309400\x00\x00\x00\x00', b'881513309500\x00\x00\x00\x00', b'881513310400\x00\x00\x00\x00', ], @@ -1281,6 +1282,7 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'8646F3302001\x00\x00\x00\x00', + b'8646F3302100\x00\x00\x00\x00', b'8646F3302200\x00\x00\x00\x00', ], }, From 27c624550f424a9c33744ea415d2da60d361b399 Mon Sep 17 00:00:00 2001 From: taggahertz Date: Sun, 28 Jan 2024 17:52:29 -0800 Subject: [PATCH 610/724] Updated Lexus ES hybrid MY (#31181) * Updated Lexus ES hybrid MY Car 2024 Lexus ES 300h Segment c52c524e1b873247|2024-01-19--12-11-57--0 Route https://connect.comma.ai/c52c524e1b873247/1705695117754/1705695298920 * Update CARS.md fixed url link --- docs/CARS.md | 2 +- selfdrive/car/toyota/values.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 92b8dd952d..97de2df8fe 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -157,7 +157,7 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|ES Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Lexus|ES Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 4f6e55b5d3..02a9e142d2 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -193,7 +193,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ], CAR.LEXUS_ES_TSS2: [ ToyotaCarInfo("Lexus ES 2019-24"), - ToyotaCarInfo("Lexus ES Hybrid 2019-23", video_link="https://youtu.be/BZ29osRVJeg?t=12"), + ToyotaCarInfo("Lexus ES Hybrid 2019-24", video_link="https://youtu.be/BZ29osRVJeg?t=12"), ], CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), CAR.LEXUS_IS_TSS2: ToyotaCarInfo("Lexus IS 2022-23"), From 36d6bb3bda64286bd5db05e85a1cc300bb8bca6a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 28 Jan 2024 21:23:53 -0600 Subject: [PATCH 611/724] Car docs: update model years from users for Toyota, Honda, Chrysler [bot] (#31203) Update car docs --- docs/CARS.md | 2 +- selfdrive/car/honda/values.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 97de2df8fe..5620bc703c 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -61,7 +61,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Civic Hatchback 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|CR-V Hybrid 2017-20|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index fefce7ff9c..1d4a174b9b 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -136,7 +136,7 @@ CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS), CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS), CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring - CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-20", min_steer_speed=12. * CV.MPH_TO_MS), CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS), CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS), CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS), From 80db6f0e28aa9ca55488c991d62bed65986624db Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 29 Jan 2024 03:11:28 -0600 Subject: [PATCH 612/724] Fingerprints: add missing FW versions from users for Toyota, Honda, Chrysler [bot] (#31205) Export fingerprints --- selfdrive/car/chrysler/fingerprints.py | 4 ++++ selfdrive/car/honda/fingerprints.py | 1 + selfdrive/car/toyota/fingerprints.py | 2 ++ 3 files changed, 7 insertions(+) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 394f9c5132..ae1449422e 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -393,6 +393,7 @@ FW_VERSIONS = { b'68527382AE', b'68527383AD', b'68527387AE', + b'68527403AC', b'68631942AA', ], (Ecu.srs, 0x744, None): [ @@ -455,6 +456,7 @@ FW_VERSIONS = { b'68552789AA', b'68552790AA', b'68552791AB', + b'68552794AA', b'68585106AB', b'68585108AB', b'68585109AB', @@ -462,6 +464,7 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7e0, None): [ b'05035699AG ', + b'05036026AB ', b'05036065AE ', b'05036066AE ', b'05149591AD ', @@ -506,6 +509,7 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'05035706AD', + b'05035842AB', b'05036069AA', b'05149536AC', b'05149537AC', diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 437c9b0381..0a64a73d72 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -832,6 +832,7 @@ FW_VERSIONS = { b'38897-THR-A020\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ + b'37805-5MR-3050\x00\x00', b'37805-5MR-3250\x00\x00', b'37805-5MR-4080\x00\x00', b'37805-5MR-4180\x00\x00', diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index 90ad19ee4a..fc24f0e72c 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -617,6 +617,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\x0230E40000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230E40100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x0230E51000\x00\x00\x00\x00\x00\x00\x00\x0050E17000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230EA2000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x0230EA2100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -1441,6 +1442,7 @@ FW_VERSIONS = { b'\x02348N0000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348Q4000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348Q4100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02348T1000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348T1100\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348T1200\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02348T3000\x00\x00\x00\x00\x00\x00\x00\x00A4802000\x00\x00\x00\x00\x00\x00\x00\x00', From fd0b9cd9301da9962d933e69939f34d0ffef343d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 29 Jan 2024 11:38:06 -0600 Subject: [PATCH 613/724] Fingerprints: add missing FW versions from users for Toyota, Honda, Chrysler [bot] (#31210) --- selfdrive/car/chrysler/fingerprints.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index ae1449422e..6461ceec4d 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -390,6 +390,7 @@ FW_VERSIONS = { b'68527346AE', b'68527361AD', b'68527375AD', + b'68527381AE', b'68527382AE', b'68527383AD', b'68527387AE', @@ -467,6 +468,7 @@ FW_VERSIONS = { b'05036026AB ', b'05036065AE ', b'05036066AE ', + b'05149368AA ', b'05149591AD ', b'05149591AE ', b'05149592AE ', @@ -533,6 +535,7 @@ FW_VERSIONS = { b'68502994AD', b'68520867AE', b'68520867AF', + b'68520870AC', b'68540431AB', b'68540433AB', b'68629936AC', From 5f3a425015fd82899cb876014e7d4f8b568e751d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 29 Jan 2024 09:43:18 -0800 Subject: [PATCH 614/724] Update Python packages and pre-commit hooks (#31208) Co-authored-by: jnewb1 --- poetry.lock | 171 ++++++++++++++++++++++++++-------------------------- 1 file changed, 86 insertions(+), 85 deletions(-) diff --git a/poetry.lock b/poetry.lock index a289470d47..4cd6f5062e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,87 +2,87 @@ [[package]] name = "aiohttp" -version = "3.9.1" +version = "3.9.2" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, - {file = "aiohttp-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f"}, - {file = "aiohttp-3.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d"}, - {file = "aiohttp-3.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501"}, - {file = "aiohttp-3.9.1-cp310-cp310-win32.whl", hash = "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489"}, - {file = "aiohttp-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e"}, - {file = "aiohttp-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d"}, - {file = "aiohttp-3.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd"}, - {file = "aiohttp-3.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a"}, - {file = "aiohttp-3.9.1-cp311-cp311-win32.whl", hash = "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544"}, - {file = "aiohttp-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821"}, - {file = "aiohttp-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c"}, - {file = "aiohttp-3.9.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f"}, - {file = "aiohttp-3.9.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f"}, - {file = "aiohttp-3.9.1-cp312-cp312-win32.whl", hash = "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed"}, - {file = "aiohttp-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672"}, - {file = "aiohttp-3.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361"}, - {file = "aiohttp-3.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a"}, - {file = "aiohttp-3.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8"}, - {file = "aiohttp-3.9.1-cp38-cp38-win32.whl", hash = "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4"}, - {file = "aiohttp-3.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0"}, - {file = "aiohttp-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f"}, - {file = "aiohttp-3.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f"}, - {file = "aiohttp-3.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c"}, - {file = "aiohttp-3.9.1-cp39-cp39-win32.whl", hash = "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7"}, - {file = "aiohttp-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf"}, - {file = "aiohttp-3.9.1.tar.gz", hash = "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"}, + {file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"}, + {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"}, + {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"}, + {file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"}, + {file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"}, + {file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"}, + {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"}, + {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"}, + {file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"}, + {file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"}, + {file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"}, + {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"}, + {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"}, + {file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"}, + {file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"}, + {file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"}, + {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"}, + {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"}, + {file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"}, + {file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"}, + {file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"}, + {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"}, + {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"}, + {file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"}, + {file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"}, + {file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"}, ] [package.dependencies] @@ -6449,20 +6449,20 @@ cp2110 = ["hidapi"] [[package]] name = "pytest" -version = "7.4.4" +version = "8.0.0" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, + {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" +pluggy = ">=1.3.0,<2.0" [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] @@ -6630,13 +6630,13 @@ files = [ [[package]] name = "pytz" -version = "2023.3.post1" +version = "2023.4" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, + {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, + {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, ] [[package]] @@ -6727,6 +6727,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, From bbeeea04a73ff3b21ab4dfe6b2777453c6a4e9e9 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 29 Jan 2024 13:02:11 -0500 Subject: [PATCH 615/724] CI: fix bot location (#31211) g --- .github/workflows/repo-maintenance.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 26b1192efa..bd882210fa 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -25,11 +25,11 @@ jobs: with: token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} commit-message: bump submodules - title: 'Bump submodules' + title: '[bot] Bump submodules' branch: auto-bump-submodules base: master delete-branch: true - body: '[bot] Automatic PR from repo-maintenance -> bump_submodules' + body: 'Automatic PR from repo-maintenance -> bump_submodules' labels: bot package_updates: name: package_updates @@ -51,9 +51,9 @@ jobs: with: token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} commit-message: Update Python packages and pre-commit hooks - title: 'Update Python packages and pre-commit hooks' + title: '[bot] Update Python packages and pre-commit hooks' branch: auto-package-updates base: master delete-branch: true - body: '[bot] Automatic PR from repo-maintenance -> package_updates' + body: 'Automatic PR from repo-maintenance -> package_updates' labels: bot From 75d3e5fb4e16c0407cbf7a9b4df08939fa0fe660 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 29 Jan 2024 13:10:55 -0500 Subject: [PATCH 616/724] Webcam: prepare to make camerad a PythonProcess (#31202) * webcam * support /dev/* syntax * support strings --- tools/webcam/camera.py | 4 ++++ tools/webcam/camerad.py | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/webcam/camera.py b/tools/webcam/camera.py index 22438f0c98..d1d61b64d7 100644 --- a/tools/webcam/camera.py +++ b/tools/webcam/camera.py @@ -3,6 +3,10 @@ import numpy as np class Camera: def __init__(self, cam_type_state, stream_type, camera_id): + try: + camera_id = int(camera_id) + except ValueError: # allow strings, ex: /dev/video0 + pass self.cam_type_state = cam_type_state self.stream_type = stream_type self.cur_frame_id = 0 diff --git a/tools/webcam/camerad.py b/tools/webcam/camerad.py index caf044e7b6..ce33473f9a 100755 --- a/tools/webcam/camerad.py +++ b/tools/webcam/camerad.py @@ -25,7 +25,7 @@ class Camerad: self.cameras = [] for c in CAMERAS: - cam = Camera(c.msg_name, c.stream_type, int(c.cam_id)) + cam = Camera(c.msg_name, c.stream_type, c.cam_id) assert cam.cap.isOpened(), f"Can't find camera {c}" self.cameras.append(cam) self.vipc_server.create_buffers(c.stream_type, 20, False, cam.W, cam.H) @@ -63,6 +63,11 @@ class Camerad: for t in threads: t.join() -if __name__ == "__main__": + +def main(): camerad = Camerad() camerad.run() + + +if __name__ == "__main__": + main() From e49eb4c54fcac8bd8a4a136d8e116b654efa73ba Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 29 Jan 2024 13:11:02 -0500 Subject: [PATCH 617/724] Simulator: prepare to make bridge a PythonProcess (#31201) * sim services * clean --- tools/sim/bridge/common.py | 6 +++--- .../sim/bridge/metadrive/metadrive_bridge.py | 4 ++-- tools/sim/run_bridge.py | 21 +++++++++++++------ 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/tools/sim/bridge/common.py b/tools/sim/bridge/common.py index 35656f39f3..bdd53852c0 100644 --- a/tools/sim/bridge/common.py +++ b/tools/sim/bridge/common.py @@ -27,14 +27,14 @@ def rk_loop(function, hz, exit_event: threading.Event): class SimulatorBridge(ABC): TICKS_PER_FRAME = 5 - def __init__(self, arguments): + def __init__(self, dual_camera, high_quality): set_params_enabled() self.params = Params() self.rk = Ratekeeper(100, None) - self.dual_camera = arguments.dual_camera - self.high_quality = arguments.high_quality + self.dual_camera = dual_camera + self.high_quality = high_quality self._exit_event = threading.Event() self._threads = [] diff --git a/tools/sim/bridge/metadrive/metadrive_bridge.py b/tools/sim/bridge/metadrive/metadrive_bridge.py index 821af7c7f7..1b1e5ffea6 100644 --- a/tools/sim/bridge/metadrive/metadrive_bridge.py +++ b/tools/sim/bridge/metadrive/metadrive_bridge.py @@ -82,10 +82,10 @@ def create_map(track_size=60): class MetaDriveBridge(SimulatorBridge): TICKS_PER_FRAME = 5 - def __init__(self, args): + def __init__(self, dual_camera, high_quality): self.should_render = False - super(MetaDriveBridge, self).__init__(args) + super(MetaDriveBridge, self).__init__(dual_camera, high_quality) def spawn_world(self): sensors = { diff --git a/tools/sim/run_bridge.py b/tools/sim/run_bridge.py index 5f0f0d5e99..0992ef4bfe 100755 --- a/tools/sim/run_bridge.py +++ b/tools/sim/run_bridge.py @@ -6,6 +6,17 @@ from multiprocessing import Queue from openpilot.tools.sim.bridge.metadrive.metadrive_bridge import MetaDriveBridge +def create_bridge(dual_camera, high_quality): + queue: Any = Queue() + + simulator_bridge = MetaDriveBridge(dual_camera, high_quality) + simulator_process = simulator_bridge.run(queue) + + return queue, simulator_process, simulator_bridge + +def main(): + _, simulator_process, _ = create_bridge(True, False) + simulator_process.join() def parse_args(add_args=None): parser = argparse.ArgumentParser(description='Bridge between the simulator and openpilot.') @@ -16,23 +27,21 @@ def parse_args(add_args=None): return parser.parse_args(add_args) if __name__ == "__main__": - q: Any = Queue() args = parse_args() - simulator_bridge = MetaDriveBridge(args) - p = simulator_bridge.run(q) + queue, simulator_process, simulator_bridge = create_bridge(args.dual_camera, args.high_quality) if args.joystick: # start input poll for joystick from openpilot.tools.sim.lib.manual_ctrl import wheel_poll_thread - wheel_poll_thread(q) + wheel_poll_thread(queue) else: # start input poll for keyboard from openpilot.tools.sim.lib.keyboard_ctrl import keyboard_poll_thread - keyboard_poll_thread(q) + keyboard_poll_thread(queue) simulator_bridge.shutdown() - p.join() + simulator_process.join() From 0b2442314d25adc7aa4f885a6b46299264d1da2d Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 29 Jan 2024 13:22:59 -0500 Subject: [PATCH 618/724] CI: increase notebook cache build timeout (#31212) allow more time --- .github/workflows/tools_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index e141071d09..c185fd1d3c 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -101,7 +101,7 @@ jobs: submodules: true - uses: ./.github/workflows/setup-with-retry - name: Build openpilot - timeout-minutes: 5 + timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache run: ${{ env.RUN }} "scons -j$(nproc)" - name: Test notebooks timeout-minutes: 2 From ed7cfc23430eb9e9c7cdfb6ec3a6dc86c3be0709 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 29 Jan 2024 12:37:35 -0800 Subject: [PATCH 619/724] [bot] Bump submodules (#31207) bump submodules Co-authored-by: jnewb1 Co-authored-by: Justin Newberry --- cereal | 2 +- panda | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cereal b/cereal index 775e7f84da..a6ade85c9d 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 775e7f84da5eb283d604e94f9bf4582ffa741c22 +Subproject commit a6ade85c9dd6652fde547b9e089a297f67606dcf diff --git a/panda b/panda index c5e79403a8..266d4573b7 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit c5e79403a8c49f5a36e3ede44fcda65fabe263bf +Subproject commit 266d4573b79802e5ad67b5558ace089417c64106 From 7835f9cce05b2f8743bbd632dd6a3093741e6bd2 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 29 Jan 2024 16:18:49 -0500 Subject: [PATCH 620/724] LogReader: add tqdm back to run_across_segments (#31216) tqdm --- tools/lib/logreader.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index e86690f3c3..192ee8e6f2 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -9,6 +9,7 @@ import os import pathlib import re import sys +import tqdm import urllib.parse import warnings @@ -259,7 +260,8 @@ class LogReader: def run_across_segments(self, num_processes, func): with multiprocessing.Pool(num_processes) as pool: ret = [] - for p in pool.map(partial(self._run_on_segment, func), range(len(self.logreader_identifiers))): + num_segs = len(self.logreader_identifiers) + for p in tqdm.tqdm(pool.imap(partial(self._run_on_segment, func), range(num_segs)), total=num_segs): ret.extend(p) return ret From 50cb6f819d0042d412c08428194a9adeeb42b357 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 29 Jan 2024 17:05:09 -0600 Subject: [PATCH 621/724] selfdrive/debug: view iso-tp communication from logs (#31217) * add * some clean up * more clean up * add * ltl * add todo * rm * use default_mode --------- Co-authored-by: Justin Newberry --- .../debug/debug_fw_fingerprinting_offline.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 selfdrive/debug/debug_fw_fingerprinting_offline.py diff --git a/selfdrive/debug/debug_fw_fingerprinting_offline.py b/selfdrive/debug/debug_fw_fingerprinting_offline.py new file mode 100755 index 0000000000..d521ab2e18 --- /dev/null +++ b/selfdrive/debug/debug_fw_fingerprinting_offline.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +import argparse + +from openpilot.tools.lib.logreader import LogReader, ReadMode +from panda.python import uds + + +def main(route: str, addrs: list[int]): + """ + TODO: + - highlight TX vs RX clearly + - disambiguate sendcan and can (useful to know if something sent on sendcan made it to the bus on can->128) + - print as fixed width table, easier to read + """ + + lr = LogReader(route, default_mode=ReadMode.RLOG) + + start_mono_time = None + prev_mono_time = 0 + + # include rx addresses + addrs = addrs + [uds.get_rx_addr_for_tx_addr(addr) for addr in addrs] + + for msg in lr: + if msg.which() == 'can': + if start_mono_time is None: + start_mono_time = msg.logMonoTime + + if msg.which() in ("can", 'sendcan'): + for can in getattr(msg, msg.which()): + if can.address in addrs: + if msg.logMonoTime != prev_mono_time: + print() + prev_mono_time = msg.logMonoTime + print(f"{msg.logMonoTime} rxaddr={can.address}, bus={can.src}, {round((msg.logMonoTime - start_mono_time) * 1e-6, 2)} ms, " + + f"0x{can.dat.hex()}, {can.dat}, {len(can.dat)=}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='View back and forth ISO-TP communication between various ECUs given an address') + parser.add_argument('route', help='Route name') + parser.add_argument('addrs', nargs='*', help='List of tx address to view (0x7e0 for engine)') + args = parser.parse_args() + + addrs = [int(addr, base=16) if addr.startswith('0x') else int(addr) for addr in args.addrs] + main(args.route, addrs) From e86aafc723479bcdb87b27586f90b1d08b9fc334 Mon Sep 17 00:00:00 2001 From: Andrei Tanas Date: Mon, 29 Jan 2024 19:07:56 -0500 Subject: [PATCH 622/724] Add 2022 Subaru Outback IDs to subaru/fingerprints.py (#31016) --- selfdrive/car/subaru/fingerprints.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py index aaee2e1c71..ad8ebe87cd 100644 --- a/selfdrive/car/subaru/fingerprints.py +++ b/selfdrive/car/subaru/fingerprints.py @@ -451,6 +451,7 @@ FW_VERSIONS = { b'\xa1 \x06\x02', b'\xa1 \x07\x00', b'\xa1 \x07\x02', + b'\xa1 \x07\x03', b'\xa1 \x08\x00', b'\xa1 \x08\x01', b'\xa1 \x08\x02', @@ -460,6 +461,7 @@ FW_VERSIONS = { (Ecu.eps, 0x746, None): [ b'\x1b\xc0\x10\x00', b'\x9b\xc0\x10\x00', + b'\x9b\xc0\x10\x02', b'\x9b\xc0 \x00', ], (Ecu.fwdCamera, 0x787, None): [ @@ -478,6 +480,7 @@ FW_VERSIONS = { b'\xde,\xa0@\x07', b'\xe2"`0\x07', b'\xe2"`p\x07', + b'\xe2"`q\x07', b'\xe3,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ From 54fe19206eb3b25432f8ddaf7f39b996c07dbdce Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 29 Jan 2024 19:48:03 -0500 Subject: [PATCH 623/724] Subaru: eps is non-essential for gen2 cars (#31220) * eps non essential * comment * suggestions --- selfdrive/car/subaru/values.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 9fb4b45686..d9d2f78cea 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -137,6 +137,15 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { CAR.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), } +LKAS_ANGLE = {CAR.FORESTER_2022, CAR.OUTBACK_2023, CAR.ASCENT_2023} +GLOBAL_GEN2 = {CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023, CAR.ASCENT_2023} +PREGLOBAL_CARS = {CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018} +HYBRID_CARS = {CAR.CROSSTREK_HYBRID, CAR.FORESTER_HYBRID} + +# Cars that temporarily fault when steering angle rate is greater than some threshold. +# Appears to be all torque-based cars produced around 2019 - present +STEER_RATE_LIMITED = GLOBAL_GEN2 | {CAR.IMPREZA_2020, CAR.FORESTER} + SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ @@ -171,6 +180,10 @@ FW_QUERY_CONFIG = FwQueryConfig( obd_multiplexing=False, ), ], + # We don't get the EPS from non-OBD queries on GEN2 cars. Note that we still attempt to match when it exists + non_essential_ecus={ + Ecu.eps: list(GLOBAL_GEN2), + } ) DBC = { @@ -190,12 +203,3 @@ DBC = { CAR.OUTBACK_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None), CAR.OUTBACK_PREGLOBAL_2018: dbc_dict('subaru_outback_2019_generated', None), } - -LKAS_ANGLE = {CAR.FORESTER_2022, CAR.OUTBACK_2023, CAR.ASCENT_2023} -GLOBAL_GEN2 = {CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023, CAR.ASCENT_2023} -PREGLOBAL_CARS = {CAR.FORESTER_PREGLOBAL, CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018} -HYBRID_CARS = {CAR.CROSSTREK_HYBRID, CAR.FORESTER_HYBRID} - -# Cars that temporarily fault when steering angle rate is greater than some threshold. -# Appears to be all torque-based cars produced around 2019 - present -STEER_RATE_LIMITED = GLOBAL_GEN2 | {CAR.IMPREZA_2020, CAR.FORESTER} From ba7f840a06dbc8ae3c45b3b4976c88a21895aed0 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Mon, 29 Jan 2024 16:52:30 -0800 Subject: [PATCH 624/724] DM: Quarter Pounder Deluxe model (#31170) * 1 * pg This reverts commit 71409ec84a3e04e5e7ec960271d4e278a2472462. * ee new * 60ab * update replay --- selfdrive/modeld/models/dmonitoring_model.current | 4 ++-- selfdrive/modeld/models/dmonitoring_model.onnx | 4 ++-- selfdrive/modeld/models/dmonitoring_model_q.dlc | 4 ++-- selfdrive/monitoring/driver_monitor.py | 6 +++--- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/modeld/models/dmonitoring_model.current b/selfdrive/modeld/models/dmonitoring_model.current index 065bc7d489..ed495df3a8 100644 --- a/selfdrive/modeld/models/dmonitoring_model.current +++ b/selfdrive/modeld/models/dmonitoring_model.current @@ -1,2 +1,2 @@ -d1124586-761e-4e18-a771-6b5ef35124fe -6fec774f513a19e44d4316e46ad38277197d45ea \ No newline at end of file +60abed33-9f25-4e34-9937-aaf918d41dfc +50887963963a3022e85ac0c94b0801aef955a608 \ No newline at end of file diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index f8bf94c061..3e94e7d36a 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:517262fa9f1ad3cc8049ad3722903f40356d87ea423ee5cf011226fb6cfc3d5b -size 16072278 +oid sha256:9a667cbde6eca86c5b653f57853e3d33b9a875bceb557e193d1bef78c2df9c37 +size 16132779 diff --git a/selfdrive/modeld/models/dmonitoring_model_q.dlc b/selfdrive/modeld/models/dmonitoring_model_q.dlc index e4e6fb3347..041949ad94 100644 --- a/selfdrive/modeld/models/dmonitoring_model_q.dlc +++ b/selfdrive/modeld/models/dmonitoring_model_q.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64b94659226a1e3c6594a13c2e5d029465d5803a5c3005121ec7217acdbbef20 -size 4443461 +oid sha256:f5729c457dd47f6c35e2a818eae14113526fd0bfac6417a809cbaf9d38697d9b +size 4488413 diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index ab82da301f..2279002f35 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -29,10 +29,10 @@ class DRIVER_MONITOR_SETTINGS(): self._FACE_THRESHOLD = 0.7 self._EYE_THRESHOLD = 0.65 self._SG_THRESHOLD = 0.9 - self._BLINK_THRESHOLD = 0.895 + self._BLINK_THRESHOLD = 0.865 - self._EE_THRESH11 = 0.275 - self._EE_THRESH12 = 5.5 + self._EE_THRESH11 = 0.241 + self._EE_THRESH12 = 4.7 self._EE_MAX_OFFSET1 = 0.06 self._EE_MIN_OFFSET1 = 0.025 self._EE_THRESH21 = 0.01 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 2e78b1e608..9ec78e1401 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -4b983afeba5ec953f0d37e7b3499bde5209728eb +4a01784a6b83a49301a68adf52bb7dcfcb7173b5 From c782fbc34e05922765959ee3c3b6bba38c0150fb Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Mon, 29 Jan 2024 20:33:47 -0500 Subject: [PATCH 625/724] LogReader: add ```.filter``` (#31222) add filter --- tools/lib/logreader.py | 6 ++++-- tools/lib/tests/test_logreader.py | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 192ee8e6f2..c53b9c4c79 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -275,9 +275,11 @@ are uploaded or auto fallback to qlogs with '/a' selector at the end of the rout def from_bytes(dat): return _LogFileReader("", dat=dat) + def filter(self, msg_type: str): + return (getattr(m, m.which()) for m in filter(lambda m: m.which() == msg_type, self)) + def first(self, msg_type: str): - m = next(filter(lambda m: m.which() == msg_type, self), None) - return None if m is None else getattr(m, msg_type) + return next(self.filter(msg_type), None) if __name__ == "__main__": diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index d8a9c14088..676d2bbadf 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -118,6 +118,12 @@ class TestLogReader(unittest.TestCase): self.assertEqual(qlog_len1, qlog_len2) + @pytest.mark.slow + def test_helpers(self): + lr = LogReader(f"{TEST_ROUTE}/0/q") + self.assertEqual(lr.first("carParams").carFingerprint, "SUBARU OUTBACK 6TH GEN") + self.assertTrue(0 < len(list(lr.filter("carParams"))) < len(list(lr))) + if __name__ == "__main__": unittest.main() From 6caf89bca531b2e1e5f514dcebd82e33cb0dadd1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 29 Jan 2024 17:44:14 -0800 Subject: [PATCH 626/724] agnos 9.3 (#31163) * agnos 9.2 * agnos 9.3 --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index c16f061912..d86ec63593 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="9.1" + export AGNOS_VERSION="9.3" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 3fe3942305..a87f3d278d 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -1,9 +1,9 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275.img.xz", - "hash": "fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275", - "hash_raw": "fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275", + "url": "https://commadist.azureedge.net/agnosupdate/boot-1cc21f31a7c09772fd759e6f2a614974bf4f2fc320c91a799ffadd11abc1f85f.img.xz", + "hash": "1cc21f31a7c09772fd759e6f2a614974bf4f2fc320c91a799ffadd11abc1f85f", + "hash_raw": "1cc21f31a7c09772fd759e6f2a614974bf4f2fc320c91a799ffadd11abc1f85f", "size": 15636480, "sparse": false, "full_check": true, @@ -11,9 +11,9 @@ }, { "name": "abl", - "url": "https://commadist.azureedge.net/agnosupdate/abl-bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a.img.xz", - "hash": "bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a", - "hash_raw": "bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a", + "url": "https://commadist.azureedge.net/agnosupdate/abl-eeb89a74c968a5a2ffce96f23158b72e03e2814adf72ef59d1200ba8ea5d2f39.img.xz", + "hash": "eeb89a74c968a5a2ffce96f23158b72e03e2814adf72ef59d1200ba8ea5d2f39", + "hash_raw": "eeb89a74c968a5a2ffce96f23158b72e03e2814adf72ef59d1200ba8ea5d2f39", "size": 274432, "sparse": false, "full_check": true, @@ -61,17 +61,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98.img.xz", - "hash": "3b6cdf9bd881a5e90b21dd02c6faa923b415e32ecae9bfdc96753d4208fb82fe", - "hash_raw": "e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98", + "url": "https://commadist.azureedge.net/agnosupdate/system-38402b90b65729f8a4feb729c8a862cdf306659a85f27431d3ff7e52d4027082.img.xz", + "hash": "5dc1718e21c49e4fa910fbb3b2321381f497b38335a0cf3ca923157d589abe89", + "hash_raw": "38402b90b65729f8a4feb729c8a862cdf306659a85f27431d3ff7e52d4027082", "size": 10737418240, "sparse": true, "full_check": false, "has_ab": true, "alt": { - "hash": "2fb81e58f4bc6c4e5e71c8e7ac7553f85082c430627d7a5cc54a6bbc82862500", - "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98.img.xz", - "size": 4543090376 + "hash": "1809e36d8e376e0a0c8348e3f684aba4100fe0382042c051efd0e946af1ce696", + "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-38402b90b65729f8a4feb729c8a862cdf306659a85f27431d3ff7e52d4027082.img.xz", + "size": 4077270244 } } ] \ No newline at end of file From 4243e9322a9da239acd7e2c33c7b491b53ad6d1b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 29 Jan 2024 17:49:37 -0800 Subject: [PATCH 627/724] update release notes --- RELEASES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 36a4a89dd2..aa3a80d56d 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,10 +1,14 @@ -Version 0.9.6 (20XX-XX-XX) +Version 0.9.6 (2024-02-XX) ======================== * New driving model * Vision model trained on more data * Improved driving performance + * Directly outputs curvature for lateral control +* New driver monitoring model + * Trained on larger dataset * AGNOS 9 * comma body streaming and controls over WebRTC +* Improved fuzzy fingerprinting for many makes and models * Hyundai Staria 2023 support thanks to sunnyhaibin! * Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! * Toyota RAV4 2023-24 support From 3a0033e81eec20f640c0d9be57b7f7da9a97d99d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 29 Jan 2024 23:28:29 -0600 Subject: [PATCH 628/724] vin: fix retry logging (#31225) * retry should really be outside inner query loop * just req * nls * bump * rmnl * useful to have * is it though --- selfdrive/car/fw_query_definitions.py | 3 +++ selfdrive/car/vin.py | 13 ++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index 410040b10e..fd49fa8e1a 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -14,6 +14,9 @@ EcuAddrSubAddr = Tuple[int, int, Optional[int]] LiveFwVersions = Dict[AddrType, Set[bytes]] OfflineFwVersions = Dict[str, Dict[EcuAddrSubAddr, List[bytes]]] +STANDARD_VIN_ADDRS = [0x7e0, 0x7e2, 0x18da10f1, 0x18da0ef1] # engine, VMCU, 29-bit engine, PGM-FI + + def p16(val): return struct.pack("!H", val) diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index ba081764b5..400470df50 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -4,7 +4,7 @@ import re import cereal.messaging as messaging from panda.python.uds import get_rx_addr_for_tx_addr, FUNCTIONAL_ADDRS from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery -from openpilot.selfdrive.car.fw_query_definitions import StdQueries +from openpilot.selfdrive.car.fw_query_definitions import STANDARD_VIN_ADDRS, StdQueries from openpilot.common.swaglog import cloudlog VIN_UNKNOWN = "0" * 17 @@ -16,16 +16,14 @@ def is_valid_vin(vin: str): def get_vin(logcan, sendcan, buses, timeout=0.1, retry=3, debug=False): - addrs = list(range(0x7e0, 0x7e8)) + list(range(0x18DA00F1, 0x18DB00F1, 0x100)) # addrs to process/wait for - valid_vin_addrs = [0x7e0, 0x7e2, 0x18da10f1, 0x18da0ef1] # engine, VMCU, 29-bit engine, PGM-FI for i in range(retry): for bus in buses: for request, response in ((StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE), (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE)): try: - query = IsoTpParallelQuery(sendcan, logcan, bus, addrs, [request, ], [response, ], functional_addrs=FUNCTIONAL_ADDRS, debug=debug) + query = IsoTpParallelQuery(sendcan, logcan, bus, STANDARD_VIN_ADDRS, [request, ], [response, ], functional_addrs=FUNCTIONAL_ADDRS, debug=debug) results = query.get_data(timeout) - for addr in valid_vin_addrs: + for addr in STANDARD_VIN_ADDRS: vin = results.get((addr, None)) if vin is not None: # Ford pads with null bytes @@ -36,12 +34,13 @@ def get_vin(logcan, sendcan, buses, timeout=0.1, retry=3, debug=False): if vin.startswith(b'\x11'): vin = vin[1:18] + cloudlog.warning(f"got vin with {request=}") return get_rx_addr_for_tx_addr(addr), bus, vin.decode() - - cloudlog.error(f"vin query retry ({i+1}) ...") except Exception: cloudlog.exception("VIN query exception") + cloudlog.error(f"vin query retry ({i+1}) ...") + return -1, -1, VIN_UNKNOWN From a4cf7745e845e9cbb7e4d11db9f2bf88a51a1682 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 29 Jan 2024 23:44:00 -0600 Subject: [PATCH 629/724] fix manually running fw_versions.py (#31226) --- selfdrive/car/fw_versions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 085131118a..df9b206ac6 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -355,6 +355,13 @@ if __name__ == "__main__": pandaStates_sock = messaging.sub_sock('pandaStates') sendcan = messaging.pub_sock('sendcan') + # Set up params for boardd + params = Params() + params.delete("FirmwareQueryDone") + params.put_bool("IsOnroad", False) + time.sleep(0.2) # thread is 10 Hz + params.put_bool("IsOnroad", True) + extra: Any = None if args.scan: extra = {} @@ -365,7 +372,6 @@ if __name__ == "__main__": extra[(Ecu.unknown, 0x750, i)] = [] extra = {"any": {"debug": extra}} - time.sleep(1.) num_pandas = len(messaging.recv_one_retry(pandaStates_sock).pandaStates) t = time.time() From 5a8686b439d183e56a56e916abaa4cbeb43254a1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 29 Jan 2024 22:36:29 -0800 Subject: [PATCH 630/724] fw_versions: fix params.remove --- selfdrive/car/fw_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index df9b206ac6..9f4b40147b 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -357,7 +357,7 @@ if __name__ == "__main__": # Set up params for boardd params = Params() - params.delete("FirmwareQueryDone") + params.remove("FirmwareQueryDone") params.put_bool("IsOnroad", False) time.sleep(0.2) # thread is 10 Hz params.put_bool("IsOnroad", True) From c3fcf75737b28efe05bf78c4ec886482749844c6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 01:24:21 -0600 Subject: [PATCH 631/724] GM camera ACC: get VIN from camera (#31224) * bump * gm vin * that's not right * only check relevant buses * instead try queries for each bus first * clean up * use default retry in function * all --- panda | 2 +- selfdrive/car/fw_query_definitions.py | 3 +++ selfdrive/car/fw_versions.py | 2 +- selfdrive/car/tests/test_fw_fingerprint.py | 16 +++++++++------- selfdrive/car/vin.py | 14 +++++++++++--- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/panda b/panda index 266d4573b7..ec17f75efc 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 266d4573b79802e5ad67b5558ace089417c64106 +Subproject commit ec17f75efca05c04313049e1d6dd376ef54d42ec diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index fd49fa8e1a..fcd0845e14 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -59,6 +59,9 @@ class StdQueries: UDS_VIN_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + p16(uds.DATA_IDENTIFIER_TYPE.VIN) UDS_VIN_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + p16(uds.DATA_IDENTIFIER_TYPE.VIN) + GM_VIN_REQUEST = b'\x1a\x90' + GM_VIN_RESPONSE = b'\x5a\x90' + @dataclass class Request: diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 9f4b40147b..ab4255a8d2 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -376,7 +376,7 @@ if __name__ == "__main__": t = time.time() print("Getting vin...") - vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (1, 0), retry=10, debug=args.debug) + vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1), retry=10, debug=args.debug) print(f'RX: {hex(vin_rx_addr)}, BUS: {vin_rx_bus}, VIN: {vin}') print(f"Getting VIN took {time.time() - t:.3f} s") print() diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index d640b32ac6..09b05d5166 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -212,7 +212,7 @@ class TestFwFingerprintTiming(unittest.TestCase): def test_startup_timing(self): # Tests worse-case VIN query time and typical present ECU query time - vin_ref_time = 1.2 + vin_ref_times = {'worst': 1.5, 'best': 0.5} # best assumes we go through all queries to get a match present_ecu_ref_time = 0.75 def fake_get_ecu_addrs(*_, timeout): @@ -229,12 +229,14 @@ class TestFwFingerprintTiming(unittest.TestCase): self._assert_timing(self.total_time / self.N, present_ecu_ref_time) print(f'get_present_ecus, query time={self.total_time / self.N} seconds') - self.total_time = 0.0 - with (mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)): - for _ in range(self.N): - get_vin(fake_socket, fake_socket, (0, 1)) - self._assert_timing(self.total_time / self.N, vin_ref_time) - print(f'get_vin, query time={self.total_time / self.N} seconds') + for name, args in (('worst', {}), ('best', {'retry': 1})): + with self.subTest(name=name): + self.total_time = 0.0 + with (mock.patch("openpilot.selfdrive.car.isotp_parallel_query.IsoTpParallelQuery.get_data", self.fake_get_data)): + for _ in range(self.N): + get_vin(fake_socket, fake_socket, (0, 1), **args) + self._assert_timing(self.total_time / self.N, vin_ref_times[name]) + print(f'get_vin {name} case, query time={self.total_time / self.N} seconds') @pytest.mark.timeout(60) def test_fw_query_timing(self): diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 400470df50..f69771546f 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -18,12 +18,20 @@ def is_valid_vin(vin: str): def get_vin(logcan, sendcan, buses, timeout=0.1, retry=3, debug=False): for i in range(retry): for bus in buses: - for request, response in ((StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE), (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE)): + for request, response, valid_buses, vin_addrs, functional_addrs, rx_offset in ( + (StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE, (0, 1), STANDARD_VIN_ADDRS, FUNCTIONAL_ADDRS, 0x8), + (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE, (0, 1), STANDARD_VIN_ADDRS, FUNCTIONAL_ADDRS, 0x8), + (StdQueries.GM_VIN_REQUEST, StdQueries.GM_VIN_RESPONSE, (0,), [0x24b], None, 0x400), # Bolt fwdCamera + ): + if bus not in valid_buses: + continue + try: - query = IsoTpParallelQuery(sendcan, logcan, bus, STANDARD_VIN_ADDRS, [request, ], [response, ], functional_addrs=FUNCTIONAL_ADDRS, debug=debug) + query = IsoTpParallelQuery(sendcan, logcan, bus, vin_addrs, [request, ], [response, ], response_offset=rx_offset, + functional_addrs=functional_addrs, debug=debug) results = query.get_data(timeout) - for addr in STANDARD_VIN_ADDRS: + for addr in vin_addrs: vin = results.get((addr, None)) if vin is not None: # Ford pads with null bytes From 2c0f7b872702e44bb8911702d027d39b01134e6f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 04:08:08 -0600 Subject: [PATCH 632/724] FPv2: support FwQueryConfig with no FW versions (#31227) * bump * from https://github.com/commaai/openpilot/pull/27929 * get VIN on bolt! * might as well try on other gms * remove vin * ugh gm is going to be slow * fix * should really fix this * revert * happy?1 * fix unit test * bump * functional addressing must be an OBD gateway feature, this does nothing * fix vin response * fix addr! * finally fix fw_versions bugs since boardd IsOnroad refactor * for * only bus 0 * clean up * Update selfdrive/car/gm/values.py * ChatGPT re-write * filter out did * todo * oof * preview: what multiple DIDs per ECU would look like in the future * Revert "preview: what multiple DIDs per ECU would look like in the future" This reverts commit 88f0d8611e638de644adc5feabade848c03d59e4. * function to get all ecus * we can remove this! * can also do this! * and this one too :o * consistency * yay * add tests * revert GM stuff * another PR * reads better * revert rest of gm * use that * interesting * these are exactly the same (with ordering differences) * Revert "these are exactly the same (with ordering differences)" This reverts commit a9e918dc351d24f1b7c8a05c7ec402e057a0e09a. * flip --- selfdrive/car/fw_query_definitions.py | 13 +++++ selfdrive/car/fw_versions.py | 64 +++++++++------------- selfdrive/car/tests/test_fw_fingerprint.py | 6 ++ 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index fcd0845e14..e069fad13b 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -96,3 +96,16 @@ class FwQueryConfig: new_request = copy.deepcopy(self.requests[i]) new_request.bus += 4 self.requests.append(new_request) + + def get_all_ecus(self, offline_fw_versions: OfflineFwVersions, include_ecu_type: bool = True, + include_extra_ecus: bool = True) -> set[EcuAddrSubAddr] | set[AddrType]: + # Add ecus in database + extra ecus + brand_ecus = {ecu for ecus in offline_fw_versions.values() for ecu in ecus} + + if include_extra_ecus: + brand_ecus |= set(self.extra_ecus) + + if not include_ecu_type: + return {(addr, subaddr) for _, addr, subaddr in brand_ecus} + + return brand_ecus diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index ab4255a8d2..c9ca249ceb 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -45,16 +45,6 @@ def build_fw_dict(fw_versions: List[capnp.lib.capnp._DynamicStructBuilder], return dict(fw_versions_dict) -def get_brand_addrs() -> Dict[str, Set[AddrType]]: - brand_addrs: DefaultDict[str, Set[AddrType]] = defaultdict(set) - for brand, cars in VERSIONS.items(): - # Add ecus in database + extra ecus to match against - brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in FW_QUERY_CONFIGS[brand].extra_ecus} - for fw in cars.values(): - brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in fw.keys()} - return dict(brand_addrs) - - def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude=None): """Do a fuzzy FW match. This function will return a match, and the number of firmware version that were matched uniquely to that specific car. If multiple ECUs uniquely match to different cars @@ -184,22 +174,21 @@ def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]: if r.bus > num_pandas * 4 - 1: continue - for brand_versions in VERSIONS[brand].values(): - for ecu_type, addr, sub_addr in list(brand_versions) + config.extra_ecus: - # Only query ecus in whitelist if whitelist is not empty - if len(r.whitelist_ecus) == 0 or ecu_type in r.whitelist_ecus: - a = (addr, sub_addr, r.bus) - # Build set of queries - if sub_addr is None: - if a not in parallel_queries[r.obd_multiplexing]: - parallel_queries[r.obd_multiplexing].append(a) - else: # subaddresses must be queried one by one - if [a] not in queries[r.obd_multiplexing]: - queries[r.obd_multiplexing].append([a]) + for ecu_type, addr, sub_addr in config.get_all_ecus(VERSIONS[brand]): + # Only query ecus in whitelist if whitelist is not empty + if len(r.whitelist_ecus) == 0 or ecu_type in r.whitelist_ecus: + a = (addr, sub_addr, r.bus) + # Build set of queries + if sub_addr is None: + if a not in parallel_queries[r.obd_multiplexing]: + parallel_queries[r.obd_multiplexing].append(a) + else: # subaddresses must be queried one by one + if [a] not in queries[r.obd_multiplexing]: + queries[r.obd_multiplexing].append([a]) - # Build set of expected responses to filter - response_addr = uds.get_rx_addr_for_tx_addr(addr, r.rx_offset) - responses.add((response_addr, sub_addr, r.bus)) + # Build set of expected responses to filter + response_addr = uds.get_rx_addr_for_tx_addr(addr, r.rx_offset) + responses.add((response_addr, sub_addr, r.bus)) for obd_multiplexing in queries: queries[obd_multiplexing].insert(0, parallel_queries[obd_multiplexing]) @@ -215,7 +204,8 @@ def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]: def get_brand_ecu_matches(ecu_rx_addrs): """Returns dictionary of brands and matches with ECUs in their FW versions""" - brand_addrs = get_brand_addrs() + brand_addrs = {brand: config.get_all_ecus(VERSIONS[brand], include_ecu_type=False) for + brand, config in FW_QUERY_CONFIGS.items()} brand_matches = {brand: set() for brand, _, _ in REQUESTS} brand_rx_offsets = {(brand, r.rx_offset) for brand, _, r in REQUESTS} @@ -280,19 +270,17 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, for brand, brand_versions in versions.items(): config = FW_QUERY_CONFIGS[brand] - for ecu in brand_versions.values(): - # Each brand can define extra ECUs to query for data collection - for ecu_type, addr, sub_addr in list(ecu) + config.extra_ecus: - a = (brand, addr, sub_addr) - if a not in ecu_types: - ecu_types[a] = ecu_type + for ecu_type, addr, sub_addr in config.get_all_ecus(brand_versions): + a = (brand, addr, sub_addr) + if a not in ecu_types: + ecu_types[a] = ecu_type - if sub_addr is None: - if a not in parallel_addrs: - parallel_addrs.append(a) - else: - if [a] not in addrs: - addrs.append([a]) + if sub_addr is None: + if a not in parallel_addrs: + parallel_addrs.append(a) + else: + if [a] not in addrs: + addrs.append([a]) addrs.insert(0, parallel_addrs) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 09b05d5166..7cd670cf7c 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -147,6 +147,12 @@ class TestFwFingerprint(unittest.TestCase): with self.subTest(): self.fail(f"Brands do not implement FW_QUERY_CONFIG: {brand_versions - brand_configs}") + # Ensure each brand has at least 1 ECU to query, and extra ECU retrieval + for brand, config in FW_QUERY_CONFIGS.items(): + self.assertEqual(len(config.get_all_ecus({}, include_extra_ecus=False)), 0) + self.assertEqual(config.get_all_ecus({}, include_ecu_type=True), set(config.extra_ecus)) + self.assertGreater(len(config.get_all_ecus(VERSIONS[brand])), 0) + def test_fw_request_ecu_whitelist(self): for brand, config in FW_QUERY_CONFIGS.items(): with self.subTest(brand=brand): From 06f0e5096424acd643046f4bd5a3c84a4f684e8e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 04:19:12 -0600 Subject: [PATCH 633/724] GM: FPv2 logging (#31221) * bump * from https://github.com/commaai/openpilot/pull/27929 * get VIN on bolt! * might as well try on other gms * remove vin * ugh gm is going to be slow * fix * should really fix this * revert * happy?1 * fix unit test * bump * functional addressing must be an OBD gateway feature, this does nothing * fix vin response * fix addr! * finally fix fw_versions bugs since boardd IsOnroad refactor * for * only bus 0 * clean up * Update selfdrive/car/gm/values.py * ChatGPT re-write * filter out did * todo * oof * preview: what multiple DIDs per ECU would look like in the future * Revert "preview: what multiple DIDs per ECU would look like in the future" This reverts commit 88f0d8611e638de644adc5feabade848c03d59e4. * function to get all ecus * we can remove this! * can also do this! * and this one too :o * consistency * yay * clean up --- selfdrive/car/gm/fingerprints.py | 4 ++- selfdrive/car/gm/values.py | 36 ++++++++++++++++++++++ selfdrive/car/tests/test_fw_fingerprint.py | 3 +- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/gm/fingerprints.py b/selfdrive/car/gm/fingerprints.py index 9159ddefbf..445d9571d4 100644 --- a/selfdrive/car/gm/fingerprints.py +++ b/selfdrive/car/gm/fingerprints.py @@ -1,7 +1,7 @@ # ruff: noqa: E501 from openpilot.selfdrive.car.gm.values import CAR -# Trailblazer also matches as a SILVERADO, TODO: split with fw verisions +# Trailblazer also matches as a SILVERADO, TODO: split with fw versions FINGERPRINTS = { @@ -54,3 +54,5 @@ FINGERPRINTS = { 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 }], } + +FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = {} diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index dbbf15100f..8188ad4e6e 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -6,6 +6,8 @@ from typing import Dict, List, Union from cereal import car from openpilot.selfdrive.car import dbc_dict from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column +from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries + Ecu = car.CarParams.Ecu @@ -143,8 +145,42 @@ class CanBus: DROPPED = 192 +# In a Data Module, an identifier is a string used to recognize an object, +# either by itself or together with the identifiers of parent objects. +# Each returns a 4 byte hex representation of the decimal part number. `b"\x02\x8c\xf0'"` -> 42790951 +GM_SOFTWARE_MODULE_1_REQUEST = b'\x1a\xc1' +GM_SOFTWARE_MODULE_2_REQUEST = b'\x1a\xc2' +GM_SOFTWARE_MODULE_3_REQUEST = b'\x1a\xc3' +# This DID is for identifying the part number that reflects the mix of hardware, +# software, and calibrations in the ECU when it first arrives at the vehicle assembly plant. +# If there's an Alpha Code, it's associated with this part number and stored in the DID $DB. +GM_END_MODEL_PART_NUMBER_REQUEST = b'\x1a\xcb' +GM_BASE_MODEL_PART_NUMBER_REQUEST = b'\x1a\xcc' +GM_FW_RESPONSE = b'\x5a' + +GM_FW_REQUESTS = [ + GM_SOFTWARE_MODULE_1_REQUEST, + GM_SOFTWARE_MODULE_2_REQUEST, + GM_SOFTWARE_MODULE_3_REQUEST, + GM_END_MODEL_PART_NUMBER_REQUEST, + GM_BASE_MODEL_PART_NUMBER_REQUEST, +] + GM_RX_OFFSET = 0x400 +FW_QUERY_CONFIG = FwQueryConfig( + requests=[request for req in GM_FW_REQUESTS for request in [ + Request( + [StdQueries.SHORT_TESTER_PRESENT_REQUEST, req], + [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, GM_FW_RESPONSE + bytes([req[-1]])], + rx_offset=GM_RX_OFFSET, + bus=0, + logging=True, + ), + ]], + extra_ecus=[(Ecu.fwdCamera, 0x24b, None)], +) + DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')) EV_CAR = {CAR.VOLT, CAR.BOLT_EUV} diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 7cd670cf7c..e7ba67d433 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -246,9 +246,10 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.5 + total_ref_time = 7.1 brand_ref_times = { 1: { + 'gm': 0.5, 'body': 0.1, 'chrysler': 0.3, 'ford': 0.2, From 1d1c9936cf5711c5414b2da2ee823f7233e7882e Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 30 Jan 2024 10:42:33 +0000 Subject: [PATCH 634/724] Ford: don't fingerprint on engine (#31195) * Ford: do not fingerprint engine * add notebook * Ford: add missing Explorer FW * revert * revert * Revert "revert" This reverts commit ad32feaa689f2b497c8bcc9fedc73cae6c87f697. * TestFwFingerprintTiming: try these * TestFwFingerprintTiming: update total ref time * remove notebook * fix ref --------- Co-authored-by: Shane Smiskol --- selfdrive/car/ford/fingerprints.py | 54 +--------------------- selfdrive/car/ford/tests/test_ford.py | 2 +- selfdrive/car/ford/values.py | 8 +--- selfdrive/car/tests/test_fw_fingerprint.py | 6 +-- 4 files changed, 6 insertions(+), 64 deletions(-) diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py index 0cd812bc6b..0085b6b9c6 100644 --- a/selfdrive/car/ford/fingerprints.py +++ b/selfdrive/car/ford/fingerprints.py @@ -19,12 +19,6 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [ - b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'M1PA-14C204-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.ESCAPE_MK4: { (Ecu.eps, 0x730, None): [ @@ -47,17 +41,6 @@ FW_VERSIONS = { b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LV4T-14F397-GG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [ - b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6A-14C204-BJX\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6A-14C204-CNG\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6A-14C204-DPK\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MX6A-14C204-BEJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MX6A-14C204-CAB\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NX6A-14C204-BLE\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.EXPLORER_MK6: { (Ecu.eps, 0x730, None): [ @@ -83,21 +66,9 @@ FW_VERSIONS = { b'LB5T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LC5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LC5T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [ - b'LB5A-14C204-ATJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-ATS\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-AUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-AZL\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-BUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PB5A-14C204-DA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.F_150_MK14: { (Ecu.eps, 0x730, None): [ @@ -112,9 +83,6 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [ - b'PL3A-14C204-BRB\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.F_150_LIGHTNING_MK1: { (Ecu.abs, 0x760, None): [ @@ -126,9 +94,6 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x764, None): [ b'ML3T-14D049-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [ - b'NL3A-14C204-BAR\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.MUSTANG_MACH_E_MK1: { (Ecu.eps, 0x730, None): [ @@ -144,11 +109,6 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'ML3T-14H102-ABS\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [ - b'MJ98-14C204-BBP\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'MJ98-14C204-BBS\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NJ98-14C204-VH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.FOCUS_MK4: { (Ecu.eps, 0x730, None): [ @@ -163,9 +123,6 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'JX7T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [ - b'JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.MAVERICK_MK1: { (Ecu.eps, 0x730, None): [ @@ -182,14 +139,5 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None): [ - b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'NZ6A-14C204-ZC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ6A-14C204-BE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ6A-14C204-JC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ6A-14C204-JE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, } diff --git a/selfdrive/car/ford/tests/test_ford.py b/selfdrive/car/ford/tests/test_ford.py index a1f0b81a58..dff29629f6 100755 --- a/selfdrive/car/ford/tests/test_ford.py +++ b/selfdrive/car/ford/tests/test_ford.py @@ -52,7 +52,7 @@ class TestFordFW(unittest.TestCase): @parameterized.expand(FW_VERSIONS.items()) def test_fw_versions(self, car_model: str, fw_versions: Dict[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]], Iterable[bytes]]): for (ecu, addr, subaddr), fws in fw_versions.items(): - self.assertIn(ecu, ECU_ADDRESSES, "Unknown ECU") + self.assertIn(ecu, ECU_FW_CORE, "Unexpected ECU") self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch") self.assertIsNone(subaddr, "Unexpected ECU subaddress") diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 2c4415be2b..a18e6f461e 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -111,21 +111,15 @@ FW_QUERY_CONFIG = FwQueryConfig( requests=[ # CAN and CAN FD queries are combined. # FIXME: For CAN FD, ECUs respond with frames larger than 8 bytes on the powertrain bus - # TODO: properly handle auxiliary requests to separate queries and add back whitelists Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], - # whitelist_ecus=[Ecu.engine], - ), - Request( - [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], - [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], - # whitelist_ecus=[Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.shiftByWire], bus=0, auxiliary=True, ), ], extra_ecus=[ + (Ecu.engine, 0x7e0, None), (Ecu.shiftByWire, 0x732, None), ], ) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index e7ba67d433..620fa25691 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -246,13 +246,13 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 7.1 + total_ref_time = 6.9 brand_ref_times = { 1: { 'gm': 0.5, 'body': 0.1, 'chrysler': 0.3, - 'ford': 0.2, + 'ford': 0.1, 'honda': 0.55, 'hyundai': 0.65, 'mazda': 0.2, @@ -263,7 +263,7 @@ class TestFwFingerprintTiming(unittest.TestCase): 'volkswagen': 0.2, }, 2: { - 'ford': 0.3, + 'ford': 0.2, 'hyundai': 1.05, } } From c2ee07290721ef0317ef49021f95e71016222bc1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 05:07:00 -0600 Subject: [PATCH 635/724] format fingerprints: fix empty FW versions (#31230) fix --- selfdrive/car/gm/fingerprints.py | 3 ++- selfdrive/debug/format_fingerprints.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/gm/fingerprints.py b/selfdrive/car/gm/fingerprints.py index 445d9571d4..c349ee2856 100644 --- a/selfdrive/car/gm/fingerprints.py +++ b/selfdrive/car/gm/fingerprints.py @@ -55,4 +55,5 @@ FINGERPRINTS = { }], } -FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = {} +FW_VERSIONS: dict[str, dict[tuple, list[bytes]]] = { +} diff --git a/selfdrive/debug/format_fingerprints.py b/selfdrive/debug/format_fingerprints.py index 1752aa1f06..bd5822729a 100755 --- a/selfdrive/debug/format_fingerprints.py +++ b/selfdrive/debug/format_fingerprints.py @@ -44,9 +44,8 @@ FINGERPRINTS = { {% endfor %} } {% endif %} -{% if FW_VERSIONS[brand] %} -FW_VERSIONS = { +FW_VERSIONS{% if not FW_VERSIONS[brand] %}: dict[str, dict[tuple, list[bytes]]]{% endif %} = { {% for car, _ in FW_VERSIONS[brand].items() %} CAR.{{car.name}}: { {% for key, fw_versions in FW_VERSIONS[brand][car].items() %} @@ -60,7 +59,7 @@ FW_VERSIONS = { }, {% endfor %} } -{% endif %} + """, trim_blocks=True) From feaf52dd4c1f1b406b068e195fa48f3daf0b67ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 30 Jan 2024 11:40:52 -0800 Subject: [PATCH 636/724] Ford long control improvements (#31123) * Ford updates * back to normal * Small fixes * Try max * Does pred matter * ref commit * Update ref --- selfdrive/car/ford/carcontroller.py | 4 ++-- selfdrive/car/ford/fordcan.py | 3 +-- selfdrive/car/ford/interface.py | 4 ++++ selfdrive/test/process_replay/ref_commit | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 88ec6a5072..45516d6035 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -1,10 +1,10 @@ from cereal import car from openpilot.common.numpy_fast import clip -from openpilot.common.conversions import Conversions as CV from opendbc.can.packer import CANPacker from openpilot.selfdrive.car import apply_std_steer_angle_limits from openpilot.selfdrive.car.ford import fordcan from openpilot.selfdrive.car.ford.values import CANFD_CAR, CarControllerParams +from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX LongCtrlState = car.CarControl.Actuators.LongControlState VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -90,7 +90,7 @@ class CarController: if not CC.longActive or gas < CarControllerParams.MIN_GAS: gas = CarControllerParams.INACTIVE_GAS stopping = CC.actuators.longControlState == LongCtrlState.stopping - can_sends.append(fordcan.create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping, v_ego_kph=40 * CV.MPH_TO_KPH)) + can_sends.append(fordcan.create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping, v_ego_kph=V_CRUISE_MAX)) ### ui ### send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert) diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index 78a249e387..c5ef0900f3 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -127,13 +127,12 @@ def create_acc_msg(packer, CAN: CanBus, long_active: bool, gas: float, accel: fl Frequency is 50Hz. """ - decel = accel < 0 and long_active values = { "AccBrkTot_A_Rq": accel, # Brake total accel request: [-20|11.9449] m/s^2 "Cmbb_B_Enbl": 1 if long_active else 0, # Enabled: 0=No, 1=Yes "AccPrpl_A_Rq": gas, # Acceleration request: [-5|5.23] m/s^2 - "AccPrpl_A_Pred": gas, # Acceleration request: [-5|5.23] m/s^2 + "AccPrpl_A_Pred": -5.0, # Acceleration request: [-5|5.23] m/s^2 "AccResumEnbl_B_Rq": 1 if long_active else 0, "AccVeh_V_Trg": v_ego_kph, # Target speed: [0|255] km/h # TODO: we may be able to improve braking response by utilizing pre-charging better diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 8a8ce36bd0..cc013fb54b 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -21,6 +21,10 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.2 ret.steerLimitTimer = 1.0 + ret.longitudinalTuning.kpBP = [0.] + ret.longitudinalTuning.kpV = [0.5] + ret.longitudinalTuning.kiV = [0.] + CAN = CanBus(fingerprint=fingerprint) cfgs = [get_safety_config(car.CarParams.SafetyModel.ford)] if CAN.main >= 4: diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 803f4e96ee..0cfbbde0da 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -58ef40eb2264a9d62a1715486677c342d5d113ce \ No newline at end of file +6a61df84e67e3177f0ba73865a1bb239385c0ade From 0176c870eaf5c59e076811b607bf7f0f5f6d82cc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 15:57:52 -0600 Subject: [PATCH 637/724] Fingerprints: add missing FW versions from users for Toyota, Honda, Chrysler, Mazda, Ford [bot] (#31231) --- selfdrive/car/chrysler/fingerprints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index 6461ceec4d..f9a3fe5b10 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -494,6 +494,7 @@ FW_VERSIONS = { b'68455145AE ', b'68455146AC ', b'68467915AC ', + b'68467936AC ', b'68500630AD', b'68500630AE', b'68502719AC ', @@ -528,6 +529,7 @@ FW_VERSIONS = { b'68445533AB', b'68445536AB', b'68445537AB', + b'68466081AB', b'68466087AB', b'68484466AC', b'68484467AC', From bc5fdf24ae847b3a55c04d905ed5b1acd1d71fb9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 30 Jan 2024 15:00:41 -0800 Subject: [PATCH 638/724] update min valid time --- common/time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/time.py b/common/time.py index c8ef9cd383..f2e49eb546 100644 --- a/common/time.py +++ b/common/time.py @@ -1,6 +1,6 @@ import datetime -MIN_DATE = datetime.datetime(year=2023, month=6, day=1) +MIN_DATE = datetime.datetime(year=2024, month=1, day=28) def system_time_valid(): return datetime.datetime.now() > MIN_DATE From bd1678e71f700126558dfa771544f90fa6d3e406 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 18:06:28 -0600 Subject: [PATCH 639/724] Toyota: fix rare panda standstill mismatch (#31232) * fix * clean up * just use 2x lower threshold so we're not on edge again --- selfdrive/car/toyota/carstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 3a89d655da..2461fa9a26 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -71,7 +71,7 @@ class CarState(CarStateBase): ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.vEgoCluster = ret.vEgo * 1.015 # minimum of all the cars - ret.standstill = ret.vEgoRaw == 0 + ret.standstill = abs(ret.vEgoRaw) < 1e-3 ret.steeringAngleDeg = cp.vl["STEER_ANGLE_SENSOR"]["STEER_ANGLE"] + cp.vl["STEER_ANGLE_SENSOR"]["STEER_FRACTION"] ret.steeringRateDeg = cp.vl["STEER_ANGLE_SENSOR"]["STEER_RATE"] From 97aa08d8dea2a6941a19d13edbeffb994725cbc3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 20:46:46 -0600 Subject: [PATCH 640/724] docs: fix car docs video icon (#31218) * fix yt icon on docs.comma.ai * bigger --- docs/CARS.md | 108 ++++++++++++++++----------------- docs/assets/icon-youtube.svg | 4 +- selfdrive/car/CARS_template.md | 2 +- 3 files changed, 57 insertions(+), 57 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 5620bc703c..4312e479c7 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -21,11 +21,11 @@ A supported vehicle is one that just works when you install a comma device. All |Cadillac|Escalade 2017[4](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade ESV 2016[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade ESV 2019[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/xvwzGMUA210)| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Volt 2017-18[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Chevrolet|Volt 2017-18[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/QeMCN_4TFfQ)| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -50,15 +50,15 @@ A supported vehicle is one that just works when you install a comma device. All |Genesis|GV70 (2.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|GV70 (3.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|GV80 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|GMC|Acadia 2018[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|GMC|Acadia 2018[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=0ZN6DdsBUZo)| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/5HbNoBLzRwE)| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=mrUwlj3Mi58)| |Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/-IkImTe1NYE)| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=4Iz1Mz5LGF8)| +|Honda|Civic 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/ytiOT5lcp6Q)| |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic Hatchback 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Civic Hatchback 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/ytiOT5lcp6Q)| |Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V Hybrid 2017-20|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -79,9 +79,9 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/_EdYQtV52-c)| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/_EdYQtV52-c)| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -97,16 +97,16 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=U2fOCmcQ8hw)| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/TAnDqjF4fDY?t=456)| |Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/bjDR0YjM__s)| +|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/VnHzSTygTS4)| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=ix63r9kE3Fw)| |Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Staria 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -115,8 +115,8 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=eLR9o2JkuRk)| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=jBe4lWnRSu4)| |Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -128,10 +128,10 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=lT7zcG6ZpGo)| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=lT7zcG6ZpGo)| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=lT7zcG6ZpGo)| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=lT7zcG6ZpGo)| |Kia|Niro EV 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -143,21 +143,21 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=Fkh3s6WHJz8)| +|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=Fkh3s6WHJz8)| |Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=MJ94qoofYw0)| |Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|ES Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Lexus|ES Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/BZ29osRVJeg?t=12)| |Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -174,26 +174,26 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|UX Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lincoln|Aviator 2020-21|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| +|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| |Mazda|CX-5 2022-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/dA3duO4a0O4)| |Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/vaMbtAh_0cY)| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|[![video](assets/icon-youtube.svg)](https://youtu.be/Agww7oE1k-s?t=26)| |Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Forester 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| +|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|[![video](assets/icon-youtube.svg)](https://youtu.be/Agww7oE1k-s?t=26)| |Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Škoda|Fabia 2022-23[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| |Škoda|Kamiq 2021-23[10,12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| @@ -215,49 +215,49 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2021-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2018-20|All|Stock|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry 2018-20|All|Stock|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=fkcjviZY9CM)| |Toyota|Camry 2021-24|All|openpilot|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=Q2DYY0AWKgk)| |Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=_66pXk0CBYA)| |Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=_66pXk0CBYA)| |Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla Hybrid (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=0wS0wXSLzoo)| |Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=8zopPJI8XQ0)| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=8zopPJI8XQ0)| +|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=J58TvCpUd4U)| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=8zopPJI8XQ0)| +|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=J58TvCpUd4U)| |Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=wJxjDd42gGA)| |Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2023-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/LhT5VzJVfNI?t=26)| +|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/LhT5VzJVfNI?t=26)| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/U0nH9cnrFB0)| |Toyota|RAV4 Hybrid 2023-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=q1UPOo4Sh68)| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/FAomFKPFlDA)| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/FAomFKPFlDA)| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/FAomFKPFlDA)| |Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/FAomFKPFlDA)| +|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| +|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| |Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -266,7 +266,7 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| |Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Passat 2015-22[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/docs/assets/icon-youtube.svg b/docs/assets/icon-youtube.svg index f738dca10a..6316e085d7 100644 --- a/docs/assets/icon-youtube.svg +++ b/docs/assets/icon-youtube.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6cb64f9da10b818c56763a7c48347f6043da20a2a77fb14f6d60d9457c575b6b -size 1278 +oid sha256:532442e171c7370fc613eff5a878eb3e96292764fb86d9761ccf78744e8f12b1 +size 1280 diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index ab4d231fa7..4ac21457eb 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -1,6 +1,6 @@ {% set footnote_tag = '[{}](#footnotes)' %} {% set star_icon = '[![star](assets/icon-star-{}.svg)](##)' %} -{% set video_icon = '' %} +{% set video_icon = '[![video](assets/icon-youtube.svg)]({})' %} {# Force hardware column wider by using a blank image with max width. #} {% set width_tag = '%s
 ' %} {% set hardware_col_name = 'Hardware Needed' %} From 9c48e85c786d1c7551abada628635b78c5c60fb9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 21:04:43 -0600 Subject: [PATCH 641/724] Revert "docs: fix car docs video icon" (#31234) --- docs/CARS.md | 108 ++++++++++++++++----------------- docs/assets/icon-youtube.svg | 4 +- selfdrive/car/CARS_template.md | 2 +- 3 files changed, 57 insertions(+), 57 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 4312e479c7..5620bc703c 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -21,11 +21,11 @@ A supported vehicle is one that just works when you install a comma device. All |Cadillac|Escalade 2017[4](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade ESV 2016[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Cadillac|Escalade ESV 2019[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/xvwzGMUA210)| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Volt 2017-18[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/QeMCN_4TFfQ)| +|Chevrolet|Volt 2017-18[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -50,15 +50,15 @@ A supported vehicle is one that just works when you install a comma device. All |Genesis|GV70 (2.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|GV70 (3.5T Trim) 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|GV80 2023[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|GMC|Acadia 2018[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=0ZN6DdsBUZo)| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/5HbNoBLzRwE)| -|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=mrUwlj3Mi58)| +|GMC|Acadia 2018[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/-IkImTe1NYE)| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=4Iz1Mz5LGF8)| -|Honda|Civic 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/ytiOT5lcp6Q)| +|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[5](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Civic 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Civic Hatchback 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/ytiOT5lcp6Q)| +|Honda|Civic Hatchback 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|CR-V Hybrid 2017-20|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -79,9 +79,9 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/_EdYQtV52-c)| +|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/_EdYQtV52-c)| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -97,16 +97,16 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=U2fOCmcQ8hw)| +|Hyundai|Kona Electric (with HDA II, Korea only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/TAnDqjF4fDY?t=456)| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/bjDR0YjM__s)| -|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/VnHzSTygTS4)| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=ix63r9kE3Fw)| +|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Staria 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -115,8 +115,8 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Tucson Hybrid 2022-24[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=eLR9o2JkuRk)| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=jBe4lWnRSu4)| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Carnival 2022-24[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Carnival (China only) 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -128,10 +128,10 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K8 Hybrid (with HDA II) 2023[6](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=lT7zcG6ZpGo)| -|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=lT7zcG6ZpGo)| -|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=lT7zcG6ZpGo)| -|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=lT7zcG6ZpGo)| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro EV 2023[6](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -143,21 +143,21 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=Fkh3s6WHJz8)| -|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=Fkh3s6WHJz8)| +|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2021-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento Hybrid 2021-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento Plug-in Hybrid 2022-23[6](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sportage Hybrid 2023[6](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=MJ94qoofYw0)| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|ES Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/BZ29osRVJeg?t=12)| +|Lexus|ES Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -174,26 +174,26 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|UX Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lincoln|Aviator 2020-21|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| -|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| +|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-5 2022-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/dA3duO4a0O4)| +|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/vaMbtAh_0cY)| +|Nissan|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Subaru|Ascent 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|[![video](assets/icon-youtube.svg)](https://youtu.be/Agww7oE1k-s?t=26)| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Forester 2019-21|All[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Impreza 2017-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Impreza 2020-22|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| -|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|[![video](assets/icon-youtube.svg)](https://youtu.be/Agww7oE1k-s?t=26)| +|Subaru|XV 2018-19|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Subaru|XV 2020-21|EyeSight Driver Assistance[7](#footnotes)|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
|| |Škoda|Fabia 2022-23[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| |Škoda|Kamiq 2021-23[10,12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[14](#footnotes)|| @@ -215,49 +215,49 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2021-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2018-20|All|Stock|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=fkcjviZY9CM)| +|Toyota|Camry 2018-20|All|Stock|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry 2021-24|All|openpilot|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=Q2DYY0AWKgk)| +|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=_66pXk0CBYA)| +|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=_66pXk0CBYA)| +|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Corolla Hybrid (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=0wS0wXSLzoo)| +|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=8zopPJI8XQ0)| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=8zopPJI8XQ0)| -|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=J58TvCpUd4U)| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=8zopPJI8XQ0)| -|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=J58TvCpUd4U)| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=wJxjDd42gGA)| +|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2023-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/LhT5VzJVfNI?t=26)| -|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/LhT5VzJVfNI?t=26)| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/U0nH9cnrFB0)| +|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2023-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://www.youtube.com/watch?v=q1UPOo4Sh68)| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/FAomFKPFlDA)| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/FAomFKPFlDA)| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/FAomFKPFlDA)| +|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/FAomFKPFlDA)| -|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| -|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -266,7 +266,7 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|[![video](assets/icon-youtube.svg)](https://youtu.be/4100gLeabmo)| +|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Passat 2015-22[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/docs/assets/icon-youtube.svg b/docs/assets/icon-youtube.svg index 6316e085d7..f738dca10a 100644 --- a/docs/assets/icon-youtube.svg +++ b/docs/assets/icon-youtube.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:532442e171c7370fc613eff5a878eb3e96292764fb86d9761ccf78744e8f12b1 -size 1280 +oid sha256:6cb64f9da10b818c56763a7c48347f6043da20a2a77fb14f6d60d9457c575b6b +size 1278 diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index 4ac21457eb..ab4d231fa7 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -1,6 +1,6 @@ {% set footnote_tag = '[{}](#footnotes)' %} {% set star_icon = '[![star](assets/icon-star-{}.svg)](##)' %} -{% set video_icon = '[![video](assets/icon-youtube.svg)]({})' %} +{% set video_icon = '' %} {# Force hardware column wider by using a blank image with max width. #} {% set width_tag = '%s
 ' %} {% set hardware_col_name = 'Hardware Needed' %} From b40660692b7f713cde1265d73d4a271a24e85221 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 30 Jan 2024 23:57:29 -0600 Subject: [PATCH 642/724] Hyundai: use hybrid & ev flags (#31236) * hyundai: use hybrid & ev flags * Update ref_commit * todo * match order --- selfdrive/car/hyundai/carstate.py | 24 ++++++++++++------------ selfdrive/car/hyundai/interface.py | 10 ++++++++-- selfdrive/car/hyundai/values.py | 2 ++ selfdrive/test/process_replay/ref_commit | 2 +- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 2f1fb10804..64a9fdf2ce 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -8,7 +8,7 @@ from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine from openpilot.selfdrive.car.hyundai.hyundaicanfd import CanBus from openpilot.selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CAN_GEARS, CAMERA_SCC_CAR, \ - CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams + CANFD_CAR, Buttons, CarControllerParams from openpilot.selfdrive.car.interfaces import CarStateBase PREV_BUTTON_SAMPLES = 8 @@ -36,8 +36,8 @@ class CarState(CarStateBase): else: # preferred and elect gear methods use same definition self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"] - self.accelerator_msg_canfd = "ACCELERATOR" if CP.carFingerprint in EV_CAR else \ - "ACCELERATOR_ALT" if CP.carFingerprint in HYBRID_CAR else \ + self.accelerator_msg_canfd = "ACCELERATOR" if CP.flags & HyundaiFlags.EV else \ + "ACCELERATOR_ALT" if CP.flags & HyundaiFlags.HYBRID else \ "ACCELERATOR_BRAKE_ALT" self.cruise_btns_msg_canfd = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else \ "CRUISE_BUTTONS" @@ -120,8 +120,8 @@ class CarState(CarStateBase): ret.parkingBrake = cp.vl["TCS13"]["PBRAKE_ACT"] == 1 ret.accFaulted = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED - if self.CP.carFingerprint in (HYBRID_CAR | EV_CAR): - if self.CP.carFingerprint in HYBRID_CAR: + if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): + if self.CP.flags & HyundaiFlags.HYBRID: ret.gas = cp.vl["E_EMS11"]["CR_Vcu_AccPedDep_Pos"] / 254. else: ret.gas = cp.vl["E_EMS11"]["Accel_Pedal_Pos"] / 254. @@ -132,7 +132,7 @@ class CarState(CarStateBase): # Gear Selection via Cluster - For those Kia/Hyundai which are not fully discovered, we can use the Cluster Indicator for Gear Selection, # as this seems to be standard over all cars, but is not the preferred method. - if self.CP.carFingerprint in (HYBRID_CAR | EV_CAR): + if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): gear = cp.vl["ELECT_GEAR"]["Elect_Gear_Shifter"] elif self.CP.carFingerprint in CAN_GEARS["use_cluster_gears"]: gear = cp.vl["CLU15"]["CF_Clu_Gear"] @@ -172,8 +172,8 @@ class CarState(CarStateBase): self.is_metric = cp.vl["CRUISE_BUTTONS_ALT"]["DISTANCE_UNIT"] != 1 speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS - if self.CP.carFingerprint in (EV_CAR | HYBRID_CAR): - offset = 255. if self.CP.carFingerprint in EV_CAR else 1023. + if self.CP.flags & (HyundaiFlags.EV | HyundaiFlags.HYBRID): + offset = 255. if self.CP.flags & HyundaiFlags.EV else 1023. ret.gas = cp.vl[self.accelerator_msg_canfd]["ACCELERATOR_PEDAL"] / offset ret.gasPressed = ret.gas > 1e-5 else: @@ -233,7 +233,7 @@ class CarState(CarStateBase): # It limits the vehicle speed, overridable by pressing the accelerator past a certain point. # The car will brake, but does not respect positive acceleration commands in this mode # TODO: find this message on ICE & HYBRID cars + cruise control signals (if exists) - if self.CP.carFingerprint in EV_CAR: + if self.CP.flags & HyundaiFlags.EV: ret.cruiseState.nonAdaptive = cp.vl["MANUAL_SPEED_LIMIT_ASSIST"]["MSLA_ENABLED"] == 1 self.prev_cruise_buttons = self.cruise_buttons[-1] @@ -278,7 +278,7 @@ class CarState(CarStateBase): if CP.enableBsm: messages.append(("LCA11", 50)) - if CP.carFingerprint in (HYBRID_CAR | EV_CAR): + if CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): messages.append(("E_EMS11", 50)) else: messages += [ @@ -286,7 +286,7 @@ class CarState(CarStateBase): ("EMS16", 100), ] - if CP.carFingerprint in (HYBRID_CAR | EV_CAR): + if CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV): messages.append(("ELECT_GEAR", 20)) elif CP.carFingerprint in CAN_GEARS["use_cluster_gears"]: pass @@ -330,7 +330,7 @@ class CarState(CarStateBase): ("DOORS_SEATBELTS", 4), ] - if CP.carFingerprint in EV_CAR: + if CP.flags & HyundaiFlags.EV: messages += [ ("MANUAL_SPEED_LIMIT_ASSIST", 10), ] diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index cd6657a23c..a7356e20e6 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -45,6 +45,12 @@ class CarInterface(CarInterfaceBase): hda2 = Ecu.adas in [fw.ecu for fw in car_fw] CAN = CanBus(None, hda2, fingerprint) + # TODO: detect EV and hybrid + if candidate in HYBRID_CAR: + ret.flags |= HyundaiFlags.HYBRID.value + elif candidate in EV_CAR: + ret.flags |= HyundaiFlags.EV.value + if candidate in CANFD_CAR: # detect HDA2 with ADAS Driving ECU if hda2: @@ -313,9 +319,9 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC if ret.openpilotLongitudinalControl: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_LONG - if candidate in HYBRID_CAR: + if ret.flags & HyundaiFlags.HYBRID: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_HYBRID_GAS - elif candidate in EV_CAR: + elif ret.flags & HyundaiFlags.EV: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS if candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 4b92bc5a49..0097fb7b34 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -64,6 +64,8 @@ class HyundaiFlags(IntFlag): SEND_LFA = 128 USE_FCA = 256 CANFD_HDA2_ALT_STEERING = 512 + HYBRID = 1024 + EV = 2048 class CAR(StrEnum): diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 0cfbbde0da..bde2296e6b 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -6a61df84e67e3177f0ba73865a1bb239385c0ade +eaab6bd55c5eab33fc9a0d8de8289b912e923887 From e5a8ddc3d0c5f7df7655409ed8ffae182ec57ab6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Jan 2024 00:43:56 -0600 Subject: [PATCH 643/724] Hyundai CAN FD: detect hybrid (#31237) * detect can fd hybrid * remove from set * prevent EV & hybrid somehow --- selfdrive/car/hyundai/interface.py | 18 ++++++++++++------ selfdrive/car/hyundai/tests/test_hyundai.py | 4 ++++ selfdrive/car/hyundai/values.py | 6 ++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index a7356e20e6..12b775fcdb 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -45,13 +45,13 @@ class CarInterface(CarInterfaceBase): hda2 = Ecu.adas in [fw.ecu for fw in car_fw] CAN = CanBus(None, hda2, fingerprint) - # TODO: detect EV and hybrid - if candidate in HYBRID_CAR: - ret.flags |= HyundaiFlags.HYBRID.value - elif candidate in EV_CAR: - ret.flags |= HyundaiFlags.EV.value - if candidate in CANFD_CAR: + # detect if car is hybrid + if 0x105 in fingerprint[CAN.ECAN]: + ret.flags |= HyundaiFlags.HYBRID.value + elif candidate in EV_CAR: + ret.flags |= HyundaiFlags.EV.value + # detect HDA2 with ADAS Driving ECU if hda2: if 0x110 in fingerprint[CAN.CAM]: @@ -69,6 +69,12 @@ class CarInterface(CarInterfaceBase): if candidate not in CANFD_RADAR_SCC_CAR: ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value else: + # TODO: detect EV and hybrid + if candidate in HYBRID_CAR: + ret.flags |= HyundaiFlags.HYBRID.value + elif candidate in EV_CAR: + ret.flags |= HyundaiFlags.EV.value + # Send LFA message on cars with HDA if 0x485 in fingerprint[2]: ret.flags |= HyundaiFlags.SEND_LFA.value diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index cd0ec4484b..4faf956595 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -49,6 +49,10 @@ class TestHyundaiFingerprint(unittest.TestCase): for car_model in CANFD_CAR: self.assertNotIn(car_model, can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list") + def test_hybrid_ev_sets(self): + self.assertEqual(HYBRID_CAR & EV_CAR, set(), "Shared cars between hybrid and EV") + self.assertEqual(CANFD_CAR & HYBRID_CAR, set(), "Hard coding CAN FD cars as hybrid is no longer supported") + def test_auxiliary_request_ecu_whitelist(self): # Asserts only auxiliary Ecus can exist in database for CAN-FD cars whitelisted_ecus = {ecu for r in FW_QUERY_CONFIG.requests for ecu in r.whitelist_ecus if r.auxiliary} diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 0097fb7b34..6bf6039b32 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -501,10 +501,8 @@ CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } # these cars use a different gas signal HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, - CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, - CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN, - CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.KIA_K8_HEV_1ST_GEN, - CAR.AZERA_HEV_6TH_GEN, CAR.KIA_NIRO_PHEV_2022} + CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KIA_K5_HEV_2020, + CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL, CAR.AZERA_HEV_6TH_GEN, CAR.KIA_NIRO_PHEV_2022} EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_EV_2ND_GEN, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KONA_EV_2ND_GEN} From e92e856f7ad5f19692b6328ab0d89027c1495f2c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Jan 2024 01:06:24 -0600 Subject: [PATCH 644/724] Hyundai CAN FD: merge Tucson and Tucson Hybrid (#31238) * combine tucson and tucson hybrid can fd * format fingerprints --- selfdrive/car/hyundai/fingerprints.py | 12 ++---------- selfdrive/car/hyundai/interface.py | 2 +- selfdrive/car/hyundai/tests/test_hyundai.py | 3 --- selfdrive/car/hyundai/values.py | 6 ++---- selfdrive/car/tests/routes.py | 2 +- selfdrive/car/tests/test_models_segs.txt | 4 ++-- selfdrive/car/torque_data/params.toml | 2 +- selfdrive/car/torque_data/substitute.toml | 1 - 8 files changed, 9 insertions(+), 23 deletions(-) diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 585a2ec732..9ea9bd766b 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -1563,23 +1563,15 @@ FW_VERSIONS = { ], }, CAR.TUCSON_4TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', - b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', - b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', - ], - }, - CAR.TUCSON_HYBRID_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9250 14W', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9260 14Y', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 12b775fcdb..b07f5ad3ec 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -159,7 +159,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.67 ret.steerRatio = 14.00 * 1.15 ret.tireStiffnessFactor = 0.385 - elif candidate in (CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN): + elif candidate == CAR.TUCSON_4TH_GEN: ret.mass = 1630. # average ret.wheelbase = 2.756 ret.steerRatio = 16. diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index 4faf956595..38dafddb83 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -21,7 +21,6 @@ NO_DATES_PLATFORMS = { CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.TUCSON_4TH_GEN, - CAR.TUCSON_HYBRID_4TH_GEN, # CAN CAR.ELANTRA, CAR.ELANTRA_GT_I30, @@ -182,8 +181,6 @@ class TestHyundaiFingerprint(unittest.TestCase): excluded_platforms = { CAR.GENESIS_G70, # shared platform code, part number, and date CAR.GENESIS_G70_2020, - CAR.TUCSON_4TH_GEN, # shared platform code and part number - CAR.TUCSON_HYBRID_4TH_GEN, } excluded_platforms |= CANFD_CAR - EV_CAR # shared platform codes excluded_platforms |= NO_DATES_PLATFORMS # date codes are required to match diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 6bf6039b32..ca62c66c2e 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -102,7 +102,6 @@ class CAR(StrEnum): IONIQ_5 = "HYUNDAI IONIQ 5 2022" IONIQ_6 = "HYUNDAI IONIQ 6 2023" TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN" - TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN" SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN" CUSTIN_1ST_GEN = "HYUNDAI CUSTIN 1ST GEN" @@ -227,8 +226,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.TUCSON_4TH_GEN: [ HyundaiCarInfo("Hyundai Tucson 2022", car_parts=CarParts.common([CarHarness.hyundai_n])), HyundaiCarInfo("Hyundai Tucson 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), + HyundaiCarInfo("Hyundai Tucson Hybrid 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), ], - CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022-24", "All", car_parts=CarParts.common([CarHarness.hyundai_n])), CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2022-23", car_parts=CarParts.common([CarHarness.hyundai_n])), CAR.CUSTIN_1ST_GEN: HyundaiCarInfo("Hyundai Custin 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), @@ -482,7 +481,7 @@ CAN_GEARS = { "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.KIA_K8_HEV_1ST_GEN, @@ -567,7 +566,6 @@ DBC = { CAR.KIA_EV6: dbc_dict('hyundai_canfd', None), CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.TUCSON_4TH_GEN: dbc_dict('hyundai_canfd', None), - CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), CAR.IONIQ_6: dbc_dict('hyundai_canfd', None), CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 69b39b3b2e..89c9d17e12 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -122,7 +122,7 @@ routes = [ CarTestRoute("c344fd2492c7a9d2|2023-12-11--09-03-23", HYUNDAI.STARIA_4TH_GEN), CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), CarTestRoute("db68bbe12250812c|2022-12-05--00-54-12", HYUNDAI.TUCSON_4TH_GEN), # 2023 - CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN), + CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_4TH_GEN), # hybrid CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), CarTestRoute("1d0d000db3370fd0|2023-01-04--22-28-42", HYUNDAI.KIA_SORENTO_4TH_GEN, segment=5), CarTestRoute("fc19648042eb6896|2023-08-16--11-43-27", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN, segment=14), diff --git a/selfdrive/car/tests/test_models_segs.txt b/selfdrive/car/tests/test_models_segs.txt index 92835ae8ad..6f5748d408 100644 --- a/selfdrive/car/tests/test_models_segs.txt +++ b/selfdrive/car/tests/test_models_segs.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8570e4144f2c17a1c804b18cea0d4358bea211f67c823fd3bd152b6af7cf207d -size 125147 +oid sha256:2274c383d822cf655f45f00111374420f88f4058ad12766d72e0580e6a1e4e0f +size 125126 diff --git a/selfdrive/car/torque_data/params.toml b/selfdrive/car/torque_data/params.toml index 564f50df5e..568646c84c 100644 --- a/selfdrive/car/torque_data/params.toml +++ b/selfdrive/car/torque_data/params.toml @@ -38,7 +38,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "HYUNDAI SONATA 2019" = [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] "HYUNDAI SONATA 2020" = [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] "HYUNDAI SONATA HYBRID 2021" = [2.8990264092395734, 2.061410192222139, 0.0899805488717382] -"HYUNDAI TUCSON HYBRID 4TH GEN" = [2.960174, 2.860284, 0.108745] +"HYUNDAI TUCSON 4TH GEN" = [2.960174, 2.860284, 0.108745] "JEEP GRAND CHEROKEE 2019" = [2.30972, 1.289689569171081, 0.117048] "JEEP GRAND CHEROKEE V6 2018" = [2.27116, 1.4057367824262523, 0.11725947414922003] "KIA EV6 2022" = [3.2, 2.093457, 0.05] diff --git a/selfdrive/car/torque_data/substitute.toml b/selfdrive/car/torque_data/substitute.toml index 2e4513a484..bab10bc062 100644 --- a/selfdrive/car/torque_data/substitute.toml +++ b/selfdrive/car/torque_data/substitute.toml @@ -33,7 +33,6 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "HYUNDAI I30 N LINE 2019 & GT 2018 DCT" = "HYUNDAI SONATA 2019" "HYUNDAI ELANTRA HYBRID 2021" = "HYUNDAI SONATA 2020" "HYUNDAI TUCSON 2019" = "HYUNDAI SANTA FE 2019" -"HYUNDAI TUCSON 4TH GEN" = "HYUNDAI TUCSON HYBRID 4TH GEN" "HYUNDAI SANTA FE 2022" = "HYUNDAI SANTA FE HYBRID 2022" "KIA K5 HYBRID 2020" = "KIA K5 2021" "KIA STINGER 2022" = "KIA STINGER GT2 2018" From 2f4cadc6bfb53a6ac25040b0f2aa181ee601d106 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Jan 2024 02:19:15 -0600 Subject: [PATCH 645/724] Hyundai CAN FD: merge Sportage platforms (#31235) * merge Tucson 4th gen! * format fingerprints * fix torque params * fix * detect CANFD hybrid with ACCELERATOR_ALT * passes * add test * pass test * cmt * Revert "pass test" This reverts commit 5f48ab1f6e6569def1cbbc92c9c0dd4ea5eebbe2. * combine sportage hybrid * merge sportage * format --- selfdrive/car/hyundai/fingerprints.py | 12 +++--------- selfdrive/car/hyundai/interface.py | 6 +----- selfdrive/car/hyundai/tests/test_hyundai.py | 1 - selfdrive/car/hyundai/values.py | 10 +++++----- selfdrive/car/tests/routes.py | 2 +- selfdrive/car/tests/test_models_segs.txt | 4 ++-- selfdrive/car/torque_data/override.toml | 3 +-- 7 files changed, 13 insertions(+), 25 deletions(-) diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 9ea9bd766b..e84905a34c 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -1579,15 +1579,6 @@ FW_VERSIONS = { b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', ], }, - CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00NQ5 FR_CMR AT GEN LHD 1.00 1.00 99211-P1060 665', - b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ', - ], - }, CAR.SANTA_CRUZ_1ST_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M', @@ -1601,12 +1592,15 @@ FW_VERSIONS = { CAR.KIA_SPORTAGE_5TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NQ5 FR_CMR AT AUS RHD 1.00 1.00 99211-P1040 663', + b'\xf1\x00NQ5 FR_CMR AT GEN LHD 1.00 1.00 99211-P1060 665', b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662', b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663', + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ', b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ', + b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ', b'\xf1\x00NQ5__ 1.01 1.03 99110-P1000 ', ], }, diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index b07f5ad3ec..345619cbe2 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -195,7 +195,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.63 ret.steerRatio = 14.56 elif candidate == CAR.KIA_SPORTAGE_5TH_GEN: - ret.mass = 1700. # weight from SX and above trims, average of FWD and AWD versions + ret.mass = 1725. # weight from SX and above trims, average of FWD and AWD versions ret.wheelbase = 2.756 ret.steerRatio = 13.6 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications elif candidate in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA_H_G4_FL): @@ -229,10 +229,6 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.9 ret.steerRatio = 16. ret.tireStiffnessFactor = 0.65 - elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: - ret.mass = 1767. # SX Prestige trim support only - ret.wheelbase = 2.756 - ret.steerRatio = 13.6 elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN): ret.wheelbase = 2.81 ret.steerRatio = 13.5 # average of the platforms diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index 38dafddb83..944938659b 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -18,7 +18,6 @@ ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} NO_DATES_PLATFORMS = { # CAN FD CAR.KIA_SPORTAGE_5TH_GEN, - CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.TUCSON_4TH_GEN, # CAN diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index ca62c66c2e..39cc8d296d 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -126,7 +126,6 @@ class CAR(StrEnum): KIA_SORENTO_4TH_GEN = "KIA SORENTO 4TH GEN" KIA_SORENTO_HEV_4TH_GEN = "KIA SORENTO HYBRID 4TH GEN" KIA_SORENTO_PHEV_4TH_GEN = "KIA SORENTO PLUG-IN HYBRID 4TH GEN" - KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" KIA_STINGER_2022 = "KIA STINGER 2022" KIA_CEED = "KIA CEED INTRO ED 2019" @@ -263,7 +262,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.KIA_OPTIMA_H: HyundaiCarInfo("Kia Optima Hybrid 2017", "Advanced Smart Cruise Control", car_parts=CarParts.common([CarHarness.hyundai_c])), CAR.KIA_OPTIMA_H_G4_FL: HyundaiCarInfo("Kia Optima Hybrid 2019", car_parts=CarParts.common([CarHarness.hyundai_h])), CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), + CAR.KIA_SPORTAGE_5TH_GEN: [ + HyundaiCarInfo("Kia Sportage 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), + HyundaiCarInfo("Kia Sportage Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), + ], CAR.KIA_SORENTO: [ HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control & LKAS", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", car_parts=CarParts.common([CarHarness.hyundai_e])), @@ -272,7 +274,6 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2021-23", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.KIA_SORENTO_HEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Hybrid 2021-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", car_parts=CarParts.common([CarHarness.hyundai_n])), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", car_parts=CarParts.common([CarHarness.hyundai_c])), CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), @@ -481,7 +482,7 @@ CAN_GEARS = { "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.KIA_K8_HEV_1ST_GEN, @@ -570,7 +571,6 @@ DBC = { CAR.IONIQ_6: dbc_dict('hyundai_canfd', None), CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), - CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV60_EV_1ST_GEN: dbc_dict('hyundai_canfd', None), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 89c9d17e12..e78cafb085 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -116,7 +116,6 @@ routes = [ CarTestRoute("f9716670b2481438|2023-08-23--14-49-50", HYUNDAI.KIA_OPTIMA_H), CarTestRoute("6a42c1197b2a8179|2023-09-21--10-23-44", HYUNDAI.KIA_OPTIMA_H_G4_FL), CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), - CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_HYBRID_5TH_GEN), CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF), CarTestRoute("c344fd2492c7a9d2|2023-12-11--09-03-23", HYUNDAI.STARIA_4TH_GEN), @@ -162,6 +161,7 @@ routes = [ CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE), CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN), CarTestRoute("09559f1fcaed4704|2023-11-16--02-24-57", HYUNDAI.KIA_SPORTAGE_5TH_GEN), # openpilot longitudinal + CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_5TH_GEN), # hybrid CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA), CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA_GT_I30), CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021), diff --git a/selfdrive/car/tests/test_models_segs.txt b/selfdrive/car/tests/test_models_segs.txt index 6f5748d408..9936dc4b00 100644 --- a/selfdrive/car/tests/test_models_segs.txt +++ b/selfdrive/car/tests/test_models_segs.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2274c383d822cf655f45f00111374420f88f4058ad12766d72e0580e6a1e4e0f -size 125126 +oid sha256:19032387eb89f831be2c05a85faa1a9a0eb76ee9f67c8916128ade4108ce3b12 +size 125112 diff --git a/selfdrive/car/torque_data/override.toml b/selfdrive/car/torque_data/override.toml index 5cdebc1709..a0f1ea8068 100644 --- a/selfdrive/car/torque_data/override.toml +++ b/selfdrive/car/torque_data/override.toml @@ -46,8 +46,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "VOLKSWAGEN PASSAT NMS" = [2.5, 2.5, 0.1] "VOLKSWAGEN SHARAN 2ND GEN" = [2.5, 2.5, 0.1] "HYUNDAI SANTA CRUZ 1ST GEN" = [2.7, 2.7, 0.1] -"KIA SPORTAGE 5TH GEN" = [2.7, 2.7, 0.1] -"KIA SPORTAGE HYBRID 5TH GEN" = [2.5, 2.5, 0.1] +"KIA SPORTAGE 5TH GEN" = [2.6, 2.6, 0.1] "GENESIS GV70 1ST GEN" = [2.42, 2.42, 0.1] "KIA SORENTO PLUG-IN HYBRID 4TH GEN" = [2.5, 2.5, 0.1] "GENESIS GV60 ELECTRIC 1ST GEN" = [2.5, 2.5, 0.1] From b4f958c13f221704012578518b00ab88a4e33faf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Jan 2024 02:41:57 -0600 Subject: [PATCH 646/724] HKG CAN FD: Merge Sorento Plug-in Hybrid and Hybrid (#31241) * merge sorento plug-in hybrid and hybrid * format * allow fuzzy fingerprinting * rm todo * Revert "rm todo" This reverts commit f2457eb58f88f780b25625942dbd9c74202acbb8. * Revert "allow fuzzy fingerprinting" This reverts commit 91f143f3115e31107752a5e84d3f587e6917933f. --- selfdrive/car/hyundai/fingerprints.py | 33 ++++++++++-------------- selfdrive/car/hyundai/interface.py | 6 ++--- selfdrive/car/hyundai/values.py | 14 +++++----- selfdrive/car/tests/routes.py | 2 +- selfdrive/car/tests/test_models_segs.txt | 4 +-- selfdrive/car/torque_data/override.toml | 3 +-- 6 files changed, 25 insertions(+), 37 deletions(-) diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index e84905a34c..35ee682b4b 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -1511,15 +1511,6 @@ FW_VERSIONS = { b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, - CAR.KIA_SORENTO_PHEV_4TH_GEN: { - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ', - ], - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406', - b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', - ], - }, CAR.KIA_EV6: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', @@ -1638,6 +1629,19 @@ FW_VERSIONS = { b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.08 99110-P2000 ', ], }, + CAR.KIA_SORENTO_HEV_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.04 99210-P2000 200330', + b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331', + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406', + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQhe SCC FHCUP 1.00 1.04 99110-P4000 ', + b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ', + b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', + ], + }, CAR.KIA_NIRO_HEV_2ND_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.08 99211-AT000 220531', @@ -1669,17 +1673,6 @@ FW_VERSIONS = { b'\xf1\x00KA4c SCC FHCUP 1.00 1.01 99110-I4000 ', ], }, - CAR.KIA_SORENTO_HEV_4TH_GEN: { - (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.04 99210-P2000 200330', - b'\xf1\x00MQ4HMFC AT KOR LHD 1.00 1.12 99210-P2000 230331', - b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', - ], - (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x00MQhe SCC FHCUP 1.00 1.04 99110-P4000 ', - b'\xf1\x00MQhe SCC FHCUP 1.00 1.07 99110-P4000 ', - ], - }, CAR.KIA_K8_HEV_1ST_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00GL3HMFC AT KOR LHD 1.00 1.03 99211-L8000 210907', diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 345619cbe2..a3210f8071 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -229,15 +229,13 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.9 ret.steerRatio = 16. ret.tireStiffnessFactor = 0.65 - elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN): + elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN): ret.wheelbase = 2.81 ret.steerRatio = 13.5 # average of the platforms if candidate == CAR.KIA_SORENTO_4TH_GEN: ret.mass = 3957 * CV.LB_TO_KG - elif candidate == CAR.KIA_SORENTO_HEV_4TH_GEN: - ret.mass = 4255 * CV.LB_TO_KG else: - ret.mass = 4537 * CV.LB_TO_KG + ret.mass = 4396 * CV.LB_TO_KG elif candidate == CAR.KIA_CARNIVAL_4TH_GEN: ret.mass = 2087. ret.wheelbase = 3.09 diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 39cc8d296d..77e0ad6fb1 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -125,7 +125,6 @@ class CAR(StrEnum): KIA_SORENTO = "KIA SORENTO GT LINE 2018" KIA_SORENTO_4TH_GEN = "KIA SORENTO 4TH GEN" KIA_SORENTO_HEV_4TH_GEN = "KIA SORENTO HYBRID 4TH GEN" - KIA_SORENTO_PHEV_4TH_GEN = "KIA SORENTO PLUG-IN HYBRID 4TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" KIA_STINGER_2022 = "KIA STINGER 2022" KIA_CEED = "KIA CEED INTRO ED 2019" @@ -272,8 +271,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", car_parts=CarParts.common([CarHarness.hyundai_e])), ], CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2021-23", car_parts=CarParts.common([CarHarness.hyundai_k])), - CAR.KIA_SORENTO_HEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Hybrid 2021-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), - CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + CAR.KIA_SORENTO_HEV_4TH_GEN: [ + HyundaiCarInfo("Kia Sorento Hybrid 2021-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + ], CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", car_parts=CarParts.common([CarHarness.hyundai_c])), CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), @@ -482,15 +483,13 @@ CAN_GEARS = { "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, - CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.IONIQ_6, CAR.TUCSON_4TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN, CAR.KIA_NIRO_EV_2ND_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN, CAR.KONA_EV_2ND_GEN, CAR.KIA_K8_HEV_1ST_GEN, CAR.STARIA_4TH_GEN} # The radar does SCC on these cars when HDA I, rather than the camera -CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, - CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN} +CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.GENESIS_GV80, CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN} # These CAN FD cars do not accept communication control to disable the ADAS ECU, # responds with 0x7F2822 - 'conditions not correct' @@ -572,7 +571,6 @@ DBC = { CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None), - CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV60_EV_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SORENTO_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_NIRO_HEV_2ND_GEN: dbc_dict('hyundai_canfd', None), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index e78cafb085..8db950ced0 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -125,7 +125,7 @@ routes = [ CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), CarTestRoute("1d0d000db3370fd0|2023-01-04--22-28-42", HYUNDAI.KIA_SORENTO_4TH_GEN, segment=5), CarTestRoute("fc19648042eb6896|2023-08-16--11-43-27", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN, segment=14), - CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_PHEV_4TH_GEN), + CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_HEV_4TH_GEN), # plug-in hybrid CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE), CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2 CarTestRoute("eb4eae1476647463|2023-08-26--18-07-04", HYUNDAI.IONIQ_6, segment=6), # HDA2 diff --git a/selfdrive/car/tests/test_models_segs.txt b/selfdrive/car/tests/test_models_segs.txt index 9936dc4b00..ca089bbdde 100644 --- a/selfdrive/car/tests/test_models_segs.txt +++ b/selfdrive/car/tests/test_models_segs.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19032387eb89f831be2c05a85faa1a9a0eb76ee9f67c8916128ade4108ce3b12 -size 125112 +oid sha256:cadad21e1230729c70cffb4c46dd0a5dda4eec1a262b1bcd9f1b6b98265c20b5 +size 125104 diff --git a/selfdrive/car/torque_data/override.toml b/selfdrive/car/torque_data/override.toml index a0f1ea8068..86723efb7b 100644 --- a/selfdrive/car/torque_data/override.toml +++ b/selfdrive/car/torque_data/override.toml @@ -48,16 +48,15 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "HYUNDAI SANTA CRUZ 1ST GEN" = [2.7, 2.7, 0.1] "KIA SPORTAGE 5TH GEN" = [2.6, 2.6, 0.1] "GENESIS GV70 1ST GEN" = [2.42, 2.42, 0.1] -"KIA SORENTO PLUG-IN HYBRID 4TH GEN" = [2.5, 2.5, 0.1] "GENESIS GV60 ELECTRIC 1ST GEN" = [2.5, 2.5, 0.1] "KIA SORENTO 4TH GEN" = [2.5, 2.5, 0.1] +"KIA SORENTO HYBRID 4TH GEN" = [2.5, 2.5, 0.1] "KIA NIRO HYBRID 2ND GEN" = [2.42, 2.5, 0.12] "KIA NIRO EV 2ND GEN" = [2.05, 2.5, 0.14] "GENESIS GV80 2023" = [2.5, 2.5, 0.1] "KIA CARNIVAL 4TH GEN" = [1.75, 1.75, 0.15] "GMC ACADIA DENALI 2018" = [1.6, 1.6, 0.2] "LEXUS IS 2023" = [2.0, 2.0, 0.1] -"KIA SORENTO HYBRID 4TH GEN" = [2.5, 2.5, 0.1] "HYUNDAI KONA ELECTRIC 2ND GEN" = [2.5, 2.5, 0.1] "HYUNDAI IONIQ 6 2023" = [2.5, 2.5, 0.1] "HYUNDAI AZERA 6TH GEN" = [1.8, 1.8, 0.1] From e4e161f8dedce5b2c87f0e833681c53f2fb106a0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Jan 2024 02:52:49 -0600 Subject: [PATCH 647/724] Hyundai CAN FD: enable enhanced fuzzy fingerprinting for Sorento & Sorento Hybrid (#31242) * allow fuzzy fingerprinting * rm todo * better cmt --- selfdrive/car/hyundai/tests/test_hyundai.py | 4 ++-- selfdrive/car/hyundai/values.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index 944938659b..c7100d03f0 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -5,7 +5,7 @@ import unittest from cereal import car from openpilot.selfdrive.car.fw_versions import build_fw_dict from openpilot.selfdrive.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \ - HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, \ + HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, CANFD_FUZZY_WHITELIST, \ UNSUPPORTED_LONGITUDINAL_CAR, PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \ get_platform_codes from openpilot.selfdrive.car.hyundai.fingerprints import FW_VERSIONS @@ -181,7 +181,7 @@ class TestHyundaiFingerprint(unittest.TestCase): CAR.GENESIS_G70, # shared platform code, part number, and date CAR.GENESIS_G70_2020, } - excluded_platforms |= CANFD_CAR - EV_CAR # shared platform codes + excluded_platforms |= CANFD_CAR - EV_CAR - CANFD_FUZZY_WHITELIST # shared platform codes excluded_platforms |= NO_DATES_PLATFORMS # date codes are required to match platforms_with_shared_codes = set() diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 77e0ad6fb1..dfa4f70c2c 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -336,8 +336,7 @@ def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> Set[str]: # Non-electric CAN FD platforms often do not have platform code specifiers needed # to distinguish between hybrid and ICE. All EVs so far are either exclusively # electric or specify electric in the platform code. - # TODO: whitelist platforms that we've seen hybrid and ICE versions of that have these specifiers - fuzzy_platform_blacklist = {str(c) for c in set(CANFD_CAR - EV_CAR)} + fuzzy_platform_blacklist = {str(c) for c in (CANFD_CAR - EV_CAR - CANFD_FUZZY_WHITELIST)} candidates: Set[str] = set() for candidate, fws in offline_fw_versions.items(): @@ -402,6 +401,9 @@ PLATFORM_CODE_FW_PATTERN = re.compile(b'((?<=' + HYUNDAI_VERSION_REQUEST_LONG[1: DATE_FW_PATTERN = re.compile(b'(?<=[ -])([0-9]{6}$)') PART_NUMBER_FW_PATTERN = re.compile(b'(?<=[0-9][.,][0-9]{2} )([0-9]{5}[-/]?[A-Z][A-Z0-9]{3}[0-9])') +# We've seen both ICE and hybrid for these platforms, and they have hybrid descriptors (e.g. MQ4 vs MQ4H) +CANFD_FUZZY_WHITELIST = {CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_HEV_4TH_GEN} + # List of ECUs expected to have platform codes, camera and radar should exist on all cars # TODO: use abs, it has the platform code and part number on many platforms PLATFORM_CODE_ECUS = [Ecu.fwdRadar, Ecu.fwdCamera, Ecu.eps] From 883f9458eb6156d161075f9d77d152e8d2a8aaf6 Mon Sep 17 00:00:00 2001 From: Elshan <8066214+ekodikara@users.noreply.github.com> Date: Wed, 31 Jan 2024 22:20:58 +1100 Subject: [PATCH 648/724] Mazda: add missing FW versions for CX9 2022 (#30725) * Adding CX9 engine ECU FW * Adding cx92023 fingerprints for transmission * add fingerprints --------- Co-authored-by: Elshan Co-authored-by: Shane Smiskol --- selfdrive/car/mazda/fingerprints.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/car/mazda/fingerprints.py b/selfdrive/car/mazda/fingerprints.py index aa91f0fb31..292f407935 100644 --- a/selfdrive/car/mazda/fingerprints.py +++ b/selfdrive/car/mazda/fingerprints.py @@ -235,6 +235,7 @@ FW_VERSIONS = { b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM7-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -253,6 +254,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PXM7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, } From 5195f0a6240ec2f96b472d8ee758ddbbbc5243dd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Jan 2024 11:52:44 -0600 Subject: [PATCH 649/724] [bot] Fingerprints: add missing FW versions from new users (#31245) --- selfdrive/car/chrysler/fingerprints.py | 3 +++ selfdrive/car/mazda/fingerprints.py | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index f9a3fe5b10..940d73c249 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -395,6 +395,7 @@ FW_VERSIONS = { b'68527383AD', b'68527387AE', b'68527403AC', + b'68631938AA', b'68631942AA', ], (Ecu.srs, 0x744, None): [ @@ -459,6 +460,7 @@ FW_VERSIONS = { b'68552791AB', b'68552794AA', b'68585106AB', + b'68585107AB', b'68585108AB', b'68585109AB', b'68585112AB', @@ -508,6 +510,7 @@ FW_VERSIONS = { b'68539651AD', b'68586101AA ', b'68586105AB ', + b'68629922AC ', b'68629926AC ', ], (Ecu.transmission, 0x7e1, None): [ diff --git a/selfdrive/car/mazda/fingerprints.py b/selfdrive/car/mazda/fingerprints.py index 292f407935..aa91f0fb31 100644 --- a/selfdrive/car/mazda/fingerprints.py +++ b/selfdrive/car/mazda/fingerprints.py @@ -235,7 +235,6 @@ FW_VERSIONS = { b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM7-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -254,7 +253,6 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PXM7-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, } From 6010a39bf8cb030b5b10e1d0657f9291467b6872 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 31 Jan 2024 19:43:58 -0500 Subject: [PATCH 650/724] LogReader: skip file_exists check for comma_api (#31251) * valid file * missed this one --- tools/lib/logreader.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index c53b9c4c79..decffccd66 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -89,28 +89,31 @@ def create_slice_from_string(s: str): return start return slice(start, end, step) -def auto_strategy(rlog_paths, qlog_paths, interactive): +def default_valid_file(fn): + return fn is not None and file_exists(fn) + +def auto_strategy(rlog_paths, qlog_paths, interactive, valid_file): # auto select logs based on availability - if any(rlog is None or not file_exists(rlog) for rlog in rlog_paths): + if any(rlog is None or not valid_file(rlog) for rlog in rlog_paths): if interactive: if input("Some rlogs were not found, would you like to fallback to qlogs for those segments? (y/n) ").lower() != "y": return rlog_paths else: cloudlog.warning("Some rlogs were not found, falling back to qlogs for those segments...") - return [rlog if (rlog is not None and file_exists(rlog)) else (qlog if (qlog is not None and file_exists(qlog)) else None) - for (rlog, qlog) in zip(rlog_paths, qlog_paths, strict=True)] + return [rlog if (valid_file(rlog)) else (qlog if (valid_file(qlog)) else None) + for (rlog, qlog) in zip(rlog_paths, qlog_paths, strict=True)] return rlog_paths -def apply_strategy(mode: ReadMode, rlog_paths, qlog_paths): +def apply_strategy(mode: ReadMode, rlog_paths, qlog_paths, valid_file=default_valid_file): if mode == ReadMode.RLOG: return rlog_paths elif mode == ReadMode.QLOG: return qlog_paths elif mode == ReadMode.AUTO: - return auto_strategy(rlog_paths, qlog_paths, False) + return auto_strategy(rlog_paths, qlog_paths, False, valid_file) elif mode == ReadMode.AUTO_INTERACIVE: - return auto_strategy(rlog_paths, qlog_paths, True) + return auto_strategy(rlog_paths, qlog_paths, True, valid_file) def parse_slice(sr: SegmentRange): s = create_slice_from_string(sr._slice) @@ -133,7 +136,11 @@ def comma_api_source(sr: SegmentRange, mode: ReadMode): rlog_paths = [route.log_paths()[seg] for seg in segs] qlog_paths = [route.qlog_paths()[seg] for seg in segs] - return apply_strategy(mode, rlog_paths, qlog_paths) + # comma api will have already checked if the file exists + def valid_file(fn): + return fn is not None + + return apply_strategy(mode, rlog_paths, qlog_paths, valid_file=valid_file) def internal_source(sr: SegmentRange, mode: ReadMode): segs = parse_slice(sr) From 7222eb3665aea9483fbaa672640c22b3f9856e22 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Jan 2024 20:20:33 -0600 Subject: [PATCH 651/724] Nissan: use bus 0 to fingerprint (#31243) * Nissan: use bus 0 to fingerprint * update refs * switch OBD query to be logging instead, might want OBD logging for one more release --- selfdrive/car/nissan/values.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index c013b2056f..d064ce894d 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -59,7 +59,7 @@ NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83' NISSAN_RX_OFFSET = 0x20 FW_QUERY_CONFIG = FwQueryConfig( - requests=[request for bus, logging in ((0, True), (1, False)) for request in [ + requests=[request for bus, logging in ((0, False), (1, True)) for request in [ Request( [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP], [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP], From 5c85925e9bc5baf709f4630545a76a11e044f936 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 31 Jan 2024 21:44:04 -0500 Subject: [PATCH 652/724] test_athenad: create with_upload_handler decorator (#31250) cleaner --- selfdrive/athena/tests/test_athenad.py | 143 +++++++++++-------------- 1 file changed, 60 insertions(+), 83 deletions(-) diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 9c7582a260..2fecab1b1b 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from functools import partial +from functools import partial, wraps import json import multiprocessing import os @@ -42,6 +42,20 @@ def seed_athena_server(host, port): with_mock_athena = partial(with_http_server, handler=HTTPRequestHandler, setup=seed_athena_server) +def with_upload_handler(func): + @wraps(func) + def wrapper(*args, **kwargs): + end_event = threading.Event() + thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) + thread.start() + try: + return func(*args, **kwargs) + finally: + end_event.set() + thread.join() + return wrapper + + class TestAthenadMethods(unittest.TestCase): @classmethod def setUpClass(cls): @@ -209,77 +223,59 @@ class TestAthenadMethods(unittest.TestCase): self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']}) @with_mock_athena + @with_upload_handler def test_upload_handler(self, host): fn = self._create_file('qlog.bz2') item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() - athenad.upload_queue.put_nowait(item) - try: - self._wait_for_upload() - time.sleep(0.1) + self._wait_for_upload() + time.sleep(0.1) - # TODO: verify that upload actually succeeded - self.assertEqual(athenad.upload_queue.qsize(), 0) - finally: - end_event.set() + # TODO: verify that upload actually succeeded + self.assertEqual(athenad.upload_queue.qsize(), 0) + @parameterized.expand([(500, True), (412, False)]) @with_mock_athena @mock.patch('requests.put') - def test_upload_handler_retry(self, host, mock_put): - for status, retry in ((500, True), (412, False)): - mock_put.return_value.status_code = status - fn = self._create_file('qlog.bz2') - item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) + @with_upload_handler + def test_upload_handler_retry(self, status, retry, mock_put, host): + mock_put.return_value.status_code = status + fn = self._create_file('qlog.bz2') + item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() + athenad.upload_queue.put_nowait(item) + self._wait_for_upload() + time.sleep(0.1) - athenad.upload_queue.put_nowait(item) - try: - self._wait_for_upload() - time.sleep(0.1) + self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0) - self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0) - finally: - end_event.set() - - if retry: - self.assertEqual(athenad.upload_queue.get().retry_count, 1) + if retry: + self.assertEqual(athenad.upload_queue.get().retry_count, 1) + @with_upload_handler def test_upload_handler_timeout(self): """When an upload times out or fails to connect it should be placed back in the queue""" fn = self._create_file('qlog.bz2') item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) item_no_retry = replace(item, retry_count=MAX_RETRY_COUNT) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() + athenad.upload_queue.put_nowait(item_no_retry) + self._wait_for_upload() + time.sleep(0.1) - try: - athenad.upload_queue.put_nowait(item_no_retry) - self._wait_for_upload() - time.sleep(0.1) + # Check that upload with retry count exceeded is not put back + self.assertEqual(athenad.upload_queue.qsize(), 0) - # Check that upload with retry count exceeded is not put back - self.assertEqual(athenad.upload_queue.qsize(), 0) + athenad.upload_queue.put_nowait(item) + self._wait_for_upload() + time.sleep(0.1) - athenad.upload_queue.put_nowait(item) - self._wait_for_upload() - time.sleep(0.1) - - # Check that upload item was put back in the queue with incremented retry count - self.assertEqual(athenad.upload_queue.qsize(), 1) - self.assertEqual(athenad.upload_queue.get().retry_count, 1) - - finally: - end_event.set() + # Check that upload item was put back in the queue with incremented retry count + self.assertEqual(athenad.upload_queue.qsize(), 1) + self.assertEqual(athenad.upload_queue.get().retry_count, 1) + @with_upload_handler def test_cancelUpload(self): item = athenad.UploadItem(path="qlog.bz2", url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='id', allow_cellular=True) @@ -288,18 +284,13 @@ class TestAthenadMethods(unittest.TestCase): self.assertIn(item.id, athenad.cancelled_uploads) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() - try: - self._wait_for_upload() - time.sleep(0.1) + self._wait_for_upload() + time.sleep(0.1) - self.assertEqual(athenad.upload_queue.qsize(), 0) - self.assertEqual(len(athenad.cancelled_uploads), 0) - finally: - end_event.set() + self.assertEqual(athenad.upload_queue.qsize(), 0) + self.assertEqual(len(athenad.cancelled_uploads), 0) + @with_upload_handler def test_cancelExpiry(self): t_future = datetime.now() - timedelta(days=40) ts = int(t_future.strftime("%s")) * 1000 @@ -308,42 +299,28 @@ class TestAthenadMethods(unittest.TestCase): fn = self._create_file('qlog.bz2') item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=ts, id='', allow_cellular=True) + athenad.upload_queue.put_nowait(item) + self._wait_for_upload() + time.sleep(0.1) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() - try: - athenad.upload_queue.put_nowait(item) - self._wait_for_upload() - time.sleep(0.1) - - self.assertEqual(athenad.upload_queue.qsize(), 0) - finally: - end_event.set() + self.assertEqual(athenad.upload_queue.qsize(), 0) def test_listUploadQueueEmpty(self): items = dispatcher["listUploadQueue"]() self.assertEqual(len(items), 0) @with_http_server + @with_upload_handler def test_listUploadQueueCurrent(self, host: str): fn = self._create_file('qlog.bz2') item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() + athenad.upload_queue.put_nowait(item) + self._wait_for_upload() - try: - athenad.upload_queue.put_nowait(item) - self._wait_for_upload() - - items = dispatcher["listUploadQueue"]() - self.assertEqual(len(items), 1) - self.assertTrue(items[0]['current']) - - finally: - end_event.set() + items = dispatcher["listUploadQueue"]() + self.assertEqual(len(items), 1) + self.assertTrue(items[0]['current']) def test_listUploadQueue(self): item = athenad.UploadItem(path="qlog.bz2", url="http://localhost:44444/qlog.bz2", headers={}, From 1ab37454009d5a40e441350e1abaa615ab36ec6d Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Wed, 31 Jan 2024 19:45:10 -0700 Subject: [PATCH 653/724] GM: Fix wheel direction signals (#31258) * Fix wheel direction signals * Update name * Add comment * Remove signal value table Co-authored-by: Shane Smiskol * bump * add 3 --------- Co-authored-by: Shane Smiskol --- opendbc | 2 +- selfdrive/car/gm/carstate.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/opendbc b/opendbc index 3cfd0bf4eb..7397e466d9 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 3cfd0bf4eb73953f3d179dddc1ba2c92e317188c +Subproject commit 7397e466d9cfd7f5bc1f49218b8d2afeedec582b diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index f7309939b1..c3d061de78 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -33,7 +33,9 @@ class CarState(CarStateBase): self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"] self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"] self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"]) - self.moving_backward = pt_cp.vl["EBCMWheelSpdRear"]["MovingBackward"] != 0 + # This is to avoid a fault where you engage while still moving backwards after shifting to D. + # An Equinox has been seen with an unsupported status (3), so only check if either wheel is in reverse (2) + self.moving_backward = (pt_cp.vl["EBCMWheelSpdRear"]["RLWheelDir"] == 2) or (pt_cp.vl["EBCMWheelSpdRear"]["RRWheelDir"] == 2) # Variables used for avoiding LKAS faults self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0 From be33b0049982ade3cc639998c8142b373693c929 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 31 Jan 2024 21:46:40 -0500 Subject: [PATCH 654/724] Jenkins: better timeout when disconnected from devices (#31256) * timeout better * timeoutes --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index e45bcd16b2..efea896d94 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,7 +12,7 @@ def retryWithDelay(int maxRetries, int delay, Closure body) { def device(String ip, String step_label, String cmd) { withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) { def ssh_cmd = """ -ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END' +ssh -tt -o ConnectTimeout=30 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END' set -e From 086c509fde806bb03f89765800d9667f4de6a372 Mon Sep 17 00:00:00 2001 From: Justin Newberry Date: Wed, 31 Jan 2024 21:47:49 -0500 Subject: [PATCH 655/724] Create message mocking tools (#31249) * add mocking tools * fix map renderer * use for power draw * fix those * whitespace * rename to services * fix the rate * remove --- common/mock/__init__.py | 51 +++++++++++++++++++ common/mock/generators.py | 20 ++++++++ .../controls/lib/tests/test_latcontrol.py | 4 +- selfdrive/navd/tests/test_map_renderer.py | 18 +------ selfdrive/ui/tests/test_ui/run.py | 6 +-- system/hardware/tici/tests/test_power_draw.py | 16 +----- 6 files changed, 79 insertions(+), 36 deletions(-) create mode 100644 common/mock/__init__.py create mode 100644 common/mock/generators.py diff --git a/common/mock/__init__.py b/common/mock/__init__.py new file mode 100644 index 0000000000..e0dbf45c74 --- /dev/null +++ b/common/mock/__init__.py @@ -0,0 +1,51 @@ +""" +Utilities for generating mock messages for testing. +example in common/tests/test_mock.py +""" + + +import functools +import threading +from typing import List, Union +from cereal.messaging import PubMaster +from cereal.services import SERVICE_LIST +from openpilot.common.mock.generators import generate_liveLocationKalman +from openpilot.common.realtime import Ratekeeper + + +MOCK_GENERATOR = { + "liveLocationKalman": generate_liveLocationKalman +} + + +def generate_messages_loop(services: List[str], done: threading.Event): + pm = PubMaster(services) + rk = Ratekeeper(100) + i = 0 + while not done.is_set(): + for s in services: + should_send = i % (100/SERVICE_LIST[s].frequency) == 0 + if should_send: + message = MOCK_GENERATOR[s]() + pm.send(s, message) + i += 1 + rk.keep_time() + + +def mock_messages(services: Union[List[str], str]): + if isinstance(services, str): + services = [services] + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + done = threading.Event() + t = threading.Thread(target=generate_messages_loop, args=(services, done)) + t.start() + try: + return func(*args, **kwargs) + finally: + done.set() + t.join() + return wrapper + return decorator diff --git a/common/mock/generators.py b/common/mock/generators.py new file mode 100644 index 0000000000..40951faf85 --- /dev/null +++ b/common/mock/generators.py @@ -0,0 +1,20 @@ +from cereal import messaging + + +LOCATION1 = (32.7174, -117.16277) +LOCATION2 = (32.7558, -117.2037) + +LLK_DECIMATION = 10 +RENDER_FRAMES = 15 +DEFAULT_ITERATIONS = RENDER_FRAMES * LLK_DECIMATION + + +def generate_liveLocationKalman(location=LOCATION1): + msg = messaging.new_message('liveLocationKalman') + msg.liveLocationKalman.positionGeodetic = {'value': [*location, 0], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.positionECEF = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.calibratedOrientationNED = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.velocityCalibrated = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.status = 'valid' + msg.liveLocationKalman.gpsOK = True + return msg diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index 248abf1d7b..866270ca60 100755 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel -from openpilot.selfdrive.navd.tests.test_map_renderer import gen_llk +from openpilot.common.mock.generators import generate_liveLocationKalman class TestLatControl(unittest.TestCase): @@ -32,7 +32,7 @@ class TestLatControl(unittest.TestCase): params = log.LiveParametersData.new_message() - llk = gen_llk() + llk = generate_liveLocationKalman() for _ in range(1000): _, _, lac_log = controller.update(True, CS, VM, params, False, 1, llk) diff --git a/selfdrive/navd/tests/test_map_renderer.py b/selfdrive/navd/tests/test_map_renderer.py index a7289d02d9..b5f186dbb0 100755 --- a/selfdrive/navd/tests/test_map_renderer.py +++ b/selfdrive/navd/tests/test_map_renderer.py @@ -11,30 +11,16 @@ import cereal.messaging as messaging from typing import Any from cereal.visionipc import VisionIpcClient, VisionStreamType +from openpilot.common.mock.generators import LLK_DECIMATION, LOCATION1, LOCATION2, generate_liveLocationKalman from openpilot.selfdrive.test.helpers import with_processes -LLK_DECIMATION = 10 CACHE_PATH = "/data/mbgl-cache-navd.db" -LOCATION1 = (32.7174, -117.16277) -LOCATION2 = (32.7558, -117.2037) - RENDER_FRAMES = 15 DEFAULT_ITERATIONS = RENDER_FRAMES * LLK_DECIMATION - LOCATION1_REPEATED = [LOCATION1] * DEFAULT_ITERATIONS LOCATION2_REPEATED = [LOCATION2] * DEFAULT_ITERATIONS -def gen_llk(location=LOCATION1): - msg = messaging.new_message('liveLocationKalman') - msg.liveLocationKalman.positionGeodetic = {'value': [*location, 0], 'std': [0., 0., 0.], 'valid': True} - msg.liveLocationKalman.positionECEF = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} - msg.liveLocationKalman.calibratedOrientationNED = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} - msg.liveLocationKalman.velocityCalibrated = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} - msg.liveLocationKalman.status = 'valid' - msg.liveLocationKalman.gpsOK = True - return msg - class MapBoxInternetDisabledRequestHandler(http.server.BaseHTTPRequestHandler): INTERNET_ACTIVE = True @@ -134,7 +120,7 @@ class TestMapRenderer(unittest.TestCase): if starting_frame_id is None: starting_frame_id = prev_frame_id - llk = gen_llk(location) + llk = generate_liveLocationKalman(location) self.pm.send("liveLocationKalman", llk) self.pm.wait_for_readers_to_update("liveLocationKalman", 10) self.sm.update(1000 if frame_expected else 0) diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index 1fea759efb..7a2ac9a110 100644 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -15,10 +15,10 @@ from cereal import messaging, car, log from cereal.visionipc import VisionIpcServer, VisionStreamType from cereal.messaging import SubMaster, PubMaster +from openpilot.common.mock import mock_messages from openpilot.common.params import Params from openpilot.common.realtime import DT_MDL from openpilot.common.transformations.camera import tici_f_frame_size -from openpilot.selfdrive.navd.tests.test_map_renderer import gen_llk from openpilot.selfdrive.test.helpers import with_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state from openpilot.tools.webcam.camera import Camera @@ -94,12 +94,10 @@ def setup_onroad(click, pm: PubMaster): pm.send(msg.which(), msg) server.send(cam_meta.stream, IMG_BYTES, cs.frameId, cs.timestampSof, cs.timestampEof) +@mock_messages(['liveLocationKalman']) def setup_onroad_map(click, pm: PubMaster): setup_onroad(click, pm) - dat = gen_llk() - pm.send("liveLocationKalman", dat) - click(500, 500) time.sleep(UI_DELAY) # give time for the map to render diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index eed2ce231b..0518eec543 100755 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -2,7 +2,6 @@ import pytest import unittest import time -import threading import numpy as np from dataclasses import dataclass from tabulate import tabulate @@ -10,12 +9,12 @@ from typing import List import cereal.messaging as messaging from cereal.services import SERVICE_LIST +from openpilot.common.mock import mock_messages from openpilot.selfdrive.car.car_helpers import write_car_param from openpilot.system.hardware import HARDWARE from openpilot.system.hardware.tici.power_monitor import get_power from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.selfdrive.manager.manager import manager_cleanup -from openpilot.selfdrive.navd.tests.test_map_renderer import gen_llk SAMPLE_TIME = 8 # seconds to sample power @@ -37,14 +36,6 @@ PROCS = [ Proc('navmodeld', 0.05, msgs=['navModel']), ] -def send_llk_msg(done): - # Send liveLocationKalman at 20Hz - pm = messaging.PubMaster(['liveLocationKalman']) - while not done.is_set(): - msg = gen_llk() - pm.send('liveLocationKalman', msg) - time.sleep(1/20.) - @pytest.mark.tici class TestPowerDraw(unittest.TestCase): @@ -60,11 +51,9 @@ class TestPowerDraw(unittest.TestCase): def tearDown(self): manager_cleanup() + @mock_messages(['liveLocationKalman']) def test_camera_procs(self): baseline = get_power() - done = threading.Event() - thread = threading.Thread(target=send_llk_msg, args=(done,), daemon=True) - thread.start() prev = baseline used = {} @@ -82,7 +71,6 @@ class TestPowerDraw(unittest.TestCase): for msg,sock in socks.items(): msg_counts[msg] = len(messaging.drain_sock_raw(sock)) - done.set() manager_cleanup() tab = [['process', 'expected (W)', 'measured (W)', '# msgs expected', '# msgs received']] From f0b6f489489731b4d968880d6d9e5a58dbc14035 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 1 Feb 2024 02:28:00 -0600 Subject: [PATCH 656/724] Mazda: use bus 0 to fingerprint (#31261) * for testing * switch OBD port to logging * revert * cmt * cmt * remove OBD query * Update selfdrive/car/mazda/values.py --- selfdrive/car/mazda/values.py | 7 +------ selfdrive/car/tests/test_fw_fingerprint.py | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index c95ae162f9..b43ab3df66 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -67,16 +67,11 @@ class Buttons: FW_QUERY_CONFIG = FwQueryConfig( requests=[ - Request( - [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], - [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], - ), - # Log responses on powertrain bus + # TODO: check data to ensure ABS does not skip ISO-TP frames on bus 0 Request( [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], bus=0, - logging=True, ), ], ) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 620fa25691..f2de3ab770 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -246,7 +246,7 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.9 + total_ref_time = 6.8 brand_ref_times = { 1: { 'gm': 0.5, @@ -255,7 +255,7 @@ class TestFwFingerprintTiming(unittest.TestCase): 'ford': 0.1, 'honda': 0.55, 'hyundai': 0.65, - 'mazda': 0.2, + 'mazda': 0.1, 'nissan': 0.8, 'subaru': 0.45, 'tesla': 0.2, From 056b330e8bd1ffafdd0bd310782dd140c8f2174d Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Thu, 1 Feb 2024 04:12:48 -0800 Subject: [PATCH 657/724] LatControlTorque: Add more inputs (#31252) * add history and state to the ff inputs * add history * resolve comments * remove history, simplify * don't compute lateral accel, roll comp always --- selfdrive/car/gm/interface.py | 8 +++---- selfdrive/car/interfaces.py | 19 ++++++++++++----- selfdrive/controls/lib/latcontrol_torque.py | 23 +++++++++++---------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index af34100d74..e0f616dbf9 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -7,7 +7,7 @@ from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus -from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD +from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs from openpilot.selfdrive.controls.lib.drive_helpers import get_friction ButtonType = car.CarState.ButtonEvent.Type @@ -44,8 +44,8 @@ class CarInterface(CarInterfaceBase): else: return CarInterfaceBase.get_steer_feedforward_default - def torque_from_lateral_accel_siglin(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, - lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float: + def torque_from_lateral_accel_siglin(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning, lateral_accel_error: float, + lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float: friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) def sig(val): @@ -58,7 +58,7 @@ class CarInterface(CarInterfaceBase): non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint) assert non_linear_torque_params, "The params are not defined" a, b, c, _ = non_linear_torque_params - steer_torque = (sig(lateral_accel_value * a) * b) + (lateral_accel_value * c) + steer_torque = (sig(latcontrol_inputs.lateral_acceleration * a) * b) + (latcontrol_inputs.lateral_acceleration * c) return float(steer_torque) + friction def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 59657c5437..7ecc47f2a4 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -4,7 +4,7 @@ import numpy as np import tomllib from abc import abstractmethod, ABC from enum import StrEnum -from typing import Any, Dict, Optional, Tuple, List, Callable +from typing import Any, Dict, Optional, Tuple, List, Callable, NamedTuple from cereal import car from openpilot.common.basedir import BASEDIR @@ -20,7 +20,6 @@ from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel ButtonType = car.CarState.ButtonEvent.Type GearShifter = car.CarState.GearShifter EventName = car.CarEvent.EventName -TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, float, float, bool], float] MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 @@ -32,6 +31,16 @@ TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.toml') +class LatControlInputs(NamedTuple): + lateral_acceleration: float + roll_compensation: float + vego: float + aego: float + + +TorqueFromLateralAccelCallbackType = Callable[[LatControlInputs, car.CarParams.LateralTorqueTuning, float, float, bool, bool], float] + + def get_torque_params(candidate): with open(TORQUE_SUBSTITUTE_PATH, 'rb') as f: sub = tomllib.load(f) @@ -130,11 +139,11 @@ class CarInterfaceBase(ABC): def get_steer_feedforward_function(self): return self.get_steer_feedforward_default - def torque_from_lateral_accel_linear(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, - lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float: + def torque_from_lateral_accel_linear(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning, + lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float: # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) - return (lateral_accel_value / float(torque_params.latAccelFactor)) + friction + return (latcontrol_inputs.lateral_acceleration / float(torque_params.latAccelFactor)) + friction def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: return self.torque_from_lateral_accel_linear diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 65fd1b51c5..34b0d47124 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -2,6 +2,7 @@ import math from cereal import log from openpilot.common.numpy_fast import interp +from openpilot.selfdrive.car.interfaces import LatControlInputs from openpilot.selfdrive.controls.lib.latcontrol import LatControl from openpilot.selfdrive.controls.lib.pid import PIDController from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY @@ -38,16 +39,16 @@ class LatControlTorque(LatControl): def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk): pid_log = log.ControlsState.LateralTorqueState.new_message() - if not active: output_torque = 0.0 pid_log.active = False else: + actual_curvature_vm = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY if self.use_steering_angle: - actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + actual_curvature = actual_curvature_vm curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) else: - actual_curvature_vm = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) actual_curvature_llk = llk.angularVelocityCalibrated.value[2] / CS.vEgo actual_curvature = interp(CS.vEgo, [2.0, 5.0], [actual_curvature_vm, actual_curvature_llk]) curvature_deadzone = 0.0 @@ -61,15 +62,15 @@ class LatControlTorque(LatControl): low_speed_factor = interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2 setpoint = desired_lateral_accel + low_speed_factor * desired_curvature measurement = actual_lateral_accel + low_speed_factor * actual_curvature - gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY - torque_from_setpoint = self.torque_from_lateral_accel(setpoint, self.torque_params, setpoint, - lateral_accel_deadzone, friction_compensation=False) - torque_from_measurement = self.torque_from_lateral_accel(measurement, self.torque_params, measurement, - lateral_accel_deadzone, friction_compensation=False) + gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation + torque_from_setpoint = self.torque_from_lateral_accel(LatControlInputs(setpoint, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, + setpoint, lateral_accel_deadzone, friction_compensation=False, gravity_adjusted=False) + torque_from_measurement = self.torque_from_lateral_accel(LatControlInputs(measurement, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, + measurement, lateral_accel_deadzone, friction_compensation=False, gravity_adjusted=False) pid_log.error = torque_from_setpoint - torque_from_measurement - ff = self.torque_from_lateral_accel(gravity_adjusted_lateral_accel, self.torque_params, - desired_lateral_accel - actual_lateral_accel, - lateral_accel_deadzone, friction_compensation=True) + ff = self.torque_from_lateral_accel(LatControlInputs(gravity_adjusted_lateral_accel, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, + desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, friction_compensation=True, + gravity_adjusted=True) freeze_integrator = steer_limited or CS.steeringPressed or CS.vEgo < 5 output_torque = self.pid.update(pid_log.error, From 628c829d195434a99c1d83e8119cf0d4d53c167c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20W=C3=B6rmann?= Date: Thu, 1 Feb 2024 19:31:10 +0100 Subject: [PATCH 658/724] GH Actions PR review bot: Check only for substrings in checkbox text when validating PR template (#31262) check only for substrings in checkbox text --- .github/workflows/auto_pr_review.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 820801a2c7..abb6c38d6b 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -89,6 +89,13 @@ jobs: return subset.every((item) => superset.includes(item)); }; + // Utility function to check if a list of checkboxes is a subset of another list of checkboxes + isCheckboxSubset = (templateCheckBoxTexts, prTextCheckBoxTexts) => { + // Check if each template checkbox text is a substring of at least one PR checkbox text + // (user should be allowed to add additional text) + return templateCheckBoxTexts.every((item) => prTextCheckBoxTexts.some((element) => element.includes(item))) + } + // Get filenames of all currently checked-in PR templates const template_contents = await github.rest.repos.getContent({ owner: context.repo.owner, @@ -146,7 +153,7 @@ jobs: template.checkboxes + "]" ); if ( - isSubset(template.checkboxes, pr_checkboxes) && + isCheckboxSubset(template.checkboxes, pr_checkboxes) && isSubset(template.headings, pr_headings) ) { console.debug("Found matching template!"); From 619625625ce9dc67835aaaca266ccd66f63545e3 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Thu, 1 Feb 2024 12:30:43 -0800 Subject: [PATCH 659/724] CHEVROLET BOLT EUV 2022: Add a simple neural feed forward (#31266) * add simple neural feed forward * update refs * do not sample during inference in op * update refs --- selfdrive/car/gm/interface.py | 19 +++++++++-- selfdrive/car/interfaces.py | 33 +++++++++++++++++++ .../car/torque_data/neural_ff_weights.json | 1 + selfdrive/test/process_replay/ref_commit | 2 +- 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 selfdrive/car/torque_data/neural_ff_weights.json diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index e0f616dbf9..0c78b0061d 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 +import os from cereal import car from math import fabs, exp from panda import Panda +from openpilot.common.basedir import BASEDIR from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus -from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs +from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel from openpilot.selfdrive.controls.lib.drive_helpers import get_friction ButtonType = car.CarState.ButtonEvent.Type @@ -25,6 +27,8 @@ NON_LINEAR_TORQUE_PARAMS = { CAR.SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122] } +NEURAL_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/neural_ff_weights.json') + class CarInterface(CarInterfaceBase): @staticmethod @@ -61,8 +65,19 @@ class CarInterface(CarInterfaceBase): steer_torque = (sig(latcontrol_inputs.lateral_acceleration * a) * b) + (latcontrol_inputs.lateral_acceleration * c) return float(steer_torque) + friction + def torque_from_lateral_accel_neural(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning, lateral_accel_error: float, + lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float: + friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) + inputs = list(latcontrol_inputs) + if gravity_adjusted: + inputs[0] += inputs[1] + return float(self.neural_ff_model.predict(inputs)) + friction + def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: - if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS: + if self.CP.carFingerprint == CAR.BOLT_EUV: + self.neural_ff_model = NanoFFModel(NEURAL_PARAMS_PATH, self.CP.carFingerprint) + return self.torque_from_lateral_accel_neural + elif self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS: return self.torque_from_lateral_accel_siglin else: return self.torque_from_lateral_accel_linear diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 7ecc47f2a4..b7357d2c93 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -1,3 +1,4 @@ +import json import os import time import numpy as np @@ -473,3 +474,35 @@ def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: boo pass return result + + +class NanoFFModel: + def __init__(self, weights_loc: str, platform: str): + self.weights_loc = weights_loc + self.platform = platform + self.load_weights(platform) + + def load_weights(self, platform: str): + with open(self.weights_loc, 'r') as fob: + self.weights = {k: np.array(v) for k, v in json.load(fob)[platform].items()} + + def relu(self, x: np.ndarray): + return np.maximum(0.0, x) + + def forward(self, x: np.ndarray): + assert x.ndim == 1 + x = (x - self.weights['input_norm_mat'][:, 0]) / (self.weights['input_norm_mat'][:, 1] - self.weights['input_norm_mat'][:, 0]) + x = self.relu(np.dot(x, self.weights['w_1']) + self.weights['b_1']) + x = self.relu(np.dot(x, self.weights['w_2']) + self.weights['b_2']) + x = self.relu(np.dot(x, self.weights['w_3']) + self.weights['b_3']) + x = np.dot(x, self.weights['w_4']) + self.weights['b_4'] + return x + + def predict(self, x: List[float], do_sample: bool = False): + x = self.forward(np.array(x)) + if do_sample: + pred = np.random.laplace(x[0], np.exp(x[1]) / self.weights['temperature']) + else: + pred = x[0] + pred = pred * (self.weights['output_norm_mat'][1] - self.weights['output_norm_mat'][0]) + self.weights['output_norm_mat'][0] + return pred diff --git a/selfdrive/car/torque_data/neural_ff_weights.json b/selfdrive/car/torque_data/neural_ff_weights.json new file mode 100644 index 0000000000..c526f07fa2 --- /dev/null +++ b/selfdrive/car/torque_data/neural_ff_weights.json @@ -0,0 +1 @@ +{"CHEVROLET BOLT EUV 2022": {"w_1": [[0.3452189564704895, -0.15614677965641022, -0.04062516987323761, -0.5960758328437805, 0.3211185932159424, 0.31732726097106934, -0.04430829733610153, -0.37327295541763306, -0.14118380844593048, 0.12712529301643372, 0.2641555070877075, -0.3451094627380371, -0.005127656273543835, 0.6185108423233032, 0.03725295141339302, 0.3763789236545563], [-0.0708412230014801, 0.3667356073856354, 0.031383827328681946, 0.1740853488445282, -0.04695861041545868, 0.018055908381938934, 0.009072160348296165, -0.23640218377113342, -0.10362917929887772, 0.022628149017691612, -0.224413201212883, 0.20718418061733246, -0.016947750002145767, -0.3872031271457672, -0.15500062704086304, -0.06375953555107117], [-0.0838046595454216, -0.0242826659232378, -0.07765661180019379, 0.028858814388513565, -0.09516210108995438, 0.008368706330657005, 0.1689300835132599, 0.015036891214549541, -0.15121428668498993, 0.1388195902109146, 0.11486363410949707, 0.0651545450091362, 0.13559958338737488, 0.04300367832183838, -0.13856294751167297, -0.058136988431215286], [-0.006249868310987949, 0.08809533715248108, -0.040690965950489044, 0.02359287068247795, -0.00766348373144865, 0.24816390872001648, -0.17360293865203857, -0.03676899895071983, -0.17564819753170013, 0.18998438119888306, -0.050583917647600174, -0.006488069426268339, 0.10649101436138153, -0.024557121098041534, -0.103276826441288, 0.18448011577129364]], "b_1": [0.2935388386249542, 0.10967712104320526, -0.014007942751049995, 0.211833655834198, 0.33605605363845825, 0.37722209095954895, -0.16615016758441925, 0.3134673535823822, 0.06695777177810669, 0.3425212800502777, 0.3769673705101013, 0.23186539113521576, 0.5770409107208252, -0.05929069593548775, 0.01839117519557476, 0.03828774020075798], "w_2": [[-0.06261160969734192, 0.010185074992477894, -0.06083013117313385, -0.04531499370932579, -0.08979734033346176, 0.3432150185108185, -0.019801849499344826, 0.3010321259498596], [0.19698476791381836, -0.009238275699317455, 0.08842222392559052, -0.09516377002000809, -0.05022778362035751, 0.13626104593276978, -0.052890390157699585, 0.15569131076335907], [0.0724768117070198, -0.09018408507108688, 0.06850195676088333, -0.025572121143341064, 0.0680626779794693, -0.07648195326328278, 0.07993496209383011, -0.059752143919467926], [1.267876386642456, -0.05755887180566788, -0.08429178595542908, 0.021366603672504425, -0.0006479775765910745, -1.4292563199996948, -0.08077696710824966, -1.414825439453125], [0.04535430669784546, 0.06555880606174469, -0.027145234867930412, -0.07661093026399612, -0.05702832341194153, 0.23650476336479187, 0.0024587824009358883, 0.20126521587371826], [0.006042032968252897, 0.042880818247795105, 0.002187949838116765, -0.017126334831118584, -0.08352015167474747, 0.19801731407642365, -0.029196614399552345, 0.23713473975658417], [-0.01644900068640709, -0.04358499124646187, 0.014584392309188843, 0.07155826687812805, -0.09354910999536514, -0.033351872116327286, 0.07138452678918839, -0.04755295440554619], [-1.1012420654296875, -0.03534531593322754, 0.02167935110628605, -0.01116552110761404, -0.08436500281095505, 1.1038788557052612, 0.027903547510504723, 1.0676132440567017], [0.03843916580080986, -0.0952216386795044, 0.039226632565259933, 0.002778085647150874, -0.020275786519050598, -0.07848760485649109, 0.04803166165947914, 0.015538203530013561], [0.018385495990514755, -0.025189843028783798, 0.0036680365446954966, -0.02105865254998207, 0.04808586835861206, 0.1575016975402832, 0.02703506126999855, 0.23039312660694122], [-0.0033881019335240126, -0.10210853815078735, -0.04877309128642082, 0.006989633198827505, 0.046798162162303925, 0.38676899671554565, -0.032304272055625916, 0.2345031052827835], [0.22092825174331665, -0.09642873704433441, 0.04499409720301628, 0.05108088254928589, -0.10191166400909424, 0.12818090617656708, -0.021021494641900063, 0.09440375864505768], [0.1212429478764534, -0.028194155544042587, -0.0981956496834755, 0.08226924389600754, 0.055346839129924774, 0.27067816257476807, -0.09064067900180817, 0.12580905854701996], [-1.6740131378173828, -0.02066155895590782, -0.05924689769744873, 0.06347910314798355, -0.07821853458881378, 1.2807466983795166, 0.04589352011680603, 1.310766577720642], [-0.09893272817134857, -0.04093599319458008, -0.02502273954451084, 0.09490344673395157, -0.0211324505507946, -0.09021010994911194, 0.07936318963766098, -0.03593116253614426], [-0.08490308374166489, -0.015558987855911255, -0.048692114651203156, -0.007421435788273811, -0.040531404316425323, 0.25889304280281067, 0.06012800335884094, 0.27946868538856506]], "b_2": [0.07973937690258026, -0.010446485131978989, -0.003066520905122161, -0.031895797699689865, 0.006032303906977177, 0.24106740951538086, -0.008969511836767197, 0.2872662842273712], "w_3": [[-1.364486813545227, -0.11682678014039993, 0.01764785870909691, 0.03926877677440643], [-0.05695437639951706, 0.05472218990325928, 0.1266128271818161, 0.09950875490903854], [0.11415273696184158, -0.10069356113672256, 0.0864749327301979, -0.043946366757154465], [-0.10138195008039474, -0.040128443390131, -0.08937158435583115, -0.0048376512713730335], [-0.0028251828625798225, -0.04743027314543724, 0.06340016424655914, 0.07277824729681015], [0.49482327699661255, -0.06410001963376999, -0.0999293103814125, -0.14250673353672028], [0.042802367359399796, 0.0015462725423276424, -0.05991362780332565, 0.1022040992975235], [0.3523194193840027, 0.07343732565641403, 0.04157765582203865, -0.12358107417821884]], "b_3": [0.2653026282787323, -0.058485131710767746, -0.0744510293006897, 0.012550175189971924], "w_4": [[0.5988775491714478, 0.09668736904859543], [-0.04360569268465042, 0.06491032242774963], [-0.11868984252214432, -0.09601487964391708], [-0.06554870307445526, -0.14189276099205017]], "b_4": [-0.08148707449436188, -2.8251802921295166], "input_norm_mat": [[-3.0, 3.0], [-3.0, 3.0], [0.0, 40.0], [-3.0, 3.0]], "output_norm_mat": [-1.0, 1.0], "temperature": 100.0}} \ No newline at end of file diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index bde2296e6b..bc79d3be9e 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -eaab6bd55c5eab33fc9a0d8de8289b912e923887 +d9a3a0d4e806b49ec537233d30926bec70308485 \ No newline at end of file From 08037594e27292ca755a211628f4141469347f82 Mon Sep 17 00:00:00 2001 From: Hoang Bui <47828508+bongbui321@users.noreply.github.com> Date: Thu, 1 Feb 2024 16:37:22 -0500 Subject: [PATCH 660/724] map: Transfer to MapLibre (#31185) * change codebase * compile * add mapboxprovider * works with map_renderer in c * remove maplibre temp * maplibre works * cleanup build.sh * x86 stuff * add lib * update release files * don't need that * tici build * tici build * add tici lib * update refs --------- Co-authored-by: Adeeb Shihadeh --- SConstruct | 9 +- release/files_common | 1 + release/files_pc | 2 - release/files_tici | 1 - selfdrive/navd/SConscript | 4 +- selfdrive/navd/map_renderer.cc | 36 +-- selfdrive/navd/map_renderer.h | 11 +- .../process_replay/model_replay_ref_commit | 2 +- selfdrive/ui/SConscript | 6 +- selfdrive/ui/qt/maps/map.cc | 39 ++- selfdrive/ui/qt/maps/map.h | 13 +- selfdrive/ui/qt/maps/map_helpers.cc | 37 +-- selfdrive/ui/qt/maps/map_helpers.h | 17 +- selfdrive/ui/qt/maps/map_panel.cc | 2 +- selfdrive/ui/qt/maps/map_panel.h | 4 +- .../mapbox-gl-native-qt/.gitattributes | 2 - .../aarch64/libqmapboxgl.so | 3 - third_party/mapbox-gl-native-qt/build.sh | 7 - .../mapbox-gl-native-qt/include/QMapbox | 1 - .../mapbox-gl-native-qt/include/QMapboxGL | 1 - .../mapbox-gl-native-qt/include/qmapbox.hpp | 147 ---------- .../mapbox-gl-native-qt/include/qmapboxgl.hpp | 277 ------------------ .../x86_64/libqmapboxgl.so | 3 - third_party/maplibre-native-qt/.gitignore | 1 + third_party/maplibre-native-qt/aarch64 | 1 + third_party/maplibre-native-qt/build.sh | 38 +++ .../include/conversion_p.hpp | 241 +++++++++++++++ .../include/export_core.hpp | 20 ++ .../include/export_location.hpp | 20 ++ .../include/export_widgets.hpp | 20 ++ .../maplibre-native-qt/include/geojson_p.hpp | 30 ++ .../maplibre-native-qt/include/gl_widget.hpp | 56 ++++ .../include/gl_widget_p.hpp | 42 +++ .../maplibre-native-qt/include/map.hpp | 205 +++++++++++++ .../include/map_observer_p.hpp | 54 ++++ .../maplibre-native-qt/include/map_p.hpp | 79 +++++ .../include/map_renderer_p.hpp | 63 ++++ .../maplibre-native-qt/include/qgeomap.hpp | 54 ++++ .../maplibre-native-qt/include/qgeomap_p.hpp | 93 ++++++ .../maplibre-native-qt/include/qmaplibre.hpp | 9 + .../include/qmaplibrewidgets.hpp | 6 + .../include/qt_mapping_engine.hpp | 31 ++ .../maplibre-native-qt/include/settings.hpp | 125 ++++++++ .../maplibre-native-qt/include/settings_p.hpp | 57 ++++ .../include/style_change_utils_p.hpp | 34 +++ .../include/texture_node.hpp | 42 +++ .../maplibre-native-qt/include/types.hpp | 206 +++++++++++++ .../maplibre-native-qt/include/utils.hpp | 28 ++ .../larch64/include/QMapLibre/Export | 1 + .../larch64/include/QMapLibre/LayerParameter | 1 + .../larch64/include/QMapLibre/Map | 1 + .../larch64/include/QMapLibre/QMapLibre | 1 + .../larch64/include/QMapLibre/Settings | 1 + .../larch64/include/QMapLibre/SourceParameter | 1 + .../larch64/include/QMapLibre/StyleParameter | 1 + .../larch64/include/QMapLibre/Types | 1 + .../larch64/include/QMapLibre/Utils | 1 + .../larch64/lib/libQMapLibre.so | 3 + .../larch64/lib/libQMapLibre.so.3.0.0 | 3 + .../x86_64/include/QMapLibre/Export | 1 + .../x86_64/include/QMapLibre/LayerParameter | 1 + .../x86_64/include/QMapLibre/Map | 1 + .../x86_64/include/QMapLibre/QMapLibre | 1 + .../x86_64/include/QMapLibre/Settings | 1 + .../x86_64/include/QMapLibre/SourceParameter | 1 + .../x86_64/include/QMapLibre/StyleParameter | 1 + .../x86_64/include/QMapLibre/Types | 1 + .../x86_64/include/QMapLibre/Utils | 1 + .../x86_64/lib/libQMapLibre.so | 1 + .../x86_64/lib/libQMapLibre.so.3.0.0 | 3 + 70 files changed, 1673 insertions(+), 535 deletions(-) delete mode 100644 third_party/mapbox-gl-native-qt/.gitattributes delete mode 100755 third_party/mapbox-gl-native-qt/aarch64/libqmapboxgl.so delete mode 100755 third_party/mapbox-gl-native-qt/build.sh delete mode 100644 third_party/mapbox-gl-native-qt/include/QMapbox delete mode 100644 third_party/mapbox-gl-native-qt/include/QMapboxGL delete mode 100644 third_party/mapbox-gl-native-qt/include/qmapbox.hpp delete mode 100644 third_party/mapbox-gl-native-qt/include/qmapboxgl.hpp delete mode 100755 third_party/mapbox-gl-native-qt/x86_64/libqmapboxgl.so create mode 100644 third_party/maplibre-native-qt/.gitignore create mode 120000 third_party/maplibre-native-qt/aarch64 create mode 100755 third_party/maplibre-native-qt/build.sh create mode 100644 third_party/maplibre-native-qt/include/conversion_p.hpp create mode 100644 third_party/maplibre-native-qt/include/export_core.hpp create mode 100644 third_party/maplibre-native-qt/include/export_location.hpp create mode 100644 third_party/maplibre-native-qt/include/export_widgets.hpp create mode 100644 third_party/maplibre-native-qt/include/geojson_p.hpp create mode 100644 third_party/maplibre-native-qt/include/gl_widget.hpp create mode 100644 third_party/maplibre-native-qt/include/gl_widget_p.hpp create mode 100644 third_party/maplibre-native-qt/include/map.hpp create mode 100644 third_party/maplibre-native-qt/include/map_observer_p.hpp create mode 100644 third_party/maplibre-native-qt/include/map_p.hpp create mode 100644 third_party/maplibre-native-qt/include/map_renderer_p.hpp create mode 100644 third_party/maplibre-native-qt/include/qgeomap.hpp create mode 100644 third_party/maplibre-native-qt/include/qgeomap_p.hpp create mode 100644 third_party/maplibre-native-qt/include/qmaplibre.hpp create mode 100644 third_party/maplibre-native-qt/include/qmaplibrewidgets.hpp create mode 100644 third_party/maplibre-native-qt/include/qt_mapping_engine.hpp create mode 100644 third_party/maplibre-native-qt/include/settings.hpp create mode 100644 third_party/maplibre-native-qt/include/settings_p.hpp create mode 100644 third_party/maplibre-native-qt/include/style_change_utils_p.hpp create mode 100644 third_party/maplibre-native-qt/include/texture_node.hpp create mode 100644 third_party/maplibre-native-qt/include/types.hpp create mode 100644 third_party/maplibre-native-qt/include/utils.hpp create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/Export create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/LayerParameter create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/Map create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/QMapLibre create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/Settings create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/SourceParameter create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/StyleParameter create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/Types create mode 100644 third_party/maplibre-native-qt/larch64/include/QMapLibre/Utils create mode 100755 third_party/maplibre-native-qt/larch64/lib/libQMapLibre.so create mode 100755 third_party/maplibre-native-qt/larch64/lib/libQMapLibre.so.3.0.0 create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/Export create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/LayerParameter create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/Map create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/QMapLibre create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/Settings create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/SourceParameter create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/StyleParameter create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/Types create mode 100644 third_party/maplibre-native-qt/x86_64/include/QMapLibre/Utils create mode 120000 third_party/maplibre-native-qt/x86_64/lib/libQMapLibre.so create mode 100755 third_party/maplibre-native-qt/x86_64/lib/libQMapLibre.so.3.0.0 diff --git a/SConstruct b/SConstruct index 3faa978087..b04e3903ed 100644 --- a/SConstruct +++ b/SConstruct @@ -145,7 +145,6 @@ else: libpath = [ f"#third_party/acados/{arch}/lib", f"#third_party/libyuv/{arch}/lib", - f"#third_party/mapbox-gl-native-qt/{arch}", "/usr/lib", "/usr/local/lib", ] @@ -208,11 +207,12 @@ env = Environment( "#third_party/json11", "#third_party/linux/include", "#third_party/snpe/include", - "#third_party/mapbox-gl-native-qt/include", "#third_party/qrcode", "#third_party", "#cereal", "#opendbc/can", + "#third_party/maplibre-native-qt/include", + f"#third_party/maplibre-native-qt/{arch}/include" ], CC='clang', @@ -318,7 +318,7 @@ try: except SCons.Errors.UserError: qt_env.Tool('qt') -qt_env['CPPPATH'] += qt_dirs + ["#selfdrive/ui/qt/"] +qt_env['CPPPATH'] += qt_dirs# + ["#selfdrive/ui/qt/"] qt_flags = [ "-D_REENTRANT", "-DQT_NO_DEBUG", @@ -331,7 +331,8 @@ qt_flags = [ "-DQT_MESSAGELOGCONTEXT", ] qt_env['CXXFLAGS'] += qt_flags -qt_env['LIBPATH'] += ['#selfdrive/ui'] +qt_env['LIBPATH'] += ['#selfdrive/ui', f"#third_party/maplibre-native-qt/{arch}/lib"] +qt_env['RPATH'] += [Dir(f"#third_party/maplibre-native-qt/{arch}/lib").srcnode().abspath] qt_env['LIBS'] = qt_libs if GetOption("clazy"): diff --git a/release/files_common b/release/files_common index cf985f682d..91945a0621 100644 --- a/release/files_common +++ b/release/files_common @@ -394,6 +394,7 @@ third_party/acados/acados_template/** third_party/bootstrap/** third_party/qt5/larch64/bin/** +third_party/maplibre-native-qt/** scripts/update_now.sh scripts/stop_updater.sh diff --git a/release/files_pc b/release/files_pc index 13f1b52166..f2bf090f2c 100644 --- a/release/files_pc +++ b/release/files_pc @@ -1,5 +1,3 @@ -third_party/mapbox-gl-native-qt/x86_64/*.so - third_party/libyuv/x86_64/** third_party/snpe/x86_64/** third_party/snpe/x86_64-linux-clang/** diff --git a/release/files_tici b/release/files_tici index 5d65ef458a..1771c45138 100644 --- a/release/files_tici +++ b/release/files_tici @@ -1,7 +1,6 @@ third_party/libyuv/larch64/** third_party/snpe/larch64** third_party/snpe/aarch64-ubuntu-gcc7.5/* -third_party/mapbox-gl-native-qt/include/* third_party/acados/larch64/** system/camerad/cameras/camera_qcom2.cc diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript index c116ef1535..5a173c0351 100644 --- a/selfdrive/navd/SConscript +++ b/selfdrive/navd/SConscript @@ -1,14 +1,14 @@ Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'transformations') map_env = qt_env.Clone() -libs = ['qt_widgets', 'qt_util', 'qmapboxgl', common, messaging, cereal, visionipc, transformations, +libs = ['qt_widgets', 'qt_util', 'QMapLibre', common, messaging, cereal, visionipc, transformations, 'zmq', 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread', 'json11'] + map_env["LIBS"] if arch == 'larch64': libs.append(':libEGL_mesa.so.0') if arch in ['larch64', 'aarch64', 'x86_64']: if arch == 'x86_64': - rpath = Dir(f"#third_party/mapbox-gl-native-qt/{arch}").srcnode().abspath + rpath = Dir(f"#third_party/maplibre-native-qt/{arch}/lib").srcnode().abspath map_env["RPATH"] += [rpath, ] style_path = File("style.json").abspath diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index 543515c631..d52ee162bd 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -28,16 +28,16 @@ float get_zoom_level_for_scale(float lat, float meters_per_pixel) { return log2(num_tiles) - 1; } -QMapbox::Coordinate get_point_along_line(float lat, float lon, float bearing, float dist) { +QMapLibre::Coordinate get_point_along_line(float lat, float lon, float bearing, float dist) { float ang_dist = dist / EARTH_RADIUS_METERS; float lat1 = DEG2RAD(lat), lon1 = DEG2RAD(lon), bearing1 = DEG2RAD(bearing); float lat2 = asin(sin(lat1)*cos(ang_dist) + cos(lat1)*sin(ang_dist)*cos(bearing1)); float lon2 = lon1 + atan2(sin(bearing1)*sin(ang_dist)*cos(lat1), cos(ang_dist)-sin(lat1)*sin(lat2)); - return QMapbox::Coordinate(RAD2DEG(lat2), RAD2DEG(lon2)); + return QMapLibre::Coordinate(RAD2DEG(lat2), RAD2DEG(lon2)); } -MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_settings(settings) { +MapRenderer::MapRenderer(const QMapLibre::Settings &settings, bool online) : m_settings(settings) { QSurfaceFormat fmt; fmt.setRenderableType(QSurfaceFormat::OpenGLES); @@ -60,8 +60,8 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set fbo.reset(new QOpenGLFramebufferObject(WIDTH, HEIGHT, fbo_format)); std::string style = util::read_file(STYLE_PATH); - m_map.reset(new QMapboxGL(nullptr, m_settings, fbo->size(), 1)); - m_map->setCoordinateZoom(QMapbox::Coordinate(0, 0), DEFAULT_ZOOM); + m_map.reset(new QMapLibre::Map(nullptr, m_settings, fbo->size(), 1)); + m_map->setCoordinateZoom(QMapLibre::Coordinate(0, 0), DEFAULT_ZOOM); m_map->setStyleJson(style.c_str()); m_map->createRenderer(); ever_loaded = false; @@ -70,20 +70,20 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set m_map->setFramebufferObject(fbo->handle(), fbo->size()); gl_functions->glViewport(0, 0, WIDTH, HEIGHT); - QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { + QObject::connect(m_map.data(), &QMapLibre::Map::mapChanged, [=](QMapLibre::Map::MapChange change) { // Ignore expected signals // https://github.com/mapbox/mapbox-gl-native/blob/cf734a2fec960025350d8de0d01ad38aeae155a0/platform/qt/include/qmapboxgl.hpp#L116 if (ever_loaded) { - if (change != QMapboxGL::MapChange::MapChangeRegionWillChange && - change != QMapboxGL::MapChange::MapChangeRegionDidChange && - change != QMapboxGL::MapChange::MapChangeWillStartRenderingFrame && - change != QMapboxGL::MapChange::MapChangeDidFinishRenderingFrameFullyRendered) { + if (change != QMapLibre::Map::MapChange::MapChangeRegionWillChange && + change != QMapLibre::Map::MapChange::MapChangeRegionDidChange && + change != QMapLibre::Map::MapChange::MapChangeWillStartRenderingFrame && + change != QMapLibre::Map::MapChange::MapChangeDidFinishRenderingFrameFullyRendered) { LOGD("New map state: %d", change); } } }); - QObject::connect(m_map.data(), &QMapboxGL::mapLoadingFailed, [=](QMapboxGL::MapLoadingFailure err_code, const QString &reason) { + QObject::connect(m_map.data(), &QMapLibre::Map::mapLoadingFailed, [=](QMapLibre::Map::MapLoadingFailure err_code, const QString &reason) { LOGE("Map loading failed with %d: '%s'\n", err_code, reason.toStdString().c_str()); }); @@ -145,7 +145,7 @@ void MapRenderer::msgUpdate() { timer->start(0); } -void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { +void MapRenderer::updatePosition(QMapLibre::Coordinate position, float bearing) { if (m_map.isNull()) { return; } @@ -261,10 +261,10 @@ void MapRenderer::updateRoute(QList coordinates) { initLayers(); auto route_points = coordinate_list_to_collection(coordinates); - QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {}); QVariantMap navSource; navSource["type"] = "geojson"; - navSource["data"] = QVariant::fromValue(feature); + navSource["data"] = QVariant::fromValue(feature); m_map->updateSource("navSource", navSource); m_map->setLayoutProperty("navLayer", "visibility", "visible"); } @@ -273,10 +273,9 @@ void MapRenderer::initLayers() { if (!m_map->layerExists("navLayer")) { LOGD("Initializing navLayer"); QVariantMap nav; - nav["id"] = "navLayer"; nav["type"] = "line"; nav["source"] = "navSource"; - m_map->addLayer(nav, "road-intersection"); + m_map->addLayer("navLayer", nav, "road-intersection"); m_map->setPaintProperty("navLayer", "line-color", QColor("grey")); m_map->setPaintProperty("navLayer", "line-width", 5); m_map->setLayoutProperty("navLayer", "line-cap", "round"); @@ -296,9 +295,10 @@ extern "C" { QApplication *app = new QApplication(argc, argv); assert(app); - QMapboxGLSettings settings; + QMapLibre::Settings settings; + settings.setProviderTemplate(QMapLibre::Settings::ProviderTemplate::MapboxProvider); settings.setApiBaseUrl(maps_host == nullptr ? MAPS_HOST : maps_host); - settings.setAccessToken(token == nullptr ? get_mapbox_token() : token); + settings.setApiKey(token == nullptr ? get_mapbox_token() : token); return new MapRenderer(settings, false); } diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index dc92c70b0f..fd5922b668 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -3,7 +3,8 @@ #include #include -#include +#include +#include #include #include #include @@ -19,7 +20,7 @@ class MapRenderer : public QObject { Q_OBJECT public: - MapRenderer(const QMapboxGLSettings &, bool online=true); + MapRenderer(const QMapLibre::Settings &, bool online=true); uint8_t* getImage(); void update(); bool loaded(); @@ -37,8 +38,8 @@ private: void publish(const double render_time, const bool loaded); void sendThumbnail(const uint64_t ts, const kj::Array &buf); - QMapboxGLSettings m_settings; - QScopedPointer m_map; + QMapLibre::Settings m_settings; + QScopedPointer m_map; void initLayers(); @@ -52,7 +53,7 @@ private: bool ever_loaded = false; public slots: - void updatePosition(QMapbox::Coordinate position, float bearing); + void updatePosition(QMapLibre::Coordinate position, float bearing); void updateRoute(QList coordinates); void msgUpdate(); }; diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 9ec78e1401..a2f6896307 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -4a01784a6b83a49301a68adf52bb7dcfcb7173b5 +fee90bcee1e545c7ec9a39d3c7d4e42cfefb9955 diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a3cba124fe..4c866332f9 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -11,10 +11,6 @@ if arch == 'larch64': maps = arch in ['larch64', 'aarch64', 'x86_64'] -if maps and arch != 'larch64': - rpath = [Dir(f"#third_party/mapbox-gl-native-qt/{arch}").srcnode().abspath] - qt_env["RPATH"] += rpath - if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] qt_env['FRAMEWORKS'] += ['OpenCL'] @@ -31,7 +27,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", qt_env['CPPDEFINES'] = [] if maps: - base_libs += ['qmapboxgl'] + base_libs += ['QMapLibre'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc", "qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"] qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index db56fcdcfc..a5644bae4f 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -18,7 +18,7 @@ const float MAX_PITCH = 50; const float MIN_PITCH = 0; const float MAP_SCALE = 2; -MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) { +MapWindow::MapWindow(const QMapLibre::Settings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) { QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState); map_overlay = new QWidget (this); @@ -57,10 +57,10 @@ void MapWindow::initLayers() { if (!m_map->layerExists("modelPathLayer")) { qDebug() << "Initializing modelPathLayer"; QVariantMap modelPath; - modelPath["id"] = "modelPathLayer"; + //modelPath["id"] = "modelPathLayer"; modelPath["type"] = "line"; modelPath["source"] = "modelPathSource"; - m_map->addLayer(modelPath); + m_map->addLayer("modelPathLayer", modelPath); m_map->setPaintProperty("modelPathLayer", "line-color", QColor("red")); m_map->setPaintProperty("modelPathLayer", "line-width", 5.0); m_map->setLayoutProperty("modelPathLayer", "line-cap", "round"); @@ -68,10 +68,9 @@ void MapWindow::initLayers() { if (!m_map->layerExists("navLayer")) { qDebug() << "Initializing navLayer"; QVariantMap nav; - nav["id"] = "navLayer"; nav["type"] = "line"; nav["source"] = "navSource"; - m_map->addLayer(nav, "road-intersection"); + m_map->addLayer("navLayer", nav, "road-intersection"); QVariantMap transition; transition["duration"] = 400; // ms @@ -84,10 +83,9 @@ void MapWindow::initLayers() { qDebug() << "Initializing pinLayer"; m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg")); QVariantMap pin; - pin["id"] = "pinLayer"; pin["type"] = "symbol"; pin["source"] = "pinSource"; - m_map->addLayer(pin); + m_map->addLayer("pinLayer", pin); m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport"); m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker"); m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true); @@ -100,10 +98,9 @@ void MapWindow::initLayers() { m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg")); QVariantMap carPos; - carPos["id"] = "carPosLayer"; carPos["type"] = "symbol"; carPos["source"] = "carPosSource"; - m_map->addLayer(carPos); + m_map->addLayer("carPosLayer", carPos); m_map->setLayoutProperty("carPosLayer", "icon-pitch-alignment", "map"); m_map->setLayoutProperty("carPosLayer", "icon-image", "label-arrow"); m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5); @@ -149,7 +146,7 @@ void MapWindow::updateState(const UIState &s) { locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && pos_accurate_enough); if (locationd_valid) { - last_position = QMapbox::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); + last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); last_bearing = RAD2DEG(locationd_orientation.getValue()[2]); velocity_filter.update(std::max(10.0, locationd_velocity.getValue()[0])); } @@ -186,10 +183,10 @@ void MapWindow::updateState(const UIState &s) { if (locationd_valid) { // Update current location marker auto point = coordinate_to_collection(*last_position); - QMapbox::Feature feature1(QMapbox::Feature::PointType, point, {}, {}); + QMapLibre::Feature feature1(QMapLibre::Feature::PointType, point, {}, {}); QVariantMap carPosSource; carPosSource["type"] = "geojson"; - carPosSource["data"] = QVariant::fromValue(feature1); + carPosSource["data"] = QVariant::fromValue(feature1); m_map->updateSource("carPosSource", carPosSource); // Map bearing isn't updated when interacting, keep location marker up to date @@ -230,10 +227,10 @@ void MapWindow::updateState(const UIState &s) { qWarning() << "Updating navLayer with new route"; auto route = sm["navRoute"].getNavRoute(); auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates()); - QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {}); QVariantMap navSource; navSource["type"] = "geojson"; - navSource["data"] = QVariant::fromValue(feature); + navSource["data"] = QVariant::fromValue(feature); m_map->updateSource("navSource", navSource); m_map->setLayoutProperty("navLayer", "visibility", "visible"); @@ -256,24 +253,24 @@ void MapWindow::resizeGL(int w, int h) { } void MapWindow::initializeGL() { - m_map.reset(new QMapboxGL(this, m_settings, size(), 1)); + m_map.reset(new QMapLibre::Map(this, m_settings, size(), 1)); if (last_position) { m_map->setCoordinateZoom(*last_position, MAX_ZOOM); } else { - m_map->setCoordinateZoom(QMapbox::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM); + m_map->setCoordinateZoom(QMapLibre::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM); } m_map->setMargins({0, 350, 0, 50}); m_map->setPitch(MIN_PITCH); m_map->setStyleUrl("mapbox://styles/commaai/clkqztk0f00ou01qyhsa5bzpj"); - QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { + QObject::connect(m_map.data(), &QMapLibre::Map::mapChanged, [=](QMapLibre::Map::MapChange change) { // set global animation duration to 0 ms so visibility changes are instant - if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingStyle) { + if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingStyle) { m_map->setTransitionOptions(0, 0); } - if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) { + if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingMap) { loaded_once = true; } }); @@ -381,10 +378,10 @@ void MapWindow::updateDestinationMarker() { auto nav_dest = coordinate_from_param("NavDestination"); if (nav_dest.has_value()) { auto point = coordinate_to_collection(*nav_dest); - QMapbox::Feature feature(QMapbox::Feature::PointType, point, {}, {}); + QMapLibre::Feature feature(QMapLibre::Feature::PointType, point, {}, {}); QVariantMap pinSource; pinSource["type"] = "geojson"; - pinSource["data"] = QVariant::fromValue(feature); + pinSource["data"] = QVariant::fromValue(feature); m_map->updateSource("pinSource", pinSource); m_map->setPaintProperty("pinLayer", "visibility", "visible"); } else { diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 5fe79f8b15..81ba50037a 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -6,7 +6,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -27,7 +28,7 @@ class MapWindow : public QOpenGLWidget { Q_OBJECT public: - MapWindow(const QMapboxGLSettings &); + MapWindow(const QMapLibre::Settings &); ~MapWindow(); private: @@ -35,8 +36,8 @@ private: void paintGL() final; void resizeGL(int w, int h) override; - QMapboxGLSettings m_settings; - QScopedPointer m_map; + QMapLibre::Settings m_settings; + QScopedPointer m_map; void initLayers(); @@ -56,8 +57,8 @@ private: int interaction_counter = 0; // Position - std::optional last_valid_nav_dest; - std::optional last_position; + std::optional last_valid_nav_dest; + std::optional last_position; std::optional last_bearing; FirstOrderFilter velocity_filter; bool locationd_valid = false; diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 022355e3ce..3907ff7b05 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -16,24 +16,25 @@ QString get_mapbox_token() { return MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; } -QMapboxGLSettings get_mapbox_settings() { - QMapboxGLSettings settings; +QMapLibre::Settings get_mapbox_settings() { + QMapLibre::Settings settings; + settings.setProviderTemplate(QMapLibre::Settings::ProviderTemplate::MapboxProvider); if (!Hardware::PC()) { settings.setCacheDatabasePath(MAPS_CACHE_PATH); settings.setCacheDatabaseMaximumSize(100 * 1024 * 1024); } settings.setApiBaseUrl(MAPS_HOST); - settings.setAccessToken(get_mapbox_token()); + settings.setApiKey(get_mapbox_token()); return settings; } -QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in) { +QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in) { return QGeoCoordinate(in.first, in.second); } -QMapbox::CoordinatesCollections model_to_collection( +QMapLibre::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, const cereal::XYZTData::Reader &line){ @@ -42,7 +43,7 @@ QMapbox::CoordinatesCollections model_to_collection( Eigen::Vector3d orient(calibratedOrientationECEF.getValue()[0], calibratedOrientationECEF.getValue()[1], calibratedOrientationECEF.getValue()[2]); Eigen::Matrix3d ecef_from_local = euler2rot(orient); - QMapbox::Coordinates coordinates; + QMapLibre::Coordinates coordinates; auto x = line.getX(); auto y = line.getY(); auto z = line.getZ(); @@ -52,28 +53,28 @@ QMapbox::CoordinatesCollections model_to_collection( coordinates.push_back({point_geodetic.lat, point_geodetic.lon}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordinate &c) { - QMapbox::Coordinates coordinates{c}; - return {QMapbox::CoordinatesCollection{coordinates}}; +QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c) { + QMapLibre::Coordinates coordinates{c}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader& coordinate_list) { - QMapbox::Coordinates coordinates; +QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader& coordinate_list) { + QMapLibre::Coordinates coordinates; for (auto const &c : coordinate_list) { coordinates.push_back({c.getLatitude(), c.getLongitude()}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list) { - QMapbox::Coordinates coordinates; +QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list) { + QMapLibre::Coordinates coordinates; for (auto &c : coordinate_list) { coordinates.push_back({c.latitude(), c.longitude()}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } QList polyline_to_coordinate_list(const QString &polylineString) { @@ -118,7 +119,7 @@ QList polyline_to_coordinate_list(const QString &polylineString) return path; } -std::optional coordinate_from_param(const std::string ¶m) { +std::optional coordinate_from_param(const std::string ¶m) { QString json_str = QString::fromStdString(Params().get(param)); if (json_str.isEmpty()) return {}; @@ -127,7 +128,7 @@ std::optional coordinate_from_param(const std::string ¶ QJsonObject json = doc.object(); if (json["latitude"].isDouble() && json["longitude"].isDouble()) { - QMapbox::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble()); + QMapLibre::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble()); return coord; } else { return {}; diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index 4d6e9b5382..0f4be674f0 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -3,8 +3,9 @@ #include #include #include +#include +#include #include -#include #include #include "common/util.h" @@ -17,15 +18,15 @@ const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "ht const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db"; QString get_mapbox_token(); -QMapboxGLSettings get_mapbox_settings(); -QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in); -QMapbox::CoordinatesCollections model_to_collection( +QMapLibre::Settings get_mapbox_settings(); +QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in); +QMapLibre::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, const cereal::XYZTData::Reader &line); -QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordinate &c); -QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); -QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list); +QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c); +QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); +QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list); QList polyline_to_coordinate_list(const QString &polylineString); -std::optional coordinate_from_param(const std::string ¶m); +std::optional coordinate_from_param(const std::string ¶m); std::pair map_format_distance(float d, bool is_metric); diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index 0a2286ff6f..c4cc20e21d 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -8,7 +8,7 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/ui.h" -MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : QFrame(parent) { +MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); content_stack->setContentsMargins(0, 0, 0, 0); diff --git a/selfdrive/ui/qt/maps/map_panel.h b/selfdrive/ui/qt/maps/map_panel.h index 43a2cc7c89..190bb63446 100644 --- a/selfdrive/ui/qt/maps/map_panel.h +++ b/selfdrive/ui/qt/maps/map_panel.h @@ -1,14 +1,14 @@ #pragma once #include -#include +#include #include class MapPanel : public QFrame { Q_OBJECT public: - explicit MapPanel(const QMapboxGLSettings &settings, QWidget *parent = nullptr); + explicit MapPanel(const QMapLibre::Settings &settings, QWidget *parent = nullptr); signals: void mapPanelRequested(); diff --git a/third_party/mapbox-gl-native-qt/.gitattributes b/third_party/mapbox-gl-native-qt/.gitattributes deleted file mode 100644 index 323c737ba1..0000000000 --- a/third_party/mapbox-gl-native-qt/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -x86_64 filter=lfs diff=lfs merge=lfs -text -larch64 filter=lfs diff=lfs merge=lfs -text diff --git a/third_party/mapbox-gl-native-qt/aarch64/libqmapboxgl.so b/third_party/mapbox-gl-native-qt/aarch64/libqmapboxgl.so deleted file mode 100755 index 508463c141..0000000000 --- a/third_party/mapbox-gl-native-qt/aarch64/libqmapboxgl.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fb33cb09b184a689eaa7f78c839d12a5bdd5b6f576d55758ec7e1246187a53f -size 9300680 diff --git a/third_party/mapbox-gl-native-qt/build.sh b/third_party/mapbox-gl-native-qt/build.sh deleted file mode 100755 index f2936fad3d..0000000000 --- a/third_party/mapbox-gl-native-qt/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -cd /tmp -git clone --recursive https://github.com/commaai/mapbox-gl-native.git -cd mapbox-gl-native -mkdir build && cd build -cmake -DMBGL_WITH_QT=ON .. -make -j$(nproc) mbgl-qt diff --git a/third_party/mapbox-gl-native-qt/include/QMapbox b/third_party/mapbox-gl-native-qt/include/QMapbox deleted file mode 100644 index a8479c09aa..0000000000 --- a/third_party/mapbox-gl-native-qt/include/QMapbox +++ /dev/null @@ -1 +0,0 @@ -#include "qmapbox.hpp" diff --git a/third_party/mapbox-gl-native-qt/include/QMapboxGL b/third_party/mapbox-gl-native-qt/include/QMapboxGL deleted file mode 100644 index 15b55a9abe..0000000000 --- a/third_party/mapbox-gl-native-qt/include/QMapboxGL +++ /dev/null @@ -1 +0,0 @@ -#include "qmapboxgl.hpp" diff --git a/third_party/mapbox-gl-native-qt/include/qmapbox.hpp b/third_party/mapbox-gl-native-qt/include/qmapbox.hpp deleted file mode 100644 index 3acc9d55e0..0000000000 --- a/third_party/mapbox-gl-native-qt/include/qmapbox.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef QMAPBOX_H -#define QMAPBOX_H - -#include -#include -#include -#include -#include - -// This header follows the Qt coding style: https://wiki.qt.io/Qt_Coding_Style - -#if !defined(QT_MAPBOXGL_STATIC) -# if defined(QT_BUILD_MAPBOXGL_LIB) -# define Q_MAPBOXGL_EXPORT Q_DECL_EXPORT -# else -# define Q_MAPBOXGL_EXPORT Q_DECL_IMPORT -# endif -#else -# define Q_MAPBOXGL_EXPORT -#endif - -namespace QMapbox { - -typedef QPair Coordinate; -typedef QPair CoordinateZoom; -typedef QPair ProjectedMeters; - -typedef QVector Coordinates; -typedef QVector CoordinatesCollection; - -typedef QVector CoordinatesCollections; - -struct Q_MAPBOXGL_EXPORT Feature { - enum Type { - PointType = 1, - LineStringType, - PolygonType - }; - - /*! Class constructor. */ - Feature(Type type_ = PointType, const CoordinatesCollections& geometry_ = CoordinatesCollections(), - const QVariantMap& properties_ = QVariantMap(), const QVariant& id_ = QVariant()) - : type(type_), geometry(geometry_), properties(properties_), id(id_) {} - - Type type; - CoordinatesCollections geometry; - QVariantMap properties; - QVariant id; -}; - -struct Q_MAPBOXGL_EXPORT ShapeAnnotationGeometry { - enum Type { - LineStringType = 1, - PolygonType, - MultiLineStringType, - MultiPolygonType - }; - - /*! Class constructor. */ - ShapeAnnotationGeometry(Type type_ = LineStringType, const CoordinatesCollections& geometry_ = CoordinatesCollections()) - : type(type_), geometry(geometry_) {} - - Type type; - CoordinatesCollections geometry; -}; - -struct Q_MAPBOXGL_EXPORT SymbolAnnotation { - Coordinate geometry; - QString icon; -}; - -struct Q_MAPBOXGL_EXPORT LineAnnotation { - /*! Class constructor. */ - LineAnnotation(const ShapeAnnotationGeometry& geometry_ = ShapeAnnotationGeometry(), float opacity_ = 1.0f, - float width_ = 1.0f, const QColor& color_ = Qt::black) - : geometry(geometry_), opacity(opacity_), width(width_), color(color_) {} - - ShapeAnnotationGeometry geometry; - float opacity; - float width; - QColor color; -}; - -struct Q_MAPBOXGL_EXPORT FillAnnotation { - /*! Class constructor. */ - FillAnnotation(const ShapeAnnotationGeometry& geometry_ = ShapeAnnotationGeometry(), float opacity_ = 1.0f, - const QColor& color_ = Qt::black, const QVariant& outlineColor_ = QVariant()) - : geometry(geometry_), opacity(opacity_), color(color_), outlineColor(outlineColor_) {} - - ShapeAnnotationGeometry geometry; - float opacity; - QColor color; - QVariant outlineColor; -}; - -typedef QVariant Annotation; -typedef quint32 AnnotationID; -typedef QVector AnnotationIDs; - -enum NetworkMode { - Online, // Default - Offline, -}; - -Q_MAPBOXGL_EXPORT QVector >& defaultStyles(); - -Q_MAPBOXGL_EXPORT NetworkMode networkMode(); -Q_MAPBOXGL_EXPORT void setNetworkMode(NetworkMode); - -// This struct is a 1:1 copy of mbgl::CustomLayerRenderParameters. -struct Q_MAPBOXGL_EXPORT CustomLayerRenderParameters { - double width; - double height; - double latitude; - double longitude; - double zoom; - double bearing; - double pitch; - double fieldOfView; -}; - -class Q_MAPBOXGL_EXPORT CustomLayerHostInterface { -public: - virtual ~CustomLayerHostInterface() = default; - virtual void initialize() = 0; - virtual void render(const CustomLayerRenderParameters&) = 0; - virtual void deinitialize() = 0; -}; - -Q_MAPBOXGL_EXPORT double metersPerPixelAtLatitude(double latitude, double zoom); -Q_MAPBOXGL_EXPORT ProjectedMeters projectedMetersForCoordinate(const Coordinate &); -Q_MAPBOXGL_EXPORT Coordinate coordinateForProjectedMeters(const ProjectedMeters &); - -} // namespace QMapbox - -Q_DECLARE_METATYPE(QMapbox::Coordinate); -Q_DECLARE_METATYPE(QMapbox::Coordinates); -Q_DECLARE_METATYPE(QMapbox::CoordinatesCollection); -Q_DECLARE_METATYPE(QMapbox::CoordinatesCollections); -Q_DECLARE_METATYPE(QMapbox::Feature); - -Q_DECLARE_METATYPE(QMapbox::SymbolAnnotation); -Q_DECLARE_METATYPE(QMapbox::ShapeAnnotationGeometry); -Q_DECLARE_METATYPE(QMapbox::LineAnnotation); -Q_DECLARE_METATYPE(QMapbox::FillAnnotation); - -#endif // QMAPBOX_H diff --git a/third_party/mapbox-gl-native-qt/include/qmapboxgl.hpp b/third_party/mapbox-gl-native-qt/include/qmapboxgl.hpp deleted file mode 100644 index 337991aa1c..0000000000 --- a/third_party/mapbox-gl-native-qt/include/qmapboxgl.hpp +++ /dev/null @@ -1,277 +0,0 @@ -#ifndef QMAPBOXGL_H -#define QMAPBOXGL_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -class QMapboxGLPrivate; - -// This header follows the Qt coding style: https://wiki.qt.io/Qt_Coding_Style - -class Q_MAPBOXGL_EXPORT QMapboxGLSettings -{ -public: - QMapboxGLSettings(); - - enum GLContextMode { - UniqueGLContext = 0, - SharedGLContext - }; - - enum MapMode { - Continuous = 0, - Static - }; - - enum ConstrainMode { - NoConstrain = 0, - ConstrainHeightOnly, - ConstrainWidthAndHeight - }; - - enum ViewportMode { - DefaultViewport = 0, - FlippedYViewport - }; - - GLContextMode contextMode() const; - void setContextMode(GLContextMode); - - MapMode mapMode() const; - void setMapMode(MapMode); - - ConstrainMode constrainMode() const; - void setConstrainMode(ConstrainMode); - - ViewportMode viewportMode() const; - void setViewportMode(ViewportMode); - - unsigned cacheDatabaseMaximumSize() const; - void setCacheDatabaseMaximumSize(unsigned); - - QString cacheDatabasePath() const; - void setCacheDatabasePath(const QString &); - - QString assetPath() const; - void setAssetPath(const QString &); - - QString accessToken() const; - void setAccessToken(const QString &); - - QString apiBaseUrl() const; - void setApiBaseUrl(const QString &); - - QString localFontFamily() const; - void setLocalFontFamily(const QString &); - - std::function resourceTransform() const; - void setResourceTransform(const std::function &); - -private: - GLContextMode m_contextMode; - MapMode m_mapMode; - ConstrainMode m_constrainMode; - ViewportMode m_viewportMode; - - unsigned m_cacheMaximumSize; - QString m_cacheDatabasePath; - QString m_assetPath; - QString m_accessToken; - QString m_apiBaseUrl; - QString m_localFontFamily; - std::function m_resourceTransform; -}; - -struct Q_MAPBOXGL_EXPORT QMapboxGLCameraOptions { - QVariant center; // Coordinate - QVariant anchor; // QPointF - QVariant zoom; // double - QVariant bearing; // double - QVariant pitch; // double -}; - -class Q_MAPBOXGL_EXPORT QMapboxGL : public QObject -{ - Q_OBJECT - Q_PROPERTY(double latitude READ latitude WRITE setLatitude) - Q_PROPERTY(double longitude READ longitude WRITE setLongitude) - Q_PROPERTY(double zoom READ zoom WRITE setZoom) - Q_PROPERTY(double bearing READ bearing WRITE setBearing) - Q_PROPERTY(double pitch READ pitch WRITE setPitch) - Q_PROPERTY(QString styleJson READ styleJson WRITE setStyleJson) - Q_PROPERTY(QString styleUrl READ styleUrl WRITE setStyleUrl) - Q_PROPERTY(double scale READ scale WRITE setScale) - Q_PROPERTY(QMapbox::Coordinate coordinate READ coordinate WRITE setCoordinate) - Q_PROPERTY(QMargins margins READ margins WRITE setMargins) - -public: - enum MapChange { - MapChangeRegionWillChange = 0, - MapChangeRegionWillChangeAnimated, - MapChangeRegionIsChanging, - MapChangeRegionDidChange, - MapChangeRegionDidChangeAnimated, - MapChangeWillStartLoadingMap, - MapChangeDidFinishLoadingMap, - MapChangeDidFailLoadingMap, - MapChangeWillStartRenderingFrame, - MapChangeDidFinishRenderingFrame, - MapChangeDidFinishRenderingFrameFullyRendered, - MapChangeWillStartRenderingMap, - MapChangeDidFinishRenderingMap, - MapChangeDidFinishRenderingMapFullyRendered, - MapChangeDidFinishLoadingStyle, - MapChangeSourceDidChange - }; - - enum MapLoadingFailure { - StyleParseFailure, - StyleLoadFailure, - NotFoundFailure, - UnknownFailure - }; - - // Determines the orientation of the map. - enum NorthOrientation { - NorthUpwards, // Default - NorthRightwards, - NorthDownwards, - NorthLeftwards, - }; - - QMapboxGL(QObject* parent = 0, - const QMapboxGLSettings& = QMapboxGLSettings(), - const QSize& size = QSize(), - qreal pixelRatio = 1); - virtual ~QMapboxGL(); - - QString styleJson() const; - QString styleUrl() const; - - void setStyleJson(const QString &); - void setStyleUrl(const QString &); - - double latitude() const; - void setLatitude(double latitude); - - double longitude() const; - void setLongitude(double longitude); - - double scale() const; - void setScale(double scale, const QPointF ¢er = QPointF()); - - double zoom() const; - void setZoom(double zoom); - - double minimumZoom() const; - double maximumZoom() const; - - double bearing() const; - void setBearing(double degrees); - void setBearing(double degrees, const QPointF ¢er); - - double pitch() const; - void setPitch(double pitch); - void pitchBy(double pitch); - - NorthOrientation northOrientation() const; - void setNorthOrientation(NorthOrientation); - - QMapbox::Coordinate coordinate() const; - void setCoordinate(const QMapbox::Coordinate &); - void setCoordinateZoom(const QMapbox::Coordinate &, double zoom); - - void jumpTo(const QMapboxGLCameraOptions&); - - void setGestureInProgress(bool inProgress); - - void setTransitionOptions(qint64 duration, qint64 delay = 0); - - void addAnnotationIcon(const QString &name, const QImage &sprite); - - QMapbox::AnnotationID addAnnotation(const QMapbox::Annotation &); - void updateAnnotation(QMapbox::AnnotationID, const QMapbox::Annotation &); - void removeAnnotation(QMapbox::AnnotationID); - - bool setLayoutProperty(const QString &layer, const QString &property, const QVariant &value); - bool setPaintProperty(const QString &layer, const QString &property, const QVariant &value); - - bool isFullyLoaded() const; - - void moveBy(const QPointF &offset); - void scaleBy(double scale, const QPointF ¢er = QPointF()); - void rotateBy(const QPointF &first, const QPointF &second); - - void resize(const QSize &size); - - double metersPerPixelAtLatitude(double latitude, double zoom) const; - QMapbox::ProjectedMeters projectedMetersForCoordinate(const QMapbox::Coordinate &) const; - QMapbox::Coordinate coordinateForProjectedMeters(const QMapbox::ProjectedMeters &) const; - QPointF pixelForCoordinate(const QMapbox::Coordinate &) const; - QMapbox::Coordinate coordinateForPixel(const QPointF &) const; - - QMapbox::CoordinateZoom coordinateZoomForBounds(const QMapbox::Coordinate &sw, QMapbox::Coordinate &ne) const; - QMapbox::CoordinateZoom coordinateZoomForBounds(const QMapbox::Coordinate &sw, QMapbox::Coordinate &ne, double bearing, double pitch); - - void setMargins(const QMargins &margins); - QMargins margins() const; - - void addSource(const QString &sourceID, const QVariantMap& params); - bool sourceExists(const QString &sourceID); - void updateSource(const QString &sourceID, const QVariantMap& params); - void removeSource(const QString &sourceID); - - void addImage(const QString &name, const QImage &sprite); - void removeImage(const QString &name); - - void addCustomLayer(const QString &id, - QScopedPointer& host, - const QString& before = QString()); - void addLayer(const QVariantMap ¶ms, const QString& before = QString()); - bool layerExists(const QString &id); - void removeLayer(const QString &id); - - QVector layerIds() const; - - void setFilter(const QString &layer, const QVariant &filter); - QVariant getFilter(const QString &layer) const; - // When rendering on a different thread, - // should be called on the render thread. - void createRenderer(); - void destroyRenderer(); - void setFramebufferObject(quint32 fbo, const QSize &size); - -public slots: - void render(); - void connectionEstablished(); - - // Commit changes, load all the resources - // and renders the map when completed. - void startStaticRender(); - -signals: - void needsRendering(); - void mapChanged(QMapboxGL::MapChange); - void mapLoadingFailed(QMapboxGL::MapLoadingFailure, const QString &reason); - void copyrightsChanged(const QString ©rightsHtml); - - void staticRenderFinished(const QString &error); - -private: - Q_DISABLE_COPY(QMapboxGL) - - QMapboxGLPrivate *d_ptr; -}; - -Q_DECLARE_METATYPE(QMapboxGL::MapChange); -Q_DECLARE_METATYPE(QMapboxGL::MapLoadingFailure); - -#endif // QMAPBOXGL_H diff --git a/third_party/mapbox-gl-native-qt/x86_64/libqmapboxgl.so b/third_party/mapbox-gl-native-qt/x86_64/libqmapboxgl.so deleted file mode 100755 index a61c80c63d..0000000000 --- a/third_party/mapbox-gl-native-qt/x86_64/libqmapboxgl.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ee37b571a5a50d07f2fd1a3150aa2842f10576e96e01278bbc060815549d57e9 -size 10219704 diff --git a/third_party/maplibre-native-qt/.gitignore b/third_party/maplibre-native-qt/.gitignore new file mode 100644 index 0000000000..9adc6681c0 --- /dev/null +++ b/third_party/maplibre-native-qt/.gitignore @@ -0,0 +1 @@ +/maplibre/ diff --git a/third_party/maplibre-native-qt/aarch64 b/third_party/maplibre-native-qt/aarch64 new file mode 120000 index 0000000000..062c65e8d9 --- /dev/null +++ b/third_party/maplibre-native-qt/aarch64 @@ -0,0 +1 @@ +larch64/ \ No newline at end of file diff --git a/third_party/maplibre-native-qt/build.sh b/third_party/maplibre-native-qt/build.sh new file mode 100755 index 0000000000..c64f0fc649 --- /dev/null +++ b/third_party/maplibre-native-qt/build.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + +ARCHNAME="x86_64" +MAPLIBRE_FLAGS="-DMLN_QT_WITH_LOCATION=OFF" +if [ -f /AGNOS ]; then + ARCHNAME="larch64" + #MAPLIBRE_FLAGS="$MAPLIBRE_FLAGS -DCMAKE_SYSTEM_NAME=Android -DANDROID_ABI=arm64-v8a" +fi + +cd $DIR +if [ ! -d maplibre ]; then + git clone git@github.com:maplibre/maplibre-native-qt.git $DIR/maplibre +fi + +cd maplibre +git fetch --all +git checkout 3726266e127c1f94ad64837c9dbe03d238255816 +git submodule update --depth=1 --recursive --init + +# build +mkdir -p build +cd build +set -x +cmake $MAPLIBRE_FLAGS $DIR/maplibre +make -j$(nproc) || make -j2 || make -j1 + +INSTALL_DIR="$DIR/$ARCHNAME" +rm -rf $INSTALL_DIR +mkdir -p $INSTALL_DIR + +rm -rf $INSTALL_DIR/lib $DIR/include +mkdir -p $INSTALL_DIR/lib $INSTALL_DIR/include $DIR/include +cp -r $DIR/maplibre/build/src/core/*.so* $INSTALL_DIR/lib +cp -r $DIR/maplibre/build/src/core/include/* $INSTALL_DIR/include +cp -r $DIR/maplibre/src/**/*.hpp $DIR/include diff --git a/third_party/maplibre-native-qt/include/conversion_p.hpp b/third_party/maplibre-native-qt/include/conversion_p.hpp new file mode 100644 index 0000000000..38b03d498e --- /dev/null +++ b/third_party/maplibre-native-qt/include/conversion_p.hpp @@ -0,0 +1,241 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2018 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "geojson_p.hpp" +#include "types.hpp" + +#include +#include + +#include +#include + +#include + +namespace mbgl::style::conversion { + +std::string convertColor(const QColor &color); + +template <> +class ConversionTraits { +public: + static bool isUndefined(const QVariant &value) { return value.isNull() || !value.isValid(); } + + static bool isArray(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QMetaType::canConvert(value.metaType(), QMetaType(QMetaType::QVariantList)); +#else + return value.canConvert(QVariant::List); +#endif + } + + static std::size_t arrayLength(const QVariant &value) { return value.toList().size(); } + + static QVariant arrayMember(const QVariant &value, std::size_t i) { return value.toList()[static_cast(i)]; } + + static bool isObject(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QMetaType::canConvert(value.metaType(), QMetaType(QMetaType::QVariantMap)) || + value.typeId() == QMetaType::QByteArray +#else + return value.canConvert(QVariant::Map) || value.type() == QVariant::ByteArray +#endif + || QString(value.typeName()) == QStringLiteral("QMapLibre::Feature") || + value.userType() == qMetaTypeId>() || + value.userType() == qMetaTypeId>() || + value.userType() == qMetaTypeId>(); + } + + static std::optional objectMember(const QVariant &value, const char *key) { + auto map = value.toMap(); + auto iter = map.constFind(key); + + if (iter != map.constEnd()) { + return iter.value(); + } + + return {}; + } + + template + static std::optional eachMember(const QVariant &value, Fn &&fn) { + auto map = value.toMap(); + auto iter = map.constBegin(); + + while (iter != map.constEnd()) { + std::optional result = fn(iter.key().toStdString(), QVariant(iter.value())); + if (result) { + return result; + } + + ++iter; + } + + return {}; + } + + static std::optional toBool(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::Bool) { +#else + if (value.type() == QVariant::Bool) { +#endif + return value.toBool(); + } + + return {}; + } + + static std::optional toNumber(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::Int || value.typeId() == QMetaType::Double || + value.typeId() == QMetaType::Long || value.typeId() == QMetaType::LongLong || + value.typeId() == QMetaType::ULong || value.typeId() == QMetaType::ULongLong) { +#else + if (value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong || + value.type() == QVariant::ULongLong) { +#endif + return value.toFloat(); + } + + return {}; + } + + static std::optional toDouble(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::Int || value.typeId() == QMetaType::Double || + value.typeId() == QMetaType::Long || value.typeId() == QMetaType::LongLong || + value.typeId() == QMetaType::ULong || value.typeId() == QMetaType::ULongLong) { +#else + if (value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong || + value.type() == QVariant::ULongLong) { +#endif + return value.toDouble(); + } + + return {}; + } + + static std::optional toString(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::QString) { + return value.toString().toStdString(); + } + + if (value.typeId() == QMetaType::QColor) { + return convertColor(value.value()); + } +#else + if (value.type() == QVariant::String) { + return value.toString().toStdString(); + } + + if (value.type() == QVariant::Color) { + return convertColor(value.value()); + } +#endif + return {}; + } + + static std::optional toValue(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::Bool) { + return {value.toBool()}; + } + + if (value.typeId() == QMetaType::QString) { + return {value.toString().toStdString()}; + } + + if (value.typeId() == QMetaType::QColor) { + return {convertColor(value.value())}; + } + + if (value.typeId() == QMetaType::Int) { + return {static_cast(value.toInt())}; + } + + if (QMetaType::canConvert(value.metaType(), QMetaType(QMetaType::Double))) { + return {value.toDouble()}; + } +#else + if (value.type() == QVariant::Bool) { + return {value.toBool()}; + } + + if (value.type() == QVariant::String) { + return {value.toString().toStdString()}; + } + + if (value.type() == QVariant::Color) { + return {convertColor(value.value())}; + } + + if (value.type() == QVariant::Int) { + return {static_cast(value.toInt())}; + } + + if (value.canConvert(QVariant::Double)) { + return {value.toDouble()}; + } +#endif + return {}; + } + + static std::optional toGeoJSON(const QVariant &value, Error &error) { + if (value.typeName() == QStringLiteral("QMapLibre::Feature")) { + return GeoJSON{QMapLibre::GeoJSON::asFeature(value.value())}; + } + + if (value.userType() == qMetaTypeId>()) { + return featureCollectionToGeoJSON(value.value>()); + } + + if (value.userType() == qMetaTypeId>()) { + return featureCollectionToGeoJSON(value.value>()); + } + + if (value.userType() == qMetaTypeId>()) { + return featureCollectionToGeoJSON(value.value>()); + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() != QMetaType::QByteArray) { +#else + if (value.type() != QVariant::ByteArray) { +#endif + error = {"JSON data must be in QByteArray"}; + return {}; + } + + const QByteArray data = value.toByteArray(); + return parseGeoJSON(std::string(data.constData(), data.size()), error); + } + +private: + template + static GeoJSON featureCollectionToGeoJSON(const T &features) { + mapbox::feature::feature_collection collection; + collection.reserve(static_cast(features.size())); + for (const auto &feature : features) { + collection.push_back(QMapLibre::GeoJSON::asFeature(feature)); + } + return GeoJSON{std::move(collection)}; + } +}; + +template +std::optional convert(const QVariant &value, Error &error, Args &&...args) { + return convert(Convertible(value), error, std::forward(args)...); +} + +inline std::string convertColor(const QColor &color) { + return QString::asprintf("rgba(%d,%d,%d,%lf)", color.red(), color.green(), color.blue(), color.alphaF()) + .toStdString(); +} + +} // namespace mbgl::style::conversion diff --git a/third_party/maplibre-native-qt/include/export_core.hpp b/third_party/maplibre-native-qt/include/export_core.hpp new file mode 100644 index 0000000000..bd5ad495db --- /dev/null +++ b/third_party/maplibre-native-qt/include/export_core.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_CORE_EXPORT_H +#define QMAPLIBRE_CORE_EXPORT_H + +#include + +#if !defined(QT_MAPLIBRE_STATIC) +#if defined(QT_BUILD_MAPLIBRE_CORE_LIB) +#define Q_MAPLIBRE_CORE_EXPORT Q_DECL_EXPORT +#else +#define Q_MAPLIBRE_CORE_EXPORT Q_DECL_IMPORT +#endif +#else +#define Q_MAPLIBRE_CORE_EXPORT +#endif + +#endif // QMAPLIBRE_CORE_EXPORT_H diff --git a/third_party/maplibre-native-qt/include/export_location.hpp b/third_party/maplibre-native-qt/include/export_location.hpp new file mode 100644 index 0000000000..a986346884 --- /dev/null +++ b/third_party/maplibre-native-qt/include/export_location.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_LOCATION_EXPORT_H +#define QMAPLIBRE_LOCATION_EXPORT_H + +#include + +#if !defined(QT_MAPLIBRE_STATIC) +#if defined(QT_BUILD_MAPLIBRE_LOCATION_LIB) +#define Q_MAPLIBRE_LOCATION_EXPORT Q_DECL_EXPORT +#else +#define Q_MAPLIBRE_LOCATION_EXPORT Q_DECL_IMPORT +#endif +#else +#define Q_MAPLIBRE_LOCATION_EXPORT +#endif + +#endif // QMAPLIBRE_LOCATION_EXPORT_H diff --git a/third_party/maplibre-native-qt/include/export_widgets.hpp b/third_party/maplibre-native-qt/include/export_widgets.hpp new file mode 100644 index 0000000000..11bc288190 --- /dev/null +++ b/third_party/maplibre-native-qt/include/export_widgets.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_WIDGETS_EXPORT_H +#define QMAPLIBRE_WIDGETS_EXPORT_H + +#include + +#if !defined(QT_MAPLIBRE_STATIC) +#if defined(QT_BUILD_MAPLIBRE_WIDGETS_LIB) +#define Q_MAPLIBRE_WIDGETS_EXPORT Q_DECL_EXPORT +#else +#define Q_MAPLIBRE_WIDGETS_EXPORT Q_DECL_IMPORT +#endif +#else +#define Q_MAPLIBRE_WIDGETS_EXPORT +#endif + +#endif // QMAPLIBRE_WIDGETS_EXPORT_H diff --git a/third_party/maplibre-native-qt/include/geojson_p.hpp b/third_party/maplibre-native-qt/include/geojson_p.hpp new file mode 100644 index 0000000000..8387f70c4b --- /dev/null +++ b/third_party/maplibre-native-qt/include/geojson_p.hpp @@ -0,0 +1,30 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "types.hpp" + +#include +#include +#include + +#include + +#include + +namespace QMapLibre::GeoJSON { + +mbgl::Point asPoint(const Coordinate &coordinate); +mbgl::MultiPoint asMultiPoint(const Coordinates &multiPoint); +mbgl::LineString asLineString(const Coordinates &lineString); +mbgl::MultiLineString asMultiLineString(const CoordinatesCollection &multiLineString); +mbgl::Polygon asPolygon(const CoordinatesCollection &polygon); +mbgl::MultiPolygon asMultiPolygon(const CoordinatesCollections &multiPolygon); +mbgl::Value asPropertyValue(const QVariant &value); +mbgl::FeatureIdentifier asFeatureIdentifier(const QVariant &id); +mbgl::GeoJSONFeature asFeature(const Feature &feature); + +} // namespace QMapLibre::GeoJSON diff --git a/third_party/maplibre-native-qt/include/gl_widget.hpp b/third_party/maplibre-native-qt/include/gl_widget.hpp new file mode 100644 index 0000000000..b2630daea7 --- /dev/null +++ b/third_party/maplibre-native-qt/include/gl_widget.hpp @@ -0,0 +1,56 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_GL_WIDGET_H +#define QMAPLIBRE_GL_WIDGET_H + +#include + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + +QT_END_NAMESPACE + +namespace QMapLibre { + +class GLWidgetPrivate; + +class Q_MAPLIBRE_WIDGETS_EXPORT GLWidget : public QOpenGLWidget { + Q_OBJECT + +public: + explicit GLWidget(const Settings &); + ~GLWidget() override; + + Map *map(); + +protected: + // QWidget implementation. + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + + // Q{,Open}GLWidget implementation. + void initializeGL() override; + void paintGL() override; + +private: + Q_DISABLE_COPY(GLWidget) + + std::unique_ptr d_ptr; +}; + +} // namespace QMapLibre + +#endif // QMAPLIBRE_GL_WIDGET_H diff --git a/third_party/maplibre-native-qt/include/gl_widget_p.hpp b/third_party/maplibre-native-qt/include/gl_widget_p.hpp new file mode 100644 index 0000000000..c97781fd29 --- /dev/null +++ b/third_party/maplibre-native-qt/include/gl_widget_p.hpp @@ -0,0 +1,42 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + +QT_END_NAMESPACE + +namespace QMapLibre { + +class GLWidgetPrivate : public QObject { + Q_OBJECT + +public: + explicit GLWidgetPrivate(QObject *parent, Settings settings); + ~GLWidgetPrivate() override; + + void handleMousePressEvent(QMouseEvent *event); + void handleMouseMoveEvent(QMouseEvent *event); + void handleWheelEvent(QWheelEvent *event) const; + + std::unique_ptr m_map{}; + Settings m_settings; + +private: + Q_DISABLE_COPY(GLWidgetPrivate); + + QPointF m_lastPos; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/map.hpp b/third_party/maplibre-native-qt/include/map.hpp new file mode 100644 index 0000000000..cd56996185 --- /dev/null +++ b/third_party/maplibre-native-qt/include/map.hpp @@ -0,0 +1,205 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_MAP_H +#define QMAPLIBRE_MAP_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace QMapLibre { + +class MapPrivate; + +class Q_MAPLIBRE_CORE_EXPORT Map : public QObject { + Q_OBJECT + Q_PROPERTY(double latitude READ latitude WRITE setLatitude) + Q_PROPERTY(double longitude READ longitude WRITE setLongitude) + Q_PROPERTY(double zoom READ zoom WRITE setZoom) + Q_PROPERTY(double bearing READ bearing WRITE setBearing) + Q_PROPERTY(double pitch READ pitch WRITE setPitch) + Q_PROPERTY(QString styleJson READ styleJson WRITE setStyleJson) + Q_PROPERTY(QString styleUrl READ styleUrl WRITE setStyleUrl) + Q_PROPERTY(double scale READ scale WRITE setScale) + Q_PROPERTY(QMapLibre::Coordinate coordinate READ coordinate WRITE setCoordinate) + Q_PROPERTY(QMargins margins READ margins WRITE setMargins) + +public: + enum MapChange { + MapChangeRegionWillChange = 0, + MapChangeRegionWillChangeAnimated, + MapChangeRegionIsChanging, + MapChangeRegionDidChange, + MapChangeRegionDidChangeAnimated, + MapChangeWillStartLoadingMap, + MapChangeDidFinishLoadingMap, + MapChangeDidFailLoadingMap, + MapChangeWillStartRenderingFrame, + MapChangeDidFinishRenderingFrame, + MapChangeDidFinishRenderingFrameFullyRendered, + MapChangeWillStartRenderingMap, + MapChangeDidFinishRenderingMap, + MapChangeDidFinishRenderingMapFullyRendered, + MapChangeDidFinishLoadingStyle, + MapChangeSourceDidChange + }; + + enum MapLoadingFailure { + StyleParseFailure, + StyleLoadFailure, + NotFoundFailure, + UnknownFailure + }; + + // Determines the orientation of the map. + enum NorthOrientation { + NorthUpwards, // Default + NorthRightwards, + NorthDownwards, + NorthLeftwards, + }; + + explicit Map(QObject *parent = nullptr, + const Settings &settings = Settings(), + const QSize &size = QSize(), + qreal pixelRatio = 1); + ~Map() override; + + [[nodiscard]] QString styleJson() const; + [[nodiscard]] QString styleUrl() const; + + void setStyleJson(const QString &); + void setStyleUrl(const QString &); + + [[nodiscard]] double latitude() const; + void setLatitude(double latitude); + + [[nodiscard]] double longitude() const; + void setLongitude(double longitude); + + [[nodiscard]] double scale() const; + void setScale(double scale, const QPointF ¢er = QPointF()); + + [[nodiscard]] double zoom() const; + void setZoom(double zoom); + + [[nodiscard]] double minimumZoom() const; + [[nodiscard]] double maximumZoom() const; + + [[nodiscard]] double bearing() const; + void setBearing(double degrees); + void setBearing(double degrees, const QPointF ¢er); + + [[nodiscard]] double pitch() const; + void setPitch(double pitch); + void pitchBy(double pitch); + + [[nodiscard]] NorthOrientation northOrientation() const; + void setNorthOrientation(NorthOrientation); + + [[nodiscard]] Coordinate coordinate() const; + void setCoordinate(const Coordinate &coordinate); + void setCoordinateZoom(const Coordinate &coordinate, double zoom); + + void jumpTo(const CameraOptions &); + + void setGestureInProgress(bool inProgress); + + void setTransitionOptions(qint64 duration, qint64 delay = 0); + + void addAnnotationIcon(const QString &name, const QImage &sprite); + + AnnotationID addAnnotation(const Annotation &annotation); + void updateAnnotation(AnnotationID id, const Annotation &annotation); + void removeAnnotation(AnnotationID id); + + bool setLayoutProperty(const QString &layerId, const QString &propertyName, const QVariant &value); + bool setPaintProperty(const QString &layerId, const QString &propertyName, const QVariant &value); + + [[nodiscard]] bool isFullyLoaded() const; + + void moveBy(const QPointF &offset); + void scaleBy(double scale, const QPointF ¢er = QPointF()); + void rotateBy(const QPointF &first, const QPointF &second); + + void resize(const QSize &size); + + [[nodiscard]] QPointF pixelForCoordinate(const Coordinate &coordinate) const; + [[nodiscard]] Coordinate coordinateForPixel(const QPointF &pixel) const; + + [[nodiscard]] CoordinateZoom coordinateZoomForBounds(const Coordinate &sw, const Coordinate &ne) const; + [[nodiscard]] CoordinateZoom coordinateZoomForBounds(const Coordinate &sw, + const Coordinate &ne, + double bearing, + double pitch); + + void setMargins(const QMargins &margins); + [[nodiscard]] QMargins margins() const; + + void addSource(const QString &id, const QVariantMap ¶ms); + bool sourceExists(const QString &id); + void updateSource(const QString &id, const QVariantMap ¶ms); + void removeSource(const QString &id); + + void addImage(const QString &id, const QImage &sprite); + void removeImage(const QString &id); + + void addCustomLayer(const QString &id, + std::unique_ptr host, + const QString &before = QString()); + void addLayer(const QString &id, const QVariantMap ¶ms, const QString &before = QString()); + bool layerExists(const QString &id); + void removeLayer(const QString &id); + + [[nodiscard]] QVector layerIds() const; + + void setFilter(const QString &layerId, const QVariant &filter); + [[nodiscard]] QVariant getFilter(const QString &layerId) const; + // When rendering on a different thread, + // should be called on the render thread. + void createRenderer(); + void destroyRenderer(); + void setFramebufferObject(quint32 fbo, const QSize &size); + +public slots: + void render(); + void setConnectionEstablished(); + + // Commit changes, load all the resources + // and renders the map when completed. + void startStaticRender(); + +signals: + void needsRendering(); + void mapChanged(Map::MapChange); + void mapLoadingFailed(Map::MapLoadingFailure, const QString &reason); + void copyrightsChanged(const QString ©rightsHtml); + + void staticRenderFinished(const QString &error); + +private: + Q_DISABLE_COPY(Map) + + std::unique_ptr d_ptr; +}; + +} // namespace QMapLibre + +Q_DECLARE_METATYPE(QMapLibre::Map::MapChange); +Q_DECLARE_METATYPE(QMapLibre::Map::MapLoadingFailure); + +#endif // QMAPLIBRE_MAP_H diff --git a/third_party/maplibre-native-qt/include/map_observer_p.hpp b/third_party/maplibre-native-qt/include/map_observer_p.hpp new file mode 100644 index 0000000000..e68c72b17b --- /dev/null +++ b/third_party/maplibre-native-qt/include/map_observer_p.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "map.hpp" + +#include +#include + +#include + +#include +#include + +namespace QMapLibre { + +class MapPrivate; + +class MapObserver : public QObject, public mbgl::MapObserver { + Q_OBJECT + +public: + explicit MapObserver(MapPrivate *ptr); + ~MapObserver() override; + + // mbgl::MapObserver implementation. + void onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) final; + void onCameraIsChanging() final; + void onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) final; + void onWillStartLoadingMap() final; + void onDidFinishLoadingMap() final; + void onDidFailLoadingMap(mbgl::MapLoadError error, const std::string &what) final; + void onWillStartRenderingFrame() final; + void onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) final; + void onWillStartRenderingMap() final; + void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) final; + void onDidFinishLoadingStyle() final; + void onSourceChanged(mbgl::style::Source &source) final; + +signals: + void mapChanged(Map::MapChange); + void mapLoadingFailed(Map::MapLoadingFailure, const QString &reason); + void copyrightsChanged(const QString ©rightsHtml); + +private: + Q_DISABLE_COPY(MapObserver) + + MapPrivate *d_ptrRef; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/map_p.hpp b/third_party/maplibre-native-qt/include/map_p.hpp new file mode 100644 index 0000000000..9ca0c7e6f5 --- /dev/null +++ b/third_party/maplibre-native-qt/include/map_p.hpp @@ -0,0 +1,79 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "map.hpp" +#include "map_observer_p.hpp" +#include "map_renderer_p.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace QMapLibre { + +class MapPrivate : public QObject, public mbgl::RendererFrontend { + Q_OBJECT + +public: + explicit MapPrivate(Map *map, const Settings &settings, const QSize &size, qreal pixelRatio); + ~MapPrivate() override; + + // mbgl::RendererFrontend implementation. + void reset() final {} + void setObserver(mbgl::RendererObserver &observer) final; + void update(std::shared_ptr parameters) final; + + // These need to be called on the same thread. + void createRenderer(); + void destroyRenderer(); + void render(); + void setFramebufferObject(quint32 fbo, const QSize &size); + + using PropertySetter = std::optional (mbgl::style::Layer::*)( + const std::string &, const mbgl::style::conversion::Convertible &); + [[nodiscard]] bool setProperty(const PropertySetter &setter, + const QString &layerId, + const QString &name, + const QVariant &value) const; + + mbgl::EdgeInsets margins; + std::unique_ptr mapObj{}; + +public slots: + void requestRendering(); + +signals: + void needsRendering(); + +private: + Q_DISABLE_COPY(MapPrivate) + + std::recursive_mutex m_mapRendererMutex; + std::shared_ptr m_rendererObserver{}; + std::shared_ptr m_updateParameters{}; + + std::unique_ptr m_mapObserver{}; + std::unique_ptr m_mapRenderer{}; + std::unique_ptr> m_resourceTransform{}; + + Settings::GLContextMode m_mode; + qreal m_pixelRatio; + + QString m_localFontFamily; + + std::atomic_flag m_renderQueued = ATOMIC_FLAG_INIT; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/map_renderer_p.hpp b/third_party/maplibre-native-qt/include/map_renderer_p.hpp new file mode 100644 index 0000000000..b9a087c392 --- /dev/null +++ b/third_party/maplibre-native-qt/include/map_renderer_p.hpp @@ -0,0 +1,63 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "settings.hpp" + +#include "utils/renderer_backend.hpp" + +#include +#include +#include + +#include + +#include + +#include +#include + +namespace mbgl { +class Renderer; +class UpdateParameters; +} // namespace mbgl + +namespace QMapLibre { + +class RendererBackend; + +class MapRenderer : public QObject { + Q_OBJECT + +public: + MapRenderer(qreal pixelRatio, Settings::GLContextMode, const QString &localFontFamily); + ~MapRenderer() override; + + void render(); + void updateFramebuffer(quint32 fbo, const mbgl::Size &size); + void setObserver(mbgl::RendererObserver *observer); + + // Thread-safe, called by the Frontend + void updateParameters(std::shared_ptr parameters); + +signals: + void needsRendering(); + +private: + MBGL_STORE_THREAD(tid) + + Q_DISABLE_COPY(MapRenderer) + + std::mutex m_updateMutex; + std::shared_ptr m_updateParameters; + + RendererBackend m_backend; + std::unique_ptr m_renderer{}; + + bool m_forceScheduler{}; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/qgeomap.hpp b/third_party/maplibre-native-qt/include/qgeomap.hpp new file mode 100644 index 0000000000..5eb0180503 --- /dev/null +++ b/third_party/maplibre-native-qt/include/qgeomap.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "export_location.hpp" + +#include +#include + +#include + +namespace QMapLibre { + +class QGeoMapMapLibrePrivate; + +class Q_MAPLIBRE_LOCATION_EXPORT QGeoMapMapLibre : public QGeoMap { + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoMapMapLibre) + +public: + explicit QGeoMapMapLibre(QGeoMappingManagerEngine *engine, QObject *parent = nullptr); + ~QGeoMapMapLibre() override; + + [[nodiscard]] Capabilities capabilities() const override; + + void setSettings(const Settings &settings); + void setMapItemsBefore(const QString &mapItemsBefore); + + void addStyleParameter(StyleParameter *parameter); + void removeStyleParameter(StyleParameter *parameter); + void clearStyleParameters(); + +private Q_SLOTS: + // QMapLibre + void onMapChanged(Map::MapChange); + + // QDeclarativeGeoMapItemBase + void onMapItemPropertyChanged(); + void onMapItemSubPropertyChanged(); + void onMapItemUnsupportedPropertyChanged(); + void onMapItemGeometryChanged(); + + // StyleParameter + void onStyleParameterUpdated(StyleParameter *parameter); + +private: + QSGNode *updateSceneGraph(QSGNode *oldNode, QQuickWindow *window) override; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/qgeomap_p.hpp b/third_party/maplibre-native-qt/include/qgeomap_p.hpp new file mode 100644 index 0000000000..ce415d9bcf --- /dev/null +++ b/third_party/maplibre-native-qt/include/qgeomap_p.hpp @@ -0,0 +1,93 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "qgeomap.hpp" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace QMapLibre { + +class Map; +class StyleChange; + +class QGeoMapMapLibrePrivate : public QGeoMapPrivate { + Q_DECLARE_PUBLIC(QGeoMapMapLibre) + +public: + explicit QGeoMapMapLibrePrivate(QGeoMappingManagerEngine *engine); + ~QGeoMapMapLibrePrivate() override; + + QSGNode *updateSceneGraph(QSGNode *oldNode, QQuickWindow *window); + + QGeoMap::ItemTypes supportedMapItemTypes() const override; + void addMapItem(QDeclarativeGeoMapItemBase *item) override; + void removeMapItem(QDeclarativeGeoMapItemBase *item) override; + + void addStyleParameter(StyleParameter *parameter); + void removeStyleParameter(StyleParameter *parameter); + void clearStyleParameters(); + + /* Data members */ + enum SyncState : int { + NoSync = 0, + ViewportSync = 1 << 0, + CameraDataSync = 1 << 1, + MapTypeSync = 1 << 2, + VisibleAreaSync = 1 << 3 + }; + Q_DECLARE_FLAGS(SyncStates, SyncState); + + Settings m_settings; + QString m_mapItemsBefore; + + QList m_mapParameters; + + QTimer m_refresh; + bool m_shouldRefresh = true; + bool m_warned = false; + bool m_threadedRendering = false; + bool m_styleLoaded = false; + + SyncStates m_syncState = NoSync; + + std::vector> m_styleChanges; + +protected: + void changeViewportSize(const QSize &size) override; + void changeCameraData(const QGeoCameraData &data) override; +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + void changeActiveMapType(const QGeoMapType &mapType) override; +#else + void changeActiveMapType(const QGeoMapType mapType) override; +#endif + + void setVisibleArea(const QRectF &visibleArea) override; + QRectF visibleArea() const override; + +private: + Q_DISABLE_COPY(QGeoMapMapLibrePrivate); + + void syncStyleChanges(Map *map); + void threadedRenderingHack(QQuickWindow *window, Map *map); + + QRectF m_visibleArea; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoMapMapLibrePrivate::SyncStates) + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/qmaplibre.hpp b/third_party/maplibre-native-qt/include/qmaplibre.hpp new file mode 100644 index 0000000000..a8dc445e2b --- /dev/null +++ b/third_party/maplibre-native-qt/include/qmaplibre.hpp @@ -0,0 +1,9 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#include "export_core.hpp" +#include "map.hpp" +#include "settings.hpp" +#include "types.hpp" +#include "utils.hpp" diff --git a/third_party/maplibre-native-qt/include/qmaplibrewidgets.hpp b/third_party/maplibre-native-qt/include/qmaplibrewidgets.hpp new file mode 100644 index 0000000000..ebe9a8eea4 --- /dev/null +++ b/third_party/maplibre-native-qt/include/qmaplibrewidgets.hpp @@ -0,0 +1,6 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#include "export_widgets.hpp" +#include "gl_widget.hpp" diff --git a/third_party/maplibre-native-qt/include/qt_mapping_engine.hpp b/third_party/maplibre-native-qt/include/qt_mapping_engine.hpp new file mode 100644 index 0000000000..67cb4b56ce --- /dev/null +++ b/third_party/maplibre-native-qt/include/qt_mapping_engine.hpp @@ -0,0 +1,31 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "export_location.hpp" + +#include + +#include +#include + +namespace QMapLibre { + +class Q_MAPLIBRE_LOCATION_EXPORT QtMappingEngine : public QGeoMappingManagerEngine { + Q_OBJECT + +public: + QtMappingEngine(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString); + + QGeoMap *createMap() override; + +private: + Settings m_settings; + QString m_mapItemsBefore; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/settings.hpp b/third_party/maplibre-native-qt/include/settings.hpp new file mode 100644 index 0000000000..d6f88b871b --- /dev/null +++ b/third_party/maplibre-native-qt/include/settings.hpp @@ -0,0 +1,125 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_SETTINGS_H +#define QMAPLIBRE_SETTINGS_H + +#include +#include + +#include +#include + +#include +#include + +// TODO: this will be wrapped at some point +namespace mbgl { +class TileServerOptions; +} // namespace mbgl + +namespace QMapLibre { + +class SettingsPrivate; + +class Q_MAPLIBRE_CORE_EXPORT Settings { +public: + enum GLContextMode : bool { + UniqueGLContext, + SharedGLContext + }; + + enum MapMode { + Continuous = 0, + Static + }; + + enum ConstrainMode { + NoConstrain = 0, + ConstrainHeightOnly, + ConstrainWidthAndHeight + }; + + enum ViewportMode { + DefaultViewport = 0, + FlippedYViewport + }; + + enum ProviderTemplate { + NoProvider = 0, + MapLibreProvider, + MapTilerProvider, + MapboxProvider + }; + + using ResourceTransformFunction = std::function; + + explicit Settings(ProviderTemplate provider = NoProvider); + ~Settings(); + Settings(const Settings &s); + Settings(Settings &&s) noexcept; + Settings &operator=(const Settings &s); + Settings &operator=(Settings &&s) noexcept; + + [[nodiscard]] GLContextMode contextMode() const; + void setContextMode(GLContextMode); + + [[nodiscard]] MapMode mapMode() const; + void setMapMode(MapMode); + + [[nodiscard]] ConstrainMode constrainMode() const; + void setConstrainMode(ConstrainMode); + + [[nodiscard]] ViewportMode viewportMode() const; + void setViewportMode(ViewportMode); + + [[nodiscard]] unsigned cacheDatabaseMaximumSize() const; + void setCacheDatabaseMaximumSize(unsigned); + + [[nodiscard]] QString cacheDatabasePath() const; + void setCacheDatabasePath(const QString &path); + + [[nodiscard]] QString assetPath() const; + void setAssetPath(const QString &path); + + [[nodiscard]] QString apiKey() const; + void setApiKey(const QString &key); + + [[nodiscard]] QString apiBaseUrl() const; + void setApiBaseUrl(const QString &url); + + [[nodiscard]] QString localFontFamily() const; + void setLocalFontFamily(const QString &family); + + [[nodiscard]] QString clientName() const; + void setClientName(const QString &name); + + [[nodiscard]] QString clientVersion() const; + void setClientVersion(const QString &version); + + [[nodiscard]] ResourceTransformFunction resourceTransform() const; + void setResourceTransform(const ResourceTransformFunction &transform); + + void setProviderTemplate(ProviderTemplate providerTemplate); + void setStyles(const Styles &styles); + + [[nodiscard]] const Styles &styles() const; + [[nodiscard]] Styles providerStyles() const; + + [[nodiscard]] Coordinate defaultCoordinate() const; + void setDefaultCoordinate(const Coordinate &coordinate); + [[nodiscard]] double defaultZoom() const; + void setDefaultZoom(double zoom); + + [[nodiscard]] bool customTileServerOptions() const; + [[nodiscard]] const mbgl::TileServerOptions &tileServerOptions() const; + +private: + std::unique_ptr d_ptr; +}; + +} // namespace QMapLibre + +#endif // QMAPLIBRE_SETTINGS_H diff --git a/third_party/maplibre-native-qt/include/settings_p.hpp b/third_party/maplibre-native-qt/include/settings_p.hpp new file mode 100644 index 0000000000..257bdfd5a9 --- /dev/null +++ b/third_party/maplibre-native-qt/include/settings_p.hpp @@ -0,0 +1,57 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "settings.hpp" +#include "types.hpp" + +#include + +#include +#include + +#include +#include + +namespace mbgl { +class TileServerOptions; +} // namespace mbgl + +namespace QMapLibre { + +class SettingsPrivate { +public: + SettingsPrivate(); + + void setProviderTemplate(Settings::ProviderTemplate providerTemplate); + void setProviderApiBaseUrl(const QString &url); + + Settings::GLContextMode m_contextMode{Settings::SharedGLContext}; + Settings::MapMode m_mapMode{Settings::Continuous}; + Settings::ConstrainMode m_constrainMode{Settings::ConstrainHeightOnly}; + Settings::ViewportMode m_viewportMode{Settings::DefaultViewport}; + Settings::ProviderTemplate m_providerTemplate{Settings::NoProvider}; + + unsigned m_cacheMaximumSize; + QString m_cacheDatabasePath; + QString m_assetPath; + QString m_apiKey; + QString m_localFontFamily; + QString m_clientName; + QString m_clientVersion; + + Coordinate m_defaultCoordinate{}; + double m_defaultZoom{}; + + Styles m_styles; + + std::function m_resourceTransform; + + bool m_customTileServerOptions{}; + mbgl::TileServerOptions m_tileServerOptions{}; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/style_change_utils_p.hpp b/third_party/maplibre-native-qt/include/style_change_utils_p.hpp new file mode 100644 index 0000000000..991bb4077e --- /dev/null +++ b/third_party/maplibre-native-qt/include/style_change_utils_p.hpp @@ -0,0 +1,34 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace QMapLibre::StyleChangeUtils { + +Feature featureFromMapRectangle(QDeclarativeRectangleMapItem *item); +Feature featureFromMapCircle(QDeclarativeCircleMapItem *item); +Feature featureFromMapPolygon(QDeclarativePolygonMapItem *item); +Feature featureFromMapPolyline(QDeclarativePolylineMapItem *item); +Feature featureFromMapItem(QDeclarativeGeoMapItemBase *item); + +QString featureId(QDeclarativeGeoMapItemBase *item); +std::vector featureLayoutPropertiesFromMapPolyline(QDeclarativePolylineMapItem *item); +std::vector featureLayoutPropertiesFromMapItem(QDeclarativeGeoMapItemBase *item); +std::vector featurePaintPropertiesFromMapRectangle(QDeclarativeRectangleMapItem *item); +std::vector featurePaingPropertiesFromMapCircle(QDeclarativeCircleMapItem *item); +std::vector featurePaintPropertiesFromMapPolygon(QDeclarativePolygonMapItem *item); +std::vector featurePaintPropertiesFromMapPolyline(QDeclarativePolylineMapItem *item); +std::vector featurePaintPropertiesFromMapItem(QDeclarativeGeoMapItemBase *item); +std::vector featurePropertiesFromMapItem(QDeclarativeGeoMapItemBase *item); + +} // namespace QMapLibre::StyleChangeUtils diff --git a/third_party/maplibre-native-qt/include/texture_node.hpp b/third_party/maplibre-native-qt/include/texture_node.hpp new file mode 100644 index 0000000000..96f63b3534 --- /dev/null +++ b/third_party/maplibre-native-qt/include/texture_node.hpp @@ -0,0 +1,42 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include +#include +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif + +#include + +namespace QMapLibre { + +class QGeoMapMapLibre; + +class TextureNode : public QSGSimpleTextureNode { +public: + TextureNode(const Settings &setting, const QSize &size, qreal pixelRatio, QGeoMapMapLibre *geoMap); + + [[nodiscard]] Map *map() const; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void resize(const QSize &size, qreal pixelRatio, QQuickWindow *window); +#else + void resize(const QSize &size, qreal pixelRatio); +#endif + void render(QQuickWindow *); + +private: + std::unique_ptr m_map{}; + std::unique_ptr m_fbo{}; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/types.hpp b/third_party/maplibre-native-qt/include/types.hpp new file mode 100644 index 0000000000..696fab1a88 --- /dev/null +++ b/third_party/maplibre-native-qt/include/types.hpp @@ -0,0 +1,206 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_TYPES_H +#define QMAPLIBRE_TYPES_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace QMapLibre { + +using Coordinate = QPair; +using CoordinateZoom = QPair; +using ProjectedMeters = QPair; + +using Coordinates = QVector; +using CoordinatesCollection = QVector; + +using CoordinatesCollections = QVector; + +struct Q_MAPLIBRE_CORE_EXPORT Style { + enum Type { // Taken from Qt to be in sync with QtLocation + NoMap = 0, + StreetMap, + SatelliteMapDay, + SatelliteMapNight, + TerrainMap, + HybridMap, + TransitMap, + GrayStreetMap, + PedestrianMap, + CarNavigationMap, + CycleMap, + CustomMap = 100 + }; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + explicit Style(QString url_, QString name_ = QString()) + : url(std::move(url_)), + name(std::move(name_)) {} +#else + explicit Style(QString url_ = QString(), QString name_ = QString()) + : url(std::move(url_)), + name(std::move(name_)) {} +#endif + + QString url; + QString name; + QString description; + bool night{}; + Type type{CustomMap}; +}; + +using Styles = QVector