diff --git a/RELEASES.md b/RELEASES.md index 547add8a80..482e959d9b 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,11 @@ Version 0.9.3 (2023-06-XX) ======================== * New driving model +* 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 Version 0.9.2 (2023-05-22) ======================== diff --git a/cereal b/cereal index 8af4582508..c331e76b96 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 8af4582508dd7acef082c40ede11eb7018415d9a +Subproject commit c331e76b966d324a4f8d56b48db44f59601423dc diff --git a/common/params.cc b/common/params.cc index 530ffe3051..843c57c564 100644 --- a/common/params.cc +++ b/common/params.cc @@ -104,6 +104,7 @@ std::unordered_map keys = { {"DisablePowerDown", PERSISTENT}, {"ExperimentalMode", PERSISTENT}, {"ExperimentalModeConfirmed", PERSISTENT}, + {"LongitudinalPersonality", PERSISTENT}, {"ExperimentalLongitudinalEnabled", PERSISTENT}, {"DisableUpdates", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT}, diff --git a/panda b/panda index 5847d7dbc0..bf2c007103 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 5847d7dbc05df3bc76243b6704b9d98544eb53df +Subproject commit bf2c0071036bae4e0c00c37ae43237fce6063e31 diff --git a/poetry.lock b/poetry.lock index e0e843ff0d..e2ef2fc29e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -190,7 +190,7 @@ tests = ["pytest"] name = "astroid" version = "2.15.5" description = "An abstract syntax tree for Python with inference support." -category = "main" +category = "dev" optional = false python-versions = ">=3.7.2" @@ -837,7 +837,7 @@ tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest (==5.4.3)", "pytes name = "dill" version = "0.3.5.1" description = "serialize all of python" -category = "main" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" @@ -1631,7 +1631,7 @@ six = "*" name = "isort" version = "5.10.1" description = "A Python utility / library to sort Python imports." -category = "main" +category = "dev" optional = false python-versions = ">=3.6.1,<4.0" @@ -1982,7 +1982,7 @@ tabulate = "*" name = "lazy-object-proxy" version = "1.7.1" description = "A fast and thorough lazy object proxy." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -3255,7 +3255,7 @@ python-versions = "*" name = "pylint" version = "2.17.4" description = "python code static checker" -category = "main" +category = "dev" optional = false python-versions = ">=3.7.2" @@ -4403,7 +4403,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -4709,7 +4709,7 @@ python-versions = ">=3.7" name = "wrapt" version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." -category = "main" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" @@ -4804,7 +4804,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "1.1" python-versions = "~3.8" -content-hash = "454ab16c681fa1754b26156f767ca0701eac357b66c146d71d61b39cbed42f5a" +content-hash = "8c0419671beb3e8c8f0add750929f5be32be6fa3f077d44614cbd598b4cadc00" [metadata.files] adal = [ diff --git a/pyproject.toml b/pyproject.toml index fa03248b9b..52dd7f2a02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,6 @@ psutil = "^5.9.1" pycapnp = "==1.1.0" pycryptodome = "^3.15.0" PyJWT = "^2.5.0" -pylint = "^2.15.4" pyopencl = "^2022.2.4" pyserial = "^3.5" python-dateutil = "^2.8.2" @@ -92,6 +91,7 @@ pprofile = "^2.1.0" pre-commit = "^2.19.0" pycurl = "^7.45.1" pygame = "^2.1.2" +pylint = "^2.17.4" pyprof2calltree = "^1.4.5" pytest = "^7.1.2" pytest-xdist = "^2.5.0" diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index bd7c3acc36..b1390bee0b 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -227,6 +227,7 @@ FW_VERSIONS = { b'68510283AG', b'68527346AE', b'68527375AD', + b'68527382AE', ], (Ecu.srs, 0x744, None): [ b'68428609AB', @@ -254,6 +255,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x753, None): [ b'04672892AB', b'04672932AB', + b'22DTRHD_AA', b'68320950AH', b'68320950AI', b'68320950AJ', @@ -265,8 +267,10 @@ FW_VERSIONS = { b'68475160AG', ], (Ecu.eps, 0x75A, None): [ + b'21590101AA', b'68273275AF', b'68273275AG', + b'68273275AH', b'68312176AE', b'68312176AG', b'68440789AC', @@ -283,7 +287,9 @@ FW_VERSIONS = { b'05036065AE ', b'05036066AE ', b'05149846AA ', + b'05149848AA ', b'68378701AI ', + b'68378748AL ', b'68378758AM ', b'68448163AJ', b'68448165AK', diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index edc40f629c..9b7b6ad83e 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -213,8 +213,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): [ diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index e420af3890..5507d37df9 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) @@ -99,12 +105,6 @@ def match_fw_to_car_fuzzy(fw_versions_dict, config, log=True, exclude=None): 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 # TODO: actually fine to be a set. we never assumed that there would be duplicate fw versions, # but now there can be duplicate platform codes for a platform. well we now get the set of platform codes for a @@ -116,7 +116,11 @@ def match_fw_to_car_fuzzy(fw_versions_dict, config, log=True, exclude=None): continue for addr, fws in fw_by_addr.items(): - if addr[0] not 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] not in FUZZY_EXCLUDE_ECUS: for f in fws: all_fw_versions[(addr[1], addr[2], f)].append(candidate) @@ -128,6 +132,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, config, log=True, exclude=None): matched_ecus = set() candidate = None for addr, versions in fw_versions_dict.items(): + ecu_key = (addr[0], addr[1]) for version in versions: # All cars that have this FW response on the specified address ecu_key = (addr[0], addr[1]) @@ -162,7 +167,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, config, log=True, exclude=None): return set() -def match_fw_to_car_exact(fw_versions_dict, _=None) -> Set[str]: +def match_fw_to_car_exact(fw_versions_dict, _=None, 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 @@ -218,7 +223,7 @@ def match_fw_to_car_orig(fw_versions, allow_exact=True, allow_fuzzy=True): return True, set() -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: @@ -231,7 +236,7 @@ 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, FW_QUERY_CONFIGS[brand]) + matches |= match_func(fw_versions_dict, FW_QUERY_CONFIGS[brand], log=log) if len(matches): return exact_match, matches @@ -366,7 +371,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/hyundai/values.py b/selfdrive/car/hyundai/values.py index 9d9504763b..a368f03000 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -865,12 +865,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): [ diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 2386f493e0..f89595198b 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -10,7 +10,7 @@ from cereal import car from common.params import Params from selfdrive.car.car_helpers import interfaces from selfdrive.car.fingerprints import FW_VERSIONS -from selfdrive.car.fw_versions import FW_QUERY_CONFIGS, VERSIONS, match_fw_to_car, get_fw_versions +from selfdrive.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, match_fw_to_car, get_fw_versions CarFw = car.CarParams.CarFw Ecu = car.CarParams.Ecu @@ -33,27 +33,51 @@ class TestFwFingerprint(unittest.TestCase): self.assertEqual(candidates[0], expected) @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) - def test_fw_fingerprint(self, brand, car_model, ecus): + def test_exact_match(self, brand, car_model, ecus): CP = car.CarParams.new_message() for _ in range(200): fw = [] for ecu, fw_versions in ecus.items(): - if not len(fw_versions): - raise unittest.SkipTest("Car model has no FW versions") ecu_name, addr, sub_addr = ecu fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) CP.carFw = fw - _, matches = match_fw_to_car(CP.carFw) + _, matches = match_fw_to_car(CP.carFw, allow_fuzzy=False) self.assertFingerprints(matches, car_model) - def test_no_duplicate_fw_versions(self): + @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) + def test_fuzzy_match_ecu_count(self, brand, car_model, ecus): + # Asserts that fuzzy matching does not count matching FW, but ECU address keys + valid_ecus = [e for e in ecus if e[0] not in FUZZY_EXCLUDE_ECUS] + if not len(valid_ecus): + raise unittest.SkipTest("Car model has no compatible ECUs for fuzzy matching") + + fw = [] + for ecu in valid_ecus: + ecu_name, addr, sub_addr = ecu + for _ in range(5): + # Add multiple FW versions to simulate ECU returning to multiple queries in a brand + fw.append({"ecu": ecu_name, "fwVersion": random.choice(ecus[ecu]), 'brand': brand, + "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) + CP = car.CarParams.new_message(carFw=fw) + _, matches = match_fw_to_car(CP.carFw, allow_exact=False, log=False) + + # Assert no match if there are not enough unique ECUs + unique_ecus = {(f['address'], f['subAddress']) for f in fw} + if len(unique_ecus) < 2: + self.assertEqual(len(matches), 0, car_model) + # There won't always be a match due to shared FW, but if there is it should be correct + elif len(matches): + self.assertFingerprints(matches, car_model) + + def test_fw_version_lists(self): for car_model, ecus in FW_VERSIONS.items(): with self.subTest(car_model=car_model): for ecu, ecu_fw in ecus.items(): with self.subTest(ecu): duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1} - self.assertFalse(len(duplicates), f"{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}") + self.assertFalse(len(duplicates), f'{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}') + self.assertGreater(len(ecu_fw), 0, f'{car_model}: No FW versions: Ecu.{ECU_NAME[ecu[0]]}') def test_all_addrs_map_to_one_ecu(self): for brand, cars in VERSIONS.items(): diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 1fc21fe5c0..1529f4742c 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -923,6 +923,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 +933,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 +950,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 +963,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 +974,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', @@ -1154,6 +1157,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 +1166,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 +1175,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 +1185,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 +1193,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', diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 660002691a..23fb1b7790 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import os import numpy as np - +from cereal import log from common.realtime import sec_since_boot from common.numpy_fast import clip from system.swaglog import cloudlog @@ -54,18 +54,38 @@ FCW_IDXS = T_IDXS < 5.0 T_DIFFS = np.diff(T_IDXS, prepend=[0.]) MIN_ACCEL = -3.5 MAX_ACCEL = 2.0 -T_FOLLOW = 1.45 COMFORT_BRAKE = 2.5 STOP_DISTANCE = 6.0 +def get_jerk_factor(personality=log.LongitudinalPersonality.standard): + if personality==log.LongitudinalPersonality.relaxed: + return 1.0 + elif personality==log.LongitudinalPersonality.standard: + return 1.0 + elif personality==log.LongitudinalPersonality.aggressive: + return 0.5 + else: + raise NotImplementedError("Longitudinal personality not supported") + + +def get_T_FOLLOW(personality=log.LongitudinalPersonality.standard): + if personality==log.LongitudinalPersonality.relaxed: + return 1.75 + elif personality==log.LongitudinalPersonality.standard: + return 1.45 + elif personality==log.LongitudinalPersonality.aggressive: + return 1.25 + else: + raise NotImplementedError("Longitudinal personality not supported") + def get_stopped_equivalence_factor(v_lead): return (v_lead**2) / (2 * COMFORT_BRAKE) -def get_safe_obstacle_distance(v_ego, t_follow=T_FOLLOW): +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): - return get_safe_obstacle_distance(v_ego) - 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(): @@ -161,7 +181,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, T_FOLLOW, LEAD_DANGER_FACTOR]) + 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) @@ -249,10 +270,11 @@ class LongitudinalMpc: for i in range(N): self.solver.cost_set(i, 'Zl', Zl) - def set_weights(self, prev_accel_constraint=True): + def set_weights(self, prev_accel_constraint=True, personality=log.LongitudinalPersonality.standard): + jerk_factor = get_jerk_factor(personality) if self.mode == 'acc': a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0 - cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, J_EGO_COST] + cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, jerk_factor * a_change_cost, jerk_factor * J_EGO_COST] constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] elif self.mode == 'blended': a_change_cost = 40.0 if prev_accel_constraint else 0 @@ -307,7 +329,8 @@ class LongitudinalMpc: self.cruise_min_a = min_a self.max_a = max_a - def update(self, radarstate, v_cruise, x, v, a, j): + def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard): + t_follow = get_T_FOLLOW(personality) v_ego = self.x0[1] self.status = radarstate.leadOne.status or radarstate.leadTwo.status @@ -334,7 +357,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) + cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, get_T_FOLLOW()) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) self.source = SOURCES[np.argmin(x_obstacles[0])] @@ -368,7 +391,7 @@ class LongitudinalMpc: self.params[:,2] = np.min(x_obstacles, axis=1) self.params[:,3] = np.copy(self.prev_a) - self.params[:,4] = T_FOLLOW + 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 @@ -380,9 +403,9 @@ 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], T_FOLLOW))- 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], T_FOLLOW))- self.x_sol[:,0] < 0.0) and \ + 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' diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 3089499687..a9c3cc7804 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -2,6 +2,8 @@ import math import numpy as np from common.numpy_fast import clip, interp +from common.params import Params +from cereal import log import cereal.messaging as messaging from common.conversions import Conversions as CV @@ -57,6 +59,16 @@ class LongitudinalPlanner: self.a_desired_trajectory = np.zeros(CONTROL_N) self.j_desired_trajectory = np.zeros(CONTROL_N) self.solverExecutionTime = 0.0 + self.params = Params() + self.param_read_counter = 0 + self.read_param() + self.personality = log.LongitudinalPersonality.standard + + def read_param(self): + try: + self.personality = int(self.params.get('LongitudinalPersonality')) + except (ValueError, TypeError): + self.personality = log.LongitudinalPersonality.standard @staticmethod def parse_model(model_msg, model_error): @@ -75,6 +87,9 @@ class LongitudinalPlanner: return x, v, a, j def update(self, sm): + if self.param_read_counter % 50 == 0: + self.read_param() + self.param_read_counter += 1 self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' v_ego = sm['carState'].vEgo @@ -114,11 +129,11 @@ class LongitudinalPlanner: accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) 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) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=self.personality) 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) @@ -154,5 +169,6 @@ class LongitudinalPlanner: longitudinalPlan.fcw = self.fcw longitudinalPlan.solverExecutionTime = self.mpc.solve_time + longitudinalPlan.personality = self.personality pm.send('longitudinalPlan', plan_send) diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index 63b0733bba..0ea76d260f 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -2,10 +2,10 @@ import argparse -from selfdrive.test.process_replay.compare_logs import save_log from selfdrive.test.process_replay.process_replay import CONFIGS, replay_process from tools.lib.logreader import MultiLogIterator from tools.lib.route import Route +from tools.lib.helpers import save_log if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run process on route and create new logs", diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index c6b135935f..e9a1b2cb5b 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -7,6 +7,7 @@ import sys import traceback from typing import List, Tuple, Union +from cereal import log import cereal.messaging as messaging import selfdrive.sentry as sentry from common.basedir import BASEDIR @@ -44,6 +45,7 @@ def manager_init() -> None: ("HasAcceptedTerms", "0"), ("LanguageSetting", "main_en"), ("OpenpilotEnabledToggle", "1"), + ("LongitudinalPersonality", str(log.LongitudinalPersonality.standard)), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) diff --git a/selfdrive/test/process_replay/__init__.py b/selfdrive/test/process_replay/__init__.py index e69de29bb2..6667deaa2d 100644 --- a/selfdrive/test/process_replay/__init__.py +++ b/selfdrive/test/process_replay/__init__.py @@ -0,0 +1 @@ +from selfdrive.test.process_replay.process_replay import CONFIGS, get_process_config, replay_process, replay_process_with_name # noqa: F401 diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index 48752d2222..52898d8810 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import bz2 import sys import math import capnp @@ -12,16 +11,6 @@ from tools.lib.logreader import LogReader EPSILON = sys.float_info.epsilon -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) - - def remove_ignored_fields(msg, ignore): msg = msg.as_builder() for key in ignore: diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 9c1ec4e931..55d95a6a6a 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -14,11 +14,12 @@ from common.transformations.camera import tici_f_frame_size, tici_d_frame_size from system.hardware import PC from selfdrive.manager.process_config import managed_processes from selfdrive.test.openpilotci import BASE_URL, get_url -from selfdrive.test.process_replay.compare_logs import compare_logs, save_log +from selfdrive.test.process_replay.compare_logs import compare_logs from selfdrive.test.process_replay.test_processes import format_diff from system.version import get_commit from tools.lib.framereader import FrameReader from tools.lib.logreader import LogReader +from tools.lib.helpers import save_log TEST_ROUTE = "2f4452b03ccb98f0|2022-12-03--13-45-30" SEGMENT = 6 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index d6d5b25bde..7ee1d05962 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -333,7 +333,28 @@ def get_process_config(name): raise Exception(f"Cannot find process config with name: {name}") from ex -def replay_process(cfg, lr, fingerprint=None): +def replay_process_with_name(name, lr, *args, **kwargs): + cfg = get_process_config(name) + return replay_process(cfg, lr, *args, **kwargs) + + +def replay_process(cfg, lr, fingerprint=None, return_all_logs=False): + all_msgs = list(lr) + process_logs = _replay_single_process(cfg, all_msgs, fingerprint) + + if return_all_logs: + keys = set(cfg.subs) + modified_logs = [m for m in all_msgs if m.which() not in keys] + modified_logs.extend(process_logs) + modified_logs.sort(key=lambda m: m.logMonoTime) + log_msgs = modified_logs + else: + log_msgs = process_logs + + return log_msgs + + +def _replay_single_process(cfg, lr, fingerprint): with OpenpilotPrefix(): controlsState = None initialized = False @@ -484,7 +505,7 @@ def setup_env(CP=None, cfg=None, controlsState=None, lr=None, fingerprint=None, params.put_bool("ExperimentalLongitudinalEnabled", True) -def check_enabled(msgs): +def check_openpilot_enabled(msgs): cur_enabled_count = 0 max_enabled_count = 0 for msg in msgs: diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c794a0434a..2332054b69 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -dfe1e4a5fd7a464c91c0d2e70592b4b1470fda8d \ No newline at end of file +3e684aef4483b8d311d71bab3bb543d7bad26563 diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index 63eb37d29d..cc697bb9bf 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -19,7 +19,7 @@ from panda.python import Panda from selfdrive.car.toyota.values import EPS_SCALE from selfdrive.manager.process import ensure_running from selfdrive.manager.process_config import managed_processes -from selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, setup_env, check_enabled +from selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, setup_env, check_openpilot_enabled from selfdrive.test.update_ci_routes import upload_route from tools.lib.route import Route from tools.lib.framereader import FrameReader @@ -343,7 +343,7 @@ def regen_segment(lr, frs=None, daemons="all", outdir=FAKEDATA, disable_tqdm=Fal segment = params.get("CurrentRoute", encoding='utf-8') + "--0" seg_path = os.path.join(outdir, segment) # check to make sure openpilot is engaged in the route - if not check_enabled(LogReader(os.path.join(seg_path, "rlog"))): + if not check_openpilot_enabled(LogReader(os.path.join(seg_path, "rlog"))): raise Exception(f"Route did not engage for long enough: {segment}") return seg_path diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index deed1ec48f..02922c530d 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -9,11 +9,12 @@ from typing import Any, DefaultDict, Dict from selfdrive.car.car_helpers import interface_names from selfdrive.test.openpilotci import get_url, upload_file -from selfdrive.test.process_replay.compare_logs import compare_logs, save_log -from selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, check_enabled, replay_process +from selfdrive.test.process_replay.compare_logs import compare_logs +from selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, check_openpilot_enabled, replay_process from system.version import get_commit from tools.lib.filereader import FileReader from tools.lib.logreader import LogReader +from tools.lib.helpers import save_log source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY @@ -104,7 +105,7 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non # check to make sure openpilot is engaged in the route if cfg.proc_name == "controlsd": - if not check_enabled(log_msgs): + if not check_openpilot_enabled(log_msgs): return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs try: diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 0c8132343c..64bf44d2ce 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -89,6 +89,12 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { #endif }; + + std::vector longi_button_texts{tr("Aggressive"), tr("Standard"), tr("Relaxed")}; + ButtonParamControl* long_personality_setting = new ButtonParamControl("LongitudinalPersonality", tr("Driving Personality"), + tr("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."), + "../assets/offroad/icon_speed_limit.png", + longi_button_texts); for (auto &[param, title, desc, icon] : toggle_defs) { auto toggle = new ParamControl(param, title, desc, icon, this); @@ -97,6 +103,11 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { addItem(toggle); toggles[param.toStdString()] = toggle; + + // insert longitudinal personality after NDOG toggle + if (param == "DisengageOnAccelerator") { + addItem(long_personality_setting); + } } // Toggles with confirmation dialogs diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 6db6a6cdfe..d25c8490f3 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -126,19 +126,19 @@ void SoftwarePanel::updateLabels() { downloadBtn->setValue(updater_state); } else { if (failed) { - downloadBtn->setText("CHECK"); - downloadBtn->setValue("failed to check for update"); + downloadBtn->setText(tr("CHECK")); + downloadBtn->setValue(tr("failed to check for update")); } else if (params.getBool("UpdaterFetchAvailable")) { - downloadBtn->setText("DOWNLOAD"); - downloadBtn->setValue("update available"); + downloadBtn->setText(tr("DOWNLOAD")); + downloadBtn->setValue(tr("update available")); } else { - QString lastUpdate = "never"; + QString lastUpdate = tr("never"); auto tm = params.get("LastUpdateTime"); if (!tm.empty()) { lastUpdate = timeAgo(QDateTime::fromString(QString::fromStdString(tm + "Z"), Qt::ISODate)); } - downloadBtn->setText("CHECK"); - downloadBtn->setValue("up to date, last checked " + lastUpdate); + downloadBtn->setText(tr("CHECK")); + downloadBtn->setValue(tr("up to date, last checked %1").arg(lastUpdate)); } downloadBtn->setEnabled(true); } diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 770b9b92dd..654c6e2c1c 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -60,7 +61,7 @@ public: public slots: void showDescription() { description->setVisible(true); - }; + } signals: void showDescriptionEvent(); @@ -106,7 +107,7 @@ signals: void clicked(); public slots: - void setEnabled(bool enabled) { btn.setEnabled(enabled); }; + void setEnabled(bool enabled) { btn.setEnabled(enabled); } private: QPushButton btn; @@ -163,7 +164,7 @@ public: void setConfirmation(bool _confirm, bool _store_confirm) { confirm = _confirm; store_confirm = _store_confirm; - }; + } void setActiveIcon(const QString &icon) { active_icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); @@ -175,11 +176,11 @@ public: toggle.togglePosition(); setIcon(state); } - }; + } void showEvent(QShowEvent *event) override { refresh(); - }; + } private: void setIcon(bool state) { @@ -188,7 +189,7 @@ private: } else if (!icon_pixmap.isNull()) { icon_label->setPixmap(icon_pixmap); } - }; + } std::string key; Params params; @@ -197,6 +198,55 @@ private: bool store_confirm = false; }; +class ButtonParamControl : public AbstractControl { + Q_OBJECT +public: + ButtonParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, + const std::vector &button_texts, const int minimum_button_width = 225) : AbstractControl(title, desc, icon) { + const QString style = R"( + QPushButton { + border-radius: 50px; + font-size: 40px; + font-weight: 500; + height:100px; + padding: 0 25 0 25; + color: #E4E4E4; + background-color: #393939; + } + QPushButton:pressed { + background-color: #4a4a4a; + } + QPushButton:checked { + background-color: #33Ab4C; + } + )"; + key = param.toStdString(); + int value = atoi(params.get(key).c_str()); + + QButtonGroup *button_group = new QButtonGroup(this); + button_group->setExclusive(true); + for (int i = 0; i < button_texts.size(); i++) { + QPushButton *button = new QPushButton(button_texts[i], this); + button->setCheckable(true); + button->setChecked(i == value); + button->setStyleSheet(style); + button->setMinimumWidth(minimum_button_width); + hlayout->addWidget(button); + button_group->addButton(button, i); + } + + QObject::connect(button_group, QOverload::of(&QButtonGroup::buttonToggled), [=](int id, bool checked) { + if (checked) { + params.put(key, std::to_string(id)); + } + }); + } + +private: + std::string key; + Params params; +}; + class ListWidget : public QWidget { Q_OBJECT public: @@ -236,7 +286,7 @@ class LayoutWidget : public QWidget { public: LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { setLayout(l); - }; + } }; class ClickableWidget : public QWidget { diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 71f3df7597..447f66f016 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -858,6 +858,26 @@ This may take up to a minute. Uninstall Deinstallieren + + failed to check for update + + + + up to date, last checked %1 + + + + DOWNLOAD + + + + update available + + + + never + + SshControl @@ -1036,6 +1056,26 @@ 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. + + 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. + + Updater diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 019eb1b71b..6dc183d7f7 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -854,6 +854,26 @@ This may take up to a minute. Uninstall アンインストール + + failed to check for update + + + + up to date, last checked %1 + + + + DOWNLOAD + + + + update available + + + + never + + SshControl @@ -1030,6 +1050,26 @@ 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. + + 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. + + Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index c5247ccfa0..b751e9cf57 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -855,6 +855,26 @@ This may take up to a minute. Uninstall 제거 + + failed to check for update + 업데이트 확인 실패 + + + up to date, last checked %1 + 최신 상태, 마지막으로 확인 %1 + + + DOWNLOAD + 다운로드 + + + update available + 업데이트 가능 + + + never + 업데이트 안함 + SshControl @@ -1031,6 +1051,26 @@ 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 롱컨트롤 알파를 활성화하는경우 실험적 모드 활성화를 권장합니다. + + 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. + + Updater diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 6687fea865..4b2a3d88c8 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -859,6 +859,26 @@ Isso pode levar até um minuto. Uninstall Desinstalar + + 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 + SshControl @@ -1035,6 +1055,26 @@ Isso pode levar até um minuto. 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. + + Aggressive + Disputador + + + Standard + Neutro + + + Relaxed + Calmo + + + Driving Personality + Personalidade de Condução + + + 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 disputador 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. + Updater diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index f6924b3c57..1d7cdd9f60 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -852,6 +852,26 @@ This may take up to a minute. Uninstall 卸载 + + failed to check for update + + + + up to date, last checked %1 + + + + DOWNLOAD + + + + update available + + + + never + + SshControl @@ -1028,6 +1048,26 @@ 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. + + 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. + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 243a91ebd1..c6e2d22f5a 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -854,6 +854,26 @@ This may take up to a minute. Uninstall 解除安裝 + + failed to check for update + 檢查更新失敗 + + + up to date, last checked %1 + 已經是最新版本,上次檢查時間為 %1 + + + DOWNLOAD + 下載 + + + update available + 有可用的更新 + + + never + 從未更新 + SshControl @@ -1020,15 +1040,35 @@ This may take up to a minute. 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 版本時,建議同時啟用實驗性模式(Experimental mode)。 + + + 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 會與前車保持較遠的距離。 @@ -1070,23 +1110,23 @@ 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 diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index df4852183e..1487db0842 100755 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -2,9 +2,11 @@ import unittest import time import math +import threading from dataclasses import dataclass from tabulate import tabulate +import cereal.messaging as messaging from system.hardware import HARDWARE, TICI from system.hardware.tici.power_monitor import get_power from selfdrive.manager.process_config import managed_processes @@ -24,8 +26,22 @@ PROCS = [ Proc('modeld', 0.93, atol=0.2), Proc('dmonitoringmodeld', 0.4), Proc('encoderd', 0.23), + Proc('mapsd', 0.05), + Proc('navmodeld', 0.05), ] +def send_llk_msg(done): + pm = messaging.PubMaster(['liveLocationKalman']) + 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' + + # Send liveLocationKalman at 20hz + while not done.is_set(): + pm.send('liveLocationKalman', msg) + time.sleep(1/20) + class TestPowerDraw(unittest.TestCase): @@ -46,6 +62,9 @@ class TestPowerDraw(unittest.TestCase): def test_camera_procs(self): baseline = get_power() + done = threading.Event() + thread = threading.Thread(target=send_llk_msg, args=(done,), daemon=True) + thread.start() prev = baseline used = {} @@ -57,6 +76,7 @@ class TestPowerDraw(unittest.TestCase): used[proc.name] = now - prev prev = now + done.set() manager_cleanup() tab = [] diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 50f66e5182..13cd76af3a 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -437,9 +437,11 @@ void BinaryItemDelegate::drawSignalCell(QPainter *painter, const QStyleOptionVie auto sig_color = getColor(sig); QColor color = sig_color; color.setAlpha(item->bg_color.alpha()); - painter->fillRect(rc, Qt::white); + // Mixing the signal colour with the Base background color to fade it + painter->fillRect(rc, QApplication::palette().color(QPalette::Base)); painter->fillRect(rc, color); + // Draw edges color = sig_color.darker(125); painter->setPen(QPen(color, 1)); if (draw_left) painter->drawLine(rc.topLeft(), rc.bottomLeft()); diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index a7f700f3c2..353bb3fab3 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -213,9 +213,19 @@ void ChartView::updateTitle() { for (QLegendMarker *marker : chart()->legend()->markers()) { QObject::connect(marker, &QLegendMarker::clicked, this, &ChartView::handleMarkerClicked, Qt::UniqueConnection); } + + // Use CSS to draw titles in the WindowText color + auto tmp = palette().color(QPalette::WindowText); + auto titleColorCss = tmp.name(QColor::HexArgb); + // Draw message details in similar color, but slightly fade it to the background + tmp.setAlpha(180); + auto msgColorCss = tmp.name(QColor::HexArgb); + for (auto &s : sigs) { auto decoration = s.series->isVisible() ? "none" : "line-through"; - s.series->setName(QString("%2 %3 %4").arg(decoration, s.sig->name, msgName(s.msg_id), s.msg_id.toString())); + s.series->setName(QString("%3 %5 %6") + .arg(decoration, titleColorCss, s.sig->name, + msgColorCss, msgName(s.msg_id), s.msg_id.toString())); } split_chart_act->setEnabled(sigs.size() > 1); resetChartCache(); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 51abc3d50f..01c1f31645 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -63,7 +63,10 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i return "Data"; } } else if (role == Qt::BackgroundRole && section > 0 && show_signals) { - return QBrush(getColor(sigs[section - 1])); + // Alpha-blend the signal color with the background to ensure contrast + QColor sigColor = getColor(sigs[section - 1]); + sigColor.setAlpha(128); + return QBrush(sigColor); } } return {}; diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index 762b494f35..98cd08a690 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -459,7 +459,10 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); tree->header()->setStretchLastSection(true); tree->setMinimumHeight(300); - tree->setStyleSheet("QSpinBox{background-color:white;border:none;} QLineEdit{background-color:white;}"); + + // Use a distinctive background for the whole row containing a QSpinBox or QLineEdit + QString nodeBgColor = palette().color(QPalette::AlternateBase).name(QColor::HexArgb); + tree->setStyleSheet(QString("QSpinBox{background-color:%1;border:none;} QLineEdit{background-color:%1;}").arg(nodeBgColor)); QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); diff --git a/tools/lib/helpers.py b/tools/lib/helpers.py index efe704b9ec..067b64b6ac 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/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 1e592da3b1..e147cf9ce8 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -11,10 +11,10 @@ import requests import argparse from common.basedir import BASEDIR -from selfdrive.test.process_replay.compare_logs import save_log from selfdrive.test.openpilotci import get_url from tools.lib.logreader import LogReader from tools.lib.route import Route, SegmentName +from tools.lib.helpers import save_log from urllib.parse import urlparse, parse_qs juggle_dir = os.path.dirname(os.path.realpath(__file__))