Merge branch 'master' into ui_disable_longitudinal_personality_setting

pull/28443/head
deanlee 2 years ago
commit 3b297513fe
  1. 2
      panda
  2. 6
      selfdrive/car/chrysler/values.py
  3. 2
      selfdrive/car/ford/values.py
  4. 39
      selfdrive/car/fw_versions.py
  5. 38
      selfdrive/car/tests/test_fw_fingerprint.py
  6. 11
      selfdrive/car/volkswagen/values.py
  7. 3
      selfdrive/ui/qt/widgets/controls.h
  8. 36
      selfdrive/ui/translations/main_zh-CHT.ts
  9. 4
      tools/cabana/binaryview.cc
  10. 12
      tools/cabana/chart/chart.cc
  11. 5
      tools/cabana/historylog.cc
  12. 5
      tools/cabana/signalview.cc

@ -1 +1 @@
Subproject commit 5847d7dbc05df3bc76243b6704b9d98544eb53df Subproject commit bf2c0071036bae4e0c00c37ae43237fce6063e31

@ -227,6 +227,7 @@ FW_VERSIONS = {
b'68510283AG', b'68510283AG',
b'68527346AE', b'68527346AE',
b'68527375AD', b'68527375AD',
b'68527382AE',
], ],
(Ecu.srs, 0x744, None): [ (Ecu.srs, 0x744, None): [
b'68428609AB', b'68428609AB',
@ -254,6 +255,7 @@ FW_VERSIONS = {
(Ecu.fwdRadar, 0x753, None): [ (Ecu.fwdRadar, 0x753, None): [
b'04672892AB', b'04672892AB',
b'04672932AB', b'04672932AB',
b'22DTRHD_AA',
b'68320950AH', b'68320950AH',
b'68320950AI', b'68320950AI',
b'68320950AJ', b'68320950AJ',
@ -265,8 +267,10 @@ FW_VERSIONS = {
b'68475160AG', b'68475160AG',
], ],
(Ecu.eps, 0x75A, None): [ (Ecu.eps, 0x75A, None): [
b'21590101AA',
b'68273275AF', b'68273275AF',
b'68273275AG', b'68273275AG',
b'68273275AH',
b'68312176AE', b'68312176AE',
b'68312176AG', b'68312176AG',
b'68440789AC', b'68440789AC',
@ -283,7 +287,9 @@ FW_VERSIONS = {
b'05036065AE ', b'05036065AE ',
b'05036066AE ', b'05036066AE ',
b'05149846AA ', b'05149846AA ',
b'05149848AA ',
b'68378701AI ', b'68378701AI ',
b'68378748AL ',
b'68378758AM ', b'68378758AM ',
b'68448163AJ', b'68448163AJ',
b'68448165AK', b'68448165AK',

@ -213,8 +213,6 @@ FW_VERSIONS = {
(Ecu.engine, 0x7E0, None): [ (Ecu.engine, 0x7E0, None): [
b'JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.shiftByWire, 0x732, None): [
],
}, },
CAR.MAVERICK_MK1: { CAR.MAVERICK_MK1: {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [

@ -15,6 +15,7 @@ from system.swaglog import cloudlog
Ecu = car.CarParams.Ecu Ecu = car.CarParams.Ecu
ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] 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) FW_QUERY_CONFIGS = get_interface_attr('FW_QUERY_CONFIG', ignore_none=True)
VERSIONS = get_interface_attr('FW_VERSIONS', 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] 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], def build_fw_dict(fw_versions: List[capnp.lib.capnp._DynamicStructBuilder],
filter_brand: Optional[str] = None) -> Dict[Tuple[int, Optional[int]], Set[bytes]]: filter_brand: Optional[str] = None) -> Dict[Tuple[int, Optional[int]], Set[bytes]]:
fw_versions_dict = defaultdict(set) fw_versions_dict = defaultdict(set)
for fw in fw_versions: 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 sub_addr = fw.subAddress if fw.subAddress != 0 else None
fw_versions_dict[(fw.address, sub_addr)].add(fw.fwVersion) fw_versions_dict[(fw.address, sub_addr)].add(fw.fwVersion)
return dict(fw_versions_dict) return dict(fw_versions_dict)
@ -53,12 +59,6 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
that were matched uniquely to that specific car. If multiple ECUs uniquely match to different cars that were matched uniquely to that specific car. If multiple ECUs uniquely match to different cars
the match is rejected.""" 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 # Build lookup table from (addr, sub_addr, fw) to list of candidate cars
all_fw_versions = defaultdict(list) all_fw_versions = defaultdict(list)
for candidate, fw_by_addr in FW_VERSIONS.items(): for candidate, fw_by_addr in FW_VERSIONS.items():
@ -66,35 +66,40 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
continue continue
for addr, fws in fw_by_addr.items(): 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 continue
for f in fws: for f in fws:
all_fw_versions[(addr[1], addr[2], f)].append(candidate) all_fw_versions[(addr[1], addr[2], f)].append(candidate)
match_count = 0 matched_ecus = set()
candidate = None candidate = None
for addr, versions in fw_versions_dict.items(): for addr, versions in fw_versions_dict.items():
ecu_key = (addr[0], addr[1])
for version in versions: for version in versions:
# All cars that have this FW response on the specified address # 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: if len(candidates) == 1:
match_count += 1 matched_ecus.add(ecu_key)
if candidate is None: if candidate is None:
candidate = candidates[0] candidate = candidates[0]
# We uniquely matched two different cars. No fuzzy match possible # We uniquely matched two different cars. No fuzzy match possible
elif candidate != candidates[0]: elif candidate != candidates[0]:
return set() return set()
if match_count >= 2: if len(matched_ecus) >= 2:
if log: 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} return {candidate}
else: else:
return set() return set()
def match_fw_to_car_exact(fw_versions_dict) -> Set[str]: def match_fw_to_car_exact(fw_versions_dict, log=True) -> Set[str]:
"""Do an exact FW match. Returns all cars that match the given """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 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 essential the FW version can be missing to get a fingerprint, but if it's present it
@ -129,7 +134,7 @@ def match_fw_to_car_exact(fw_versions_dict) -> Set[str]:
return set(candidates.keys()) - set(invalid) return set(candidates.keys()) - set(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 # Try exact matching first
exact_matches = [] exact_matches = []
if allow_exact: if allow_exact:
@ -142,7 +147,7 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True):
matches = set() matches = set()
for brand in VERSIONS.keys(): for brand in VERSIONS.keys():
fw_versions_dict = build_fw_dict(fw_versions, filter_brand=brand) 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 len(matches): if len(matches):
return exact_match, matches return exact_match, matches
@ -277,7 +282,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 # Get versions and build capnp list to put into CarParams
car_fw = [] 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 in tqdm(addrs, disable=not progress):
for addr_chunk in chunks(addr): for addr_chunk in chunks(addr):
for brand, config, r in requests: for brand, config, r in requests:

@ -10,7 +10,7 @@ from cereal import car
from common.params import Params from common.params import Params
from selfdrive.car.car_helpers import interfaces from selfdrive.car.car_helpers import interfaces
from selfdrive.car.fingerprints import FW_VERSIONS 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 CarFw = car.CarParams.CarFw
Ecu = car.CarParams.Ecu Ecu = car.CarParams.Ecu
@ -33,27 +33,51 @@ class TestFwFingerprint(unittest.TestCase):
self.assertEqual(candidates[0], expected) self.assertEqual(candidates[0], expected)
@parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) @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() CP = car.CarParams.new_message()
for _ in range(200): for _ in range(200):
fw = [] fw = []
for ecu, fw_versions in ecus.items(): 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 ecu_name, addr, sub_addr = ecu
fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand,
"address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) "address": addr, "subAddress": 0 if sub_addr is None else sub_addr})
CP.carFw = fw 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) 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(): for car_model, ecus in FW_VERSIONS.items():
with self.subTest(car_model=car_model): with self.subTest(car_model=car_model):
for ecu, ecu_fw in ecus.items(): for ecu, ecu_fw in ecus.items():
with self.subTest(ecu): with self.subTest(ecu):
duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1} 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): def test_all_addrs_map_to_one_ecu(self):
for brand, cars in VERSIONS.items(): for brand, cars in VERSIONS.items():

@ -923,6 +923,7 @@ FW_VERSIONS = {
b'\xf1\x8704E906027CJ\xf1\x897798', b'\xf1\x8704E906027CJ\xf1\x897798',
b'\xf1\x8704L997022N \xf1\x899459', b'\xf1\x8704L997022N \xf1\x899459',
b'\xf1\x875G0906259A \xf1\x890004', b'\xf1\x875G0906259A \xf1\x890004',
b'\xf1\x875G0906259D \xf1\x890002',
b'\xf1\x875G0906259L \xf1\x890002', b'\xf1\x875G0906259L \xf1\x890002',
b'\xf1\x875G0906259Q \xf1\x890002', b'\xf1\x875G0906259Q \xf1\x890002',
b'\xf1\x878V0906259F \xf1\x890002', b'\xf1\x878V0906259F \xf1\x890002',
@ -932,7 +933,7 @@ FW_VERSIONS = {
b'\xf1\x878V0906264B \xf1\x890003', b'\xf1\x878V0906264B \xf1\x890003',
b'\xf1\x878V0907115B \xf1\x890007', b'\xf1\x878V0907115B \xf1\x890007',
b'\xf1\x878V0907404A \xf1\x890005', b'\xf1\x878V0907404A \xf1\x890005',
b'\xf1\x875G0906259D \xf1\x890002', b'\xf1\x878V0907404G \xf1\x890005',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x870CW300044T \xf1\x895245', b'\xf1\x870CW300044T \xf1\x895245',
@ -949,6 +950,7 @@ FW_VERSIONS = {
b'\xf1\x870DD300046F \xf1\x891602', b'\xf1\x870DD300046F \xf1\x891602',
b'\xf1\x870DD300046G \xf1\x891601', b'\xf1\x870DD300046G \xf1\x891601',
b'\xf1\x870DL300012E \xf1\x892012', b'\xf1\x870DL300012E \xf1\x892012',
b'\xf1\x870DL300012H \xf1\x892112',
b'\xf1\x870GC300011 \xf1\x890403', b'\xf1\x870GC300011 \xf1\x890403',
b'\xf1\x870GC300013M \xf1\x892402', b'\xf1\x870GC300013M \xf1\x892402',
b'\xf1\x870GC300042J \xf1\x891402', b'\xf1\x870GC300042J \xf1\x891402',
@ -961,9 +963,9 @@ FW_VERSIONS = {
b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1311110011131100311111011731179321342100', b'\xf1\x875Q0959655BJ\xf1\x890339\xf1\x82\x1311110011131100311111011731179321342100',
b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221',
b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221', 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\x13111112111111--241115141112221291163221',
b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221', 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\0211212001112110004110411111421149114',
b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114', b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114',
], ],
@ -972,6 +974,7 @@ FW_VERSIONS = {
b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566G0HA14A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566G0HA14A1',
b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1',
b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1', 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\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1',
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521G0G809A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521G0G809A1',
b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00303A0', b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00503G00303A0',
@ -1163,6 +1166,7 @@ FW_VERSIONS = {
b'\xf1\x8705E906018DJ\xf1\x891903', b'\xf1\x8705E906018DJ\xf1\x891903',
b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890003',
b'\xf1\x875NA907115E \xf1\x890005', b'\xf1\x875NA907115E \xf1\x890005',
b'\xf1\x875NA906259E \xf1\x890003',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870D9300043 \xf1\x895202',
@ -1171,6 +1175,7 @@ FW_VERSIONS = {
b'\xf1\x870DL300012N \xf1\x892110', b'\xf1\x870DL300012N \xf1\x892110',
b'\xf1\x870DL300013G \xf1\x892119', b'\xf1\x870DL300013G \xf1\x892119',
b'\xf1\x870GC300014N \xf1\x892801', b'\xf1\x870GC300014N \xf1\x892801',
b'\xf1\x870GC300019H \xf1\x892806',
b'\xf1\x870GC300046Q \xf1\x892802', b'\xf1\x870GC300046Q \xf1\x892802',
], ],
(Ecu.srs, 0x715, None): [ (Ecu.srs, 0x715, None): [
@ -1180,6 +1185,7 @@ FW_VERSIONS = {
b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e1213001211001205212112052100', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e1213001211001205212112052100',
b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111',
b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00',
b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121240749',
], ],
(Ecu.eps, 0x712, None): [ (Ecu.eps, 0x712, None): [
b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405',
@ -1187,6 +1193,7 @@ FW_VERSIONS = {
b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6070405', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6070405',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G500', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G500',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G600', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G600',
b'\xf1\x875TA907145F \xf1\x891063\xf1\x82\x002LT61A2LOM',
], ],
(Ecu.fwdRadar, 0x757, None): [ (Ecu.fwdRadar, 0x757, None): [
b'\xf1\x872Q0907572Q \xf1\x890342', b'\xf1\x872Q0907572Q \xf1\x890342',

@ -202,7 +202,7 @@ class ButtonParamControl : public AbstractControl {
Q_OBJECT Q_OBJECT
public: public:
ButtonParamControl(const QString &param, const QString &title, const QString &desc, const QString &icon, ButtonParamControl(const QString &param, const QString &title, const QString &desc, const QString &icon,
const std::vector<QString> &button_texts) : AbstractControl(title, desc, icon) { const std::vector<QString> &button_texts, const int minimum_button_width = 225) : AbstractControl(title, desc, icon) {
const QString style = R"( const QString style = R"(
QPushButton { QPushButton {
border-radius: 50px; border-radius: 50px;
@ -233,6 +233,7 @@ public:
button->setCheckable(true); button->setCheckable(true);
button->setChecked(i == value); button->setChecked(i == value);
button->setStyleSheet(style); button->setStyleSheet(style);
button->setMinimumWidth(minimum_button_width);
hlayout->addWidget(button); hlayout->addWidget(button);
button_group->addButton(button, i); button_group->addButton(button, i);
} }

@ -856,23 +856,23 @@ This may take up to a minute.</source>
</message> </message>
<message> <message>
<source>failed to check for update</source> <source>failed to check for update</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>up to date, last checked %1</source> <source>up to date, last checked %1</source>
<translation type="unfinished"></translation> <translation> %1</translation>
</message> </message>
<message> <message>
<source>DOWNLOAD</source> <source>DOWNLOAD</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>update available</source> <source>update available</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>never</source> <source>never</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
</context> </context>
<context> <context>
@ -1040,35 +1040,35 @@ This may take up to a minute.</source>
</message> </message>
<message> <message>
<source>openpilot Longitudinal Control (Alpha)</source> <source>openpilot Longitudinal Control (Alpha)</source>
<translation type="unfinished"></translation> <translation>openpilot (Alpha )</translation>
</message> </message>
<message> <message>
<source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source> <source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source>
<translation type="unfinished"></translation> <translation> Openpilot Alpha 使AEB</translation>
</message> </message>
<message> <message>
<source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.</source> <source>On this car, openpilot defaults to the car&apos;s built-in ACC instead of openpilot&apos;s longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha.</source>
<translation type="unfinished"></translation> <translation>Openpilot 使ACC Openpilot Openpilot Openpilot Alpha Experimental mode</translation>
</message> </message>
<message> <message>
<source>Aggressive</source> <source>Aggressive</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Standard</source> <source>Standard</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Relaxed</source> <source>Relaxed</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Driving Personality</source> <source>Driving Personality</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>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.</source> <source>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.</source>
<translation type="unfinished"></translation> <translation>使openpilot openpilot </translation>
</message> </message>
</context> </context>
<context> <context>
@ -1110,23 +1110,23 @@ This may take up to a minute.</source>
<name>WiFiPromptWidget</name> <name>WiFiPromptWidget</name>
<message> <message>
<source>Setup Wi-Fi</source> <source>Setup Wi-Fi</source>
<translation type="unfinished"></translation> <translation> Wi-Fi </translation>
</message> </message>
<message> <message>
<source>Connect to Wi-Fi to upload driving data and help improve openpilot</source> <source>Connect to Wi-Fi to upload driving data and help improve openpilot</source>
<translation type="unfinished"></translation> <translation> Wi-Fi openpilot</translation>
</message> </message>
<message> <message>
<source>Open Settings</source> <source>Open Settings</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Uploading training data</source> <source>Uploading training data</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<source>Your data is used to train driving models and help improve openpilot</source> <source>Your data is used to train driving models and help improve openpilot</source>
<translation type="unfinished"></translation> <translation> openpilot</translation>
</message> </message>
</context> </context>
<context> <context>

@ -437,9 +437,11 @@ void BinaryItemDelegate::drawSignalCell(QPainter *painter, const QStyleOptionVie
auto sig_color = getColor(sig); auto sig_color = getColor(sig);
QColor color = sig_color; QColor color = sig_color;
color.setAlpha(item->bg_color.alpha()); 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); painter->fillRect(rc, color);
// Draw edges
color = sig_color.darker(125); color = sig_color.darker(125);
painter->setPen(QPen(color, 1)); painter->setPen(QPen(color, 1));
if (draw_left) painter->drawLine(rc.topLeft(), rc.bottomLeft()); if (draw_left) painter->drawLine(rc.topLeft(), rc.bottomLeft());

@ -213,9 +213,19 @@ void ChartView::updateTitle() {
for (QLegendMarker *marker : chart()->legend()->markers()) { for (QLegendMarker *marker : chart()->legend()->markers()) {
QObject::connect(marker, &QLegendMarker::clicked, this, &ChartView::handleMarkerClicked, Qt::UniqueConnection); 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) { for (auto &s : sigs) {
auto decoration = s.series->isVisible() ? "none" : "line-through"; auto decoration = s.series->isVisible() ? "none" : "line-through";
s.series->setName(QString("<span style=\"text-decoration:%1\"><b>%2</b> <font color=\"gray\">%3 %4</font></span>").arg(decoration, s.sig->name, msgName(s.msg_id), s.msg_id.toString())); s.series->setName(QString("<span style=\"text-decoration:%1; color:%2\"><b>%3</b> <font color=\"%4\">%5 %6</font></span>")
.arg(decoration, titleColorCss, s.sig->name,
msgColorCss, msgName(s.msg_id), s.msg_id.toString()));
} }
split_chart_act->setEnabled(sigs.size() > 1); split_chart_act->setEnabled(sigs.size() > 1);
resetChartCache(); resetChartCache();

@ -63,7 +63,10 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i
return "Data"; return "Data";
} }
} else if (role == Qt::BackgroundRole && section > 0 && show_signals) { } 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 {}; return {};

@ -459,7 +459,10 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts),
tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
tree->header()->setStretchLastSection(true); tree->header()->setStretchLastSection(true);
tree->setMinimumHeight(300); 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); QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setContentsMargins(0, 0, 0, 0);

Loading…
Cancel
Save