From b3be8636710c3ef4699bbb256f56823302d08397 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Fri, 23 Sep 2022 13:40:15 +0200 Subject: [PATCH 001/178] Tesla FW query (#25785) * early wip * add addresses * cleaned up fw versions * remove CAN fingerprint * Revert "remove CAN fingerprint" This reverts commit 337e8d65ef7ee2724cfe6ff711ee8aeffdbbb22f. * bump panda * Revert "Revert "remove CAN fingerprint"" This reverts commit 12536fa8358438a6d6713c0b7bef0383bbc83588. * bump cereal * fix ecu type * whitelist per rx_offset * bump submodules again Co-authored-by: Comma Device --- cereal | 2 +- panda | 2 +- selfdrive/car/tesla/values.py | 46 ++++++++++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/cereal b/cereal index 3baa20e1da..a0c6f28d6b 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 3baa20e1da5d88dcb1d3ae9678471eb8013958f2 +Subproject commit a0c6f28d6bce2fd7d7ef2fd29e80d2eab118a6c6 diff --git a/panda b/panda index 11ea112258..10c0991666 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 11ea112258b66a0969fa340cd5e2d870378e5c5d +Subproject commit 10c0991666865feb734120f2544586d461292866 diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index 7648a4a504..e28666c625 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -1,9 +1,12 @@ from collections import namedtuple from typing import Dict, List, Union +from cereal import car from selfdrive.car import dbc_dict from selfdrive.car.docs_definitions import CarInfo -from cereal import car +from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries + +Ecu = car.CarParams.Ecu Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points']) @@ -20,11 +23,6 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { } FINGERPRINTS = { - CAR.AP2_MODELS: [ - { - 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 277: 6, 280: 6, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 518: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 538: 8, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 576: 3, 577: 8, 582: 5, 583: 8, 584: 4, 585: 8, 590: 8, 601: 8, 606: 8, 608: 1, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 692: 8, 693: 8, 695: 8, 696: 8, 697: 8, 699: 8, 700: 8, 701: 8, 702: 8, 703: 8, 704: 8, 708: 8, 709: 8, 710: 8, 711: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 811: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 845: 8, 846: 5, 848: 8, 852: 8, 853: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 876: 8, 877: 8, 879: 8, 880: 8, 882: 8, 884: 8, 888: 8, 893: 8, 894: 8, 901: 6, 904: 3, 905: 8, 906: 8, 908: 2, 909: 8, 910: 8, 912: 8, 920: 8, 921: 8, 925: 4, 926: 6, 936: 8, 941: 8, 949: 8, 952: 8, 953: 6, 968: 8, 969: 7, 970: 8, 971: 8, 977: 8, 984: 8, 986: 8, 987: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1007: 8, 1008: 8, 1010: 6, 1014: 1, 1015: 8, 1016: 8, 1017: 8, 1018: 8, 1020: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1049: 8, 1061: 8, 1064: 8, 1065: 8, 1070: 8, 1080: 8, 1081: 8, 1097: 8, 1113: 8, 1129: 8, 1145: 8, 1160: 4, 1177: 8, 1281: 8, 1328: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1353: 8, 1368: 8, 1412: 8, 1436: 8, 1476: 8, 1481: 8, 1497: 8, 1513: 8, 1519: 8, 1601: 8, 1605: 8, 1617: 8, 1621: 8, 1625: 8, 1665: 8, 1792: 8, 1798: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1824: 8, 1825: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1842: 8, 1848: 8, 1864: 8, 1872: 8, 1880: 8, 1888: 8, 1892: 8, 1896: 8, 1912: 8, 1937: 8, 1953: 8, 1960: 8, 1968: 8, 1992: 8, 2001: 8, 2008: 3, 2015: 8, 2016: 8, 2043: 5, 2045: 4 - }, - ], CAR.AP1_MODELS: [ { 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4 @@ -37,6 +35,42 @@ DBC = { CAR.AP1_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'), } +FW_QUERY_CONFIG = FwQueryConfig( + requests=[ + Request( + [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], + whitelist_ecus=[Ecu.eps], + rx_offset=0x08, + bus=0, + ), + Request( + [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], + whitelist_ecus=[Ecu.adas, Ecu.electricBrakeBooster, Ecu.fwdRadar], + rx_offset=0x10, + bus=0, + ), + ] +) + +FW_VERSIONS = { + CAR.AP2_MODELS: { + (Ecu.adas, 0x649, None): [ + b'\x01\x00\x8b\x07\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11', + ], + (Ecu.electricBrakeBooster, 0x64d, None): [ + b'1037123-00-A', + ], + (Ecu.fwdRadar, 0x671, None): [ + b'\x01\x00W\x00\x00\x00\x07\x00\x00\x00\x00\x08\x01\x00\x00\x00\x07\xff\xfe', + ], + (Ecu.eps, 0x730, None): [ + b'\x10#\x01', + ], + }, +} + class CANBUS: # Lateral harness chassis = 0 From 2f878830c841402cc971b76a3b14e58fc8c69fd9 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 24 Sep 2022 01:54:13 +0800 Subject: [PATCH 002/178] modeld: removed extra spaces (#25880) --- selfdrive/modeld/modeld.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index 653661a3a8..cfc71a0e27 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -35,7 +35,7 @@ mat3 update_calibration(Eigen::Vector3d device_from_calib_euler, bool wide_camer 0.00000000e+00, 7.31372216e-19, 1.00000000e+00, 2.19780220e-03, 4.11497335e-19, -5.62637363e-01, -6.66298828e-20, 2.19780220e-03, -3.33626374e-01).finished(); - + static const auto view_from_device = (Eigen::Matrix() << 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, From f4a4ec8fa21dfef5ef0ebe2eafaadc1c2536d861 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 24 Sep 2022 04:28:25 +0800 Subject: [PATCH 003/178] bootlog: rename bz_file to file (#25881) --- selfdrive/loggerd/bootlog.cc | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/selfdrive/loggerd/bootlog.cc b/selfdrive/loggerd/bootlog.cc index 6ff052655a..e882e4cf8d 100644 --- a/selfdrive/loggerd/bootlog.cc +++ b/selfdrive/loggerd/bootlog.cc @@ -55,13 +55,11 @@ int main(int argc, char** argv) { bool r = util::create_directories(LOG_ROOT + "/boot/", 0775); assert(r); - RawFile bz_file(path.c_str()); - + RawFile file(path.c_str()); // Write initdata - bz_file.write(logger_build_init_data().asBytes()); - + file.write(logger_build_init_data().asBytes()); // Write bootlog - bz_file.write(build_boot_log().asBytes()); + file.write(build_boot_log().asBytes()); return 0; } From 755f24885aa3e89933cc8ad9784cdaf480156239 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Fri, 23 Sep 2022 18:51:07 -0700 Subject: [PATCH 004/178] pin sensord and lsm interrupt to core 1, for better timing (#25867) Co-authored-by: Kurt Nistelberger --- selfdrive/sensord/sensors_qcom2.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/sensord/sensors_qcom2.cc b/selfdrive/sensord/sensors_qcom2.cc index ded4b5c0b1..5e741a89a5 100644 --- a/selfdrive/sensord/sensors_qcom2.cc +++ b/selfdrive/sensord/sensors_qcom2.cc @@ -174,6 +174,11 @@ int sensor_loop() { return -1; } + // increase interrupt quality by pinning interrupt and process to core 1 + setpriority(PRIO_PROCESS, 0, -18); + util::set_core_affinity({1}); + std::system("sudo su -c 'echo 1 > /proc/irq/336/smp_affinity_list'"); + PubMaster pm({"sensorEvents"}); init_ts = nanos_since_boot(); @@ -207,7 +212,7 @@ int sensor_loop() { std::this_thread::sleep_for(std::chrono::milliseconds(10) - (end - begin)); } - for (Sensor *sensor : sensors) { + for (Sensor *sensor : sensors) { sensor->shutdown(); } @@ -217,6 +222,5 @@ int sensor_loop() { } int main(int argc, char *argv[]) { - setpriority(PRIO_PROCESS, 0, -18); return sensor_loop(); } From b2376909f4b3c95f27eb11d081d3952566b06815 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 23 Sep 2022 19:44:18 -0700 Subject: [PATCH 005/178] updated: allow reusing overlay (#25883) --- selfdrive/updated.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/selfdrive/updated.py b/selfdrive/updated.py index c806e726c6..4556876c27 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -52,6 +52,8 @@ OVERLAY_METADATA = os.path.join(STAGING_ROOT, "metadata") OVERLAY_MERGED = os.path.join(STAGING_ROOT, "merged") FINALIZED = os.path.join(STAGING_ROOT, "finalized") +OVERLAY_INIT = Path(os.path.join(BASEDIR, ".overlay_init")) + DAYS_NO_CONNECTIVITY_MAX = 14 # do not allow to engage after this many days DAYS_NO_CONNECTIVITY_PROMPT = 10 # send an offroad prompt after this many days @@ -134,12 +136,10 @@ def dismount_overlay() -> None: def init_overlay() -> None: - overlay_init_file = Path(os.path.join(BASEDIR, ".overlay_init")) - # Re-create the overlay if BASEDIR/.git has changed since we created the overlay - if overlay_init_file.is_file(): + if OVERLAY_INIT.is_file(): git_dir_path = os.path.join(BASEDIR, ".git") - new_files = run(["find", git_dir_path, "-newer", str(overlay_init_file)]) + new_files = run(["find", git_dir_path, "-newer", str(OVERLAY_INIT)]) if not len(new_files.splitlines()): # A valid overlay already exists return @@ -170,7 +170,7 @@ def init_overlay() -> None: consistent_file = Path(os.path.join(BASEDIR, ".overlay_consistent")) if consistent_file.is_file(): consistent_file.unlink() - overlay_init_file.touch() + OVERLAY_INIT.touch() os.sync() overlay_opts = f"lowerdir={BASEDIR},upperdir={OVERLAY_UPPER},workdir={OVERLAY_METADATA}" @@ -419,9 +419,6 @@ def main() -> None: t = datetime.datetime.utcnow().isoformat() params.put("InstallDate", t.encode('utf8')) - overlay_init = Path(os.path.join(BASEDIR, ".overlay_init")) - overlay_init.unlink(missing_ok=True) - updater = Updater() update_failed_count = 0 # TODO: Load from param? @@ -461,11 +458,11 @@ def main() -> None: returncode=e.returncode ) exception = f"command failed: {e.cmd}\n{e.output}" - overlay_init.unlink(missing_ok=True) + OVERLAY_INIT.unlink(missing_ok=True) except Exception as e: cloudlog.exception("uncaught updated exception, shouldn't happen") exception = str(e) - overlay_init.unlink(missing_ok=True) + OVERLAY_INIT.unlink(missing_ok=True) try: params.put("UpdaterState", "idle") From d997d40f17406d079c2f343bcde446be44094021 Mon Sep 17 00:00:00 2001 From: Lee Jong Mun <43285072+crwusiz@users.noreply.github.com> Date: Mon, 26 Sep 2022 11:33:35 +0900 Subject: [PATCH 006/178] update system.hardware import path (#25889) --- selfdrive/sensord/pigeond.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/sensord/pigeond.py b/selfdrive/sensord/pigeond.py index e38e2d4c33..e1fa2f4cad 100755 --- a/selfdrive/sensord/pigeond.py +++ b/selfdrive/sensord/pigeond.py @@ -12,9 +12,9 @@ from typing import List, Optional from cereal import messaging from common.params import Params from system.swaglog import cloudlog -from selfdrive.hardware import TICI +from system.hardware import TICI from common.gpio import gpio_init, gpio_set -from selfdrive.hardware.tici.pins import GPIO +from system.hardware.tici.pins import GPIO UBLOX_TTY = "/dev/ttyHS0" From a124fa22e412cd38f68a8e8db998dd786ed9509c Mon Sep 17 00:00:00 2001 From: Lee Jong Mun <43285072+crwusiz@users.noreply.github.com> Date: Tue, 27 Sep 2022 04:01:24 +0900 Subject: [PATCH 007/178] Multilang: kor translation update (#25893) --- selfdrive/ui/translations/main_ko.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 2a806aaed9..56524e49fa 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -960,11 +960,11 @@ location set openpilot longitudinal control is not currently available for this car. - + 현재 이 차량에는 openpilot 롱컨트롤을 사용할 수 없습니다. Enable experimental longitudinal control to enable this. - + openpilot 롱컨트롤을 활성화합니다. (실험적) Disengage On Accelerator Pedal From d2869f4779495685730c5eb27e90fabf559f68d4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 26 Sep 2022 16:57:21 -0700 Subject: [PATCH 008/178] updated: check overlay mounted --- selfdrive/updated.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 4556876c27..f46cda3204 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -137,7 +137,7 @@ def dismount_overlay() -> None: def init_overlay() -> None: # Re-create the overlay if BASEDIR/.git has changed since we created the overlay - if OVERLAY_INIT.is_file(): + if OVERLAY_INIT.is_file() and os.path.ismount(OVERLAY_MERGED): git_dir_path = os.path.join(BASEDIR, ".git") new_files = run(["find", git_dir_path, "-newer", str(OVERLAY_INIT)]) if not len(new_files.splitlines()): From f18773acbea2c54a2edc70d74c35a8dea6cfdf82 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 26 Sep 2022 22:27:30 -0700 Subject: [PATCH 009/178] compressed_vipc: add main() --- tools/camerastream/compressed_vipc.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tools/camerastream/compressed_vipc.py b/tools/camerastream/compressed_vipc.py index 4322ce279a..42a416985e 100755 --- a/tools/camerastream/compressed_vipc.py +++ b/tools/camerastream/compressed_vipc.py @@ -85,6 +85,16 @@ def decoder(addr, sock_name, vipc_server, vst, nvidia): time_q = time_q[1:] print("%2d %4d %.3f %.3f roll %6.2f ms latency %6.2f ms + %6.2f ms + %6.2f ms = %6.2f ms" % (len(msgs), evta.idx.encodeId, evt.logMonoTime/1e9, evta.idx.timestampEof/1e6, frame_latency, process_latency, network_latency, pc_latency, process_latency+network_latency+pc_latency ), len(evta.data), sock_name) +def main(addr, cams, nvidia=False): + vipc_server = VisionIpcServer("camerad") + for vst in cams.values(): + vipc_server.create_buffers(vst, 4, False, W, H) + vipc_server.start_listener() + + for k, v in cams.items(): + multiprocessing.Process(target=decoder, args=(addr, k, vipc_server, v, nvidia)).start() + + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Decode video streams and broadcast on VisionIPC") parser.add_argument("addr", help="Address of comma three") @@ -98,11 +108,4 @@ if __name__ == "__main__": ("driverEncodeData", VisionStreamType.VISION_STREAM_DRIVER), ] cams = dict([all_cams[int(x)] for x in args.cams.split(",")]) - - vipc_server = VisionIpcServer("camerad") - for vst in cams.values(): - vipc_server.create_buffers(vst, 4, False, W, H) - vipc_server.start_listener() - - for k,v in cams.items(): - multiprocessing.Process(target=decoder, args=(args.addr, k, vipc_server, v, args.nvidia)).start() + main(args.addr, cams, args.nvidia) From 29b9a07393bffc6d7915245958e38b9219304aa7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 26 Sep 2022 23:22:50 -0700 Subject: [PATCH 010/178] Log tx addr for IsoTpMessage exceptions --- selfdrive/car/isotp_parallel_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 65122ab897..971309439e 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -109,7 +109,7 @@ class IsoTpParallelQuery: try: dat: Optional[bytes] = msg.recv() except Exception: - cloudlog.exception("Error processing UDS response") + cloudlog.exception(f"Error processing UDS response: {tx_addr}") request_done[tx_addr] = True continue From 26517a0ef920eb7dc89663ee599c1f1d06166c88 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 26 Sep 2022 23:50:49 -0700 Subject: [PATCH 011/178] replay: set CarParamsPersistent --- tools/replay/replay.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 3e482b5474..c6c78f47ae 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -290,6 +290,7 @@ void Replay::startStream(const Segment *cur_segment) { auto words = capnp::messageToFlatArray(builder); auto bytes = words.asBytes(); Params().put("CarParams", (const char *)bytes.begin(), bytes.size()); + Params().put("CarParamsPersistent", (const char *)bytes.begin(), bytes.size()); } else { rWarning("failed to read CarParams from current segment"); } From 89768376c0e141fb4143f95136693c6fe48f1f0b Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 27 Sep 2022 14:00:08 -0700 Subject: [PATCH 012/178] docs: remove redundant keyword args (#25899) remove unnecessary keyword args, unnecessary package --- selfdrive/car/gm/values.py | 6 +++--- selfdrive/car/honda/values.py | 4 ++-- selfdrive/car/hyundai/values.py | 18 +++++++++--------- selfdrive/car/toyota/values.py | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 943e8a6585..fec21d8d24 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -86,13 +86,13 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), - CAR.BOLT_EV: GMCarInfo("Chevrolet Bolt EV 2022-23", "Adaptive Cruise Control (ACC)", footnotes=[], harness=Harness.gm), - CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), + CAR.BOLT_EV: GMCarInfo("Chevrolet Bolt EV 2022-23", footnotes=[], harness=Harness.gm), + CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), CAR.SILVERADO: [ GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II", footnotes=[], harness=Harness.gm), GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", footnotes=[], harness=Harness.gm), ], - CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22", "Adaptive Cruise Control (ACC)", footnotes=[], harness=Harness.gm), + CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22", footnotes=[], harness=Harness.gm), } diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 43c3c77369..e48edc42ba 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -110,13 +110,13 @@ class HondaCarInfo(CarInfo): CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { CAR.ACCORD: [ - HondaCarInfo("Honda Accord 2018-22", "All", video_link="https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), ], CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", harness=Harness.nidec, video_link="https://youtu.be/-IkImTe1NYE"), CAR.CIVIC_BOSCH: [ - HondaCarInfo("Honda Civic 2019-21", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8", footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS, harness=Harness.bosch_a), HondaCarInfo("Honda Civic Hatchback 2017-21", harness=Harness.bosch_a), ], CAR.CIVIC_BOSCH_DIESEL: None, # same platform diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index f0e78b22b8..52a5088319 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -113,7 +113,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.ELANTRA_GT_I30: None, # dashcamOnly and same platform as CAR.ELANTRA CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c), - CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", "Smart Cruise Control (SCC)", harness=Harness.hyundai_h), # TODO: confirm 2020-21 harness + CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", harness=Harness.hyundai_h), # TODO: confirm 2020-21 harness CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", harness=Harness.hyundai_c), CAR.IONIQ_EV_2020: HyundaiCarInfo("Hyundai Ioniq Electric 2020", "All", harness=Harness.hyundai_h), CAR.IONIQ_PHEV_2019: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2019", harness=Harness.hyundai_c), @@ -123,17 +123,17 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022", harness=Harness.hyundai_o), CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", video_link="https://youtu.be/0dwpAHiZgFo", harness=Harness.hyundai_i), # TODO: check packages CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", harness=Harness.hyundai_d), - CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", video_link="https://youtu.be/VnHzSTygTS4", harness=Harness.hyundai_l), + CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", "https://youtu.be/VnHzSTygTS4", harness=Harness.hyundai_l), CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022", "All", harness=Harness.hyundai_l), CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022", "All", harness=Harness.hyundai_l), - CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-22", "All", video_link="https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a), + CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-22", "All", "https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a), CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", harness=Harness.hyundai_e), CAR.TUCSON: [ HyundaiCarInfo("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_l), HyundaiCarInfo("Hyundai Tucson Diesel 2019", harness=Harness.hyundai_l), ], CAR.PALISADE: [ - HyundaiCarInfo("Hyundai Palisade 2020-22", "All", video_link="https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h), + HyundaiCarInfo("Hyundai Palisade 2020-22", "All", "https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h), HyundaiCarInfo("Kia Telluride 2020", "All", harness=Harness.hyundai_h), ], CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), @@ -145,10 +145,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.KIA_FORTE: HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g), CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", harness=Harness.hyundai_a), CAR.KIA_NIRO_EV: [ - HyundaiCarInfo("Kia Niro EV 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), - HyundaiCarInfo("Kia Niro EV 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f), - HyundaiCarInfo("Kia Niro EV 2021", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c), - HyundaiCarInfo("Kia Niro EV 2022", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), + HyundaiCarInfo("Kia Niro EV 2019", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), + HyundaiCarInfo("Kia Niro EV 2020", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f), + HyundaiCarInfo("Kia Niro EV 2021", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c), + HyundaiCarInfo("Kia Niro EV 2022", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), ], CAR.KIA_NIRO_PHEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c), CAR.KIA_NIRO_HEV_2021: [ @@ -163,7 +163,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { ], CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", harness=Harness.hyundai_a), CAR.KIA_SORENTO: [ - HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), + HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), ], CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 26c61f5b74..1f8ab2498d 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -136,7 +136,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19", footnotes=[Footnote.DSU]), CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-22"), CAR.PRIUS: [ - ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0", [Footnote.DSU]), ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), ToyotaCarInfo("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), ], @@ -150,7 +150,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Toyota RAV4 2017-18", footnotes=[Footnote.DSU]) ], CAR.RAV4H: [ - ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", video_link="https://youtu.be/LhT5VzJVfNI?t=26", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", "https://youtu.be/LhT5VzJVfNI?t=26", [Footnote.DSU]), ToyotaCarInfo("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26", footnotes=[Footnote.DSU]) ], CAR.RAV4_TSS2: ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"), From 2a30da6698d92a3729bc5478a3c53996b8077152 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 27 Sep 2022 14:23:32 -0700 Subject: [PATCH 013/178] sensor tests: bump light sensor threshold --- selfdrive/sensord/tests/test_sensord.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/sensord/tests/test_sensord.py b/selfdrive/sensord/tests/test_sensord.py index 0b5f054d2e..b4a7aef343 100755 --- a/selfdrive/sensord/tests/test_sensord.py +++ b/selfdrive/sensord/tests/test_sensord.py @@ -51,7 +51,7 @@ Sensor = log.SensorEventData.SensorSource SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max']) ALL_SENSORS = { Sensor.rpr0521: { - SensorConfig("light", 0, 150), + SensorConfig("light", 0, 1023), }, Sensor.lsm6ds3: { From 870c5f383d2feb1b5dd255fd0c528d1427f8478c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Sep 2022 18:13:13 -0700 Subject: [PATCH 014/178] IsoTpParallelQuery: extend timeout for each ISO-TP frame (#25897) * raise timeouts * extend timeout for each message/frame (not multi-frame full response) * bump panda * 100 ms timeout between frames (max I've seen is 20, should be good) * bump panda * remove unused * Add tester present to HKG queries (temp) * send tester present to all ecus first * vin and fw_versions.py sleep, should figure out why sendcan drops packets * None is wildcard (some ecus respond with negative code, that's fine) * typing * try bus 0 * revert brand-specific changes * Update selfdrive/car/isotp_parallel_query.py --- panda | 2 +- selfdrive/car/isotp_parallel_query.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/panda b/panda index 10c0991666..5984fd3509 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 10c0991666865feb734120f2544586d461292866 +Subproject commit 5984fd35096a9e3f9748435868ad49a199d4404d diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 971309439e..31dc31d7a4 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -1,7 +1,6 @@ import time from collections import defaultdict from functools import partial -from typing import Optional import cereal.messaging as messaging from system.swaglog import cloudlog @@ -107,12 +106,15 @@ class IsoTpParallelQuery: for tx_addr, msg in msgs.items(): try: - dat: Optional[bytes] = msg.recv() + dat, updated = msg.recv() except Exception: cloudlog.exception(f"Error processing UDS response: {tx_addr}") request_done[tx_addr] = True continue + if updated: + response_timeouts[tx_addr] = time.monotonic() + timeout + if not dat: continue @@ -121,7 +123,6 @@ class IsoTpParallelQuery: response_valid = dat[:len(expected_response)] == expected_response if response_valid: - response_timeouts[tx_addr] = time.monotonic() + timeout if counter + 1 < len(self.request): msg.send(self.request[counter + 1]) request_counter[tx_addr] += 1 From 12998520b98571190ed379564a26b9db22cf642c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Sep 2022 18:56:25 -0700 Subject: [PATCH 015/178] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index 5984fd3509..51f023bc66 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 5984fd35096a9e3f9748435868ad49a199d4404d +Subproject commit 51f023bc66c2caa9007be1dda2738d0df51cbf0e From ba1c3cda41de2002a31591fe0b92eca63df683b5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Sep 2022 20:17:42 -0700 Subject: [PATCH 016/178] Log more FW/VIN errors in qlogs (#25901) * log VIN query errors * Update fw_versions.py * use exception * post-commit --- selfdrive/car/fw_versions.py | 3 +-- selfdrive/car/vin.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 9c0c406f14..bf88e77db5 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import traceback from collections import defaultdict from typing import Any, Optional, Set, Tuple from tqdm import tqdm @@ -270,7 +269,7 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, car_fw.append(f) except Exception: - cloudlog.warning(f"FW query exception: {traceback.format_exc()}") + cloudlog.exception("FW query exception") return car_fw diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index fba0c54eba..909322f9c9 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import re -import traceback import cereal.messaging as messaging from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery @@ -28,9 +27,9 @@ def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False): vin = vin[1:18] return addr[0], rx_addr, vin.decode() - print(f"vin query retry ({i+1}) ...") + cloudlog.error(f"vin query retry ({i+1}) ...") except Exception: - cloudlog.warning(f"VIN query exception: {traceback.format_exc()}") + cloudlog.exception("VIN query exception") return 0, 0, VIN_UNKNOWN From 96ed5aa5816c692856ed352ef404f040a9615b69 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 27 Sep 2022 20:33:45 -0700 Subject: [PATCH 017/178] Sensor events regen (#25903) --- cereal | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- selfdrive/test/process_replay/regen.py | 61 +++++++++++++++++++ .../test/process_replay/test_processes.py | 30 ++++----- 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/cereal b/cereal index a0c6f28d6b..e310f4860d 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit a0c6f28d6bce2fd7d7ef2fd29e80d2eab118a6c6 +Subproject commit e310f4860d349d2e260cfd4bb060b0705b17244c diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 7aad2b291d..c7daecd5a3 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -051fa5bea42027c1a756ae61fd0c752c1e911899 \ No newline at end of file +a82580edc3a842dd58814bf2fe1a0f0f85d438f5 \ No newline at end of file diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index d565e36390..356016c642 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -106,6 +106,23 @@ def replay_sensor_events(s, msgs): rk.keep_time() +def replay_sensor_event(s, msgs): + smsgs = [m for m in msgs if m.which() == s] + #if len(smsgs) == 0: + # return + + pm = messaging.PubMaster([s, ]) + rk = Ratekeeper(service_list[s].frequency, print_delay_threshold=None) + + while True: + for m in smsgs: + m = m.as_builder() + m.logMonoTime = int(sec_since_boot() * 1e9) + getattr(m, m.which()).timestamp = m.logMonoTime + pm.send(m.which(), m) + rk.keep_time() + + def replay_service(s, msgs): pm = messaging.PubMaster([s, ]) rk = Ratekeeper(service_list[s].frequency, print_delay_threshold=None) @@ -193,8 +210,49 @@ def migrate_carparams(lr): return all_msgs +def migrate_sensorEvents(lr): + all_msgs = [] + for msg in lr: + if msg.which() != 'sensorEvents': + all_msgs.append(msg) + continue + + # migrate to split sensor events + for evt in msg.sensorEvents: + # build new message for each sensor type + sensor_service = '' + if evt.which() == 'acceleration': + sensor_service = 'accelerometer' + elif evt.which() == 'gyro' or evt.which() == 'gyroUncalibrated': + sensor_service = 'gyroscope' + elif evt.which() == 'light' or evt.which() == 'proximity': + sensor_service = 'lightSensor' + elif evt.which() == 'magnetic' or evt.which() == 'magneticUncalibrated': + sensor_service = 'magnetometer' + elif evt.which() == 'temperature': + sensor_service = 'temperatureSensor' + + m = messaging.new_message(sensor_service) + m.valid = True + + m_dat = getattr(m, sensor_service) + m_dat.version = evt.version + m_dat.sensor = evt.sensor + m_dat.type = evt.type + m_dat.source = evt.source + setattr(m_dat, evt.which(), getattr(evt, evt.which())) + + all_msgs.append(m.as_reader()) + + # append also legacy sensorEvents, to have both (remove later) + all_msgs.append(msg) + + return all_msgs + + def regen_segment(lr, frs=None, outdir=FAKEDATA, disable_tqdm=False): lr = migrate_carparams(list(lr)) + lr = migrate_sensorEvents(list(lr)) if frs is None: frs = dict() @@ -213,6 +271,9 @@ def regen_segment(lr, frs=None, outdir=FAKEDATA, disable_tqdm=False): fake_daemons = { 'sensord': [ multiprocessing.Process(target=replay_sensor_events, args=('sensorEvents', lr)), + multiprocessing.Process(target=replay_sensor_event, args=('accelerometer', lr)), + multiprocessing.Process(target=replay_sensor_event, args=('gyroscope', lr)), + multiprocessing.Process(target=replay_sensor_event, args=('magnetometer', lr)), ], 'pandad': [ multiprocessing.Process(target=replay_service, args=('can', lr)), diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index ee892a2fd9..5ad947b225 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -38,21 +38,21 @@ source_segments = [ ] segments = [ - ("BODY", "regen9D38397D30D|2022-09-09--13-12-48--0"), - ("HYUNDAI", "regenB3953B393C0|2022-09-09--14-49-37--0"), - ("HYUNDAI", "regen8DB830E5376|2022-09-13--17-24-37--0"), - ("TOYOTA", "regen8FCBB6F06F1|2022-09-09--13-14-07--0"), - ("TOYOTA2", "regen956BFA75300|2022-09-09--14-51-24--0"), - ("TOYOTA3", "regenE909BC2F430|2022-09-09--20-44-49--0"), - ("HONDA", "regenD1D10209015|2022-09-09--14-53-09--0"), - ("HONDA2", "regen3F7C2EFDC08|2022-09-09--19-41-19--0"), - ("CHRYSLER", "regen92783EAE66B|2022-09-09--13-15-44--0"), - ("RAM", "regenBE5DAAEF30F|2022-09-13--17-06-24--0"), - ("SUBARU", "regen8A363AF7E14|2022-09-13--17-20-39--0"), - ("GM", "regen31EA3F9A37C|2022-09-09--21-06-36--0"), - ("NISSAN", "regenAA21ADE5921|2022-09-09--19-44-37--0"), - ("VOLKSWAGEN", "regenA1BF4D17761|2022-09-09--19-46-24--0"), - ("MAZDA", "regen1994C97E977|2022-09-13--16-34-44--0"), + ("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"), + ("HYUNDAI", "regenBE53A59065B|2022-09-27--16-52-03--0"), + ("HYUNDAI", "regen11AA43BCA5F|2022-09-27--15-39-54--0"), + ("TOYOTA", "regen929C5790007|2022-09-27--16-27-47--0"), + ("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"), + ("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--0"), + ("HONDA", "regenC7D5645EB17|2022-09-27--15-47-29--0"), + ("HONDA2", "regenCC2ECCE5742|2022-09-27--16-18-01--0"), + ("CHRYSLER", "regenC253C4DAC90|2022-09-27--15-51-03--0"), + ("RAM", "regen20490083AE7|2022-09-27--15-53-15--0"), + ("SUBARU", "regen1E72BBDCED5|2022-09-27--15-55-31--0"), + ("GM", "regen45B05A80EF6|2022-09-27--15-57-22--0"), + ("NISSAN", "regenC19D899B46D|2022-09-27--15-59-13--0"), + ("VOLKSWAGEN", "regenD8F7AC4BD0D|2022-09-27--16-41-45--0"), + ("MAZDA", "regenFC3F9ECBB64|2022-09-27--16-03-09--0"), ] # dashcamOnly makes don't need to be tested until a full port is done From 4e310b807fb1ccdde4b2078468fab46e5324dfa2 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 27 Sep 2022 20:43:05 -0700 Subject: [PATCH 018/178] configure mypy outside of pre-commit environment (#25892) * add mypy config matching precommit * use local mypy, add files to config * excludes too * fix config * pylint is sad now... did it get updated? * fix typing hints * ignore * this should be a regexp * mypy doesn't like Deque despite inheriting MutableSequence * more excludes * Revert "pylint is sad now... did it get updated?" This reverts commit 250c632f18ecb3d33ffb931e15425f9314a0964b. --- .pre-commit-config.yaml | 16 +- Pipfile | 6 + Pipfile.lock | 1051 ++++++++++++++++++------------ common/realtime.py | 2 +- mypy.ini | 14 +- selfdrive/debug/check_freq.py | 4 +- selfdrive/debug/check_timings.py | 4 +- system/hardware/tici/casync.py | 1 + 8 files changed, 665 insertions(+), 433 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 347216f2fb..1eb5f632b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,18 +24,14 @@ repos: # if you've got a short variable name that's getting flagged, add it here - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup - --builtins clear,rare,informal,usage,code,names,en-GB_to_en-US -- repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.931 +- repo: local hooks: - id: mypy - exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)/|(tinygrad/)|(tinygrad_repo/)' - additional_dependencies: ['types-PyYAML', 'lxml', 'numpy', 'types-atomicwrites', 'types-pycurl', 'types-requests', 'types-certifi'] - args: - - --warn-redundant-casts - - --warn-return-any - - --warn-unreachable - - --warn-unused-ignores - #- --html-report=/home/batman/openpilot + name: mypy + entry: mypy + language: system + types: [python] + exclude: '^(pyextra/)|(cereal/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: diff --git a/Pipfile b/Pipfile index 644fd2a90b..778cf8c33c 100644 --- a/Pipfile +++ b/Pipfile @@ -43,6 +43,12 @@ carla = {version = "==0.9.13", markers="platform_system != 'Darwin'"} ft4222 = "*" pandas = "*" tabulate = "*" +types-pyyaml = "*" +lxml = "*" +types-atomicwrites = "*" +types-pycurl = "*" +types-requests = "*" +types-certifi = "*" [packages] atomicwrites = "*" diff --git a/Pipfile.lock b/Pipfile.lock index e6f05fbcd4..d033a517cb 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "adf64558652d394d9de8e45777f1a2f50ed1ac37b75664206e7957792832b5a4" + "sha256": "c406463198490fc40dcac3dd438c77cf36f6fe681793072e96fdecf089ff7639" }, "pipfile-spec": 6, "requires": { @@ -18,11 +18,11 @@ "default": { "astroid": { "hashes": [ - "sha256:396c88d0a58d7f8daadf730b2ce90838bf338c6752558db719ec6f99c18ec20e", - "sha256:d612609242996c4365aeb0345e61edba34363eaaba55f1c0addf6a98f073bef6" + "sha256:81f870105d892e73bf535da77a8261aa5bde838fa4ed12bb2f435291a098c581", + "sha256:997e0c735df60d4a4caff27080a3afc51f9bdd693d3572a4a0b7090b645c36c5" ], "markers": "python_full_version >= '3.7.2'", - "version": "==2.12.5" + "version": "==2.12.10" }, "atomicwrites": { "hashes": [ @@ -78,11 +78,11 @@ }, "certifi": { "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", + "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" ], "markers": "python_version >= '3.6'", - "version": "==2022.6.15" + "version": "==2022.9.24" }, "cffi": { "hashes": [ @@ -190,31 +190,35 @@ }, "cryptography": { "hashes": [ - "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", - "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", - "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", - "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", - "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", - "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", - "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", - "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", - "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", - "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", - "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", - "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", - "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", - "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", - "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", - "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", - "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", - "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", - "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", - "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", - "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", - "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" - ], - "index": "pypi", - "version": "==37.0.4" + "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", + "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", + "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", + "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", + "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", + "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", + "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", + "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", + "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", + "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", + "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", + "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", + "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", + "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", + "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", + "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", + "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", + "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", + "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", + "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", + "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", + "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", + "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", + "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", + "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", + "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" + ], + "index": "pypi", + "version": "==38.0.1" }, "cython": { "hashes": [ @@ -373,11 +377,11 @@ }, "idna": { "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], "markers": "python_version >= '3.5'", - "version": "==3.3" + "version": "==3.4" }, "importlib-metadata": { "hashes": [ @@ -400,7 +404,7 @@ "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" ], - "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "markers": "python_version < '4' and python_full_version >= '3.6.1'", "version": "==5.10.1" }, "itsdangerous": { @@ -593,37 +597,37 @@ }, "numpy": { "hashes": [ - "sha256:17e5226674f6ea79e14e3b91bfbc153fdf3ac13f5cc54ee7bc8fdbe820a32da0", - "sha256:2bd879d3ca4b6f39b7770829f73278b7c5e248c91d538aab1e506c628353e47f", - "sha256:4f41f5bf20d9a521f8cab3a34557cd77b6f205ab2116651f12959714494268b0", - "sha256:5593f67e66dea4e237f5af998d31a43e447786b2154ba1ad833676c788f37cde", - "sha256:5e28cd64624dc2354a349152599e55308eb6ca95a13ce6a7d5679ebff2962913", - "sha256:633679a472934b1c20a12ed0c9a6c9eb167fbb4cb89031939bfd03dd9dbc62b8", - "sha256:806970e69106556d1dd200e26647e9bee5e2b3f1814f9da104a943e8d548ca38", - "sha256:806cc25d5c43e240db709875e947076b2826f47c2c340a5a2f36da5bb10c58d6", - "sha256:8247f01c4721479e482cc2f9f7d973f3f47810cbc8c65e38fd1bbd3141cc9842", - "sha256:8ebf7e194b89bc66b78475bd3624d92980fca4e5bb86dda08d677d786fefc414", - "sha256:8ecb818231afe5f0f568c81f12ce50f2b828ff2b27487520d85eb44c71313b9e", - "sha256:8f9d84a24889ebb4c641a9b99e54adb8cab50972f0166a3abc14c3b93163f074", - "sha256:909c56c4d4341ec8315291a105169d8aae732cfb4c250fbc375a1efb7a844f8f", - "sha256:9b83d48e464f393d46e8dd8171687394d39bc5abfe2978896b77dc2604e8635d", - "sha256:ac987b35df8c2a2eab495ee206658117e9ce867acf3ccb376a19e83070e69418", - "sha256:b78d00e48261fbbd04aa0d7427cf78d18401ee0abd89c7559bbf422e5b1c7d01", - "sha256:b8b97a8a87cadcd3f94659b4ef6ec056261fa1e1c3317f4193ac231d4df70215", - "sha256:bd5b7ccae24e3d8501ee5563e82febc1771e73bd268eef82a1e8d2b4d556ae66", - "sha256:bdc02c0235b261925102b1bd586579b7158e9d0d07ecb61148a1799214a4afd5", - "sha256:be6b350dfbc7f708d9d853663772a9310783ea58f6035eec649fb9c4371b5389", - "sha256:c403c81bb8ffb1c993d0165a11493fd4bf1353d258f6997b3ee288b0a48fce77", - "sha256:cf8c6aed12a935abf2e290860af8e77b26a042eb7f2582ff83dc7ed5f963340c", - "sha256:d98addfd3c8728ee8b2c49126f3c44c703e2b005d4a95998e2167af176a9e722", - "sha256:dc76bca1ca98f4b122114435f83f1fcf3c0fe48e4e6f660e07996abf2f53903c", - "sha256:dec198619b7dbd6db58603cd256e092bcadef22a796f778bf87f8592b468441d", - "sha256:df28dda02c9328e122661f399f7655cdcbcf22ea42daa3650a26bce08a187450", - "sha256:e603ca1fb47b913942f3e660a15e55a9ebca906857edfea476ae5f0fe9b457d5", - "sha256:ecfdd68d334a6b97472ed032b5b37a30d8217c097acfff15e8452c710e775524" - ], - "index": "pypi", - "version": "==1.23.2" + "sha256:004f0efcb2fe1c0bd6ae1fcfc69cc8b6bf2407e0f18be308612007a0762b4089", + "sha256:09f6b7bdffe57fc61d869a22f506049825d707b288039d30f26a0d0d8ea05164", + "sha256:0ea3f98a0ffce3f8f57675eb9119f3f4edb81888b6874bc1953f91e0b1d4f440", + "sha256:17c0e467ade9bda685d5ac7f5fa729d8d3e76b23195471adae2d6a6941bd2c18", + "sha256:1f27b5322ac4067e67c8f9378b41c746d8feac8bdd0e0ffede5324667b8a075c", + "sha256:22d43376ee0acd547f3149b9ec12eec2f0ca4a6ab2f61753c5b29bb3e795ac4d", + "sha256:2ad3ec9a748a8943e6eb4358201f7e1c12ede35f510b1a2221b70af4bb64295c", + "sha256:301c00cf5e60e08e04d842fc47df641d4a181e651c7135c50dc2762ffe293dbd", + "sha256:39a664e3d26ea854211867d20ebcc8023257c1800ae89773cbba9f9e97bae036", + "sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd", + "sha256:78a63d2df1d947bd9d1b11d35564c2f9e4b57898aae4626638056ec1a231c40c", + "sha256:7cd1328e5bdf0dee621912f5833648e2daca72e3839ec1d6695e91089625f0b4", + "sha256:8355fc10fd33a5a70981a5b8a0de51d10af3688d7a9e4a34fcc8fa0d7467bb7f", + "sha256:8c79d7cf86d049d0c5089231a5bcd31edb03555bd93d81a16870aa98c6cfb79d", + "sha256:91b8d6768a75247026e951dce3b2aac79dc7e78622fc148329135ba189813584", + "sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8", + "sha256:98dcbc02e39b1658dc4b4508442a560fe3ca5ca0d989f0df062534e5ca3a5c1a", + "sha256:a64403f634e5ffdcd85e0b12c08f04b3080d3e840aef118721021f9b48fc1460", + "sha256:bc6e8da415f359b578b00bcfb1d08411c96e9a97f9e6c7adada554a0812a6cc6", + "sha256:bdc9febce3e68b697d931941b263c59e0c74e8f18861f4064c1f712562903411", + "sha256:c1ba66c48b19cc9c2975c0d354f24058888cdc674bebadceb3cdc9ec403fb5d1", + "sha256:c9f707b5bb73bf277d812ded9896f9512a43edff72712f31667d0a8c2f8e71ee", + "sha256:d5422d6a1ea9b15577a9432e26608c73a78faf0b9039437b075cf322c92e98e7", + "sha256:e5d5420053bbb3dd64c30e58f9363d7a9c27444c3648e61460c1237f9ec3fa14", + "sha256:e868b0389c5ccfc092031a861d4e158ea164d8b7fdbb10e3b5689b4fc6498df6", + "sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e", + "sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85", + "sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54" + ], + "index": "pypi", + "version": "==1.23.3" }, "onnx": { "hashes": [ @@ -692,6 +696,7 @@ "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", + "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437", "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", @@ -720,6 +725,7 @@ "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", + "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc", "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", @@ -793,41 +799,41 @@ }, "psutil": { "hashes": [ - "sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685", - "sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc", - "sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36", - "sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1", - "sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329", - "sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81", - "sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de", - "sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4", - "sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574", - "sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237", - "sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22", - "sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b", - "sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0", - "sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954", - "sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021", - "sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537", - "sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87", - "sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0", - "sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc", - "sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af", - "sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4", - "sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453", - "sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689", - "sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8", - "sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680", - "sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e", - "sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9", - "sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b", - "sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d", - "sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2", - "sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5", - "sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676" - ], - "index": "pypi", - "version": "==5.9.1" + "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97", + "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84", + "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969", + "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf", + "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32", + "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12", + "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727", + "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06", + "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c", + "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9", + "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea", + "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8", + "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f", + "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c", + "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d", + "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339", + "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444", + "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab", + "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb", + "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b", + "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8", + "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec", + "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8", + "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d", + "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34", + "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85", + "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1", + "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9", + "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1", + "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d", + "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5", + "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c" + ], + "index": "pypi", + "version": "==5.9.2" }, "pycapnp": { "hashes": [ @@ -909,62 +915,62 @@ }, "pyjwt": { "hashes": [ - "sha256:72d1d253f32dbd4f5c88eaf1fdc62f3a19f676ccbadb9dbc5d07e951b2b26daf", - "sha256:d42908208c699b3b973cbeb01a969ba6a96c821eefb1c5bfe4c390c01d67abba" + "sha256:8d82e7087868e94dd8d7d418e5088ce64f7daab4b36db654cbaedb46f9d1ca80", + "sha256:e77ab89480905d86998442ac5788f35333fa85f65047a534adc38edf3c88fc3b" ], "index": "pypi", - "version": "==2.4.0" + "version": "==2.5.0" }, "pylint": { "hashes": [ - "sha256:4b124affc198b7f7c9b5f9ab690d85db48282a025ef9333f51d2d7281b92a6c3", - "sha256:4f3f7e869646b0bd63b3dfb79f3c0f28fc3d2d923ea220d52620fd625aed92b0" + "sha256:5fdfd44af182866999e6123139d265334267339f29961f00c89783155eacc60b", + "sha256:7f6aad1d8d50807f7bc64f89ac75256a9baf8e6ed491cc9bc65592bc3f462cf1" ], "index": "pypi", - "version": "==2.15.0" + "version": "==2.15.3" }, "pyopencl": { "hashes": [ - "sha256:069e7eb1a223d88c13eafa54d6ae896fa892e75ba3d56ff2135a26107ef1142b", - "sha256:1490e6cdeaecba42854013c273685d65fd9102ee6dc6bc3bcb814e9e2b8179e5", - "sha256:15f7b3d29c9359e1e440e4f52f70de031f8d0d8d0f8de53a3bc01501b89360c0", - "sha256:1a2029b7fda6709eca077f618f997372c3d6f2780ad45512632b0d056e6305f9", - "sha256:25e87b4ccc0cc53487d445bea07ce9bdb478a335725df16986aead2ff65b68a4", - "sha256:2c9ad1cbc3f540afc52038851be8e06640aacfece051c89408bc3aece605a7ee", - "sha256:2df01c95ea9ae3dd66b277f0df47144cf7535a27b48a8d49fdd98e0583e368ce", - "sha256:316f59d0c40bfce4f6c160dbaf6501883b33880370bb1819f360dad747e52dfe", - "sha256:4836bc4619be967d6c28627adac151223037fdca056c4ab54da16b591f719347", - "sha256:4b53f7f3ed85ab671c8bfc61a0bbc5476725a7a5f51a94bba5512c3962b2d609", - "sha256:5304cb336af7316ae0650abb7467c076032635bfe4710b8df191612d245dca28", - "sha256:55e9302b8f0b1964c87b0fdab7b853aa2b2f10b4188f5b4618782d4380448c11", - "sha256:6032bef8a35f6df727a0b66e3c9faedb3f560318052848b28d2f72622cfbeace", - "sha256:6ec55934057e99461f684ccd293d87db59a452f5834c13ae36b19d31dfe38599", - "sha256:7176f96728be9b43024bd71704f60849cbfcf0fafd20270181b68ea4730ceb2d", - "sha256:730901d409d8251cd6e9dc59e6c518dff5cdb20a3a0b728344bfd2c707f28b64", - "sha256:75be43c7f33fb86f9d18b7b6f8e9081d8bd5b6331a90aec0d2cad3e81e72bc8f", - "sha256:7bef8e8bcfff574b481565390113ea0a37cf33fd2587ade7f2980f15e73f7b08", - "sha256:7ca9597877e1f8bdb4a49810988230f538b2d7aac389c33418a21cf4358f2fd4", - "sha256:814389b3eb9e6930cf43b984283c94a955edf20ec286402da5acfa503d3ae790", - "sha256:8efc3467454ce8c644f09029a3308496f9cb6e93ca5e8c08f6b79e7825da72c5", - "sha256:98bad7035f27b6de5c9268f52c1e10bffe3a2874994e862468a1792b699a4884", - "sha256:9bbfe94bb6e9d0458693183334e73c973e2fcba01568f42db15b453b926fb816", - "sha256:9d112a4426f5b356641c1312bf1004247dc4019e649502589b86333557203c01", - "sha256:a845779f505ed57b83f279307ae6307d886f3e41fb24dcf7889da27daa726118", - "sha256:aca3581f1a7f6b809b8cdc78b0e66587848b38b143bf2983e91ff8fb9a41bc8f", - "sha256:af5664b98140a29966c5fb12e9d29b85b6c6310efa97d82aee58310774917e8f", - "sha256:b85fa5ba1678dd40713587fd437787b6aa940000c2ddffa360884431be21723a", - "sha256:bcabfb5217ca8f8770f9c69298f79576080bb994b1883a99494b4c2668b04836", - "sha256:c00989bed1e7e5b32ad498fec3deb1c93403ab802cd99b7c78b9c692bd0910ef", - "sha256:d0ddc3b74ad1804eb3fe238dfa3b844b997e88b1ca5164a717c16b362b4f34c3", - "sha256:d8bb2eea4e960917e0a6132dedd34c8ec0b7a384f22713f775d50dbce154263a", - "sha256:db833ebb1e756969a8f851f15486598eb9e3fb27b0535c2a8193cc1c71455016", - "sha256:dc2d78cb5da0081ada1c263aaa773fd5479b3da5e2c421547bf7f3258d3239a5", - "sha256:dd2728e59ae088c900ed68f68d953476d0ff07189f182f917b74de2ac7b3972e", - "sha256:ea4eff6b922fa4ad2077ef90b3254d78597d050ada09bfbe74c22dd22d10c6ac", - "sha256:f8887d54e654598f3854472540b2eb228ac56b56a2491b95bdfac8f15be1c943" - ], - "index": "pypi", - "version": "==2022.1.6" + "sha256:024f7ad835f70fff2c27a3d111eec438761c42413ca78af20cc87ad3ecaba01a", + "sha256:03f3e96c8743edf16dceddef564c5198f8d988152a26a859de9d3e1e0f14888c", + "sha256:07e58b74a59cd27c390f3099597c1f05e50c441f82fb17fb84d43e5785951ecf", + "sha256:07e9ffe8d5d38066fd5a8a5540b6944617322b13d55db7e3f78609dc309e4da8", + "sha256:10bc9e39e6bb5c6f842dd4a3af869cb73a4ee29d47a74deed8120390c7bcf4ad", + "sha256:15d4843b88eb2379cb29d2ef13b93b6ce8f917bae70eb6c1584ba21c4c5c4ff6", + "sha256:21da5f08aabbc2b2fdc81466ffaac07c1db94b1f47a95bc57f633b55590ccba2", + "sha256:2f5010cfcd434c56e9bbad5fb4dba2fa64b1de6d8f9282b29a5d837268fe93cc", + "sha256:3361f7b1797cdd38c9fff54c6e34750a5665c9c573aab46fcb486fefdf6fcfca", + "sha256:4599fba8aff4c381d2f16651c025b4e6e9488100781a95634215755ea4935a0d", + "sha256:462f1ba6ee3769492bc376bdaeaf2f34f3528bd0fd0dc60569f3edf9346cc769", + "sha256:4890137300a18d94265f1342f5eb8de420029637a09020b682f09f63271170c5", + "sha256:4e55c1df1976040c01315dc09477c7369422e67f36dcf69d9f570bdf48759802", + "sha256:58641144140025b39dbf27a095e250018426a25973e7cfd87724b24c6c8a6a3e", + "sha256:5b8f4358d6bdcf719c46f91c0e9a8ff8ece05843f26de1d047ea1b1ae1875aaf", + "sha256:647ff48cb851040650d79d99eae0229bfd9cd8d931ffae8d33a887891be2b8f4", + "sha256:71ec7b12833a9e29807dc95421791c5ebed8f9435f3e99c5ac77f9f409429863", + "sha256:80db5d46608dfa9e37109bfb925be64cb8d24b06e27945cf7c8996fa9e64ae50", + "sha256:8189b95ac0c845b2639395d5dffe2c4406ac5e42c61b8db972e5dc596e5b8685", + "sha256:8906173bf2d3bc036e0acc8e4d785fc33c89951bacbb0049d64e75d7c476beed", + "sha256:9fbd13defa6f7d719540433890b5318f16a8c3c453dd5feb66a6054a79144c01", + "sha256:a238dd164b141dd9eea0fbab39f0d247bd3c5f8d31b85997aa5619c43a6cb9f5", + "sha256:a2a00fbe65fcebf071a6af59e96fc6b03c8dc95c0d5623cd7d96a083930ffb31", + "sha256:a41e0b02b4c9e6bfdf880d8f6d8d5a21fdd04cd7e7f2afd4d0c5c94994963530", + "sha256:a6bb5e7cb9612b011da83a274ee5477ee9c8892860bae5f3f105c80b512192f9", + "sha256:a78827b7d0f5ee22890a950d08cd2516b136c6af10abad696e5a8f4b62d55db5", + "sha256:b0711e2508fbf9de1d26817f79f57a43b6d84f088f6e40208582e7173a0a3b22", + "sha256:bdbbe72731fdc8152b555360efec791f9c5b71f0eed033052302d51547ca00cb", + "sha256:c4d734261075c750fe7347eb07cdc155d53deb4175be2b9d73524bcc9868072c", + "sha256:c6bd1e1d086d4b28322e88d22deeff06684360f3a9017d7d7eb2f1ae48095479", + "sha256:c8835ad22ac0e656d5b437abb99c763bb4529485c858216015c1286d6a919159", + "sha256:cd322df0f8bec18445e3be0d9dfb7899ed570e520949d337dfb42cc3306e835a", + "sha256:d04291fa318c7ce1033fba5e16f5ca5f7b28dab49213596b5139bac51812cd69", + "sha256:d1eecc9bd2c2dbc32b6f550b7e9264bb8e8bef8520e75fd4dbc627c21faebaf1", + "sha256:d493b83e1be953e7d7849704a0970d2e6915fb5b3514222ab8a6abd3d47e123e", + "sha256:dae8527318f60b557103d4d385892a86c3f470eeed1f560b830d1e8a5724a4b5", + "sha256:e0e81c075fb1c21a3080259fe7de771f1b06861140901c22e47f4e8a69bd8984" + ], + "index": "pypi", + "version": "==2022.2.3" }, "pyparsing": { "hashes": [ @@ -1011,6 +1017,7 @@ }, "pyyaml": { "hashes": [ + "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", @@ -1022,26 +1029,32 @@ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], @@ -1050,82 +1063,83 @@ }, "pyzmq": { "hashes": [ - "sha256:022cf5ea7bcaa8a06a03c2706e0ae66904b6138b2155577cd34c64bc7cc637ab", - "sha256:044447ae4b2016a6b8697571fd633f799f860b19b76c4a2fd9b1140d52ee6745", - "sha256:07ed8aaf7ffe150af873269690cc654ffeca7491f62aae0f3821baa181f8d5fe", - "sha256:10d1910ec381b851aeb024a042a13db178cb1edf125e76a4e9d2548ad103aadb", - "sha256:12e62ff0d5223ec09b597ab6d73858b9f64a51221399f3cb08aa495e1dff7935", - "sha256:1f368a82b29f80071781b20663c0fc0c8f6b13273f9f5abe1526af939534f90f", - "sha256:20bafc4095eab00f41a510579363a3f5e1f5c69d7ee10f1d88895c4df0259183", - "sha256:2141e6798d5981be04c08996d27962086a1aa3ea536fe9cf7e89817fd4523f86", - "sha256:23e708fbfdf4ee3107422b69ca65da1b9f056b431fc0888096a8c1d6cd908e8f", - "sha256:28dbdb90b2f6b131f8f10e6081012e4e25234213433420e67e0c1162de537113", - "sha256:29b74774a0bfd3c4d98ac853f0bdca55bd9ec89d5b0def5486407cca54472ef8", - "sha256:2b381aa867ece7d0a82f30a0c7f3d4387b7cf2e0697e33efaa5bed6c5784abcd", - "sha256:2f67b63f53c6994d601404fd1a329e6d940ac3dd1d92946a93b2b9c70df67b9f", - "sha256:342ca3077f47ec2ee41b9825142b614e03e026347167cbc72a59b618c4f6106c", - "sha256:35e635343ff367f697d00fa1484262bb68e36bc74c9b80737eac5a1e04c4e1b1", - "sha256:385609812eafd9970c3752c51f2f6c4f224807e3e441bcfd8c8273877d00c8a8", - "sha256:38e106b64bad744fe469dc3dd864f2764d66399178c1bf39d45294cc7980f14f", - "sha256:39dd252b683816935702825e5bf775df16090619ced9bb4ba68c2d0b6f0c9b18", - "sha256:407f909c4e8fde62fbdad9ebd448319792258cc0550c2815567a4d9d8d9e6d18", - "sha256:415ff62ac525d9add1e3550430a09b9928d2d24a20cc4ce809e67caac41219ab", - "sha256:4805af9614b0b41b7e57d17673459facf85604dac502a5a9244f6e8c9a4de658", - "sha256:48400b96788cdaca647021bf19a9cd668384f46e4d9c55cf045bdd17f65299c8", - "sha256:49d30ba7074f469e8167917abf9eb854c6503ae10153034a6d4df33618f1db5f", - "sha256:4bb798bef181648827019001f6be43e1c48b34b477763b37a8d27d8c06d197b8", - "sha256:4d6f110c56f7d5b4d64dde3a382ae61b6d48174e30742859d8e971b18b6c9e5c", - "sha256:55568a020ad2cae9ae36da6058e7ca332a56df968f601cbdb7cf6efb2a77579a", - "sha256:565bd5ab81f6964fc4067ccf2e00877ad0fa917308975694bbb54378389215f8", - "sha256:5c558b50402fca1acc94329c5d8f12aa429738904a5cfb32b9ed3c61235221bb", - "sha256:5e05492be125dce279721d6b54fd1b956546ecc4bcdfcf8e7b4c413bc0874c10", - "sha256:624fd38071a817644acdae075b92a23ea0bdd126a58148288e8284d23ec361ce", - "sha256:650389bbfca73955b262b2230423d89992f38ec48033307ae80e700eaa2fbb63", - "sha256:67975a9e1237b9ccc78f457bef17691bbdd2055a9d26e81ee914ba376846d0ce", - "sha256:6b1e79bba24f6df1712e3188d5c32c480d8eda03e8ecff44dc8ecb0805fa62f3", - "sha256:6fd5d0d50cbcf4bc376861529a907bed026a4cbe8c22a500ff8243231ef02433", - "sha256:71b32a1e827bdcbf73750e60370d3b07685816ff3d8695f450f0f8c3226503f8", - "sha256:794871988c34727c7f79bdfe2546e6854ae1fa2e1feb382784f23a9c6c63ecb3", - "sha256:79a87831b47a9f6161ad23fa5e89d5469dc585abc49f90b9b07fea8905ae1234", - "sha256:7e0113d70b095339e99bb522fe7294f5ae6a7f3b2b8f52f659469a74b5cc7661", - "sha256:84678153432241bcdca2210cf4ff83560b200556867aea913ffbb960f5d5f340", - "sha256:8a68f57b7a3f7b6b52ada79876be1efb97c8c0952423436e84d70cc139f16f0d", - "sha256:8c02a0cd39dc01659b3d6cb70bb3a41aebd9885fd78239acdd8d9c91351c4568", - "sha256:8c842109d31a9281d678f668629241c405928afbebd913c48a5a8e7aee61f63d", - "sha256:8dc66f109a245653b19df0f44a5af7a3f14cb8ad6c780ead506158a057bd36ce", - "sha256:90d88f9d9a2ae6cfb1dc4ea2d1710cdf6456bc1b9a06dd1bb485c5d298f2517e", - "sha256:9269fbfe3a4eb2009199120861c4571ef1655fdf6951c3e7f233567c94e8c602", - "sha256:929d548b74c0f82f7f95b54e4a43f9e4ce2523cfb8a54d3f7141e45652304b2a", - "sha256:99a5a77a10863493a1ee8dece02578c6b32025fb3afff91b40476bc489e81648", - "sha256:9a39ddb0431a68954bd318b923230fa5b649c9c62b0e8340388820c5f1b15bd2", - "sha256:9d0ab2936085c85a1fc6f9fd8f89d5235ae99b051e90ec5baa5e73ad44346e1f", - "sha256:9e5bf6e7239fc9687239de7a283aa8b801ab85371116045b33ae20132a1325d6", - "sha256:a0f09d85c45f58aa8e715b42f8b26beba68b3b63a8f7049113478aca26efbc30", - "sha256:a114992a193577cb62233abf8cb2832970f9975805a64740e325d2f895e7f85a", - "sha256:a3fd44b5046d247e7f0f1660bcafe7b5fb0db55d0934c05dd57dda9e1f823ce7", - "sha256:ad28ddb40db8e450d7d4bf8a1d765d3f87b63b10e7e9a825a3c130c6371a8c03", - "sha256:aecd6ceaccc4b594e0092d6513ef3f1c0fa678dd89f86bb8ff1a47014b8fca35", - "sha256:b815991c7d024bf461f358ad871f2be1135576274caed5749c4828859e40354e", - "sha256:b861db65f6b8906c8d6db51dde2448f266f0c66bf28db2c37aea50f58a849859", - "sha256:c3ebf1668664d20c8f7d468955f18379b7d1f7bc8946b13243d050fa3888c7ff", - "sha256:c56b1a62a1fb87565343c57b6743fd5da6e138b8c6562361d7d9b5ce4acf399a", - "sha256:c780acddd2934c6831ff832ecbf78a45a7b62d4eb216480f863854a8b7d54fa7", - "sha256:c890309296f53f9aa32ffcfc51d805705e1982bffd27c9692a8f1e1b8de279f4", - "sha256:c9cfaf530e6a7ff65f0afe275e99f983f68b54dfb23ea401f0bc297a632766b6", - "sha256:d904f6595acfaaf99a1a61881fea068500c40374d263e5e073aa4005e5f9c28a", - "sha256:e06747014a5ad1b28cebf5bc1ddcdaccfb44e9b441d35e6feb1286c8a72e54be", - "sha256:e1fe30bcd5aea5948c42685fad910cd285eacb2518ea4dc6c170d6b535bee95d", - "sha256:e753eee6d3b93c5354e8ba0a1d62956ee49355f0a36e00570823ef64e66183f5", - "sha256:ec9803aca9491fd6f0d853d2a6147f19f8deaaa23b1b713d05c5d09e56ea7142", - "sha256:efb9e38b2a590282704269585de7eb33bf43dc294cad092e1b172e23d4c217e5", - "sha256:f07016e3cf088dbfc6e7c5a7b3f540db5c23b0190d539e4fd3e2b5e6beffa4b5", - "sha256:f392cbea531b7142d1958c0d4a0c9c8d760dc451e5848d8dd3387804d3e3e62c", - "sha256:f619fd38fc2641abfb53cca719c165182500600b82c695cc548a0f05f764be05", - "sha256:fefdf9b685fda4141b95ebec975946076a5e0723ff70b037032b2085c5317684", - "sha256:ffc6b1623d0f9affb351db4ca61f432dca3628a5ee015f9bf2bfbe9c6836881c" - ], - "index": "pypi", - "version": "==23.2.1" + "sha256:0108358dab8c6b27ff6b985c2af4b12665c1bc659648284153ee501000f5c107", + "sha256:07bec1a1b22dacf718f2c0e71b49600bb6a31a88f06527dfd0b5aababe3fa3f7", + "sha256:0e8f482c44ccb5884bf3f638f29bea0f8dc68c97e38b2061769c4cb697f6140d", + "sha256:0ec91f1bad66f3ee8c6deb65fa1fe418e8ad803efedd69c35f3b5502f43bd1dc", + "sha256:0f14cffd32e9c4c73da66db97853a6aeceaac34acdc0fae9e5bbc9370281864c", + "sha256:15975747462ec49fdc863af906bab87c43b2491403ab37a6d88410635786b0f4", + "sha256:1724117bae69e091309ffb8255412c4651d3f6355560d9af312d547f6c5bc8b8", + "sha256:1a7c280185c4da99e0cc06c63bdf91f5b0b71deb70d8717f0ab870a43e376db8", + "sha256:1b7928bb7580736ffac5baf814097be342ba08d3cfdfb48e52773ec959572287", + "sha256:2032d9cb994ce3b4cba2b8dfae08c7e25bc14ba484c770d4d3be33c27de8c45b", + "sha256:20e7eeb1166087db636c06cae04a1ef59298627f56fb17da10528ab52a14c87f", + "sha256:216f5d7dbb67166759e59b0479bca82b8acf9bed6015b526b8eb10143fb08e77", + "sha256:28b119ba97129d3001673a697b7cce47fe6de1f7255d104c2f01108a5179a066", + "sha256:3104f4b084ad5d9c0cb87445cc8cfd96bba710bef4a66c2674910127044df209", + "sha256:3e6192dbcefaaa52ed81be88525a54a445f4b4fe2fffcae7fe40ebb58bd06bfd", + "sha256:42d4f97b9795a7aafa152a36fe2ad44549b83a743fd3e77011136def512e6c2a", + "sha256:44e706bac34e9f50779cb8c39f10b53a4d15aebb97235643d3112ac20bd577b4", + "sha256:47b11a729d61a47df56346283a4a800fa379ae6a85870d5a2e1e4956c828eedc", + "sha256:4854f9edc5208f63f0841c0c667260ae8d6846cfa233c479e29fdc85d42ebd58", + "sha256:48f721f070726cd2a6e44f3c33f8ee4b24188e4b816e6dd8ba542c8c3bb5b246", + "sha256:52afb0ac962963fff30cf1be775bc51ae083ef4c1e354266ab20e5382057dd62", + "sha256:54d8b9c5e288362ec8595c1d98666d36f2070fd0c2f76e2b3c60fbad9bd76227", + "sha256:5bd3d7dfd9cd058eb68d9a905dec854f86649f64d4ddf21f3ec289341386c44b", + "sha256:613010b5d17906c4367609e6f52e9a2595e35d5cc27d36ff3f1b6fa6e954d944", + "sha256:624321120f7e60336be8ec74a172ae7fba5c3ed5bf787cc85f7e9986c9e0ebc2", + "sha256:65c94410b5a8355cfcf12fd600a313efee46ce96a09e911ea92cf2acf6708804", + "sha256:6640f83df0ae4ae1104d4c62b77e9ef39be85ebe53f636388707d532bee2b7b8", + "sha256:687700f8371643916a1d2c61f3fdaa630407dd205c38afff936545d7b7466066", + "sha256:77c2713faf25a953c69cf0f723d1b7dd83827b0834e6c41e3fb3bbc6765914a1", + "sha256:78068e8678ca023594e4a0ab558905c1033b2d3e806a0ad9e3094e231e115a33", + "sha256:7a23ccc1083c260fa9685c93e3b170baba45aeed4b524deb3f426b0c40c11639", + "sha256:7abddb2bd5489d30ffeb4b93a428130886c171b4d355ccd226e83254fcb6b9ef", + "sha256:80093b595921eed1a2cead546a683b9e2ae7f4a4592bb2ab22f70d30174f003a", + "sha256:8242543c522d84d033fe79be04cb559b80d7eb98ad81b137ff7e0a9020f00ace", + "sha256:838812c65ed5f7c2bd11f7b098d2e5d01685a3f6d1f82849423b570bae698c00", + "sha256:83ea1a398f192957cb986d9206ce229efe0ee75e3c6635baff53ddf39bd718d5", + "sha256:8421aa8c9b45ea608c205db9e1c0c855c7e54d0e9c2c2f337ce024f6843cab3b", + "sha256:858375573c9225cc8e5b49bfac846a77b696b8d5e815711b8d4ba3141e6e8879", + "sha256:86de64468cad9c6d269f32a6390e210ca5ada568c7a55de8e681ca3b897bb340", + "sha256:87f7ac99b15270db8d53f28c3c7b968612993a90a5cf359da354efe96f5372b4", + "sha256:8bad8210ad4df68c44ff3685cca3cda448ee46e20d13edcff8909eba6ec01ca4", + "sha256:8bb4af15f305056e95ca1bd086239b9ebc6ad55e9f49076d27d80027f72752f6", + "sha256:8c78bfe20d4c890cb5580a3b9290f700c570e167d4cdcc55feec07030297a5e3", + "sha256:8f3f3154fde2b1ff3aa7b4f9326347ebc89c8ef425ca1db8f665175e6d3bd42f", + "sha256:94010bd61bc168c103a5b3b0f56ed3b616688192db7cd5b1d626e49f28ff51b3", + "sha256:941fab0073f0a54dc33d1a0460cb04e0d85893cb0c5e1476c785000f8b359409", + "sha256:9dca7c3956b03b7663fac4d150f5e6d4f6f38b2462c1e9afd83bcf7019f17913", + "sha256:a180dbd5ea5d47c2d3b716d5c19cc3fb162d1c8db93b21a1295d69585bfddac1", + "sha256:a2712aee7b3834ace51738c15d9ee152cc5a98dc7d57dd93300461b792ab7b43", + "sha256:a435ef8a3bd95c8a2d316d6e0ff70d0db524f6037411652803e118871d703333", + "sha256:abb756147314430bee5d10919b8493c0ccb109ddb7f5dfd2fcd7441266a25b75", + "sha256:abe6eb10122f0d746a0d510c2039ae8edb27bc9af29f6d1b05a66cc2401353ff", + "sha256:acbd0a6d61cc954b9f535daaa9ec26b0a60a0d4353c5f7c1438ebc88a359a47e", + "sha256:ae08ac90aa8fa14caafc7a6251bd218bf6dac518b7bff09caaa5e781119ba3f2", + "sha256:ae61446166983c663cee42c852ed63899e43e484abf080089f771df4b9d272ef", + "sha256:afe1f3bc486d0ce40abb0a0c9adb39aed3bbac36ebdc596487b0cceba55c21c1", + "sha256:b946da90dc2799bcafa682692c1d2139b2a96ec3c24fa9fc6f5b0da782675330", + "sha256:b947e264f0e77d30dcbccbb00f49f900b204b922eb0c3a9f0afd61aaa1cedc3d", + "sha256:bb5635c851eef3a7a54becde6da99485eecf7d068bd885ac8e6d173c4ecd68b0", + "sha256:bcbebd369493d68162cddb74a9c1fcebd139dfbb7ddb23d8f8e43e6c87bac3a6", + "sha256:c31805d2c8ade9b11feca4674eee2b9cce1fec3e8ddb7bbdd961a09dc76a80ea", + "sha256:c8840f064b1fb377cffd3efeaad2b190c14d4c8da02316dae07571252d20b31f", + "sha256:ccb94342d13e3bf3ffa6e62f95b5e3f0bc6bfa94558cb37f4b3d09d6feb536ff", + "sha256:d66689e840e75221b0b290b0befa86f059fb35e1ee6443bce51516d4d61b6b99", + "sha256:dabf1a05318d95b1537fd61d9330ef4313ea1216eea128a17615038859da3b3b", + "sha256:db03704b3506455d86ec72c3358a779e9b1d07b61220dfb43702b7b668edcd0d", + "sha256:de4217b9eb8b541cf2b7fde4401ce9d9a411cc0af85d410f9d6f4333f43640be", + "sha256:df0841f94928f8af9c7a1f0aaaffba1fb74607af023a152f59379c01c53aee58", + "sha256:dfb992dbcd88d8254471760879d48fb20836d91baa90f181c957122f9592b3dc", + "sha256:e7e66b4e403c2836ac74f26c4b65d8ac0ca1eef41dfcac2d013b7482befaad83", + "sha256:e8012bce6836d3f20a6c9599f81dfa945f433dab4dbd0c4917a6fb1f998ab33d", + "sha256:f01de4ec083daebf210531e2cca3bdb1608dbbbe00a9723e261d92087a1f6ebc", + "sha256:f0d945a85b70da97ae86113faf9f1b9294efe66bd4a5d6f82f2676d567338b66", + "sha256:fa0ae3275ef706c0309556061185dd0e4c4cd3b7d6f67ae617e4e677c7a41e2e" + ], + "index": "pypi", + "version": "==24.0.1" }, "requests": { "hashes": [ @@ -1145,11 +1159,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:2d7ec7bc88ebbdf2c4b6b2650b3257893d386325a96c9b723adcd31033469b63", - "sha256:b4b41f90951ed83e7b4c176eef021b19ecba39da5b73aca106c97a9b7e279a90" + "sha256:d6c71d2f85710b66822adaa954af7912bab135d6c85febd5b0f3dfd4ab37e181", + "sha256:ef925b5338625448645a778428d8f22a3d17de8b28cc8e6fba60b93393ad86fe" ], "index": "pypi", - "version": "==1.9.5" + "version": "==1.9.9" }, "setproctitle": { "hashes": [ @@ -1219,11 +1233,11 @@ }, "setuptools": { "hashes": [ - "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", - "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57" + "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9", + "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1" ], "markers": "python_version >= '3.7'", - "version": "==65.3.0" + "version": "==65.4.0" }, "shiboken2": { "hashes": [ @@ -1255,19 +1269,19 @@ }, "sympy": { "hashes": [ - "sha256:1fe96b4c56bb7a7630cdf150a6cd98bc97a79e6be233e30502aba1cf54dee33d", - "sha256:b53069f5f30e4a4690b57cdb8e3d0d9065fff42627239db718214f804e442481" + "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf", + "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658" ], "index": "pypi", - "version": "==1.11" + "version": "==1.11.1" }, "timezonefinder": { "hashes": [ - "sha256:2791ad459b85c110226057cb5ebdd6503f4fb0a33cc4f76fb93e98ed545edd68", - "sha256:406bea77a7baec5e2b1c2b4793ff8f40c174b6d8e894631e60864d956139afef" + "sha256:96c96db94e75e072187843152e6c5dc0718500a9a91986032365abe09162d0e7", + "sha256:f2ee561b1e7692b933fcd914df38800e93db7caf278e7328de7328829b04f275" ], "index": "pypi", - "version": "==6.1.1" + "version": "==6.1.3" }, "tomli": { "hashes": [ @@ -1282,16 +1296,16 @@ "sha256:25d4e2e446c453be6360c67ddfb88838cfc42026322770ba13d1fbd403a93a5c", "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83" ], - "markers": "python_version >= '3.6' and python_version < '4.0'", + "markers": "python_version >= '3.6' and python_version < '4'", "version": "==0.11.4" }, "tqdm": { "hashes": [ - "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", - "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" + "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", + "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" ], "index": "pypi", - "version": "==4.64.0" + "version": "==4.64.1" }, "typing-extensions": { "hashes": [ @@ -1318,11 +1332,11 @@ }, "websocket-client": { "hashes": [ - "sha256:33ad3cf0aef4270b95d10a5a66b670a66be1f5ccf10ce390b3644f9eddfdca9d", - "sha256:79d730c9776f4f112f33b10b78c8d209f23b5806d9a783e296b3813fc5add2f1" + "sha256:398909eb7e261f44b8f4bd474785b6ec5f5b499d4953342fe9755e01ef624090", + "sha256:f9611eb65c8241a67fb373bef040b3cf8ad377a9f6546a12b620b6511e8ea9ef" ], "index": "pypi", - "version": "==1.4.0" + "version": "==1.4.1" }, "werkzeug": { "hashes": [ @@ -1543,11 +1557,11 @@ }, "certifi": { "hashes": [ - "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", - "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" + "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", + "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" ], "markers": "python_version >= '3.6'", - "version": "==2022.6.15" + "version": "==2022.9.24" }, "cffi": { "hashes": [ @@ -1635,6 +1649,81 @@ "markers": "python_version >= '3.6'", "version": "==2.1.1" }, + "contourpy": { + "hashes": [ + "sha256:0389349875424aa8c5e61f757e894687916bc4e9616cc6afcbd8051aa2428952", + "sha256:0395ae71164bfeb2dedd136e03c71a2718a5aa9873a46f518f4133be0d63e1d2", + "sha256:057114f698ffb9e54657e8fda6802e2f5c8fad609845cf6afaf31590ef6a33c0", + "sha256:061e1f066c419ffe25b615a1df031b4832ea1d7f2676937e69e8e00e24512005", + "sha256:06c4d1dde5ee4f909a8a95ba1eb04040c6c26946b4f3b5beaf10d45f14e940ee", + "sha256:09ed9b63f4df8a7591b7a4a26c1ad066dcaafda1f846250fdcb534074a411692", + "sha256:0f7672148f8fca48e4efc16aba24a7455b40c22d4f8abe42475dec6a12b0bb9a", + "sha256:0f89f0608a5aa8142ed0e53957916623791a88c7f5e5f07ae530c328beeb888f", + "sha256:128bd7acf569f8443ad5b2227f30ac909e4f5399ed221727eeacf0c6476187e6", + "sha256:19ea64fa0cf389d2ebc10974616acfa1fdecbd73d1fd9c72215b782f3c40f561", + "sha256:1fb782982c42cee667b892a0b0c52a9f6c7ecf1da5c5f4345845f04eaa862f93", + "sha256:218722a29c5c26677d37c44f5f8a372daf6f07870aad793a97d47eb6ad6b3290", + "sha256:2b5e334330d82866923015b455260173cb3b9e3b4e297052d758abd262031289", + "sha256:2bf5c846c257578b03d498b20f54f53551616a507d8e5463511c58bb58e9a9cf", + "sha256:2d0ad9a85f208473b1f3613c45756c7aa6fcc288266a8c7b873f896aaf741b6b", + "sha256:2f54dcc9bb9390fd0636301ead134d46d5229fe86da0db4d974c0fda349f560e", + "sha256:3109fa601d2a448cec4643abd3a31f972bf05b7c2f2e83df9d3429878f8c10ae", + "sha256:3210d93ad2af742b6a96cf39792f7181822edbb8fe11c3ef29d1583fe637a8d8", + "sha256:3b3082ade8849130203d461b98c2a061b382c46074b43b4edd5cefd81af92b8a", + "sha256:3c3f2f6b898a40207843ae01970e57e33d22a26b22f23c6a5e07b4716751085f", + "sha256:3ca40d7844b391d90b864c6a6d1bb6b88b09035fb4d866d64d43c4d26fb0ab64", + "sha256:3cfc067ddde78b76dcbc9684d82688b7d3c5158fa2254a085f9bcb9586c1e2d8", + "sha256:434942fa2f9019b9ae525fb752dc523800c49a1a28fbd6d9240b0fa959573dcc", + "sha256:46b8e24813e2fb5a3e598c1f8b9ae403e1438cb846a80cc2b33cddf19dddd7f2", + "sha256:59c827e536bb5e3ef58e06da0faba61fd89a14f30b68bcfeca41f43ca83a1942", + "sha256:60f37acd4e4227c5a29f737d9a85ca3145c529a8dd4bf70af7f0637c61b49222", + "sha256:689d7d2a840619915d0abd1ecc6e399fee202f8ad315acda2807f4ca420d0802", + "sha256:6c02e22cf09996194bcb3a4784099975cf527d5c29caf759abadf29ebdb2fe27", + "sha256:79908b9d02b1d6c1c71ff3b7ad127f3f82e14a8e091ab44b3c7e34b649fea733", + "sha256:7c9e99aac7b430f6a9f15eebf058c742097cea3369f23a2bfc5e64d374b67e3a", + "sha256:813c2944e940ef8dccea71305bacc942d4b193a021140874b3e58933ec44f5b6", + "sha256:87121b9428ac568fb84fae4af5e7852fc34f02eadc4e3e91f6c8989327692186", + "sha256:896631cd40222aef3697e4e51177d14c3709fda49d30983269d584f034acc8a4", + "sha256:970a4be7ec84ccda7c27cb4ae74930bbbd477bc8d849ed55ea798084dd5fca8c", + "sha256:9939796abcadb2810a63dfb26ff8ca4595fe7dd70a3ceae7f607a2639b714307", + "sha256:99a8071e351b50827ad976b92ed91845fb614ac67a3c41109b24f3d8bd3afada", + "sha256:9c16fa267740d67883899e054cccb4279e002f3f4872873b752c1ba15045ff49", + "sha256:a30e95274f5c0e007ccc759ec258aa5708c534ec058f153ee25ac700a2f1438b", + "sha256:a74afd8d560eaafe0d9e3e1db8c06081282a05ca4de00ee416195085a79d7d3d", + "sha256:b46a04588ceb7cf132568e0e564a854627ef87a1ed3bf536234540a79ced44b0", + "sha256:b4963cf08f4320d98ae72ec7694291b8ab85cb7da3b0cd824bc32701bc992edf", + "sha256:b50e481a4317a8efcfffcfddcd4c9b36eacba440440e70cbe0256aeb6fd6abae", + "sha256:b85553699862c09937a7a5ea14ee6229087971a7d51ae97d5f4b407f571a2c17", + "sha256:bcc98d397c3dea45d5b262029564b29cb8e945f2607a38bee6163694c0a8b4ef", + "sha256:bed3a2a823a041e8d249b1a7ec132933e1505299329b5cfe1b2b5ec689ec7675", + "sha256:bf6b4c0c723664f65c2a47c8cb6ebbf660b0b2e2d936adf2e8503d4e93359465", + "sha256:bfd634cb9685161b2a51f73a7fc4736fd0d67a56632d52319317afaa27f08243", + "sha256:c0d5ee865b5fd16bf62d72122aadcc90aab296c30c1adb0a32b4b66bd843163e", + "sha256:c2b4eab7c12f9cb460509bc34a3b086f9802f0dba27c89a63df4123819ad64af", + "sha256:c51568e94f7f232296de30002f2a50f77a7bd346673da3e4f2aaf9d2b833f2e5", + "sha256:c5158616ab39d34b76c50f40c81552ee180598f7825dc7a66fd187d29958820f", + "sha256:cdacddb18d55ffec42d1907079cdc04ec4fa8a990cdf5b9d9fe67d281fc0d12e", + "sha256:ce763369e646e59e4ca2c09735cd1bdd3048d909ad5f2bc116e83166a9352f3c", + "sha256:d45822b0a2a452327ab4f95efe368d234d5294bbf89a99968be27c7938a21108", + "sha256:d8150579bf30cdf896906baf256aa200cd50dbe6e565c17d6fd3d678e21ff5de", + "sha256:d88814befbd1433152c5f6dd536905149ba028d795a22555b149ae0a36024d9e", + "sha256:dca5be83a6dfaf933a46e3bc2b9f2685e5ec61b22f6a38ad740aac9c16e9a0ff", + "sha256:dd084459ecdb224e617e4ab3f1d5ebe4d1c48facb41f24952b76aa6ba9712bb0", + "sha256:def9a01b73c9e27d70ea03b381fb3e7aadfac1f398dbd63751313c3a46747ef5", + "sha256:df65f4b2b4e74977f0336bef12a88051ab24e6a16873cd9249f34d67cb3e345d", + "sha256:dfe924e5a63861c82332a12adeeab955dc8c8009ddbbd80cc2fcca049ff89a49", + "sha256:e67dcaa34dcd908fcccbf49194211d847c731b6ebaac661c1c889f1bf6af1e44", + "sha256:eba62b7c21a33e72dd8adab2b92dd5610d8527f0b2ac28a8e0770e71b21a13f9", + "sha256:ed9c91bf4ce614efed5388c3f989a7cfe08728ab871d995a486ea74ff88993db", + "sha256:f05d311c937da03b0cd26ac3e14cb991f6ff8fc94f98b3df9713537817539795", + "sha256:f1cc623fd6855b25da52b3275e0c9e51711b86a9dccc75f8c9ab4432fd8e42c7", + "sha256:f670686d99c867d0f24b28ce8c6f02429c6eef5e2674aab287850d0ee2d20437", + "sha256:f856652f9b533c6cd2b9ad6836a7fc0e43917d7ff15be46c5baf1350f8cdc5d9", + "sha256:fb0458d74726937ead9e2effc91144aea5a58ecee9754242f8539a782bed685a" + ], + "markers": "python_version >= '3.7'", + "version": "==1.0.5" + }, "control": { "hashes": [ "sha256:0891d2d32d6006ac1faa4e238ed8223ca342a4721d202dfeccae24fb02563183" @@ -1700,31 +1789,35 @@ }, "cryptography": { "hashes": [ - "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", - "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", - "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", - "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", - "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", - "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", - "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", - "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", - "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", - "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", - "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", - "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", - "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", - "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", - "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", - "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", - "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", - "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", - "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", - "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", - "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", - "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" - ], - "index": "pypi", - "version": "==37.0.4" + "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", + "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", + "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", + "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", + "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", + "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", + "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", + "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", + "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", + "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", + "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", + "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", + "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", + "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", + "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", + "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", + "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", + "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", + "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", + "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", + "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", + "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", + "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", + "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", + "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", + "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" + ], + "index": "pypi", + "version": "==38.0.1" }, "cycler": { "hashes": [ @@ -1818,11 +1911,11 @@ }, "fonttools": { "hashes": [ - "sha256:4606e1a88ee1f6699d182fea9511bd9a8a915d913eab4584e5226da1180fcce7", - "sha256:fff6b752e326c15756c819fe2fe7ceab69f96a1dbcfe8911d0941cdb49905007" + "sha256:a5bc5f5d48faa4085310b8ebd4c5d33bf27c6636c5f10a7de792510af2745a81", + "sha256:f32ef6ec966cf0e7d2aa88601fed2e3a8f2851c26b5db2c80ccc8f82bee4eedc" ], "markers": "python_version >= '3.7'", - "version": "==4.37.1" + "version": "==4.37.3" }, "ft4222": { "hashes": [ @@ -1876,19 +1969,19 @@ }, "identify": { "hashes": [ - "sha256:25851c8c1370effb22aaa3c987b30449e9ff0cece408f810ae6ce408fdd20893", - "sha256:887e7b91a1be152b0d46bbf072130235a8117392b9f1828446079a816a05ef44" + "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6", + "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97" ], "markers": "python_version >= '3.7'", - "version": "==2.5.3" + "version": "==2.5.5" }, "idna": { "hashes": [ - "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", - "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" ], "markers": "python_version >= '3.5'", - "version": "==3.3" + "version": "==3.4" }, "imagesize": { "hashes": [ @@ -2057,6 +2150,82 @@ "index": "pypi", "version": "==1.1.8" }, + "lxml": { + "hashes": [ + "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318", + "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c", + "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b", + "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000", + "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73", + "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d", + "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb", + "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8", + "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2", + "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345", + "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94", + "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e", + "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b", + "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc", + "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a", + "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9", + "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc", + "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387", + "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb", + "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7", + "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4", + "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97", + "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67", + "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627", + "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7", + "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd", + "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3", + "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7", + "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130", + "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b", + "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036", + "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785", + "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca", + "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91", + "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc", + "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536", + "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391", + "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3", + "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d", + "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21", + "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3", + "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d", + "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29", + "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715", + "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed", + "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25", + "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c", + "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785", + "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837", + "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4", + "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b", + "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2", + "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067", + "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448", + "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d", + "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2", + "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc", + "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c", + "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5", + "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84", + "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8", + "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf", + "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7", + "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e", + "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb", + "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b", + "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3", + "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad", + "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8", + "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f" + ], + "index": "pypi", + "version": "==4.9.1" + }, "markdown-it-py": { "hashes": [ "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27", @@ -2113,44 +2282,50 @@ }, "matplotlib": { "hashes": [ - "sha256:0bcdfcb0f976e1bac6721d7d457c17be23cf7501f977b6a38f9d38a3762841f7", - "sha256:1e64ac9be9da6bfff0a732e62116484b93b02a0b4d4b19934fb4f8e7ad26ad6a", - "sha256:22227c976ad4dc8c5a5057540421f0d8708c6560744ad2ad638d48e2984e1dbc", - "sha256:2886cc009f40e2984c083687251821f305d811d38e3df8ded414265e4583f0c5", - "sha256:2e6d184ebe291b9e8f7e78bbab7987d269c38ea3e062eace1fe7d898042ef804", - "sha256:3211ba82b9f1518d346f6309df137b50c3dc4421b4ed4815d1d7eadc617f45a1", - "sha256:339cac48b80ddbc8bfd05daae0a3a73414651a8596904c2a881cfd1edb65f26c", - "sha256:35a8ad4dddebd51f94c5d24bec689ec0ec66173bf614374a1244c6241c1595e0", - "sha256:3b4fa56159dc3c7f9250df88f653f085068bcd32dcd38e479bba58909254af7f", - "sha256:43e9d3fa077bf0cc95ded13d331d2156f9973dce17c6f0c8b49ccd57af94dbd9", - "sha256:57f1b4e69f438a99bb64d7f2c340db1b096b41ebaa515cf61ea72624279220ce", - "sha256:5c096363b206a3caf43773abebdbb5a23ea13faef71d701b21a9c27fdcef72f4", - "sha256:6bb93a0492d68461bd458eba878f52fdc8ac7bdb6c4acdfe43dba684787838c2", - "sha256:6ea6aef5c4338e58d8d376068e28f80a24f54e69f09479d1c90b7172bad9f25b", - "sha256:6fe807e8a22620b4cd95cfbc795ba310dc80151d43b037257250faf0bfcd82bc", - "sha256:73dd93dc35c85dece610cca8358003bf0760d7986f70b223e2306b4ea6d1406b", - "sha256:839d47b8ead7ad9669aaacdbc03f29656dc21f0d41a6fea2d473d856c39c8b1c", - "sha256:874df7505ba820e0400e7091199decf3ff1fde0583652120c50cd60d5820ca9a", - "sha256:879c7e5fce4939c6aa04581dfe08d57eb6102a71f2e202e3314d5fbc072fd5a0", - "sha256:94ff86af56a3869a4ae26a9637a849effd7643858a1a04dd5ee50e9ab75069a7", - "sha256:99482b83ebf4eb6d5fc6813d7aacdefdd480f0d9c0b52dcf9f1cc3b2c4b3361a", - "sha256:9ab29589cef03bc88acfa3a1490359000c18186fc30374d8aa77d33cc4a51a4a", - "sha256:9befa5954cdbc085e37d974ff6053da269474177921dd61facdad8023c4aeb51", - "sha256:a206a1b762b39398efea838f528b3a6d60cdb26fe9d58b48265787e29cd1d693", - "sha256:ab8d26f07fe64f6f6736d635cce7bfd7f625320490ed5bfc347f2cdb4fae0e56", - "sha256:b28de401d928890187c589036857a270a032961411934bdac4cf12dde3d43094", - "sha256:b428076a55fb1c084c76cb93e68006f27d247169f056412607c5c88828d08f88", - "sha256:bf618a825deb6205f015df6dfe6167a5d9b351203b03fab82043ae1d30f16511", - "sha256:c995f7d9568f18b5db131ab124c64e51b6820a92d10246d4f2b3f3a66698a15b", - "sha256:cd45a6f3e93a780185f70f05cf2a383daed13c3489233faad83e81720f7ede24", - "sha256:d2484b350bf3d32cae43f85dcfc89b3ed7bd2bcd781ef351f93eb6fb2cc483f9", - "sha256:d62880e1f60e5a30a2a8484432bcb3a5056969dc97258d7326ad465feb7ae069", - "sha256:dacddf5bfcec60e3f26ec5c0ae3d0274853a258b6c3fc5ef2f06a8eb23e042be", - "sha256:f3840c280ebc87a48488a46f760ea1c0c0c83fcf7abbe2e6baf99d033fd35fd8", - "sha256:f814504e459c68118bf2246a530ed953ebd18213dc20e3da524174d84ed010b2" - ], - "index": "pypi", - "version": "==3.5.3" + "sha256:0958fc3fdc59c1b716ee1a5d14e73d03d541d873241a37c5c3a86f7ef6017923", + "sha256:0ae1b9b555212c1e242666af80e7ed796705869581e2d749971db4e682ccc1f3", + "sha256:11c1987b803cc2b26725659cfe817478f0a9597878e5c4bf374cfe4e12cbbd79", + "sha256:140316427a7c384e3dd37efb3a73cd67e14b0b237a6d277def91227f43cdcec2", + "sha256:1559213b803959a2b8309122585b5226d1c2fb66c933b1a2094cf1e99cb4fb90", + "sha256:16a899b958dd76606b571bc7eaa38f09160c27dfb262e493584644cfd4a77f0f", + "sha256:1739935d293d0348d7bf662e8cd0edb9c2aa8f20ccd646db755ce0f3456d24e4", + "sha256:1a4835c177821f3729be27ae9be7b8ae209fe75e83db7d9b2bfd319a998f0a42", + "sha256:2b60d4abcb6a405ca7d909c80791b00637d22c62aa3bb0ffff7e589f763867f5", + "sha256:2ed779a896b70c8012fe301fb91ee37e713e1dda1eb8f37de04cdbf506706983", + "sha256:3ec2edf7f74829eae287aa53d64d83ad5d43ee51d29fb1d88e689d8b36028312", + "sha256:408bbf968c15e9e38df9f25a588e372e28a43240cf5884c9bc6039a5021b7d5b", + "sha256:4699bb671dbc4afdb544eb893e4deb8a34e294b7734733f65b4fd2787ba5fbc6", + "sha256:4eba6972b796d97c8fcc5266b6dc42ef27c2dce4421b846cded0f3af851b81c9", + "sha256:51092d13499be72e47c15c3a1ae0209edaca6be42b65ffbbefbe0c85f6153c6f", + "sha256:62319d57dab5ad3e3494dd97a214e22079d3f72a0c8a2fd001829c2c6abbf8d1", + "sha256:657fb7712185f82211170ac4debae0800ed4f5992b8f7ebba2a9eabaf133a857", + "sha256:66a0db13f77aa7806dba29273874cf862450c61c2e5158245d17ee85d983fe8e", + "sha256:6b98e098549d3aea2bfb93f38f0b2ecadcb423fa1504bbff902c01efdd833fd8", + "sha256:7127e2b94571318531caf098dc9e8f60f5aba1704600f0b2483bf151d535674a", + "sha256:798559837156b8e2e2df97cffca748c5c1432af6ec5004c2932e475d813f1743", + "sha256:802feae98addb9f21707649a7f229c90a59fad34511881f20b906a5e8e6ea475", + "sha256:89e1978c3fbe4e3d4c6ad7db7e6f982607cb2546f982ccbe42708392437b1972", + "sha256:9295ca10a140c21e40d2ee43ef423213dc20767f6cea6b87c36973564bc51095", + "sha256:9711ef291e184b5a73c9d3af3f2d5cfe25d571c8dd95aa498415f74ac7e221a8", + "sha256:b0320f882214f6ffde5992081520b57b55450510bdaa020e96aacff9b7ae10e6", + "sha256:b5bd3b3ff191f81509d9a1afd62e1e3cda7a7889c35b5b6359a1241fe1511015", + "sha256:baa19508d8445f5648cd1ffe4fc6d4f7daf8b876f804e9a453df6c3708f6200b", + "sha256:c5108ebe67da60a9204497d8d403316228deb52b550388190c53a57394d41531", + "sha256:ccea337fb9a44866c5300c594b13d4d87e827ebc3c353bff15d298bac976b654", + "sha256:cd73a16a759865831be5a8fb6546f2a908c8d7d7f55c75f94ee7c2ca13cc95de", + "sha256:d840712f4b4c7d2a119f993d7e43ca9bcaa73aeaa24c322fa2bdf4f689a3ee09", + "sha256:df26a09d955b3ab9b6bc18658b9403ed839096c97d7abe8806194e228a485a3c", + "sha256:e01382c06ac3710155a0ca923047c5abe03c676d08f03e146c6a240d0a910713", + "sha256:e572c67958f7d55eae77f5f64dc7bd31968cc9f24c233926833efe63c60545f2", + "sha256:eca6f59cd0729edaeaa7032d582dffce518a420d4961ef3e8c93dce86be352c3", + "sha256:efd2e12f8964f8fb4ba1984df71d85d02ef0531e687e59f78ec8fc07271a3857", + "sha256:efe9e8037b989b14bb1887089ae763385431cc06fe488406413079cfd2a3a089", + "sha256:f0d5b9b14ccc7f539143ac9eb1c6b57d26d69ca52d30c3d719a7bc4123579e44", + "sha256:f1954d71cdf15c19e7f3bf2235a4fe1600ba42f34d472c9495bcf54d75a43e4e", + "sha256:fbbceb0a0dfe9213f6314510665a32ef25fe29b50657567cd00115fbfcb3b20d" + ], + "index": "pypi", + "version": "==3.6.0" }, "mdit-py-plugins": { "hashes": [ @@ -2178,32 +2353,12 @@ }, "mypy": { "hashes": [ - "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655", - "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9", - "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3", - "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6", - "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0", - "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58", - "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103", - "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09", - "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417", - "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56", - "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2", - "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856", - "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0", - "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8", - "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27", - "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5", - "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71", - "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27", - "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe", - "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca", - "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf", - "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9", - "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c" - ], - "index": "pypi", - "version": "==0.971" + "sha256:794f385653e2b749387a42afb1e14c2135e18daeb027e0d97162e4b7031210f8", + "sha256:ad77c13037d3402fbeffda07d51e3f228ba078d1c7096a73759c9419ea031bf4", + "sha256:fa38f82f53e1e7beb45557ff167c177802ba7b387ad017eab1663d567017c8ee" + ], + "index": "pypi", + "version": "==0.981" }, "mypy-extensions": { "hashes": [ @@ -2222,11 +2377,11 @@ }, "natsort": { "hashes": [ - "sha256:c7c1f3f27c375719a4dfcab353909fe39f26c2032a062a8c80cc844eaaca0445", - "sha256:f59988d2f24e77b6b56f8a8f882d5df6b3b637e09e075abc67b486d59fba1a4b" + "sha256:04fe18fdd2b9e5957f19f687eb117f102ef8dde6b574764e536e91194bed4f5f", + "sha256:57f85b72c688b09e053cdac302dd5b5b53df5f73ae20b4874fcbffd8bf783d11" ], "index": "pypi", - "version": "==8.1.0" + "version": "==8.2.0" }, "nodeenv": { "hashes": [ @@ -2238,37 +2393,37 @@ }, "numpy": { "hashes": [ - "sha256:17e5226674f6ea79e14e3b91bfbc153fdf3ac13f5cc54ee7bc8fdbe820a32da0", - "sha256:2bd879d3ca4b6f39b7770829f73278b7c5e248c91d538aab1e506c628353e47f", - "sha256:4f41f5bf20d9a521f8cab3a34557cd77b6f205ab2116651f12959714494268b0", - "sha256:5593f67e66dea4e237f5af998d31a43e447786b2154ba1ad833676c788f37cde", - "sha256:5e28cd64624dc2354a349152599e55308eb6ca95a13ce6a7d5679ebff2962913", - "sha256:633679a472934b1c20a12ed0c9a6c9eb167fbb4cb89031939bfd03dd9dbc62b8", - "sha256:806970e69106556d1dd200e26647e9bee5e2b3f1814f9da104a943e8d548ca38", - "sha256:806cc25d5c43e240db709875e947076b2826f47c2c340a5a2f36da5bb10c58d6", - "sha256:8247f01c4721479e482cc2f9f7d973f3f47810cbc8c65e38fd1bbd3141cc9842", - "sha256:8ebf7e194b89bc66b78475bd3624d92980fca4e5bb86dda08d677d786fefc414", - "sha256:8ecb818231afe5f0f568c81f12ce50f2b828ff2b27487520d85eb44c71313b9e", - "sha256:8f9d84a24889ebb4c641a9b99e54adb8cab50972f0166a3abc14c3b93163f074", - "sha256:909c56c4d4341ec8315291a105169d8aae732cfb4c250fbc375a1efb7a844f8f", - "sha256:9b83d48e464f393d46e8dd8171687394d39bc5abfe2978896b77dc2604e8635d", - "sha256:ac987b35df8c2a2eab495ee206658117e9ce867acf3ccb376a19e83070e69418", - "sha256:b78d00e48261fbbd04aa0d7427cf78d18401ee0abd89c7559bbf422e5b1c7d01", - "sha256:b8b97a8a87cadcd3f94659b4ef6ec056261fa1e1c3317f4193ac231d4df70215", - "sha256:bd5b7ccae24e3d8501ee5563e82febc1771e73bd268eef82a1e8d2b4d556ae66", - "sha256:bdc02c0235b261925102b1bd586579b7158e9d0d07ecb61148a1799214a4afd5", - "sha256:be6b350dfbc7f708d9d853663772a9310783ea58f6035eec649fb9c4371b5389", - "sha256:c403c81bb8ffb1c993d0165a11493fd4bf1353d258f6997b3ee288b0a48fce77", - "sha256:cf8c6aed12a935abf2e290860af8e77b26a042eb7f2582ff83dc7ed5f963340c", - "sha256:d98addfd3c8728ee8b2c49126f3c44c703e2b005d4a95998e2167af176a9e722", - "sha256:dc76bca1ca98f4b122114435f83f1fcf3c0fe48e4e6f660e07996abf2f53903c", - "sha256:dec198619b7dbd6db58603cd256e092bcadef22a796f778bf87f8592b468441d", - "sha256:df28dda02c9328e122661f399f7655cdcbcf22ea42daa3650a26bce08a187450", - "sha256:e603ca1fb47b913942f3e660a15e55a9ebca906857edfea476ae5f0fe9b457d5", - "sha256:ecfdd68d334a6b97472ed032b5b37a30d8217c097acfff15e8452c710e775524" - ], - "index": "pypi", - "version": "==1.23.2" + "sha256:004f0efcb2fe1c0bd6ae1fcfc69cc8b6bf2407e0f18be308612007a0762b4089", + "sha256:09f6b7bdffe57fc61d869a22f506049825d707b288039d30f26a0d0d8ea05164", + "sha256:0ea3f98a0ffce3f8f57675eb9119f3f4edb81888b6874bc1953f91e0b1d4f440", + "sha256:17c0e467ade9bda685d5ac7f5fa729d8d3e76b23195471adae2d6a6941bd2c18", + "sha256:1f27b5322ac4067e67c8f9378b41c746d8feac8bdd0e0ffede5324667b8a075c", + "sha256:22d43376ee0acd547f3149b9ec12eec2f0ca4a6ab2f61753c5b29bb3e795ac4d", + "sha256:2ad3ec9a748a8943e6eb4358201f7e1c12ede35f510b1a2221b70af4bb64295c", + "sha256:301c00cf5e60e08e04d842fc47df641d4a181e651c7135c50dc2762ffe293dbd", + "sha256:39a664e3d26ea854211867d20ebcc8023257c1800ae89773cbba9f9e97bae036", + "sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd", + "sha256:78a63d2df1d947bd9d1b11d35564c2f9e4b57898aae4626638056ec1a231c40c", + "sha256:7cd1328e5bdf0dee621912f5833648e2daca72e3839ec1d6695e91089625f0b4", + "sha256:8355fc10fd33a5a70981a5b8a0de51d10af3688d7a9e4a34fcc8fa0d7467bb7f", + "sha256:8c79d7cf86d049d0c5089231a5bcd31edb03555bd93d81a16870aa98c6cfb79d", + "sha256:91b8d6768a75247026e951dce3b2aac79dc7e78622fc148329135ba189813584", + "sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8", + "sha256:98dcbc02e39b1658dc4b4508442a560fe3ca5ca0d989f0df062534e5ca3a5c1a", + "sha256:a64403f634e5ffdcd85e0b12c08f04b3080d3e840aef118721021f9b48fc1460", + "sha256:bc6e8da415f359b578b00bcfb1d08411c96e9a97f9e6c7adada554a0812a6cc6", + "sha256:bdc9febce3e68b697d931941b263c59e0c74e8f18861f4064c1f712562903411", + "sha256:c1ba66c48b19cc9c2975c0d354f24058888cdc674bebadceb3cdc9ec403fb5d1", + "sha256:c9f707b5bb73bf277d812ded9896f9512a43edff72712f31667d0a8c2f8e71ee", + "sha256:d5422d6a1ea9b15577a9432e26608c73a78faf0b9039437b075cf322c92e98e7", + "sha256:e5d5420053bbb3dd64c30e58f9363d7a9c27444c3648e61460c1237f9ec3fa14", + "sha256:e868b0389c5ccfc092031a861d4e158ea164d8b7fdbb10e3b5689b4fc6498df6", + "sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e", + "sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85", + "sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54" + ], + "index": "pypi", + "version": "==1.23.3" }, "opencv-python-headless": { "hashes": [ @@ -2293,30 +2448,36 @@ }, "pandas": { "hashes": [ - "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6", - "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d", - "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5", - "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76", - "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81", - "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1", - "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c", - "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf", - "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d", - "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e", - "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0", - "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230", - "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e", - "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84", - "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92", - "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f", - "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4", - "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640", - "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318", - "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef", - "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112" - ], - "index": "pypi", - "version": "==1.4.3" + "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484", + "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc", + "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8", + "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b", + "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989", + "sha256:207d63ac851e60ec57458814613ef4b3b6a5e9f0b33c57623ba2bf8126c311f8", + "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488", + "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f", + "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977", + "sha256:41aec9f87455306496d4486df07c1b98c15569c714be2dd552a6124cd9fda88f", + "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761", + "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c", + "sha256:62e61003411382e20d7c2aec1ee8d7c86c8b9cf46290993dd8a0a3be44daeb38", + "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515", + "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a", + "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a", + "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec", + "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3", + "sha256:947ed9f896ee61adbe61829a7ae1ade493c5a28c66366ec1de85c0642009faac", + "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b", + "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be", + "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d", + "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9", + "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8", + "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060", + "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009", + "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9" + ], + "index": "pypi", + "version": "==1.5.0" }, "parameterized": { "hashes": [ @@ -2351,6 +2512,7 @@ "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", + "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437", "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", @@ -2379,6 +2541,7 @@ "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", + "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc", "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", @@ -2556,11 +2719,11 @@ }, "pytest": { "hashes": [ - "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", - "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" + "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", + "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" ], "index": "pypi", - "version": "==7.1.2" + "version": "==7.1.3" }, "pytest-forked": { "hashes": [ @@ -2595,6 +2758,7 @@ }, "pyyaml": { "hashes": [ + "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", @@ -2606,26 +2770,32 @@ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], @@ -2678,11 +2848,11 @@ }, "setuptools": { "hashes": [ - "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", - "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57" + "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9", + "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1" ], "markers": "python_version >= '3.7'", - "version": "==65.3.0" + "version": "==65.4.0" }, "six": { "hashes": [ @@ -2708,11 +2878,11 @@ }, "sphinx": { "hashes": [ - "sha256:309a8da80cb6da9f4713438e5b55861877d5d7976b69d87e336733637ea12693", - "sha256:ba3224a4e206e1fbdecf98a4fae4992ef9b24b85ebf7b584bb340156eaf08d89" + "sha256:3dcf00fcf82cf91118db9b7177edea4fc01998976f893928d0ab0c58c54be2ca", + "sha256:c009bb2e9ac5db487bcf53f015504005a330ff7c631bb6ab2604e0d65bae8b54" ], "index": "pypi", - "version": "==5.1.1" + "version": "==5.2.1" }, "sphinx-rtd-theme": { "hashes": [ @@ -2797,11 +2967,11 @@ }, "tenacity": { "hashes": [ - "sha256:43242a20e3e73291a28bcbcacfd6e000b02d3857a9a9fff56b297a27afdc932f", - "sha256:f78f4ea81b0fabc06728c11dc2a8c01277bfc5181b321a4770471902e3eb844a" + "sha256:35525cd47f82830069f0d6b73f7eb83bc5b73ee2fff0437952cedf98b27653ac", + "sha256:e48c437fdf9340f5666b92cd7990e96bc5fc955e1298baf4a907e3972067a445" ], "index": "pypi", - "version": "==8.0.1" + "version": "==8.1.0" }, "toml": { "hashes": [ @@ -2819,6 +2989,53 @@ "markers": "python_version < '3.11'", "version": "==2.0.1" }, + "types-atomicwrites": { + "hashes": [ + "sha256:458a985b2cfaa963becec21ba63faaa5dd241e237ba8bf024732b37b18690de6", + "sha256:ef824f7e639c178e3e571f60d228c7745198756ebfcc5d249a0e3e02e04b2858" + ], + "index": "pypi", + "version": "==1.4.5" + }, + "types-certifi": { + "hashes": [ + "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f", + "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a" + ], + "index": "pypi", + "version": "==2021.10.8.3" + }, + "types-pycurl": { + "hashes": [ + "sha256:82e00aa2981595bfa55e5a3bac42221eb3435b0026dffbe1177f6ac9f2d51200", + "sha256:9eab3414da4a1b1e9a628bd288fc5172b8c182e1d9fb6d8d082441b0fd64baed" + ], + "index": "pypi", + "version": "==7.45.1" + }, + "types-pyyaml": { + "hashes": [ + "sha256:7f7da2fd11e9bc1e5e9eb3ea1be84f4849747017a59fc2eee0ea34ed1147c2e0", + "sha256:8f890028123607379c63550179ddaec4517dc751f4c527a52bb61934bf495989" + ], + "index": "pypi", + "version": "==6.0.11" + }, + "types-requests": { + "hashes": [ + "sha256:7ee827eb8ce611b02b5117cfec5da6455365b6a575f5e3ff19f655ba603e6b4e", + "sha256:af5f55e803cabcfb836dad752bd6d8a0fc8ef1cd84243061c0e27dee04ccf4fd" + ], + "index": "pypi", + "version": "==2.28.11" + }, + "types-urllib3": { + "hashes": [ + "sha256:a1b3aaea7dda3eb1b51699ee723aadd235488e4dc4648e030f09bc429ecff42f", + "sha256:cf7918503d02d3576e503bbfb419b0e047c4617653bba09624756ab7175e15c9" + ], + "version": "==1.26.24" + }, "typing-extensions": { "hashes": [ "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", @@ -2837,11 +3054,11 @@ }, "virtualenv": { "hashes": [ - "sha256:4193b7bc8a6cd23e4eb251ac64f29b4398ab2c233531e66e40b19a6b7b0d30c1", - "sha256:d86ea0bb50e06252d79e6c241507cb904fcd66090c3271381372d6221a3970f9" + "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da", + "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27" ], "markers": "python_version >= '3.6'", - "version": "==20.16.3" + "version": "==20.16.5" }, "zipp": { "hashes": [ diff --git a/common/realtime.py b/common/realtime.py index 8a79d8d39f..7dd2eb98a6 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -31,7 +31,7 @@ class Priority: def set_realtime_priority(level: int) -> None: if not PC: - os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level)) # type: ignore[attr-defined] # pylint: disable=no-member + os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level)) # pylint: disable=no-member def set_core_affinity(cores: List[int]) -> None: diff --git a/mypy.ini b/mypy.ini index e2da60f926..39b1b007a7 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,4 +1,16 @@ [mypy] python_version = 3.8 -ignore_missing_imports = True plugins = numpy.typing.mypy_plugin +files = body, common, docs, scripts, selfdrive, site_scons, system, tools +exclude = ^(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/) + +; third-party packages +ignore_missing_imports = True + +; helpful warnings +warn_redundant_casts = True +warn_unreachable = True +warn_unused_ignores = True + +; restrict dynamic typing +warn_return_any = True diff --git a/selfdrive/debug/check_freq.py b/selfdrive/debug/check_freq.py index b6f3c91bd0..6436abb4f1 100755 --- a/selfdrive/debug/check_freq.py +++ b/selfdrive/debug/check_freq.py @@ -2,7 +2,7 @@ import argparse import numpy as np from collections import defaultdict, deque -from typing import DefaultDict, Deque +from typing import DefaultDict, Deque, MutableSequence from common.realtime import sec_since_boot import cereal.messaging as messaging @@ -19,7 +19,7 @@ if __name__ == "__main__": socket_names = args.socket sockets = {} - rcv_times: DefaultDict[str, Deque[float]] = defaultdict(lambda: deque(maxlen=100)) + rcv_times: DefaultDict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100)) valids: DefaultDict[str, Deque[bool]] = defaultdict(lambda: deque(maxlen=100)) t = sec_since_boot() diff --git a/selfdrive/debug/check_timings.py b/selfdrive/debug/check_timings.py index 69304f97b5..083e084ca7 100755 --- a/selfdrive/debug/check_timings.py +++ b/selfdrive/debug/check_timings.py @@ -3,13 +3,13 @@ import sys import time import numpy as np -from typing import DefaultDict, Deque +from typing import DefaultDict, MutableSequence from collections import defaultdict, deque import cereal.messaging as messaging socks = {s: messaging.sub_sock(s, conflate=False) for s in sys.argv[1:]} -ts: DefaultDict[str, Deque[float]] = defaultdict(lambda: deque(maxlen=100)) +ts: DefaultDict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100)) if __name__ == "__main__": while True: diff --git a/system/hardware/tici/casync.py b/system/hardware/tici/casync.py index 8ae42fa714..993336616d 100755 --- a/system/hardware/tici/casync.py +++ b/system/hardware/tici/casync.py @@ -86,6 +86,7 @@ class RemoteChunkReader(ChunkReader): def parse_caibx(caibx_path: str) -> List[Chunk]: """Parses the chunks from a caibx file. Can handle both local and remote files. Returns a list of chunks with hash, offset and length""" + caibx: io.BufferedIOBase if os.path.isfile(caibx_path): caibx = open(caibx_path, 'rb') else: From 1c6dc12a04618ccd660952fe1a15452898d2c593 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 27 Sep 2022 21:32:21 -0700 Subject: [PATCH 019/178] camerad: improved frame sync and skip tests (#25904) * camerad: cleanup frame sync and skip tests * fix linter Co-authored-by: Comma Device --- system/camerad/test/test_camerad.py | 87 +++++++++++++++++------------ 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 1a2e365a8f..378d4b7058 100755 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -1,25 +1,18 @@ #!/usr/bin/env python3 - import time import unittest +from collections import defaultdict import cereal.messaging as messaging +from cereal.services import service_list +from selfdrive.manager.process_config import managed_processes from system.hardware import TICI -from selfdrive.test.helpers import with_processes -TEST_TIMESPAN = 30 # random.randint(60, 180) # seconds -SKIP_FRAME_TOLERANCE = 0 -LAG_FRAME_TOLERANCE = 2 # ms +TEST_TIMESPAN = 30 +LAG_FRAME_TOLERANCE = 0.5 # ms -FPS_BASELINE = 20 -CAMERAS = { - "roadCameraState": FPS_BASELINE, - "driverCameraState": FPS_BASELINE // 2, -} +CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') -if TICI: - CAMERAS["driverCameraState"] = FPS_BASELINE - CAMERAS["wideRoadCameraState"] = FPS_BASELINE class TestCamerad(unittest.TestCase): @classmethod @@ -27,37 +20,57 @@ class TestCamerad(unittest.TestCase): if not TICI: raise unittest.SkipTest - @with_processes(['camerad']) - def test_frame_packets(self): - print("checking frame pkts continuity") - print(TEST_TIMESPAN) + # run camerad and record logs + managed_processes['camerad'].start() + time.sleep(3) + socks = {c: messaging.sub_sock(c, conflate=False, timeout=100) for c in CAMERAS} + + cls.logs = defaultdict(list) + start_time = time.monotonic() + while time.monotonic()- start_time < TEST_TIMESPAN: + for cam, s in socks.items(): + cls.logs[cam] += messaging.drain_sock(s) + time.sleep(0.2) + managed_processes['camerad'].stop() - sm = messaging.SubMaster([socket_name for socket_name in CAMERAS]) + cls.log_by_frame_id = defaultdict(list) + for cam, msgs in cls.logs.items(): + expected_frames = service_list[cam].frequency * TEST_TIMESPAN + assert expected_frames*0.95 < len(msgs) < expected_frames*1.05, f"unexpected frame count {cam}: {expected_frames=}, got {len(msgs)}" - last_frame_id = dict.fromkeys(CAMERAS, None) - last_ts = dict.fromkeys(CAMERAS, None) - start_time_sec = time.time() - while time.time()- start_time_sec < TEST_TIMESPAN: - sm.update() + for m in msgs: + cls.log_by_frame_id[getattr(m, m.which()).frameId].append(m) - for camera in CAMERAS: - if sm.updated[camera]: - ct = (sm[camera].timestampEof if not TICI else sm[camera].timestampSof) / 1e6 - if last_frame_id[camera] is None: - last_frame_id[camera] = sm[camera].frameId - last_ts[camera] = ct - continue + # strip beginning and end + for _ in range(3): + mn, mx = min(cls.log_by_frame_id.keys()), max(cls.log_by_frame_id.keys()) + del cls.log_by_frame_id[mn] + del cls.log_by_frame_id[mx] + + @classmethod + def tearDownClass(cls): + managed_processes['camerad'].stop() - dfid = sm[camera].frameId - last_frame_id[camera] - self.assertTrue(abs(dfid - 1) <= SKIP_FRAME_TOLERANCE, "%s frame id diff is %d" % (camera, dfid)) + def test_frame_skips(self): + skips = {} + frame_ids = self.log_by_frame_id.keys() + for frame_id in range(min(frame_ids), max(frame_ids)): + seen_cams = [msg.which() for msg in self.log_by_frame_id[frame_id]] + skip_cams = set(CAMERAS) - set(seen_cams) + if len(skip_cams): + skips[frame_id] = skip_cams + assert len(skips) == 0, f"Found frame skips, missing cameras for the following frames: {skips}" - dts = ct - last_ts[camera] - self.assertTrue(abs(dts - (1000/CAMERAS[camera])) < LAG_FRAME_TOLERANCE, f"{camera} frame t(ms) diff is {dts:f}") + def test_frame_sync(self): + frame_times = {frame_id: [getattr(m, m.which()).timestampSof for m in msgs] for frame_id, msgs in self.log_by_frame_id.items()} + diffs = {frame_id: (max(ts) - min(ts))/1e6 for frame_id, ts in frame_times.items()} - last_frame_id[camera] = sm[camera].frameId - last_ts[camera] = ct - time.sleep(0.01) + def get_desc(fid, diff): + cam_times = [(m.which(), getattr(m, m.which()).timestampSof/1e6) for m in self.log_by_frame_id[fid]] + return f"{diff=} {cam_times=}" + laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE} + assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" if __name__ == "__main__": unittest.main() From b944a22b9b05300dce3c3d122cf51ec7d0ec28b6 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Tue, 27 Sep 2022 22:53:50 -0700 Subject: [PATCH 020/178] exclude opendbc and xx from mypy pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1eb5f632b9..b1696e823c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: entry: mypy language: system types: [python] - exclude: '^(pyextra/)|(cereal/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' + exclude: '^(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: From 1007df874f284f2c01a48c245f48c209ed806957 Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Wed, 28 Sep 2022 13:27:07 -0700 Subject: [PATCH 021/178] Minor ACC fixes (#25911) * Change cruise accel limits * Long tuning script looks good * Cap cruise slowdown aggression in e2e mode * Revert atau change * Cleanup * Update ref * fix ref --- selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 5 +++-- selfdrive/controls/lib/longitudinal_planner.py | 4 ++-- selfdrive/controls/lib/radar_helpers.py | 3 +-- selfdrive/test/longitudinal_maneuvers/plant.py | 5 +++-- selfdrive/test/process_replay/ref_commit | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index ea9b0683a4..057cf5623f 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -320,7 +320,7 @@ class LongitudinalMpc: # Update in ACC mode or ACC/e2e blend if self.mode == 'acc': - self.params[:,0] = MIN_ACCEL if self.status else self.cruise_min_a + self.params[:,0] = MIN_ACCEL self.params[:,1] = self.cruise_max_a self.params[:,5] = LEAD_DANGER_FACTOR @@ -341,11 +341,12 @@ class LongitudinalMpc: elif self.mode == 'blended': self.params[:,0] = MIN_ACCEL self.params[:,1] = MAX_ACCEL + self.params[:,5] = 1.0 x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle]) - cruise_target = T_IDXS * v_cruise + x[0] + cruise_target = T_IDXS * np.clip(v_cruise, v_ego - 2.0, 1e3) + x[0] xforward = ((v[1:] + v[:-1]) / 2) * (T_IDXS[1:] - T_IDXS[:-1]) x = np.cumsum(np.insert(xforward, 0, x[0])) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index b9b16e34fc..45a6db2e90 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -18,8 +18,8 @@ from system.swaglog import cloudlog 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 A_CRUISE_MIN = -1.2 -A_CRUISE_MAX_VALS = [1.2, 1.2, 0.8, 0.6] -A_CRUISE_MAX_BP = [0., 15., 25., 40.] +A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6] +A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] # Lookup table for turns _A_TOTAL_MAX_V = [1.7, 3.2] diff --git a/selfdrive/controls/lib/radar_helpers.py b/selfdrive/controls/lib/radar_helpers.py index 0e2b96668f..bf788190cd 100644 --- a/selfdrive/controls/lib/radar_helpers.py +++ b/selfdrive/controls/lib/radar_helpers.py @@ -2,8 +2,7 @@ from common.numpy_fast import mean from common.kalman.simple_kalman import KF1D -# the longer lead decels, the more likely it will keep decelerating -# TODO is this a good default? +# Default lead acceleration decay set to 50% at 1s _LEAD_ACCEL_TAU = 1.5 # radar tracks diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index 21af1cd3b1..e81510e9ba 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -8,7 +8,7 @@ from common.realtime import Ratekeeper, DT_MDL from selfdrive.controls.lib.longcontrol import LongCtrlState from selfdrive.modeld.constants import T_IDXS from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner - +from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU class Plant(): messaging_initialized = False @@ -83,7 +83,8 @@ class Plant(): lead.vLead = float(v_lead) lead.vLeadK = float(v_lead) lead.aLeadK = float(a_lead) - lead.aLeadTau = float(1.5) + # TODO use real radard logic for this + lead.aLeadTau = float(_LEAD_ACCEL_TAU) lead.status = status lead.modelProb = float(prob) if not self.only_lead2: diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c7daecd5a3..98fbb74ae4 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -a82580edc3a842dd58814bf2fe1a0f0f85d438f5 \ No newline at end of file +9098c1cf6993598071c3e27448356eef86660d02 From 0ec8546042091f97072edcf8f5218627ea8a75a9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 28 Sep 2022 14:01:17 -0700 Subject: [PATCH 022/178] vin and ecu_addrs: add more argparse options (#25913) * add bus options * use debug * add timeout and try * fix * don't need keywords * log this too * fix --- selfdrive/car/ecu_addrs.py | 7 ++++--- selfdrive/car/vin.py | 12 +++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index 267701509a..9f6ace2b5f 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import capnp import time -import traceback from typing import Optional, Set, Tuple import cereal.messaging as messaging @@ -62,7 +61,7 @@ def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, que print(f"Duplicate ECU address: {hex(msg.address)}") ecu_responses.add((msg.address, subaddr, msg.src)) except Exception: - cloudlog.warning(f"ECU addr scan exception: {traceback.format_exc()}") + cloudlog.exception("ECU addr scan exception") return ecu_responses @@ -71,6 +70,8 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description='Get addresses of all ECUs') parser.add_argument('--debug', action='store_true') + parser.add_argument('--bus', type=int, default=1) + parser.add_argument('--timeout', type=float, default=1.0) args = parser.parse_args() logcan = messaging.sub_sock('can') @@ -79,7 +80,7 @@ if __name__ == "__main__": time.sleep(1.0) print("Getting ECU addresses ...") - ecu_addrs = get_all_ecu_addrs(logcan, sendcan, 1, debug=args.debug) + ecu_addrs = get_all_ecu_addrs(logcan, sendcan, args.bus, args.timeout, debug=args.debug) print() print("Found ECUs on addresses:") diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 909322f9c9..860f3b0fa2 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -35,9 +35,19 @@ def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False): if __name__ == "__main__": + import argparse import time + + parser = argparse.ArgumentParser(description='Get VIN of the car') + parser.add_argument('--debug', action='store_true') + parser.add_argument('--bus', type=int, default=1) + parser.add_argument('--timeout', type=float, default=0.1) + parser.add_argument('--retry', type=int, default=5) + args = parser.parse_args() + sendcan = messaging.pub_sock('sendcan') logcan = messaging.sub_sock('can') time.sleep(1) - addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, debug=False) + + addr, vin_rx_addr, vin = get_vin(logcan, sendcan, args.bus, args.timeout, args.retry, debug=args.debug) print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}') From 685533a3a121b3cc50e8a0b6402547999d86829b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 28 Sep 2022 14:06:34 -0700 Subject: [PATCH 023/178] Subaru: Add missing FW for 2021 Legacy (#25914) Add missing 2021 Legacy FW from 9936b4bbf75a5fe4|2022-09-26--20-29-47 --- selfdrive/car/subaru/values.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 6c3a35307e..da5ff1785a 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -124,23 +124,26 @@ FW_VERSIONS = { CAR.LEGACY: { (Ecu.abs, 0x7b0, None): [ b'\xa1\\ x04\x01', - b'\xa1 \x03\x03' + b'\xa1 \x03\x03', + b'\xa1 \x02\x01', ], (Ecu.eps, 0x746, None): [ b'\x9b\xc0\x11\x00', - b'\x9b\xc0\x11\x02' + b'\x9b\xc0\x11\x02', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00e\x80\x00\x1f@ \x19\x00', - b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00' + b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xde\"a0\x07', - b'\xe2"aq\x07' + b'\xe2"aq\x07', + b'\xde,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xa5\xf6\x05@\x00', - b'\xa7\xf6\x04@\x00' + b'\xa7\xf6\x04@\x00', + b'\xa5\xfe\xc7@\x00', ], }, CAR.IMPREZA: { From 42210a99104d83d19cf7c426433721e1bf4ad1eb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 28 Sep 2022 14:44:05 -0700 Subject: [PATCH 024/178] multilang: nav instructions (#25102) * Try Chinese nav * try this * not sure what does what * read language setting in navd * probably not used Co-authored-by: Willem Melching --- selfdrive/navd/navd.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index 72874b2113..4855b63594 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -120,6 +120,10 @@ class RouteEngine: cloudlog.warning(f"Calculating route {self.last_position} -> {destination}") self.nav_destination = destination + lang = self.params.get('LanguageSetting', encoding='utf8') + if lang is not None: + lang = lang.replace('main_', '') + params = { 'access_token': self.mapbox_token, 'annotations': 'maxspeed', @@ -128,6 +132,7 @@ class RouteEngine: 'steps': 'true', 'banner_instructions': 'true', 'alternatives': 'false', + 'language': lang, } if self.last_bearing is not None: From 2384a9ee67fc4480199149657a031510f9f48d14 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 28 Sep 2022 15:45:15 -0700 Subject: [PATCH 025/178] fix autobrightness for OX03C10 (#25915) * fix autobrightness for OX03C10 * fix scaling * use cur ev * oops * bump cereal --- cereal | 2 +- selfdrive/ui/ui.cc | 13 ++----------- system/camerad/cameras/camera_common.cc | 4 ++++ system/camerad/cameras/camera_qcom2.cc | 13 ++++++------- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/cereal b/cereal index e310f4860d..5aa49864bc 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit e310f4860d349d2e260cfd4bb060b0705b17244c +Subproject commit 5aa49864bce38f520705b6ed0b98e7cf9560ed0a diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 2722b68f17..b9fdaf329e 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -178,15 +178,7 @@ static void update_state(UIState *s) { } } if (sm.updated("wideRoadCameraState")) { - auto camera_state = sm["wideRoadCameraState"].getWideRoadCameraState(); - - float max_lines = 1618; - float max_gain = 10.0; - float max_ev = max_lines * max_gain / 6; - - float ev = camera_state.getGain() * float(camera_state.getIntegLines()); - - scene.light_sensor = std::clamp(1.0 - (ev / max_ev), 0.0, 1.0); + scene.light_sensor = 100.0f - sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(); } scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; } @@ -286,8 +278,7 @@ void Device::resetInteractiveTimout() { void Device::updateBrightness(const UIState &s) { float clipped_brightness = BACKLIGHT_OFFROAD; if (s.scene.started) { - // Scale to 0% to 100% - clipped_brightness = 100.0 * s.scene.light_sensor; + clipped_brightness = s.scene.light_sensor; // CIE 1931 - https://www.photonstophotos.net/GeneralTopics/Exposure/Psychometric_Lightness_and_Gamma.htm if (clipped_brightness <= 8) { diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 580c4bc5ee..30e2810ec4 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -162,6 +162,10 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setLensTruePos(frame_data.lens_true_pos); framed.setProcessingTime(frame_data.processing_time); + const float ev = c->cur_ev[frame_data.frame_id % 3]; + const float perc = util::map_val(ev, c->min_ev, c->max_ev, 0.0f, 100.0f); + framed.setExposureValPercent(perc); + if (c->camera_id == CAMERA_ID_AR0231) { framed.setSensor(cereal::FrameData::ImageSensor::AR0321); } else if (c->camera_id == CAMERA_ID_OX03C10) { diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 65a0de0a6f..78848af5be 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -1078,8 +1078,8 @@ void CameraState::set_camera_exposure(float grey_frac) { std::string gain_bytes, time_bytes; if (env_ctrl_exp_from_params) { - gain_bytes = Params().get("CameraDebugExpGain"); - time_bytes = Params().get("CameraDebugExpTime"); + gain_bytes = Params().get("CameraDebugExpGain"); + time_bytes = Params().get("CameraDebugExpTime"); } if (gain_bytes.size() > 0 && time_bytes.size() > 0) { @@ -1150,10 +1150,10 @@ void CameraState::set_camera_exposure(float grey_frac) { if (camera_id == CAMERA_ID_AR0231) { uint16_t analog_gain_reg = 0xFF00 | (new_g << 4) | new_g; struct i2c_random_wr_payload exp_reg_array[] = { - {0x3366, analog_gain_reg}, - {0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)}, - {0x3012, (uint16_t)exposure_time}, - }; + {0x3366, analog_gain_reg}, + {0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)}, + {0x3012, (uint16_t)exposure_time}, + }; sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); } else if (camera_id == CAMERA_ID_OX03C10) { // t_HCG + t_LCG + t_VS on LPD, t_SPD on SPD @@ -1166,7 +1166,6 @@ void CameraState::set_camera_exposure(float grey_frac) { uint32_t real_gain = ox03c10_analog_gains_reg[new_g]; uint32_t min_gain = ox03c10_analog_gains_reg[0]; struct i2c_random_wr_payload exp_reg_array[] = { - {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, {0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, {0x3541, spd_time>>8}, {0x3542, spd_time&0xFF}, From 17ed8dd0e9a8ecd0dcf8b573176ea27355c6a1ee Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 28 Sep 2022 16:08:32 -0700 Subject: [PATCH 026/178] updated: configure branch upstream (#25916) --- selfdrive/updated.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/updated.py b/selfdrive/updated.py index f46cda3204..675c2c1a36 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -339,7 +339,7 @@ class Updater: excluded_branches = ('release2', 'release2-staging', 'dashcam', 'dashcam-staging') setup_git_options(OVERLAY_MERGED) - output = run(["git", "ls-remote", "--heads"], OVERLAY_MERGED) + output = run(["git", "ls-remote", "--heads", "origin"], OVERLAY_MERGED) self.branches = defaultdict(lambda: None) for line in output.split('\n'): @@ -375,6 +375,7 @@ class Updater: cloudlog.info("git reset in progress") cmds = [ ["git", "checkout", "--force", "--no-recurse-submodules", "-B", branch, "FETCH_HEAD"], + ["git", "branch", "--set-upstream-to", f"origin/{branch}"], ["git", "reset", "--hard"], ["git", "clean", "-xdf"], ["git", "submodule", "init"], From 4e32387ce5b916df1a62ddb695c0cd10ba6f4f38 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 28 Sep 2022 16:33:42 -0700 Subject: [PATCH 027/178] updated: clean untracked nested git repos (#25917) * updated: clean untracked nested git repos * ff all the cleans --- release/build_devel.sh | 4 ++-- scripts/switch_to_master.sh | 16 ---------------- selfdrive/test/setup_device_ci.sh | 4 ++-- selfdrive/updated.py | 2 +- 4 files changed, 5 insertions(+), 21 deletions(-) delete mode 100755 scripts/switch_to_master.sh diff --git a/release/build_devel.sh b/release/build_devel.sh index f06e3102c8..fc3f8184a2 100755 --- a/release/build_devel.sh +++ b/release/build_devel.sh @@ -32,7 +32,7 @@ git checkout -f --track origin/master-ci git reset --hard master-ci git checkout master-ci git reset --hard origin/devel -git clean -xdf +git clean -xdff git lfs uninstall # remove everything except .git @@ -41,7 +41,7 @@ find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm - # reset source tree cd $SOURCE_DIR -git clean -xdf +git clean -xdff # do the files copy echo "[-] copying files T=$SECONDS" diff --git a/scripts/switch_to_master.sh b/scripts/switch_to_master.sh deleted file mode 100755 index cad51eb549..0000000000 --- a/scripts/switch_to_master.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd $DIR/.. - -git clean -xdf . -git rm -r --cached . - -git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" -git fetch origin master -git checkout master -git reset --hard -git submodule update --init - -printf '\n\n' -echo "master checked out. reboot to start building openpilot master" diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index bf2f93e1c3..a9c6527970 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -59,9 +59,9 @@ git fetch --verbose origin $GIT_COMMIT find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; git reset --hard $GIT_COMMIT git checkout $GIT_COMMIT -git clean -xdf +git clean -xdff git submodule update --init --recursive -git submodule foreach --recursive "git reset --hard && git clean -xdf" +git submodule foreach --recursive "git reset --hard && git clean -xdff" git lfs pull (ulimit -n 65535 && git lfs prune) diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 675c2c1a36..331ee6e4af 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -377,7 +377,7 @@ class Updater: ["git", "checkout", "--force", "--no-recurse-submodules", "-B", branch, "FETCH_HEAD"], ["git", "branch", "--set-upstream-to", f"origin/{branch}"], ["git", "reset", "--hard"], - ["git", "clean", "-xdf"], + ["git", "clean", "-xdff"], ["git", "submodule", "init"], ["git", "submodule", "update"], ] From 2e5b50c2d7c09accaa51e954790a478b125af591 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 29 Sep 2022 08:35:55 +0800 Subject: [PATCH 028/178] test_proclog: add cmdline check in buildProcLogerMessage (#25891) --- system/proclogd/tests/test_proclog.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/system/proclogd/tests/test_proclog.cc b/system/proclogd/tests/test_proclog.cc index 230e855acb..affde2f320 100644 --- a/system/proclogd/tests/test_proclog.cc +++ b/system/proclogd/tests/test_proclog.cc @@ -140,6 +140,18 @@ TEST_CASE("buildProcLogerMessage") { REQUIRE(p.getName() == "test_proclog"); REQUIRE(p.getState() == 'R'); REQUIRE_THAT(p.getExe().cStr(), Catch::Matchers::Contains("test_proclog")); + REQUIRE(p.getCmdline().size() == 1); + REQUIRE_THAT(p.getCmdline()[0], Catch::Matchers::Contains("test_proclog")); + } else { + std::string cmd_path = "/proc/" + std::to_string(p.getPid()) + "/cmdline"; + if (util::file_exists(cmd_path)) { + std::ifstream stream(cmd_path); + auto cmdline = Parser::cmdline(stream); + REQUIRE(cmdline.size() == p.getCmdline().size()); + for (int i = 0; i < p.getCmdline().size(); ++i) { + REQUIRE(cmdline[i] == p.getCmdline()[i].cStr()); + } + } } } } From 47de20ffb58d29cc77ff7f01c0932145247f3da8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 28 Sep 2022 18:56:12 -0700 Subject: [PATCH 029/178] update release notes --- RELEASES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index dcea1e72cc..35ed1b9520 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,13 +3,14 @@ Version 0.8.17 (2022-XX-XX) * New driving model * Internal feature space accuracy increased tenfold during training, this makes the model dramatically more accurate. * Self-tuning torque lateral controller parameters - * Parameters are learned live for each car + * Parameters learned live for each car * Enabled only on Toyota Corolla for now * UI updates + * Multi-language in navigation * Matched speeds shown on car's dash * Improved update experience * Border turns grey while overriding steering - * Added button to flag events that are shown in comma connect + * Added button to bookmark events while driving; view them later in comma connect * AGNOS 6 Version 0.8.16 (2022-08-26) From 51e296733d196dcc504607571460e671cc2861e8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 28 Sep 2022 20:19:47 -0700 Subject: [PATCH 030/178] test_sensord: get 10s of events --- selfdrive/sensord/tests/test_sensord.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/sensord/tests/test_sensord.py b/selfdrive/sensord/tests/test_sensord.py index b4a7aef343..60fe8b06d4 100755 --- a/selfdrive/sensord/tests/test_sensord.py +++ b/selfdrive/sensord/tests/test_sensord.py @@ -115,9 +115,9 @@ class TestSensord(unittest.TestCase): # read initial sensor values every test case can use os.system("pkill -f ./_sensord") - cls.sample_secs = 5 managed_processes["sensord"].start() - time.sleep(2) + time.sleep(3) + cls.sample_secs = 10 cls.events = read_sensor_events(cls.sample_secs) managed_processes["sensord"].stop() From 683096e309e9e88cc0c5af9da3df70ce62279324 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Wed, 28 Sep 2022 21:38:31 -0700 Subject: [PATCH 031/178] update replay refs --- selfdrive/test/process_replay/ref_commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 98fbb74ae4..c17e6afbd1 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -9098c1cf6993598071c3e27448356eef86660d02 +51e296733d196dcc504607571460e671cc2861e8 \ No newline at end of file From 03977a8783726038cdeae16e2115e3b9d1f47ab8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 28 Sep 2022 21:42:58 -0700 Subject: [PATCH 032/178] Revert "update replay refs" This reverts commit 683096e309e9e88cc0c5af9da3df70ce62279324. --- selfdrive/test/process_replay/ref_commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c17e6afbd1..98fbb74ae4 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -51e296733d196dcc504607571460e671cc2861e8 \ No newline at end of file +9098c1cf6993598071c3e27448356eef86660d02 From 391780551a362faf9c6c290d47313583d14aa594 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 29 Sep 2022 11:02:27 -0700 Subject: [PATCH 033/178] UI: remove imu tap detection (#25924) --- selfdrive/ui/ui.cc | 32 ++------------------------------ selfdrive/ui/ui.h | 2 +- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index b9fdaf329e..fcee00d1c8 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -162,21 +162,6 @@ static void update_state(UIState *s) { if (sm.updated("carParams")) { scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); } - if (!scene.started && sm.updated("sensorEvents")) { - for (auto sensor : sm["sensorEvents"].getSensorEvents()) { - if (sensor.which() == cereal::SensorEventData::ACCELERATION) { - auto accel = sensor.getAcceleration().getV(); - if (accel.totalSize().wordCount) { // TODO: sometimes empty lists are received. Figure out why - scene.accel_sensor = accel[2]; - } - } else if (sensor.which() == cereal::SensorEventData::GYRO_UNCALIBRATED) { - auto gyro = sensor.getGyroUncalibrated().getV(); - if (gyro.totalSize().wordCount) { - scene.gyro_sensor = gyro[1]; - } - } - } - } if (sm.updated("wideRoadCameraState")) { scene.light_sensor = 100.0f - sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(); } @@ -221,7 +206,7 @@ void UIState::updateStatus() { UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", - "pandaStates", "carParams", "driverMonitoringState", "sensorEvents", "carState", "liveLocationKalman", + "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements", }); @@ -304,24 +289,11 @@ void Device::updateBrightness(const UIState &s) { } } -bool Device::motionTriggered(const UIState &s) { - static float accel_prev = 0; - static float gyro_prev = 0; - - bool accel_trigger = abs(s.scene.accel_sensor - accel_prev) > 0.2; - bool gyro_trigger = abs(s.scene.gyro_sensor - gyro_prev) > 0.15; - - gyro_prev = s.scene.gyro_sensor; - accel_prev = (accel_prev * (accel_samples - 1) + s.scene.accel_sensor) / accel_samples; - - return (!awake && accel_trigger && gyro_trigger); -} - void Device::updateWakefulness(const UIState &s) { bool ignition_just_turned_off = !s.scene.ignition && ignition_on; ignition_on = s.scene.ignition; - if (ignition_just_turned_off || motionTriggered(s)) { + if (ignition_just_turned_off) { resetInteractiveTimout(); } else if (interactive_timeout > 0 && --interactive_timeout == 0) { emit interactiveTimout(); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index b4ec900eef..887b7ee841 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -100,7 +100,7 @@ typedef struct UIScene { // lead QPointF lead_vertices[2]; - float light_sensor, accel_sensor, gyro_sensor; + float light_sensor; bool started, ignition, is_metric, map_on_left, longitudinal_control, end_to_end_long; uint64_t started_frame; } UIScene; From badecfd060bc4fe2265a680c9a42f9a9afa1647a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 30 Sep 2022 02:30:12 +0800 Subject: [PATCH 034/178] boardd: remove global variable pigeon_active (#25926) --- selfdrive/boardd/boardd.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 9819944af8..09e7137b38 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -56,7 +56,6 @@ using namespace std::chrono_literals; std::atomic ignition(false); -std::atomic pigeon_active(false); ExitHandler do_exit; @@ -346,7 +345,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector } #ifndef __x86_64__ - bool power_save_desired = !ignition_local && !pigeon_active; + bool power_save_desired = !ignition_local; if (health.power_save_enabled_pkt != power_save_desired) { panda->set_power_saving(power_save_desired); } From 74f741d0be89c9a03573d47b00d59be964884d5f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 29 Sep 2022 13:18:01 -0700 Subject: [PATCH 035/178] agnos updater: set timeout on download (#25927) --- system/hardware/tici/agnos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/hardware/tici/agnos.py b/system/hardware/tici/agnos.py index 51998bf8b7..5f446a8e90 100755 --- a/system/hardware/tici/agnos.py +++ b/system/hardware/tici/agnos.py @@ -20,7 +20,7 @@ class StreamingDecompressor: def __init__(self, url: str) -> None: self.buf = b"" - self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}) # type: ignore # pylint: disable=missing-timeout + self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}, timeout=60) # type: ignore self.it = self.req.iter_content(chunk_size=1024 * 1024) self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO) self.eof = False From 7df0e3efcfd2926405f5eaa872d03a652f9c66f9 Mon Sep 17 00:00:00 2001 From: Lee Jong Mun <43285072+crwusiz@users.noreply.github.com> Date: Fri, 30 Sep 2022 05:52:41 +0900 Subject: [PATCH 036/178] remove blank whitespace (#25921) --- common/modeldata.h | 2 +- selfdrive/controls/controlsd.py | 4 ++-- selfdrive/controls/lib/drive_helpers.py | 8 ++++---- selfdrive/controls/lib/longcontrol.py | 4 ---- selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 3 +-- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/common/modeldata.h b/common/modeldata.h index e13840d53e..a00d3d49d3 100644 --- a/common/modeldata.h +++ b/common/modeldata.h @@ -4,7 +4,7 @@ #include "common/mat.h" #include "system/hardware/hw.h" -const int TRAJECTORY_SIZE = 33; +const int TRAJECTORY_SIZE = 33; const int LAT_MPC_N = 16; const int LON_MPC_N = 32; const float MIN_DRAW_DISTANCE = 10.0; diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 5639a1f6c7..b6479e5608 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -595,7 +595,7 @@ class Controls: CC.enabled = self.enabled # Check which actuators can be enabled CC.latActive = self.active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ - CS.vEgo > self.CP.minSteerSpeed and not CS.standstill + CS.vEgo > self.CP.minSteerSpeed and not CS.standstill CC.longActive = self.active and not self.events.any(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl actuators = CC.actuators @@ -718,7 +718,7 @@ class Controls: recent_blinker = (self.sm.frame - self.last_blinker_frame) * DT_CTRL < 5.0 # 5s blinker cooldown ldw_allowed = self.is_ldw_enabled and CS.vEgo > LDW_MIN_SPEED and not recent_blinker \ - and not CC.latActive and self.sm['liveCalibration'].calStatus == Calibration.CALIBRATED + and not CC.latActive and self.sm['liveCalibration'].calStatus == Calibration.CALIBRATED model_v2 = self.sm['modelV2'] desire_prediction = model_v2.meta.desirePrediction diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index ffa8373834..e8f9585a6f 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -125,10 +125,10 @@ def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): desired_curvature_rate = curvature_rates[0] max_curvature_rate = MAX_LATERAL_JERK / (v_ego**2) # inexact calculation, check https://github.com/commaai/openpilot/pull/24755 safe_desired_curvature_rate = clip(desired_curvature_rate, - -max_curvature_rate, - max_curvature_rate) + -max_curvature_rate, + max_curvature_rate) safe_desired_curvature = clip(desired_curvature, - current_curvature_desired - max_curvature_rate * DT_MDL, - current_curvature_desired + max_curvature_rate * DT_MDL) + current_curvature_desired - max_curvature_rate * DT_MDL, + current_curvature_desired + max_curvature_rate * DT_MDL) return safe_desired_curvature, safe_desired_curvature_rate diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index f72995d414..43e1f9cc4b 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -50,10 +50,6 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, elif started_condition: long_control_state = LongCtrlState.pid - - - - return long_control_state diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 057cf5623f..01a69d6f87 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -352,7 +352,7 @@ class LongitudinalMpc: x_and_cruise = np.column_stack([x, cruise_target]) x = np.min(x_and_cruise, axis=1) - + self.source = 'e2e' if x_and_cruise[0,0] < x_and_cruise[0,1] else 'cruise' else: @@ -386,7 +386,6 @@ class LongitudinalMpc: (lead_1_obstacle[0] - lead_0_obstacle[0]): self.source = 'lead1' - def update_with_xva(self, x, v, a): self.params[:,0] = -10. From 784246cf54505660afd0ebc6b8a5b43915543aef Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Thu, 29 Sep 2022 14:24:19 -0700 Subject: [PATCH 037/178] Gps test Setup, PoC (#25919) * first ignore * init gps test * make LimeGPS git clone * revert ignore * . * remove prebuilt bins * Update README.md Co-authored-by: Kurt Nistelberger Co-authored-by: Adeeb Shihadeh --- tools/gpstest/.gitignore | 2 ++ tools/gpstest/README.md | 38 ++++++++++++++++++++++++++++++++++++++ tools/gpstest/gpstest.sh | 8 ++++++++ tools/gpstest/setup.sh | 25 +++++++++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 tools/gpstest/.gitignore create mode 100644 tools/gpstest/README.md create mode 100755 tools/gpstest/gpstest.sh create mode 100755 tools/gpstest/setup.sh diff --git a/tools/gpstest/.gitignore b/tools/gpstest/.gitignore new file mode 100644 index 0000000000..b33aaa403c --- /dev/null +++ b/tools/gpstest/.gitignore @@ -0,0 +1,2 @@ +LimeGPS/ +LimeSuite/ diff --git a/tools/gpstest/README.md b/tools/gpstest/README.md new file mode 100644 index 0000000000..4125bf00af --- /dev/null +++ b/tools/gpstest/README.md @@ -0,0 +1,38 @@ +# GPS test setup + +# Usage +``` +# replaying a static location +./gpstest.sh -e -s + +# replaying a prerecorded route (NMEA cvs file) +./gpstest.sh -e -d +``` + +If `-e` is not provided the latest ephemeris file will be downloaded from +https://cddis.nasa.gov/archive/gnss/data/daily/20xx/brdc/. +(TODO: add auto downloader) + +# Hardware Setup + +* [LimeSDR USB](https://wiki.myriadrf.org/LimeSDR-USB) +* Asus AX58BT antenna + +# Software Setup +* https://github.com/myriadrf/LimeSuite +To communicate with LimeSDR the LimeSuite is needed it abstracts the direct +communication. It also contains examples for a quick start. + +The latest stable version (22.09) does not have the corresponding firmware +download available at https://downloads.myriadrf.org/project/limesuite. Therefore +version 20.10 was chosen. + +* https://github.com/osqzss/LimeGPS +Built on top of LimeSuite (libLimeSuite.so.20.10-1), generates the GPS signal. + +``` +./LimeGPS -e -l + +# Example +./LimeGPS -e /pathTo/brdc2660.22n -l 47.202028,15.740394,100 +``` diff --git a/tools/gpstest/gpstest.sh b/tools/gpstest/gpstest.sh new file mode 100755 index 0000000000..dfb71fe563 --- /dev/null +++ b/tools/gpstest/gpstest.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +LimeGPS_BIN=LimeGPS/LimeGPS +if test -f "$LimeGPS_BIN"; then + LD_PRELOAD=LimeSuite/builddir/src/libLimeSuite.so $LimeGPS_BIN $@ +else + echo "LimeGPS binary not found, run 'setup.sh' first" +fi diff --git a/tools/gpstest/setup.sh b/tools/gpstest/setup.sh new file mode 100755 index 0000000000..c893f6aba8 --- /dev/null +++ b/tools/gpstest/setup.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if [ ! -d LimeSuite ]; then + git clone https://github.com/myriadrf/LimeSuite.git + cd LimeSuite + # checkout latest version which has firmware updates available + git checkout v20.10.0 + mkdir builddir && cd builddir + cmake .. + make -j4 + cd ../.. +fi + +if [ ! -d LimeGPS ]; then + git clone https://github.com/osqzss/LimeGPS.git + cd LimeGPS + sed -i 's/LimeSuite/LimeSuite -I..\/LimeSuite\/src -L..\/LimeSuite\/builddir\/src/' makefile + make + cd .. +fi + From 29d3ed2ce63a65f793dc0ecb553180a0d0fba03e Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Thu, 29 Sep 2022 14:31:54 -0700 Subject: [PATCH 038/178] Sensor events splitup (#25714) * PoC of reading sensors via interrupts instead of polling * add Gyro and draft for magn * add more functionality to gpio.cc * change LSM gyro to interrupt * resolve rebase conflict * update BMX accel interrupt impl * add interrupt collector thread to fetch in parallel * change get_event interface to return true on successful read * update BMX gyro interrupt impl * update gpio.h/.cc according to comments * address comments, rename Edgetype enum * Edgetype to EdgeType * update sensor interrupt interface * add error handling, and read fd on trigger * avoid sending empty messages * fix build * use gpiochip * less diff * gpiochip on both edges, but skip falling edge if rising edge is detected * init last_ts with 0 * update sensord testcases * update sensord testsweet * test for pipeline * readd with_process * add null check * move tests update to seperate PR * sensord: improve test coverage (#25683) * update sensord-interrupt testsweet * address review comments * inc stddev threshold * fix format string * add version 0 check again * relax strictness after c3 with bmx tests * relax strictness after tests Co-authored-by: Kurt Nistelberger * address PR comments * fix typo * remove 4ms limit, and skip first 0.5sec of data * revert disable_interuppt change to destructor * fix and remove timing skip * make gpiochip generic * sensord port * change from sensorEvents to separated events * fix gyro usage * add splitted sensor tests * modify debug script sensor_data_to_hist.py * refactor get_event interface to remove sensorEvent message type * update locationd to non sensorEvent usage * tmp commit * fix replay * fix accelerometer type * fix sensor to hist debug script * update sensord tests to split events * remove rebase artifacts * port test_sensord.py * small clean up * change cereal to sensorEvents-splitup branch * upate sensorEvents in regen * fix route generation for splitted sensor events * regen cleanUp from sensorEvents change * . * remove light and temp from locationd * add generic init delay per sensor * . * update routes * move bmx gyro/accel to its own channel * adopt sensor tests to bmx channel * remove rebase artifacts * fix sensord test * handle bmx not present * add bmx sockets to regen * . * . * code cleanUp * . * address PR comments * address PR comments * address PR comments * lsm clean up * readd sensorEvents * rever regen.py * . * update replay refs * move channels * fix artifact * bump cereal * update refs * fix timing issue Co-authored-by: Bruce Wayne Co-authored-by: gast04 Co-authored-by: Willem Melching Co-authored-by: Adeeb Shihadeh --- cereal | 2 +- selfdrive/car/mock/interface.py | 10 +- selfdrive/debug/sensor_data_to_hist.py | 34 ++--- selfdrive/locationd/locationd.cc | 81 ++++++------ selfdrive/locationd/locationd.h | 2 +- .../locationd/test/_test_locationd_lib.py | 24 ++-- selfdrive/locationd/test/test_locationd.py | 3 +- selfdrive/sensord/sensors/bmx055_accel.cc | 4 +- selfdrive/sensord/sensors/bmx055_accel.h | 2 +- selfdrive/sensord/sensors/bmx055_gyro.cc | 4 +- selfdrive/sensord/sensors/bmx055_gyro.h | 2 +- selfdrive/sensord/sensors/bmx055_magn.cc | 4 +- selfdrive/sensord/sensors/bmx055_magn.h | 2 +- selfdrive/sensord/sensors/bmx055_temp.cc | 4 +- selfdrive/sensord/sensors/bmx055_temp.h | 2 +- selfdrive/sensord/sensors/file_sensor.cc | 3 +- selfdrive/sensord/sensors/file_sensor.h | 2 +- selfdrive/sensord/sensors/i2c_sensor.cc | 3 +- selfdrive/sensord/sensors/i2c_sensor.h | 2 +- selfdrive/sensord/sensors/light_sensor.cc | 5 +- selfdrive/sensord/sensors/light_sensor.h | 4 +- selfdrive/sensord/sensors/lsm6ds3_accel.cc | 19 +-- selfdrive/sensord/sensors/lsm6ds3_accel.h | 2 +- selfdrive/sensord/sensors/lsm6ds3_gyro.cc | 19 +-- selfdrive/sensord/sensors/lsm6ds3_gyro.h | 2 +- selfdrive/sensord/sensors/lsm6ds3_temp.cc | 4 +- selfdrive/sensord/sensors/lsm6ds3_temp.h | 2 +- selfdrive/sensord/sensors/mmc5603nj_magn.cc | 4 +- selfdrive/sensord/sensors/mmc5603nj_magn.h | 2 +- selfdrive/sensord/sensors/sensor.h | 9 +- selfdrive/sensord/sensors_qcom2.cc | 124 ++++++++---------- selfdrive/sensord/tests/test_sensord.py | 107 ++++++++------- .../test/process_replay/process_replay.py | 3 +- selfdrive/test/process_replay/ref_commit | 2 +- selfdrive/test/process_replay/regen.py | 30 +---- tools/sim/bridge.py | 25 ++-- 36 files changed, 264 insertions(+), 289 deletions(-) mode change 100755 => 100644 selfdrive/sensord/tests/test_sensord.py diff --git a/cereal b/cereal index 5aa49864bc..d4cf8728e2 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 5aa49864bce38f520705b6ed0b98e7cf9560ed0a +Subproject commit d4cf8728e2fa2d87d90098efa7ddeaf8f98a03db diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index 77e3703c2d..36062da1d1 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -20,7 +20,7 @@ class CarInterface(CarInterfaceBase): cloudlog.debug("Using Mock Car Interface") - self.sensor = messaging.sub_sock('sensorEvents') + self.gyro = messaging.sub_sock('gyroscope') self.gps = messaging.sub_sock('gpsLocationExternal') self.speed = 0. @@ -46,11 +46,9 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): # get basic data from phone and gps since CAN isn't connected - sensors = messaging.recv_sock(self.sensor) - if sensors is not None: - for sensor in sensors.sensorEvents: - if sensor.type == 4: # gyro - self.yaw_rate_meas = -sensor.gyro.v[0] + gyro_sensor = messaging.recv_sock(self.gyro) + if gyro_sensor is not None: + self.yaw_rate_meas = -gyro_sensor.gyroscope.gyroUncalibrated.v[0] gps = messaging.recv_sock(self.gps) if gps is not None: diff --git a/selfdrive/debug/sensor_data_to_hist.py b/selfdrive/debug/sensor_data_to_hist.py index 3c30b3c17c..ceed4b0ec3 100755 --- a/selfdrive/debug/sensor_data_to_hist.py +++ b/selfdrive/debug/sensor_data_to_hist.py @@ -7,6 +7,7 @@ get interrupts in a 2kHz rate. import argparse import sys +import numpy as np from collections import defaultdict from tools.lib.logreader import LogReader @@ -23,28 +24,22 @@ def parseEvents(log_reader): lsm_data = defaultdict(list) for m in log_reader: - # only sensorEvents - if m.which() != 'sensorEvents': + if m.which() not in ['accelerometer', 'gyroscope']: continue - for se in m.sensorEvents: - # convert data to dictionary - d = se.to_dict() + d = getattr(m, m.which()).to_dict() - if d["timestamp"] == 0: - continue # empty event? + if d["source"] == SRC_BMX and "acceleration" in d: + bmx_data["accel"].append(d["timestamp"] / 1e9) - if d["source"] == SRC_BMX and "acceleration" in d: - bmx_data["accel"].append(d["timestamp"] / 1e9) + if d["source"] == SRC_BMX and "gyroUncalibrated" in d: + bmx_data["gyro"].append(d["timestamp"] / 1e9) - if d["source"] == SRC_BMX and "gyroUncalibrated" in d: - bmx_data["gyro"].append(d["timestamp"] / 1e9) + if d["source"] == SRC_LSM and "acceleration" in d: + lsm_data["accel"].append(d["timestamp"] / 1e9) - if d["source"] == SRC_LSM and "acceleration" in d: - lsm_data["accel"].append(d["timestamp"] / 1e9) - - if d["source"] == SRC_LSM and "gyroUncalibrated" in d: - lsm_data["gyro"].append(d["timestamp"] / 1e9) + if d["source"] == SRC_LSM and "gyroUncalibrated" in d: + lsm_data["gyro"].append(d["timestamp"] / 1e9) return bmx_data, lsm_data @@ -54,11 +49,7 @@ def cleanData(data): return [], [] data.sort() - prev = data[0] - diffs = [] - for v in data[1:]: - diffs.append(v - prev) - prev = v + diffs = np.diff(data) return data, diffs @@ -118,4 +109,3 @@ if __name__ == "__main__": print("check plot...") plt.show() - diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 2fb3e0081d..087710d846 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -195,51 +195,48 @@ VectorXd Localizer::get_stdev() { return this->kf->get_P().diagonal().array().sqrt(); } -void Localizer::handle_sensors(double current_time, const capnp::List::Reader& log) { +void Localizer::handle_sensor(double current_time, const cereal::SensorEventData::Reader& log) { // TODO does not yet account for double sensor readings in the log - for (int i = 0; i < log.size(); i++) { - const cereal::SensorEventData::Reader& sensor_reading = log[i]; - // Ignore empty readings (e.g. in case the magnetometer had no data ready) - if (sensor_reading.getTimestamp() == 0) { - continue; - } + // Ignore empty readings (e.g. in case the magnetometer had no data ready) + if (log.getTimestamp() == 0) { + return; + } - double sensor_time = 1e-9 * sensor_reading.getTimestamp(); + double sensor_time = 1e-9 * log.getTimestamp(); - // sensor time and log time should be close - if (std::abs(current_time - sensor_time) > 0.1) { - LOGE("Sensor reading ignored, sensor timestamp more than 100ms off from log time"); - return; - } + // sensor time and log time should be close + if (std::abs(current_time - sensor_time) > 0.1) { + LOGE("Sensor reading ignored, sensor timestamp more than 100ms off from log time"); + return; + } - // TODO: handle messages from two IMUs at the same time - if (sensor_reading.getSource() == cereal::SensorEventData::SensorSource::BMX055) { - continue; - } + // TODO: handle messages from two IMUs at the same time + if (log.getSource() == cereal::SensorEventData::SensorSource::BMX055) { + return; + } - // Gyro Uncalibrated - if (sensor_reading.getSensor() == SENSOR_GYRO_UNCALIBRATED && sensor_reading.getType() == SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) { - auto v = sensor_reading.getGyroUncalibrated().getV(); - auto meas = Vector3d(-v[2], -v[1], -v[0]); - if (meas.norm() < ROTATION_SANITY_CHECK) { - this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_GYRO, { meas }); - } + // Gyro Uncalibrated + if (log.getSensor() == SENSOR_GYRO_UNCALIBRATED && log.getType() == SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) { + auto v = log.getGyroUncalibrated().getV(); + auto meas = Vector3d(-v[2], -v[1], -v[0]); + if (meas.norm() < ROTATION_SANITY_CHECK) { + this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_GYRO, { meas }); } + } - // Accelerometer - if (sensor_reading.getSensor() == SENSOR_ACCELEROMETER && sensor_reading.getType() == SENSOR_TYPE_ACCELEROMETER) { - auto v = sensor_reading.getAcceleration().getV(); + // Accelerometer + if (log.getSensor() == SENSOR_ACCELEROMETER && log.getType() == SENSOR_TYPE_ACCELEROMETER) { + auto v = log.getAcceleration().getV(); - // TODO: reduce false positives and re-enable this check - // check if device fell, estimate 10 for g - // 40m/s**2 is a good filter for falling detection, no false positives in 20k minutes of driving - //this->device_fell |= (floatlist2vector(v) - Vector3d(10.0, 0.0, 0.0)).norm() > 40.0; + // TODO: reduce false positives and re-enable this check + // check if device fell, estimate 10 for g + // 40m/s**2 is a good filter for falling detection, no false positives in 20k minutes of driving + // this->device_fell |= (floatlist2vector(v) - Vector3d(10.0, 0.0, 0.0)).norm() > 40.0; - auto meas = Vector3d(-v[2], -v[1], -v[0]); - if (meas.norm() < ACCEL_SANITY_CHECK) { - this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_ACCEL, { meas }); - } + auto meas = Vector3d(-v[2], -v[1], -v[0]); + if (meas.norm() < ACCEL_SANITY_CHECK) { + this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_ACCEL, { meas }); } } } @@ -441,8 +438,10 @@ void Localizer::handle_msg_bytes(const char *data, const size_t size) { void Localizer::handle_msg(const cereal::Event::Reader& log) { double t = log.getLogMonoTime() * 1e-9; this->time_check(t); - if (log.isSensorEvents()) { - this->handle_sensors(t, log.getSensorEvents()); + if (log.isAccelerometer()) { + this->handle_sensor(t, log.getAccelerometer()); + } else if (log.isGyroscope()) { + this->handle_sensor(t, log.getGyroscope()); } else if (log.isGpsLocation()) { this->handle_gps(t, log.getGpsLocation()); } else if (log.isGpsLocationExternal()) { @@ -498,7 +497,9 @@ int Localizer::locationd_thread() { } else { gps_location_socket = "gpsLocationExternal"; } - const std::initializer_list service_list = {gps_location_socket, "sensorEvents", "cameraOdometry", "liveCalibration", "carState", "carParams"}; + const std::initializer_list service_list = {gps_location_socket, + "cameraOdometry", "liveCalibration", "carState", "carParams", + "accelerometer", "gyroscope", "magnetometer"}; PubMaster pm({"liveLocationKalman"}); // TODO: remove carParams once we're always sending at 100Hz @@ -521,11 +522,11 @@ int Localizer::locationd_thread() { } // 100Hz publish for notcars, 20Hz for cars - const char* trigger_msg = sm["carParams"].getCarParams().getNotCar() ? "sensorEvents" : "cameraOdometry"; + const char* trigger_msg = sm["carParams"].getCarParams().getNotCar() ? "accelerometer" : "cameraOdometry"; if (sm.updated(trigger_msg)) { bool inputsOK = sm.allAliveAndValid(); - bool sensorsOK = sm.alive("sensorEvents") && sm.valid("sensorEvents"); bool gpsOK = this->isGpsOK(); + bool sensorsOK = sm.allAliveAndValid({"accelerometer", "gyroscope", "magnetometer"}); MessageBuilder msg_builder; kj::ArrayPtr bytes = this->get_message_bytes(msg_builder, inputsOK, sensorsOK, gpsOK, filterInitialized); diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index 7c0cb6b7fe..b17ab04b23 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -45,7 +45,7 @@ public: void handle_msg_bytes(const char *data, const size_t size); void handle_msg(const cereal::Event::Reader& log); - void handle_sensors(double current_time, const capnp::List::Reader& log); + void handle_sensor(double current_time, const cereal::SensorEventData::Reader& log); void handle_gps(double current_time, const cereal::GpsLocationData::Reader& log); void handle_car_state(double current_time, const cereal::CarState::Reader& log); void handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log); diff --git a/selfdrive/locationd/test/_test_locationd_lib.py b/selfdrive/locationd/test/_test_locationd_lib.py index 8a0ed3ef05..c4a053bbc6 100755 --- a/selfdrive/locationd/test/_test_locationd_lib.py +++ b/selfdrive/locationd/test/_test_locationd_lib.py @@ -51,23 +51,23 @@ void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t @unittest.skip("temporarily disabled due to false positives") def test_device_fell(self): - msg = messaging.new_message('sensorEvents', 1) - msg.sensorEvents[0].sensor = 1 - msg.sensorEvents[0].timestamp = msg.logMonoTime - msg.sensorEvents[0].type = 1 - msg.sensorEvents[0].init('acceleration') - msg.sensorEvents[0].acceleration.v = [10.0, 0.0, 0.0] # zero with gravity + msg = messaging.new_message('accelerometer') + msg.accelerometer.sensor = 1 + msg.accelerometer.timestamp = msg.logMonoTime + msg.accelerometer.type = 1 + msg.accelerometer.init('acceleration') + msg.accelerometer.acceleration.v = [10.0, 0.0, 0.0] # zero with gravity self.localizer_handle_msg(msg) ret = self.localizer_get_msg() self.assertTrue(ret.liveLocationKalman.deviceStable) - msg = messaging.new_message('sensorEvents', 1) - msg.sensorEvents[0].sensor = 1 - msg.sensorEvents[0].timestamp = msg.logMonoTime - msg.sensorEvents[0].type = 1 - msg.sensorEvents[0].init('acceleration') - msg.sensorEvents[0].acceleration.v = [50.1, 0.0, 0.0] # more than 40 m/s**2 + msg = messaging.new_message('accelerometer') + msg.accelerometer.sensor = 1 + msg.accelerometer.timestamp = msg.logMonoTime + msg.accelerometer.type = 1 + msg.accelerometer.init('acceleration') + msg.accelerometer.acceleration.v = [50.1, 0.0, 0.0] # more than 40 m/s**2 self.localizer_handle_msg(msg) ret = self.localizer_get_msg() diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index e30331a460..8841b3e67c 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -14,7 +14,8 @@ from selfdrive.manager.process_config import managed_processes class TestLocationdProc(unittest.TestCase): MAX_WAITS = 1000 - LLD_MSGS = ['gpsLocationExternal', 'cameraOdometry', 'carState', 'sensorEvents', 'liveCalibration'] + LLD_MSGS = ['gpsLocationExternal', 'cameraOdometry', 'carState', 'liveCalibration', + 'accelerometer', 'gyroscope', 'magnetometer'] def setUp(self): random.seed(123489234) diff --git a/selfdrive/sensord/sensors/bmx055_accel.cc b/selfdrive/sensord/sensors/bmx055_accel.cc index c6dcdbd7aa..78b3ac526d 100644 --- a/selfdrive/sensord/sensors/bmx055_accel.cc +++ b/selfdrive/sensord/sensors/bmx055_accel.cc @@ -42,6 +42,7 @@ int BMX055_Accel::init() { if (ret < 0) { goto fail; } + ret = set_register(BMX055_ACCEL_I2C_REG_BW, BMX055_ACCEL_BW_125HZ); if (ret < 0) { goto fail; @@ -61,7 +62,7 @@ int BMX055_Accel::shutdown() { return ret; } -bool BMX055_Accel::get_event(cereal::SensorEventData::Builder &event) { +bool BMX055_Accel::get_event(MessageBuilder &msg, uint64_t ts) { uint64_t start_time = nanos_since_boot(); uint8_t buffer[6]; int len = read_register(BMX055_ACCEL_I2C_REG_X_LSB, buffer, sizeof(buffer)); @@ -73,6 +74,7 @@ bool BMX055_Accel::get_event(cereal::SensorEventData::Builder &event) { float y = -read_12_bit(buffer[2], buffer[3]) * scale; float z = read_12_bit(buffer[4], buffer[5]) * scale; + auto event = msg.initEvent().initAccelerometer2(); event.setSource(cereal::SensorEventData::SensorSource::BMX055); event.setVersion(1); event.setSensor(SENSOR_ACCELEROMETER); diff --git a/selfdrive/sensord/sensors/bmx055_accel.h b/selfdrive/sensord/sensors/bmx055_accel.h index 6a0f9f1ada..8ef660a99f 100644 --- a/selfdrive/sensord/sensors/bmx055_accel.h +++ b/selfdrive/sensord/sensors/bmx055_accel.h @@ -36,6 +36,6 @@ class BMX055_Accel : public I2CSensor { public: BMX055_Accel(I2CBus *bus); int init(); - bool get_event(cereal::SensorEventData::Builder &event); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown(); }; diff --git a/selfdrive/sensord/sensors/bmx055_gyro.cc b/selfdrive/sensord/sensors/bmx055_gyro.cc index 4deb15ec6d..9d70b9e431 100644 --- a/selfdrive/sensord/sensors/bmx055_gyro.cc +++ b/selfdrive/sensord/sensors/bmx055_gyro.cc @@ -72,7 +72,7 @@ int BMX055_Gyro::shutdown() { return ret; } -bool BMX055_Gyro::get_event(cereal::SensorEventData::Builder &event) { +bool BMX055_Gyro::get_event(MessageBuilder &msg, uint64_t ts) { uint64_t start_time = nanos_since_boot(); uint8_t buffer[6]; int len = read_register(BMX055_GYRO_I2C_REG_RATE_X_LSB, buffer, sizeof(buffer)); @@ -84,6 +84,7 @@ bool BMX055_Gyro::get_event(cereal::SensorEventData::Builder &event) { float y = -DEG2RAD(read_16_bit(buffer[2], buffer[3]) * scale); float z = DEG2RAD(read_16_bit(buffer[4], buffer[5]) * scale); + auto event = msg.initEvent().initGyroscope2(); event.setSource(cereal::SensorEventData::SensorSource::BMX055); event.setVersion(1); event.setSensor(SENSOR_GYRO_UNCALIBRATED); @@ -94,5 +95,6 @@ bool BMX055_Gyro::get_event(cereal::SensorEventData::Builder &event) { auto svec = event.initGyroUncalibrated(); svec.setV(xyz); svec.setStatus(true); + return true; } diff --git a/selfdrive/sensord/sensors/bmx055_gyro.h b/selfdrive/sensord/sensors/bmx055_gyro.h index ac5dacc4a6..80b93f128c 100644 --- a/selfdrive/sensord/sensors/bmx055_gyro.h +++ b/selfdrive/sensord/sensors/bmx055_gyro.h @@ -36,6 +36,6 @@ class BMX055_Gyro : public I2CSensor { public: BMX055_Gyro(I2CBus *bus); int init(); - bool get_event(cereal::SensorEventData::Builder &event); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown(); }; diff --git a/selfdrive/sensord/sensors/bmx055_magn.cc b/selfdrive/sensord/sensors/bmx055_magn.cc index 74e18b7c82..394b1e83d3 100644 --- a/selfdrive/sensord/sensors/bmx055_magn.cc +++ b/selfdrive/sensord/sensors/bmx055_magn.cc @@ -223,7 +223,7 @@ bool BMX055_Magn::parse_xyz(uint8_t buffer[8], int16_t *x, int16_t *y, int16_t * } -bool BMX055_Magn::get_event(cereal::SensorEventData::Builder &event) { +bool BMX055_Magn::get_event(MessageBuilder &msg, uint64_t ts) { uint64_t start_time = nanos_since_boot(); uint8_t buffer[8]; int16_t _x, _y, x, y, z; @@ -233,6 +233,8 @@ bool BMX055_Magn::get_event(cereal::SensorEventData::Builder &event) { bool parsed = parse_xyz(buffer, &_x, &_y, &z); if (parsed) { + + auto event = msg.initEvent().initMagnetometer(); event.setSource(cereal::SensorEventData::SensorSource::BMX055); event.setVersion(2); event.setSensor(SENSOR_MAGNETOMETER_UNCALIBRATED); diff --git a/selfdrive/sensord/sensors/bmx055_magn.h b/selfdrive/sensord/sensors/bmx055_magn.h index 0549e163f6..e4a79bc7e0 100644 --- a/selfdrive/sensord/sensors/bmx055_magn.h +++ b/selfdrive/sensord/sensors/bmx055_magn.h @@ -59,6 +59,6 @@ class BMX055_Magn : public I2CSensor{ public: BMX055_Magn(I2CBus *bus); int init(); - bool get_event(cereal::SensorEventData::Builder &event); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown(); }; diff --git a/selfdrive/sensord/sensors/bmx055_temp.cc b/selfdrive/sensord/sensors/bmx055_temp.cc index 3cee34ef19..bdb34f1508 100644 --- a/selfdrive/sensord/sensors/bmx055_temp.cc +++ b/selfdrive/sensord/sensors/bmx055_temp.cc @@ -28,7 +28,7 @@ fail: return ret; } -bool BMX055_Temp::get_event(cereal::SensorEventData::Builder &event) { +bool BMX055_Temp::get_event(MessageBuilder &msg, uint64_t ts) { uint64_t start_time = nanos_since_boot(); uint8_t buffer[1]; int len = read_register(BMX055_ACCEL_I2C_REG_TEMP, buffer, sizeof(buffer)); @@ -36,10 +36,12 @@ bool BMX055_Temp::get_event(cereal::SensorEventData::Builder &event) { float temp = 23.0f + int8_t(buffer[0]) / 2.0f; + auto event = msg.initEvent().initTemperatureSensor(); event.setSource(cereal::SensorEventData::SensorSource::BMX055); event.setVersion(1); event.setType(SENSOR_TYPE_AMBIENT_TEMPERATURE); event.setTimestamp(start_time); event.setTemperature(temp); + return true; } diff --git a/selfdrive/sensord/sensors/bmx055_temp.h b/selfdrive/sensord/sensors/bmx055_temp.h index f5d771a29c..0b6802deaa 100644 --- a/selfdrive/sensord/sensors/bmx055_temp.h +++ b/selfdrive/sensord/sensors/bmx055_temp.h @@ -8,6 +8,6 @@ class BMX055_Temp : public I2CSensor { public: BMX055_Temp(I2CBus *bus); int init(); - bool get_event(cereal::SensorEventData::Builder &event); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown() { return 0; } }; diff --git a/selfdrive/sensord/sensors/file_sensor.cc b/selfdrive/sensord/sensors/file_sensor.cc index 80e5121564..a74ae1ae1e 100644 --- a/selfdrive/sensord/sensors/file_sensor.cc +++ b/selfdrive/sensord/sensors/file_sensor.cc @@ -2,8 +2,7 @@ #include -FileSensor::FileSensor(std::string filename) : file(filename) { -} +FileSensor::FileSensor(std::string filename) : file(filename) {} int FileSensor::init() { return file.is_open() ? 0 : 1; diff --git a/selfdrive/sensord/sensors/file_sensor.h b/selfdrive/sensord/sensors/file_sensor.h index 5bcaee66a8..39d695167d 100644 --- a/selfdrive/sensord/sensors/file_sensor.h +++ b/selfdrive/sensord/sensors/file_sensor.h @@ -15,5 +15,5 @@ public: ~FileSensor(); int init(); bool has_interrupt_enabled(); - virtual bool get_event(cereal::SensorEventData::Builder &event) = 0; + virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0; }; diff --git a/selfdrive/sensord/sensors/i2c_sensor.cc b/selfdrive/sensord/sensors/i2c_sensor.cc index 06a216478b..f563f93d2b 100644 --- a/selfdrive/sensord/sensors/i2c_sensor.cc +++ b/selfdrive/sensord/sensors/i2c_sensor.cc @@ -15,7 +15,8 @@ int32_t read_20_bit(uint8_t b2, uint8_t b1, uint8_t b0) { return int32_t(combined) / (1 << 4); } -I2CSensor::I2CSensor(I2CBus *bus, int gpio_nr, bool shared_gpio) : bus(bus), gpio_nr(gpio_nr), shared_gpio(shared_gpio) {} +I2CSensor::I2CSensor(I2CBus *bus, int gpio_nr, bool shared_gpio) : + bus(bus), gpio_nr(gpio_nr), shared_gpio(shared_gpio) {} I2CSensor::~I2CSensor() { if (gpio_fd != -1) { diff --git a/selfdrive/sensord/sensors/i2c_sensor.h b/selfdrive/sensord/sensors/i2c_sensor.h index f6820a2471..0de2a98738 100644 --- a/selfdrive/sensord/sensors/i2c_sensor.h +++ b/selfdrive/sensord/sensors/i2c_sensor.h @@ -31,6 +31,6 @@ public: int init_gpio(); bool has_interrupt_enabled(); virtual int init() = 0; - virtual bool get_event(cereal::SensorEventData::Builder &event) = 0; + virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0; virtual int shutdown() = 0; }; diff --git a/selfdrive/sensord/sensors/light_sensor.cc b/selfdrive/sensord/sensors/light_sensor.cc index 321ac75c7e..58c602ea39 100644 --- a/selfdrive/sensord/sensors/light_sensor.cc +++ b/selfdrive/sensord/sensors/light_sensor.cc @@ -5,7 +5,9 @@ #include "common/timing.h" #include "selfdrive/sensord/sensors/constants.h" -bool LightSensor::get_event(cereal::SensorEventData::Builder &event) { +LightSensor::LightSensor(std::string filename) : FileSensor(filename) {} + +bool LightSensor::get_event(MessageBuilder &msg, uint64_t ts) { uint64_t start_time = nanos_since_boot(); file.clear(); file.seekg(0); @@ -13,6 +15,7 @@ bool LightSensor::get_event(cereal::SensorEventData::Builder &event) { int value; file >> value; + auto event = msg.initEvent().initLightSensor(); event.setSource(cereal::SensorEventData::SensorSource::RPR0521); event.setVersion(1); event.setSensor(SENSOR_LIGHT); diff --git a/selfdrive/sensord/sensors/light_sensor.h b/selfdrive/sensord/sensors/light_sensor.h index 63bda74755..7ed1c1f70c 100644 --- a/selfdrive/sensord/sensors/light_sensor.h +++ b/selfdrive/sensord/sensors/light_sensor.h @@ -3,7 +3,7 @@ class LightSensor : public FileSensor { public: - LightSensor(std::string filename) : FileSensor(filename){}; - bool get_event(cereal::SensorEventData::Builder &event); + LightSensor(std::string filename); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown() { return 0; } }; diff --git a/selfdrive/sensord/sensors/lsm6ds3_accel.cc b/selfdrive/sensord/sensors/lsm6ds3_accel.cc index 513125fd59..27cd4d0c70 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_accel.cc +++ b/selfdrive/sensord/sensors/lsm6ds3_accel.cc @@ -5,7 +5,8 @@ #include "common/swaglog.h" #include "common/timing.h" -LSM6DS3_Accel::LSM6DS3_Accel(I2CBus *bus, int gpio_nr, bool shared_gpio) : I2CSensor(bus, gpio_nr, shared_gpio) {} +LSM6DS3_Accel::LSM6DS3_Accel(I2CBus *bus, int gpio_nr, bool shared_gpio) : + I2CSensor(bus, gpio_nr, shared_gpio) {} int LSM6DS3_Accel::init() { int ret = 0; @@ -93,15 +94,13 @@ fail: return ret; } -bool LSM6DS3_Accel::get_event(cereal::SensorEventData::Builder &event) { +bool LSM6DS3_Accel::get_event(MessageBuilder &msg, uint64_t ts) { - if (has_interrupt_enabled()) { - // INT1 shared with gyro, check STATUS_REG who triggered - uint8_t status_reg = 0; - read_register(LSM6DS3_ACCEL_I2C_REG_STAT_REG, &status_reg, sizeof(status_reg)); - if ((status_reg & LSM6DS3_ACCEL_DRDY_XLDA) == 0) { - return false; - } + // INT1 shared with gyro, check STATUS_REG who triggered + uint8_t status_reg = 0; + read_register(LSM6DS3_ACCEL_I2C_REG_STAT_REG, &status_reg, sizeof(status_reg)); + if ((status_reg & LSM6DS3_ACCEL_DRDY_XLDA) == 0) { + return false; } uint8_t buffer[6]; @@ -113,10 +112,12 @@ bool LSM6DS3_Accel::get_event(cereal::SensorEventData::Builder &event) { float y = read_16_bit(buffer[2], buffer[3]) * scale; float z = read_16_bit(buffer[4], buffer[5]) * scale; + auto event = msg.initEvent().initAccelerometer(); event.setSource(source); event.setVersion(1); event.setSensor(SENSOR_ACCELEROMETER); event.setType(SENSOR_TYPE_ACCELEROMETER); + event.setTimestamp(ts); float xyz[] = {y, -x, z}; auto svec = event.initAcceleration(); diff --git a/selfdrive/sensord/sensors/lsm6ds3_accel.h b/selfdrive/sensord/sensors/lsm6ds3_accel.h index 6ed94a8f12..84084fc916 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_accel.h +++ b/selfdrive/sensord/sensors/lsm6ds3_accel.h @@ -28,6 +28,6 @@ class LSM6DS3_Accel : public I2CSensor { public: LSM6DS3_Accel(I2CBus *bus, int gpio_nr = 0, bool shared_gpio = false); int init(); - bool get_event(cereal::SensorEventData::Builder &event); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown(); }; diff --git a/selfdrive/sensord/sensors/lsm6ds3_gyro.cc b/selfdrive/sensord/sensors/lsm6ds3_gyro.cc index fd0436a5f0..014a72bb73 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_gyro.cc +++ b/selfdrive/sensord/sensors/lsm6ds3_gyro.cc @@ -8,7 +8,8 @@ #define DEG2RAD(x) ((x) * M_PI / 180.0) -LSM6DS3_Gyro::LSM6DS3_Gyro(I2CBus *bus, int gpio_nr, bool shared_gpio) : I2CSensor(bus, gpio_nr, shared_gpio) {} +LSM6DS3_Gyro::LSM6DS3_Gyro(I2CBus *bus, int gpio_nr, bool shared_gpio) : + I2CSensor(bus, gpio_nr, shared_gpio) {} int LSM6DS3_Gyro::init() { int ret = 0; @@ -96,15 +97,13 @@ fail: return ret; } -bool LSM6DS3_Gyro::get_event(cereal::SensorEventData::Builder &event) { +bool LSM6DS3_Gyro::get_event(MessageBuilder &msg, uint64_t ts) { - if (has_interrupt_enabled()) { - // INT1 shared with accel, check STATUS_REG who triggered - uint8_t status_reg = 0; - read_register(LSM6DS3_GYRO_I2C_REG_STAT_REG, &status_reg, sizeof(status_reg)); - if ((status_reg & LSM6DS3_GYRO_DRDY_GDA) == 0) { - return false; - } + // INT1 shared with accel, check STATUS_REG who triggered + uint8_t status_reg = 0; + read_register(LSM6DS3_GYRO_I2C_REG_STAT_REG, &status_reg, sizeof(status_reg)); + if ((status_reg & LSM6DS3_GYRO_DRDY_GDA) == 0) { + return false; } uint8_t buffer[6]; @@ -116,10 +115,12 @@ bool LSM6DS3_Gyro::get_event(cereal::SensorEventData::Builder &event) { float y = DEG2RAD(read_16_bit(buffer[2], buffer[3]) * scale); float z = DEG2RAD(read_16_bit(buffer[4], buffer[5]) * scale); + auto event = msg.initEvent().initGyroscope(); event.setSource(source); event.setVersion(2); event.setSensor(SENSOR_GYRO_UNCALIBRATED); event.setType(SENSOR_TYPE_GYROSCOPE_UNCALIBRATED); + event.setTimestamp(ts); float xyz[] = {y, -x, z}; auto svec = event.initGyroUncalibrated(); diff --git a/selfdrive/sensord/sensors/lsm6ds3_gyro.h b/selfdrive/sensord/sensors/lsm6ds3_gyro.h index c2ed5ab76e..6c61ffcef2 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_gyro.h +++ b/selfdrive/sensord/sensors/lsm6ds3_gyro.h @@ -28,6 +28,6 @@ class LSM6DS3_Gyro : public I2CSensor { public: LSM6DS3_Gyro(I2CBus *bus, int gpio_nr = 0, bool shared_gpio = false); int init(); - bool get_event(cereal::SensorEventData::Builder &event); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown(); }; diff --git a/selfdrive/sensord/sensors/lsm6ds3_temp.cc b/selfdrive/sensord/sensors/lsm6ds3_temp.cc index 082ffee08f..7e1c4d4c81 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_temp.cc +++ b/selfdrive/sensord/sensors/lsm6ds3_temp.cc @@ -31,8 +31,7 @@ fail: return ret; } -bool LSM6DS3_Temp::get_event(cereal::SensorEventData::Builder &event) { - +bool LSM6DS3_Temp::get_event(MessageBuilder &msg, uint64_t ts) { uint64_t start_time = nanos_since_boot(); uint8_t buffer[2]; int len = read_register(LSM6DS3_TEMP_I2C_REG_OUT_TEMP_L, buffer, sizeof(buffer)); @@ -41,6 +40,7 @@ bool LSM6DS3_Temp::get_event(cereal::SensorEventData::Builder &event) { float scale = (source == cereal::SensorEventData::SensorSource::LSM6DS3TRC) ? 256.0f : 16.0f; float temp = 25.0f + read_16_bit(buffer[0], buffer[1]) / scale; + auto event = msg.initEvent().initTemperatureSensor(); event.setSource(source); event.setVersion(1); event.setType(SENSOR_TYPE_AMBIENT_TEMPERATURE); diff --git a/selfdrive/sensord/sensors/lsm6ds3_temp.h b/selfdrive/sensord/sensors/lsm6ds3_temp.h index 9d95236901..1d6bcc228a 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_temp.h +++ b/selfdrive/sensord/sensors/lsm6ds3_temp.h @@ -21,6 +21,6 @@ class LSM6DS3_Temp : public I2CSensor { public: LSM6DS3_Temp(I2CBus *bus); int init(); - bool get_event(cereal::SensorEventData::Builder &event); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown() { return 0; } }; diff --git a/selfdrive/sensord/sensors/mmc5603nj_magn.cc b/selfdrive/sensord/sensors/mmc5603nj_magn.cc index 8af4956edf..7a9b7a298b 100644 --- a/selfdrive/sensord/sensors/mmc5603nj_magn.cc +++ b/selfdrive/sensord/sensors/mmc5603nj_magn.cc @@ -79,8 +79,7 @@ fail: return ret; } -bool MMC5603NJ_Magn::get_event(cereal::SensorEventData::Builder &event) { - +bool MMC5603NJ_Magn::get_event(MessageBuilder &msg, uint64_t ts) { uint64_t start_time = nanos_since_boot(); uint8_t buffer[9]; int len = read_register(MMC5603NJ_I2C_REG_XOUT0, buffer, sizeof(buffer)); @@ -91,6 +90,7 @@ bool MMC5603NJ_Magn::get_event(cereal::SensorEventData::Builder &event) { float y = read_20_bit(buffer[7], buffer[3], buffer[2]) * scale; float z = read_20_bit(buffer[8], buffer[5], buffer[4]) * scale; + auto event = msg.initEvent().initMagnetometer(); event.setSource(cereal::SensorEventData::SensorSource::MMC5603NJ); event.setVersion(1); event.setSensor(SENSOR_MAGNETOMETER_UNCALIBRATED); diff --git a/selfdrive/sensord/sensors/mmc5603nj_magn.h b/selfdrive/sensord/sensors/mmc5603nj_magn.h index 2c06cab96f..a364c7c37a 100644 --- a/selfdrive/sensord/sensors/mmc5603nj_magn.h +++ b/selfdrive/sensord/sensors/mmc5603nj_magn.h @@ -25,6 +25,6 @@ class MMC5603NJ_Magn : public I2CSensor { public: MMC5603NJ_Magn(I2CBus *bus); int init(); - bool get_event(cereal::SensorEventData::Builder &event); + bool get_event(MessageBuilder &msg, uint64_t ts = 0); int shutdown(); }; diff --git a/selfdrive/sensord/sensors/sensor.h b/selfdrive/sensord/sensors/sensor.h index 0bdc560275..603aa3586e 100644 --- a/selfdrive/sensord/sensors/sensor.h +++ b/selfdrive/sensord/sensors/sensor.h @@ -1,13 +1,18 @@ #pragma once -#include "cereal/gen/cpp/log.capnp.h" +#include "cereal/messaging/messaging.h" class Sensor { public: int gpio_fd = -1; + uint64_t init_delay = 500e6; // default dealy 500ms virtual ~Sensor() {}; virtual int init() = 0; - virtual bool get_event(cereal::SensorEventData::Builder &event) = 0; + virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0; virtual bool has_interrupt_enabled() = 0; virtual int shutdown() = 0; + + virtual bool is_data_valid(uint64_t st, uint64_t ct) { + return (ct - st) > init_delay; + } }; diff --git a/selfdrive/sensord/sensors_qcom2.cc b/selfdrive/sensord/sensors_qcom2.cc index 5e741a89a5..2279cf2532 100644 --- a/selfdrive/sensord/sensors_qcom2.cc +++ b/selfdrive/sensord/sensors_qcom2.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -27,18 +28,18 @@ ExitHandler do_exit; std::mutex pm_mutex; - -// filter first values (0.5sec) as those may contain inaccuracies uint64_t init_ts = 0; -constexpr uint64_t init_delay = 500*1e6; -void interrupt_loop(int fd, std::vector& sensors, PubMaster& pm) { +void interrupt_loop(std::vector& sensors, + std::map& sensor_service) +{ + PubMaster pm_int({"gyroscope", "accelerometer"}); + + int fd = sensors[0]->gpio_fd; struct pollfd fd_list[1] = {0}; fd_list[0].fd = fd; fd_list[0].events = POLLIN | POLLPRI; - uint64_t offset = nanos_since_epoch() - nanos_since_boot(); - while (!do_exit) { int err = poll(fd_list, 1, 100); if (err == -1) { @@ -65,39 +66,21 @@ void interrupt_loop(int fd, std::vector& sensors, PubMaster& pm) { } int num_events = err / sizeof(*evdata); + uint64_t offset = nanos_since_epoch() - nanos_since_boot(); uint64_t ts = evdata[num_events - 1].timestamp - offset; - MessageBuilder msg; - auto orphanage = msg.getOrphanage(); - std::vector> collected_events; - collected_events.reserve(sensors.size()); - for (Sensor *sensor : sensors) { - auto orphan = orphanage.newOrphan(); - auto event = orphan.get(); - if (!sensor->get_event(event)) { + MessageBuilder msg; + if (!sensor->get_event(msg, ts)) { continue; } - event.setTimestamp(ts); - collected_events.push_back(kj::mv(orphan)); - } - - if (collected_events.size() == 0) { - continue; - } - - auto events = msg.initEvent().initSensorEvents(collected_events.size()); - for (int i = 0; i < collected_events.size(); ++i) { - events.adoptWithCaveats(i, kj::mv(collected_events[i])); - } + if (!sensor->is_data_valid(init_ts, ts)) { + continue; + } - if (ts - init_ts < init_delay) { - continue; + pm_int.send(sensor_service[sensor].c_str(), msg); } - - std::lock_guard lock(pm_mutex); - pm.send("sensorEvents", msg); } // poweroff sensors, disable interrupts @@ -106,16 +89,7 @@ void interrupt_loop(int fd, std::vector& sensors, PubMaster& pm) { } } -int sensor_loop() { - I2CBus *i2c_bus_imu; - - try { - i2c_bus_imu = new I2CBus(I2C_BUS_IMU); - } catch (std::exception &e) { - LOGE("I2CBus init failed"); - return -1; - } - +int sensor_loop(I2CBus *i2c_bus_imu) { BMX055_Accel bmx055_accel(i2c_bus_imu); BMX055_Gyro bmx055_gyro(i2c_bus_imu); BMX055_Magn bmx055_magn(i2c_bus_imu); @@ -129,6 +103,20 @@ int sensor_loop() { LightSensor light("/sys/class/i2c-adapter/i2c-2/2-0038/iio:device1/in_intensity_both_raw"); + std::map sensor_service = { + {&bmx055_accel, "accelerometer2"}, + {&bmx055_gyro, "gyroscope2"}, + {&bmx055_magn, "magnetometer"}, + {&bmx055_temp, "temperatureSensor"}, + + {&lsm6ds3_accel, "accelerometer"}, + {&lsm6ds3_gyro, "gyroscope"}, + {&lsm6ds3_temp, "temperatureSensor"}, + + {&mmc5603nj_magn, "magnetometer"}, + {&light, "lightSensor"} + }; + // Sensor init std::vector> sensors_init; // Sensor, required sensors_init.push_back({&bmx055_accel, false}); @@ -148,29 +136,27 @@ int sensor_loop() { // Initialize sensors std::vector sensors; - for (auto &sensor : sensors_init) { - int err = sensor.first->init(); + for (auto &[sensor, required] : sensors_init) { + int err = sensor->init(); if (err < 0) { - // Fail on required sensors - if (sensor.second) { + if (required) { LOGE("Error initializing sensors"); - delete i2c_bus_imu; return -1; } } else { - if (sensor.first == &bmx055_magn || sensor.first == &mmc5603nj_magn) { + + if (sensor == &bmx055_magn || sensor == &mmc5603nj_magn) { has_magnetometer = true; } - if (!sensor.first->has_interrupt_enabled()) { - sensors.push_back(sensor.first); + if (!sensor->has_interrupt_enabled()) { + sensors.push_back(sensor); } } } if (!has_magnetometer) { LOGE("No magnetometer present"); - delete i2c_bus_imu; return -1; } @@ -179,33 +165,30 @@ int sensor_loop() { util::set_core_affinity({1}); std::system("sudo su -c 'echo 1 > /proc/irq/336/smp_affinity_list'"); - PubMaster pm({"sensorEvents"}); + PubMaster pm_non_int({"gyroscope2", "accelerometer2", "temperatureSensor", + "lightSensor", "magnetometer"}); init_ts = nanos_since_boot(); // thread for reading events via interrupts std::vector lsm_interrupt_sensors = {&lsm6ds3_accel, &lsm6ds3_gyro}; - std::thread lsm_interrupt_thread(&interrupt_loop, lsm6ds3_accel.gpio_fd, std::ref(lsm_interrupt_sensors), std::ref(pm)); + std::thread lsm_interrupt_thread(&interrupt_loop, std::ref(lsm_interrupt_sensors), + std::ref(sensor_service)); // polling loop for non interrupt handled sensors while (!do_exit) { std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - const int num_events = sensors.size(); - MessageBuilder msg; - auto sensor_events = msg.initEvent().initSensorEvents(num_events); - - for (int i = 0; i < num_events; i++) { - auto event = sensor_events[i]; - sensors[i]->get_event(event); - } + for (Sensor *sensor : sensors) { + MessageBuilder msg; + if (!sensor->get_event(msg)) { + continue; + } - if (nanos_since_boot() - init_ts < init_delay) { - continue; - } + if (!sensor->is_data_valid(init_ts, nanos_since_boot())) { + continue; + } - { - std::lock_guard lock(pm_mutex); - pm.send("sensorEvents", msg); + pm_non_int.send(sensor_service[sensor].c_str(), msg); } std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); @@ -217,10 +200,15 @@ int sensor_loop() { } lsm_interrupt_thread.join(); - delete i2c_bus_imu; return 0; } int main(int argc, char *argv[]) { - return sensor_loop(); + try { + auto i2c_bus_imu = std::make_unique(I2C_BUS_IMU); + return sensor_loop(i2c_bus_imu.get()); + } catch (std::exception &e) { + LOGE("I2CBus init failed"); + return -1; + } } diff --git a/selfdrive/sensord/tests/test_sensord.py b/selfdrive/sensord/tests/test_sensord.py old mode 100755 new mode 100644 index 60fe8b06d4..1af76c8384 --- a/selfdrive/sensord/tests/test_sensord.py +++ b/selfdrive/sensord/tests/test_sensord.py @@ -3,7 +3,7 @@ import os import time import unittest import numpy as np -from collections import namedtuple +from collections import namedtuple, defaultdict import cereal.messaging as messaging from cereal import log @@ -78,21 +78,8 @@ ALL_SENSORS = { } } -SENSOR_BUS = 1 -I2C_ADDR_LSM = 0x6A LSM_INT_GPIO = 84 -def read_sensor_events(duration_sec): - sensor_events = messaging.sub_sock("sensorEvents", timeout=0.1) - start_time_sec = time.monotonic() - events = [] - while time.monotonic() - start_time_sec < duration_sec: - events += messaging.drain_sock(sensor_events) - time.sleep(0.01) - - assert len(events) != 0, "No sensor events collected" - return events - def get_proc_interrupts(int_pin): with open("/proc/interrupts") as f: lines = f.read().split("\n") @@ -100,9 +87,27 @@ def get_proc_interrupts(int_pin): for line in lines: if f" {int_pin} " in line: return ''.join(list(filter(lambda e: e != '', line.split(' ')))[1:6]) - return "" +def read_sensor_events(duration_sec): + + sensor_types = ['accelerometer', 'gyroscope', 'magnetometer', 'accelerometer2', + 'gyroscope2', 'lightSensor', 'temperatureSensor'] + esocks = {} + events = defaultdict(list) + for stype in sensor_types: + esocks[stype] = messaging.sub_sock(stype, timeout=0.1) + + start_time_sec = time.monotonic() + while time.monotonic() - start_time_sec < duration_sec: + for esock in esocks: + events[esock] += messaging.drain_sock(esocks[esock]) + time.sleep(0.1) + + for etype in events: + assert len(events[etype]) != 0, f"No {etype} events collected" + + return events class TestSensord(unittest.TestCase): @classmethod @@ -132,12 +137,10 @@ class TestSensord(unittest.TestCase): # verify correct sensors configuration seen = set() - for event in self.events: - for measurement in event.sensorEvents: - # filter unset events (bmx magn) - if measurement.version == 0: - continue - seen.add((str(measurement.source), measurement.which())) + for etype in self.events: + for measurement in self.events[etype]: + m = getattr(measurement, measurement.which()) + seen.add((str(m.source), m.which())) self.assertIn(seen, SENSOR_CONFIGURATIONS) @@ -148,10 +151,14 @@ class TestSensord(unittest.TestCase): 1: [], # accel 5: [], # gyro } - for event in self.events: - for measurement in event.sensorEvents: - if str(measurement.source).startswith("lsm6ds3") and measurement.sensor in sensor_t: - sensor_t[measurement.sensor].append(measurement.timestamp) + + for measurement in self.events['accelerometer']: + m = getattr(measurement, measurement.which()) + sensor_t[m.sensor].append(m.timestamp) + + for measurement in self.events['gyroscope']: + m = getattr(measurement, measurement.which()) + sensor_t[m.sensor].append(m.timestamp) for s, vals in sensor_t.items(): with self.subTest(sensor=s): @@ -172,17 +179,14 @@ class TestSensord(unittest.TestCase): # verify if all sensors produce events sensor_events = dict() - for event in self.events: - for measurement in event.sensorEvents: + for etype in self.events: + for measurement in self.events[etype]: + m = getattr(measurement, measurement.which()) - # filter unset events (bmx magn) - if measurement.version == 0: - continue - - if measurement.type in sensor_events: - sensor_events[measurement.type] += 1 + if m.type in sensor_events: + sensor_events[m.type] += 1 else: - sensor_events[measurement.type] = 1 + sensor_events[m.type] = 1 for s in sensor_events: err_msg = f"Sensor {s}: 200 < {sensor_events[s]}" @@ -192,22 +196,19 @@ class TestSensord(unittest.TestCase): # ensure diff between the message logMonotime and sample timestamp is small tdiffs = list() - for event in self.events: - for measurement in event.sensorEvents: - - # filter unset events (bmx magn) - if measurement.version == 0: - continue + for etype in self.events: + for measurement in self.events[etype]: + m = getattr(measurement, measurement.which()) # check if gyro and accel timestamps are before logMonoTime - if str(measurement.source).startswith("lsm6ds3"): - if measurement.which() != 'temperature': - err_msg = f"Timestamp after logMonoTime: {measurement.timestamp} > {event.logMonoTime}" - assert measurement.timestamp < event.logMonoTime, err_msg + if str(m.source).startswith("lsm6ds3"): + if m.which() != 'temperature': + err_msg = f"Timestamp after logMonoTime: {m.timestamp} > {measurement.logMonoTime}" + assert m.timestamp < measurement.logMonoTime, err_msg # negative values might occur, as non interrupt packages created # before the sensor is read - tdiffs.append(abs(event.logMonoTime - measurement.timestamp)) + tdiffs.append(abs(measurement.logMonoTime - m.timestamp)) high_delay_diffs = set(filter(lambda d: d >= 10*10**6, tdiffs)) assert len(high_delay_diffs) < 15, f"Too many high delay packages: {high_delay_diffs}" @@ -219,16 +220,14 @@ class TestSensord(unittest.TestCase): assert stddev < 2*10**6, f"Timing diffs have to high stddev: {stddev}" def test_sensor_values_sanity_check(self): - sensor_values = dict() - for event in self.events: - for m in event.sensorEvents: - - # filter unset events (bmx magn) - if m.version == 0: - continue + sensor_values = dict() + for etype in self.events: + for measurement in self.events[etype]: + m = getattr(measurement, measurement.which()) key = (m.source.raw, m.which()) values = getattr(m, m.which()) + if hasattr(values, 'v'): values = values.v values = np.atleast_1d(values) @@ -265,7 +264,8 @@ class TestSensord(unittest.TestCase): time.sleep(1) state_two = get_proc_interrupts(LSM_INT_GPIO) - assert state_one != state_two, f"no interrupts received after sensord start!\n{state_one} {state_two}" + error_msg = f"no interrupts received after sensord start!\n{state_one} {state_two}" + assert state_one != state_two, error_msg managed_processes["sensord"].stop() time.sleep(1) @@ -276,6 +276,5 @@ class TestSensord(unittest.TestCase): state_two = get_proc_interrupts(LSM_INT_GPIO) assert state_one == state_two, "Interrupts received after sensord stop!" - if __name__ == "__main__": unittest.main() diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 038b0cf468..c8389bb71c 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -328,7 +328,8 @@ CONFIGS = [ proc_name="locationd", pub_sub={ "cameraOdometry": ["liveLocationKalman"], - "sensorEvents": [], "gpsLocationExternal": [], "liveCalibration": [], "carState": [], + "accelerometer": [], "gyroscope": [], "magnetometer": [], + "gpsLocationExternal": [], "liveCalibration": [], "carState": [], }, ignore=["logMonoTime", "valid"], init_callback=get_car_params, diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 98fbb74ae4..e308d2dff4 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -9098c1cf6993598071c3e27448356eef86660d02 +761eada809a0eaa67989e6e435042633f965d1fe \ No newline at end of file diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index 356016c642..54c84978c2 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -90,30 +90,10 @@ def replay_device_state(s, msgs): rk.keep_time() -def replay_sensor_events(s, msgs): - pm = messaging.PubMaster([s, ]) - rk = Ratekeeper(service_list[s].frequency, print_delay_threshold=None) - smsgs = [m for m in msgs if m.which() == s] - while True: - for m in smsgs: - new_m = m.as_builder() - new_m.logMonoTime = int(sec_since_boot() * 1e9) - - for evt in new_m.sensorEvents: - evt.timestamp = new_m.logMonoTime - - pm.send(s, new_m) - rk.keep_time() - - def replay_sensor_event(s, msgs): - smsgs = [m for m in msgs if m.which() == s] - #if len(smsgs) == 0: - # return - pm = messaging.PubMaster([s, ]) rk = Ratekeeper(service_list[s].frequency, print_delay_threshold=None) - + smsgs = [m for m in msgs if m.which() == s] while True: for m in smsgs: m = m.as_builder() @@ -213,12 +193,12 @@ def migrate_carparams(lr): def migrate_sensorEvents(lr): all_msgs = [] for msg in lr: - if msg.which() != 'sensorEvents': + if msg.which() != 'sensorEventsDEPRECATED': all_msgs.append(msg) continue # migrate to split sensor events - for evt in msg.sensorEvents: + for evt in msg.sensorEventsDEPRECATED: # build new message for each sensor type sensor_service = '' if evt.which() == 'acceleration': @@ -244,9 +224,6 @@ def migrate_sensorEvents(lr): all_msgs.append(m.as_reader()) - # append also legacy sensorEvents, to have both (remove later) - all_msgs.append(msg) - return all_msgs @@ -270,7 +247,6 @@ def regen_segment(lr, frs=None, outdir=FAKEDATA, disable_tqdm=False): vs, cam_procs = replay_cameras(lr, frs, disable_tqdm=disable_tqdm) fake_daemons = { 'sensord': [ - multiprocessing.Process(target=replay_sensor_events, args=('sensorEvents', lr)), multiprocessing.Process(target=replay_sensor_event, args=('accelerometer', lr)), multiprocessing.Process(target=replay_sensor_event, args=('gyroscope', lr)), multiprocessing.Process(target=replay_sensor_event, args=('magnetometer', lr)), diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index 0e4f47963b..cf836f3db7 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -29,7 +29,7 @@ REPEAT_COUNTER = 5 PRINT_DECIMATION = 100 STEER_RATIO = 15. -pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'sensorEvents', 'can', "gpsLocationExternal"]) +pm = messaging.PubMaster(['roadCameraState', 'wideRoadCameraState', 'accelerometer', 'gyroscope', 'can', "gpsLocationExternal"]) sm = messaging.SubMaster(['carControl', 'controlsState']) def parse_args(add_args=None): @@ -123,17 +123,20 @@ class Camerad: def imu_callback(imu, vehicle_state): vehicle_state.bearing_deg = math.degrees(imu.compass) - dat = messaging.new_message('sensorEvents', 2) - dat.sensorEvents[0].sensor = 4 - dat.sensorEvents[0].type = 0x10 - dat.sensorEvents[0].init('acceleration') - dat.sensorEvents[0].acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z] + dat = messaging.new_message('accelerometer') + dat.accelerometer.sensor = 4 + dat.accelerometer.type = 0x1 + dat.accelerometer.init('acceleration') + dat.accelerometer.acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z] + pm.send('accelerometer', dat) + # copied these numbers from locationd - dat.sensorEvents[1].sensor = 5 - dat.sensorEvents[1].type = 0x10 - dat.sensorEvents[1].init('gyroUncalibrated') - dat.sensorEvents[1].gyroUncalibrated.v = [imu.gyroscope.x, imu.gyroscope.y, imu.gyroscope.z] - pm.send('sensorEvents', dat) + dat = messaging.new_message('gyroscope') + dat.gyroscope.sensor = 5 + dat.gyroscope.type = 0x10 + dat.gyroscope.init('gyroUncalibrated') + dat.gyroscope.gyroUncalibrated.v = [imu.gyroscope.x, imu.gyroscope.y, imu.gyroscope.z] + pm.send('gyroscope', dat) def panda_state_function(vs: VehicleState, exit_event: threading.Event): From 4bc175bb9c9dd5976d1dba40abae716bb4a207ba Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Thu, 29 Sep 2022 16:36:43 -0700 Subject: [PATCH 039/178] Live torque fix (#25868) * fix np empty array quirk * reset to offline values if saved values were not valid live * edit cloudlog text --- selfdrive/locationd/torqued.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 9f07008214..78c3029af4 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -42,7 +42,7 @@ def slope2rot(slope): return np.array([[cos, -sin], [sin, cos]]) -class npqueue: +class NPQueue: def __init__(self, maxlen, rowsize): self.maxlen = maxlen self.arr = np.empty((0, rowsize)) @@ -61,7 +61,7 @@ class npqueue: class PointBuckets: def __init__(self, x_bounds, min_points): self.x_bounds = x_bounds - self.buckets = {bounds: npqueue(maxlen=POINTS_PER_BUCKET, rowsize=3) for bounds in x_bounds} + self.buckets = {bounds: NPQueue(maxlen=POINTS_PER_BUCKET, rowsize=3) for bounds in x_bounds} self.buckets_min_points = {bounds: min_point for bounds, min_point in zip(x_bounds, min_points)} def bucket_lengths(self): @@ -80,7 +80,7 @@ class PointBuckets: break def get_points(self, num_points=None): - points = np.concatenate([x.arr for x in self.buckets.values() if len(x) > 0]) + points = np.vstack([x.arr for x in self.buckets.values()]) if num_points is None: return points return points[np.random.choice(np.arange(len(points)), min(len(points), num_points), replace=False)] @@ -127,12 +127,13 @@ class TorqueEstimator: cache_ltp = log.Event.from_bytes(torque_cache).liveTorqueParameters cache_CP = car.CarParams.from_bytes(params_cache) if self.get_restore_key(cache_CP, cache_ltp.version) == self.get_restore_key(CP, VERSION): - initial_params = { - 'latAccelFactor': cache_ltp.latAccelFactorFiltered, - 'latAccelOffset': cache_ltp.latAccelOffsetFiltered, - 'frictionCoefficient': cache_ltp.frictionCoefficientFiltered, - 'points': cache_ltp.points - } + if cache_ltp.liveValid: + initial_params = { + 'latAccelFactor': cache_ltp.latAccelFactorFiltered, + 'latAccelOffset': cache_ltp.latAccelOffsetFiltered, + 'frictionCoefficient': cache_ltp.frictionCoefficientFiltered + } + initial_params['points'] = cache_ltp.points self.decay = cache_ltp.decay self.filtered_points.load_points(initial_params['points']) cloudlog.info("restored torque params from cache") @@ -224,7 +225,7 @@ class TorqueEstimator: self.update_params({'latAccelFactor': latAccelFactor, 'latAccelOffset': latAccelOffset, 'frictionCoefficient': friction_coeff}) self.invalid_values_tracker = max(0.0, self.invalid_values_tracker - 0.5) else: - cloudlog.exception("live torque params are numerically unstable") + cloudlog.exception("Live torque parameters are outside acceptable bounds.") liveTorqueParameters.liveValid = False self.invalid_values_tracker += 1.0 # Reset when ~10 invalid over 5 secs From c5514f344024f7523ba45dffe92e09147fb3704f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 29 Sep 2022 19:07:28 -0700 Subject: [PATCH 040/178] IsoTpParallelQuery: process all functional responses (#25930) * Revert "VIN: query physical addresses (#25122)" This reverts commit 0697ca223974f2361ca655506ceab7d18917b9de. * try sending tester present * do CAN fingerprinting first * looks like we can get rid of this! * remove import * no cache for testing * revert * revert * move function to fw_versions * Exception * Revert fp order, sleep to let PubSocket connect * comment comment * space * Update selfdrive/car/car_helpers.py * at 0.06 is where it becomes more consistent * treat functional addrs like physical addrs (process all responses) * fixes and debugging * fix * revert other changes * Update selfdrive/car/isotp_parallel_query.py * caps --- selfdrive/car/isotp_parallel_query.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 31dc31d7a4..5bc27770cd 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -16,17 +16,19 @@ class IsoTpParallelQuery: self.request = request self.response = response self.debug = debug - self.functional_addr = functional_addr self.response_pending_timeout = response_pending_timeout - self.real_addrs = [] - for a in addrs: - if isinstance(a, tuple): - self.real_addrs.append(a) - else: - self.real_addrs.append((a, None)) + if functional_addr: + assert all([a in FUNCTIONAL_ADDRS for a in addrs]), "Non-functional addresses in addrs" + real_addrs = [] + if 0x7DF in addrs: + real_addrs.extend([(0x7E0 + i, None) for i in range(8)]) + if 0x18DB33F1 in addrs: + real_addrs.extend([(0x18DA00F1 + (i << 8), None) for i in range(256)]) + else: + real_addrs = [a if isinstance(a, tuple) else (a, None) for a in addrs] - self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in self.real_addrs} + self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in real_addrs} self.msg_buffer = defaultdict(list) def rx(self): @@ -35,13 +37,8 @@ class IsoTpParallelQuery: for packet in can_packets: for msg in packet.can: - if msg.src == self.bus: - if self.functional_addr: - if (0x7E8 <= msg.address <= 0x7EF) or (0x18DAF100 <= msg.address <= 0x18DAF1FF): - fn_addr = next(a for a in FUNCTIONAL_ADDRS if msg.address - a <= 32) - self.msg_buffer[fn_addr].append((msg.address, msg.busTime, msg.dat, msg.src)) - elif msg.address in self.msg_addrs.values(): - self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src)) + if msg.src == self.bus and msg.address in self.msg_addrs.values(): + self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src)) def _can_tx(self, tx_addr, dat, bus): """Helper function to send single message""" From 9e2cca23cfefd21483f78b0712583d564a6a3539 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 29 Sep 2022 21:15:34 -0700 Subject: [PATCH 041/178] Revert "IsoTpParallelQuery: process all functional responses (#25930)" This reverts commit c5514f344024f7523ba45dffe92e09147fb3704f. --- selfdrive/car/isotp_parallel_query.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 5bc27770cd..31dc31d7a4 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -16,19 +16,17 @@ class IsoTpParallelQuery: self.request = request self.response = response self.debug = debug + self.functional_addr = functional_addr self.response_pending_timeout = response_pending_timeout - if functional_addr: - assert all([a in FUNCTIONAL_ADDRS for a in addrs]), "Non-functional addresses in addrs" - real_addrs = [] - if 0x7DF in addrs: - real_addrs.extend([(0x7E0 + i, None) for i in range(8)]) - if 0x18DB33F1 in addrs: - real_addrs.extend([(0x18DA00F1 + (i << 8), None) for i in range(256)]) - else: - real_addrs = [a if isinstance(a, tuple) else (a, None) for a in addrs] + self.real_addrs = [] + for a in addrs: + if isinstance(a, tuple): + self.real_addrs.append(a) + else: + self.real_addrs.append((a, None)) - self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in real_addrs} + self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in self.real_addrs} self.msg_buffer = defaultdict(list) def rx(self): @@ -37,8 +35,13 @@ class IsoTpParallelQuery: for packet in can_packets: for msg in packet.can: - if msg.src == self.bus and msg.address in self.msg_addrs.values(): - self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src)) + if msg.src == self.bus: + if self.functional_addr: + if (0x7E8 <= msg.address <= 0x7EF) or (0x18DAF100 <= msg.address <= 0x18DAF1FF): + fn_addr = next(a for a in FUNCTIONAL_ADDRS if msg.address - a <= 32) + self.msg_buffer[fn_addr].append((msg.address, msg.busTime, msg.dat, msg.src)) + elif msg.address in self.msg_addrs.values(): + self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src)) def _can_tx(self, tx_addr, dat, bus): """Helper function to send single message""" From 5d33199905cbf9d9b45ef722a40530b08d5cecf4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 29 Sep 2022 22:36:09 -0700 Subject: [PATCH 042/178] sim: fix sensor freq and timestamps (#25937) * sim: fix sensor freq and timestamps * 100hz * fix that too --- selfdrive/locationd/locationd.cc | 4 ++-- tools/sim/bridge.py | 36 +++++++++++++++++++------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 087710d846..ac340fb4aa 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -499,7 +499,7 @@ int Localizer::locationd_thread() { } const std::initializer_list service_list = {gps_location_socket, "cameraOdometry", "liveCalibration", "carState", "carParams", - "accelerometer", "gyroscope", "magnetometer"}; + "accelerometer", "gyroscope"}; PubMaster pm({"liveLocationKalman"}); // TODO: remove carParams once we're always sending at 100Hz @@ -526,7 +526,7 @@ int Localizer::locationd_thread() { if (sm.updated(trigger_msg)) { bool inputsOK = sm.allAliveAndValid(); bool gpsOK = this->isGpsOK(); - bool sensorsOK = sm.allAliveAndValid({"accelerometer", "gyroscope", "magnetometer"}); + bool sensorsOK = sm.allAliveAndValid({"accelerometer", "gyroscope"}); MessageBuilder msg_builder; kj::ArrayPtr bytes = this->get_message_bytes(msg_builder, inputsOK, sensorsOK, gpsOK, filterInitialized); diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index cf836f3db7..c400eb93f5 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -122,21 +122,26 @@ class Camerad: pm.send(pub_type, dat) def imu_callback(imu, vehicle_state): - vehicle_state.bearing_deg = math.degrees(imu.compass) - dat = messaging.new_message('accelerometer') - dat.accelerometer.sensor = 4 - dat.accelerometer.type = 0x1 - dat.accelerometer.init('acceleration') - dat.accelerometer.acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z] - pm.send('accelerometer', dat) - - # copied these numbers from locationd - dat = messaging.new_message('gyroscope') - dat.gyroscope.sensor = 5 - dat.gyroscope.type = 0x10 - dat.gyroscope.init('gyroUncalibrated') - dat.gyroscope.gyroUncalibrated.v = [imu.gyroscope.x, imu.gyroscope.y, imu.gyroscope.z] - pm.send('gyroscope', dat) + # send 5x since 'sensor_tick' doesn't seem to work. limited by the world tick? + for _ in range(5): + vehicle_state.bearing_deg = math.degrees(imu.compass) + dat = messaging.new_message('accelerometer') + dat.accelerometer.sensor = 4 + dat.accelerometer.type = 0x1 + dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp + dat.accelerometer.init('acceleration') + dat.accelerometer.acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z] + pm.send('accelerometer', dat) + + # copied these numbers from locationd + dat = messaging.new_message('gyroscope') + dat.gyroscope.sensor = 5 + dat.gyroscope.type = 0x10 + dat.gyroscope.timestamp = dat.logMonoTime # TODO: use the IMU timestamp + dat.gyroscope.init('gyroUncalibrated') + dat.gyroscope.gyroUncalibrated.v = [imu.gyroscope.x, imu.gyroscope.y, imu.gyroscope.z] + pm.send('gyroscope', dat) + time.sleep(0.01) def panda_state_function(vs: VehicleState, exit_event: threading.Event): @@ -351,6 +356,7 @@ class CarlaBridge: # re-enable IMU imu_bp = blueprint_library.find('sensor.other.imu') + imu_bp.set_attribute('sensor_tick', '0.01') imu = world.spawn_actor(imu_bp, transform, attach_to=vehicle) imu.listen(lambda imu: imu_callback(imu, vehicle_state)) From a75e85045e19016efd9e038fa2e8b53d50ac6c35 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 30 Sep 2022 12:24:56 -0700 Subject: [PATCH 043/178] build_devel: only fetch target branches (#25941) --- release/build_devel.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/release/build_devel.sh b/release/build_devel.sh index fc3f8184a2..7108334808 100755 --- a/release/build_devel.sh +++ b/release/build_devel.sh @@ -12,10 +12,7 @@ fi # set git identity source $DIR/identity.sh -echo "[-] Setting up repo T=$SECONDS" - -cd $SOURCE_DIR -git fetch origin +echo "[-] Setting up target repo T=$SECONDS" rm -rf $TARGET_DIR mkdir -p $TARGET_DIR From 80c40048cb62fff15a8e884cb05fba8ddfb11809 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 30 Sep 2022 12:25:11 -0700 Subject: [PATCH 044/178] sensord: test fixups (#25935) * sensord: test fixups * fix linter * check freq * fp seems reliable * clean that up * update refs Co-authored-by: Comma Device Co-authored-by: Bruce Wayne --- selfdrive/sensord/tests/test_sensord.py | 124 ++++++++---------- .../test/process_replay/process_replay.py | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 57 insertions(+), 71 deletions(-) mode change 100644 => 100755 selfdrive/sensord/tests/test_sensord.py diff --git a/selfdrive/sensord/tests/test_sensord.py b/selfdrive/sensord/tests/test_sensord.py old mode 100644 new mode 100755 index 1af76c8384..82bd28446b --- a/selfdrive/sensord/tests/test_sensord.py +++ b/selfdrive/sensord/tests/test_sensord.py @@ -10,41 +10,33 @@ from cereal import log from system.hardware import TICI, HARDWARE from selfdrive.manager.process_config import managed_processes +BMX = { + ('bmx055', 'acceleration'), + ('bmx055', 'gyroUncalibrated'), + ('bmx055', 'magneticUncalibrated'), + ('bmx055', 'temperature'), +} + +LSM = { + ('lsm6ds3', 'acceleration'), + ('lsm6ds3', 'gyroUncalibrated'), + ('lsm6ds3', 'temperature'), +} +LSM_C = {(x[0]+'trc', x[1]) for x in LSM} + +MMC = { + ('mmc5603nj', 'magneticUncalibrated'), +} + +RPR = { + ('rpr0521', 'light'), +} + SENSOR_CONFIGURATIONS = ( - { - ('bmx055', 'acceleration'), - ('bmx055', 'gyroUncalibrated'), - ('bmx055', 'magneticUncalibrated'), - ('bmx055', 'temperature'), - ('lsm6ds3', 'acceleration'), - ('lsm6ds3', 'gyroUncalibrated'), - ('lsm6ds3', 'temperature'), - ('rpr0521', 'light'), - }, - { - ('lsm6ds3', 'acceleration'), - ('lsm6ds3', 'gyroUncalibrated'), - ('lsm6ds3', 'temperature'), - ('mmc5603nj', 'magneticUncalibrated'), - ('rpr0521', 'light'), - }, - { - ('bmx055', 'acceleration'), - ('bmx055', 'gyroUncalibrated'), - ('bmx055', 'magneticUncalibrated'), - ('bmx055', 'temperature'), - ('lsm6ds3trc', 'acceleration'), - ('lsm6ds3trc', 'gyroUncalibrated'), - ('lsm6ds3trc', 'temperature'), - ('rpr0521', 'light'), - }, - { - ('lsm6ds3trc', 'acceleration'), - ('lsm6ds3trc', 'gyroUncalibrated'), - ('lsm6ds3trc', 'temperature'), - ('mmc5603nj', 'magneticUncalibrated'), - ('rpr0521', 'light'), - }, + (BMX | LSM | RPR), + (MMC | LSM | RPR), + (BMX | LSM_C | RPR), + (MMC| LSM_C | RPR), ) Sensor = log.SensorEventData.SensorSource @@ -78,19 +70,14 @@ ALL_SENSORS = { } } -LSM_INT_GPIO = 84 - -def get_proc_interrupts(int_pin): - with open("/proc/interrupts") as f: - lines = f.read().split("\n") +LSM_IRQ = 336 - for line in lines: - if f" {int_pin} " in line: - return ''.join(list(filter(lambda e: e != '', line.split(' ')))[1:6]) - return "" +def get_irq_count(irq: int): + with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f: + per_cpu = map(int, f.read().split(",")) + return sum(per_cpu) def read_sensor_events(duration_sec): - sensor_types = ['accelerometer', 'gyroscope', 'magnetometer', 'accelerometer2', 'gyroscope2', 'lightSensor', 'temperatureSensor'] esocks = {} @@ -104,8 +91,7 @@ def read_sensor_events(duration_sec): events[esock] += messaging.drain_sock(esocks[esock]) time.sleep(0.1) - for etype in events: - assert len(events[etype]) != 0, f"No {etype} events collected" + assert sum(map(len, events.values())) != 0, "No sensor events collected!" return events @@ -120,11 +106,14 @@ class TestSensord(unittest.TestCase): # read initial sensor values every test case can use os.system("pkill -f ./_sensord") - managed_processes["sensord"].start() - time.sleep(3) - cls.sample_secs = 10 - cls.events = read_sensor_events(cls.sample_secs) - managed_processes["sensord"].stop() + try: + managed_processes["sensord"].start() + time.sleep(3) + cls.sample_secs = 10 + cls.events = read_sensor_events(cls.sample_secs) + finally: + # teardown won't run if this doesn't succeed + managed_processes["sensord"].stop() @classmethod def tearDownClass(cls): @@ -168,9 +157,9 @@ class TestSensord(unittest.TestCase): high_delay_diffs = list(filter(lambda d: d >= 20., tdiffs)) assert len(high_delay_diffs) < 15, f"Too many large diffs: {high_delay_diffs}" - # 100-108Hz, expected 104Hz avg_diff = sum(tdiffs)/len(tdiffs) - assert 9.3 < avg_diff < 10., f"avg difference {avg_diff}, below threshold" + avg_freq = 1. / (avg_diff * 1e-3) + assert 92. < avg_freq < 114., f"avg freq {avg_freq}Hz wrong, expected 104Hz" stddev = np.std(tdiffs) assert stddev < 2.0, f"Standard-dev to big {stddev}" @@ -201,26 +190,24 @@ class TestSensord(unittest.TestCase): m = getattr(measurement, measurement.which()) # check if gyro and accel timestamps are before logMonoTime - if str(m.source).startswith("lsm6ds3"): - if m.which() != 'temperature': - err_msg = f"Timestamp after logMonoTime: {m.timestamp} > {measurement.logMonoTime}" - assert m.timestamp < measurement.logMonoTime, err_msg + if str(m.source).startswith("lsm6ds3") and m.which() != 'temperature': + err_msg = f"Timestamp after logMonoTime: {m.timestamp} > {measurement.logMonoTime}" + assert m.timestamp < measurement.logMonoTime, err_msg # negative values might occur, as non interrupt packages created # before the sensor is read - tdiffs.append(abs(measurement.logMonoTime - m.timestamp)) + tdiffs.append(abs(measurement.logMonoTime - m.timestamp) / 1e6) - high_delay_diffs = set(filter(lambda d: d >= 10*10**6, tdiffs)) - assert len(high_delay_diffs) < 15, f"Too many high delay packages: {high_delay_diffs}" + high_delay_diffs = set(filter(lambda d: d >= 15., tdiffs)) + assert len(high_delay_diffs) < 20, f"Too many measurements published : {high_delay_diffs}" avg_diff = round(sum(tdiffs)/len(tdiffs), 4) - assert avg_diff < 4*10**6, f"Avg packet diff: {avg_diff:.1f}ns" + assert avg_diff < 4, f"Avg packet diff: {avg_diff:.1f}ms" stddev = np.std(tdiffs) - assert stddev < 2*10**6, f"Timing diffs have to high stddev: {stddev}" + assert stddev < 2, f"Timing diffs have too high stddev: {stddev}" def test_sensor_values_sanity_check(self): - sensor_values = dict() for etype in self.events: for measurement in self.events[etype]: @@ -239,7 +226,6 @@ class TestSensord(unittest.TestCase): # Sanity check sensor values and counts for sensor, stype in sensor_values: - for s in ALL_SENSORS[sensor]: if s.type != stype: continue @@ -255,14 +241,13 @@ class TestSensord(unittest.TestCase): assert s.sanity_min <= mean_norm <= s.sanity_max, err_msg def test_sensor_verify_no_interrupts_after_stop(self): - managed_processes["sensord"].start() time.sleep(3) # read /proc/interrupts to verify interrupts are received - state_one = get_proc_interrupts(LSM_INT_GPIO) + state_one = get_irq_count(LSM_IRQ) time.sleep(1) - state_two = get_proc_interrupts(LSM_INT_GPIO) + state_two = get_irq_count(LSM_IRQ) error_msg = f"no interrupts received after sensord start!\n{state_one} {state_two}" assert state_one != state_two, error_msg @@ -271,10 +256,11 @@ class TestSensord(unittest.TestCase): time.sleep(1) # read /proc/interrupts to verify no more interrupts are received - state_one = get_proc_interrupts(LSM_INT_GPIO) + state_one = get_irq_count(LSM_IRQ) time.sleep(1) - state_two = get_proc_interrupts(LSM_INT_GPIO) + state_two = get_irq_count(LSM_IRQ) assert state_one == state_two, "Interrupts received after sensord stop!" + if __name__ == "__main__": unittest.main() diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index c8389bb71c..10084bff9f 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -328,7 +328,7 @@ CONFIGS = [ proc_name="locationd", pub_sub={ "cameraOdometry": ["liveLocationKalman"], - "accelerometer": [], "gyroscope": [], "magnetometer": [], + "accelerometer": [], "gyroscope": [], "gpsLocationExternal": [], "liveCalibration": [], "carState": [], }, ignore=["logMonoTime", "valid"], diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index e308d2dff4..7485942244 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -761eada809a0eaa67989e6e435042633f965d1fe \ No newline at end of file +5d33199905cbf9d9b45ef722a40530b08d5cecf4 \ No newline at end of file From 75735675bddd39abf01fc1e1c8f004394559f51f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Sep 2022 14:49:27 -0700 Subject: [PATCH 045/178] process replay: rename second HYUNDAI entry --- selfdrive/test/process_replay/test_processes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 5ad947b225..28134385d5 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -18,7 +18,7 @@ from tools.lib.logreader import LogReader source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA - ("HYUNDAI", "d824e27e8c60172c|2022-09-13--11-26-50--2"), # HYUNDAI.KIA_EV6 + ("HYUNDAI2", "d824e27e8c60172c|2022-09-13--11-26-50--2"), # HYUNDAI.KIA_EV6 ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 @@ -40,7 +40,7 @@ source_segments = [ segments = [ ("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"), ("HYUNDAI", "regenBE53A59065B|2022-09-27--16-52-03--0"), - ("HYUNDAI", "regen11AA43BCA5F|2022-09-27--15-39-54--0"), + ("HYUNDAI2", "regen11AA43BCA5F|2022-09-27--15-39-54--0"), ("TOYOTA", "regen929C5790007|2022-09-27--16-27-47--0"), ("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"), ("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--0"), From bea960675f2ae7d2d9e3c95d8c79fde6b61d93d0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Sep 2022 15:15:13 -0700 Subject: [PATCH 046/178] IsoTpParallelQuery: process all functional responses (#25939) * stash * process all functional addrs (stash) * clean up * rm * simplify * let user pass in tx for rx addrs * revert panda * simplify * comment order * need to go by rx_addr now * Revert "need to go by rx_addr now" This reverts commit 1197ecfbc5b9e5df20b523a0623f644cd8cae1ef. * stash * should also work * this seems pretty clean * not used * properly use * comment * some fixes * some fixes * send consecutive frames on physical addrs * bump panda * looks better * setup_only * Revert VIN changes * rev * bump panda to master * Update selfdrive/car/isotp_parallel_query.py --- panda | 2 +- selfdrive/car/isotp_parallel_query.py | 54 +++++++++++++-------------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/panda b/panda index 51f023bc66..1910db8d4c 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 51f023bc66c2caa9007be1dda2738d0df51cbf0e +Subproject commit 1910db8d4c3f932fe85b186fba1d24795cb2b742 diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 31dc31d7a4..d3e4972bd8 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -9,24 +9,21 @@ from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_a class IsoTpParallelQuery: - def __init__(self, sendcan, logcan, bus, addrs, request, response, response_offset=0x8, functional_addr=False, debug=False, response_pending_timeout=10): + def __init__(self, sendcan, logcan, bus, addrs, request, response, response_offset=0x8, functional_addrs=None, debug=False, response_pending_timeout=10): self.sendcan = sendcan self.logcan = logcan self.bus = bus self.request = request self.response = response + self.functional_addrs = functional_addrs or [] self.debug = debug - self.functional_addr = functional_addr self.response_pending_timeout = response_pending_timeout - self.real_addrs = [] - for a in addrs: - if isinstance(a, tuple): - self.real_addrs.append(a) - else: - self.real_addrs.append((a, None)) + real_addrs = [a if isinstance(a, tuple) else (a, None) for a in addrs] + for tx_addr, _ in real_addrs: + assert tx_addr not in FUNCTIONAL_ADDRS, f"Functional address should be defined in functional_addrs: {hex(tx_addr)}" - self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in self.real_addrs} + self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in real_addrs} self.msg_buffer = defaultdict(list) def rx(self): @@ -35,13 +32,8 @@ class IsoTpParallelQuery: for packet in can_packets: for msg in packet.can: - if msg.src == self.bus: - if self.functional_addr: - if (0x7E8 <= msg.address <= 0x7EF) or (0x18DAF100 <= msg.address <= 0x18DAF1FF): - fn_addr = next(a for a in FUNCTIONAL_ADDRS if msg.address - a <= 32) - self.msg_buffer[fn_addr].append((msg.address, msg.busTime, msg.dat, msg.src)) - elif msg.address in self.msg_addrs.values(): - self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src)) + if msg.src == self.bus and msg.address in self.msg_addrs.values(): + self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src)) def _can_tx(self, tx_addr, dat, bus): """Helper function to send single message""" @@ -71,6 +63,13 @@ class IsoTpParallelQuery: messaging.drain_sock(self.logcan) self.msg_buffer = defaultdict(list) + def _create_isotp_msg(self, tx_addr, sub_addr, rx_addr): + can_client = CanClient(self._can_tx, partial(self._can_rx, rx_addr, sub_addr=sub_addr), tx_addr, rx_addr, + self.bus, sub_addr=sub_addr, debug=self.debug) + + max_len = 8 if sub_addr is None else 7 + return IsoTpMessage(can_client, timeout=0, max_len=max_len, debug=self.debug) + def get_data(self, timeout, total_timeout=60.): self._drain_rx() @@ -79,22 +78,19 @@ class IsoTpParallelQuery: request_counter = {} request_done = {} for tx_addr, rx_addr in self.msg_addrs.items(): - # rx_addr not set when using functional tx addr - id_addr = rx_addr or tx_addr[0] - sub_addr = tx_addr[1] - - can_client = CanClient(self._can_tx, partial(self._can_rx, id_addr, sub_addr=sub_addr), tx_addr[0], rx_addr, - self.bus, sub_addr=sub_addr, debug=self.debug) - - max_len = 8 if sub_addr is None else 7 - - msg = IsoTpMessage(can_client, timeout=0, max_len=max_len, debug=self.debug) - msg.send(self.request[0]) - - msgs[tx_addr] = msg + msgs[tx_addr] = self._create_isotp_msg(*tx_addr, rx_addr) request_counter[tx_addr] = 0 request_done[tx_addr] = False + # Send first request to functional addrs, subsequent responses are handled on physical addrs + if len(self.functional_addrs): + for addr in self.functional_addrs: + self._create_isotp_msg(addr, None, -1).send(self.request[0]) + + # If querying functional addrs, set up physical IsoTpMessages to send consecutive frames + for msg in msgs.values(): + msg.send(self.request[0], setup_only=len(self.functional_addrs) > 0) + results = {} start_time = time.monotonic() response_timeouts = {tx_addr: start_time + timeout for tx_addr in self.msg_addrs} From 4bd146ee7af639f4a49e3d5cdeeab4ed022190ec Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Sep 2022 15:43:04 -0700 Subject: [PATCH 047/178] Longitudinal planner: clip a_desired to cruise limits (#25928) * Clip a_desired to cruise limits * Update selfdrive/controls/lib/longitudinal_planner.py * fix * update refs * explicit --- selfdrive/controls/lib/longitudinal_planner.py | 10 ++++++---- selfdrive/test/process_replay/ref_commit | 2 +- selfdrive/test/process_replay/test_processes.py | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 45a6db2e90..6ec3762a94 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import math import numpy as np -from common.numpy_fast import interp +from common.numpy_fast import clip, interp import cereal.messaging as messaging from common.conversions import Conversions as CV @@ -106,15 +106,17 @@ class LongitudinalPlanner: # No change cost when user is controlling the speed, or when standstill prev_accel_constraint = not (reset_state or sm['carState'].standstill) + accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)] + accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP) + if reset_state: self.v_desired_filter.x = v_ego - self.a_desired = 0.0 + # 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]) # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) - accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)] - 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) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 7485942244..1f459b1948 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -5d33199905cbf9d9b45ef722a40530b08d5cecf4 \ No newline at end of file +53079010a5db8105854212157b5ee90029df7b92 \ No newline at end of file diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 28134385d5..38ed0e07ad 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -18,7 +18,7 @@ from tools.lib.logreader import LogReader source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA - ("HYUNDAI2", "d824e27e8c60172c|2022-09-13--11-26-50--2"), # HYUNDAI.KIA_EV6 + ("HYUNDAI2", "d824e27e8c60172c|2022-09-13--11-26-50--2"), # HYUNDAI.KIA_EV6 ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 From 4d7f4b4c9db739c85989797b750226b0ec217f71 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Sep 2022 16:01:22 -0700 Subject: [PATCH 048/178] IsoTpParallelQuery: don't return rx_addr (#25934) * revert isotpparallelquery returning rx addr for functional special case * we don't really use the tx addr (and soon won't make sense with fun querying) --- selfdrive/car/car_helpers.py | 2 +- selfdrive/car/fw_versions.py | 10 +++++----- selfdrive/car/isotp_parallel_query.py | 2 +- selfdrive/car/vin.py | 11 ++++++----- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 4f098fadb5..273364071b 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -97,7 +97,7 @@ def fingerprint(logcan, sendcan): car_fw = list(cached_params.carFw) else: cloudlog.warning("Getting VIN & FW versions") - _, vin_rx_addr, vin = get_vin(logcan, sendcan, bus) + vin_rx_addr, vin = get_vin(logcan, sendcan, bus) ecu_rx_addrs = get_present_ecus(logcan, sendcan) car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index bf88e77db5..390deab52e 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -253,13 +253,13 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, if addrs: query = IsoTpParallelQuery(sendcan, logcan, r.bus, addrs, r.request, r.response, r.rx_offset, debug=debug) - for (addr, rx_addr), version in query.get_data(timeout).items(): + for (tx_addr, sub_addr), version in query.get_data(timeout).items(): f = car.CarParams.CarFw.new_message() - f.ecu = ecu_types.get((brand, addr[0], addr[1]), Ecu.unknown) + f.ecu = ecu_types.get((brand, tx_addr, sub_addr), Ecu.unknown) f.fwVersion = version f.address = addr[0] - f.responseAddress = rx_addr + f.responseAddress = uds.get_rx_addr_for_tx_addr(tx_addr, r.rx_offset) f.request = r.request f.brand = brand f.bus = r.bus @@ -303,8 +303,8 @@ if __name__ == "__main__": t = time.time() print("Getting vin...") - addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) - print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}') + vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) + print(f'RX: {hex(vin_rx_addr)}, VIN: {vin}') print(f"Getting VIN took {time.time() - t:.3f} s") print() diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index d3e4972bd8..94c8d052b3 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -123,7 +123,7 @@ class IsoTpParallelQuery: msg.send(self.request[counter + 1]) request_counter[tx_addr] += 1 else: - results[(tx_addr, msg._can_client.rx_addr)] = dat[len(expected_response):] + results[tx_addr] = dat[len(expected_response):] request_done[tx_addr] = True else: error_code = dat[2] if len(dat) > 2 else -1 diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 860f3b0fa2..2974cebada 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -2,6 +2,7 @@ import re import cereal.messaging as messaging +from panda.python.uds import get_rx_addr_for_tx_addr from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery from selfdrive.car.fw_query_definitions import StdQueries from system.swaglog import cloudlog @@ -20,18 +21,18 @@ def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False): for request, response in ((StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE), (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE)): try: query = IsoTpParallelQuery(sendcan, logcan, bus, addrs, [request, ], [response, ], debug=debug) - for (addr, rx_addr), vin in query.get_data(timeout).items(): + for (tx_addr, _), vin in query.get_data(timeout).items(): # Honda Bosch response starts with a length, trim to correct length if vin.startswith(b'\x11'): vin = vin[1:18] - return addr[0], rx_addr, vin.decode() + return get_rx_addr_for_tx_addr(tx_addr), vin.decode() cloudlog.error(f"vin query retry ({i+1}) ...") except Exception: cloudlog.exception("VIN query exception") - return 0, 0, VIN_UNKNOWN + return 0, VIN_UNKNOWN if __name__ == "__main__": @@ -49,5 +50,5 @@ if __name__ == "__main__": logcan = messaging.sub_sock('can') time.sleep(1) - addr, vin_rx_addr, vin = get_vin(logcan, sendcan, args.bus, args.timeout, args.retry, debug=args.debug) - print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}') + vin_rx_addr, vin = get_vin(logcan, sendcan, args.bus, args.timeout, args.retry, debug=args.debug) + print(f'RX: {hex(vin_rx_addr)}, VIN: {vin}') From 86f0632afdafe75869e07dd7e329f866579e60c9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Sep 2022 17:44:48 -0700 Subject: [PATCH 049/178] Use tx_addr/sub_addr from query --- selfdrive/car/fw_versions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 390deab52e..7e03f4b020 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -258,14 +258,14 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, f.ecu = ecu_types.get((brand, tx_addr, sub_addr), Ecu.unknown) f.fwVersion = version - f.address = addr[0] + f.address = tx_addr f.responseAddress = uds.get_rx_addr_for_tx_addr(tx_addr, r.rx_offset) f.request = r.request f.brand = brand f.bus = r.bus - if addr[1] is not None: - f.subAddress = addr[1] + if sub_addr is not None: + f.subAddress = sub_addr car_fw.append(f) except Exception: From 68cc53a0852fc1ec620c81d996b868fa26048ca2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Sep 2022 20:29:54 -0700 Subject: [PATCH 050/178] VIN query: switch to functional address (#25933) * stash * go in defined order * \n feels cleaner --- selfdrive/car/vin.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 2974cebada..50c2abde46 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -2,7 +2,7 @@ import re import cereal.messaging as messaging -from panda.python.uds import get_rx_addr_for_tx_addr +from panda.python.uds import get_rx_addr_for_tx_addr, FUNCTIONAL_ADDRS from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery from selfdrive.car.fw_query_definitions import StdQueries from system.swaglog import cloudlog @@ -16,18 +16,23 @@ def is_valid_vin(vin: str): def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False): - addrs = [0x7e0, 0x7e2, 0x18da10f1, 0x18da0ef1] # engine, VMCU, 29-bit engine, PGM-FI + addrs = list(range(0x7e0, 0x7e8)) + list(range(0x18DA00F1, 0x18DB00F1, 0x100)) # addrs to process/wait for + valid_vin_addrs = [0x7e0, 0x7e2, 0x18da10f1, 0x18da0ef1] # engine, VMCU, 29-bit engine, PGM-FI for i in range(retry): for request, response in ((StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE), (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE)): try: - query = IsoTpParallelQuery(sendcan, logcan, bus, addrs, [request, ], [response, ], debug=debug) - for (tx_addr, _), vin in query.get_data(timeout).items(): + query = IsoTpParallelQuery(sendcan, logcan, bus, addrs, [request, ], [response, ], functional_addrs=FUNCTIONAL_ADDRS, debug=debug) + results = query.get_data(timeout) - # Honda Bosch response starts with a length, trim to correct length - if vin.startswith(b'\x11'): - vin = vin[1:18] + for addr in valid_vin_addrs: + vin = results.get((addr, None)) + if vin is not None: + # Honda Bosch response starts with a length, trim to correct length + if vin.startswith(b'\x11'): + vin = vin[1:18] + + return get_rx_addr_for_tx_addr(addr), vin.decode() - return get_rx_addr_for_tx_addr(tx_addr), vin.decode() cloudlog.error(f"vin query retry ({i+1}) ...") except Exception: cloudlog.exception("VIN query exception") From f6119603912efa4fe28f5da21f3d57f6903a0c77 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Sat, 1 Oct 2022 14:47:06 -0700 Subject: [PATCH 051/178] networking: add unmetered cellular toggle (#25902) * add metered toggle to UI * add GsmMetered param * add NMMetered constants * change LTE connection settings: connection.metered * change to GsmUnmetered override * update translations * debug ui * remove comment * Revert "debug ui" This reverts commit 2ad9e65ea229b814782be9f30cc7664125d7e908. * 'Force Unmetered Cellular' toggle * update translations * remove description * update translations * change unmetered to metered --- common/params.cc | 1 + selfdrive/athena/tests/helpers.py | 2 +- selfdrive/manager/manager.py | 1 + selfdrive/ui/qt/offroad/networking.cc | 18 +++++++++++++----- selfdrive/ui/qt/offroad/networkmanager.h | 7 +++++++ selfdrive/ui/qt/offroad/wifiManager.cc | 9 ++++++++- selfdrive/ui/qt/offroad/wifiManager.h | 2 +- selfdrive/ui/translations/main_ja.ts | 8 ++++++++ selfdrive/ui/translations/main_ko.ts | 8 ++++++++ selfdrive/ui/translations/main_pt-BR.ts | 8 ++++++++ selfdrive/ui/translations/main_zh-CHS.ts | 8 ++++++++ selfdrive/ui/translations/main_zh-CHT.ts | 8 ++++++++ 12 files changed, 72 insertions(+), 8 deletions(-) diff --git a/common/params.cc b/common/params.cc index 63208879b2..d4c5cd7caf 100644 --- a/common/params.cc +++ b/common/params.cc @@ -118,6 +118,7 @@ std::unordered_map keys = { {"GithubUsername", PERSISTENT}, {"GitRemote", PERSISTENT}, {"GsmApn", PERSISTENT}, + {"GsmMetered", PERSISTENT}, {"GsmRoaming", PERSISTENT}, {"HardwareSerial", PERSISTENT}, {"HasAcceptedTerms", PERSISTENT}, diff --git a/selfdrive/athena/tests/helpers.py b/selfdrive/athena/tests/helpers.py index 071393cb14..a43527c260 100644 --- a/selfdrive/athena/tests/helpers.py +++ b/selfdrive/athena/tests/helpers.py @@ -53,8 +53,8 @@ class MockParams(): default_params = { "DongleId": b"0000000000000000", "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501 + "GsmMetered": True, "AthenadUploadQueue": '[]', - "CellularUnmetered": False, } params = default_params.copy() diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index ff2bf4bc89..928507f65b 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -39,6 +39,7 @@ def manager_init() -> None: default_params: List[Tuple[str, Union[str, bytes]]] = [ ("CompletedTrainingVersion", "0"), ("DisengageOnAccelerator", "1"), + ("GsmMetered", "1"), ("HasAcceptedTerms", "0"), ("LanguageSetting", "main_en"), ("OpenpilotEnabledToggle", "1"), diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index 7ec8691feb..0ed6317c3c 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -151,16 +151,15 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid // Roaming toggle const bool roamingEnabled = params.getBool("GsmRoaming"); ToggleControl *roamingToggle = new ToggleControl(tr("Enable Roaming"), "", "", roamingEnabled); - QObject::connect(roamingToggle, &SshToggle::toggleFlipped, [=](bool state) { + QObject::connect(roamingToggle, &ToggleControl::toggleFlipped, [=](bool state) { params.putBool("GsmRoaming", state); - wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn"))); + wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")), params.getBool("GsmMetered")); }); list->addItem(roamingToggle); // APN settings ButtonControl *editApnButton = new ButtonControl(tr("APN Setting"), tr("EDIT")); connect(editApnButton, &ButtonControl::clicked, [=]() { - const bool roamingEnabled = params.getBool("GsmRoaming"); const QString cur_apn = QString::fromStdString(params.get("GsmApn")); QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed(); @@ -169,12 +168,21 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid } else { params.put("GsmApn", apn.toStdString()); } - wifi->updateGsmSettings(roamingEnabled, apn); + wifi->updateGsmSettings(params.getBool("GsmRoaming"), apn, params.getBool("GsmMetered")); }); list->addItem(editApnButton); + // Metered toggle + const bool metered = params.getBool("GsmMetered"); + ToggleControl *meteredToggle = new ToggleControl(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered connection"), "", metered); + QObject::connect(meteredToggle, &SshToggle::toggleFlipped, [=](bool state) { + params.putBool("GsmMetered", state); + wifi->updateGsmSettings(params.getBool("GsmRoaming"), QString::fromStdString(params.get("GsmApn")), state); + }); + list->addItem(meteredToggle); + // Set initial config - wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn"))); + wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered); main_layout->addWidget(new ScrollView(list, this)); main_layout->addStretch(1); diff --git a/selfdrive/ui/qt/offroad/networkmanager.h b/selfdrive/ui/qt/offroad/networkmanager.h index 52d85c16af..31b33fc9f5 100644 --- a/selfdrive/ui/qt/offroad/networkmanager.h +++ b/selfdrive/ui/qt/offroad/networkmanager.h @@ -36,3 +36,10 @@ const int NM_DEVICE_TYPE_WIFI = 2; const int NM_DEVICE_TYPE_MODEM = 8; const int NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8; const int DBUS_TIMEOUT = 100; + +// https://developer-old.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NMMetered +const int NM_METERED_UNKNOWN = 0; +const int NM_METERED_YES = 1; +const int NM_METERED_NO = 2; +const int NM_METERED_GUESS_YES = 3; +const int NM_METERED_GUESS_NO = 4; diff --git a/selfdrive/ui/qt/offroad/wifiManager.cc b/selfdrive/ui/qt/offroad/wifiManager.cc index fbb64b972e..3a30456c93 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.cc +++ b/selfdrive/ui/qt/offroad/wifiManager.cc @@ -345,7 +345,7 @@ NetworkType WifiManager::currentNetworkType() { return NetworkType::NONE; } -void WifiManager::updateGsmSettings(bool roaming, QString apn) { +void WifiManager::updateGsmSettings(bool roaming, QString apn, bool metered) { if (!lteConnectionPath.path().isEmpty()) { bool changes = false; bool auto_config = apn.isEmpty(); @@ -368,6 +368,13 @@ void WifiManager::updateGsmSettings(bool roaming, QString apn) { changes = true; } + int meteredInt = metered ? NM_METERED_NO : NM_METERED_UNKNOWN; + if (settings.value("connection").value("metered").toInt() != meteredInt) { + qWarning() << "Changing connection.metered to" << meteredInt; + settings["connection"]["metered"] = meteredInt; + changes = true; + } + if (changes) { call(lteConnectionPath.path(), NM_DBUS_INTERFACE_SETTINGS_CONNECTION, "UpdateUnsaved", QVariant::fromValue(settings)); // update is temporary deactivateConnection(lteConnectionPath); diff --git a/selfdrive/ui/qt/offroad/wifiManager.h b/selfdrive/ui/qt/offroad/wifiManager.h index 07b982c2c2..01f9cd6b65 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.h +++ b/selfdrive/ui/qt/offroad/wifiManager.h @@ -50,7 +50,7 @@ public: bool isKnownConnection(const QString &ssid); std::optional activateWifiConnection(const QString &ssid); NetworkType currentNetworkType(); - void updateGsmSettings(bool roaming, QString apn); + void updateGsmSettings(bool roaming, QString apn, bool metered); void connect(const Network &ssid, const QString &password = {}, const QString &username = {}); // Tethering functions diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 543893d440..b39c83c098 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -58,6 +58,14 @@ leave blank for automatic configuration 空白のままにして、自動設定にします + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + ConfirmationDialog diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 56524e49fa..86cd8f990a 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -58,6 +58,14 @@ leave blank for automatic configuration 자동설정하려면 공백으로 두세요 + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + ConfirmationDialog diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index ff6f27dd46..6a772a1f69 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -58,6 +58,14 @@ leave blank for automatic configuration deixe em branco para configuração automática + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + ConfirmationDialog diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 6300875ee3..1d942387e0 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -58,6 +58,14 @@ leave blank for automatic configuration 留空以自动配置 + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + ConfirmationDialog diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 73faf11f69..816d4fd3cc 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -58,6 +58,14 @@ leave blank for automatic configuration 留空白將自動配置 + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + ConfirmationDialog From cd40652e64b41a87539909bab2887e7930d3001a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 2 Oct 2022 16:13:28 -0700 Subject: [PATCH 052/178] updated: don't show failed alert with no internet (#25948) --- selfdrive/updated.py | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 331ee6e4af..f96abcb7d5 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -1,27 +1,4 @@ #!/usr/bin/env python3 - -# Safe Update: A simple service that waits for network access and tries to -# update every 10 minutes. It's intended to make the OP update process more -# robust against Git repository corruption. This service DOES NOT try to fix -# an already-corrupt BASEDIR Git repo, only prevent it from happening. -# -# During normal operation, both onroad and offroad, the update process makes -# no changes to the BASEDIR install of OP. All update attempts are performed -# in a disposable staging area provided by OverlayFS. It assumes the deleter -# process provides enough disk space to carry out the process. -# -# If an update succeeds, a flag is set, and the update is swapped in at the -# next reboot. If an update is interrupted or otherwise fails, the OverlayFS -# upper layer and metadata can be discarded before trying again. -# -# The swap on boot is triggered by launch_chffrplus.sh -# gated on the existence of $FINALIZED/.overlay_consistent and also the -# existence and mtime of $BASEDIR/.overlay_init. -# -# Other than build byproducts, BASEDIR should not be modified while this -# service is running. Developers modifying code directly in BASEDIR should -# disable this service. - import os import re import datetime @@ -241,6 +218,11 @@ class Updater: def __init__(self): self.params = Params() self.branches = defaultdict(lambda: '') + self._has_internet: bool = False + + @property + def has_internet(self) -> bool: + return self._has_internet @property def target_branch(self) -> str: @@ -321,7 +303,7 @@ class Updater: now = datetime.datetime.utcnow() dt = now - last_update - if failed_count > 15 and exception is not None: + if failed_count > 15 and exception is not None and self.has_internet: if is_tested_branch(): extra_text = "Ensure the software is correctly installed. Uninstall and re-install if this error persists." else: @@ -338,6 +320,12 @@ class Updater: excluded_branches = ('release2', 'release2-staging', 'dashcam', 'dashcam-staging') + try: + run(["git", "ls-remote", "origin", "HEAD"], OVERLAY_MERGED) + self._has_internet = True + except subprocess.CalledProcessError: + self._has_internet = False + setup_git_options(OVERLAY_MERGED) output = run(["git", "ls-remote", "--heads", "origin"], OVERLAY_MERGED) @@ -353,9 +341,9 @@ class Updater: new_branch = self.target_branch new_commit = self.branches[new_branch] if (cur_branch, cur_commit) != (new_branch, new_commit): - cloudlog.info(f"update available, {cur_branch} ({cur_commit[:7]}) -> {new_branch} ({new_commit[:7]})") + cloudlog.info(f"update available, {cur_branch} ({str(cur_commit)[:7]}) -> {new_branch} ({str(new_commit)[:7]})") else: - cloudlog.info(f"up to date on {cur_branch} ({cur_commit[:7]})") + cloudlog.info(f"up to date on {cur_branch} ({str(cur_commit)[:7]})") def fetch_update(self) -> None: cloudlog.info("attempting git fetch inside staging overlay") From 379b7cf8b6573a448ebd1eba4b64c2b588a74d81 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Oct 2022 01:16:38 +0800 Subject: [PATCH 053/178] proclogd: fix wrong type for rss (#25923) rss is long --- system/proclogd/proclog.cc | 2 +- system/proclogd/proclog.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/system/proclogd/proclog.cc b/system/proclogd/proclog.cc index cbe3b53493..09ab4f559e 100644 --- a/system/proclogd/proclog.cc +++ b/system/proclogd/proclog.cc @@ -95,7 +95,7 @@ std::optional procStat(std::string stat) { .num_threads = stol(v[StatPos::num_threads - 1]), .starttime = stoull(v[StatPos::starttime - 1]), .vms = stoul(v[StatPos::vsize - 1]), - .rss = stoul(v[StatPos::rss - 1]), + .rss = stol(v[StatPos::rss - 1]), .processor = stoi(v[StatPos::processor - 1]), }; return p; diff --git a/system/proclogd/proclog.h b/system/proclogd/proclog.h index 9ed53d1bac..49f97cdd36 100644 --- a/system/proclogd/proclog.h +++ b/system/proclogd/proclog.h @@ -20,8 +20,8 @@ struct ProcCache { struct ProcStat { int pid, ppid, processor; char state; - long cutime, cstime, priority, nice, num_threads; - unsigned long utime, stime, vms, rss; + long cutime, cstime, priority, nice, num_threads, rss; + unsigned long utime, stime, vms; unsigned long long starttime; std::string name; }; From 50b8fc19b692d0d4d0ef2dc9161fef6cf9d3013d Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Mon, 3 Oct 2022 13:48:56 -0500 Subject: [PATCH 054/178] VW PQ: Use correct brake signal (#25952) --- selfdrive/car/volkswagen/carstate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index cf4a252b65..3e99ca8252 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -170,7 +170,7 @@ class CarState(CarStateBase): ret.gas = pt_cp.vl["Motor_3"]["Fahrpedal_Rohsignal"] / 100.0 ret.gasPressed = ret.gas > 0 ret.brake = pt_cp.vl["Bremse_5"]["Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects - ret.brakePressed = bool(pt_cp.vl["Motor_2"]["Bremstestschalter"]) + ret.brakePressed = bool(pt_cp.vl["Motor_2"]["Bremslichtschalter"]) ret.parkingBrake = bool(pt_cp.vl["Kombi_1"]["Bremsinfo"]) # Update gear and/or clutch position data. From 6393d29b0437417fe15cbc9eca5c87534531e9bd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 3 Oct 2022 12:26:12 -0700 Subject: [PATCH 055/178] pre-commit: test translations (#25695) * add test translations to precommit * fix test translations pre-commit hook * revert * fix that * add to release files add to release files * fix * don't run test on stripped dir * fix --- .github/workflows/selfdrive_tests.yaml | 2 +- .pre-commit-config.yaml | 7 +++++++ release/files_common | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 9606c05631..d4971a4339 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -62,7 +62,7 @@ jobs: cp .pylintrc $STRIPPED_DIR cp mypy.ini $STRIPPED_DIR cd $STRIPPED_DIR - ${{ env.RUN }} "pre-commit run --all" + ${{ env.RUN }} "SKIP=test_translations pre-commit run --all" build_all: name: build all diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b1696e823c..25b8490f92 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -70,3 +70,10 @@ repos: - --quiet - --force - -j8 +- repo: local + hooks: + - id: test_translations + name: test translations + entry: selfdrive/ui/tests/test_translations.py + language: script + pass_filenames: false diff --git a/release/files_common b/release/files_common index 5783edd070..0c341eb3f6 100644 --- a/release/files_common +++ b/release/files_common @@ -305,6 +305,8 @@ selfdrive/ui/soundd/soundd selfdrive/ui/soundd/.gitignore selfdrive/ui/translations/*.ts selfdrive/ui/translations/languages.json +selfdrive/ui/update_translations.py +selfdrive/ui/tests/test_translations.py selfdrive/ui/qt/*.cc selfdrive/ui/qt/*.h From 2a0ce3e8b871b4e58b01b56a37bb8cfaf77d8b0a Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 3 Oct 2022 13:10:35 -0700 Subject: [PATCH 056/178] direct model feedback issues to GitHub discussions (#25953) * add links to give model feedback in github discussions * use correct link --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/ISSUE_TEMPLATE/config.yml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b1a14076ea..47eb6c216f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -8,6 +8,7 @@ body: value: > Before creating a **bug report**, please check the following: * If the issue likely only affects your car model or make, go back and open a **car bug report** instead. + * If the issue is related to the driving or driver monitoring models, you should open a [discussion](https://github.com/commaai/openpilot/discussions/categories/model-feedback) instead. * Ensure you're running the latest openpilot release. * Ensure you're using officially supported hardware. Issues running on PCs have a different issue template. * Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2c2deb17ba..45a8af0aaf 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,11 @@ blank_issues_enabled: false contact_links: + - name: Report model bugs + url: https://github.com/commaai/openpilot/discussions/categories/model-feedback + about: Provide feedback for the driving or driver monitoring models - name: Discussions url: https://github.com/commaai/openpilot/discussions - about: For questions and discussion about openpilot + about: For questions and general discussion about openpilot - name: Community Wiki url: https://github.com/commaai/openpilot/wiki about: Check out our community wiki From bc7be114d838aafc54a5e480dc969c76552e7f26 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 3 Oct 2022 13:58:40 -0700 Subject: [PATCH 057/178] UI: remove unused tap detection constant (#25956) --- selfdrive/ui/ui.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 887b7ee841..d7e51ccfeb 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -155,9 +155,6 @@ public: Device(QObject *parent = 0); private: - // auto brightness - const float accel_samples = 5*UI_FREQ; - bool awake = false; int interactive_timeout = 0; bool ignition_on = false; From 1b8324af876e66630b5f4e50623e3136a39f6ecb Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Tue, 4 Oct 2022 06:19:42 +0800 Subject: [PATCH 058/178] c++ cabana: Initial version (#25946) * draft * continue * fix QChart unresponsive with large points * build with --extras * add filter * save DBC button * more buttons * add flag to use qcamera * stop replay in dctor * README * use getMsg * video control * edit signal * add colors * correct ts * add/edit signals * use bus:address as key --- SConstruct | 4 + selfdrive/ui/SConscript | 1 + tools/cabana/.gitignore | 4 + tools/cabana/README | 9 + tools/cabana/SConscript | 20 ++ tools/cabana/cabana | 4 + tools/cabana/cabana.cc | 34 +++ tools/cabana/chartswidget.cc | 102 ++++++++ tools/cabana/chartswidget.h | 34 +++ tools/cabana/detailwidget.cc | 458 +++++++++++++++++++++++++++++++++ tools/cabana/detailwidget.h | 102 ++++++++ tools/cabana/mainwin.cc | 38 +++ tools/cabana/mainwin.h | 22 ++ tools/cabana/messageswidget.cc | 94 +++++++ tools/cabana/messageswidget.h | 24 ++ tools/cabana/parser.cc | 98 +++++++ tools/cabana/parser.h | 66 +++++ tools/cabana/videowidget.cc | 80 ++++++ tools/cabana/videowidget.h | 17 ++ tools/replay/SConscript | 1 + tools/replay/replay.cc | 2 +- tools/replay/replay.h | 4 + tools/ubuntu_setup.sh | 1 + 23 files changed, 1218 insertions(+), 1 deletion(-) create mode 100644 tools/cabana/.gitignore create mode 100644 tools/cabana/README create mode 100644 tools/cabana/SConscript create mode 100755 tools/cabana/cabana create mode 100644 tools/cabana/cabana.cc create mode 100644 tools/cabana/chartswidget.cc create mode 100644 tools/cabana/chartswidget.h create mode 100644 tools/cabana/detailwidget.cc create mode 100644 tools/cabana/detailwidget.h create mode 100644 tools/cabana/mainwin.cc create mode 100644 tools/cabana/mainwin.h create mode 100644 tools/cabana/messageswidget.cc create mode 100644 tools/cabana/messageswidget.h create mode 100644 tools/cabana/parser.cc create mode 100644 tools/cabana/parser.h create mode 100644 tools/cabana/videowidget.cc create mode 100644 tools/cabana/videowidget.h diff --git a/SConstruct b/SConstruct index 178b0cc872..e015218f2a 100644 --- a/SConstruct +++ b/SConstruct @@ -433,6 +433,10 @@ SConscript(['selfdrive/navd/SConscript']) SConscript(['tools/replay/SConscript']) +opendbc = abspath([File('opendbc/can/libdbc.so')]) +Export('opendbc') +SConscript(['tools/cabana/SConscript']) + if GetOption('test'): SConscript('panda/tests/safety/SConscript') diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 92f6578dfc..84e055752a 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -32,6 +32,7 @@ if maps: qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs) +Export('widgets') qt_libs = [widgets, qt_util] + base_libs # build assets diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore new file mode 100644 index 0000000000..0c21d5530d --- /dev/null +++ b/tools/cabana/.gitignore @@ -0,0 +1,4 @@ +moc_* +*.moc + +_cabana diff --git a/tools/cabana/README b/tools/cabana/README new file mode 100644 index 0000000000..f64e6b2d2d --- /dev/null +++ b/tools/cabana/README @@ -0,0 +1,9 @@ +# Cabana + + + +Cabana is a tool developed to view raw CAN data. One use for this is creating and editing [CAN Dictionaries](http://socialledge.com/sjsu/index.php/DBC_Format) (DBC files), and the tool provides direct integration with [commaai/opendbc](https://github.com/commaai/opendbc) (a collection of DBC files), allowing you to load the DBC files direct from source, and save to your fork. In addition, you can load routes from [comma connect](https://connect.comma.ai). + +## Usage Instructions + +See [openpilot wiki](https://github.com/commaai/openpilot/wiki/Cabana) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript new file mode 100644 index 0000000000..f32ee166b6 --- /dev/null +++ b/tools/cabana/SConscript @@ -0,0 +1,20 @@ +import os +Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', + 'cereal', 'transformations', 'widgets', 'replay_lib', 'opendbc') + +base_frameworks = qt_env['FRAMEWORKS'] +base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', + 'capnp', 'kj', 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] + +if arch == "Darwin": + base_frameworks.append('OpenCL') +else: + base_libs.append('OpenCL') + +qt_libs = ['qt_util', 'Qt5Charts'] + base_libs +if arch in ['x86_64', 'Darwin'] and GetOption('extras'): + qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] + + # qt_env["LD_LIBRARY_PATH"] = [Dir(f"#opendbc/can").abspath] + cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs + qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'videowidget.cc', 'parser.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/cabana/cabana b/tools/cabana/cabana new file mode 100755 index 0000000000..b29dd66e3d --- /dev/null +++ b/tools/cabana/cabana @@ -0,0 +1,4 @@ +#!/bin/sh +cd "$(dirname "$0")" +export LD_LIBRARY_PATH="../../opendbc/can:$LD_LIBRARY_PATH" +exec ./_cabana "$1" diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc new file mode 100644 index 0000000000..0adc744b49 --- /dev/null +++ b/tools/cabana/cabana.cc @@ -0,0 +1,34 @@ +#include +#include + +#include "selfdrive/ui/qt/util.h" +#include "tools/cabana/mainwin.h" + +const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; +Parser *parser = nullptr; + +int main(int argc, char *argv[]) { + initApp(argc, argv); + QApplication app(argc, argv); + + QCommandLineParser cmd_parser; + cmd_parser.addHelpOption(); + cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); + cmd_parser.addOption({"demo", "use a demo route instead of providing your own"}); + cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); + cmd_parser.addOption({"qcam", "use qcamera"}); + cmd_parser.process(app); + const QStringList args = cmd_parser.positionalArguments(); + if (args.empty() && !cmd_parser.isSet("demo")) { + cmd_parser.showHelp(); + } + + const QString route = args.empty() ? DEMO_ROUTE : args.first(); + parser = new Parser(&app); + if (!parser->loadRoute(route, cmd_parser.value("data_dir"), cmd_parser.isSet("qcam"))) { + return 0; + } + MainWindow w; + w.showMaximized(); + return app.exec(); +} diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc new file mode 100644 index 0000000000..a8fe39968e --- /dev/null +++ b/tools/cabana/chartswidget.cc @@ -0,0 +1,102 @@ +#include "tools/cabana/chartswidget.h" + +#include + +using namespace QtCharts; + +int64_t get_raw_value(const QByteArray &msg, const Signal &sig) { + int64_t ret = 0; + + int i = sig.msb / 8; + int bits = sig.size; + while (i >= 0 && i < msg.size() && bits > 0) { + int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8; + int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1; + int size = msb - lsb + 1; + + uint64_t d = (msg[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); + ret |= d << (bits - size); + + bits -= size; + i = sig.is_little_endian ? i - 1 : i + 1; + } + return ret; +} + +ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { + main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + connect(parser, &Parser::updated, this, &ChartsWidget::updateState); + connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart); + connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart); +} + +void ChartsWidget::addChart(const QString &id, const QString &sig_name) { + const QString char_name = id + sig_name; + if (charts.find(char_name) == charts.end()) { + QLineSeries *series = new QLineSeries(); + series->setUseOpenGL(true); + auto chart = new QChart(); + chart->setTitle(id + ": " + sig_name); + chart->addSeries(series); + chart->createDefaultAxes(); + chart->legend()->hide(); + auto chart_view = new QChartView(chart); + chart_view->setMinimumSize({width(), 300}); + chart_view->setMaximumSize({width(), 300}); + chart_view->setRenderHint(QPainter::Antialiasing); + main_layout->addWidget(chart_view); + charts[char_name] = {.id = id, .sig_name = sig_name, .chart_view = chart_view}; + } +} + +void ChartsWidget::removeChart(const QString &id, const QString &sig_name) { + auto it = charts.find(id + sig_name); + if (it == charts.end()) return; + + delete it->second.chart_view; + charts.erase(it); +} + +void ChartsWidget::updateState() { + static double last_update = millis_since_boot(); + double current_ts = millis_since_boot(); + bool update = (current_ts - last_update) > 500; + if (update) { + last_update = current_ts; + } + + auto getSig = [=](const QString &id, const QString &name) -> const Signal * { + for (auto &sig : parser->getMsg(id)->sigs) { + if (name == sig.name.c_str()) return &sig; + } + return nullptr; + }; + + for (auto &[_, c] : charts) { + if (auto sig = getSig(c.id, c.sig_name)) { + const auto &can_data = parser->can_msgs[c.id].back(); + int64_t val = get_raw_value(can_data.dat, *sig); + if (sig->is_signed) { + val -= ((val >> (sig->size - 1)) & 0x1) ? (1ULL << sig->size) : 0; + } + double value = val * sig->factor + sig->offset; + + if (value > c.max_y) c.max_y = value; + if (value < c.min_y) c.min_y = value; + + while (c.data.size() > DATA_LIST_SIZE) { + c.data.pop_front(); + } + c.data.push_back({can_data.ts / 1000., value}); + + if (update) { + QChart *chart = c.chart_view->chart(); + QLineSeries *series = (QLineSeries *)chart->series()[0]; + series->replace(c.data); + chart->axisX()->setRange(c.data.front().x(), c.data.back().x()); + chart->axisY()->setRange(c.min_y, c.max_y); + } + } + } +} diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h new file mode 100644 index 0000000000..7bc8335a32 --- /dev/null +++ b/tools/cabana/chartswidget.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "tools/cabana/parser.h" + +class ChartsWidget : public QWidget { + Q_OBJECT + + public: + ChartsWidget(QWidget *parent = nullptr); + inline bool hasChart(const QString &id, const QString &sig_name) { + return charts.find(id+sig_name) != charts.end(); + } + void addChart(const QString &id, const QString &sig_name); + void removeChart(const QString &id, const QString &sig_name); + void updateState(); + + protected: + QVBoxLayout *main_layout; + struct SignalChart { + QString id; + QString sig_name; + int max_y = 0; + int min_y = 0; + QList data; + QtCharts::QChartView *chart_view = nullptr; + }; + std::map charts; +}; diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc new file mode 100644 index 0000000000..6799376577 --- /dev/null +++ b/tools/cabana/detailwidget.cc @@ -0,0 +1,458 @@ +#include "tools/cabana/detailwidget.h" + +#include +#include +#include +#include +#include +#include + +#include "selfdrive/ui/qt/widgets/scrollview.h" + +const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; + +static QVector BIG_ENDIAN_START_BITS = []() { + QVector ret; + for (int i = 0; i < 64; i++) { + for (int j = 7; j >= 0; j--) { + ret.push_back(j + i * 8); + } + } + return ret; +}(); + +static int bigEndianBitIndex(int index) { + // TODO: Add a helper function in dbc.h + return BIG_ENDIAN_START_BITS.indexOf(index); +} + +DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + QLabel *title = new QLabel(tr("SELECTED MESSAGE:"), this); + main_layout->addWidget(title); + + QHBoxLayout *name_layout = new QHBoxLayout(); + name_label = new QLabel(this); + name_label->setStyleSheet("font-weight:bold;"); + name_layout->addWidget(name_label); + name_layout->addStretch(); + edit_btn = new QPushButton(tr("Edit"), this); + edit_btn->setVisible(false); + QObject::connect(edit_btn, &QPushButton::clicked, [=]() { + EditMessageDialog dlg(msg_id, this); + int ret = dlg.exec(); + if (ret) { + setMsg(msg_id); + } + }); + name_layout->addWidget(edit_btn); + main_layout->addLayout(name_layout); + + binary_view = new BinaryView(this); + main_layout->addWidget(binary_view); + + QHBoxLayout *signals_layout = new QHBoxLayout(); + signals_layout->addWidget(new QLabel(tr("Signals"))); + signals_layout->addStretch(); + add_sig_btn = new QPushButton(tr("Add signal"), this); + add_sig_btn->setVisible(false); + QObject::connect(add_sig_btn, &QPushButton::clicked, [=]() { + AddSignalDialog dlg(msg_id, this); + int ret = dlg.exec(); + if (ret) { + setMsg(msg_id); + } + }); + signals_layout->addWidget(add_sig_btn); + main_layout->addLayout(signals_layout); + + QWidget *container = new QWidget(this); + QVBoxLayout *container_layout = new QVBoxLayout(container); + signal_edit_layout = new QVBoxLayout(); + signal_edit_layout->setSpacing(2); + container_layout->addLayout(signal_edit_layout); + + messages_view = new MessagesView(this); + container_layout->addWidget(messages_view); + + QScrollArea *scroll = new QScrollArea(this); + scroll->setWidget(container); + scroll->setWidgetResizable(true); + scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + main_layout->addWidget(scroll); + setFixedWidth(600); + + connect(parser, &Parser::updated, this, &DetailWidget::updateState); +} + +void DetailWidget::updateState() { + if (msg_id.isEmpty()) return; + + auto &list = parser->can_msgs[msg_id]; + if (!list.empty()) { + binary_view->setData(list.back().dat); + messages_view->setMessages(list); + } +} + +SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { + QVBoxLayout *v_layout = new QVBoxLayout(this); + + QHBoxLayout *h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Name"))); + name = new QLineEdit(sig.name.c_str()); + h->addWidget(name); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Size"))); + size = new QSpinBox(); + size->setValue(sig.size); + h->addWidget(size); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Most significant bit"))); + msb = new QSpinBox(); + msb->setValue(sig.msb); + h->addWidget(msb); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Endianness"))); + endianness = new QComboBox(); + endianness->addItems({"Little", "Big"}); + endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1); + h->addWidget(endianness); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("sign"))); + sign = new QComboBox(); + sign->addItems({"Signed", "Unsigned"}); + sign->setCurrentIndex(sig.is_signed ? 0 : 1); + h->addWidget(sign); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Factor"))); + factor = new QSpinBox(); + factor->setValue(sig.factor); + h->addWidget(factor); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Offset"))); + offset = new QSpinBox(); + offset->setValue(sig.offset); + h->addWidget(offset); + v_layout->addLayout(h); + + // TODO: parse the following parameters in opendbc + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Unit"))); + unit = new QLineEdit(); + h->addWidget(unit); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Comment"))); + comment = new QLineEdit(); + h->addWidget(comment); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Minimum value"))); + min_val = new QSpinBox(); + h->addWidget(min_val); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Maximum value"))); + max_val = new QSpinBox(); + h->addWidget(max_val); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Value descriptions"))); + val_desc = new QLineEdit(); + h->addWidget(val_desc); + v_layout->addLayout(h); +} + +std::optional SignalForm::getSignal() { + Signal sig = {}; + sig.name = name->text().toStdString(); + sig.size = size->text().toInt(); + sig.offset = offset->text().toDouble(); + sig.factor = factor->text().toDouble(); + sig.msb = msb->text().toInt(); + sig.is_signed = sign->currentIndex() == 0; + sig.is_little_endian = endianness->currentIndex() == 0; + if (sig.is_little_endian) { + sig.lsb = sig.start_bit; + sig.msb = sig.start_bit + sig.size - 1; + } else { + sig.lsb = BIG_ENDIAN_START_BITS[bigEndianBitIndex(sig.start_bit) + sig.size - 1]; + sig.msb = sig.start_bit; + } + return (sig.name.empty() || sig.size <= 0) ? std::nullopt : std::optional(sig); +} + +void DetailWidget::setMsg(const QString &id) { + msg_id = id; + QString name = tr("untitled"); + + for (auto edit : signal_edit) { + delete edit; + } + signal_edit.clear(); + int i = 0; + auto msg = parser->getMsg(id); + if (msg) { + for (auto &s : msg->sigs) { + SignalEdit *edit = new SignalEdit(id, s, i++, this); + connect(edit, &SignalEdit::removed, [=]() { + QTimer::singleShot(0, [=]() { setMsg(id); }); + }); + signal_edit_layout->addWidget(edit); + signal_edit.push_back(edit); + } + name = msg->name.c_str(); + } + name_label->setText(name); + binary_view->setMsg(msg_id); + + edit_btn->setVisible(true); + add_sig_btn->setVisible(msg != nullptr); +} + +SignalEdit::SignalEdit(const QString &id, const Signal &sig, int idx, QWidget *parent) : id(id), name_(sig.name.c_str()), QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + + // title + QHBoxLayout *title_layout = new QHBoxLayout(); + QLabel *icon = new QLabel(">"); + icon->setStyleSheet("font-weight:bold"); + title_layout->addWidget(icon); + title = new ElidedLabel(this); + title->setText(sig.name.c_str()); + title->setStyleSheet(QString("font-weight:bold; color:%1").arg(SIGNAL_COLORS[idx % std::size(SIGNAL_COLORS)])); + connect(title, &ElidedLabel::clicked, [=]() { + edit_container->isVisible() ? edit_container->hide() : edit_container->show(); + icon->setText(edit_container->isVisible() ? "▼" : ">"); + }); + title_layout->addWidget(title); + title_layout->addStretch(); + QPushButton *show_plot = new QPushButton(tr("Show Plot")); + QObject::connect(show_plot, &QPushButton::clicked, [=]() { + if (show_plot->text() == tr("Show Plot")) { + emit parser->showPlot(id, name_); + show_plot->setText(tr("Hide Plot")); + } else { + emit parser->hidePlot(id, name_); + show_plot->setText(tr("Show Plot")); + } + }); + title_layout->addWidget(show_plot); + main_layout->addLayout(title_layout); + + edit_container = new QWidget(this); + QVBoxLayout *v_layout = new QVBoxLayout(edit_container); + form = new SignalForm(sig, this); + v_layout->addWidget(form); + + QHBoxLayout *h = new QHBoxLayout(); + remove_btn = new QPushButton(tr("Remove Signal")); + QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); + h->addWidget(remove_btn); + h->addStretch(); + QPushButton *save_btn = new QPushButton(tr("Save")); + QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::save); + h->addWidget(save_btn); + v_layout->addLayout(h); + + edit_container->setVisible(false); + main_layout->addWidget(edit_container); +} + +void SignalEdit::save() { + Msg *msg = const_cast(parser->getMsg(id)); + if (!msg) return; + + for (auto &sig : msg->sigs) { + if (name_ == sig.name.c_str()) { + if (auto s = form->getSignal()) { + sig = *s; + } + break; + } + } +} + +void SignalEdit::remove() { + QMessageBox msgbox; + msgbox.setText(tr("Remove signal")); + msgbox.setInformativeText(tr("Are you sure you want to remove signal '%1'").arg(name_)); + msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgbox.setDefaultButton(QMessageBox::Cancel); + if (msgbox.exec()) { + parser->removeSignal(id, name_); + emit removed(); + } +} + +BinaryView::BinaryView(QWidget *parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + table = new QTableWidget(this); + table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + table->horizontalHeader()->hide(); + table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + main_layout->addWidget(table); + table->setColumnCount(9); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +void BinaryView::setMsg(const QString &id) { + auto msg = parser->getMsg(Parser::addressFromId(id)); + int row_count = msg ? msg->size : parser->can_msgs[id].back().dat.size(); + + table->setRowCount(row_count); + table->setColumnCount(9); + for (int i = 0; i < table->rowCount(); ++i) { + for (int j = 0; j < table->columnCount(); ++j) { + auto item = new QTableWidgetItem(); + item->setTextAlignment(Qt::AlignCenter); + if (j == 8) { + QFont font; + font.setBold(true); + item->setFont(font); + } + table->setItem(i, j, item); + } + } + + if (msg) { + for (int i = 0; i < msg->sigs.size(); ++i) { + const auto &sig = msg->sigs[i]; + int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit); + for (int j = start; j <= start + sig.size - 1; ++j) { + table->item(j / 8, j % 8)->setBackground(QColor(SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)])); + } + } + } + + setFixedHeight(table->rowHeight(0) * table->rowCount() + 25); + if (!parser->can_msgs.empty()) { + setData(parser->can_msgs[id].back().dat); + } +} + +void BinaryView::setData(const QByteArray &binary) { + std::string s; + for (int j = 0; j < binary.size(); ++j) { + s += std::bitset<8>(binary[j]).to_string(); + } + + char hex[3] = {'\0'}; + for (int i = 0; i < binary.size(); ++i) { + for (int j = 0; j < 8; ++j) { + table->item(i, j)->setText(QChar(s[i * 8 + j])); + } + sprintf(&hex[0], "%02X", (unsigned char)binary[i]); + table->item(i, 8)->setText(hex); + } +} + +MessagesView::MessagesView(QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + QLabel *title = new QLabel("MESSAGE TIME BYTES"); + main_layout->addWidget(title); + + message_layout = new QVBoxLayout(); + main_layout->addLayout(message_layout); + main_layout->addStretch(); +} + +void MessagesView::setMessages(const std::list &list) { + auto begin = list.begin(); + std::advance(begin, std::max(0, (int)(list.size() - 100))); + int j = 0; + for (auto it = begin; it != list.end(); ++it) { + QLabel *label; + if (j >= messages.size()) { + label = new QLabel(); + message_layout->addWidget(label); + messages.push_back(label); + } else { + label = messages[j]; + } + label->setText(it->hex_dat); + ++j; + } +} + +EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Edit message")); + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->addWidget(new QLabel(tr("ID: (%1)").arg(id))); + + auto msg = const_cast(parser->getMsg(Parser::addressFromId(id))); + QHBoxLayout *h_layout = new QHBoxLayout(); + h_layout->addWidget(new QLabel(tr("Name"))); + h_layout->addStretch(); + QLineEdit *name_edit = new QLineEdit(this); + name_edit->setText(msg ? msg->name.c_str() : "untitled"); + h_layout->addWidget(name_edit); + main_layout->addLayout(h_layout); + + h_layout = new QHBoxLayout(); + h_layout->addWidget(new QLabel(tr("Size"))); + h_layout->addStretch(); + QSpinBox *size_spin = new QSpinBox(this); + size_spin->setValue(msg ? msg->size : parser->can_msgs[id].back().dat.size()); + h_layout->addWidget(size_spin); + main_layout->addLayout(h_layout); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + main_layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, [=]() { + if (size_spin->value() <= 0 || name_edit->text().isEmpty()) return; + + if (msg) { + msg->name = name_edit->text().toStdString(); + msg->size = size_spin->value(); + } else { + Msg m = {}; + m.address = Parser::addressFromId(id); + m.name = name_edit->text().toStdString(); + m.size = size_spin->value(); + parser->addNewMsg(m); + } + QDialog::accept(); + }); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +AddSignalDialog::AddSignalDialog(const QString &id, QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Add signal to %1").arg(parser->getMsg(id)->name.c_str())); + QVBoxLayout *main_layout = new QVBoxLayout(this); + Signal sig = {.name = "untitled"}; + auto form = new SignalForm(sig, this); + main_layout->addWidget(form); + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + main_layout->addWidget(buttonBox); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(buttonBox, &QDialogButtonBox::accepted, [=]() { + if (auto msg = const_cast(parser->getMsg(id))) { + if (auto signal = form->getSignal()) { + msg->sigs.push_back(*signal); + } + } + QDialog::accept(); + }); +} diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h new file mode 100644 index 0000000000..70f2804f70 --- /dev/null +++ b/tools/cabana/detailwidget.h @@ -0,0 +1,102 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "opendbc/can/common.h" +#include "opendbc/can/common_dbc.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "tools/cabana/parser.h" + +class SignalForm : public QWidget { + Q_OBJECT + + public: + SignalForm(const Signal &sig, QWidget *parent); + std::optional getSignal(); + QLineEdit *name, *unit, *comment, *val_desc; + QSpinBox *size, *msb, *lsb, *factor, *offset, *min_val, *max_val; + QComboBox *sign, *endianness; +}; + +class MessagesView : public QWidget { + Q_OBJECT + + public: + MessagesView(QWidget *parent); + void setMessages(const std::list &data); + std::vector messages; + QVBoxLayout *message_layout; +}; + +class BinaryView : public QWidget { + Q_OBJECT + + public: + BinaryView(QWidget *parent); + void setMsg(const QString &id); + void setData(const QByteArray &binary); + + QTableWidget *table; +}; + +class SignalEdit : public QWidget { + Q_OBJECT + + public: + SignalEdit(const QString &id, const Signal &sig, int idx, QWidget *parent); + void save(); + +signals: + void removed(); + protected: + void remove(); + QString id; + QString name_; + ElidedLabel *title; + SignalForm *form; + QWidget *edit_container; + QPushButton *remove_btn; +}; + +class DetailWidget : public QWidget { + Q_OBJECT + public: + DetailWidget(QWidget *parent); + void setMsg(const QString &id); + + public slots: + void updateState(); + + protected: + QLabel *name_label = nullptr; + QPushButton *edit_btn, *add_sig_btn; + QVBoxLayout *signal_edit_layout; + Signal *sig = nullptr; + MessagesView *messages_view; + QString msg_id; + BinaryView *binary_view; + std::vector signal_edit; +}; + +class EditMessageDialog : public QDialog { + Q_OBJECT + + public: + EditMessageDialog(const QString &id, QWidget *parent); +}; + +class AddSignalDialog : public QDialog { + Q_OBJECT + + public: + AddSignalDialog(const QString &id, QWidget *parent); +}; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc new file mode 100644 index 0000000000..d1b0d98f5f --- /dev/null +++ b/tools/cabana/mainwin.cc @@ -0,0 +1,38 @@ +#include "tools/cabana/mainwin.h" + +#include +#include + +MainWindow::MainWindow() : QWidget() { + assert(parser != nullptr); + + QVBoxLayout *main_layout = new QVBoxLayout(this); + QHBoxLayout *h_layout = new QHBoxLayout(); + main_layout->addLayout(h_layout); + + messages_widget = new MessagesWidget(this); + QObject::connect(messages_widget, &MessagesWidget::msgChanged, [=](const QString &id) { + detail_widget->setMsg(id); + }); + h_layout->addWidget(messages_widget); + + detail_widget = new DetailWidget(this); + h_layout->addWidget(detail_widget); + + // right widget + QWidget *right_container = new QWidget(this); + right_container->setFixedWidth(640); + QVBoxLayout *r_layout = new QVBoxLayout(right_container); + video_widget = new VideoWidget(this); + r_layout->addWidget(video_widget); + + charts_widget = new ChartsWidget(this); + QScrollArea *scroll = new QScrollArea(this); + scroll->setWidget(charts_widget); + scroll->setWidgetResizable(true); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + r_layout->addWidget(scroll); + + h_layout->addWidget(right_container); +} diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h new file mode 100644 index 0000000000..f19c48297e --- /dev/null +++ b/tools/cabana/mainwin.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "tools/cabana/chartswidget.h" +#include "tools/cabana/detailwidget.h" +#include "tools/cabana/messageswidget.h" +#include "tools/cabana/parser.h" +#include "tools/cabana/videowidget.h" + +class MainWindow : public QWidget { + Q_OBJECT + + public: + MainWindow(); + + protected: + VideoWidget *video_widget; + MessagesWidget *messages_widget; + DetailWidget *detail_widget; + ChartsWidget *charts_widget; +}; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc new file mode 100644 index 0000000000..d81d3e9ede --- /dev/null +++ b/tools/cabana/messageswidget.cc @@ -0,0 +1,94 @@ +#include "tools/cabana/messageswidget.h" + +#include +#include +#include +#include +#include +#include + +MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + + QHBoxLayout *dbc_file_layout = new QHBoxLayout(); + QComboBox *combo = new QComboBox(this); + auto dbc_names = get_dbc_names(); + for (const auto &name : dbc_names) { + combo->addItem(QString::fromStdString(name)); + } + connect(combo, &QComboBox::currentTextChanged, [=](const QString &dbc) { + parser->openDBC(dbc); + }); + // For test purpose + combo->setCurrentText("toyota_nodsu_pt_generated"); + dbc_file_layout->addWidget(combo); + + dbc_file_layout->addStretch(); + QPushButton *save_btn = new QPushButton(tr("Save DBC"), this); + QObject::connect(save_btn, &QPushButton::clicked, [=]() { + // TODO: save DBC to file + }); + dbc_file_layout->addWidget(save_btn); + + main_layout->addLayout(dbc_file_layout); + + filter = new QLineEdit(this); + filter->setPlaceholderText(tr("filter messages")); + main_layout->addWidget(filter); + + table_widget = new QTableWidget(this); + table_widget->setSelectionBehavior(QAbstractItemView::SelectRows); + table_widget->setSelectionMode(QAbstractItemView::SingleSelection); + table_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + table_widget->setColumnCount(4); + table_widget->setColumnWidth(0, 250); + table_widget->setColumnWidth(1, 80); + table_widget->setColumnWidth(2, 80); + table_widget->setHorizontalHeaderLabels({tr("Name"), tr("ID"), tr("Count"), tr("Bytes")}); + table_widget->horizontalHeader()->setStretchLastSection(true); + QObject::connect(table_widget, &QTableWidget::itemSelectionChanged, [=]() { + auto id = table_widget->selectedItems()[0]->data(Qt::UserRole); + emit msgChanged(id.toString()); + }); + main_layout->addWidget(table_widget); + + connect(parser, &Parser::updated, this, &MessagesWidget::updateState); +} + +void MessagesWidget::updateState() { + auto getTableItem = [=](int row, int col) -> QTableWidgetItem * { + auto item = table_widget->item(row, col); + if (!item) { + item = new QTableWidgetItem(); + table_widget->setItem(row, col, item); + } + return item; + }; + + table_widget->setRowCount(parser->can_msgs.size()); + int i = 0; + const QString filter_str = filter->text().toLower(); + for (const auto &[id, list] : parser->can_msgs) { + assert(!list.empty()); + + QString name; + if (auto msg = parser->getMsg(list.back().address)) { + name = msg->name.c_str(); + } else { + name = tr("untitled"); + } + if (!filter_str.isEmpty() && !name.toLower().contains(filter_str)) { + table_widget->hideRow(i++); + continue; + } + + auto item = getTableItem(i, 0); + item->setText(name); + item->setData(Qt::UserRole, id); + getTableItem(i, 1)->setText(id); + getTableItem(i, 2)->setText(QString("%1").arg(parser->counters[id])); + getTableItem(i, 3)->setText(list.back().hex_dat); + table_widget->showRow(i); + i++; + } +} diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h new file mode 100644 index 0000000000..dca725199d --- /dev/null +++ b/tools/cabana/messageswidget.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +#include "tools/cabana/parser.h" + +class MessagesWidget : public QWidget { + Q_OBJECT + + public: + MessagesWidget(QWidget *parent); + + public slots: + void updateState(); + + signals: + void msgChanged(const QString &id); + + protected: + QLineEdit *filter; + QTableWidget *table_widget; +}; diff --git a/tools/cabana/parser.cc b/tools/cabana/parser.cc new file mode 100644 index 0000000000..481f0dfbdf --- /dev/null +++ b/tools/cabana/parser.cc @@ -0,0 +1,98 @@ +#include "tools/cabana/parser.h" + +#include + +#include "cereal/messaging/messaging.h" + +Parser::Parser(QObject *parent) : QObject(parent) { + qRegisterMetaType>(); + QObject::connect(this, &Parser::received, this, &Parser::process, Qt::QueuedConnection); + + thread = new QThread(); + connect(thread, &QThread::started, [=]() { recvThread(); }); + QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +Parser::~Parser() { + replay->stop(); + exit = true; + thread->quit(); + thread->wait(); +} + +bool Parser::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { + replay = new Replay(route, {"can", "roadEncodeIdx"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); + if (!replay->load()) { + return false; + } + replay->start(); + return true; +} + +void Parser::openDBC(const QString &name) { + dbc_name = name; + dbc = const_cast(dbc_lookup(name.toStdString())); + msg_map.clear(); + for (auto &msg : dbc->msgs) { + msg_map[msg.address] = &msg; + } +} + +void Parser::process(std::vector can) { + for (auto &data : can) { + ++counters[data.id]; + auto &list = can_msgs[data.id]; + while (list.size() > DATA_LIST_SIZE) { + list.pop_front(); + } + list.push_back(data); + } + emit updated(); +} + +void Parser::recvThread() { + AlignedBuffer aligned_buf; + std::unique_ptr context(Context::create()); + std::unique_ptr subscriber(SubSocket::create(context.get(), "can")); + subscriber->setTimeout(100); + while (!exit) { + std::unique_ptr msg(subscriber->receive()); + if (!msg) continue; + + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); + cereal::Event::Reader event = cmsg.getRoot(); + std::vector can; + can.reserve(event.getCan().size()); + for (const auto &c : event.getCan()) { + CanData &data = can.emplace_back(); + data.address = c.getAddress(); + data.bus_time = c.getBusTime(); + data.source = c.getSrc(); + data.dat.append((char *)c.getDat().begin(), c.getDat().size()); + data.hex_dat = data.dat.toHex(' ').toUpper(); + data.id = QString("%1:%2").arg(data.source).arg(data.address, 1, 16); + data.ts = (event.getLogMonoTime() - replay->routeStartTime()) / (double)1e6; + } + emit received(can); + } +} + +void Parser::addNewMsg(const Msg &msg) { + dbc->msgs.push_back(msg); + msg_map[dbc->msgs.back().address] = &dbc->msgs.back(); +} + +void Parser::removeSignal(const QString &id, const QString &sig_name) { + Msg *msg = const_cast(getMsg(id)); + if (!msg) return; + + auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); + if (it != msg->sigs.end()) { + msg->sigs.erase(it); + } +} + +uint32_t Parser::addressFromId(const QString &id) { + return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16); +} diff --git a/tools/cabana/parser.h b/tools/cabana/parser.h new file mode 100644 index 0000000000..fd5ded7c5e --- /dev/null +++ b/tools/cabana/parser.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "opendbc/can/common.h" +#include "opendbc/can/common_dbc.h" +#include "tools/replay/replay.h" + +const int DATA_LIST_SIZE = 500; +// const int FPS = 20; + +struct CanData { + QString id; + double ts; + uint32_t address; + uint16_t bus_time; + uint8_t source; + QByteArray dat; + QString hex_dat; +}; + +class Parser : public QObject { + Q_OBJECT + + public: + Parser(QObject *parent); + ~Parser(); + static uint32_t addressFromId(const QString &id); + bool loadRoute(const QString &route, const QString &data_dir, bool use_qcam); + void openDBC(const QString &name); + void saveDBC(const QString &name) {} + void addNewMsg(const Msg &msg); + void removeSignal(const QString &id, const QString &sig_name); + const Msg *getMsg(const QString &id) { + return getMsg(addressFromId(id)); + } + const Msg *getMsg(uint32_t address) { + auto it = msg_map.find(address); + return it != msg_map.end() ? it->second : nullptr; + } + signals: + void showPlot(const QString &id, const QString &name); + void hidePlot(const QString &id, const QString &name); + void received(std::vector can); + void updated(); + + public: + void recvThread(); + void process(std::vector can); + QThread *thread; + QString dbc_name; + std::atomic exit = false; + std::map> can_msgs; + std::map counters; + Replay *replay = nullptr; + DBC *dbc = nullptr; + std::map msg_map; +}; + +Q_DECLARE_METATYPE(std::vector); + +extern Parser *parser; diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc new file mode 100644 index 0000000000..caa109eab0 --- /dev/null +++ b/tools/cabana/videowidget.cc @@ -0,0 +1,80 @@ +#include "tools/cabana/videowidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "tools/cabana/parser.h" + +inline QString formatTime(int seconds) { + return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh::mm::ss" : "mm::ss"); +} + +VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + + cam_widget = new CameraViewWidget("camerad", VISION_STREAM_ROAD, true, this); + cam_widget->setFixedSize(640, 480); + main_layout->addWidget(cam_widget); + + // slider controls + QHBoxLayout *slider_layout = new QHBoxLayout(); + QLabel *time_label = new QLabel("00:00"); + slider_layout->addWidget(time_label); + + slider = new QSlider(Qt::Horizontal, this); + // slider->setFixedWidth(640); + slider->setSingleStep(1); + slider->setMaximum(parser->replay->totalSeconds()); + QObject::connect(slider, &QSlider::sliderReleased, [=]() { + time_label->setText(formatTime(slider->value())); + parser->replay->seekTo(slider->value(), false); + }); + slider_layout->addWidget(slider); + + QLabel *total_time_label = new QLabel(formatTime(parser->replay->totalSeconds())); + slider_layout->addWidget(total_time_label); + + main_layout->addLayout(slider_layout); + + // btn controls + QHBoxLayout *control_layout = new QHBoxLayout(); + QPushButton *play = new QPushButton("⏸"); + play->setStyleSheet("font-weight:bold"); + QObject::connect(play, &QPushButton::clicked, [=]() { + bool is_paused = parser->replay->isPaused(); + play->setText(is_paused ? "⏸" : "▶"); + parser->replay->pause(!is_paused); + }); + control_layout->addWidget(play); + + QButtonGroup *group = new QButtonGroup(this); + group->setExclusive(true); + for (float speed : {0.1, 0.5, 1., 2.}) { + QPushButton *btn = new QPushButton(QString("%1x").arg(speed), this); + btn->setCheckable(true); + QObject::connect(btn, &QPushButton::clicked, [=]() { + parser->replay->setSpeed(speed); + }); + control_layout->addWidget(btn); + group->addButton(btn); + if (speed == 1.0) btn->setChecked(true); + } + + main_layout->addLayout(control_layout); + + QTimer *timer = new QTimer(this); + timer->setInterval(1000); + timer->callOnTimeout([=]() { + int current_seconds = parser->replay->currentSeconds(); + time_label->setText(formatTime(current_seconds)); + if (!slider->isSliderDown()) { + slider->setValue(current_seconds); + } + }); + timer->start(); +} diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h new file mode 100644 index 0000000000..f0b9e458bd --- /dev/null +++ b/tools/cabana/videowidget.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +#include "selfdrive/ui/qt/widgets/cameraview.h" + +class VideoWidget : public QWidget { + Q_OBJECT + +public: + VideoWidget(QWidget *parnet = nullptr); + +protected: + CameraViewWidget *cam_widget; + QSlider *slider; +}; diff --git a/tools/replay/SConscript b/tools/replay/SConscript index d3967708fa..9985375688 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -18,6 +18,7 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc"] replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=qt_libs, FRAMEWORKS=base_frameworks) + Export('replay_lib') replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + qt_libs qt_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index c6c78f47ae..1684dfaca9 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -382,7 +382,7 @@ void Replay::stream() { if (cur_which < sockets_.size() && sockets_[cur_which] != nullptr) { // keep time - long etime = cur_mono_time_ - evt_start_ts; + long etime = (cur_mono_time_ - evt_start_ts) / speed_; long rtime = nanos_since_boot() - loop_start_ts; long behind_ns = etime - rtime; // if behind_ns is greater than 1 second, it means that an invalid segemnt is skipped by seeking/replaying diff --git a/tools/replay/replay.h b/tools/replay/replay.h index e86c453f7e..3327362f97 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -52,8 +52,11 @@ public: inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } inline const Route* route() const { return route_.get(); } inline int currentSeconds() const { return (cur_mono_time_ - route_start_ts_) / 1e9; } + 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 int totalSeconds() const { return segments_.size() * 60; } + inline void setSpeed(float speed) { speed_ = speed; } + inline float getSpeed() const { return speed_; } inline const std::string &carFingerprint() const { return car_fingerprint_; } inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); @@ -112,4 +115,5 @@ protected: QFuture timeline_future; std::vector> timeline; std::string car_fingerprint_; + float speed_ = 1.0; }; diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 863b853718..7e021bcc23 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -63,6 +63,7 @@ function install_ubuntu_common_requirements() { qttools5-dev-tools \ libqt5sql5-sqlite \ libqt5svg5-dev \ + libqt5charts5-dev \ libqt5x11extras5-dev \ libreadline-dev \ libdw1 \ From 3068c48224d62ea6c3070332668fedafe522f1d3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Oct 2022 16:06:29 -0700 Subject: [PATCH 059/178] add fault for invalid safety RX checks (#25949) * add fault for invalid safety RX checks * just a bool * bump panda --- cereal | 2 +- panda | 2 +- selfdrive/boardd/boardd.cc | 1 + selfdrive/car/hyundai/carstate.py | 1 - selfdrive/controls/controlsd.py | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cereal b/cereal index d4cf8728e2..5ba96b6ded 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit d4cf8728e2fa2d87d90098efa7ddeaf8f98a03db +Subproject commit 5ba96b6ded57bcd91e60140ce0036f61370f8512 diff --git a/panda b/panda index 1910db8d4c..e987e6c639 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 1910db8d4c3f932fe85b186fba1d24795cb2b742 +Subproject commit e987e6c6393e1e1432ba6bb740eb2c1b80542043 diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 09e7137b38..3e39985c29 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -381,6 +381,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt)); ps.setInterruptLoad(health.interrupt_load); ps.setFanPower(health.fan_power); + ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid)); std::array cs = {ps.initCanState0(), ps.initCanState1(), ps.initCanState2()}; diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 61da04d04b..b9c7327a93 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -31,7 +31,6 @@ class CarState(CarStateBase): self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"] self.brake_error = False - self.park_brake = False self.buttons_counter = 0 # On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index b6479e5608..303e5f8df8 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -313,7 +313,7 @@ class Controls: else: safety_mismatch = pandaState.safetyModel not in IGNORED_SAFETY_MODES - if safety_mismatch or self.mismatch_counter >= 200: + if safety_mismatch or pandaState.safetyRxChecksInvalid or self.mismatch_counter >= 200: self.events.add(EventName.controlsMismatch) if log.PandaState.FaultType.relayMalfunction in pandaState.faults: From 4404cb42b445cdb7c2a9583357582ba2f11f2067 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Mon, 3 Oct 2022 16:45:00 -0700 Subject: [PATCH 060/178] fix build without extras (#25957) --- tools/cabana/SConscript | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index f32ee166b6..0caae14e92 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -1,6 +1,6 @@ import os Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', - 'cereal', 'transformations', 'widgets', 'replay_lib', 'opendbc') + 'cereal', 'transformations', 'widgets', 'opendbc') base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', @@ -15,6 +15,7 @@ qt_libs = ['qt_util', 'Qt5Charts'] + base_libs if arch in ['x86_64', 'Darwin'] and GetOption('extras'): qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] + Import('replay_lib') # qt_env["LD_LIBRARY_PATH"] = [Dir(f"#opendbc/can").abspath] cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'videowidget.cc', 'parser.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) From 5352006cbd00dc84262a4789b66cba47669d6d75 Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Mon, 3 Oct 2022 19:19:39 -0700 Subject: [PATCH 061/178] hyundai: fix FCA11 checksum and counter (#25027) * hyundai: fix FCA11 checksum and counter * update refs and comment about alt DBC definition we do not support --- opendbc | 2 +- selfdrive/car/hyundai/hyundaican.py | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/opendbc b/opendbc index eaac172af9..9ddcdb22c4 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit eaac172af9cb342204e69ec52339cdf3c6a8ac4e +Subproject commit 9ddcdb22c4929baf310295e832668e6e7fcfa602 diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index b3e1aa6b66..139a14d5e8 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -132,18 +132,16 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s } commands.append(packer.make_can_msg("SCC14", 0, scc14_values)) + # note that some vehicles most likely have an alternate checksum/counter definition + # https://github.com/commaai/opendbc/commit/9ddcdb22c4929baf310295e832668e6e7fcfa602 fca11_values = { - # seems to count 2,1,0,3,2,1,0,3,2,1,0,3,2,1,0,repeat... - # (where first value is aligned to Supplemental_Counter == 0) - # test: [(idx % 0xF, -((idx % 0xF) + 2) % 4) for idx in range(0x14)] - "CR_FCA_Alive": ((-((idx % 0xF) + 2) % 4) << 2) + 1, - "Supplemental_Counter": idx % 0xF, + "CR_FCA_Alive": idx % 0xF, "PAINT1_Status": 1, "FCA_DrvSetStatus": 1, "FCA_Status": 1, # AEB disabled } fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[2] - fca11_values["CR_FCA_ChkSum"] = 0x10 - sum(sum(divmod(i, 16)) for i in fca11_dat) % 0x10 + fca11_values["CR_FCA_ChkSum"] = hyundai_checksum(fca11_dat[:7]) commands.append(packer.make_can_msg("FCA11", 0, fca11_values)) return commands From dc63245b8955dc941e4c2370f6b953564f3f775b Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Mon, 3 Oct 2022 20:47:00 -0700 Subject: [PATCH 062/178] reset LaikadEphemeris after loggerd test --- selfdrive/loggerd/tests/test_loggerd.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/loggerd/tests/test_loggerd.py b/selfdrive/loggerd/tests/test_loggerd.py index a2138b0aa6..9c3565d130 100755 --- a/selfdrive/loggerd/tests/test_loggerd.py +++ b/selfdrive/loggerd/tests/test_loggerd.py @@ -110,6 +110,8 @@ class TestLoggerd(unittest.TestCase): self.assertEqual(getattr(initData, initData_key), v) self.assertEqual(logged_params[param_key].decode(), v) + params.put("LaikadEphemeris", "") + def test_rotation(self): os.environ["LOGGERD_TEST"] = "1" Params().put("RecordFront", "1") From f35c234e9c23d9af49fcc6f82475931dc2d887db Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 3 Oct 2022 21:19:51 -0700 Subject: [PATCH 063/178] thermald: consider pmic temp while onroad (#25959) * thermald: consider pmic temp while onroad * this is better --- selfdrive/thermald/thermald.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 5c2fbd6825..64ca39d296 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -177,7 +177,8 @@ def thermald_thread(end_event, hw_queue): modem_temps=[], ) - temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML) + all_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML) + offroad_temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML) should_start_prev = False in_car = False engaged_prev = False @@ -239,24 +240,32 @@ def thermald_thread(end_event, hw_queue): msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness() - max_comp_temp = temp_filter.update( - max(max(msg.deviceState.cpuTempC), msg.deviceState.memoryTempC, max(msg.deviceState.gpuTempC)) - ) + # this one is only used for offroad + temp_sources = [ + msg.deviceState.memoryTempC, + max(msg.deviceState.cpuTempC), + max(msg.deviceState.gpuTempC), + ] + offroad_comp_temp = offroad_temp_filter.update(max(temp_sources)) + + # this drives the thermal status while onroad + temp_sources.append(max(msg.deviceState.pmicTempC)) + all_comp_temp = all_temp_filter.update(max(temp_sources)) if fan_controller is not None: - msg.deviceState.fanSpeedPercentDesired = fan_controller.update(max_comp_temp, onroad_conditions["ignition"]) + 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 max_comp_temp > OFFROAD_DANGER_TEMP: + 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 thermal_status = ThermalStatus.danger else: current_band = THERMAL_BANDS[thermal_status] band_idx = list(THERMAL_BANDS.keys()).index(thermal_status) - if current_band.min_temp is not None and max_comp_temp < current_band.min_temp: + if current_band.min_temp is not None and all_comp_temp < current_band.min_temp: thermal_status = list(THERMAL_BANDS.keys())[band_idx - 1] - elif current_band.max_temp is not None and max_comp_temp > current_band.max_temp: + elif current_band.max_temp is not None and all_comp_temp > current_band.max_temp: thermal_status = list(THERMAL_BANDS.keys())[band_idx + 1] # **** starting logic **** From 84adb8d9259be92951c604b0780608de8c039f9f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 4 Oct 2022 00:39:19 -0700 Subject: [PATCH 064/178] GM: raise max brake (#25810) * -4 didn't make any sense? * comments * comments * update to 400 * bump panda * remove unused iso limit vars * update comments * bump panda * Update selfdrive/car/gm/values.py * Update ref_commit --- panda | 2 +- selfdrive/car/gm/values.py | 16 ++++++++-------- selfdrive/controls/lib/longcontrol.py | 3 --- selfdrive/test/process_replay/ref_commit | 2 +- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/panda b/panda index e987e6c639..9bcd9b9a24 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e987e6c6393e1e1432ba6bb740eb2c1b80542043 +Subproject commit 9bcd9b9a24149b241992f26caf9920d427ee609d diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index fec21d8d24..999dabfee3 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -23,11 +23,12 @@ class CarControllerParams: ADAS_KEEPALIVE_STEP = 100 CAMERA_KEEPALIVE_STEP = 100 - # Volt gasbrake lookups - # TODO: These values should be confirmed on non-Volt vehicles + # Volt gas/brake lookups + # TODO: These values should be confirmed on non-Volt vehicles. + # MAX_GAS should achieve 2 m/s^2 and MAX_BRAKE with regen should achieve -4.0 m/s^2 MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill. ZERO_GAS = 2048 # Coasting - MAX_BRAKE = 350 # ~ -3.5 m/s^2 with regen + MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen # Allow small margin below -3.5 m/s^2 from ISO 15622:2018 since we @@ -38,15 +39,14 @@ class CarControllerParams: ACCEL_MAX = 2. # m/s^2 ACCEL_MIN = -4. # m/s^2 - EV_GAS_LOOKUP_BP = [-1., 0., ACCEL_MAX] - EV_BRAKE_LOOKUP_BP = [ACCEL_MIN, -1.] - # ICE has much less engine braking force compared to regen in EVs, # lower threshold removes some braking deadzone GAS_LOOKUP_BP = [-0.1, 0., ACCEL_MAX] - BRAKE_LOOKUP_BP = [ACCEL_MIN, -0.1] - + EV_GAS_LOOKUP_BP = [-1., 0., ACCEL_MAX] GAS_LOOKUP_V = [MAX_ACC_REGEN, ZERO_GAS, MAX_GAS] + + BRAKE_LOOKUP_BP = [ACCEL_MIN, -0.1] + EV_BRAKE_LOOKUP_BP = [ACCEL_MIN, -1.] BRAKE_LOOKUP_V = [MAX_BRAKE, 0.] diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 43e1f9cc4b..db5bf4d3e6 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -7,9 +7,6 @@ from selfdrive.modeld.constants import T_IDXS LongCtrlState = car.CarControl.Actuators.LongControlState -# As per ISO 15622:2018 for all speeds -ACCEL_MIN_ISO = -3.5 # m/s^2 -ACCEL_MAX_ISO = 2.0 # m/s^2 def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, v_target_1sec, brake_pressed, cruise_standstill): diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 1f459b1948..2769483c90 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -53079010a5db8105854212157b5ee90029df7b92 \ No newline at end of file +653fbfeac952c288ffd595ee13139b9547b998bf From ca746b0a18eebc9b0fa7a6810dba0b28a60548c2 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Tue, 4 Oct 2022 13:34:31 -0700 Subject: [PATCH 065/178] Extend torqued (#25961) * extend live torque to all hyundai and toyota cars * update refs --- selfdrive/locationd/torqued.py | 5 ++--- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 78c3029af4..66af234590 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -12,7 +12,6 @@ from common.realtime import config_realtime_process, DT_MDL from common.filter_simple import FirstOrderFilter from system.swaglog import cloudlog from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY -from selfdrive.car.toyota.values import CAR as TOYOTA HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 @@ -33,7 +32,7 @@ MAX_INVALID_THRESHOLD = 10 MIN_ENGAGE_BUFFER = 2 # secs VERSION = 1 # bump this to invalidate old parameter caches -ALLOWED_FINGERPRINTS = [TOYOTA.COROLLA_TSS2, TOYOTA.COROLLA, TOYOTA.COROLLAH_TSS2] +ALLOWED_CARS = ['toyota', 'hyundai'] def slope2rot(slope): @@ -98,7 +97,7 @@ class TorqueEstimator: self.offline_friction = 0.0 self.offline_latAccelFactor = 0.0 self.resets = 0.0 - self.use_params = CP.carFingerprint in ALLOWED_FINGERPRINTS + self.use_params = CP.carName in ALLOWED_CARS if CP.lateralTuning.which() == 'torque': self.offline_friction = CP.lateralTuning.torque.friction diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 2769483c90..d4d16b726b 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -653fbfeac952c288ffd595ee13139b9547b998bf +1989d8c2a5de94faa3756b7d10fc94e6c063afa5 \ No newline at end of file From 0231c4ba851819963502d0068d574a055f865692 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 4 Oct 2022 17:23:41 -0700 Subject: [PATCH 066/178] cleanup stale longitudinal params (#25967) --- selfdrive/controls/controlsd.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 303e5f8df8..9cf079a2b3 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -143,6 +143,11 @@ class Controls: put_nonblocking("CarParamsCache", cp_bytes) put_nonblocking("CarParamsPersistent", cp_bytes) + # cleanup old params + if not self.CP.experimentalLongitudinalAvailable: + params.remove("EndToEndLong") + params.remove("ExperimentalLongitudinalEnabled") + self.CC = car.CarControl.new_message() self.CS_prev = car.CarState.new_message() self.AM = AlertManager() From 285fd56a1d9ceb4addc5f34c510817bb075af977 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 4 Oct 2022 17:47:37 -0700 Subject: [PATCH 067/178] bootlog: add helper to get a bootlog by segment id --- tools/lib/bootlog.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/lib/bootlog.py b/tools/lib/bootlog.py index 3515370823..1e474e5dde 100644 --- a/tools/lib/bootlog.py +++ b/tools/lib/bootlog.py @@ -1,6 +1,7 @@ import datetime import functools import re +from typing import List, Optional from tools.lib.auth_config import get_token from tools.lib.api import CommaApi @@ -48,8 +49,15 @@ class Bootlog: return False return self.datetime < b.datetime +def get_bootlog_from_id(bootlog_id: str) -> Optional[Bootlog]: + # TODO: implement an API endpoint for this + bl = Bootlog(bootlog_id) + for b in get_bootlogs(bl.dongle_id): + if b == bl: + return b + return None -def get_bootlogs(dongle_id: str): +def get_bootlogs(dongle_id: str) -> List[Bootlog]: api = CommaApi(get_token()) r = api.get(f'v1/devices/{dongle_id}/bootlogs') return [Bootlog(b) for b in r] From de67a28f1bae5f3677c57ed179baf8bf9c1d971d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 4 Oct 2022 19:21:23 -0700 Subject: [PATCH 068/178] Ford: handle VIN (#25966) fix ford vin --- selfdrive/car/vin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 50c2abde46..ee40cf298a 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -27,6 +27,9 @@ def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False): for addr in valid_vin_addrs: vin = results.get((addr, None)) if vin is not None: + # Ford pads with null bytes + vin = vin.replace(b'\x00', b'') + # Honda Bosch response starts with a length, trim to correct length if vin.startswith(b'\x11'): vin = vin[1:18] From 7ed064f7baff36b08713729c9c99304ef0224a58 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 4 Oct 2022 20:38:39 -0700 Subject: [PATCH 069/178] VIN: make Ford exception more explicit (#25972) * Update vin.py * only replace from end --- selfdrive/car/vin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index ee40cf298a..cf1c25e851 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -28,7 +28,8 @@ def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False): vin = results.get((addr, None)) if vin is not None: # Ford pads with null bytes - vin = vin.replace(b'\x00', b'') + if len(vin) == 24: + vin = re.sub(b'\x00*$', b'', vin) # Honda Bosch response starts with a length, trim to correct length if vin.startswith(b'\x11'): From f2859b3be55d95bc53ff90aa536a7d8ae14b212f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 4 Oct 2022 20:58:07 -0700 Subject: [PATCH 070/178] thermald: prevent started cycling too quickly (#25971) * thermald: prevent started cycling too quickly * move that * fix logging and no wait after boot Co-authored-by: Comma Device --- selfdrive/thermald/thermald.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 64ca39d296..89b81f06ec 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -278,6 +278,7 @@ def thermald_thread(end_event, hw_queue): 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 @@ -338,7 +339,8 @@ def thermald_thread(end_event, hw_queue): started_seen = True else: if onroad_conditions["ignition"] and (startup_conditions != startup_conditions_prev): - cloudlog.event("Startup blocked", startup_conditions=startup_conditions, onroad_conditions=onroad_conditions) + cloudlog.event("Startup blocked", startup_conditions=startup_conditions, onroad_conditions=onroad_conditions, error=True) + startup_conditions_prev = startup_conditions.copy() started_ts = None if off_ts is None: @@ -372,7 +374,6 @@ def thermald_thread(end_event, hw_queue): pm.send("deviceState", msg) should_start_prev = should_start - startup_conditions_prev = startup_conditions.copy() # Log to statsd statlog.gauge("free_space_percent", msg.deviceState.freeSpacePercent) From e5d2c3ce7abd9fa4c82a774e765e30ba0a97461a Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Tue, 4 Oct 2022 21:19:04 -0700 Subject: [PATCH 071/178] Planner cleanup (#25969) --- selfdrive/controls/lib/lateral_planner.py | 3 --- .../lib/longitudinal_mpc_lib/long_mpc.py | 25 ------------------- .../controls/lib/longitudinal_planner.py | 4 +-- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 3470754bc6..2ad5f784d7 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -22,7 +22,6 @@ class LateralPlanner: self.solution_invalid_cnt = 0 self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) - self.path_xyz_stds = np.ones((TRAJECTORY_SIZE, 3)) self.plan_yaw = np.zeros((TRAJECTORY_SIZE,)) self.plan_curv_rate = np.zeros((TRAJECTORY_SIZE,)) self.t_idxs = np.arange(TRAJECTORY_SIZE) @@ -45,8 +44,6 @@ class LateralPlanner: self.path_xyz = np.column_stack([md.position.x, md.position.y, md.position.z]) self.t_idxs = np.array(md.position.t) self.plan_yaw = np.array(md.orientation.z) - if len(md.position.xStd) == TRAJECTORY_SIZE: - self.path_xyz_stds = np.column_stack([md.position.xStd, md.position.yStd, md.position.zStd]) # Lane change logic desire_state = md.meta.desireState diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 01a69d6f87..695222ed4d 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -255,9 +255,6 @@ class LongitudinalMpc: elif self.mode == 'blended': cost_weights = [0., 0.2, 0.25, 1.0, 0.0, 1.0] constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, 50.0] - elif self.mode == 'e2e': - cost_weights = [0., 0.2, 0.25, 1., 0.0, .1] - constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, 0.0] else: raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner cost set') self.set_cost_weights(cost_weights, constraint_cost_weights) @@ -386,28 +383,6 @@ class LongitudinalMpc: (lead_1_obstacle[0] - lead_0_obstacle[0]): self.source = 'lead1' - - def update_with_xva(self, x, v, a): - self.params[:,0] = -10. - self.params[:,1] = 10. - self.params[:,2] = 1e5 - self.params[:,4] = T_FOLLOW - self.params[:,5] = LEAD_DANGER_FACTOR - - # 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 - for i in range(N): - self.solver.cost_set(i, "yref", self.yref[i]) - self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) - self.params[:,3] = np.copy(self.prev_a) - self.run() - def run(self): # t0 = sec_since_boot() # reset = 0 diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 6ec3762a94..a0363536ca 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -87,8 +87,8 @@ class LongitudinalPlanner: j = np.zeros(len(T_IDXS_MPC)) return x, v, a, j - def update(self, sm): - if self.param_read_counter % 50 == 0: + def update(self, sm, read=True): + if self.param_read_counter % 50 == 0 and read: self.read_param() self.param_read_counter += 1 From 300577f38f963bb2854a6139ed50544d9bb493b1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Oct 2022 13:00:22 -0700 Subject: [PATCH 072/178] fixup toggle cleanup --- selfdrive/controls/controlsd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 9cf079a2b3..6ce1156baf 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -145,8 +145,9 @@ class Controls: # cleanup old params if not self.CP.experimentalLongitudinalAvailable: - params.remove("EndToEndLong") params.remove("ExperimentalLongitudinalEnabled") + if not self.CP.openpilotLongitudinalControl: + params.remove("EndToEndLong") self.CC = car.CarControl.new_message() self.CS_prev = car.CarState.new_message() From c8b7d297b6645b82406b75f93c3ee37643575c50 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 5 Oct 2022 15:39:40 -0700 Subject: [PATCH 073/178] docs: specify Lexus Safety System+ package for RX 2016 (#25974) * docs: specify Lexus Safety System+ package for RX 2016 This package isn't standard on the 2016 MY. https://cdn.dealereprocess.org/cdn/brochures/lexus/2016-rx350.pdf * hybrid too Co-authored-by: Adeeb Shihadeh Co-authored-by: Adeeb Shihadeh --- docs/CARS.md | 8 +++++--- selfdrive/car/toyota/values.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index ecd6932e87..82ef37ae8d 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 204 Supported Cars +# 206 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -110,9 +110,11 @@ A supported vehicle is one that just works when you install a comma three. All s |Lexus|NX Hybrid 2018-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2016-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|RX 2016|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|RX 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|RX Hybrid 2016-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|RX Hybrid 2016|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| +|Lexus|RX Hybrid 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Mazda|CX-5 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda| diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 1f8ab2498d..0075a483f6 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -171,8 +171,14 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020-21"), CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020-21"), CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2017-20"), - CAR.LEXUS_RX: ToyotaCarInfo("Lexus RX 2016-19", footnotes=[Footnote.DSU]), - CAR.LEXUS_RXH: ToyotaCarInfo("Lexus RX Hybrid 2016-19", footnotes=[Footnote.DSU]), + CAR.LEXUS_RX: [ + ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Lexus RX 2017-19", footnotes=[Footnote.DSU]), + ], + CAR.LEXUS_RXH: [ + ToyotaCarInfo("Lexus RX Hybrid 2016", "Lexus Safety System+", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Lexus RX Hybrid 2017-19", footnotes=[Footnote.DSU]), + ], CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"), CAR.LEXUS_RXH_TSS2: ToyotaCarInfo("Lexus RX Hybrid 2020-21"), } From 06e283a7504b23de79f12ec2c0a7ab280eda57e4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Oct 2022 16:10:14 -0700 Subject: [PATCH 074/178] UI: only show cell settings on non-prime connection (#25067) * fix indentation * add primeTypeChanged signal to uiState * hide advanced networking toggles on prime type change * switch between map settings on prime type change * cleanup * remove duplicate code, wait for signal Co-authored-by: Cameron Clough --- selfdrive/ui/qt/maps/map_settings.cc | 5 +++-- selfdrive/ui/qt/offroad/networking.cc | 15 ++++++++++++--- selfdrive/ui/qt/offroad/networking.h | 3 +++ selfdrive/ui/qt/offroad/wifiManager.cc | 20 ++++++++++---------- selfdrive/ui/qt/widgets/prime.cc | 6 +----- selfdrive/ui/ui.cc | 7 +++++++ selfdrive/ui/ui.h | 4 +++- 7 files changed, 39 insertions(+), 21 deletions(-) diff --git a/selfdrive/ui/qt/maps/map_settings.cc b/selfdrive/ui/qt/maps/map_settings.cc index dd2ad04a7d..3205ca517d 100644 --- a/selfdrive/ui/qt/maps/map_settings.cc +++ b/selfdrive/ui/qt/maps/map_settings.cc @@ -115,7 +115,9 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { stack->addWidget(main_widget); stack->addWidget(no_prime_widget); - stack->setCurrentIndex(uiState()->prime_type ? 0 : 1); + connect(uiState(), &UIState::primeTypeChanged, [=](int prime_type) { + stack->setCurrentIndex(prime_type ? 0 : 1); + }); QVBoxLayout *wrapper = new QVBoxLayout(this); wrapper->addWidget(stack); @@ -194,7 +196,6 @@ void MapPanel::parseResponse(const QString &response, bool success) { } void MapPanel::refresh() { - stack->setCurrentIndex(uiState()->prime_type ? 0 : 1); if (cur_destinations == prev_destinations) return; QJsonDocument doc = QJsonDocument::fromJson(cur_destinations.trimmed().toUtf8()); diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index 0ed6317c3c..13697adfb5 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -8,9 +8,11 @@ #include #include +#include "selfdrive/ui/ui.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/widgets/controls.h" +#include "selfdrive/ui/qt/widgets/prime.h" #include "selfdrive/ui/qt/widgets/scrollview.h" @@ -150,7 +152,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid // Roaming toggle const bool roamingEnabled = params.getBool("GsmRoaming"); - ToggleControl *roamingToggle = new ToggleControl(tr("Enable Roaming"), "", "", roamingEnabled); + roamingToggle = new ToggleControl(tr("Enable Roaming"), "", "", roamingEnabled); QObject::connect(roamingToggle, &ToggleControl::toggleFlipped, [=](bool state) { params.putBool("GsmRoaming", state); wifi->updateGsmSettings(state, QString::fromStdString(params.get("GsmApn")), params.getBool("GsmMetered")); @@ -158,7 +160,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid list->addItem(roamingToggle); // APN settings - ButtonControl *editApnButton = new ButtonControl(tr("APN Setting"), tr("EDIT")); + editApnButton = new ButtonControl(tr("APN Setting"), tr("EDIT")); connect(editApnButton, &ButtonControl::clicked, [=]() { const QString cur_apn = QString::fromStdString(params.get("GsmApn")); QString apn = InputDialog::getText(tr("Enter APN"), this, tr("leave blank for automatic configuration"), false, -1, cur_apn).trimmed(); @@ -174,7 +176,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid // Metered toggle const bool metered = params.getBool("GsmMetered"); - ToggleControl *meteredToggle = new ToggleControl(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered connection"), "", metered); + meteredToggle = new ToggleControl(tr("Cellular Metered"), tr("Prevent large data uploads when on a metered connection"), "", metered); QObject::connect(meteredToggle, &SshToggle::toggleFlipped, [=](bool state) { params.putBool("GsmMetered", state); wifi->updateGsmSettings(params.getBool("GsmRoaming"), QString::fromStdString(params.get("GsmApn")), state); @@ -184,6 +186,13 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid // Set initial config wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")), metered); + connect(uiState(), &UIState::primeTypeChanged, this, [=](int prime_type) { + bool gsmVisible = prime_type == PrimeType::NONE || prime_type == PrimeType::LITE; + roamingToggle->setVisible(gsmVisible); + editApnButton->setVisible(gsmVisible); + meteredToggle->setVisible(gsmVisible); + }); + main_layout->addWidget(new ScrollView(list, this)); main_layout->addStretch(1); } diff --git a/selfdrive/ui/qt/offroad/networking.h b/selfdrive/ui/qt/offroad/networking.h index 4fc9a53d93..79cbcc3493 100644 --- a/selfdrive/ui/qt/offroad/networking.h +++ b/selfdrive/ui/qt/offroad/networking.h @@ -40,6 +40,9 @@ public: private: LabelControl* ipLabel; ToggleControl* tetheringToggle; + ToggleControl* roamingToggle; + ButtonControl* editApnButton; + ToggleControl* meteredToggle; WifiManager* wifi = nullptr; Params params; diff --git a/selfdrive/ui/qt/offroad/wifiManager.cc b/selfdrive/ui/qt/offroad/wifiManager.cc index 3a30456c93..62de3041b9 100644 --- a/selfdrive/ui/qt/offroad/wifiManager.cc +++ b/selfdrive/ui/qt/offroad/wifiManager.cc @@ -415,16 +415,16 @@ void WifiManager::addTetheringConnection() { } void WifiManager::tetheringActivated(QDBusPendingCallWatcher *call) { - int prime_type = uiState()->prime_type; - int ipv4_forward = (prime_type == PrimeType::NONE || prime_type == PrimeType::LITE); - - if (!ipv4_forward) { - QTimer::singleShot(5000, this, [=] { - qWarning() << "net.ipv4.ip_forward = 0"; - std::system("sudo sysctl net.ipv4.ip_forward=0"); - }); - } - call->deleteLater(); + int prime_type = uiState()->prime_type; + int ipv4_forward = (prime_type == PrimeType::NONE || prime_type == PrimeType::LITE); + + if (!ipv4_forward) { + QTimer::singleShot(5000, this, [=] { + qWarning() << "net.ipv4.ip_forward = 0"; + std::system("sudo sysctl net.ipv4.ip_forward=0"); + }); + } + call->deleteLater(); } void WifiManager::setTetheringEnabled(bool enabled) { diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index 04684fc765..da2f4e60d1 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -312,11 +312,7 @@ void SetupWidget::replyFinished(const QString &response, bool success) { QJsonObject json = doc.object(); int prime_type = json["prime_type"].toInt(); - - if (uiState()->prime_type != prime_type) { - uiState()->prime_type = prime_type; - Params().put("PrimeType", std::to_string(prime_type)); - } + uiState()->prime_type = prime_type; if (!json["is_paired"].toBool()) { mainLayout->setCurrentIndex(0); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index fcee00d1c8..152c7fbfcd 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -201,6 +201,13 @@ void UIState::updateStatus() { started_prev = scene.started; emit offroadTransition(!scene.started); } + + // Handle prime type change + if (prime_type != prime_type_prev) { + prime_type_prev = prime_type; + emit primeTypeChanged(prime_type); + Params().put("PrimeType", std::to_string(prime_type)); + } } UIState::UIState(QObject *parent) : QObject(parent) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index d7e51ccfeb..f60c26b59a 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -126,7 +126,7 @@ public: UIScene scene = {}; bool awake; - int prime_type = 0; + int prime_type; QString language; QTransform car_space_transform; @@ -135,6 +135,7 @@ public: signals: void uiUpdate(const UIState &s); void offroadTransition(bool offroad); + void primeTypeChanged(int prime_type); private slots: void update(); @@ -142,6 +143,7 @@ private slots: private: QTimer *timer; bool started_prev = false; + int prime_type_prev = -1; }; UIState *uiState(); From ef24c0b2ce8a4cf1dbc82886b806079889adf97b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Oct 2022 16:40:39 -0700 Subject: [PATCH 075/178] agnos 6.1 (#25973) * agnos 6.1 * staging manifest * no casync * prod manifest --- launch_env.sh | 2 +- selfdrive/sensord/rawgps/modemdiag.py | 15 +-------------- system/hardware/tici/agnos.json | 6 +++--- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 48c5696b94..88e1f2a9c5 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="6" + export AGNOS_VERSION="6.1" fi if [ -z "$PASSIVE" ]; then diff --git a/selfdrive/sensord/rawgps/modemdiag.py b/selfdrive/sensord/rawgps/modemdiag.py index cc2bc5b261..5d72aeba9e 100644 --- a/selfdrive/sensord/rawgps/modemdiag.py +++ b/selfdrive/sensord/rawgps/modemdiag.py @@ -1,5 +1,3 @@ -import os -import time import select from serial import Serial from crcmod import mkCrcFun @@ -11,18 +9,7 @@ class ModemDiag: self.pend = b'' def open_serial(self): - def op(): - return Serial("/dev/ttyUSB0", baudrate=115200, rtscts=True, dsrdtr=True, timeout=0) - try: - serial = op() - except Exception: - # TODO: this is a hack to get around modemmanager's exclusive open - print("unlocking serial...") - os.system('sudo su -c \'echo "1-1.1:1.0" > /sys/bus/usb/drivers/option/unbind\'') - os.system('sudo su -c \'echo "1-1.1:1.0" > /sys/bus/usb/drivers/option/bind\'') - time.sleep(0.5) - os.system("sudo chmod 666 /dev/ttyUSB0") - serial = op() + serial = Serial("/dev/ttyUSB0", baudrate=115200, rtscts=True, dsrdtr=True, timeout=0, exclusive=True) serial.flush() serial.reset_input_buffer() serial.reset_output_buffer() diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index dc0fa6f1c1..8534c8a978 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -41,9 +41,9 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-9db38e27c912005472f3ac02be336af4f82307295118b6db22921479d44a941d.img.xz", - "hash": "05e7ce440b33721b020a249043d9568a5898080e26411ca250fb330ad2e5ed8e", - "hash_raw": "9db38e27c912005472f3ac02be336af4f82307295118b6db22921479d44a941d", + "url": "https://commadist.azureedge.net/agnosupdate/system-b40b08912576bb972907acba7c201c1399395cbc0cba06ce6e5e3f70ab565cb5.img.xz", + "hash": "6e8fbcc21a265f7f58062abce7675dc05540e2b60cee2df56992a151ba64936f", + "hash_raw": "b40b08912576bb972907acba7c201c1399395cbc0cba06ce6e5e3f70ab565cb5", "size": 10737418240, "sparse": true, "full_check": false, From 8b41d817e401fcf577bc68dfc6654ee1e0c3cbb5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 5 Oct 2022 17:06:52 -0700 Subject: [PATCH 076/178] IsoTpParallelQuery: set separation time (#25978) * Specify separation time in openpilot * comment * Update selfdrive/car/isotp_parallel_query.py * Update selfdrive/car/isotp_parallel_query.py --- panda | 2 +- selfdrive/car/isotp_parallel_query.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/panda b/panda index 9bcd9b9a24..3334dc21f5 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 9bcd9b9a24149b241992f26caf9920d427ee609d +Subproject commit 3334dc21f5c55007c5a754dfd8ee5d642be3e2bb diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 94c8d052b3..4b4bdcc0ca 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -68,7 +68,10 @@ class IsoTpParallelQuery: self.bus, sub_addr=sub_addr, debug=self.debug) max_len = 8 if sub_addr is None else 7 - return IsoTpMessage(can_client, timeout=0, max_len=max_len, debug=self.debug) + # uses iso-tp frame separation time of 10 ms + # TODO: use single_frame_mode so ECUs can send as fast as they want, + # as well as reduces chances we process messages from previous queries + return IsoTpMessage(can_client, timeout=0, separation_time=0.01, debug=self.debug, max_len=max_len) def get_data(self, timeout, total_timeout=60.): self._drain_rx() From 6db9f051f761506e4d91ef1879cfae4c8cb1ddc8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 5 Oct 2022 17:09:46 -0700 Subject: [PATCH 077/178] Car docs: test no duplicate years (#25975) * unit test * clean up * revert test * clean up * like this like this * no model model --- selfdrive/car/tests/test_docs.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py index 191b36b8f2..b7056df5b3 100755 --- a/selfdrive/car/tests/test_docs.py +++ b/selfdrive/car/tests/test_docs.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from collections import defaultdict import re import unittest @@ -20,6 +21,15 @@ class TestCarDocs(unittest.TestCase): self.assertEqual(generated_cars_md, current_cars_md, "Run selfdrive/car/docs.py to update the compatibility documentation") + def test_duplicate_years(self): + make_model_years = defaultdict(list) + for car in self.all_cars: + with self.subTest(car_info_name=car.name): + make_model = (car.make, car.model) + for year in car.year_list: + self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year") + make_model_years[make_model].append(year) + def test_missing_car_info(self): all_car_info_platforms = get_interface_attr("CAR_INFO", combine_brands=True).keys() for platform in sorted(interfaces.keys()): From 6a8a38b1a13b678bddbfdbcf5ba438bd7516c9be Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Oct 2022 17:13:48 -0700 Subject: [PATCH 078/178] pigeond: prevent locking up a CPU core (#25979) --- selfdrive/sensord/pigeond.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/sensord/pigeond.py b/selfdrive/sensord/pigeond.py index e1fa2f4cad..f47cefed6c 100755 --- a/selfdrive/sensord/pigeond.py +++ b/selfdrive/sensord/pigeond.py @@ -251,6 +251,9 @@ def main(): msg = messaging.new_message('ubloxRaw', len(dat)) msg.ubloxRaw = dat[:] pm.send('ubloxRaw', msg) + else: + # prevent locking up a CPU core if ublox disconnects + time.sleep(0.001) if __name__ == "__main__": main() From c9c46c1b36d5784ed4245979eee5cc5f042fba12 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 5 Oct 2022 19:07:59 -0700 Subject: [PATCH 079/178] Revert "updated: configure branch upstream (#25916)" This reverts commit 17ed8dd0e9a8ecd0dcf8b573176ea27355c6a1ee. --- selfdrive/updated.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/updated.py b/selfdrive/updated.py index f96abcb7d5..9568b28ae3 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -327,7 +327,7 @@ class Updater: self._has_internet = False setup_git_options(OVERLAY_MERGED) - output = run(["git", "ls-remote", "--heads", "origin"], OVERLAY_MERGED) + output = run(["git", "ls-remote", "--heads"], OVERLAY_MERGED) self.branches = defaultdict(lambda: None) for line in output.split('\n'): @@ -363,7 +363,6 @@ class Updater: cloudlog.info("git reset in progress") cmds = [ ["git", "checkout", "--force", "--no-recurse-submodules", "-B", branch, "FETCH_HEAD"], - ["git", "branch", "--set-upstream-to", f"origin/{branch}"], ["git", "reset", "--hard"], ["git", "clean", "-xdff"], ["git", "submodule", "init"], From e59008bf9335fba195f1df8741954a9f34ae00d9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 5 Oct 2022 20:59:04 -0700 Subject: [PATCH 080/178] rawgpsd: more robust + simple test (#25977) * rawgps cleanup * wait for modem manager * cleanup Co-authored-by: Comma Device --- selfdrive/sensord/rawgps/rawgpsd.py | 165 +++++++++++++++--------- selfdrive/sensord/rawgps/test_rawgps.py | 49 +++++++ 2 files changed, 150 insertions(+), 64 deletions(-) create mode 100755 selfdrive/sensord/rawgps/test_rawgps.py diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index 7c4582902b..5149ab6473 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -5,23 +5,32 @@ import signal import itertools import math import time +import subprocess from typing import NoReturn from struct import unpack_from, calcsize, pack -import cereal.messaging as messaging + from cereal import log -from system.swaglog import cloudlog +import cereal.messaging as messaging from laika.gps_time import GPSTime - +from system.swaglog import cloudlog from selfdrive.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv -from selfdrive.sensord.rawgps.structs import dict_unpacker -from selfdrive.sensord.rawgps.structs import gps_measurement_report, gps_measurement_report_sv -from selfdrive.sensord.rawgps.structs import glonass_measurement_report, glonass_measurement_report_sv -from selfdrive.sensord.rawgps.structs import oemdre_measurement_report, oemdre_measurement_report_sv -from selfdrive.sensord.rawgps.structs import LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT -from selfdrive.sensord.rawgps.structs import position_report, LOG_GNSS_POSITION_REPORT, LOG_GNSS_OEMDRE_MEASUREMENT_REPORT +from selfdrive.sensord.rawgps.structs import (dict_unpacker, position_report, + gps_measurement_report, gps_measurement_report_sv, + glonass_measurement_report, glonass_measurement_report_sv, + oemdre_measurement_report, oemdre_measurement_report_sv, + LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT, + LOG_GNSS_POSITION_REPORT, LOG_GNSS_OEMDRE_MEASUREMENT_REPORT) DEBUG = int(os.getenv("DEBUG", "0"))==1 +LOG_TYPES = [ + LOG_GNSS_GPS_MEASUREMENT_REPORT, + LOG_GNSS_GLONASS_MEASUREMENT_REPORT, + LOG_GNSS_OEMDRE_MEASUREMENT_REPORT, + LOG_GNSS_POSITION_REPORT, +] + + miscStatusFields = { "multipathEstimateIsValid": 0, "directionIsValid": 1, @@ -65,59 +74,43 @@ measurementStatusGlonassFields = { "glonassTimeMarkValid": 17 } -def main() -> NoReturn: - unpack_gps_meas, size_gps_meas = dict_unpacker(gps_measurement_report, True) - unpack_gps_meas_sv, size_gps_meas_sv = dict_unpacker(gps_measurement_report_sv, True) - unpack_glonass_meas, size_glonass_meas = dict_unpacker(glonass_measurement_report, True) - unpack_glonass_meas_sv, size_glonass_meas_sv = dict_unpacker(glonass_measurement_report_sv, True) - - unpack_oemdre_meas, size_oemdre_meas = dict_unpacker(oemdre_measurement_report, True) - unpack_oemdre_meas_sv, size_oemdre_meas_sv = dict_unpacker(oemdre_measurement_report_sv, True) - - log_types = [ - LOG_GNSS_GPS_MEASUREMENT_REPORT, - LOG_GNSS_GLONASS_MEASUREMENT_REPORT, - LOG_GNSS_OEMDRE_MEASUREMENT_REPORT, - ] - pub_types = ['qcomGnss'] - unpack_position, _ = dict_unpacker(position_report) - log_types.append(LOG_GNSS_POSITION_REPORT) - pub_types.append("gpsLocation") - - # connect to modem - diag = ModemDiag() - - # NV enable OEMDRE +def try_setup_logs(diag, log_types): + for _ in range(5): + try: + setup_logs(diag, log_types) + break + except Exception: + cloudlog.exception("setup logs failed, trying again") + else: + raise Exception(f"setup logs failed, {log_types=}") + +def mmcli(cmd: str) -> None: + for _ in range(5): + try: + subprocess.check_call(f"mmcli -m 0 {cmd}", shell=True) + break + except subprocess.CalledProcessError: + cloudlog.exception("rawgps.mmcli_command_failed") + else: + raise Exception(f"failed to execute mmcli command {cmd=}") + +def setup_quectel(diag: ModemDiag): + # enable OEMDRE in the NV # TODO: it has to reboot for this to take effect DIAG_NV_READ_F = 38 DIAG_NV_WRITE_F = 39 NV_GNSS_OEM_FEATURE_MASK = 7165 + send_recv(diag, DIAG_NV_WRITE_F, pack(' NoReturn: GPSDIAG_OEM_DRE_ON = 1 # gpsdiag_OemControlReqType - opcode, payload = send_recv(diag, DIAG_SUBSYS_CMD_F, pack(' NoReturn: + unpack_gps_meas, size_gps_meas = dict_unpacker(gps_measurement_report, True) + unpack_gps_meas_sv, size_gps_meas_sv = dict_unpacker(gps_measurement_report_sv, True) + + unpack_glonass_meas, size_glonass_meas = dict_unpacker(glonass_measurement_report, True) + unpack_glonass_meas_sv, size_glonass_meas_sv = dict_unpacker(glonass_measurement_report_sv, True) + + unpack_oemdre_meas, size_oemdre_meas = dict_unpacker(oemdre_measurement_report, True) + unpack_oemdre_meas_sv, size_oemdre_meas_sv = dict_unpacker(oemdre_measurement_report_sv, True) + + unpack_position, _ = dict_unpacker(position_report) + + # wait for ModemManager to come up + cloudlog.warning("waiting for modem to come up") + while True: + ret = subprocess.call("mmcli -m 0 --location-status", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) + if ret == 0: + break + time.sleep(0.1) + + # connect to modem + diag = ModemDiag() + + def cleanup(sig, frame): + cloudlog.warning(f"caught sig {sig}, disabling quectel gps") + teardown_quectel(diag) + cloudlog.warning("quectel cleanup done") + sys.exit(0) + signal.signal(signal.SIGINT, cleanup) + signal.signal(signal.SIGTERM, cleanup) + + setup_quectel(diag) + cloudlog.warning("quectel setup done") + + pm = messaging.PubMaster(['qcomGnss', 'gpsLocation']) while 1: opcode, payload = diag.recv() assert opcode == DIAG_LOG_F + (pending_msgs, log_outer_length), inner_log_packet = unpack_from(' 0: cloudlog.debug("have %d pending messages" % pending_msgs) assert log_outer_length == len(inner_log_packet) + (log_inner_length, log_type, log_time), log_payload = unpack_from(' 0, "rawgpsd didn't start outputting messages in time" + + et = time.monotonic() - start_time + assert et < 5, f"rawgpsd took {et:.1f}s to start" + managed_processes['rawgpsd'].stop() + + def test_turns_off_gnss(self): + for s in (0.1, 0.5, 1, 5): + managed_processes['rawgpsd'].start() + time.sleep(s) + managed_processes['rawgpsd'].stop() + + ls = subprocess.check_output("mmcli -m 0 --location-status --output-json", shell=True, encoding='utf-8') + loc_status = json.loads(ls) + assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} + + +if __name__ == "__main__": + unittest.main() From dc72438be57e8908b1946df2a0ee81202a70469c Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Wed, 5 Oct 2022 21:16:41 -0700 Subject: [PATCH 081/178] regen: add arg for migrating sensorEvents with old timestamps (#25980) * add event logMonoTime * add arg for old logtime --- selfdrive/test/process_replay/regen.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index 54c84978c2..eee3745f8e 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -190,7 +190,7 @@ def migrate_carparams(lr): return all_msgs -def migrate_sensorEvents(lr): +def migrate_sensorEvents(lr, old_logtime=False): all_msgs = [] for msg in lr: if msg.which() != 'sensorEventsDEPRECATED': @@ -214,6 +214,8 @@ def migrate_sensorEvents(lr): m = messaging.new_message(sensor_service) m.valid = True + if old_logtime: + m.logMonoTime = msg.logMonoTime m_dat = getattr(m, sensor_service) m_dat.version = evt.version From a6ba073231761e06ac6f070a01b434243d9d0693 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 6 Oct 2022 12:17:22 +0800 Subject: [PATCH 082/178] canbana: complete basic functions (#25965) * add chart header * get all signal val from logs * loop in selected range * clear list before append * automatically zoom on yaxis * cleanup * sync charts * fix event_begin_sec * set the color of rubber * add TODO * sync slider with charts * keep video aspect ratio * sync plot buttons * reduce flickers * cleanup * refactor detail view * clear counters * more use qcamera --- tools/cabana/SConscript | 3 +- tools/cabana/cabana.cc | 6 +- tools/cabana/chartswidget.cc | 206 +++++++++++----- tools/cabana/chartswidget.h | 52 +++- tools/cabana/detailwidget.cc | 421 +++++++++------------------------ tools/cabana/detailwidget.h | 86 +++---- tools/cabana/mainwin.cc | 9 +- tools/cabana/mainwin.h | 4 +- tools/cabana/messageswidget.cc | 35 ++- tools/cabana/messageswidget.h | 2 +- tools/cabana/parser.cc | 114 +++++++-- tools/cabana/parser.h | 58 +++-- tools/cabana/signaledit.cc | 203 ++++++++++++++++ tools/cabana/signaledit.h | 50 ++++ tools/cabana/videowidget.cc | 49 ++-- tools/cabana/videowidget.h | 5 + tools/replay/logreader.cc | 18 +- tools/replay/logreader.h | 7 +- tools/replay/replay.cc | 21 +- tools/replay/replay.h | 9 +- tools/replay/route.cc | 6 +- tools/replay/route.h | 3 +- 22 files changed, 833 insertions(+), 534 deletions(-) create mode 100644 tools/cabana/signaledit.cc create mode 100644 tools/cabana/signaledit.h diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 0caae14e92..c87e2cdd94 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -16,6 +16,5 @@ if arch in ['x86_64', 'Darwin'] and GetOption('extras'): qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] Import('replay_lib') - # qt_env["LD_LIBRARY_PATH"] = [Dir(f"#opendbc/can").abspath] cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs - qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'videowidget.cc', 'parser.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'videowidget.cc', 'signaledit.cc', 'parser.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 0adc744b49..20cd889023 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -5,7 +5,6 @@ #include "tools/cabana/mainwin.h" const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; -Parser *parser = nullptr; int main(int argc, char *argv[]) { initApp(argc, argv); @@ -16,7 +15,6 @@ int main(int argc, char *argv[]) { cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); cmd_parser.addOption({"demo", "use a demo route instead of providing your own"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); - cmd_parser.addOption({"qcam", "use qcamera"}); cmd_parser.process(app); const QStringList args = cmd_parser.positionalArguments(); if (args.empty() && !cmd_parser.isSet("demo")) { @@ -24,8 +22,8 @@ int main(int argc, char *argv[]) { } const QString route = args.empty() ? DEMO_ROUTE : args.first(); - parser = new Parser(&app); - if (!parser->loadRoute(route, cmd_parser.value("data_dir"), cmd_parser.isSet("qcam"))) { + Parser p(&app); + if (!p.loadRoute(route, cmd_parser.value("data_dir"), true)) { return 0; } MainWindow w; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index a8fe39968e..836eb34946 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -1,20 +1,24 @@ #include "tools/cabana/chartswidget.h" +#include +#include +#include +#include +#include #include +#include -using namespace QtCharts; - -int64_t get_raw_value(const QByteArray &msg, const Signal &sig) { +int64_t get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) { int64_t ret = 0; int i = sig.msb / 8; int bits = sig.size; - while (i >= 0 && i < msg.size() && bits > 0) { + while (i >= 0 && i < data_size && bits > 0) { int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8; int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1; int size = msb - lsb + 1; - uint64_t d = (msg[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); + uint64_t d = (data[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); ret |= d << (bits - size); bits -= size; @@ -26,77 +30,163 @@ int64_t get_raw_value(const QByteArray &msg, const Signal &sig) { ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - connect(parser, &Parser::updated, this, &ChartsWidget::updateState); connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart); connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart); + connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart); } void ChartsWidget::addChart(const QString &id, const QString &sig_name) { const QString char_name = id + sig_name; if (charts.find(char_name) == charts.end()) { - QLineSeries *series = new QLineSeries(); - series->setUseOpenGL(true); - auto chart = new QChart(); - chart->setTitle(id + ": " + sig_name); - chart->addSeries(series); - chart->createDefaultAxes(); - chart->legend()->hide(); - auto chart_view = new QChartView(chart); - chart_view->setMinimumSize({width(), 300}); - chart_view->setMaximumSize({width(), 300}); - chart_view->setRenderHint(QPainter::Antialiasing); - main_layout->addWidget(chart_view); - charts[char_name] = {.id = id, .sig_name = sig_name, .chart_view = chart_view}; + auto chart = new ChartWidget(id, sig_name, this); + main_layout->addWidget(chart); + charts[char_name] = chart; } } void ChartsWidget::removeChart(const QString &id, const QString &sig_name) { - auto it = charts.find(id + sig_name); - if (it == charts.end()) return; - - delete it->second.chart_view; - charts.erase(it); + if (auto it = charts.find(id + sig_name); it != charts.end()) { + it->second->deleteLater(); + charts.erase(it); + } } -void ChartsWidget::updateState() { - static double last_update = millis_since_boot(); - double current_ts = millis_since_boot(); - bool update = (current_ts - last_update) > 500; - if (update) { - last_update = current_ts; +ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *parent) : id(id), sig_name(sig_name), QWidget(parent) { + QStackedLayout *stacked = new QStackedLayout(this); + stacked->setStackingMode(QStackedLayout::StackAll); + + QWidget *chart_widget = new QWidget(this); + QVBoxLayout *chart_layout = new QVBoxLayout(chart_widget); + chart_layout->setSpacing(0); + chart_layout->setContentsMargins(0, 0, 0, 0); + + QWidget *header = new QWidget(this); + header->setStyleSheet("background-color:white"); + QHBoxLayout *header_layout = new QHBoxLayout(header); + header_layout->setContentsMargins(11, 11, 11, 0); + auto title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id)); + header_layout->addWidget(title); + header_layout->addStretch(); + zoom_label = new QLabel("", this); + header_layout->addWidget(zoom_label); + QPushButton *zoom_in = new QPushButton("↺", this); + zoom_in->setToolTip(tr("reset zoom")); + QObject::connect(zoom_in, &QPushButton::clicked, []() { parser->resetRange(); }); + header_layout->addWidget(zoom_in); + + QPushButton *remove_btn = new QPushButton("✖", this); + QObject::connect(remove_btn, &QPushButton::clicked, [=]() { + emit parser->hidePlot(id, sig_name); + }); + header_layout->addWidget(remove_btn); + chart_layout->addWidget(header); + + QLineSeries *series = new QLineSeries(); + series->setUseOpenGL(true); + auto chart = new QChart(); + chart->setTitle(sig_name); + chart->addSeries(series); + chart->createDefaultAxes(); + chart->legend()->hide(); + QFont font; + font.setBold(true); + chart->setTitleFont(font); + chart->setMargins({0, 0, 0, 0}); + chart->layout()->setContentsMargins(0, 0, 0, 0); + QObject::connect(dynamic_cast(chart->axisX()), &QValueAxis::rangeChanged, parser, &Parser::setRange); + + chart_view = new QChartView(chart); + chart_view->setFixedHeight(300); + chart_view->setRenderHint(QPainter::Antialiasing); + chart_view->setRubberBand(QChartView::HorizontalRubberBand); + if (auto rubber = chart_view->findChild()) { + QPalette pal; + pal.setBrush(QPalette::Base, QColor(0, 0, 0, 80)); + rubber->setPalette(pal); } + chart_layout->addWidget(chart_view); + chart_layout->addStretch(); - auto getSig = [=](const QString &id, const QString &name) -> const Signal * { - for (auto &sig : parser->getMsg(id)->sigs) { - if (name == sig.name.c_str()) return &sig; - } - return nullptr; - }; - - for (auto &[_, c] : charts) { - if (auto sig = getSig(c.id, c.sig_name)) { - const auto &can_data = parser->can_msgs[c.id].back(); - int64_t val = get_raw_value(can_data.dat, *sig); - if (sig->is_signed) { - val -= ((val >> (sig->size - 1)) & 0x1) ? (1ULL << sig->size) : 0; - } - double value = val * sig->factor + sig->offset; + stacked->addWidget(chart_widget); + line_marker = new LineMarker(chart, this); + stacked->addWidget(line_marker); + line_marker->setAttribute(Qt::WA_TransparentForMouseEvents, true); + line_marker->raise(); - if (value > c.max_y) c.max_y = value; - if (value < c.min_y) c.min_y = value; + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + QObject::connect(parser, &Parser::updated, this, &ChartWidget::updateState); + QObject::connect(parser, &Parser::rangeChanged, this, &ChartWidget::rangeChanged); + QObject::connect(parser, &Parser::eventsMerged, this, &ChartWidget::updateSeries); - while (c.data.size() > DATA_LIST_SIZE) { - c.data.pop_front(); - } - c.data.push_back({can_data.ts / 1000., value}); - - if (update) { - QChart *chart = c.chart_view->chart(); - QLineSeries *series = (QLineSeries *)chart->series()[0]; - series->replace(c.data); - chart->axisX()->setRange(c.data.front().x(), c.data.back().x()); - chart->axisY()->setRange(c.min_y, c.max_y); + updateSeries(); +} + +void ChartWidget::updateState() { + line_marker->update(); +} + +void ChartWidget::updateSeries() { + const Signal *sig = parser->getSig(id, sig_name); + auto events = parser->replay->events(); + if (!sig || !events) return; + + auto l = id.split(':'); + int bus = l[0].toInt(); + uint32_t address = l[1].toUInt(nullptr, 16); + + vals.clear(); + vals.reserve(3 * 60 * 100); + uint64_t route_start_time = parser->replay->routeStartTime(); + for (auto &evt : *events) { + if (evt->which == cereal::Event::Which::CAN) { + for (auto c : evt->event.getCan()) { + if (bus == c.getSrc() && address == c.getAddress()) { + auto dat = c.getDat(); + int64_t val = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sig); + if (sig->is_signed) { + val -= ((val >> (sig->size - 1)) & 0x1) ? (1ULL << sig->size) : 0; + } + double value = val * sig->factor + sig->offset; + double ts = (evt->mono_time - route_start_time) / (double)1e9; // seconds + vals.push_back({ts, value}); + } } } } + QLineSeries *series = (QLineSeries *)chart_view->chart()->series()[0]; + series->replace(vals); + auto [begin, end] = parser->range(); + chart_view->chart()->axisX()->setRange(begin, end); +} + +void ChartWidget::rangeChanged(qreal min, qreal max) { + auto axis_x = dynamic_cast(chart_view->chart()->axisX()); + if (axis_x->min() != min || axis_x->max() != max) { + axis_x->setRange(min, max); + } + // auto zoom on yaxis + double min_y = 0, max_y = 0; + for (auto &p : vals) { + if (p.x() > max) break; + + if (p.x() >= min) { + if (p.y() < min_y) min_y = p.y(); + if (p.y() > max_y) max_y = p.y(); + } + } + chart_view->chart()->axisY()->setRange(min_y * 0.95, max_y * 1.05); +} + +LineMarker::LineMarker(QChart *chart, QWidget *parent) : chart(chart), QWidget(parent) {} + +void LineMarker::paintEvent(QPaintEvent *event) { + auto axis_x = dynamic_cast(chart->axisX()); + if (axis_x->max() <= axis_x->min()) return; + + double x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); + QPainter p(this); + QPen pen = QPen(Qt::black); + pen.setWidth(2); + p.setPen(pen); + p.drawLine(QPointF{x, 50.}, QPointF{x, (qreal)height() - 11}); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 7bc8335a32..1be5fdeecb 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -1,17 +1,53 @@ #pragma once +#include + +#include #include #include #include #include -#include #include "tools/cabana/parser.h" +using namespace QtCharts; + +class LineMarker : public QWidget { +Q_OBJECT + +public: + LineMarker(QChart *chart, QWidget *parent); + void paintEvent(QPaintEvent *event) override; + +private: + QChart *chart; +}; + +class ChartWidget : public QWidget { +Q_OBJECT + +public: + ChartWidget(const QString &id, const QString &sig_name, QWidget *parent); + inline QChart *chart() const { return chart_view->chart(); } + +protected: + void updateState(); + void addData(const CanData &can_data, const Signal &sig); + void updateSeries(); + void rangeChanged(qreal min, qreal max); + + QString id; + QString sig_name; + QLabel *zoom_label; + QChartView *chart_view = nullptr; + LineMarker *line_marker = nullptr; + QList vals; +}; + class ChartsWidget : public QWidget { Q_OBJECT - public: +public: ChartsWidget(QWidget *parent = nullptr); inline bool hasChart(const QString &id, const QString &sig_name) { return charts.find(id+sig_name) != charts.end(); @@ -20,15 +56,7 @@ class ChartsWidget : public QWidget { void removeChart(const QString &id, const QString &sig_name); void updateState(); - protected: +protected: QVBoxLayout *main_layout; - struct SignalChart { - QString id; - QString sig_name; - int max_y = 0; - int min_y = 0; - QList data; - QtCharts::QChartView *chart_view = nullptr; - }; - std::map charts; + std::map charts; }; diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 6799376577..e573a01970 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -1,68 +1,53 @@ + #include "tools/cabana/detailwidget.h" #include +#include #include -#include #include #include #include +#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/scrollview.h" -const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; - -static QVector BIG_ENDIAN_START_BITS = []() { - QVector ret; - for (int i = 0; i < 64; i++) { - for (int j = 7; j >= 0; j--) { - ret.push_back(j + i * 8); - } - } - return ret; -}(); - -static int bigEndianBitIndex(int index) { - // TODO: Add a helper function in dbc.h - return BIG_ENDIAN_START_BITS.indexOf(index); +inline const QString &getColor(int i) { + static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; + return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; } +// DetailWidget + DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); - QLabel *title = new QLabel(tr("SELECTED MESSAGE:"), this); - main_layout->addWidget(title); - QHBoxLayout *name_layout = new QHBoxLayout(); name_label = new QLabel(this); name_label->setStyleSheet("font-weight:bold;"); - name_layout->addWidget(name_label); - name_layout->addStretch(); + name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + name_label->setAlignment(Qt::AlignCenter); + main_layout->addWidget(name_label); + + // title + QHBoxLayout *title_layout = new QHBoxLayout(); + time_label = new QLabel(this); + title_layout->addWidget(time_label); + title_layout->addStretch(); + edit_btn = new QPushButton(tr("Edit"), this); edit_btn->setVisible(false); - QObject::connect(edit_btn, &QPushButton::clicked, [=]() { - EditMessageDialog dlg(msg_id, this); - int ret = dlg.exec(); - if (ret) { - setMsg(msg_id); - } - }); - name_layout->addWidget(edit_btn); - main_layout->addLayout(name_layout); + title_layout->addWidget(edit_btn); + main_layout->addLayout(title_layout); + // binary view binary_view = new BinaryView(this); main_layout->addWidget(binary_view); + // scroll area QHBoxLayout *signals_layout = new QHBoxLayout(); signals_layout->addWidget(new QLabel(tr("Signals"))); signals_layout->addStretch(); add_sig_btn = new QPushButton(tr("Add signal"), this); add_sig_btn->setVisible(false); - QObject::connect(add_sig_btn, &QPushButton::clicked, [=]() { - AddSignalDialog dlg(msg_id, this); - int ret = dlg.exec(); - if (ret) { - setMsg(msg_id); - } - }); signals_layout->addWidget(add_sig_btn); main_layout->addLayout(signals_layout); @@ -72,238 +57,67 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { signal_edit_layout->setSpacing(2); container_layout->addLayout(signal_edit_layout); - messages_view = new MessagesView(this); - container_layout->addWidget(messages_view); + history_log = new HistoryLog(this); + container_layout->addWidget(history_log); QScrollArea *scroll = new QScrollArea(this); scroll->setWidget(container); scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); main_layout->addWidget(scroll); - setFixedWidth(600); - connect(parser, &Parser::updated, this, &DetailWidget::updateState); + QObject::connect(add_sig_btn, &QPushButton::clicked, this, &DetailWidget::addSignal); + QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); + QObject::connect(parser, &Parser::updated, this, &DetailWidget::updateState); } -void DetailWidget::updateState() { - if (msg_id.isEmpty()) return; - - auto &list = parser->can_msgs[msg_id]; - if (!list.empty()) { - binary_view->setData(list.back().dat); - messages_view->setMessages(list); - } -} - -SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { - QVBoxLayout *v_layout = new QVBoxLayout(this); - - QHBoxLayout *h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Name"))); - name = new QLineEdit(sig.name.c_str()); - h->addWidget(name); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Size"))); - size = new QSpinBox(); - size->setValue(sig.size); - h->addWidget(size); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Most significant bit"))); - msb = new QSpinBox(); - msb->setValue(sig.msb); - h->addWidget(msb); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Endianness"))); - endianness = new QComboBox(); - endianness->addItems({"Little", "Big"}); - endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1); - h->addWidget(endianness); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("sign"))); - sign = new QComboBox(); - sign->addItems({"Signed", "Unsigned"}); - sign->setCurrentIndex(sig.is_signed ? 0 : 1); - h->addWidget(sign); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Factor"))); - factor = new QSpinBox(); - factor->setValue(sig.factor); - h->addWidget(factor); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Offset"))); - offset = new QSpinBox(); - offset->setValue(sig.offset); - h->addWidget(offset); - v_layout->addLayout(h); - - // TODO: parse the following parameters in opendbc - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Unit"))); - unit = new QLineEdit(); - h->addWidget(unit); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Comment"))); - comment = new QLineEdit(); - h->addWidget(comment); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Minimum value"))); - min_val = new QSpinBox(); - h->addWidget(min_val); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Maximum value"))); - max_val = new QSpinBox(); - h->addWidget(max_val); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Value descriptions"))); - val_desc = new QLineEdit(); - h->addWidget(val_desc); - v_layout->addLayout(h); -} - -std::optional SignalForm::getSignal() { - Signal sig = {}; - sig.name = name->text().toStdString(); - sig.size = size->text().toInt(); - sig.offset = offset->text().toDouble(); - sig.factor = factor->text().toDouble(); - sig.msb = msb->text().toInt(); - sig.is_signed = sign->currentIndex() == 0; - sig.is_little_endian = endianness->currentIndex() == 0; - if (sig.is_little_endian) { - sig.lsb = sig.start_bit; - sig.msb = sig.start_bit + sig.size - 1; - } else { - sig.lsb = BIG_ENDIAN_START_BITS[bigEndianBitIndex(sig.start_bit) + sig.size - 1]; - sig.msb = sig.start_bit; - } - return (sig.name.empty() || sig.size <= 0) ? std::nullopt : std::optional(sig); -} - -void DetailWidget::setMsg(const QString &id) { - msg_id = id; - QString name = tr("untitled"); +void DetailWidget::setMsg(const CanData *c) { + can_data = c; + clearLayout(signal_edit_layout); + edit_btn->setVisible(true); - for (auto edit : signal_edit) { - delete edit; - } - signal_edit.clear(); - int i = 0; - auto msg = parser->getMsg(id); - if (msg) { - for (auto &s : msg->sigs) { - SignalEdit *edit = new SignalEdit(id, s, i++, this); - connect(edit, &SignalEdit::removed, [=]() { - QTimer::singleShot(0, [=]() { setMsg(id); }); - }); - signal_edit_layout->addWidget(edit); - signal_edit.push_back(edit); + if (auto msg = parser->getMsg(can_data->address)) { + name_label->setText(msg->name.c_str()); + add_sig_btn->setVisible(true); + for (int i = 0; i < msg->sigs.size(); ++i) { + signal_edit_layout->addWidget(new SignalEdit(can_data->id, msg->sigs[i], getColor(i))); } - name = msg->name.c_str(); + } else { + name_label->setText(tr("untitled")); + add_sig_btn->setVisible(false); } - name_label->setText(name); - binary_view->setMsg(msg_id); - edit_btn->setVisible(true); - add_sig_btn->setVisible(msg != nullptr); + binary_view->setMsg(can_data); + history_log->clear(); } -SignalEdit::SignalEdit(const QString &id, const Signal &sig, int idx, QWidget *parent) : id(id), name_(sig.name.c_str()), QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(0, 0, 0, 0); - - // title - QHBoxLayout *title_layout = new QHBoxLayout(); - QLabel *icon = new QLabel(">"); - icon->setStyleSheet("font-weight:bold"); - title_layout->addWidget(icon); - title = new ElidedLabel(this); - title->setText(sig.name.c_str()); - title->setStyleSheet(QString("font-weight:bold; color:%1").arg(SIGNAL_COLORS[idx % std::size(SIGNAL_COLORS)])); - connect(title, &ElidedLabel::clicked, [=]() { - edit_container->isVisible() ? edit_container->hide() : edit_container->show(); - icon->setText(edit_container->isVisible() ? "▼" : ">"); - }); - title_layout->addWidget(title); - title_layout->addStretch(); - QPushButton *show_plot = new QPushButton(tr("Show Plot")); - QObject::connect(show_plot, &QPushButton::clicked, [=]() { - if (show_plot->text() == tr("Show Plot")) { - emit parser->showPlot(id, name_); - show_plot->setText(tr("Hide Plot")); - } else { - emit parser->hidePlot(id, name_); - show_plot->setText(tr("Show Plot")); - } - }); - title_layout->addWidget(show_plot); - main_layout->addLayout(title_layout); +void DetailWidget::updateState() { + if (!can_data) return; - edit_container = new QWidget(this); - QVBoxLayout *v_layout = new QVBoxLayout(edit_container); - form = new SignalForm(sig, this); - v_layout->addWidget(form); - - QHBoxLayout *h = new QHBoxLayout(); - remove_btn = new QPushButton(tr("Remove Signal")); - QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); - h->addWidget(remove_btn); - h->addStretch(); - QPushButton *save_btn = new QPushButton(tr("Save")); - QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::save); - h->addWidget(save_btn); - v_layout->addLayout(h); - - edit_container->setVisible(false); - main_layout->addWidget(edit_container); + time_label->setText(QString("time: %1").arg(can_data->ts, 0, 'f', 3)); + binary_view->setData(can_data->dat); + history_log->updateState(); } -void SignalEdit::save() { - Msg *msg = const_cast(parser->getMsg(id)); - if (!msg) return; - - for (auto &sig : msg->sigs) { - if (name_ == sig.name.c_str()) { - if (auto s = form->getSignal()) { - sig = *s; - } - break; - } +void DetailWidget::editMsg() { + EditMessageDialog dlg(can_data->id, this); + if (dlg.exec()) { + setMsg(can_data); } } -void SignalEdit::remove() { - QMessageBox msgbox; - msgbox.setText(tr("Remove signal")); - msgbox.setInformativeText(tr("Are you sure you want to remove signal '%1'").arg(name_)); - msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - msgbox.setDefaultButton(QMessageBox::Cancel); - if (msgbox.exec()) { - parser->removeSignal(id, name_); - emit removed(); +void DetailWidget::addSignal() { + AddSignalDialog dlg(can_data->id, this); + if (dlg.exec()) { + setMsg(can_data); } } +// BinaryView + BinaryView::BinaryView(QWidget *parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); table = new QTableWidget(this); @@ -316,15 +130,16 @@ BinaryView::BinaryView(QWidget *parent) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } -void BinaryView::setMsg(const QString &id) { - auto msg = parser->getMsg(Parser::addressFromId(id)); - int row_count = msg ? msg->size : parser->can_msgs[id].back().dat.size(); +void BinaryView::setMsg(const CanData *can_data) { + const Msg *msg = parser->getMsg(can_data->address); + int row_count = msg ? msg->size : can_data->dat.size(); table->setRowCount(row_count); table->setColumnCount(9); for (int i = 0; i < table->rowCount(); ++i) { for (int j = 0; j < table->columnCount(); ++j) { auto item = new QTableWidgetItem(); + item->setFlags(item->flags() ^ Qt::ItemIsEditable); item->setTextAlignment(Qt::AlignCenter); if (j == 8) { QFont font; @@ -336,19 +151,17 @@ void BinaryView::setMsg(const QString &id) { } if (msg) { + // set background color for (int i = 0; i < msg->sigs.size(); ++i) { const auto &sig = msg->sigs[i]; int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit); for (int j = start; j <= start + sig.size - 1; ++j) { - table->item(j / 8, j % 8)->setBackground(QColor(SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)])); + table->item(j / 8, j % 8)->setBackground(QColor(getColor(i))); } } } setFixedHeight(table->rowHeight(0) * table->rowCount() + 25); - if (!parser->can_msgs.empty()) { - setData(parser->can_msgs[id].back().dat); - } } void BinaryView::setData(const QByteArray &binary) { @@ -357,6 +170,7 @@ void BinaryView::setData(const QByteArray &binary) { s += std::bitset<8>(binary[j]).to_string(); } + setUpdatesEnabled(false); char hex[3] = {'\0'}; for (int i = 0; i < binary.size(); ++i) { for (int j = 0; j < 8; ++j) { @@ -365,46 +179,58 @@ void BinaryView::setData(const QByteArray &binary) { sprintf(&hex[0], "%02X", (unsigned char)binary[i]); table->item(i, 8)->setText(hex); } + setUpdatesEnabled(true); } -MessagesView::MessagesView(QWidget *parent) : QWidget(parent) { +// HistoryLog + +HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); - QLabel *title = new QLabel("MESSAGE TIME BYTES"); + QLabel *title = new QLabel("TIME BYTES"); main_layout->addWidget(title); - message_layout = new QVBoxLayout(); + QVBoxLayout *message_layout = new QVBoxLayout(); + for (int i = 0; i < std::size(labels); ++i) { + labels[i] = new QLabel(); + labels[i]->setVisible(false); + message_layout->addWidget(labels[i]); + } main_layout->addLayout(message_layout); main_layout->addStretch(); } -void MessagesView::setMessages(const std::list &list) { - auto begin = list.begin(); - std::advance(begin, std::max(0, (int)(list.size() - 100))); - int j = 0; - for (auto it = begin; it != list.end(); ++it) { - QLabel *label; - if (j >= messages.size()) { - label = new QLabel(); - message_layout->addWidget(label); - messages.push_back(label); - } else { - label = messages[j]; - } - label->setText(it->hex_dat); - ++j; +void HistoryLog::updateState() { + int i = 0; + for (; i < parser->history_log.size(); ++i) { + const auto &c = parser->history_log[i]; + auto label = labels[i]; + label->setVisible(true); + label->setText(QString("%1 %2").arg(c.ts, 0, 'f', 3).arg(c.hex_dat)); + } + + for (; i < std::size(labels); ++i) { + labels[i]->setVisible(false); } } -EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : QDialog(parent) { +void HistoryLog::clear() { + setUpdatesEnabled(false); + for (auto l : labels) l->setVisible(false); + setUpdatesEnabled(true); +} + +// EditMessageDialog + +EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : id(id), QDialog(parent) { setWindowTitle(tr("Edit message")); QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->addWidget(new QLabel(tr("ID: (%1)").arg(id))); - auto msg = const_cast(parser->getMsg(Parser::addressFromId(id))); + auto msg = const_cast(parser->getMsg(id)); QHBoxLayout *h_layout = new QHBoxLayout(); h_layout->addWidget(new QLabel(tr("Name"))); h_layout->addStretch(); - QLineEdit *name_edit = new QLineEdit(this); + name_edit = new QLineEdit(this); name_edit->setText(msg ? msg->name.c_str() : "untitled"); h_layout->addWidget(name_edit); main_layout->addLayout(h_layout); @@ -412,47 +238,30 @@ EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : QDial h_layout = new QHBoxLayout(); h_layout->addWidget(new QLabel(tr("Size"))); h_layout->addStretch(); - QSpinBox *size_spin = new QSpinBox(this); - size_spin->setValue(msg ? msg->size : parser->can_msgs[id].back().dat.size()); + size_spin = new QSpinBox(this); + size_spin->setValue(msg ? msg->size : parser->can_msgs[id].dat.size()); h_layout->addWidget(size_spin); main_layout->addLayout(h_layout); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox); - connect(buttonBox, &QDialogButtonBox::accepted, [=]() { - if (size_spin->value() <= 0 || name_edit->text().isEmpty()) return; - - if (msg) { - msg->name = name_edit->text().toStdString(); - msg->size = size_spin->value(); - } else { - Msg m = {}; - m.address = Parser::addressFromId(id); - m.name = name_edit->text().toStdString(); - m.size = size_spin->value(); - parser->addNewMsg(m); - } - QDialog::accept(); - }); + connect(buttonBox, &QDialogButtonBox::accepted, this, &EditMessageDialog::save); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } -AddSignalDialog::AddSignalDialog(const QString &id, QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Add signal to %1").arg(parser->getMsg(id)->name.c_str())); - QVBoxLayout *main_layout = new QVBoxLayout(this); - Signal sig = {.name = "untitled"}; - auto form = new SignalForm(sig, this); - main_layout->addWidget(form); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - main_layout->addWidget(buttonBox); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - connect(buttonBox, &QDialogButtonBox::accepted, [=]() { - if (auto msg = const_cast(parser->getMsg(id))) { - if (auto signal = form->getSignal()) { - msg->sigs.push_back(*signal); - } - } - QDialog::accept(); - }); +void EditMessageDialog::save() { + if (size_spin->value() <= 0 || name_edit->text().isEmpty()) return; + + if (auto msg = const_cast(parser->getMsg(id))) { + msg->name = name_edit->text().toStdString(); + msg->size = size_spin->value(); + } else { + Msg m = {}; + m.address = Parser::addressFromId(id); + m.name = name_edit->text().toStdString(); + m.size = size_spin->value(); + parser->addNewMsg(m); + } + QDialog::accept(); } diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 70f2804f70..b2e7cbf3b7 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -1,102 +1,70 @@ #pragma once -#include + #include -#include #include -#include #include -#include #include #include #include -#include #include "opendbc/can/common.h" #include "opendbc/can/common_dbc.h" -#include "selfdrive/ui/qt/widgets/controls.h" #include "tools/cabana/parser.h" +#include "tools/cabana/signaledit.h" -class SignalForm : public QWidget { +class HistoryLog : public QWidget { Q_OBJECT - public: - SignalForm(const Signal &sig, QWidget *parent); - std::optional getSignal(); - QLineEdit *name, *unit, *comment, *val_desc; - QSpinBox *size, *msb, *lsb, *factor, *offset, *min_val, *max_val; - QComboBox *sign, *endianness; -}; - -class MessagesView : public QWidget { - Q_OBJECT +public: + HistoryLog(QWidget *parent); + void clear(); + void updateState(); - public: - MessagesView(QWidget *parent); - void setMessages(const std::list &data); - std::vector messages; - QVBoxLayout *message_layout; +private: + QLabel *labels[LOG_SIZE] = {}; }; class BinaryView : public QWidget { Q_OBJECT - public: +public: BinaryView(QWidget *parent); - void setMsg(const QString &id); + void setMsg(const CanData *can_data); void setData(const QByteArray &binary); QTableWidget *table; }; -class SignalEdit : public QWidget { +class EditMessageDialog : public QDialog { Q_OBJECT - public: - SignalEdit(const QString &id, const Signal &sig, int idx, QWidget *parent); +public: + EditMessageDialog(const QString &id, QWidget *parent); + +protected: void save(); -signals: - void removed(); - protected: - void remove(); + QLineEdit *name_edit; + QSpinBox *size_spin; QString id; - QString name_; - ElidedLabel *title; - SignalForm *form; - QWidget *edit_container; - QPushButton *remove_btn; }; class DetailWidget : public QWidget { Q_OBJECT - public: + +public: DetailWidget(QWidget *parent); - void setMsg(const QString &id); + void setMsg(const CanData *c); - public slots: +private: void updateState(); + void addSignal(); + void editMsg(); - protected: - QLabel *name_label = nullptr; + const CanData *can_data = nullptr; + QLabel *name_label, *time_label; QPushButton *edit_btn, *add_sig_btn; QVBoxLayout *signal_edit_layout; - Signal *sig = nullptr; - MessagesView *messages_view; - QString msg_id; + HistoryLog *history_log; BinaryView *binary_view; - std::vector signal_edit; -}; - -class EditMessageDialog : public QDialog { - Q_OBJECT - - public: - EditMessageDialog(const QString &id, QWidget *parent); -}; - -class AddSignalDialog : public QDialog { - Q_OBJECT - - public: - AddSignalDialog(const QString &id, QWidget *parent); }; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index d1b0d98f5f..8852987fbe 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -4,19 +4,15 @@ #include MainWindow::MainWindow() : QWidget() { - assert(parser != nullptr); - QVBoxLayout *main_layout = new QVBoxLayout(this); QHBoxLayout *h_layout = new QHBoxLayout(); main_layout->addLayout(h_layout); messages_widget = new MessagesWidget(this); - QObject::connect(messages_widget, &MessagesWidget::msgChanged, [=](const QString &id) { - detail_widget->setMsg(id); - }); h_layout->addWidget(messages_widget); detail_widget = new DetailWidget(this); + detail_widget->setFixedWidth(600); h_layout->addWidget(detail_widget); // right widget @@ -30,9 +26,12 @@ MainWindow::MainWindow() : QWidget() { QScrollArea *scroll = new QScrollArea(this); scroll->setWidget(charts_widget); scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); r_layout->addWidget(scroll); h_layout->addWidget(right_container); + + QObject::connect(messages_widget, &MessagesWidget::msgChanged, detail_widget, &DetailWidget::setMsg); } diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index f19c48297e..82ecceb02b 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -11,10 +11,10 @@ class MainWindow : public QWidget { Q_OBJECT - public: +public: MainWindow(); - protected: +protected: VideoWidget *video_widget; MessagesWidget *messages_widget; DetailWidget *detail_widget; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index d81d3e9ede..840ea25810 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,11 +1,9 @@ #include "tools/cabana/messageswidget.h" #include -#include #include #include #include -#include MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); @@ -47,8 +45,9 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { table_widget->setHorizontalHeaderLabels({tr("Name"), tr("ID"), tr("Count"), tr("Bytes")}); table_widget->horizontalHeader()->setStretchLastSection(true); QObject::connect(table_widget, &QTableWidget::itemSelectionChanged, [=]() { - auto id = table_widget->selectedItems()[0]->data(Qt::UserRole); - emit msgChanged(id.toString()); + const CanData *c = &(parser->can_msgs[table_widget->selectedItems()[1]->text()]); + parser->setCurrentMsg(c->id); + emit msgChanged(c); }); main_layout->addWidget(table_widget); @@ -60,6 +59,7 @@ void MessagesWidget::updateState() { auto item = table_widget->item(row, col); if (!item) { item = new QTableWidgetItem(); + item->setFlags(item->flags() ^ Qt::ItemIsEditable); table_widget->setItem(row, col, item); } return item; @@ -67,28 +67,27 @@ void MessagesWidget::updateState() { table_widget->setRowCount(parser->can_msgs.size()); int i = 0; - const QString filter_str = filter->text().toLower(); - for (const auto &[id, list] : parser->can_msgs) { - assert(!list.empty()); - - QString name; - if (auto msg = parser->getMsg(list.back().address)) { + QString name, untitled = tr("untitled"); + const QString filter_str = filter->text(); + for (const auto &[_, c] : parser->can_msgs) { + if (auto msg = parser->getMsg(c.address)) { name = msg->name.c_str(); } else { - name = tr("untitled"); + name = untitled; } - if (!filter_str.isEmpty() && !name.toLower().contains(filter_str)) { + if (!filter_str.isEmpty() && !name.contains(filter_str, Qt::CaseInsensitive)) { table_widget->hideRow(i++); continue; } - auto item = getTableItem(i, 0); - item->setText(name); - item->setData(Qt::UserRole, id); - getTableItem(i, 1)->setText(id); - getTableItem(i, 2)->setText(QString("%1").arg(parser->counters[id])); - getTableItem(i, 3)->setText(list.back().hex_dat); + getTableItem(i, 0)->setText(name); + getTableItem(i, 1)->setText(c.id); + getTableItem(i, 2)->setText(QString::number(parser->counters[c.id])); + getTableItem(i, 3)->setText(c.hex_dat); table_widget->showRow(i); i++; } + if (table_widget->currentRow() == -1) { + table_widget->selectRow(0); + } } diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index dca725199d..1dbb4a1af3 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -16,7 +16,7 @@ class MessagesWidget : public QWidget { void updateState(); signals: - void msgChanged(const QString &id); + void msgChanged(const CanData *id); protected: QLineEdit *filter; diff --git a/tools/cabana/parser.cc b/tools/cabana/parser.cc index 481f0dfbdf..3950d8bd57 100644 --- a/tools/cabana/parser.cc +++ b/tools/cabana/parser.cc @@ -4,7 +4,11 @@ #include "cereal/messaging/messaging.h" +Parser *parser = nullptr; + Parser::Parser(QObject *parent) : QObject(parent) { + parser = this; + qRegisterMetaType>(); QObject::connect(this, &Parser::received, this, &Parser::process, Qt::QueuedConnection); @@ -23,32 +27,48 @@ Parser::~Parser() { bool Parser::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { replay = new Replay(route, {"can", "roadEncodeIdx"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); - if (!replay->load()) { - return false; + QObject::connect(replay, &Replay::segmentsMerged, this, &Parser::segmentsMerged); + if (replay->load()) { + replay->start(); + return true; } - replay->start(); - return true; + return false; } void Parser::openDBC(const QString &name) { dbc_name = name; dbc = const_cast(dbc_lookup(name.toStdString())); + counters.clear(); msg_map.clear(); for (auto &msg : dbc->msgs) { msg_map[msg.address] = &msg; } } -void Parser::process(std::vector can) { - for (auto &data : can) { - ++counters[data.id]; - auto &list = can_msgs[data.id]; - while (list.size() > DATA_LIST_SIZE) { - list.pop_front(); +void Parser::process(std::vector msgs) { + static double prev_update_ts = 0; + for (const auto &can_data : msgs) { + can_msgs[can_data.id] = can_data; + current_sec = can_data.ts; + ++counters[can_data.id]; + + if (can_data.id == current_msg_id) { + while (history_log.size() >= LOG_SIZE) { + history_log.pop_back(); + } + history_log.push_front(can_data); } - list.push_back(data); } - emit updated(); + double current_ts = millis_since_boot(); + if ((current_ts - prev_update_ts) > 1000.0 / FPS) { + prev_update_ts = current_ts; + emit updated(); + } + + if (current_sec < begin_sec || current_sec > end_sec) { + // loop replay in selected range. + replay->seekTo(begin_sec, false); + } } void Parser::recvThread() { @@ -56,13 +76,16 @@ void Parser::recvThread() { std::unique_ptr context(Context::create()); std::unique_ptr subscriber(SubSocket::create(context.get(), "can")); subscriber->setTimeout(100); + + std::vector can; while (!exit) { std::unique_ptr msg(subscriber->receive()); if (!msg) continue; capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); cereal::Event::Reader event = cmsg.getRoot(); - std::vector can; + + can.clear(); can.reserve(event.getCan().size()); for (const auto &c : event.getCan()) { CanData &data = can.emplace_back(); @@ -72,7 +95,7 @@ void Parser::recvThread() { data.dat.append((char *)c.getDat().begin(), c.getDat().size()); data.hex_dat = data.dat.toHex(' ').toUpper(); data.id = QString("%1:%2").arg(data.source).arg(data.address, 1, 16); - data.ts = (event.getLogMonoTime() - replay->routeStartTime()) / (double)1e6; + data.ts = (event.getLogMonoTime() - replay->routeStartTime()) / (double)1e9; // seconds } emit received(can); } @@ -90,9 +113,72 @@ void Parser::removeSignal(const QString &id, const QString &sig_name) { auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); if (it != msg->sigs.end()) { msg->sigs.erase(it); + emit signalRemoved(id, sig_name); } } uint32_t Parser::addressFromId(const QString &id) { return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16); } + +const Signal *Parser::getSig(const QString &id, const QString &sig_name) { + if (auto msg = getMsg(id)) { + auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig_name == s.name.c_str(); }); + if (it != msg->sigs.end()) { + return &(*it); + } + } + return nullptr; +} + +void Parser::setRange(double min, double max) { + if (begin_sec != min || end_sec != max) { + begin_sec = min; + end_sec = max; + is_zoomed = begin_sec != event_begin_sec || end_sec != event_end_sec; + emit rangeChanged(min, max); + } +} + +void Parser::segmentsMerged() { + auto events = replay->events(); + if (!events || events->empty()) return; + + auto it = std::find_if(events->begin(), events->end(), [=](const Event *e) { return e->which == cereal::Event::Which::CAN; }); + event_begin_sec = it == events->end() ? 0 : ((*it)->mono_time - replay->routeStartTime()) / (double)1e9; + event_end_sec = double(events->back()->mono_time - replay->routeStartTime()) / 1e9; + if (!is_zoomed) { + begin_sec = event_begin_sec; + end_sec = event_end_sec; + } + emit eventsMerged(); +} + +void Parser::resetRange() { + setRange(event_begin_sec, event_end_sec); +} + +void Parser::setCurrentMsg(const QString &id) { + current_msg_id = id; + history_log.clear(); +} + +// helper functions + +static QVector BIG_ENDIAN_START_BITS = []() { + QVector ret; + for (int i = 0; i < 64; i++) { + for (int j = 7; j >= 0; j--) { + ret.push_back(j + i * 8); + } + } + return ret; +}(); + +int bigEndianStartBitsIndex(int start_bit) { + return BIG_ENDIAN_START_BITS[start_bit]; +} + +int bigEndianBitIndex(int index) { + return BIG_ENDIAN_START_BITS.indexOf(index); +} diff --git a/tools/cabana/parser.h b/tools/cabana/parser.h index fd5ded7c5e..2f8c441059 100644 --- a/tools/cabana/parser.h +++ b/tools/cabana/parser.h @@ -1,17 +1,20 @@ #pragma once +#include +#include + #include +#include #include #include -#include -#include #include "opendbc/can/common.h" #include "opendbc/can/common_dbc.h" #include "tools/replay/replay.h" -const int DATA_LIST_SIZE = 500; -// const int FPS = 20; +const int DATA_LIST_SIZE = 50; +const int FPS = 20; +const static int LOG_SIZE = 25; struct CanData { QString id; @@ -26,7 +29,7 @@ struct CanData { class Parser : public QObject { Q_OBJECT - public: +public: Parser(QObject *parent); ~Parser(); static uint32_t addressFromId(const QString &id); @@ -35,32 +38,57 @@ class Parser : public QObject { void saveDBC(const QString &name) {} void addNewMsg(const Msg &msg); void removeSignal(const QString &id, const QString &sig_name); - const Msg *getMsg(const QString &id) { - return getMsg(addressFromId(id)); - } - const Msg *getMsg(uint32_t address) { + const Signal *getSig(const QString &id, const QString &sig_name); + void setRange(double min, double max); + void resetRange(); + void setCurrentMsg(const QString &id); + inline std::pair range() const { return {begin_sec, end_sec}; } + inline double currentSec() const { return current_sec; } + inline bool isZoomed() const { return is_zoomed; } + inline const Msg *getMsg(const QString &id) { return getMsg(addressFromId(id)); } + inline const Msg *getMsg(uint32_t address) { auto it = msg_map.find(address); return it != msg_map.end() ? it->second : nullptr; } - signals: + +signals: void showPlot(const QString &id, const QString &name); void hidePlot(const QString &id, const QString &name); + void signalRemoved(const QString &id, const QString &sig_name); + void eventsMerged(); + void rangeChanged(double min, double max); void received(std::vector can); void updated(); - public: +public: + Replay *replay = nullptr; + QHash counters; + std::map can_msgs; + QList history_log; + +protected: void recvThread(); void process(std::vector can); + void segmentsMerged(); + + double current_sec = 0.; + std::atomic exit = false; QThread *thread; QString dbc_name; - std::atomic exit = false; - std::map> can_msgs; - std::map counters; - Replay *replay = nullptr; + double begin_sec = 0; + double end_sec = 0; + double event_begin_sec = 0; + double event_end_sec = 0; + bool is_zoomed = false; DBC *dbc = nullptr; std::map msg_map; + QString current_msg_id; }; Q_DECLARE_METATYPE(std::vector); +// TODO: Add helper function in dbc.h +int bigEndianStartBitsIndex(int start_bit); +int bigEndianBitIndex(int index); + extern Parser *parser; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc new file mode 100644 index 0000000000..080b7920de --- /dev/null +++ b/tools/cabana/signaledit.cc @@ -0,0 +1,203 @@ +#include "tools/cabana/signaledit.h" + +#include +#include +#include +#include +#include + +// SignalForm + +SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { + QVBoxLayout *v_layout = new QVBoxLayout(this); + + QHBoxLayout *h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Name"))); + name = new QLineEdit(sig.name.c_str()); + h->addWidget(name); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Size"))); + size = new QSpinBox(); + size->setValue(sig.size); + h->addWidget(size); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Most significant bit"))); + msb = new QSpinBox(); + msb->setValue(sig.msb); + h->addWidget(msb); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Endianness"))); + endianness = new QComboBox(); + endianness->addItems({"Little", "Big"}); + endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1); + h->addWidget(endianness); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("sign"))); + sign = new QComboBox(); + sign->addItems({"Signed", "Unsigned"}); + sign->setCurrentIndex(sig.is_signed ? 0 : 1); + h->addWidget(sign); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Factor"))); + factor = new QSpinBox(); + factor->setValue(sig.factor); + h->addWidget(factor); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Offset"))); + offset = new QSpinBox(); + offset->setValue(sig.offset); + h->addWidget(offset); + v_layout->addLayout(h); + + // TODO: parse the following parameters in opendbc + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Unit"))); + unit = new QLineEdit(); + h->addWidget(unit); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Comment"))); + comment = new QLineEdit(); + h->addWidget(comment); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Minimum value"))); + min_val = new QSpinBox(); + h->addWidget(min_val); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Maximum value"))); + max_val = new QSpinBox(); + h->addWidget(max_val); + v_layout->addLayout(h); + + h = new QHBoxLayout(); + h->addWidget(new QLabel(tr("Value descriptions"))); + val_desc = new QLineEdit(); + h->addWidget(val_desc); + v_layout->addLayout(h); +} + +std::optional SignalForm::getSignal() { + Signal sig = {}; + sig.name = name->text().toStdString(); + sig.size = size->text().toInt(); + sig.offset = offset->text().toDouble(); + sig.factor = factor->text().toDouble(); + sig.msb = msb->text().toInt(); + sig.is_signed = sign->currentIndex() == 0; + sig.is_little_endian = endianness->currentIndex() == 0; + if (sig.is_little_endian) { + sig.lsb = sig.start_bit; + sig.msb = sig.start_bit + sig.size - 1; + } else { + sig.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(sig.start_bit) + sig.size - 1); + sig.msb = sig.start_bit; + } + return (sig.name.empty() || sig.size <= 0) ? std::nullopt : std::optional(sig); +} + +// SignalEdit + +SignalEdit::SignalEdit(const QString &id, const Signal &sig, const QString &color, QWidget *parent) : id(id), name_(sig.name.c_str()), QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + + // title + QHBoxLayout *title_layout = new QHBoxLayout(); + QLabel *icon = new QLabel(">"); + icon->setStyleSheet("font-weight:bold"); + title_layout->addWidget(icon); + title = new ElidedLabel(this); + title->setText(sig.name.c_str()); + title->setStyleSheet(QString("font-weight:bold; color:%1").arg(color)); + connect(title, &ElidedLabel::clicked, [=]() { + edit_container->isVisible() ? edit_container->hide() : edit_container->show(); + icon->setText(edit_container->isVisible() ? "▼" : ">"); + }); + title_layout->addWidget(title); + title_layout->addStretch(); + plot_btn = new QPushButton("📈"); + plot_btn->setStyleSheet("font-size:16px"); + plot_btn->setToolTip(tr("Show Plot")); + plot_btn->setContentsMargins(5, 5, 5, 5); + plot_btn->setFixedSize(30, 30); + QObject::connect(plot_btn, &QPushButton::clicked, [=]() { emit parser->showPlot(id, name_); }); + title_layout->addWidget(plot_btn); + main_layout->addLayout(title_layout); + + edit_container = new QWidget(this); + QVBoxLayout *v_layout = new QVBoxLayout(edit_container); + form = new SignalForm(sig, this); + v_layout->addWidget(form); + + QHBoxLayout *h = new QHBoxLayout(); + remove_btn = new QPushButton(tr("Remove Signal")); + QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); + h->addWidget(remove_btn); + h->addStretch(); + QPushButton *save_btn = new QPushButton(tr("Save")); + QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::save); + h->addWidget(save_btn); + v_layout->addLayout(h); + + edit_container->setVisible(false); + main_layout->addWidget(edit_container); +} + +void SignalEdit::save() { + if (auto sig = const_cast(parser->getSig(id, name_))) { + if (auto s = form->getSignal()) { + *sig = *s; + // TODO: reset the chart for sig + } + } +} + +void SignalEdit::remove() { + QMessageBox msgbox; + msgbox.setText(tr("Remove signal")); + msgbox.setInformativeText(tr("Are you sure you want to remove signal '%1'").arg(name_)); + msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgbox.setDefaultButton(QMessageBox::Cancel); + if (msgbox.exec()) { + parser->removeSignal(id, name_); + deleteLater(); + } +} + +// AddSignalDialog + +AddSignalDialog::AddSignalDialog(const QString &id, QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Add signal to %1").arg(parser->getMsg(id)->name.c_str())); + QVBoxLayout *main_layout = new QVBoxLayout(this); + Signal sig = {.name = "untitled"}; + auto form = new SignalForm(sig, this); + main_layout->addWidget(form); + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + main_layout->addWidget(buttonBox); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(buttonBox, &QDialogButtonBox::accepted, [=]() { + if (auto msg = const_cast(parser->getMsg(id))) { + if (auto signal = form->getSignal()) { + msg->sigs.push_back(*signal); + } + } + QDialog::accept(); + }); +} diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h new file mode 100644 index 0000000000..b8140cc93b --- /dev/null +++ b/tools/cabana/signaledit.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include "selfdrive/ui/qt/widgets/controls.h" +#include "tools/cabana/parser.h" + +class SignalForm : public QWidget { + Q_OBJECT + +public: + SignalForm(const Signal &sig, QWidget *parent); + std::optional getSignal(); + + QLineEdit *name, *unit, *comment, *val_desc; + QSpinBox *size, *msb, *lsb, *factor, *offset, *min_val, *max_val; + QComboBox *sign, *endianness; +}; + +class SignalEdit : public QWidget { + Q_OBJECT + +public: + SignalEdit(const QString &id, const Signal &sig, const QString &color, QWidget *parent = nullptr); + void save(); + +protected: + void remove(); + + QString id; + QString name_; + QPushButton *plot_btn; + ElidedLabel *title; + SignalForm *form; + QWidget *edit_container; + QPushButton *remove_btn; +}; + +class AddSignalDialog : public QDialog { + Q_OBJECT + +public: + AddSignalDialog(const QString &id, QWidget *parent); +}; diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index caa109eab0..dbf988d8f2 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -3,9 +3,7 @@ #include #include #include -#include #include -#include #include #include "tools/cabana/parser.h" @@ -17,17 +15,19 @@ inline QString formatTime(int seconds) { VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); - cam_widget = new CameraViewWidget("camerad", VISION_STREAM_ROAD, true, this); - cam_widget->setFixedSize(640, 480); + cam_widget = new CameraViewWidget("camerad", VISION_STREAM_ROAD, false, this); + cam_widget->setFixedSize(parent->width(), parent->width() / 1.596); main_layout->addWidget(cam_widget); // slider controls QHBoxLayout *slider_layout = new QHBoxLayout(); - QLabel *time_label = new QLabel("00:00"); + time_label = new QLabel("00:00"); slider_layout->addWidget(time_label); slider = new QSlider(Qt::Horizontal, this); - // slider->setFixedWidth(640); + QObject::connect(slider, &QSlider::sliderMoved, [=]() { + time_label->setText(formatTime(slider->value())); + }); slider->setSingleStep(1); slider->setMaximum(parser->replay->totalSeconds()); QObject::connect(slider, &QSlider::sliderReleased, [=]() { @@ -36,7 +36,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { }); slider_layout->addWidget(slider); - QLabel *total_time_label = new QLabel(formatTime(parser->replay->totalSeconds())); + total_time_label = new QLabel(formatTime(parser->replay->totalSeconds())); slider_layout->addWidget(total_time_label); main_layout->addLayout(slider_layout); @@ -57,9 +57,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { for (float speed : {0.1, 0.5, 1., 2.}) { QPushButton *btn = new QPushButton(QString("%1x").arg(speed), this); btn->setCheckable(true); - QObject::connect(btn, &QPushButton::clicked, [=]() { - parser->replay->setSpeed(speed); - }); + QObject::connect(btn, &QPushButton::clicked, [=]() { parser->replay->setSpeed(speed); }); control_layout->addWidget(btn); group->addButton(btn); if (speed == 1.0) btn->setChecked(true); @@ -67,14 +65,25 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { main_layout->addLayout(control_layout); - QTimer *timer = new QTimer(this); - timer->setInterval(1000); - timer->callOnTimeout([=]() { - int current_seconds = parser->replay->currentSeconds(); - time_label->setText(formatTime(current_seconds)); - if (!slider->isSliderDown()) { - slider->setValue(current_seconds); - } - }); - timer->start(); + QObject::connect(parser, &Parser::rangeChanged, this, &VideoWidget::rangeChanged); + QObject::connect(parser, &Parser::updated, this, &VideoWidget::updateState); +} + +void VideoWidget::rangeChanged(double min, double max) { + if (!parser->isZoomed()) { + min = 0; + max = parser->replay->totalSeconds(); + } + time_label->setText(formatTime(min)); + total_time_label->setText(formatTime(max)); + slider->setMaximum(max); + slider->setValue(parser->currentSec()); +} + +void VideoWidget::updateState() { + if (!slider->isSliderDown()) { + int current_sec = parser->currentSec(); + time_label->setText(formatTime(current_sec)); + slider->setValue(current_sec); + } } diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index f0b9e458bd..813516e78f 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -12,6 +13,10 @@ public: VideoWidget(QWidget *parnet = nullptr); protected: + void rangeChanged(double min, double max); + void updateState(); + CameraViewWidget *cam_widget; + QLabel *time_label, *total_time_label; QSlider *slider; }; diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index 9b7a07a83f..e3d5071412 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -46,7 +46,9 @@ LogReader::~LogReader() { #endif } -bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { +bool LogReader::load(const std::string &url, std::atomic *abort, + const std::set &allow, + bool local_cache, int chunk_size, int retries) { raw_ = FileReader(local_cache, chunk_size, retries).read(url, abort); if (raw_.empty()) return false; @@ -54,18 +56,26 @@ bool LogReader::load(const std::string &url, std::atomic *abort, bool loca raw_ = decompressBZ2(raw_, abort); if (raw_.empty()) return false; } - return parse(abort); + return parse(allow, abort); } bool LogReader::load(const std::byte *data, size_t size, std::atomic *abort) { raw_.assign((const char *)data, size); - return parse(abort); + return parse({}, abort); } -bool LogReader::parse(std::atomic *abort) { +bool LogReader::parse(const std::set &allow, std::atomic *abort) { try { kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); while (words.size() > 0 && !(abort && *abort)) { + if (!allow.empty()) { + capnp::FlatArrayMessageReader reader(words); + auto which = reader.getRoot().which(); + if (allow.find(which) == allow.end()) { + words = kj::arrayPtr(reader.getEnd(), words.end()); + continue; + } + } #ifdef HAS_MEMORY_RESOURCE Event *evt = new (mbr_) Event(words); diff --git a/tools/replay/logreader.h b/tools/replay/logreader.h index bd666d0a74..010839af22 100644 --- a/tools/replay/logreader.h +++ b/tools/replay/logreader.h @@ -5,6 +5,8 @@ #include #endif +#include + #include "cereal/gen/cpp/log.capnp.h" #include "system/camerad/cameras/camera_common.h" #include "tools/replay/filereader.h" @@ -50,12 +52,13 @@ class LogReader { public: LogReader(size_t memory_pool_block_size = DEFAULT_EVENT_MEMORY_POOL_BLOCK_SIZE); ~LogReader(); - bool load(const std::string &url, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); + bool load(const std::string &url, std::atomic *abort = nullptr, const std::set &allow = {}, + bool local_cache = false, int chunk_size = -1, int retries = 0); bool load(const std::byte *data, size_t size, std::atomic *abort = nullptr); std::vector events; private: - bool parse(std::atomic *abort); + bool parse(const std::set &allow, std::atomic *abort); std::string raw_; #ifdef HAS_MEMORY_RESOURCE std::pmr::monotonic_buffer_resource *mbr_ = nullptr; diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 1684dfaca9..b64e87a03e 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -19,6 +19,9 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s if ((allow.empty() || allow.contains(it.name)) && !block.contains(it.name)) { uint16_t which = event_struct.getFieldByName(it.name).getProto().getDiscriminantValue(); sockets_[which] = it.name; + if (!allow.empty() || !block.empty()) { + allow_list.insert((cereal::Event::Which)which); + } s.push_back(it.name); } } @@ -91,17 +94,17 @@ void Replay::updateEvents(const std::function &lambda) { stream_cv_.notify_one(); } -void Replay::seekTo(int seconds, bool relative) { +void Replay::seekTo(double seconds, bool relative) { seconds = relative ? seconds + currentSeconds() : seconds; updateEvents([&]() { - seconds = std::max(0, seconds); - int seg = seconds / 60; + seconds = std::max(double(0.0), seconds); + int seg = (int)seconds / 60; if (segments_.find(seg) == segments_.end()) { rWarning("can't seek to %d s segment %d is invalid", seconds, seg); return true; } - rInfo("seeking to %d s, segment %d", seconds, seg); + rInfo("seeking to %d s, segment %d", (int)seconds, seg); current_segment_ = seg; cur_mono_time_ = route_start_ts_ + seconds * 1e9; return isSegmentMerged(seg); @@ -122,7 +125,9 @@ void Replay::buildTimeline() { for (int i = 0; i < segments_.size() && !exit_; ++i) { LogReader log; - if (!log.load(route_->at(i).qlog.toStdString(), &exit_, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; + if (!log.load(route_->at(i).qlog.toStdString(), &exit_, + {cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG}, + !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; for (const Event *e : log.events) { if (e->which == cereal::Event::Which::CONTROLS_STATE) { @@ -215,7 +220,7 @@ void Replay::queueSegment() { if ((seg && !seg->isLoaded()) || !seg) { if (!seg) { rDebug("loading segment %d...", n); - seg = std::make_unique(n, route_->at(n), flags_); + seg = std::make_unique(n, route_->at(n), flags_, allow_list); QObject::connect(seg.get(), &Segment::loadFinished, this, &Replay::segmentLoadFinished); } break; @@ -270,6 +275,9 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap:: segments_merged_ = segments_need_merge; return true; }); + if (stream_thread_) { + emit segmentsMerged(); + } } } @@ -306,6 +314,7 @@ void Replay::startStream(const Segment *cur_segment) { camera_server_ = std::make_unique(camera_size); } + emit segmentsMerged(); // start stream thread stream_thread_ = new QThread(); QObject::connect(stream_thread_, &QThread::started, [=]() { stream(); }); diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 3327362f97..aa0bbc33e7 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -45,18 +45,19 @@ public: void stop(); void pause(bool pause); void seekToFlag(FindFlag flag); - void seekTo(int seconds, bool relative); + void seekTo(double seconds, bool relative); inline bool isPaused() const { return paused_; } inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } inline const Route* route() const { return route_.get(); } - inline int currentSeconds() const { return (cur_mono_time_ - route_start_ts_) / 1e9; } + inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; } 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 int totalSeconds() const { return segments_.size() * 60; } 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::string &carFingerprint() const { return car_fingerprint_; } inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); @@ -65,6 +66,7 @@ public: signals: void streamStarted(); + void segmentsMerged(); protected slots: void segmentLoadFinished(bool success); @@ -98,7 +100,7 @@ protected: bool paused_ = false; bool events_updated_ = false; uint64_t route_start_ts_ = 0; - uint64_t cur_mono_time_ = 0; + std::atomic cur_mono_time_ = 0; std::unique_ptr> events_; std::unique_ptr> new_events_; std::vector segments_merged_; @@ -114,6 +116,7 @@ protected: std::mutex timeline_lock; QFuture timeline_future; std::vector> timeline; + std::set allow_list; std::string car_fingerprint_; float speed_ = 1.0; }; diff --git a/tools/replay/route.cc b/tools/replay/route.cc index c91b27ae81..f0d6ec5a12 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -99,7 +99,9 @@ void Route::addFileToSegment(int n, const QString &file) { // class Segment -Segment::Segment(int n, const SegmentFile &files, uint32_t flags) : seg_num(n), flags(flags) { +Segment::Segment(int n, const SegmentFile &files, uint32_t flags, + const std::set &allow) + : seg_num(n), flags(flags), allow(allow) { // [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog const std::array file_list = { (flags & REPLAY_FLAG_QCAMERA) || files.road_cam.isEmpty() ? files.qcamera : files.road_cam, @@ -130,7 +132,7 @@ void Segment::loadFile(int id, const std::string file) { success = frames[id]->load(file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3); } else { log = std::make_unique(); - success = log->load(file, &abort_, local_cache, 0, 3); + success = log->load(file, &abort_, allow, local_cache, 0, 3); } if (!success) { diff --git a/tools/replay/route.h b/tools/replay/route.h index 6ca9c3b883..6b78ebad87 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -47,7 +47,7 @@ class Segment : public QObject { Q_OBJECT public: - Segment(int n, const SegmentFile &files, uint32_t flags); + Segment(int n, const SegmentFile &files, uint32_t flags, const std::set &allow = {}); ~Segment(); inline bool isLoaded() const { return !loading_ && !abort_; } @@ -65,4 +65,5 @@ protected: std::atomic loading_ = 0; QFutureSynchronizer synchronizer_; uint32_t flags; + std::set allow; }; From cb0b7375b728d1b6e92db68c9ba55f0f54c09a3f Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Wed, 5 Oct 2022 21:43:38 -0700 Subject: [PATCH 083/178] Rocket Launcher Model (#25963) * 1456d261-d232-4654-8885-4d9fde883894/440 6b7d7cec-ead8-40f3-86cc-86d52c9b03fe/300 * compute only 9 tokens: 1456d261-d232-4654-8885-4d9fde883894/440 6b7d7cec-ead8-40f3-86cc-86d52c9b03fe/300 * tinygrad: cleanup gather * 1456d261-d232-4654-8885-4d9fde883894/440 6b7d7cec-ead8-40f3-86cc-86d52c9b03fe/700 * empty commit for tests * bump tinygrad * dont use tinygrad matmul for now * bump tinygrad * 1456d261-d232-4654-8885-4d9fde883894/440 e63ab895-2222-4abd-a9a5-af86bb70e260/700 * float16 1456d261-d232-4654-8885-4d9fde883894/440 e63ab895-2222-4abd-a9a5-af86bb70e260/700 * increase steer rate cost * Revert "increase steer rate cost" This reverts commit 74ce9ab9be7ef17ecfec931f96851b12f37f2336. * fork tinygrad * empty commit for tests * basics * Kinda works * new lat * new tuning * Move LATMPCN so scons compiles * Update long weights * Add tinygrad optim * Update model ref * update weights * Update ref * Try * Error message for field ignore * update model regf * ref commit * Fix onnx test Co-authored-by: Yassine Yousfi --- .gitmodules | 2 +- release/files_common | 1 + selfdrive/controls/lib/drive_helpers.py | 6 -- .../controls/lib/lateral_mpc_lib/lat_mpc.py | 63 ++++++++++--------- selfdrive/controls/lib/lateral_planner.py | 18 +++--- .../lib/longitudinal_mpc_lib/long_mpc.py | 2 +- .../controls/lib/longitudinal_planner.py | 6 +- selfdrive/controls/tests/test_lateral_mpc.py | 5 +- selfdrive/modeld/SConscript | 4 +- selfdrive/modeld/models/driving.cc | 16 +++-- selfdrive/modeld/models/driving.h | 15 ++++- selfdrive/modeld/models/supercombo.onnx | 4 +- selfdrive/modeld/runners/onnx_runner.py | 13 ++-- selfdrive/modeld/thneed/thneed_common.cc | 2 +- selfdrive/test/process_replay/compare_logs.py | 2 +- selfdrive/test/process_replay/model_replay.py | 4 +- .../process_replay/model_replay_ref_commit | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- tinygrad_repo | 2 +- 19 files changed, 93 insertions(+), 76 deletions(-) diff --git a/.gitmodules b/.gitmodules index 26f93ef164..544c95c943 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,4 +18,4 @@ url = ../../commaai/body.git [submodule "tinygrad"] path = tinygrad_repo - url = https://github.com/geohot/tinygrad.git + url = ../../commaai/tinygrad.git diff --git a/release/files_common b/release/files_common index 0c341eb3f6..07ffaf8501 100644 --- a/release/files_common +++ b/release/files_common @@ -574,3 +574,4 @@ tinygrad_repo/tinygrad/ops.py tinygrad_repo/tinygrad/shapetracker.py tinygrad_repo/tinygrad/tensor.py tinygrad_repo/tinygrad/nn/__init__.py +tinygrad_repo/tinygrad/nn/optim.py diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index e8f9585a6f..f81cd0c40c 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -33,12 +33,6 @@ CRUISE_INTERVAL_SIGN = { } -class MPC_COST_LAT: - PATH = 1.0 - HEADING = 1.0 - STEER_RATE = 1.0 - - def apply_deadzone(error, deadzone): if error > deadzone: error -= deadzone diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index c0e7358160..07efad73c9 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -5,7 +5,6 @@ import numpy as np from casadi import SX, vertcat, sin, cos from common.realtime import sec_since_boot -from selfdrive.controls.lib.drive_helpers import LAT_MPC_N as N from selfdrive.modeld.constants import T_IDXS if __name__ == '__main__': # generating code @@ -18,6 +17,9 @@ EXPORT_DIR = os.path.join(LAT_MPC_DIR, "c_generated_code") JSON_FILE = os.path.join(LAT_MPC_DIR, "acados_ocp_lat.json") X_DIM = 4 P_DIM = 2 +N = 16 +COST_E_DIM = 3 +COST_DIM = COST_E_DIM + 1 MODEL_NAME = 'lat' ACADOS_SOLVER_TYPE = 'SQP_RTI' @@ -29,8 +31,8 @@ def gen_lat_model(): x_ego = SX.sym('x_ego') y_ego = SX.sym('y_ego') psi_ego = SX.sym('psi_ego') - curv_ego = SX.sym('curv_ego') - model.x = vertcat(x_ego, y_ego, psi_ego, curv_ego) + psi_rate_ego = SX.sym('psi_rate_ego') + model.x = vertcat(x_ego, y_ego, psi_ego, psi_rate_ego) # parameters v_ego = SX.sym('v_ego') @@ -38,22 +40,22 @@ def gen_lat_model(): model.p = vertcat(v_ego, rotation_radius) # controls - curv_rate = SX.sym('curv_rate') - model.u = vertcat(curv_rate) + psi_accel_ego = SX.sym('psi_accel_ego') + model.u = vertcat(psi_accel_ego) # xdot x_ego_dot = SX.sym('x_ego_dot') y_ego_dot = SX.sym('y_ego_dot') psi_ego_dot = SX.sym('psi_ego_dot') - curv_ego_dot = SX.sym('curv_ego_dot') + psi_rate_ego_dot = SX.sym('psi_rate_ego_dot') - model.xdot = vertcat(x_ego_dot, y_ego_dot, psi_ego_dot, curv_ego_dot) + model.xdot = vertcat(x_ego_dot, y_ego_dot, psi_ego_dot, psi_rate_ego_dot) # dynamics model - f_expl = vertcat(v_ego * cos(psi_ego) - rotation_radius * sin(psi_ego) * (v_ego * curv_ego), - v_ego * sin(psi_ego) + rotation_radius * cos(psi_ego) * (v_ego * curv_ego), - v_ego * curv_ego, - curv_rate) + f_expl = vertcat(v_ego * cos(psi_ego) - rotation_radius * sin(psi_ego) * psi_rate_ego, + v_ego * sin(psi_ego) + rotation_radius * cos(psi_ego) * psi_rate_ego, + psi_rate_ego, + psi_accel_ego) model.f_impl_expr = model.xdot - f_expl model.f_expl_expr = f_expl return model @@ -72,26 +74,28 @@ def gen_lat_ocp(): ocp.cost.cost_type = 'NONLINEAR_LS' ocp.cost.cost_type_e = 'NONLINEAR_LS' - Q = np.diag([0.0, 0.0]) - QR = np.diag([0.0, 0.0, 0.0]) + Q = np.diag(np.zeros(COST_E_DIM)) + QR = np.diag(np.zeros(COST_DIM)) ocp.cost.W = QR ocp.cost.W_e = Q - y_ego, psi_ego = ocp.model.x[1], ocp.model.x[2] - curv_rate = ocp.model.u[0] + y_ego, psi_ego, psi_rate_ego = ocp.model.x[1], ocp.model.x[2], ocp.model.x[3] + psi_rate_ego_dot = ocp.model.u[0] v_ego = ocp.model.p[0] ocp.parameter_values = np.zeros((P_DIM, )) - ocp.cost.yref = np.zeros((3, )) - ocp.cost.yref_e = np.zeros((2, )) + ocp.cost.yref = np.zeros((COST_DIM, )) + ocp.cost.yref_e = np.zeros((COST_E_DIM, )) # TODO hacky weights to keep behavior the same ocp.model.cost_y_expr = vertcat(y_ego, - ((v_ego +5.0) * psi_ego), - ((v_ego + 5.0) * 4.0 * curv_rate)) + ((v_ego + 5.0) * psi_ego), + ((v_ego + 5.0) * psi_rate_ego), + ((v_ego + 5.0) * psi_rate_ego_dot)) ocp.model.cost_y_expr_e = vertcat(y_ego, - ((v_ego +5.0) * psi_ego)) + ((v_ego + 5.0) * psi_ego), + ((v_ego + 5.0) * psi_rate_ego)) # set constraints ocp.constraints.constr_type = 'BGH' @@ -124,10 +128,10 @@ class LateralMpc(): def reset(self, x0=np.zeros(X_DIM)): self.x_sol = np.zeros((N+1, X_DIM)) self.u_sol = np.zeros((N, 1)) - self.yref = np.zeros((N+1, 3)) + self.yref = np.zeros((N+1, COST_DIM)) for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) - self.solver.cost_set(N, "yref", self.yref[N][:2]) + self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) # Somehow needed for stable init for i in range(N+1): @@ -140,14 +144,13 @@ class LateralMpc(): self.solve_time = 0.0 self.cost = 0 - def set_weights(self, path_weight, heading_weight, steer_rate_weight): - W = np.asfortranarray(np.diag([path_weight, heading_weight, steer_rate_weight])) + def set_weights(self, path_weight, heading_weight, yaw_rate_weight, yaw_accel_cost): + W = np.asfortranarray(np.diag([path_weight, heading_weight, yaw_rate_weight, yaw_accel_cost])) for i in range(N): self.solver.cost_set(i, 'W', W) - #TODO hacky weights to keep behavior the same - self.solver.cost_set(N, 'W', (3/20.)*W[:2,:2]) + self.solver.cost_set(N, 'W', W[:COST_E_DIM,:COST_E_DIM]) - def run(self, x0, p, y_pts, heading_pts, curv_rate_pts): + def run(self, x0, p, y_pts, heading_pts, yaw_rate_pts): x0_cp = np.copy(x0) p_cp = np.copy(p) self.solver.constraints_set(0, "lbx", x0_cp) @@ -155,13 +158,13 @@ class LateralMpc(): self.yref[:,0] = y_pts v_ego = p_cp[0] # rotation_radius = p_cp[1] - self.yref[:,1] = heading_pts*(v_ego+5.0) - self.yref[:,2] = curv_rate_pts * (v_ego+5.0) * 4.0 + self.yref[:,1] = heading_pts * (v_ego+5.0) + self.yref[:,2] = yaw_rate_pts * (v_ego+5.0) for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) self.solver.set(i, "p", p_cp) self.solver.set(N, "p", p_cp) - self.solver.cost_set(N, "yref", self.yref[N][:2]) + self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) t = sec_since_boot() self.solution_status = self.solver.solve() diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 2ad5f784d7..48c98e7350 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -3,7 +3,8 @@ from common.realtime import sec_since_boot, DT_MDL from common.numpy_fast import interp from system.swaglog import cloudlog from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc -from selfdrive.controls.lib.drive_helpers import CONTROL_N, MPC_COST_LAT, LAT_MPC_N +from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N +from selfdrive.controls.lib.drive_helpers import CONTROL_N from selfdrive.controls.lib.desire_helper import DesireHelper import cereal.messaging as messaging from cereal import log @@ -23,7 +24,7 @@ class LateralPlanner: self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) self.plan_yaw = np.zeros((TRAJECTORY_SIZE,)) - self.plan_curv_rate = np.zeros((TRAJECTORY_SIZE,)) + self.plan_yaw_rate = np.zeros((TRAJECTORY_SIZE,)) self.t_idxs = np.arange(TRAJECTORY_SIZE) self.y_pts = np.zeros(TRAJECTORY_SIZE) @@ -44,6 +45,7 @@ class LateralPlanner: self.path_xyz = np.column_stack([md.position.x, md.position.y, md.position.z]) self.t_idxs = np.array(md.position.t) self.plan_yaw = np.array(md.orientation.z) + self.plan_yaw_rate = np.array(md.orientationRate.z) # Lane change logic desire_state = md.meta.desireState @@ -55,24 +57,24 @@ class LateralPlanner: d_path_xyz = self.path_xyz # Heading cost is useful at low speed, otherwise end of plan can be off-heading - heading_cost = interp(v_ego, [5.0, 10.0], [MPC_COST_LAT.HEADING, 0.15]) - self.lat_mpc.set_weights(MPC_COST_LAT.PATH, heading_cost, MPC_COST_LAT.STEER_RATE) + heading_cost = interp(v_ego, [5.0, 10.0], [1.0, 0.15]) + self.lat_mpc.set_weights(1.0, heading_cost, 0.0, .075) 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) - curv_rate_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_curv_rate) + yaw_rate_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw_rate) self.y_pts = y_pts assert len(y_pts) == LAT_MPC_N + 1 assert len(heading_pts) == LAT_MPC_N + 1 - assert len(curv_rate_pts) == LAT_MPC_N + 1 + assert len(yaw_rate_pts) == LAT_MPC_N + 1 lateral_factor = max(0, self.factor1 - (self.factor2 * v_ego**2)) p = np.array([v_ego, lateral_factor]) self.lat_mpc.run(self.x0, p, y_pts, heading_pts, - curv_rate_pts) + yaw_rate_pts) # init state for next # mpc.u_sol is the desired curvature rate given x0 curv state. # with x0[3] = measured_curvature, this would be the actual desired rate. @@ -103,7 +105,7 @@ class LateralPlanner: lateralPlan.modelMonoTime = sm.logMonoTime['modelV2'] lateralPlan.dPathPoints = self.y_pts.tolist() lateralPlan.psis = self.lat_mpc.x_sol[0:CONTROL_N, 2].tolist() - lateralPlan.curvatures = self.lat_mpc.x_sol[0:CONTROL_N, 3].tolist() + lateralPlan.curvatures = (self.lat_mpc.x_sol[0:CONTROL_N, 3]/sm['carState'].vEgo).tolist() lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] lateralPlan.mpcSolutionValid = bool(plan_solution_valid) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 695222ed4d..2aab4b71af 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -253,7 +253,7 @@ class LongitudinalMpc: cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, J_EGO_COST] constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] elif self.mode == 'blended': - cost_weights = [0., 0.2, 0.25, 1.0, 0.0, 1.0] + cost_weights = [0., 0.1, 0.2, 5.0, 0.0, 1.0] constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, 50.0] else: raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner cost set') diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index a0363536ca..018136e6f2 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -58,7 +58,6 @@ class LongitudinalPlanner: self.a_desired = init_a self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL) - self.t_uniform = np.arange(0.0, T_IDXS_MPC[-1] + 0.5, 0.5) self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) @@ -76,10 +75,7 @@ class LongitudinalPlanner: x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x) v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x) a = np.interp(T_IDXS_MPC, T_IDXS, model_msg.acceleration.x) - # Uniform interp so gradient is less noisy - a_sparse = np.interp(self.t_uniform, T_IDXS, model_msg.acceleration.x) - j_sparse = np.gradient(a_sparse, self.t_uniform) - j = np.interp(T_IDXS_MPC, self.t_uniform, j_sparse) + j = np.zeros(len(T_IDXS_MPC)) else: x = np.zeros(len(T_IDXS_MPC)) v = np.zeros(len(T_IDXS_MPC)) diff --git a/selfdrive/controls/tests/test_lateral_mpc.py b/selfdrive/controls/tests/test_lateral_mpc.py index 4864dbdc06..9b986c053d 100644 --- a/selfdrive/controls/tests/test_lateral_mpc.py +++ b/selfdrive/controls/tests/test_lateral_mpc.py @@ -1,7 +1,8 @@ import unittest import numpy as np from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc -from selfdrive.controls.lib.drive_helpers import LAT_MPC_N, CAR_ROTATION_RADIUS +from selfdrive.controls.lib.drive_helpers import CAR_ROTATION_RADIUS +from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvature_init=0., @@ -9,7 +10,7 @@ def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvatur if lat_mpc is None: lat_mpc = LateralMpc() - lat_mpc.set_weights(1., 1., 1.) + lat_mpc.set_weights(1., 1., 0.0, 1.) y_pts = poly_shift * np.ones(LAT_MPC_N + 1) heading_pts = np.zeros(LAT_MPC_N + 1) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 246f8c2941..5c02e2b15f 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -71,9 +71,9 @@ if use_thneed and arch == "larch64" or GetOption('pc_thneed'): fn = File("models/supercombo").abspath if GetOption('pc_thneed'): - cmd = f"cd {Dir('#').abspath}/tinygrad_repo && NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed" + cmd = f"cd {Dir('#').abspath}/tinygrad_repo && GPU=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed" else: - cmd = f"cd {Dir('#').abspath}/tinygrad_repo && FLOAT16=1 PYOPENCL_NO_CACHE=1 MATMUL=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed" + cmd = f"cd {Dir('#').abspath}/tinygrad_repo && FLOAT16=1 MATMUL=1 PYOPENCL_NO_CACHE=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed" # is there a better way then listing all of tinygrad? lenv.Command(fn + ".thneed", [fn + ".onnx", diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index 8d02eb6b2f..3316e6d114 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -41,11 +41,11 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context) { &s->output[0], NET_OUTPUT_SIZE, USE_GPU_RUNTIME, true, false, context); #ifdef TEMPORAL - s->m->addRecurrent(&s->output[OUTPUT_SIZE], TEMPORAL_SIZE); + s->m->addRecurrent(&s->feature_buffer[0], TEMPORAL_SIZE); #endif #ifdef DESIRE - s->m->addDesire(s->pulse_desire, DESIRE_LEN); + s->m->addDesire(s->pulse_desire, DESIRE_LEN*(HISTORY_BUFFER_LEN+1)); #endif #ifdef TRAFFIC_CONVENTION @@ -56,18 +56,20 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context) { ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, bool prepare_only) { #ifdef DESIRE + std::memmove(&s->pulse_desire[0], &s->pulse_desire[DESIRE_LEN], sizeof(float) * DESIRE_LEN*HISTORY_BUFFER_LEN); if (desire_in != NULL) { for (int i = 1; i < DESIRE_LEN; i++) { // Model decides when action is completed // so desire input is just a pulse triggered on rising edge if (desire_in[i] - s->prev_desire[i] > .99) { - s->pulse_desire[i] = desire_in[i]; + s->pulse_desire[DESIRE_LEN*(HISTORY_BUFFER_LEN-1)+i] = desire_in[i]; } else { - s->pulse_desire[i] = 0.0; + s->pulse_desire[DESIRE_LEN*(HISTORY_BUFFER_LEN-1)+i] = 0.0; } s->prev_desire[i] = desire_in[i]; } } +LOGT("Desire enqueued"); #endif int rhd_idx = is_rhd; @@ -92,6 +94,12 @@ ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, s->m->execute(); LOGT("Execution finished"); + #ifdef TEMPORAL + std::memmove(&s->feature_buffer[0], &s->feature_buffer[FEATURE_LEN], sizeof(float) * FEATURE_LEN*(HISTORY_BUFFER_LEN-1)); + std::memcpy(&s->feature_buffer[FEATURE_LEN*(HISTORY_BUFFER_LEN-1)], &s->output[OUTPUT_SIZE], sizeof(float) * FEATURE_LEN); + LOGT("Features enqueued"); + #endif + return (ModelOutput*)&s->output; } diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index e2ee812e44..90767384eb 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -16,6 +16,8 @@ #include "selfdrive/modeld/models/commonmodel.h" #include "selfdrive/modeld/runners/run.h" +constexpr int FEATURE_LEN = 2048; +constexpr int HISTORY_BUFFER_LEN = 99; constexpr int DESIRE_LEN = 8; constexpr int DESIRE_PRED_LEN = 4; constexpr int TRAFFIC_CONVENTION_LEN = 2; @@ -233,6 +235,11 @@ struct ModelOutputMeta { }; static_assert(sizeof(ModelOutputMeta) == sizeof(ModelOutputDesireProb) + sizeof(float) + (sizeof(ModelOutputDisengageProb)*DISENGAGE_LEN) + (sizeof(ModelOutputBlinkerProb)*BLINKER_LEN) + (sizeof(ModelOutputDesireProb)*DESIRE_PRED_LEN)); +struct ModelOutputFeatures { + std::array feature; +}; +static_assert(sizeof(ModelOutputFeatures) == (sizeof(float)*FEATURE_LEN)); + struct ModelOutput { const ModelOutputPlans plans; const ModelOutputLaneLines lane_lines; @@ -244,22 +251,24 @@ struct ModelOutput { }; constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); + #ifdef TEMPORAL - constexpr int TEMPORAL_SIZE = 512; + constexpr int TEMPORAL_SIZE = HISTORY_BUFFER_LEN * FEATURE_LEN; #else constexpr int TEMPORAL_SIZE = 0; #endif -constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + TEMPORAL_SIZE; +constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + FEATURE_LEN; // TODO: convert remaining arrays to std::array and update model runners struct ModelState { ModelFrame *frame = nullptr; ModelFrame *wide_frame = nullptr; + std::array feature_buffer = {}; std::array output = {}; std::unique_ptr m; #ifdef DESIRE float prev_desire[DESIRE_LEN] = {}; - float pulse_desire[DESIRE_LEN] = {}; + float pulse_desire[DESIRE_LEN*(HISTORY_BUFFER_LEN+1)] = {}; #endif #ifdef TRAFFIC_CONVENTION float traffic_convention[TRAFFIC_CONVENTION_LEN] = {}; diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 7b11edbe08..37edc36c02 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50c7fc8565ac69a4b9a0de122e961326820e78bf13659255a89d0ed04be030d5 -size 95167481 +oid sha256:30f30bc1251c03db135564ecbf7dc0bc96cbb07be0ebd3691edd8d555dc087fa +size 58539693 diff --git a/selfdrive/modeld/runners/onnx_runner.py b/selfdrive/modeld/runners/onnx_runner.py index ac7cc68814..d4a11a7c0b 100755 --- a/selfdrive/modeld/runners/onnx_runner.py +++ b/selfdrive/modeld/runners/onnx_runner.py @@ -9,6 +9,8 @@ os.environ["OMP_WAIT_POLICY"] = "PASSIVE" import onnxruntime as ort # pylint: disable=import-error +ORT_TYPES_TO_NP_TYPES = {'tensor(float16)': np.float16, 'tensor(float)': np.float32, 'tensor(uint8)': np.uint8} + def read(sz, tf8=False): dd = [] gt = 0 @@ -18,7 +20,7 @@ def read(sz, tf8=False): assert(len(st) > 0) dd.append(st) gt += len(st) - r = np.frombuffer(b''.join(dd), dtype=np.uint8 if tf8 else np.float32).astype(np.float32) + r = np.frombuffer(b''.join(dd), dtype=np.uint8 if tf8 else np.float32) if tf8: r = r / 255. return r @@ -29,22 +31,23 @@ def write(d): def run_loop(m, tf8_input=False): ishapes = [[1]+ii.shape[1:] for ii in m.get_inputs()] keys = [x.name for x in m.get_inputs()] + itypes = [ORT_TYPES_TO_NP_TYPES[x.type] for x in m.get_inputs()] # run once to initialize CUDA provider if "CUDAExecutionProvider" in m.get_providers(): - m.run(None, dict(zip(keys, [np.zeros(shp, dtype=np.float32) for shp in ishapes]))) + m.run(None, dict(zip(keys, [np.zeros(shp, dtype=itp) for shp, itp in zip(ishapes, itypes)]))) print("ready to run onnx model", keys, ishapes, file=sys.stderr) while 1: inputs = [] - for k, shp in zip(keys, ishapes): + for k, shp, itp in zip(keys, ishapes, itypes): ts = np.product(shp) #print("reshaping %s with offset %d" % (str(shp), offset), file=sys.stderr) - inputs.append(read(ts, (k=='input_img' and tf8_input)).reshape(shp)) + inputs.append(read(ts, (k=='input_img' and tf8_input)).reshape(shp).astype(itp)) ret = m.run(None, dict(zip(keys, inputs))) #print(ret, file=sys.stderr) for r in ret: - write(r) + write(r.astype(np.float32)) if __name__ == "__main__": diff --git a/selfdrive/modeld/thneed/thneed_common.cc b/selfdrive/modeld/thneed/thneed_common.cc index 21170b13a6..ecdf1237e3 100644 --- a/selfdrive/modeld/thneed/thneed_common.cc +++ b/selfdrive/modeld/thneed/thneed_common.cc @@ -12,7 +12,7 @@ map, int> g_args_size; map g_program_source; void Thneed::stop() { - printf("Thneed::stop: recorded %lu commands\n", cmds.size()); + //printf("Thneed::stop: recorded %lu commands\n", cmds.size()); record = false; } diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index bf6daf5fed..c14956b1b2 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -41,7 +41,7 @@ def remove_ignored_fields(msg, ignore): elif isinstance(v, numbers.Number): val = 0 else: - raise NotImplementedError + raise NotImplementedError('Error ignoring field') setattr(attr, keys[-1], val) return msg.as_reader() diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index ccd89bea9a..ecfacbf5d2 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -22,7 +22,7 @@ from tools.lib.logreader import LogReader TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" SEGMENT = 0 -MAX_FRAMES = 10 if PC else 1300 +MAX_FRAMES = 100 if PC else 1300 SEND_EXTRA_INPUTS = bool(os.getenv("SEND_EXTRA_INPUTS", "0")) @@ -174,7 +174,7 @@ if __name__ == "__main__": 'driverStateV2.dspExecutionTime' ] # TODO this tolerance is absurdly large - tolerance = 5e-1 if PC else None + tolerance = 2.0 if PC else None results: Any = {TEST_ROUTE: {}} log_paths: Any = {TEST_ROUTE: {"models": {'ref': BASE_URL + log_fn, 'new': log_fn}}} results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 958d3da14d..9727ef3d17 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -c40319a454840d8a2196ec1227d27b536ee14375 +008c0a622b7471c6234690d668f76bcb5dc8d999 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index d4d16b726b..0e3cdf797f 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -1989d8c2a5de94faa3756b7d10fc94e6c063afa5 \ No newline at end of file +e0ffcae8def2fd9c82c547d1f257d4f06a48a3c3 diff --git a/tinygrad_repo b/tinygrad_repo index 2e9b7637b3..82ca9c6666 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 2e9b7637b3c3c8895fda9f964215db3a35fe3441 +Subproject commit 82ca9c66664b5f3739e1881f62cb94b72cf0b1fa From 8b33ee97502cf673d769fb7c2283773dd7c3dd86 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Wed, 5 Oct 2022 23:02:35 -0700 Subject: [PATCH 084/178] Bump tinygrad to master --- tinygrad_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinygrad_repo b/tinygrad_repo index 82ca9c6666..2993dfe921 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 82ca9c66664b5f3739e1881f62cb94b72cf0b1fa +Subproject commit 2993dfe9219089bd1464741757f0c2ad67fe6a2e From 4cd3753d98bea907929ce09d9f5c218d5fce7653 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 7 Oct 2022 02:20:49 +0800 Subject: [PATCH 085/178] cabana: insert new chart at the top (#25981) * small cleanup * new chart insert at the top --- tools/cabana/chartswidget.cc | 2 +- tools/cabana/mainwin.cc | 1 + tools/cabana/messageswidget.cc | 22 +++++++++++----------- tools/cabana/parser.h | 1 - 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 836eb34946..a504250e6d 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -39,7 +39,7 @@ void ChartsWidget::addChart(const QString &id, const QString &sig_name) { const QString char_name = id + sig_name; if (charts.find(char_name) == charts.end()) { auto chart = new ChartWidget(id, sig_name, this); - main_layout->addWidget(chart); + main_layout->insertWidget(0, chart); charts[char_name] = chart; } } diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 8852987fbe..079f592362 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -5,6 +5,7 @@ MainWindow::MainWindow() : QWidget() { QVBoxLayout *main_layout = new QVBoxLayout(this); + QHBoxLayout *h_layout = new QHBoxLayout(); main_layout->addLayout(h_layout); diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 840ea25810..daeb37eb9f 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -14,20 +14,11 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { for (const auto &name : dbc_names) { combo->addItem(QString::fromStdString(name)); } - connect(combo, &QComboBox::currentTextChanged, [=](const QString &dbc) { - parser->openDBC(dbc); - }); - // For test purpose - combo->setCurrentText("toyota_nodsu_pt_generated"); dbc_file_layout->addWidget(combo); dbc_file_layout->addStretch(); QPushButton *save_btn = new QPushButton(tr("Save DBC"), this); - QObject::connect(save_btn, &QPushButton::clicked, [=]() { - // TODO: save DBC to file - }); dbc_file_layout->addWidget(save_btn); - main_layout->addLayout(dbc_file_layout); filter = new QLineEdit(this); @@ -44,14 +35,23 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { table_widget->setColumnWidth(2, 80); table_widget->setHorizontalHeaderLabels({tr("Name"), tr("ID"), tr("Count"), tr("Bytes")}); table_widget->horizontalHeader()->setStretchLastSection(true); + main_layout->addWidget(table_widget); + + QObject::connect(parser, &Parser::updated, this, &MessagesWidget::updateState); + QObject::connect(save_btn, &QPushButton::clicked, [=]() { + // TODO: save DBC to file + }); + QObject::connect(combo, &QComboBox::currentTextChanged, [=](const QString &dbc) { + parser->openDBC(dbc); + }); QObject::connect(table_widget, &QTableWidget::itemSelectionChanged, [=]() { const CanData *c = &(parser->can_msgs[table_widget->selectedItems()[1]->text()]); parser->setCurrentMsg(c->id); emit msgChanged(c); }); - main_layout->addWidget(table_widget); - connect(parser, &Parser::updated, this, &MessagesWidget::updateState); + // For test purpose + combo->setCurrentText("toyota_nodsu_pt_generated"); } void MessagesWidget::updateState() { diff --git a/tools/cabana/parser.h b/tools/cabana/parser.h index 2f8c441059..4ed64a90c5 100644 --- a/tools/cabana/parser.h +++ b/tools/cabana/parser.h @@ -12,7 +12,6 @@ #include "opendbc/can/common_dbc.h" #include "tools/replay/replay.h" -const int DATA_LIST_SIZE = 50; const int FPS = 20; const static int LOG_SIZE = 25; From 7156633034ac0232b60f79b2d47bf3e4bbaad1c9 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 7 Oct 2022 02:21:04 +0800 Subject: [PATCH 086/178] cabana: use QFormLayout (#25982) --- tools/cabana/detailwidget.cc | 19 ++++----- tools/cabana/signaledit.cc | 81 ++++++++++-------------------------- 2 files changed, 30 insertions(+), 70 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index e573a01970..74044cd173 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -224,24 +225,20 @@ void HistoryLog::clear() { EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : id(id), QDialog(parent) { setWindowTitle(tr("Edit message")); QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->addWidget(new QLabel(tr("ID: (%1)").arg(id))); + + QFormLayout *form_layout = new QFormLayout(); + form_layout->addRow("ID", new QLabel(id)); auto msg = const_cast(parser->getMsg(id)); - QHBoxLayout *h_layout = new QHBoxLayout(); - h_layout->addWidget(new QLabel(tr("Name"))); - h_layout->addStretch(); name_edit = new QLineEdit(this); name_edit->setText(msg ? msg->name.c_str() : "untitled"); - h_layout->addWidget(name_edit); - main_layout->addLayout(h_layout); + form_layout->addRow(tr("Name"), name_edit); - h_layout = new QHBoxLayout(); - h_layout->addWidget(new QLabel(tr("Size"))); - h_layout->addStretch(); size_spin = new QSpinBox(this); size_spin->setValue(msg ? msg->size : parser->can_msgs[id].dat.size()); - h_layout->addWidget(size_spin); - main_layout->addLayout(h_layout); + form_layout->addRow(tr("Size"), size_spin); + + main_layout->addLayout(form_layout); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox); diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 080b7920de..e233b7b3c2 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -1,6 +1,7 @@ #include "tools/cabana/signaledit.h" #include +#include #include #include #include @@ -9,88 +10,48 @@ // SignalForm SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { - QVBoxLayout *v_layout = new QVBoxLayout(this); + QFormLayout *form_layout = new QFormLayout(this); - QHBoxLayout *h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Name"))); name = new QLineEdit(sig.name.c_str()); - h->addWidget(name); - v_layout->addLayout(h); + form_layout->addRow(tr("Name"), name); - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Size"))); size = new QSpinBox(); size->setValue(sig.size); - h->addWidget(size); - v_layout->addLayout(h); + form_layout->addRow(tr("Size"), size); - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Most significant bit"))); msb = new QSpinBox(); msb->setValue(sig.msb); - h->addWidget(msb); - v_layout->addLayout(h); + form_layout->addRow(tr("Most significant bit"), msb); - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Endianness"))); endianness = new QComboBox(); endianness->addItems({"Little", "Big"}); endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1); - h->addWidget(endianness); - v_layout->addLayout(h); + form_layout->addRow(tr("Endianness"), endianness); - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("sign"))); sign = new QComboBox(); sign->addItems({"Signed", "Unsigned"}); sign->setCurrentIndex(sig.is_signed ? 0 : 1); - h->addWidget(sign); - v_layout->addLayout(h); + form_layout->addRow(tr("sign"), sign); - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Factor"))); factor = new QSpinBox(); factor->setValue(sig.factor); - h->addWidget(factor); - v_layout->addLayout(h); + form_layout->addRow(tr("Factor"), factor); - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Offset"))); offset = new QSpinBox(); offset->setValue(sig.offset); - h->addWidget(offset); - v_layout->addLayout(h); + form_layout->addRow(tr("Offset"), offset); // TODO: parse the following parameters in opendbc - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Unit"))); unit = new QLineEdit(); - h->addWidget(unit); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Comment"))); + form_layout->addRow(tr("Unit"), unit); comment = new QLineEdit(); - h->addWidget(comment); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Minimum value"))); + form_layout->addRow(tr("Comment"), comment); min_val = new QSpinBox(); - h->addWidget(min_val); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Maximum value"))); + form_layout->addRow(tr("Minimum value"), min_val); max_val = new QSpinBox(); - h->addWidget(max_val); - v_layout->addLayout(h); - - h = new QHBoxLayout(); - h->addWidget(new QLabel(tr("Value descriptions"))); + form_layout->addRow(tr("Maximum value"), max_val); val_desc = new QLineEdit(); - h->addWidget(val_desc); - v_layout->addLayout(h); + form_layout->addRow(tr("Value descriptions"), val_desc); } std::optional SignalForm::getSignal() { @@ -126,12 +87,9 @@ SignalEdit::SignalEdit(const QString &id, const Signal &sig, const QString &colo title = new ElidedLabel(this); title->setText(sig.name.c_str()); title->setStyleSheet(QString("font-weight:bold; color:%1").arg(color)); - connect(title, &ElidedLabel::clicked, [=]() { - edit_container->isVisible() ? edit_container->hide() : edit_container->show(); - icon->setText(edit_container->isVisible() ? "▼" : ">"); - }); title_layout->addWidget(title); title_layout->addStretch(); + plot_btn = new QPushButton("📈"); plot_btn->setStyleSheet("font-size:16px"); plot_btn->setToolTip(tr("Show Plot")); @@ -148,16 +106,21 @@ SignalEdit::SignalEdit(const QString &id, const Signal &sig, const QString &colo QHBoxLayout *h = new QHBoxLayout(); remove_btn = new QPushButton(tr("Remove Signal")); - QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); h->addWidget(remove_btn); h->addStretch(); QPushButton *save_btn = new QPushButton(tr("Save")); - QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::save); h->addWidget(save_btn); v_layout->addLayout(h); edit_container->setVisible(false); main_layout->addWidget(edit_container); + + QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); + QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::save); + QObject::connect(title, &ElidedLabel::clicked, [=]() { + edit_container->isVisible() ? edit_container->hide() : edit_container->show(); + icon->setText(edit_container->isVisible() ? "▼" : ">"); + }); } void SignalEdit::save() { From 2d9e7972598918a72de736e12edbc2967d77e841 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 7 Oct 2022 04:35:56 +0800 Subject: [PATCH 087/178] cabana: Fix the incorrect Y axis (#25984) --- tools/cabana/chartswidget.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index a504250e6d..f964e5606a 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -136,6 +136,7 @@ void ChartWidget::updateSeries() { vals.clear(); vals.reserve(3 * 60 * 100); + double min_y = 0, max_y = 0; uint64_t route_start_time = parser->replay->routeStartTime(); for (auto &evt : *events) { if (evt->which == cereal::Event::Which::CAN) { @@ -147,6 +148,9 @@ void ChartWidget::updateSeries() { val -= ((val >> (sig->size - 1)) & 0x1) ? (1ULL << sig->size) : 0; } double value = val * sig->factor + sig->offset; + if (value < min_y) min_y = value; + if (value > max_y) max_y = value; + double ts = (evt->mono_time - route_start_time) / (double)1e9; // seconds vals.push_back({ts, value}); } @@ -157,6 +161,7 @@ void ChartWidget::updateSeries() { series->replace(vals); auto [begin, end] = parser->range(); chart_view->chart()->axisX()->setRange(begin, end); + chart_view->chart()->axisY()->setRange(min_y * 0.95, max_y * 1.05); } void ChartWidget::rangeChanged(qreal min, qreal max) { From 9935a651157c512c37926b43fdcda3566de11261 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 6 Oct 2022 13:47:09 -0700 Subject: [PATCH 088/178] always enable rawgps on tici (#25987) --- selfdrive/manager/process_config.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 06efdbb960..3f63fbb959 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -43,6 +43,7 @@ procs = [ PythonProcess("deleter", "selfdrive.loggerd.deleter", offroad=True), PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", enabled=(not PC or WEBCAM), callback=driverview), PythonProcess("laikad", "selfdrive.locationd.laikad"), + PythonProcess("rawgpsd", "selfdrive.sensord.rawgps.rawgpsd", enabled=TICI), PythonProcess("navd", "selfdrive.navd.navd"), PythonProcess("pandad", "selfdrive.boardd.pandad", offroad=True), PythonProcess("paramsd", "selfdrive.locationd.paramsd"), @@ -55,11 +56,9 @@ procs = [ PythonProcess("uploader", "selfdrive.loggerd.uploader", offroad=True), PythonProcess("statsd", "selfdrive.statsd", offroad=True), + # debug procs NativeProcess("bridge", "cereal/messaging", ["./bridge"], onroad=False, callback=notcar), PythonProcess("webjoystick", "tools.joystick.web", onroad=False, callback=notcar), - - # Experimental - PythonProcess("rawgpsd", "selfdrive.sensord.rawgps.rawgpsd", enabled=(TICI and os.path.isfile("/persist/comma/use-quectel-rawgps"))), ] managed_processes = {p.name: p for p in procs} From 9ec262bbfd09c7cd12773cabab0bb6d933b4d13e Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 7 Oct 2022 04:57:11 +0800 Subject: [PATCH 089/178] cabana: Docking and undocking charts (#25983) * floating dock charts * more button * setMinimumSize * move reset zoom button to title bar * show chart count * cleanup * reduce flicker * dont update linemarker if pos not changed * cleanup * remove blank line * always show dock/undock button --- tools/cabana/chartswidget.cc | 125 ++++++++++++++++++++++++++++------- tools/cabana/chartswidget.h | 34 +++++++--- tools/cabana/mainwin.cc | 40 ++++++++--- tools/cabana/mainwin.h | 7 +- tools/cabana/signaledit.cc | 2 - tools/cabana/videowidget.cc | 1 + 6 files changed, 161 insertions(+), 48 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index f964e5606a..3e1a8b8410 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -27,30 +26,105 @@ int64_t get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) { return ret; } +// ChartsWidget + ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { - main_layout = new QVBoxLayout(this); + QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart); - connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart); - connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart); + + // title bar + title_bar = new QWidget(this); + QHBoxLayout *title_layout = new QHBoxLayout(title_bar); + title_label = new QLabel(tr("Charts")); + + title_layout->addWidget(title_label); + title_layout->addStretch(); + + reset_zoom_btn = new QPushButton("⟲", this); + reset_zoom_btn->setVisible(false); + reset_zoom_btn->setFixedSize(30, 30); + reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); + title_layout->addWidget(reset_zoom_btn); + + remove_all_btn = new QPushButton(tr("✖")); + remove_all_btn->setVisible(false); + remove_all_btn->setToolTip(tr("Remove all charts")); + remove_all_btn->setFixedSize(30, 30); + title_layout->addWidget(remove_all_btn); + + dock_btn = new QPushButton(); + dock_btn->setFixedSize(30, 30); + updateDockButton(); + title_layout->addWidget(dock_btn); + + main_layout->addWidget(title_bar, 0, Qt::AlignTop); + + // charts + QWidget *charts_container = new QWidget(this); + QVBoxLayout *charts_main = new QVBoxLayout(charts_container); + charts_layout = new QVBoxLayout(); + charts_main->addLayout(charts_layout); + charts_main->addStretch(); + + QScrollArea *charts_scroll = new QScrollArea(this); + charts_scroll->setWidgetResizable(true); + charts_scroll->setWidget(charts_container); + charts_scroll->setFrameShape(QFrame::NoFrame); + charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + main_layout->addWidget(charts_scroll); + + QObject::connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart); + QObject::connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart); + QObject::connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart); + QObject::connect(reset_zoom_btn, &QPushButton::clicked, parser, &Parser::resetRange); + QObject::connect(remove_all_btn, &QPushButton::clicked, this, &ChartsWidget::removeAll); + QObject::connect(dock_btn, &QPushButton::clicked, [=]() { + emit dock(!docking); + docking = !docking; + updateDockButton(); + }); +} + +void ChartsWidget::updateDockButton() { + dock_btn->setText(docking ? "⬈" : "⬋"); + dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } void ChartsWidget::addChart(const QString &id, const QString &sig_name) { const QString char_name = id + sig_name; if (charts.find(char_name) == charts.end()) { auto chart = new ChartWidget(id, sig_name, this); - main_layout->insertWidget(0, chart); + charts_layout->insertWidget(0, chart); charts[char_name] = chart; } + remove_all_btn->setVisible(true); + reset_zoom_btn->setVisible(true); + title_label->setText(tr("Charts (%1)").arg(charts.size())); } void ChartsWidget::removeChart(const QString &id, const QString &sig_name) { if (auto it = charts.find(id + sig_name); it != charts.end()) { it->second->deleteLater(); charts.erase(it); + if (charts.empty()) { + remove_all_btn->setVisible(false); + reset_zoom_btn->setVisible(false); + } } + title_label->setText(tr("Charts (%1)").arg(charts.size())); +} + +void ChartsWidget::removeAll() { + for (auto [_, chart] : charts) + chart->deleteLater(); + charts.clear(); + remove_all_btn->setVisible(false); + reset_zoom_btn->setVisible(false); } +// ChartWidget + ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *parent) : id(id), sig_name(sig_name), QWidget(parent) { QStackedLayout *stacked = new QStackedLayout(this); stacked->setStackingMode(QStackedLayout::StackAll); @@ -64,17 +138,13 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa header->setStyleSheet("background-color:white"); QHBoxLayout *header_layout = new QHBoxLayout(header); header_layout->setContentsMargins(11, 11, 11, 0); - auto title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id)); + QLabel *title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id)); header_layout->addWidget(title); header_layout->addStretch(); - zoom_label = new QLabel("", this); - header_layout->addWidget(zoom_label); - QPushButton *zoom_in = new QPushButton("↺", this); - zoom_in->setToolTip(tr("reset zoom")); - QObject::connect(zoom_in, &QPushButton::clicked, []() { parser->resetRange(); }); - header_layout->addWidget(zoom_in); QPushButton *remove_btn = new QPushButton("✖", this); + remove_btn->setFixedSize(30, 30); + remove_btn->setToolTip(tr("Remove chart")); QObject::connect(remove_btn, &QPushButton::clicked, [=]() { emit parser->hidePlot(id, sig_name); }); @@ -108,7 +178,7 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa chart_layout->addStretch(); stacked->addWidget(chart_widget); - line_marker = new LineMarker(chart, this); + line_marker = new LineMarker(this); stacked->addWidget(line_marker); line_marker->setAttribute(Qt::WA_TransparentForMouseEvents, true); line_marker->raise(); @@ -122,7 +192,15 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa } void ChartWidget::updateState() { - line_marker->update(); + auto chart = chart_view->chart(); + auto axis_x = dynamic_cast(chart->axisX()); + if (axis_x->max() <= axis_x->min()) return; + + int x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); + if (line_marker_x != x) { + line_marker->setX(x); + line_marker_x = x; + } } void ChartWidget::updateSeries() { @@ -182,16 +260,15 @@ void ChartWidget::rangeChanged(qreal min, qreal max) { chart_view->chart()->axisY()->setRange(min_y * 0.95, max_y * 1.05); } -LineMarker::LineMarker(QChart *chart, QWidget *parent) : chart(chart), QWidget(parent) {} +// LineMarker -void LineMarker::paintEvent(QPaintEvent *event) { - auto axis_x = dynamic_cast(chart->axisX()); - if (axis_x->max() <= axis_x->min()) return; +void LineMarker::setX(double x) { + x_pos = x; + update(); +} - double x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); +void LineMarker::paintEvent(QPaintEvent *event) { QPainter p(this); - QPen pen = QPen(Qt::black); - pen.setWidth(2); - p.setPen(pen); - p.drawLine(QPointF{x, 50.}, QPointF{x, (qreal)height() - 11}); + p.setPen(QPen(Qt::black, 2)); + p.drawLine(QPointF{x_pos, 50.}, QPointF{x_pos, (qreal)height() - 11}); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 1be5fdeecb..0413d65e09 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -16,11 +17,12 @@ class LineMarker : public QWidget { Q_OBJECT public: - LineMarker(QChart *chart, QWidget *parent); - void paintEvent(QPaintEvent *event) override; + LineMarker(QWidget *parent) : QWidget(parent) {} + void setX(double x); private: - QChart *chart; + void paintEvent(QPaintEvent *event) override; + double x_pos = 0.0; }; class ChartWidget : public QWidget { @@ -30,7 +32,7 @@ public: ChartWidget(const QString &id, const QString &sig_name, QWidget *parent); inline QChart *chart() const { return chart_view->chart(); } -protected: +private: void updateState(); void addData(const CanData &can_data, const Signal &sig); void updateSeries(); @@ -38,9 +40,9 @@ protected: QString id; QString sig_name; - QLabel *zoom_label; QChartView *chart_view = nullptr; LineMarker *line_marker = nullptr; + double line_marker_x = 0.0; QList vals; }; @@ -49,14 +51,26 @@ class ChartsWidget : public QWidget { public: ChartsWidget(QWidget *parent = nullptr); - inline bool hasChart(const QString &id, const QString &sig_name) { - return charts.find(id+sig_name) != charts.end(); - } void addChart(const QString &id, const QString &sig_name); void removeChart(const QString &id, const QString &sig_name); + void removeAll(); + inline bool hasChart(const QString &id, const QString &sig_name) { + return charts.find(id + sig_name) != charts.end(); + } + +signals: + void dock(bool floating); + +private: void updateState(); + void updateDockButton(); -protected: - QVBoxLayout *main_layout; + QWidget *title_bar; + QLabel *title_label; + bool docking = true; + QPushButton *dock_btn; + QPushButton *reset_zoom_btn; + QPushButton *remove_all_btn; + QVBoxLayout *charts_layout; std::map charts; }; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 079f592362..49e6cd2cca 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -1,6 +1,7 @@ #include "tools/cabana/mainwin.h" #include +#include #include MainWindow::MainWindow() : QWidget() { @@ -16,23 +17,42 @@ MainWindow::MainWindow() : QWidget() { detail_widget->setFixedWidth(600); h_layout->addWidget(detail_widget); - // right widget + // right widgets QWidget *right_container = new QWidget(this); right_container->setFixedWidth(640); - QVBoxLayout *r_layout = new QVBoxLayout(right_container); + r_layout = new QVBoxLayout(right_container); + video_widget = new VideoWidget(this); - r_layout->addWidget(video_widget); + r_layout->addWidget(video_widget, 0, Qt::AlignTop); charts_widget = new ChartsWidget(this); - QScrollArea *scroll = new QScrollArea(this); - scroll->setWidget(charts_widget); - scroll->setWidgetResizable(true); - scroll->setFrameShape(QFrame::NoFrame); - scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - r_layout->addWidget(scroll); + r_layout->addWidget(charts_widget); h_layout->addWidget(right_container); QObject::connect(messages_widget, &MessagesWidget::msgChanged, detail_widget, &DetailWidget::setMsg); + QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); +} + +void MainWindow::dockCharts(bool dock) { + charts_widget->setUpdatesEnabled(false); + if (dock && floating_window) { + r_layout->addWidget(charts_widget); + floating_window->deleteLater(); + floating_window = nullptr; + } else if (!dock && !floating_window) { + floating_window = new QWidget(nullptr); + floating_window->setLayout(new QVBoxLayout()); + floating_window->layout()->addWidget(charts_widget); + floating_window->setWindowFlags(Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint); + floating_window->setMinimumSize(QGuiApplication::primaryScreen()->size() / 2); + floating_window->showMaximized(); + } + charts_widget->setUpdatesEnabled(true); +} + +void MainWindow::closeEvent(QCloseEvent *event) { + if (floating_window) + floating_window->deleteLater(); + QWidget::closeEvent(event); } diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 82ecceb02b..bcd15e4d8e 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "tools/cabana/chartswidget.h" #include "tools/cabana/detailwidget.h" #include "tools/cabana/messageswidget.h" @@ -13,10 +11,15 @@ class MainWindow : public QWidget { public: MainWindow(); + void dockCharts(bool dock); protected: + void closeEvent(QCloseEvent *event) override; + VideoWidget *video_widget; MessagesWidget *messages_widget; DetailWidget *detail_widget; ChartsWidget *charts_widget; + QWidget *floating_window = nullptr; + QVBoxLayout *r_layout; }; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index e233b7b3c2..c214adab09 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -91,9 +91,7 @@ SignalEdit::SignalEdit(const QString &id, const Signal &sig, const QString &colo title_layout->addStretch(); plot_btn = new QPushButton("📈"); - plot_btn->setStyleSheet("font-size:16px"); plot_btn->setToolTip(tr("Show Plot")); - plot_btn->setContentsMargins(5, 5, 5, 5); plot_btn->setFixedSize(30, 30); QObject::connect(plot_btn, &QPushButton::clicked, [=]() { emit parser->showPlot(id, name_); }); title_layout->addWidget(plot_btn); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index dbf988d8f2..c8e1ad680f 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -64,6 +64,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { } main_layout->addLayout(control_layout); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QObject::connect(parser, &Parser::rangeChanged, this, &VideoWidget::rangeChanged); QObject::connect(parser, &Parser::updated, this, &VideoWidget::updateState); From e66a70b9e988ae34acff117a686818e913b797f7 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 6 Oct 2022 14:15:36 -0700 Subject: [PATCH 090/178] bump opendbc --- opendbc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc b/opendbc index 9ddcdb22c4..ae2fd934ce 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 9ddcdb22c4929baf310295e832668e6e7fcfa602 +Subproject commit ae2fd934ceb8501c56a0802564c14963dbb201ac From 9e6265ce2188ac48442b07a77e86ca4fd3eba06b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 6 Oct 2022 15:01:57 -0700 Subject: [PATCH 091/178] CI: re-enable power draw test (#25988) * CI: re-enable power draw test * adjust for ngrl --- Jenkinsfile | 2 +- system/hardware/tici/test_power_draw.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c4038090e1..b9b9eda667 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -127,7 +127,7 @@ pipeline { steps { phone_steps("tici2", [ ["build", "cd selfdrive/manager && ./build.py"], - //["test power draw", "python system/hardware/tici/test_power_draw.py"], + ["test power draw", "python system/hardware/tici/test_power_draw.py"], ["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"], ["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"], diff --git a/system/hardware/tici/test_power_draw.py b/system/hardware/tici/test_power_draw.py index 4b380372b9..4830975917 100755 --- a/system/hardware/tici/test_power_draw.py +++ b/system/hardware/tici/test_power_draw.py @@ -20,7 +20,7 @@ class Proc: PROCS = [ Proc('camerad', 2.15), - Proc('modeld', 1.0, atol=0.15), + Proc('modeld', 1.15, atol=0.2), Proc('dmonitoringmodeld', 0.35), Proc('encoderd', 0.23), ] From 750b96aaedb88defd522a60a4bb5fbfeb46332db Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 7 Oct 2022 06:02:22 +0800 Subject: [PATCH 092/178] cabana: improve time control (#25985) * group signals/slots together * slider:fix wrong minimum * add TODO * moveing to mouse click position * show tickmark * fix seek back to the old pos after sliderReleased * reduce data copied in queued connection * drop packets while seeking * install event filter in streaming * stop replay in dctor --- tools/cabana/detailwidget.cc | 2 +- tools/cabana/messageswidget.cc | 2 +- tools/cabana/parser.cc | 61 ++++++++++++++++------------------ tools/cabana/parser.h | 14 ++++---- tools/cabana/videowidget.cc | 50 ++++++++++++++++++++-------- tools/cabana/videowidget.h | 14 +++++++- tools/replay/replay.cc | 2 ++ tools/replay/replay.h | 10 ++++++ 8 files changed, 100 insertions(+), 55 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 74044cd173..1b6552804e 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -206,7 +206,7 @@ void HistoryLog::updateState() { const auto &c = parser->history_log[i]; auto label = labels[i]; label->setVisible(true); - label->setText(QString("%1 %2").arg(c.ts, 0, 'f', 3).arg(c.hex_dat)); + label->setText(QString("%1 %2").arg(c.ts, 0, 'f', 3).arg(toHex(c.dat))); } for (; i < std::size(labels); ++i) { diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index daeb37eb9f..7de3507b3d 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -83,7 +83,7 @@ void MessagesWidget::updateState() { getTableItem(i, 0)->setText(name); getTableItem(i, 1)->setText(c.id); getTableItem(i, 2)->setText(QString::number(parser->counters[c.id])); - getTableItem(i, 3)->setText(c.hex_dat); + getTableItem(i, 3)->setText(toHex(c.dat)); table_widget->showRow(i); i++; } diff --git a/tools/cabana/parser.cc b/tools/cabana/parser.cc index 3950d8bd57..f4bacbb86d 100644 --- a/tools/cabana/parser.cc +++ b/tools/cabana/parser.cc @@ -6,27 +6,25 @@ Parser *parser = nullptr; +static bool event_filter(const Event *e, void *opaque) { + Parser *p = (Parser*)opaque; + return p->eventFilter(e); +} + Parser::Parser(QObject *parent) : QObject(parent) { parser = this; qRegisterMetaType>(); QObject::connect(this, &Parser::received, this, &Parser::process, Qt::QueuedConnection); - - thread = new QThread(); - connect(thread, &QThread::started, [=]() { recvThread(); }); - QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); - thread->start(); } Parser::~Parser() { replay->stop(); - exit = true; - thread->quit(); - thread->wait(); } bool Parser::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { replay = new Replay(route, {"can", "roadEncodeIdx"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); + replay->installEventFilter(event_filter, this); QObject::connect(replay, &Replay::segmentsMerged, this, &Parser::segmentsMerged); if (replay->load()) { replay->start(); @@ -47,9 +45,9 @@ void Parser::openDBC(const QString &name) { void Parser::process(std::vector msgs) { static double prev_update_ts = 0; + for (const auto &can_data : msgs) { can_msgs[can_data.id] = can_data; - current_sec = can_data.ts; ++counters[can_data.id]; if (can_data.id == current_msg_id) { @@ -59,46 +57,45 @@ void Parser::process(std::vector msgs) { history_log.push_front(can_data); } } - double current_ts = millis_since_boot(); - if ((current_ts - prev_update_ts) > 1000.0 / FPS) { - prev_update_ts = current_ts; + double now = millis_since_boot(); + if ((now - prev_update_ts) > 1000.0 / FPS) { + prev_update_ts = now; emit updated(); } if (current_sec < begin_sec || current_sec > end_sec) { // loop replay in selected range. - replay->seekTo(begin_sec, false); + seekTo(begin_sec); } } -void Parser::recvThread() { - AlignedBuffer aligned_buf; - std::unique_ptr context(Context::create()); - std::unique_ptr subscriber(SubSocket::create(context.get(), "can")); - subscriber->setTimeout(100); +bool Parser::eventFilter(const Event *event) { + // drop packets when the GUI thread is calling seekTo. to make sure the current_sec is accurate. + if (!seeking && event->which == cereal::Event::Which::CAN) { + current_sec = (event->mono_time - replay->routeStartTime()) / (double)1e9; - std::vector can; - while (!exit) { - std::unique_ptr msg(subscriber->receive()); - if (!msg) continue; + auto can = event->event.getCan(); + msgs_buf.clear(); + msgs_buf.reserve(can.size()); - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); - cereal::Event::Reader event = cmsg.getRoot(); - - can.clear(); - can.reserve(event.getCan().size()); - for (const auto &c : event.getCan()) { - CanData &data = can.emplace_back(); + for (const auto &c : can) { + CanData &data = msgs_buf.emplace_back(); data.address = c.getAddress(); data.bus_time = c.getBusTime(); data.source = c.getSrc(); data.dat.append((char *)c.getDat().begin(), c.getDat().size()); - data.hex_dat = data.dat.toHex(' ').toUpper(); data.id = QString("%1:%2").arg(data.source).arg(data.address, 1, 16); - data.ts = (event.getLogMonoTime() - replay->routeStartTime()) / (double)1e9; // seconds + data.ts = current_sec; } - emit received(can); + emit received(msgs_buf); } + return true; +} + +void Parser::seekTo(double ts) { + seeking = true; + replay->seekTo(ts, false); + seeking = false; } void Parser::addNewMsg(const Msg &msg) { diff --git a/tools/cabana/parser.h b/tools/cabana/parser.h index 4ed64a90c5..1632fcf6a6 100644 --- a/tools/cabana/parser.h +++ b/tools/cabana/parser.h @@ -6,7 +6,6 @@ #include #include #include -#include #include "opendbc/can/common.h" #include "opendbc/can/common_dbc.h" @@ -22,7 +21,6 @@ struct CanData { uint16_t bus_time; uint8_t source; QByteArray dat; - QString hex_dat; }; class Parser : public QObject { @@ -32,11 +30,13 @@ public: Parser(QObject *parent); ~Parser(); static uint32_t addressFromId(const QString &id); + bool eventFilter(const Event *event); bool loadRoute(const QString &route, const QString &data_dir, bool use_qcam); void openDBC(const QString &name); void saveDBC(const QString &name) {} void addNewMsg(const Msg &msg); void removeSignal(const QString &id, const QString &sig_name); + void seekTo(double ts); const Signal *getSig(const QString &id, const QString &sig_name); void setRange(double min, double max); void resetRange(); @@ -66,13 +66,11 @@ public: QList history_log; protected: - void recvThread(); void process(std::vector can); void segmentsMerged(); - double current_sec = 0.; - std::atomic exit = false; - QThread *thread; + std::atomic current_sec = 0.; + std::atomic seeking = false; QString dbc_name; double begin_sec = 0; double end_sec = 0; @@ -82,6 +80,7 @@ protected: DBC *dbc = nullptr; std::map msg_map; QString current_msg_id; + std::vector msgs_buf; }; Q_DECLARE_METATYPE(std::vector); @@ -89,5 +88,8 @@ Q_DECLARE_METATYPE(std::vector); // TODO: Add helper function in dbc.h int bigEndianStartBitsIndex(int start_bit); int bigEndianBitIndex(int index); +inline QString toHex(const QByteArray &dat) { + return dat.toHex(' ').toUpper(); +} extern Parser *parser; diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index c8e1ad680f..26fb845340 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -15,6 +16,7 @@ inline QString formatTime(int seconds) { VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); + // TODO: figure out why the CameraViewWidget crashed occasionally. cam_widget = new CameraViewWidget("camerad", VISION_STREAM_ROAD, false, this); cam_widget->setFixedSize(parent->width(), parent->width() / 1.596); main_layout->addWidget(cam_widget); @@ -24,16 +26,12 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { time_label = new QLabel("00:00"); slider_layout->addWidget(time_label); - slider = new QSlider(Qt::Horizontal, this); - QObject::connect(slider, &QSlider::sliderMoved, [=]() { - time_label->setText(formatTime(slider->value())); - }); - slider->setSingleStep(1); + slider = new Slider(this); + slider->setSingleStep(0); + slider->setMinimum(0); + slider->setTickInterval(60); + slider->setTickPosition(QSlider::TicksBelow); slider->setMaximum(parser->replay->totalSeconds()); - QObject::connect(slider, &QSlider::sliderReleased, [=]() { - time_label->setText(formatTime(slider->value())); - parser->replay->seekTo(slider->value(), false); - }); slider_layout->addWidget(slider); total_time_label = new QLabel(formatTime(parser->replay->totalSeconds())); @@ -45,11 +43,6 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QHBoxLayout *control_layout = new QHBoxLayout(); QPushButton *play = new QPushButton("⏸"); play->setStyleSheet("font-weight:bold"); - QObject::connect(play, &QPushButton::clicked, [=]() { - bool is_paused = parser->replay->isPaused(); - play->setText(is_paused ? "⏸" : "▶"); - parser->replay->pause(!is_paused); - }); control_layout->addWidget(play); QButtonGroup *group = new QButtonGroup(this); @@ -68,6 +61,20 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QObject::connect(parser, &Parser::rangeChanged, this, &VideoWidget::rangeChanged); QObject::connect(parser, &Parser::updated, this, &VideoWidget::updateState); + QObject::connect(slider, &QSlider::sliderMoved, [=]() { time_label->setText(formatTime(slider->value())); }); + QObject::connect(slider, &QSlider::sliderReleased, [this]() { setPosition(slider->value()); }); + QObject::connect(slider, &Slider::setPosition, this, &VideoWidget::setPosition); + + QObject::connect(play, &QPushButton::clicked, [=]() { + bool is_paused = parser->replay->isPaused(); + play->setText(is_paused ? "⏸" : "▶"); + parser->replay->pause(!is_paused); + }); +} + +void VideoWidget::setPosition(int value) { + time_label->setText(formatTime(value)); + parser->seekTo(value); } void VideoWidget::rangeChanged(double min, double max) { @@ -77,6 +84,7 @@ void VideoWidget::rangeChanged(double min, double max) { } time_label->setText(formatTime(min)); total_time_label->setText(formatTime(max)); + slider->setMinimum(min); slider->setMaximum(max); slider->setValue(parser->currentSec()); } @@ -88,3 +96,17 @@ void VideoWidget::updateState() { slider->setValue(current_sec); } } + +// Slider +// TODO: show timeline bar like what replay did. +Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { +} + +void Slider::mousePressEvent(QMouseEvent *e) { + QSlider::mousePressEvent(e); + if (e->button() == Qt::LeftButton && !isSliderDown()) { + int value = minimum() + ((maximum() - minimum()) * e->x()) / width(); + setValue(value); + emit setPosition(value); + } +} diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 813516e78f..a3605459e0 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -6,6 +6,17 @@ #include "selfdrive/ui/qt/widgets/cameraview.h" +class Slider : public QSlider { + Q_OBJECT + +public: + Slider(QWidget *parent); + void mousePressEvent(QMouseEvent* e) override; + +signals: + void setPosition(int value); +}; + class VideoWidget : public QWidget { Q_OBJECT @@ -15,8 +26,9 @@ public: protected: void rangeChanged(double min, double max); void updateState(); + void setPosition(int value); CameraViewWidget *cam_widget; QLabel *time_label, *total_time_label; - QSlider *slider; + Slider *slider; }; diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index b64e87a03e..80e58b47a3 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -325,6 +325,8 @@ void Replay::startStream(const Segment *cur_segment) { } void Replay::publishMessage(const Event *e) { + if (event_filter && event_filter(e, filter_opaque)) return; + if (sm == nullptr) { auto bytes = e->bytes(); int ret = pm->send(sockets_[e->which], (capnp::byte *)bytes.begin(), bytes.size()); diff --git a/tools/replay/replay.h b/tools/replay/replay.h index aa0bbc33e7..725bd1a27e 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -32,6 +32,7 @@ enum class FindFlag { }; enum class TimelineType { None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserFlag }; +typedef bool (*replayEventFilter)(const Event *, void *); class Replay : public QObject { Q_OBJECT @@ -47,6 +48,13 @@ public: void seekToFlag(FindFlag flag); void seekTo(double seconds, bool relative); inline bool isPaused() const { return paused_; } + // the filter is called in streaming thread.try to return quickly from it to avoid blocking streaming. + // the filter function must return true if the event should be filtered. + // otherwise it must return false. + inline void installEventFilter(replayEventFilter filter, void *opaque) { + filter_opaque = opaque; + event_filter = filter; + } inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } @@ -119,4 +127,6 @@ protected: std::set allow_list; std::string car_fingerprint_; float speed_ = 1.0; + replayEventFilter event_filter = nullptr; + void *filter_opaque = nullptr; }; From e7805eb5c54d336a93af730594ef1ccc725dad82 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 6 Oct 2022 15:24:52 -0700 Subject: [PATCH 093/178] FPv2: fixed fingerprint overrides query result (#25990) * query FW versions if fixed (override after) * skip here * also skip here * and here --- scripts/launch_corolla.sh | 1 + selfdrive/car/car_helpers.py | 2 +- selfdrive/test/profiling/profiler.py | 1 + tools/sim/launch_openpilot.sh | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/launch_corolla.sh b/scripts/launch_corolla.sh index 0801938e71..146fbacf0a 100755 --- a/scripts/launch_corolla.sh +++ b/scripts/launch_corolla.sh @@ -3,4 +3,5 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" export FINGERPRINT="TOYOTA COROLLA TSS2 2019" +export SKIP_FW_QUERY="1" $DIR/../launch_openpilot.sh diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 273364071b..e1c299d5c3 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -81,7 +81,7 @@ def fingerprint(logcan, sendcan): skip_fw_query = os.environ.get('SKIP_FW_QUERY', False) ecu_rx_addrs = set() - if not fixed_fingerprint and not skip_fw_query: + if not skip_fw_query: # Vin query only reliably works through OBDII bus = 1 diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 91226fc577..732a69eebd 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -53,6 +53,7 @@ def profile(proc, func, car='toyota'): msgs = list(LogReader(rlog_url)) * int(os.getenv("LOOP", "1")) os.environ['FINGERPRINT'] = fingerprint + os.environ['SKIP_FW_QUERY'] = "1" os.environ['REPLAY'] = "1" def run(sm, pm, can_sock): diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh index 15f45b4cc2..adabc40c2e 100755 --- a/tools/sim/launch_openpilot.sh +++ b/tools/sim/launch_openpilot.sh @@ -3,6 +3,7 @@ export PASSIVE="0" export NOBOARD="1" export SIMULATION="1" +export SKIP_FW_QUERY="1" export FINGERPRINT="HONDA CIVIC 2016" export BLOCK="camerad,loggerd,encoderd" From cf1e978ad1e1bd339a3d0b43b809918036df6703 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 6 Oct 2022 15:56:41 -0700 Subject: [PATCH 094/178] Fingerprinting: log if using cache (#25989) * Log if using cache or not * log in the same log message --- selfdrive/car/car_helpers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index e1c299d5c3..4a8fd5fbd9 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -95,16 +95,19 @@ def fingerprint(logcan, sendcan): cloudlog.warning("Using cached CarParams") vin, vin_rx_addr = cached_params.carVin, 0 car_fw = list(cached_params.carFw) + cached = True else: cloudlog.warning("Getting VIN & FW versions") vin_rx_addr, vin = get_vin(logcan, sendcan, bus) ecu_rx_addrs = get_present_ecus(logcan, sendcan) car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs) + cached = False exact_fw_match, fw_candidates = match_fw_to_car(car_fw) else: vin, vin_rx_addr = VIN_UNKNOWN, 0 exact_fw_match, fw_candidates, car_fw = True, set(), [] + cached = False if not is_valid_vin(vin): cloudlog.event("Malformed VIN", vin=vin, error=True) @@ -165,7 +168,7 @@ def fingerprint(logcan, sendcan): car_fingerprint = fixed_fingerprint source = car.CarParams.FingerprintSource.fixed - cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, + cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, cached=cached, fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, error=True) return car_fingerprint, finger, vin, car_fw, source, exact_match From 01d05f66fe5a189209538650dce319b2f7e192ee Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 6 Oct 2022 16:46:15 -0700 Subject: [PATCH 095/178] auto-detect pigeon or quectel (#25991) * auto-detect pigeon or quectel * persistent * fix sim * fix process replay * fix locationd unit tests * fix that Co-authored-by: Comma Device --- common/params.cc | 1 + common/params.h | 4 ++-- common/params_pyx.pyx | 6 +++--- selfdrive/locationd/laikad.py | 2 +- selfdrive/locationd/locationd.cc | 6 +++--- selfdrive/locationd/test/test_locationd.py | 1 + selfdrive/sensord/pigeond.py | 9 +++++++-- selfdrive/test/process_replay/process_replay.py | 1 + tools/sim/bridge.py | 7 +++++-- 9 files changed, 24 insertions(+), 13 deletions(-) diff --git a/common/params.cc b/common/params.cc index d4c5cd7caf..a23c0e003e 100644 --- a/common/params.cc +++ b/common/params.cc @@ -166,6 +166,7 @@ std::unordered_map keys = { {"TermsVersion", PERSISTENT}, {"Timezone", PERSISTENT}, {"TrainingVersion", PERSISTENT}, + {"UbloxAvailable", PERSISTENT}, {"UpdateAvailable", CLEAR_ON_MANAGER_START}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START}, {"UpdaterState", CLEAR_ON_MANAGER_START}, diff --git a/common/params.h b/common/params.h index 7758a015f6..aecb3ee471 100644 --- a/common/params.h +++ b/common/params.h @@ -29,8 +29,8 @@ public: // helpers for reading values std::string get(const std::string &key, bool block = false); - inline bool getBool(const std::string &key) { - return get(key) == "1"; + inline bool getBool(const std::string &key, bool block = false) { + return get(key, block) == "1"; } std::map readAll(); diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx index bbddda46ea..9d8933609f 100755 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -16,7 +16,7 @@ cdef extern from "common/params.h": cdef cppclass c_Params "Params": c_Params(string) nogil string get(string, bool) nogil - bool getBool(string) nogil + bool getBool(string, bool) nogil int remove(string) nogil int put(string, string) nogil int putBool(string, bool) nogil @@ -68,11 +68,11 @@ cdef class Params: return val if encoding is None else val.decode(encoding) - def get_bool(self, key): + def get_bool(self, key, bool block=False): cdef string k = self.check_key(key) cdef bool r with nogil: - r = self.p.getBool(k) + r = self.p.getBool(k, block) return r def put(self, key, dat): diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index c7f2d2ceac..c4fdfb9871 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -328,7 +328,7 @@ class EphemerisSourceType(IntEnum): def main(sm=None, pm=None): - use_qcom = os.path.isfile("/persist/comma/use-quectel-rawgps") + use_qcom = not Params().get_bool("UbloxAvailable", block=True) if use_qcom: raw_gnss_socket = "qcomGnss" else: diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index ac340fb4aa..9608f4003b 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -492,10 +492,10 @@ void Localizer::determine_gps_mode(double current_time) { int Localizer::locationd_thread() { const char* gps_location_socket; - if (util::file_exists("/persist/comma/use-quectel-rawgps")) { - gps_location_socket = "gpsLocation"; - } else { + if (Params().getBool("UbloxAvailable", true)) { gps_location_socket = "gpsLocationExternal"; + } else { + gps_location_socket = "gpsLocation"; } const std::initializer_list service_list = {gps_location_socket, "cameraOdometry", "liveCalibration", "carState", "carParams", diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index 8841b3e67c..e32861cfae 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -22,6 +22,7 @@ class TestLocationdProc(unittest.TestCase): self.pm = messaging.PubMaster(self.LLD_MSGS) + Params().put_bool("UbloxAvailable", True) managed_processes['locationd'].prepare() managed_processes['locationd'].start() diff --git a/selfdrive/sensord/pigeond.py b/selfdrive/sensord/pigeond.py index f47cefed6c..5fe120c061 100755 --- a/selfdrive/sensord/pigeond.py +++ b/selfdrive/sensord/pigeond.py @@ -116,7 +116,7 @@ class TTYPigeon(): time.sleep(0.001) -def initialize_pigeon(pigeon: TTYPigeon) -> None: +def initialize_pigeon(pigeon: TTYPigeon) -> bool: # try initializing a few times for _ in range(10): try: @@ -198,6 +198,10 @@ def initialize_pigeon(pigeon: TTYPigeon) -> None: break except TimeoutError: cloudlog.warning("Initialization failed, trying again!") + else: + cloudlog.warning("Failed to initialize pigeon") + return False + return True def deinitialize_and_exit(pigeon: Optional[TTYPigeon]): cloudlog.warning("Storing almanac in ublox flash") @@ -236,7 +240,8 @@ def main(): time.sleep(0.5) pigeon = TTYPigeon() - initialize_pigeon(pigeon) + r = initialize_pigeon(pigeon) + Params().put_bool("UbloxAvailable", r) # start receiving data while True: diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 10084bff9f..9d37be4a56 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -417,6 +417,7 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): params.put_bool("DisengageOnAccelerator", True) params.put_bool("WideCameraOnly", False) params.put_bool("DisableLogging", False) + params.put_bool("UbloxAvailable", True) os.environ["NO_RADAR_SLEEP"] = "1" os.environ["REPLAY"] = "1" diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index c400eb93f5..e436e92292 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -244,11 +244,13 @@ class CarlaBridge: def __init__(self, arguments): set_params_enabled() + self.params = Params() + msg = messaging.new_message('liveCalibration') msg.liveCalibration.validBlocks = 20 msg.liveCalibration.rpyCalib = [0.0, 0.0, 0.0] - Params().put("CalibrationParams", msg.to_bytes()) - Params().put_bool("WideCameraOnly", not arguments.dual_camera) + self.params.put("CalibrationParams", msg.to_bytes()) + self.params.put_bool("WideCameraOnly", not arguments.dual_camera) self._args = arguments self._carla_objects = [] @@ -363,6 +365,7 @@ class CarlaBridge: gps_bp = blueprint_library.find('sensor.other.gnss') gps = world.spawn_actor(gps_bp, transform, attach_to=vehicle) gps.listen(lambda gps: gps_callback(gps, vehicle_state)) + self.params.put_bool("UbloxAvailable", True) self._carla_objects.extend([imu, gps]) # launch fake car threads From 7c49c44c4ee2e846a275dab1cb5141009d55318e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 6 Oct 2022 16:55:14 -0700 Subject: [PATCH 096/178] fix speed of mock car with qcom gps (#25993) * fix speed of mock car with qcom gps * typo --- selfdrive/car/mock/interface.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index 36062da1d1..a3194cd79e 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -20,8 +20,7 @@ class CarInterface(CarInterfaceBase): cloudlog.debug("Using Mock Car Interface") - self.gyro = messaging.sub_sock('gyroscope') - self.gps = messaging.sub_sock('gpsLocationExternal') + self.sm = messaging.SubMaster(['gyroscope', 'gpsLocation', 'gpsLocationExternal']) self.speed = 0. self.prev_speed = 0. @@ -45,15 +44,16 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): + self.sm.update(0) + # get basic data from phone and gps since CAN isn't connected - gyro_sensor = messaging.recv_sock(self.gyro) - if gyro_sensor is not None: - self.yaw_rate_meas = -gyro_sensor.gyroscope.gyroUncalibrated.v[0] + if self.sm.updated['gyroscope']: + self.yaw_rate_meas = -self.sm['gyroscope'].gyroUncalibrated.v[0] - gps = messaging.recv_sock(self.gps) - if gps is not None: + gps_sock = 'gpsLocationExternal' if self.sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' + if self.sm.updated[gps_sock]: self.prev_speed = self.speed - self.speed = gps.gpsLocationExternal.speed + self.speed = self.sm[gps_sock].speed # create message ret = car.CarState.new_message() From 1ecf6f351c35de24affbb1e2cb5675aea1a36f10 Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Fri, 7 Oct 2022 00:16:18 -0700 Subject: [PATCH 097/178] Divide by 0 bug fix lateral planner (#25995) * Divide by speed correctly * Update * Update lateral_planner.py * Update ref_commit --- selfdrive/controls/lib/lateral_planner.py | 15 +++++++++------ selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 48c98e7350..10e3edbebe 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -75,10 +75,10 @@ class LateralPlanner: y_pts, heading_pts, yaw_rate_pts) - # init state for next - # mpc.u_sol is the desired curvature rate given x0 curv state. - # with x0[3] = measured_curvature, this would be the actual desired rate. - # instead, interpolate x_sol so that x0[3] is the desired curvature for lat_control. + # init state for next iteration + # mpc.u_sol is the desired second derivative of psi given x0 curv state. + # with x0[3] = measured_yaw_rate, this would be the actual desired yaw rate. + # instead, interpolate x_sol so that x0[3] is the desired yaw rate for lat_control. self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1], self.lat_mpc.x_sol[:, 3]) # Check for infeasible MPC solution @@ -105,8 +105,11 @@ class LateralPlanner: lateralPlan.modelMonoTime = sm.logMonoTime['modelV2'] lateralPlan.dPathPoints = self.y_pts.tolist() lateralPlan.psis = self.lat_mpc.x_sol[0:CONTROL_N, 2].tolist() - lateralPlan.curvatures = (self.lat_mpc.x_sol[0:CONTROL_N, 3]/sm['carState'].vEgo).tolist() - lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] + + # clip speed for curv calculation at 1m/s, to prevent low speed extremes + clipped_speed = max(1.0, sm['carState'].vEgo) + lateralPlan.curvatures = (self.lat_mpc.x_sol[0:CONTROL_N, 3]/clipped_speed).tolist() + lateralPlan.curvatureRates = [float(x/clipped_speed) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] lateralPlan.mpcSolutionValid = bool(plan_solution_valid) lateralPlan.solverExecutionTime = self.lat_mpc.solve_time diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 0e3cdf797f..78367235e6 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -e0ffcae8def2fd9c82c547d1f257d4f06a48a3c3 +f9536e41a6a160bdaa29d42bb164b0e4033857e5 From cfabae28e7554c12c0fcb4beab8b7b3353140316 Mon Sep 17 00:00:00 2001 From: pbkompasz <47194071+pbkompasz@users.noreply.github.com> Date: Fri, 7 Oct 2022 18:41:14 +0300 Subject: [PATCH 098/178] README -> README.md (#26005) --- tools/cabana/{README => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/cabana/{README => README.md} (100%) diff --git a/tools/cabana/README b/tools/cabana/README.md similarity index 100% rename from tools/cabana/README rename to tools/cabana/README.md From 1f5187892f0854bbc6a7d7799fa0d9ca2284714a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Oct 2022 10:39:11 -0700 Subject: [PATCH 099/178] safer modem manager commands (#25999) Co-authored-by: Comma Device --- selfdrive/sensord/rawgps/rawgpsd.py | 4 ++-- selfdrive/sensord/rawgps/test_rawgps.py | 2 +- system/hardware/tici/hardware.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index 5149ab6473..c430acc5e5 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -88,7 +88,7 @@ def try_setup_logs(diag, log_types): def mmcli(cmd: str) -> None: for _ in range(5): try: - subprocess.check_call(f"mmcli -m 0 {cmd}", shell=True) + subprocess.check_call(f"mmcli -m any --timeout 30 {cmd}", shell=True) break except subprocess.CalledProcessError: cloudlog.exception("rawgps.mmcli_command_failed") @@ -151,7 +151,7 @@ def main() -> NoReturn: # wait for ModemManager to come up cloudlog.warning("waiting for modem to come up") while True: - ret = subprocess.call("mmcli -m 0 --location-status", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) + ret = subprocess.call("mmcli -m any --timeout 10 --location-status", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) if ret == 0: break time.sleep(0.1) diff --git a/selfdrive/sensord/rawgps/test_rawgps.py b/selfdrive/sensord/rawgps/test_rawgps.py index f8dd3ad1e5..5bd0833955 100755 --- a/selfdrive/sensord/rawgps/test_rawgps.py +++ b/selfdrive/sensord/rawgps/test_rawgps.py @@ -40,7 +40,7 @@ class TestRawgpsd(unittest.TestCase): time.sleep(s) managed_processes['rawgpsd'].stop() - ls = subprocess.check_output("mmcli -m 0 --location-status --output-json", shell=True, encoding='utf-8') + ls = subprocess.check_output("mmcli -m any --location-status --output-json", shell=True, encoding='utf-8') loc_status = json.loads(ls) assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 340093b604..e2fd20c1be 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -480,7 +480,7 @@ class Tici(HardwareBase): # blue prime config if sim_id.startswith('8901410'): - os.system('mmcli -m 0 --3gpp-set-initial-eps-bearer-settings="apn=Broadband"') + os.system('mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn=Broadband"') def get_networks(self): r = {} From 8dbb25e683039e2cd393b574e0fdaa0c894b1104 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 8 Oct 2022 02:28:09 +0800 Subject: [PATCH 100/178] params: remove all files if call clearAll with the ParamKeyType.ALL key. (#26000) * remove all files in dir * fix test case name --- common/params.cc | 11 +++++++---- common/tests/test_util.cc | 17 +++++++++++++++++ common/util.cc | 16 ++++++++++++++++ common/util.h | 1 + 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/common/params.cc b/common/params.cc index a23c0e003e..155bc88487 100644 --- a/common/params.cc +++ b/common/params.cc @@ -299,10 +299,13 @@ std::map Params::readAll() { void Params::clearAll(ParamKeyType key_type) { FileLock file_lock(params_path + "/.lock"); - std::string path; - for (auto &[key, type] : keys) { - if (type & key_type) { - unlink(getParamPath(key).c_str()); + if (key_type == ALL) { + util::remove_files_in_dir(getParamPath()); + } else { + for (auto &[key, type] : keys) { + if (type & key_type) { + unlink(getParamPath(key).c_str()); + } } } diff --git a/common/tests/test_util.cc b/common/tests/test_util.cc index 25ecf09aa9..b70cc9044a 100644 --- a/common/tests/test_util.cc +++ b/common/tests/test_util.cc @@ -143,3 +143,20 @@ TEST_CASE("util::create_directories") { REQUIRE(util::create_directories("", 0755) == false); } } + +TEST_CASE("util::remove_files_in_dir") { + std::string tmp_dir = "/tmp/test_remove_all_in_dir"; + system("rm /tmp/test_remove_all_in_dir -rf"); + REQUIRE(util::create_directories(tmp_dir, 0755)); + const int tmp_file_cnt = 10; + for (int i = 0; i < tmp_file_cnt; ++i) { + std::string tmp_file = tmp_dir + "/test_XXXXXX"; + int fd = mkstemp((char*)tmp_file.c_str()); + close(fd); + REQUIRE(util::file_exists(tmp_file.c_str())); + } + + REQUIRE(util::read_files_in_dir(tmp_dir).size() == tmp_file_cnt); + util::remove_files_in_dir(tmp_dir); + REQUIRE(util::read_files_in_dir(tmp_dir).empty()); +} diff --git a/common/util.cc b/common/util.cc index 92add63997..b6a8322a27 100644 --- a/common/util.cc +++ b/common/util.cc @@ -97,6 +97,22 @@ std::map read_files_in_dir(const std::string &path) { return ret; } +void remove_files_in_dir(const std::string &path) { + DIR *d = opendir(path.c_str()); + if (!d) return; + + std::string fn; + struct dirent *de = NULL; + while ((de = readdir(d))) { + if (de->d_type != DT_DIR) { + fn = path + "/" + de->d_name; + unlink(fn.c_str()); + } + } + + closedir(d); +} + int write_file(const char* path, const void* data, size_t size, int flags, mode_t mode) { int fd = HANDLE_EINTR(open(path, flags, mode)); if (fd == -1) { diff --git a/common/util.h b/common/util.h index f3a24723b4..e13f4dc130 100644 --- a/common/util.h +++ b/common/util.h @@ -80,6 +80,7 @@ std::string dir_name(std::string const& path); // **** file fhelpers ***** std::string read_file(const std::string& fn); std::map read_files_in_dir(const std::string& path); +void remove_files_in_dir(const std::string& path); int write_file(const char* path, const void* data, size_t size, int flags = O_WRONLY, mode_t mode = 0664); FILE* safe_fopen(const char* filename, const char* mode); From beb0f8ac234d28b72c6267d0b4c55fdf81c817b9 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sat, 8 Oct 2022 02:28:24 +0800 Subject: [PATCH 101/178] cabana: custom slider to indicate the status of engaged/disengaged/alerts (#26003) * timeline * remove todo * cleanup --- tools/cabana/videowidget.cc | 46 ++++++++++++++++++++++++++++++++++++- tools/cabana/videowidget.h | 5 ++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 26fb845340..7edbe5e38f 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include #include #include "tools/cabana/parser.h" @@ -98,8 +100,50 @@ void VideoWidget::updateState() { } // Slider -// TODO: show timeline bar like what replay did. Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { + QTimer *timer = new QTimer(this); + timer->setInterval(2000); + timer->callOnTimeout([this]() { timeline = parser->replay->getTimeline(); }); + timer->start(); +} + +void Slider::paintEvent(QPaintEvent *ev) { + auto getPaintRange = [this](double begin, double end) -> std::pair { + double total_sec = maximum() - minimum(); + int start_pos = ((std::max((double)minimum(), (double)begin) - minimum()) / total_sec) * width(); + int end_pos = ((std::min((double)maximum(), (double)end) - minimum()) / total_sec) * width(); + return {start_pos, end_pos}; + }; + + QPainter p(this); + const int v_margin = 2; + p.fillRect(rect().adjusted(0, v_margin, 0, -v_margin), QColor(0, 0, 128)); + + for (auto [begin, end, type] : timeline) { + if (begin > maximum() || end < minimum()) continue; + + if (type == TimelineType::Engaged) { + auto [start_pos, end_pos] = getPaintRange(begin, end); + p.fillRect(QRect(start_pos, v_margin, end_pos - start_pos, height() - v_margin * 2), QColor(0, 135, 0)); + } + } + for (auto [begin, end, type] : timeline) { + if (type == TimelineType::Engaged || begin > maximum() || end < minimum()) continue; + + auto [start_pos, end_pos] = getPaintRange(begin, end); + if (type == TimelineType::UserFlag) { + p.fillRect(QRect(start_pos, height() - v_margin - 3, end_pos - start_pos, 3), Qt::white); + } else { + QColor color(Qt::green); + if (type != TimelineType::AlertInfo) + color = type == TimelineType::AlertWarning ? Qt::yellow : Qt::red; + + p.fillRect(QRect(start_pos, height() - v_margin - 3, end_pos - start_pos, 3), color); + } + } + p.setPen(QPen(Qt::black, 2)); + qreal x = width() * ((value() - minimum()) / double(maximum() - minimum())); + p.drawLine(QPointF{x, 0.}, QPointF{x, (qreal)height()}); } void Slider::mousePressEvent(QMouseEvent *e) { diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index a3605459e0..188456ecbe 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -5,6 +5,7 @@ #include #include "selfdrive/ui/qt/widgets/cameraview.h" +#include "tools/replay/replay.h" class Slider : public QSlider { Q_OBJECT @@ -15,6 +16,10 @@ public: signals: void setPosition(int value); + +private: + void paintEvent(QPaintEvent *ev) override; + std::vector> timeline; }; class VideoWidget : public QWidget { From 46e741c7cc652a9b1b266c0881ef6f9c30f5a226 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Fri, 7 Oct 2022 15:50:35 -0400 Subject: [PATCH 102/178] VW MNB: Add EPS config script support (#26007) --- selfdrive/debug/vw_mqb_config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/debug/vw_mqb_config.py b/selfdrive/debug/vw_mqb_config.py index c1068bf067..8c4dbc55ee 100755 --- a/selfdrive/debug/vw_mqb_config.py +++ b/selfdrive/debug/vw_mqb_config.py @@ -70,8 +70,8 @@ if __name__ == "__main__": coding_variant, current_coding_array, coding_byte, coding_bit = None, None, 0, 0 coding_length = len(current_coding) - # EV_SteerAssisMQB covers the majority of MQB racks (EPS_MQB_ZFLS) - if odx_file == "EV_SteerAssisMQB\x00": + # EV_SteerAssisMQB/MNB cover the majority of MQB racks (EPS_MQB_ZFLS) + if odx_file in ("EV_SteerAssisMQB\x00", "EV_SteerAssisMNB\x00"): coding_variant = "ZF" coding_byte = 0 coding_bit = 4 @@ -111,7 +111,7 @@ if __name__ == "__main__": if args.action in ["enable", "disable"]: print("\nAttempting configuration update") - assert(coding_variant in ("ZF", "APA")) + assert(coding_variant in ("ZF", "APA")) # ZF EPS config coding length can be anywhere from 1 to 4 bytes, but the # bit we care about is always in the same place in the first byte if args.action == "enable": From 8cee561dbfcb6e45f7ecd51adb0df67f4ebba908 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 7 Oct 2022 13:51:55 -0700 Subject: [PATCH 103/178] tombstoned: remove android code --- selfdrive/tombstoned.py | 43 +---------------------------------------- 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index 0045e0766c..61a575f141 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -62,47 +62,6 @@ def get_tombstones(): return files -def report_tombstone_android(fn): - f_size = os.path.getsize(fn) - if f_size > MAX_SIZE: - cloudlog.error(f"Tombstone {fn} too big, {f_size}. Skipping...") - return - - with open(fn, encoding='ISO-8859-1') as f: - contents = f.read() - - message = " ".join(contents.split('\n')[5:7]) - - # Cut off pid/tid, since that varies per run - name_idx = message.find('name') - if name_idx >= 0: - message = message[name_idx:] - - executable = "" - start_exe_idx = message.find('>>> ') - end_exe_idx = message.find(' <<<') - if start_exe_idx >= 0 and end_exe_idx >= 0: - executable = message[start_exe_idx + 4:end_exe_idx] - - # Cut off fault addr - fault_idx = message.find(', fault addr') - if fault_idx >= 0: - message = message[:fault_idx] - - sentry.report_tombstone(fn, message, contents) - - # Copy crashlog to upload folder - clean_path = executable.replace('./', '').replace('/', '_') - date = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S") - - new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] - - crashlog_dir = os.path.join(ROOT, "crash") - mkdirs_exists_ok(crashlog_dir) - - shutil.copy(fn, os.path.join(crashlog_dir, new_fn)) - - def report_tombstone_apport(fn): f_size = os.path.getsize(fn) if f_size > MAX_SIZE: @@ -199,7 +158,7 @@ def main() -> NoReturn: if fn.endswith(".crash"): report_tombstone_apport(fn) else: - report_tombstone_android(fn) + cloudlog.error(f"unknown crash type: {fn}") except Exception: cloudlog.exception(f"Error reporting tombstone {fn}") From 6ce511cc605782c25d45c66b55f859caaf7ce516 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 7 Oct 2022 16:12:58 -0700 Subject: [PATCH 104/178] GM: use ECM brake-pressed threshold (#25970) * This brake position value disengages stock ACC, use it to avoid controls mismatch. 2016-2017 Volt will hit this threshold and disengage, must install new design of brake pedal retaining clip, TSB 16-NA-147. * 80 hz * comment * bump panda * update refs * bump panda * bump panda * bump panda * bump panda to master Co-authored-by: qadmus <42746943+qadmus@users.noreply.github.com> --- panda | 2 +- selfdrive/car/gm/carstate.py | 13 ++++++++----- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/panda b/panda index 3334dc21f5..1303af2db2 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 3334dc21f5c55007c5a754dfd8ee5d642be3e2bb +Subproject commit 1303af2db29a72eee180b10c6097fa5b19c29207 diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 0bba1d29b8..f96a234dbd 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -40,9 +40,12 @@ class CarState(CarStateBase): else: ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None)) - # Brake pedal's potentiometer returns near-zero reading even when pedal is not pressed. - ret.brake = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] / 0xd0 - ret.brakePressed = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] >= 10 + # Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe + # that the brake is being intermittently pressed without user interaction. + # To avoid a cruise fault we need to match the ECM's brake pressed signal and threshold + # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf + ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"] / 0xd0 + ret.brakePressed = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"] >= 8 # Regen braking is braking if self.CP.transmissionType == TransmissionType.direct: @@ -100,7 +103,7 @@ class CarState(CarStateBase): def get_can_parser(CP): signals = [ # sig_name, sig_address - ("BrakePedalPosition", "EBCMBrakePedalPosition"), + ("BrakePedalPos", "ECMAcceleratorPos"), ("FrontLeftDoor", "BCMDoorBeltStatus"), ("FrontRightDoor", "BCMDoorBeltStatus"), ("RearLeftDoor", "BCMDoorBeltStatus"), @@ -141,7 +144,7 @@ class CarState(CarStateBase): ("ASCMSteeringButton", 33), ("ECMEngineStatus", 100), ("PSCMSteeringAngle", 100), - ("EBCMBrakePedalPosition", 100), + ("ECMAcceleratorPos", 80), ] if CP.transmissionType == TransmissionType.direct: diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 78367235e6..a89b4c2d71 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -f9536e41a6a160bdaa29d42bb164b0e4033857e5 +338c24158bb28952dcd1554ea91734b4281e2fed \ No newline at end of file From 6c5693e965b9c63f8678f52b9e9b5abe35f23feb Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Fri, 7 Oct 2022 17:22:42 -0700 Subject: [PATCH 105/178] faster rocket launcher model (#26009) * cache tokens 1456d261-d232-4654-8885-4d9fde883894/440 e63ab895-2222-4abd-a9a5-af86bb70e260/700 * udpate ref commit * bump tinygrad to master --- selfdrive/modeld/models/driving.h | 2 +- selfdrive/modeld/models/supercombo.onnx | 4 ++-- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- tinygrad_repo | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index 90767384eb..8bb84d0245 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -16,7 +16,7 @@ #include "selfdrive/modeld/models/commonmodel.h" #include "selfdrive/modeld/runners/run.h" -constexpr int FEATURE_LEN = 2048; +constexpr int FEATURE_LEN = 128; constexpr int HISTORY_BUFFER_LEN = 99; constexpr int DESIRE_LEN = 8; constexpr int DESIRE_PRED_LEN = 4; diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 37edc36c02..aee0ac37ff 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30f30bc1251c03db135564ecbf7dc0bc96cbb07be0ebd3691edd8d555dc087fa -size 58539693 +oid sha256:c4d37af666344af6bb218e0b939b1152ad3784c15ac79e37bcf0124643c8286a +size 58539563 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 9727ef3d17..2446ec061d 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -008c0a622b7471c6234690d668f76bcb5dc8d999 +c171250d2cc013b3eca1cda4fb62f3d0dda28d4d diff --git a/tinygrad_repo b/tinygrad_repo index 2993dfe921..46f6db7522 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 2993dfe9219089bd1464741757f0c2ad67fe6a2e +Subproject commit 46f6db75228cb7744a3a0a87616bfffeffa93658 From 57a82ced285518cad129ec61814aab48bf51910e Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Fri, 7 Oct 2022 17:27:09 -0700 Subject: [PATCH 106/178] rawgpsd: publish ephemerides (#25931) * add svpoly parsing * Publish poly * add source check * add safety check for invalid gpsWeek values * address PR comments * add qcom ephemeris source type * bump cereal and laika Co-authored-by: Kurt Nistelberger --- cereal | 2 +- laika_repo | 2 +- selfdrive/locationd/laikad.py | 52 +++++++++++++++++++++++------ selfdrive/sensord/rawgps/rawgpsd.py | 30 ++++++++++++++--- selfdrive/sensord/rawgps/structs.py | 41 +++++++++++++++++++++++ 5 files changed, 111 insertions(+), 16 deletions(-) diff --git a/cereal b/cereal index 5ba96b6ded..b29717c4c3 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 5ba96b6ded57bcd91e60140ce0036f61370f8512 +Subproject commit b29717c4c328d5cf34d46f682f25267150f82637 diff --git a/laika_repo b/laika_repo index c8bc1fa01b..e1049cde0a 160000 --- a/laika_repo +++ b/laika_repo @@ -1 +1 @@ -Subproject commit c8bc1fa01be9f22592efb991ee52d3d965d21968 +Subproject commit e1049cde0a68f7d4a70b1ebd76befdc0e163ad55 diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index c4fdfb9871..2769f394c5 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -16,7 +16,7 @@ from common.params import Params, put_nonblocking from laika import AstroDog from laika.constants import SECS_IN_HR, SECS_IN_MIN from laika.downloader import DownloadFailed -from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem +from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem, parse_qcom_ephem from laika.gps_time import GPSTime from laika.helpers import ConstellationId from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox, read_raw_qcom @@ -61,6 +61,7 @@ class Laikad: self.last_pos_fix = [] self.last_pos_residual = [] self.last_pos_fix_t = None + self.gps_week = None self.use_qcom = use_qcom def load_cache(self): @@ -107,11 +108,11 @@ class Laikad: return self.last_pos_fix def is_good_report(self, gnss_msg): - if gnss_msg.which == 'drMeasurementReport' and self.use_qcom: + if gnss_msg.which() == 'drMeasurementReport' and self.use_qcom: constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) # TODO support GLONASS return constellation_id in [ConstellationId.GPS, ConstellationId.SBAS] - elif gnss_msg.which == 'measurementReport' and not self.use_qcom: + elif gnss_msg.which() == 'measurementReport' and not self.use_qcom: return True else: return False @@ -129,9 +130,28 @@ class Laikad: new_meas = read_raw_ublox(report) return week, tow, new_meas + def is_ephemeris(self, gnss_msg): + if self.use_qcom: + return gnss_msg.which() == 'drSvPoly' + else: + return gnss_msg.which() == 'ephemeris' + + def read_ephemeris(self, gnss_msg): + # TODO this only works on GLONASS + if self.use_qcom: + # TODO this is not robust to gps week rollover + if self.gps_week is None: + return + ephem = parse_qcom_ephem(gnss_msg.drSvPoly, self.gps_week) + else: + ephem = convert_ublox_ephem(gnss_msg.ephemeris) + self.astro_dog.add_navs({ephem.prn: [ephem]}) + self.cache_ephemeris(t=ephem.epoch) + def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False): if self.is_good_report(gnss_msg): week, tow, new_meas = self.read_report(gnss_msg) + self.gps_week = week t = gnss_mono_time * 1e-9 if week > 0: @@ -172,12 +192,10 @@ class Laikad: "correctedMeasurements": meas_msgs } return dat - # TODO this only works on GLONASS, qcom needs live ephemeris parsing too - elif gnss_msg.which == 'ephemeris': - ephem = convert_ublox_ephem(gnss_msg.ephemeris) - self.astro_dog.add_navs({ephem.prn: [ephem]}) - self.cache_ephemeris(t=ephem.epoch) - #elif gnss_msg.which == 'ionoData': + elif self.is_ephemeris(gnss_msg): + self.read_ephemeris(gnss_msg) + + #elif gnss_msg.which() == 'ionoData': # todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them. def update_localizer(self, est_pos, t: float, measurements: List[GNSSMeasurement]): @@ -265,9 +283,11 @@ def create_measurement_msg(meas: GNSSMeasurement): c.satVel = meas.sat_vel.tolist() ephem = meas.sat_ephemeris assert ephem is not None + week, time_of_week = -1, -1 if ephem.eph_type == EphemerisType.NAV: source_type = EphemerisSourceType.nav - week, time_of_week = -1, -1 + elif ephem.eph_type == EphemerisType.QCOM_POLY: + source_type = EphemerisSourceType.qcom else: assert ephem.file_epoch is not None week = ephem.file_epoch.week @@ -325,6 +345,7 @@ class EphemerisSourceType(IntEnum): nav = 0 nasaUltraRapid = 1 glonassIacUltraRapid = 2 + qcom = 3 def main(sm=None, pm=None): @@ -348,6 +369,17 @@ def main(sm=None, pm=None): if sm.updated[raw_gnss_socket]: gnss_msg = sm[raw_gnss_socket] + + # TODO: Understand and use remaining unknown constellations + if gnss_msg.which() == "drMeasurementReport": + if getattr(gnss_msg, gnss_msg.which()).source not in ['glonass', 'gps', 'beidou', 'sbas']: + continue + + if getattr(gnss_msg, gnss_msg.which()).gpsWeek > np.iinfo(np.int16).max: + # gpsWeek 65535 is received rarely from quectel, this cannot be + # passed to GnssMeasurements's gpsWeek (Int16) + continue + msg = laikad.process_gnss_msg(gnss_msg, sm.logMonoTime[raw_gnss_socket], block=replay) if msg is not None: pm.send('gnssMeasurements', msg) diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index c430acc5e5..c68e7aff99 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -14,12 +14,13 @@ import cereal.messaging as messaging from laika.gps_time import GPSTime from system.swaglog import cloudlog from selfdrive.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv -from selfdrive.sensord.rawgps.structs import (dict_unpacker, position_report, +from selfdrive.sensord.rawgps.structs import (dict_unpacker, position_report, relist, gps_measurement_report, gps_measurement_report_sv, glonass_measurement_report, glonass_measurement_report_sv, - oemdre_measurement_report, oemdre_measurement_report_sv, + oemdre_measurement_report, oemdre_measurement_report_sv, oemdre_svpoly_report, LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT, - LOG_GNSS_POSITION_REPORT, LOG_GNSS_OEMDRE_MEASUREMENT_REPORT) + LOG_GNSS_POSITION_REPORT, LOG_GNSS_OEMDRE_MEASUREMENT_REPORT, + LOG_GNSS_OEMDRE_SVPOLY_REPORT) DEBUG = int(os.getenv("DEBUG", "0"))==1 @@ -28,6 +29,7 @@ LOG_TYPES = [ LOG_GNSS_GLONASS_MEASUREMENT_REPORT, LOG_GNSS_OEMDRE_MEASUREMENT_REPORT, LOG_GNSS_POSITION_REPORT, + LOG_GNSS_OEMDRE_SVPOLY_REPORT, ] @@ -146,6 +148,9 @@ def main() -> NoReturn: unpack_oemdre_meas, size_oemdre_meas = dict_unpacker(oemdre_measurement_report, True) unpack_oemdre_meas_sv, size_oemdre_meas_sv = dict_unpacker(oemdre_measurement_report_sv, True) + unpack_svpoly, _ = dict_unpacker(oemdre_svpoly_report, True) + unpack_position, _ = dict_unpacker(position_report) + unpack_position, _ = dict_unpacker(position_report) # wait for ModemManager to come up @@ -258,7 +263,24 @@ def main() -> NoReturn: pm.send('gpsLocation', msg) - if log_type in [LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT]: + elif log_type == LOG_GNSS_OEMDRE_SVPOLY_REPORT: + msg = messaging.new_message('qcomGnss') + dat = unpack_svpoly(log_payload) + dat = relist(dat) + gnss = msg.qcomGnss + gnss.logTs = log_time + gnss.init('drSvPoly') + poly = gnss.drSvPoly + for k,v in dat.items(): + if k == "version": + assert v == 2 + elif k == "flags": + pass + else: + setattr(poly, k, v) + pm.send('qcomGnss', msg) + + elif log_type in [LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT]: msg = messaging.new_message('qcomGnss') gnss = msg.qcomGnss diff --git a/selfdrive/sensord/rawgps/structs.py b/selfdrive/sensord/rawgps/structs.py index 4bc9eca875..97e3d3d605 100644 --- a/selfdrive/sensord/rawgps/structs.py +++ b/selfdrive/sensord/rawgps/structs.py @@ -56,6 +56,29 @@ oemdre_measurement_report = """ uint8_t source; """ +oemdre_svpoly_report = """ + uint8_t version; + uint16_t sv_id; + int8_t frequency_index; + uint8_t flags; + uint16_t iode; + double t0; + double xyz0[3]; + double xyzN[9]; + float other[4]; + float position_uncertainty; + float iono_delay; + float iono_dot; + float sbas_iono_delay; + float sbas_iono_dot; + float tropo_delay; + float elevation; + float elevation_dot; + float elevation_uncertainty; + double velocity_coeff[12]; +""" + + oemdre_measurement_report_sv = """ uint8_t sv_id; uint8_t unkn; @@ -311,3 +334,21 @@ def dict_unpacker(ss, camelcase = False): nams = [name_to_camelcase(x) for x in nams] sz = calcsize(st) return lambda x: dict(zip(nams, unpack_from(st, x))), sz + +def relist(dat): + list_keys = set() + for key in dat.keys(): + if '[' in key: + list_keys.add(key.split('[')[0]) + list_dict = {} + for list_key in list_keys: + list_dict[list_key] = [] + i = 0 + while True: + key = list_key + f'[{i}]' + if key not in dat: + break + list_dict[list_key].append(dat[key]) + del dat[key] + i += 1 + return {**dat, **list_dict} From fb074378194db28067dbb77e8cd15db6ab5fd882 Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Fri, 7 Oct 2022 19:15:04 -0700 Subject: [PATCH 107/178] Increase low speed jerk cost (#26008) * Increase low speed jerk cost * Update planner weight * Update ref_commit * Update lateral_planner.py * cleanup and refactor * Update ref_commit --- .../controls/lib/lateral_mpc_lib/lat_mpc.py | 16 ++++----- selfdrive/controls/lib/lateral_planner.py | 35 +++++++++++-------- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index 07efad73c9..0cbd576341 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -20,6 +20,7 @@ P_DIM = 2 N = 16 COST_E_DIM = 3 COST_DIM = COST_E_DIM + 1 +SPEED_OFFSET = 10.0 MODEL_NAME = 'lat' ACADOS_SOLVER_TYPE = 'SQP_RTI' @@ -88,14 +89,13 @@ def gen_lat_ocp(): ocp.cost.yref = np.zeros((COST_DIM, )) ocp.cost.yref_e = np.zeros((COST_E_DIM, )) - # TODO hacky weights to keep behavior the same ocp.model.cost_y_expr = vertcat(y_ego, - ((v_ego + 5.0) * psi_ego), - ((v_ego + 5.0) * psi_rate_ego), - ((v_ego + 5.0) * psi_rate_ego_dot)) + ((v_ego + SPEED_OFFSET) * psi_ego), + ((v_ego + SPEED_OFFSET) * psi_rate_ego), + ((v_ego + SPEED_OFFSET) * psi_rate_ego_dot)) ocp.model.cost_y_expr_e = vertcat(y_ego, - ((v_ego + 5.0) * psi_ego), - ((v_ego + 5.0) * psi_rate_ego)) + ((v_ego + SPEED_OFFSET) * psi_ego), + ((v_ego + SPEED_OFFSET) * psi_rate_ego)) # set constraints ocp.constraints.constr_type = 'BGH' @@ -158,8 +158,8 @@ class LateralMpc(): self.yref[:,0] = y_pts v_ego = p_cp[0] # rotation_radius = p_cp[1] - self.yref[:,1] = heading_pts * (v_ego+5.0) - self.yref[:,2] = yaw_rate_pts * (v_ego+5.0) + self.yref[:,1] = heading_pts * (v_ego + SPEED_OFFSET) + self.yref[:,2] = yaw_rate_pts * (v_ego + SPEED_OFFSET) for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) self.solver.set(i, "p", p_cp) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 10e3edbebe..d4c832b53b 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -12,6 +12,15 @@ from cereal import log TRAJECTORY_SIZE = 33 CAMERA_OFFSET = 0.04 + +PATH_COST = 1.0 +LATERAL_MOTION_COST = 0.11 +LATERAL_ACCEL_COST = 0.0 +LATERAL_JERK_COST = 0.05 + +MIN_SPEED = 1.5 + + class LateralPlanner: def __init__(self, CP): self.DH = DesireHelper() @@ -36,7 +45,8 @@ class LateralPlanner: self.lat_mpc.reset(x0=self.x0) def update(self, sm): - v_ego = sm['carState'].vEgo + # clip speed , lateral planning is not possible at 0 speed + self.v_ego = max(MIN_SPEED, sm['carState'].vEgo) measured_curvature = sm['controlsState'].curvature # Parse model predictions @@ -56,20 +66,19 @@ class LateralPlanner: self.DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob) d_path_xyz = self.path_xyz - # Heading cost is useful at low speed, otherwise end of plan can be off-heading - heading_cost = interp(v_ego, [5.0, 10.0], [1.0, 0.15]) - self.lat_mpc.set_weights(1.0, heading_cost, 0.0, .075) + self.lat_mpc.set_weights(PATH_COST, LATERAL_MOTION_COST, + LATERAL_ACCEL_COST, LATERAL_JERK_COST) - 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) - yaw_rate_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw_rate) + y_pts = np.interp(self.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(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) + yaw_rate_pts = np.interp(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw_rate) self.y_pts = y_pts assert len(y_pts) == LAT_MPC_N + 1 assert len(heading_pts) == LAT_MPC_N + 1 assert len(yaw_rate_pts) == LAT_MPC_N + 1 - lateral_factor = max(0, self.factor1 - (self.factor2 * v_ego**2)) - p = np.array([v_ego, lateral_factor]) + lateral_factor = max(0, self.factor1 - (self.factor2 * self.v_ego**2)) + p = np.array([self.v_ego, lateral_factor]) self.lat_mpc.run(self.x0, p, y_pts, @@ -86,7 +95,7 @@ class LateralPlanner: t = sec_since_boot() if mpc_nans or self.lat_mpc.solution_status != 0: self.reset_mpc() - self.x0[3] = measured_curvature + self.x0[3] = measured_curvature * self.v_ego if t > self.last_cloudlog_t + 5.0: self.last_cloudlog_t = t cloudlog.warning("Lateral mpc - nan: True") @@ -106,10 +115,8 @@ class LateralPlanner: lateralPlan.dPathPoints = self.y_pts.tolist() lateralPlan.psis = self.lat_mpc.x_sol[0:CONTROL_N, 2].tolist() - # clip speed for curv calculation at 1m/s, to prevent low speed extremes - clipped_speed = max(1.0, sm['carState'].vEgo) - lateralPlan.curvatures = (self.lat_mpc.x_sol[0:CONTROL_N, 3]/clipped_speed).tolist() - lateralPlan.curvatureRates = [float(x/clipped_speed) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] + lateralPlan.curvatures = (self.lat_mpc.x_sol[0:CONTROL_N, 3]/self.v_ego).tolist() + lateralPlan.curvatureRates = [float(x/self.v_ego) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] lateralPlan.mpcSolutionValid = bool(plan_solution_valid) lateralPlan.solverExecutionTime = self.lat_mpc.solve_time diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index a89b4c2d71..a4a24ca603 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -338c24158bb28952dcd1554ea91734b4281e2fed \ No newline at end of file +f636c68e2b4ed88d3731930cf15b6dee984eb6dd From 36701a82a344447fe33476f42cd83034dbc373a4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 8 Oct 2022 00:02:57 -0700 Subject: [PATCH 108/178] Kia: update Optima platform name (#25852) * https://en.wikipedia.org/wiki/Kia_K5 * it's actually the same generation, but 2019+ is a facelift * g4 * fix * rename --- selfdrive/car/hyundai/hyundaican.py | 2 +- selfdrive/car/hyundai/interface.py | 4 ++-- selfdrive/car/hyundai/values.py | 20 ++++++++++---------- selfdrive/car/tests/routes.py | 4 ++-- selfdrive/car/torque_data/substitute.yaml | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 139a14d5e8..7f3f6a72c5 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -38,7 +38,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0 # Likely cars lacking the ability to show individual lane lines in the dash - elif car_fingerprint in (CAR.KIA_OPTIMA, CAR.KIA_OPTIMA_2019): + elif car_fingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL): # SysWarning 4 = keep hands on wheel + beep values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 97c1f7a5dc..2d960ed17f 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -202,12 +202,12 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.indi.timeConstantV = [1.4] ret.lateralTuning.indi.actuatorEffectivenessBP = [0.] ret.lateralTuning.indi.actuatorEffectivenessV = [1.8] - elif candidate in (CAR.KIA_OPTIMA, CAR.KIA_OPTIMA_2019, CAR.KIA_OPTIMA_H): + elif candidate in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H): ret.mass = 3558. * CV.LB_TO_KG ret.wheelbase = 2.80 ret.steerRatio = 13.75 tire_stiffness_factor = 0.5 - if candidate == CAR.KIA_OPTIMA: + if candidate == CAR.KIA_OPTIMA_G4: ret.minSteerSpeed = 32 * CV.MPH_TO_MS CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.KIA_STINGER: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 52a5088319..a43f17ac00 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -85,8 +85,8 @@ class CAR: KIA_NIRO_EV = "KIA NIRO EV 2020" KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021" - KIA_OPTIMA = "KIA OPTIMA 2016" - KIA_OPTIMA_2019 = "KIA OPTIMA 2019" + KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN" + KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT" KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019" KIA_SELTOS = "KIA SELTOS 2021" KIA_SORENTO = "KIA SORENTO GT LINE 2018" @@ -155,8 +155,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h), ], - CAR.KIA_OPTIMA: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", harness=Harness.hyundai_b), # TODO: may support 2016, 2018 - CAR.KIA_OPTIMA_2019: HyundaiCarInfo("Kia Optima 2019-20", harness=Harness.hyundai_g), + CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", harness=Harness.hyundai_b), # TODO: may support 2016, 2018 + CAR.KIA_OPTIMA_G4_FL: HyundaiCarInfo("Kia Optima 2019-20", harness=Harness.hyundai_g), CAR.KIA_OPTIMA_H: [ HyundaiCarInfo("Kia Optima Hybrid 2017", "Advanced Smart Cruise Control"), # TODO: may support adjacent years HyundaiCarInfo("Kia Optima Hybrid 2019"), @@ -1136,7 +1136,7 @@ FW_VERSIONS = { b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b', ], }, - CAR.KIA_OPTIMA: { + CAR.KIA_OPTIMA_G4: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4100 ', ], @@ -1150,7 +1150,7 @@ FW_VERSIONS = { b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6J0051\x00\x00\xf1\x006T6J0_C2\x00\x006T6J0051\x00\x00TJF0T20NSB\x00\x00\x00\x00', ], }, - CAR.KIA_OPTIMA_2019: { + CAR.KIA_OPTIMA_G4_FL: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 ', ], @@ -1367,7 +1367,7 @@ CHECKSUM = { FEATURES = { # which message has the gear "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA}, - "use_tcu_gears": {CAR.KIA_OPTIMA, CAR.KIA_OPTIMA_2019, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, + "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, "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}, # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 @@ -1383,7 +1383,7 @@ HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_N EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022} # 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, CAR.KIA_OPTIMA_2019, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} +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, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} # If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points. # If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py @@ -1408,8 +1408,8 @@ DBC = { CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.KIA_NIRO_PHEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_OPTIMA: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_OPTIMA_2019: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_OPTIMA_G4: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_OPTIMA_G4_FL: dbc_dict('hyundai_kia_generic', None), CAR.KIA_OPTIMA_H: dbc_dict('hyundai_kia_generic', None), CAR.KIA_SELTOS: dbc_dict('hyundai_kia_generic', None), CAR.KIA_SORENTO: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 3ae3a357ce..82c0614493 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -88,8 +88,8 @@ routes = [ CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022), CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022, segment=1), CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED), - CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA), - CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_2019, segment=0), + CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4), + CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0), CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF), diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index 92361d37f4..f5e3d1d61d 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -17,8 +17,8 @@ LEXUS RC 2020: LEXUS NX 2020 TOYOTA AVALON HYBRID 2019: TOYOTA AVALON 2019 TOYOTA AVALON HYBRID 2022: TOYOTA AVALON 2022 -KIA OPTIMA 2016: HYUNDAI SONATA 2020 -KIA OPTIMA 2019: HYUNDAI SONATA 2020 +KIA OPTIMA 4TH GEN: HYUNDAI SONATA 2020 +KIA OPTIMA 4TH GEN FACELIFT: HYUNDAI SONATA 2020 KIA OPTIMA HYBRID 2017 & SPORTS 2019: HYUNDAI SONATA 2020 KIA FORTE E 2018 & GT 2021: HYUNDAI SONATA 2020 KIA CEED INTRO ED 2019: HYUNDAI SONATA 2020 From 2e7fa330b346b6435d0b15524f3b5ce878d213de Mon Sep 17 00:00:00 2001 From: Greg Hogan Date: Sat, 8 Oct 2022 04:07:35 -0700 Subject: [PATCH 109/178] hyundai: fix logging stock AEB events (#25152) --- selfdrive/car/hyundai/carstate.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index b9c7327a93..1f2de3286e 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -126,12 +126,12 @@ class CarState(CarStateBase): ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear)) if not self.CP.openpilotLongitudinalControl: - if self.CP.carFingerprint in FEATURES["use_fca"]: - ret.stockAeb = cp_cruise.vl["FCA11"]["FCA_CmdAct"] != 0 - ret.stockFcw = cp_cruise.vl["FCA11"]["CF_VSM_Warn"] == 2 - else: - ret.stockAeb = cp_cruise.vl["SCC12"]["AEB_CmdAct"] != 0 - ret.stockFcw = cp_cruise.vl["SCC12"]["CF_VSM_Warn"] == 2 + aeb_src = "FCA11" if self.CP.carFingerprint in FEATURES["use_fca"] else "SCC12" + aeb_sig = "FCA_CmdAct" if self.CP.carFingerprint in FEATURES["use_fca"] else "AEB_CmdAct" + aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0 + aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0 + ret.stockFcw = aeb_warning and not aeb_braking + ret.stockAeb = aeb_warning and aeb_braking if self.CP.enableBsm: ret.leftBlindspot = cp.vl["LCA11"]["CF_Lca_IndLeft"] != 0 @@ -294,12 +294,14 @@ class CarState(CarStateBase): signals += [ ("FCA_CmdAct", "FCA11"), ("CF_VSM_Warn", "FCA11"), + ("CF_VSM_DecCmdAct", "FCA11"), ] checks.append(("FCA11", 50)) else: signals += [ ("AEB_CmdAct", "SCC12"), ("CF_VSM_Warn", "SCC12"), + ("CF_VSM_DecCmdAct", "SCC12"), ] if CP.enableBsm: @@ -383,12 +385,14 @@ class CarState(CarStateBase): signals += [ ("FCA_CmdAct", "FCA11"), ("CF_VSM_Warn", "FCA11"), + ("CF_VSM_DecCmdAct", "FCA11"), ] checks.append(("FCA11", 50)) else: signals += [ ("AEB_CmdAct", "SCC12"), ("CF_VSM_Warn", "SCC12"), + ("CF_VSM_DecCmdAct", "SCC12"), ] return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2) From 2ed82387a5df5b86113d23fc4cc7d7aeadabdb52 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 9 Oct 2022 05:10:00 +0800 Subject: [PATCH 110/178] cabana: fix Incorrect Y-Axis Scale (#26018) --- tools/cabana/chartswidget.cc | 33 +++++++++++++++------------------ tools/cabana/chartswidget.h | 1 + 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 3e1a8b8410..5caa8d5a43 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -194,8 +194,6 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa void ChartWidget::updateState() { auto chart = chart_view->chart(); auto axis_x = dynamic_cast(chart->axisX()); - if (axis_x->max() <= axis_x->min()) return; - int x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); if (line_marker_x != x) { line_marker->setX(x); @@ -214,7 +212,6 @@ void ChartWidget::updateSeries() { vals.clear(); vals.reserve(3 * 60 * 100); - double min_y = 0, max_y = 0; uint64_t route_start_time = parser->replay->routeStartTime(); for (auto &evt : *events) { if (evt->which == cereal::Event::Which::CAN) { @@ -226,9 +223,6 @@ void ChartWidget::updateSeries() { val -= ((val >> (sig->size - 1)) & 0x1) ? (1ULL << sig->size) : 0; } double value = val * sig->factor + sig->offset; - if (value < min_y) min_y = value; - if (value > max_y) max_y = value; - double ts = (evt->mono_time - route_start_time) / (double)1e9; // seconds vals.push_back({ts, value}); } @@ -239,7 +233,7 @@ void ChartWidget::updateSeries() { series->replace(vals); auto [begin, end] = parser->range(); chart_view->chart()->axisX()->setRange(begin, end); - chart_view->chart()->axisY()->setRange(min_y * 0.95, max_y * 1.05); + updateAxisY(); } void ChartWidget::rangeChanged(qreal min, qreal max) { @@ -247,17 +241,20 @@ void ChartWidget::rangeChanged(qreal min, qreal max) { if (axis_x->min() != min || axis_x->max() != max) { axis_x->setRange(min, max); } - // auto zoom on yaxis - double min_y = 0, max_y = 0; - for (auto &p : vals) { - if (p.x() > max) break; - - if (p.x() >= min) { - if (p.y() < min_y) min_y = p.y(); - if (p.y() > max_y) max_y = p.y(); - } - } - chart_view->chart()->axisY()->setRange(min_y * 0.95, max_y * 1.05); + updateAxisY(); +} + +// auto zoom on yaxis +void ChartWidget::updateAxisY() { + const auto axis_x = dynamic_cast(chart_view->chart()->axisX()); + // vals is a sorted list + auto begin = std::lower_bound(vals.begin(), vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); + if (begin == vals.end()) + return; + + auto end = std::upper_bound(vals.begin(), vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); + const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); + chart_view->chart()->axisY()->setRange(min->y(), max->y()); } // LineMarker diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 0413d65e09..81df2237bc 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -37,6 +37,7 @@ private: void addData(const CanData &can_data, const Signal &sig); void updateSeries(); void rangeChanged(qreal min, qreal max); + void updateAxisY(); QString id; QString sig_name; From f31aa6853354226baada15f6a12b57f16bc3853b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 8 Oct 2022 23:28:33 -0700 Subject: [PATCH 111/178] Subaru: log stock FCW (#26012) * log stock FCW * Update selfdrive/car/subaru/carstate.py * never seen 1 (could be aeb?) --- selfdrive/car/subaru/carstate.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 128e4245b2..ba873c48d7 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -58,9 +58,6 @@ class CarState(CarStateBase): ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0 ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS - if self.car_fingerprint not in PREGLOBAL_CARS: - ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3 - if (self.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \ (self.car_fingerprint not in PREGLOBAL_CARS and cp.vl["Dashlights"]["UNITS"] == 1): ret.cruiseState.speed *= CV.MPH_TO_KPH @@ -78,6 +75,8 @@ class CarState(CarStateBase): else: 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 self.es_lkas_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam From 754b44e7ceb2b6b029cf511b821f4328b90364f9 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 9 Oct 2022 23:44:19 +0800 Subject: [PATCH 112/178] Cabana: increase slider precision to milliseconds (#26025) --- tools/cabana/videowidget.cc | 52 ++++++++++++++++++++++++------------- tools/cabana/videowidget.h | 3 +++ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 7edbe5e38f..957747584c 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -31,9 +31,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { slider = new Slider(this); slider->setSingleStep(0); slider->setMinimum(0); - slider->setTickInterval(60); - slider->setTickPosition(QSlider::TicksBelow); - slider->setMaximum(parser->replay->totalSeconds()); + slider->setMaximum(parser->replay->totalSeconds() * 1000); slider_layout->addWidget(slider); total_time_label = new QLabel(formatTime(parser->replay->totalSeconds())); @@ -63,7 +61,7 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QObject::connect(parser, &Parser::rangeChanged, this, &VideoWidget::rangeChanged); QObject::connect(parser, &Parser::updated, this, &VideoWidget::updateState); - QObject::connect(slider, &QSlider::sliderMoved, [=]() { time_label->setText(formatTime(slider->value())); }); + QObject::connect(slider, &QSlider::sliderMoved, [=]() { time_label->setText(formatTime(slider->value() / 1000)); }); QObject::connect(slider, &QSlider::sliderReleased, [this]() { setPosition(slider->value()); }); QObject::connect(slider, &Slider::setPosition, this, &VideoWidget::setPosition); @@ -75,8 +73,8 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { } void VideoWidget::setPosition(int value) { - time_label->setText(formatTime(value)); - parser->seekTo(value); + time_label->setText(formatTime(value / 1000.0)); + parser->seekTo(value / 1000.0); } void VideoWidget::rangeChanged(double min, double max) { @@ -86,16 +84,16 @@ void VideoWidget::rangeChanged(double min, double max) { } time_label->setText(formatTime(min)); total_time_label->setText(formatTime(max)); - slider->setMinimum(min); - slider->setMaximum(max); - slider->setValue(parser->currentSec()); + slider->setMinimum(min * 1000); + slider->setMaximum(max * 1000); + slider->setValue(parser->currentSec() * 1000); } void VideoWidget::updateState() { if (!slider->isSliderDown()) { - int current_sec = parser->currentSec(); + double current_sec = parser->currentSec(); time_label->setText(formatTime(current_sec)); - slider->setValue(current_sec); + slider->setValue(current_sec * 1000); } } @@ -103,10 +101,25 @@ void VideoWidget::updateState() { Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { QTimer *timer = new QTimer(this); timer->setInterval(2000); - timer->callOnTimeout([this]() { timeline = parser->replay->getTimeline(); }); + timer->callOnTimeout([this]() { + timeline = parser->replay->getTimeline(); + update(); + }); timer->start(); } +void Slider::sliderChange(QAbstractSlider::SliderChange change) { + if (change == QAbstractSlider::SliderValueChange) { + qreal x = width() * ((value() - minimum()) / double(maximum() - minimum())); + if (x != slider_x) { + slider_x = x; + update(); + } + } else { + QAbstractSlider::sliderChange(change); + } +} + void Slider::paintEvent(QPaintEvent *ev) { auto getPaintRange = [this](double begin, double end) -> std::pair { double total_sec = maximum() - minimum(); @@ -117,17 +130,21 @@ void Slider::paintEvent(QPaintEvent *ev) { QPainter p(this); const int v_margin = 2; - p.fillRect(rect().adjusted(0, v_margin, 0, -v_margin), QColor(0, 0, 128)); + p.fillRect(rect().adjusted(0, v_margin, 0, -v_margin), QColor(111, 143, 175)); for (auto [begin, end, type] : timeline) { + begin *= 1000; + end *= 1000; if (begin > maximum() || end < minimum()) continue; if (type == TimelineType::Engaged) { auto [start_pos, end_pos] = getPaintRange(begin, end); - p.fillRect(QRect(start_pos, v_margin, end_pos - start_pos, height() - v_margin * 2), QColor(0, 135, 0)); + p.fillRect(QRect(start_pos, v_margin, end_pos - start_pos, height() - v_margin * 2), QColor(0, 163, 108)); } } for (auto [begin, end, type] : timeline) { + begin *= 1000; + end *= 1000; if (type == TimelineType::Engaged || begin > maximum() || end < minimum()) continue; auto [start_pos, end_pos] = getPaintRange(begin, end); @@ -136,14 +153,13 @@ void Slider::paintEvent(QPaintEvent *ev) { } else { QColor color(Qt::green); if (type != TimelineType::AlertInfo) - color = type == TimelineType::AlertWarning ? Qt::yellow : Qt::red; + color = type == TimelineType::AlertWarning ? QColor(255, 195, 0) : QColor(199, 0, 57); p.fillRect(QRect(start_pos, height() - v_margin - 3, end_pos - start_pos, 3), color); } } - p.setPen(QPen(Qt::black, 2)); - qreal x = width() * ((value() - minimum()) / double(maximum() - minimum())); - p.drawLine(QPointF{x, 0.}, QPointF{x, (qreal)height()}); + p.setPen(QPen(QColor(88, 24, 69), 3)); + p.drawLine(QPoint{slider_x, 0}, QPoint{slider_x, height()}); } void Slider::mousePressEvent(QMouseEvent *e) { diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 188456ecbe..060565d322 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -13,6 +13,7 @@ class Slider : public QSlider { public: Slider(QWidget *parent); void mousePressEvent(QMouseEvent* e) override; + void sliderChange(QAbstractSlider::SliderChange change) override; signals: void setPosition(int value); @@ -20,6 +21,8 @@ signals: private: void paintEvent(QPaintEvent *ev) override; std::vector> timeline; + + int slider_x = -1; }; class VideoWidget : public QWidget { From c719b3b7ddd05d8369aab13e11f99af7990c73ea Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 10 Oct 2022 23:24:14 -0700 Subject: [PATCH 113/178] HKG CAN-FD: log temporary steering faults (#26031) * log hkg can-fd temporary steering faults * bump to master --- opendbc | 2 +- selfdrive/car/hyundai/carstate.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/opendbc b/opendbc index ae2fd934ce..c35e8139bf 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit ae2fd934ceb8501c56a0802564c14963dbb201ac +Subproject commit c35e8139bf9e9d87b9efb6764ab7e65983e8d33e diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 1f2de3286e..4d60afe1fd 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -180,6 +180,7 @@ class CarState(CarStateBase): ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"] ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"] ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD + ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0 ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"], cp.vl["BLINKERS"]["RIGHT_LAMP"]) @@ -414,6 +415,7 @@ class CarState(CarStateBase): ("STEERING_ANGLE", "STEERING_SENSORS"), ("STEERING_COL_TORQUE", "MDPS"), ("STEERING_OUT_TORQUE", "MDPS"), + ("LKA_FAULT", "MDPS"), ("CRUISE_ACTIVE", "SCC1"), ("COUNTER", cruise_btn_msg), From 478693771bf2570da0b0370c65c6f4113766eaca Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 11 Oct 2022 10:27:00 -0700 Subject: [PATCH 114/178] bump tinygrad (#26029) --- tinygrad_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinygrad_repo b/tinygrad_repo index 46f6db7522..870ea766ee 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 46f6db75228cb7744a3a0a87616bfffeffa93658 +Subproject commit 870ea766eec7a38d7d590c81436f15271ba2667e From 32a4dfe2fe549edff352dc54055cff680c96755f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 11 Oct 2022 12:53:35 -0700 Subject: [PATCH 115/178] Add CarInfo for Elantra GT and i30 (#26034) Fixup car info for Elantra GT/i30 --- selfdrive/car/hyundai/values.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index a43f17ac00..68a16b096e 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -110,7 +110,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.ELANTRA: HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b), CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), - CAR.ELANTRA_GT_I30: None, # dashcamOnly and same platform as CAR.ELANTRA + CAR.ELANTRA_GT_I30: [ + HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e), + HyundaiCarInfo("Hyundai i30 2019", harness=Harness.hyundai_e), + ], CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c), CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", harness=Harness.hyundai_h), # TODO: confirm 2020-21 harness From e062d2387e37f92121979803e11e9fcbb550b63b Mon Sep 17 00:00:00 2001 From: Nelson Chen Date: Tue, 11 Oct 2022 13:17:21 -0700 Subject: [PATCH 116/178] Add missing Corolla TSS2 firmware (#26032) --- selfdrive/car/toyota/values.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 0075a483f6..137db5612b 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -757,6 +757,7 @@ FW_VERSIONS = { b'\x03312N6000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', b'\x02312K4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02312U5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', From dbdb3a02a8efb637275ef6d65fa0ab08b51d3b05 Mon Sep 17 00:00:00 2001 From: wjxjmj Date: Wed, 12 Oct 2022 05:16:40 +0800 Subject: [PATCH 117/178] Add ip and port arguments to /tools/sim/bridge.py (#26011) * Add ip and port arguments * Add descriptions of ip and port arguments * Update README.md * Update README.md * prefer host/port options Co-authored-by: Cameron Clough --- tools/sim/README.md | 2 ++ tools/sim/bridge.py | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/sim/README.md b/tools/sim/README.md index cb1aea35ac..40603f3f71 100644 --- a/tools/sim/README.md +++ b/tools/sim/README.md @@ -39,6 +39,8 @@ Options: --high_quality Set simulator to higher quality (requires good GPU) --town TOWN Select map to drive in --spawn_point NUM Number of the spawn point to start in + --host HOST Host address of Carla client (127.0.0.1 as default) + --port PORT Port of Carla client (2000 as default) ``` To engage openpilot press 1 a few times while focused on bridge.py to increase the cruise speed. diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index e436e92292..a105ed751e 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -39,6 +39,8 @@ def parse_args(add_args=None): parser.add_argument('--dual_camera', action='store_true') parser.add_argument('--town', type=str, default='Town04_Opt') parser.add_argument('--spawn_point', dest='num_selected_spawn_point', type=int, default=16) + parser.add_argument('--host', dest='host', type=str, default='127.0.0.1') + parser.add_argument('--port', dest='port', type=int, default=2000) return parser.parse_args(add_args) @@ -233,8 +235,8 @@ def can_function_runner(vs: VehicleState, exit_event: threading.Event): i += 1 -def connect_carla_client(): - client = carla.Client("127.0.0.1", 2000) +def connect_carla_client(host: str, port: int): + client = carla.Client(host, port) client.set_timeout(5) return client @@ -291,7 +293,7 @@ class CarlaBridge: self.close() def _run(self, q: Queue): - client = connect_carla_client() + client = connect_carla_client(self._args.host, self._args.port) world = client.load_world(self._args.town) settings = world.get_settings() From 2c9b150761f533a6132fac3639df24bb286386bb Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Tue, 11 Oct 2022 14:53:43 -0700 Subject: [PATCH 118/178] Low speed lateral like before (#26022) * Add explicit cost on steering wheel movement * Laxer low speed control * Laxer low speed control * Lower min speed now there is a cost * 3m/s * Similar to old master * Add cost * Crazy high * Update ref * comment --- selfdrive/controls/lib/latcontrol_torque.py | 14 ++++++---- .../controls/lib/lateral_mpc_lib/lat_mpc.py | 28 +++++++++++++------ selfdrive/controls/lib/lateral_planner.py | 10 +++++-- selfdrive/controls/tests/test_lateral_mpc.py | 6 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index fa1bb480f1..7d656b55a9 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -17,6 +17,8 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY # friction in the steering wheel that needs to be overcome to # move it at all, this is compensated for too. +LOW_SPEED_FACTOR = 100 + class LatControlTorque(LatControl): def __init__(self, CP, CI): @@ -55,13 +57,15 @@ class LatControlTorque(LatControl): actual_lateral_accel = actual_curvature * CS.vEgo ** 2 lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - low_speed_factor = interp(CS.vEgo, [0, 10, 20], [500, 500, 200]) - setpoint = desired_lateral_accel + low_speed_factor * desired_curvature - measurement = actual_lateral_accel + low_speed_factor * actual_curvature + setpoint = desired_lateral_accel + LOW_SPEED_FACTOR * desired_curvature + measurement = actual_lateral_accel + LOW_SPEED_FACTOR * actual_curvature error = setpoint - measurement gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY - pid_log.error = self.torque_from_lateral_accel(error, self.torque_params, error, lateral_accel_deadzone, friction_compensation=False) - ff = self.torque_from_lateral_accel(gravity_adjusted_lateral_accel, self.torque_params, error, lateral_accel_deadzone, friction_compensation=True) + pid_log.error = self.torque_from_lateral_accel(error, self.torque_params, error, + lateral_accel_deadzone, friction_compensation=False) + ff = self.torque_from_lateral_accel(gravity_adjusted_lateral_accel, self.torque_params, + desired_lateral_accel - actual_lateral_accel, + lateral_accel_deadzone, friction_compensation=True) freeze_integrator = steer_limited or CS.steeringPressed or CS.vEgo < 5 output_torque = self.pid.update(pid_log.error, diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index 0cbd576341..9607532ace 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -19,7 +19,7 @@ X_DIM = 4 P_DIM = 2 N = 16 COST_E_DIM = 3 -COST_DIM = COST_E_DIM + 1 +COST_DIM = COST_E_DIM + 2 SPEED_OFFSET = 10.0 MODEL_NAME = 'lat' ACADOS_SOLVER_TYPE = 'SQP_RTI' @@ -89,13 +89,21 @@ def gen_lat_ocp(): ocp.cost.yref = np.zeros((COST_DIM, )) ocp.cost.yref_e = np.zeros((COST_E_DIM, )) + # Add offset to smooth out low speed control + # TODO unclear if this right solution long term + v_ego_offset = v_ego + SPEED_OFFSET + # TODO there are two costs on psi_rate_ego_dot, one + # is correlated to jerk the other to steering wheel movement + # the steering wheel movement cost is added to prevent excessive + # wheel movements ocp.model.cost_y_expr = vertcat(y_ego, - ((v_ego + SPEED_OFFSET) * psi_ego), - ((v_ego + SPEED_OFFSET) * psi_rate_ego), - ((v_ego + SPEED_OFFSET) * psi_rate_ego_dot)) + v_ego_offset * psi_ego, + v_ego_offset * psi_rate_ego, + v_ego_offset * psi_rate_ego_dot, + psi_rate_ego_dot / (v_ego + 0.1)) ocp.model.cost_y_expr_e = vertcat(y_ego, - ((v_ego + SPEED_OFFSET) * psi_ego), - ((v_ego + SPEED_OFFSET) * psi_rate_ego)) + v_ego_offset * psi_ego, + v_ego_offset * psi_rate_ego) # set constraints ocp.constraints.constr_type = 'BGH' @@ -144,8 +152,12 @@ class LateralMpc(): self.solve_time = 0.0 self.cost = 0 - def set_weights(self, path_weight, heading_weight, yaw_rate_weight, yaw_accel_cost): - W = np.asfortranarray(np.diag([path_weight, heading_weight, yaw_rate_weight, yaw_accel_cost])) + def set_weights(self, path_weight, heading_weight, + lat_accel_weight, lat_jerk_weight, + steering_rate_weight): + W = np.asfortranarray(np.diag([path_weight, heading_weight, + lat_accel_weight, lat_jerk_weight, + steering_rate_weight])) for i in range(N): self.solver.cost_set(i, 'W', W) self.solver.cost_set(N, 'W', W[:COST_E_DIM,:COST_E_DIM]) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index d4c832b53b..9fbfd4a11f 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -17,8 +17,13 @@ PATH_COST = 1.0 LATERAL_MOTION_COST = 0.11 LATERAL_ACCEL_COST = 0.0 LATERAL_JERK_COST = 0.05 +# Extreme steering rate is unpleasant, even +# when it does not cause bad jerk. +# TODO this cost should be lowered when low +# speed lateral control is stable on all cars +STEERING_RATE_COST = 800.0 -MIN_SPEED = 1.5 +MIN_SPEED = .3 class LateralPlanner: @@ -67,7 +72,8 @@ class LateralPlanner: d_path_xyz = self.path_xyz self.lat_mpc.set_weights(PATH_COST, LATERAL_MOTION_COST, - LATERAL_ACCEL_COST, LATERAL_JERK_COST) + LATERAL_ACCEL_COST, LATERAL_JERK_COST, + STEERING_RATE_COST) y_pts = np.interp(self.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(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) diff --git a/selfdrive/controls/tests/test_lateral_mpc.py b/selfdrive/controls/tests/test_lateral_mpc.py index 9b986c053d..df5154b2b4 100644 --- a/selfdrive/controls/tests/test_lateral_mpc.py +++ b/selfdrive/controls/tests/test_lateral_mpc.py @@ -10,7 +10,7 @@ def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvatur if lat_mpc is None: lat_mpc = LateralMpc() - lat_mpc.set_weights(1., 1., 0.0, 1.) + lat_mpc.set_weights(1., .1, 0.0, .05, 800) y_pts = poly_shift * np.ones(LAT_MPC_N + 1) heading_pts = np.zeros(LAT_MPC_N + 1) @@ -77,9 +77,9 @@ class TestLateralMpc(unittest.TestCase): def test_switch_convergence(self): lat_mpc = LateralMpc() - sol = run_mpc(lat_mpc=lat_mpc, poly_shift=30.0, v_ref=7.0) + sol = run_mpc(lat_mpc=lat_mpc, poly_shift=3.0, v_ref=7.0) right_psi_deg = np.degrees(sol[:,2]) - sol = run_mpc(lat_mpc=lat_mpc, poly_shift=-30.0, v_ref=7.0) + sol = run_mpc(lat_mpc=lat_mpc, poly_shift=-3.0, v_ref=7.0) left_psi_deg = np.degrees(sol[:,2]) np.testing.assert_almost_equal(right_psi_deg, -left_psi_deg, decimal=3) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index a4a24ca603..0d806ae5f1 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -f636c68e2b4ed88d3731930cf15b6dee984eb6dd +0f0a9aa8fed425468c488a4f8b7581c48d724e67 From 741867813285672a723b8fc53ead65a5cbe5c6dd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 11 Oct 2022 16:27:46 -0700 Subject: [PATCH 119/178] Use longActive for car-specific override signals (#26030) * add override field to cruiseControl * need to check if long *can* be active * bump cereal to master * revert * better * fix * update refs * rename variable --- cereal | 2 +- selfdrive/car/hyundai/carcontroller.py | 2 +- selfdrive/car/hyundai/hyundaican.py | 6 +++--- selfdrive/controls/controlsd.py | 1 + selfdrive/test/process_replay/ref_commit | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cereal b/cereal index b29717c4c3..b1003dd012 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit b29717c4c328d5cf34d46f682f25267150f82637 +Subproject commit b1003dd0128a451439d4fe98d098d90e994f81ed diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 6f7cc319e4..4a1f7bcb51 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -133,7 +133,7 @@ class CarController: stopping = actuators.longControlState == LongCtrlState.stopping set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_MPH if CS.clu11["CF_Clu_SPEED_UNIT"] == 1 else CV.MS_TO_KPH) can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2), - hud_control.leadVisible, set_speed_in_units, stopping, CS.out.gasPressed)) + hud_control.leadVisible, set_speed_in_units, stopping, CC.cruiseControl.override)) self.accel = accel # 20 Hz LFA MFA message diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 7f3f6a72c5..f4fe8f1126 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -94,7 +94,7 @@ def create_lfahda_mfc(packer, enabled, hda_set_speed=0): } return packer.make_can_msg("LFAHDA_MFC", 0, values) -def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, gas_pressed): +def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, long_override): commands = [] scc11_values = { @@ -111,7 +111,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s commands.append(packer.make_can_msg("SCC11", 0, scc11_values)) scc12_values = { - "ACCMode": 2 if enabled and gas_pressed else 1 if enabled else 0, + "ACCMode": 2 if enabled and long_override else 1 if enabled else 0, "StopReq": 1 if stopping else 0, "aReqRaw": accel, "aReqValue": accel, # stock ramps up and down respecting jerk limit until it reaches aReqRaw @@ -127,7 +127,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s "ComfortBandLower": 0.0, # stock usually is 0 but sometimes uses higher values "JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values "JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values - "ACCMode": 2 if enabled and gas_pressed else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage + "ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage "ObjGap": 2 if lead_visible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead } commands.append(packer.make_can_msg("SCC14", 0, scc14_values)) diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 6ce1156baf..8abb1a02a6 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -705,6 +705,7 @@ class Controls: if len(angular_rate_value) > 2: CC.angularVelocity = angular_rate_value + CC.cruiseControl.override = self.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl CC.cruiseControl.cancel = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) if self.joystick_mode and self.sm.rcv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]: CC.cruiseControl.cancel = True diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 0d806ae5f1..db3684fb0b 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -0f0a9aa8fed425468c488a4f8b7581c48d724e67 +02c6922762fef41c8188a5a4f1f3267b76651330 \ No newline at end of file From 182c5c48102a8d719402eece658c16f36f3d7046 Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Tue, 11 Oct 2022 17:41:20 -0700 Subject: [PATCH 120/178] add extra logging for rawgpsd opcode crash --- selfdrive/sensord/rawgps/rawgpsd.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index c68e7aff99..95f8dab1c2 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -179,6 +179,8 @@ def main() -> NoReturn: while 1: opcode, payload = diag.recv() + if opcode != DIAG_LOG_F: + cloudlog.exception(f"Unhandled opcode: {opcode}") assert opcode == DIAG_LOG_F (pending_msgs, log_outer_length), inner_log_packet = unpack_from(' Date: Tue, 11 Oct 2022 18:55:10 -0700 Subject: [PATCH 121/178] GPS test station first unittests (#25950) * init gps test * gps test v1 * add static signal gen script * update readme * remove LD_PRELOAD by using rpath, update values after testing * remove LD_PRELOAD * update fuzzy testing * address comments * cleanup Co-authored-by: Kurt Nistelberger --- selfdrive/sensord/pigeond.py | 99 +++++++++--- tools/gpstest/.gitignore | 2 +- tools/gpstest/README.md | 14 +- tools/gpstest/fuzzy_testing.py | 142 ++++++++++++++++++ tools/gpstest/gpstest.sh | 8 - .../limeGPS/inc_ephem_array_size.patch | 13 ++ tools/gpstest/patches/limeGPS/makefile.patch | 11 ++ .../gpstest/patches/limeSuite/mcu_error.patch | 13 ++ .../patches/limeSuite/reference_print.patch | 13 ++ tools/gpstest/remote_checker.py | 48 ++++++ tools/gpstest/run_static_gps_signal.py | 83 ++++++++++ tools/gpstest/setup.sh | 6 +- tools/gpstest/test_gps.py | 140 +++++++++++++++++ 13 files changed, 547 insertions(+), 45 deletions(-) create mode 100755 tools/gpstest/fuzzy_testing.py delete mode 100755 tools/gpstest/gpstest.sh create mode 100644 tools/gpstest/patches/limeGPS/inc_ephem_array_size.patch create mode 100644 tools/gpstest/patches/limeGPS/makefile.patch create mode 100644 tools/gpstest/patches/limeSuite/mcu_error.patch create mode 100644 tools/gpstest/patches/limeSuite/reference_print.patch create mode 100644 tools/gpstest/remote_checker.py create mode 100755 tools/gpstest/run_static_gps_signal.py create mode 100644 tools/gpstest/test_gps.py diff --git a/selfdrive/sensord/pigeond.py b/selfdrive/sensord/pigeond.py index 5fe120c061..f56af1c705 100755 --- a/selfdrive/sensord/pigeond.py +++ b/selfdrive/sensord/pigeond.py @@ -7,7 +7,7 @@ import struct import requests import urllib.parse from datetime import datetime -from typing import List, Optional +from typing import List, Optional, Tuple from cereal import messaging from common.params import Params @@ -34,7 +34,6 @@ def set_power(enabled: bool) -> None: gpio_set(GPIO.UBLOX_PWR_EN, enabled) gpio_set(GPIO.UBLOX_RST_N, enabled) - def add_ubx_checksum(msg: bytes) -> bytes: A = B = 0 for b in msg[2:]: @@ -115,35 +114,70 @@ class TTYPigeon(): raise TimeoutError('No response from ublox') time.sleep(0.001) + def reset_device(self) -> bool: + # deleting the backup does not always work on first try (mostly on second try) + for _ in range(5): + # device cold start + self.send(b"\xb5\x62\x06\x04\x04\x00\xff\xff\x00\x00\x0c\x5d") + time.sleep(1) # wait for cold start + init_baudrate(self) + + # clear configuration + self.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x00\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\x5b") + + # clear flash memory (almanac backup) + self.send_with_ack(b"\xB5\x62\x09\x14\x04\x00\x01\x00\x00\x00\x22\xf0") + + # try restoring backup to verify it got deleted + self.send(b"\xB5\x62\x09\x14\x00\x00\x1D\x60") + # 1: failed to restore, 2: could restore, 3: no backup + status = self.wait_for_backup_restore_status() + if status == 1 or status == 3: + return True + return False + +def init_baudrate(pigeon: TTYPigeon): + # ublox default setting on startup is 9600 baudrate + pigeon.set_baud(9600) + + # $PUBX,41,1,0007,0003,460800,0*15\r\n + pigeon.send(b"\x24\x50\x55\x42\x58\x2C\x34\x31\x2C\x31\x2C\x30\x30\x30\x37\x2C\x30\x30\x30\x33\x2C\x34\x36\x30\x38\x30\x30\x2C\x30\x2A\x31\x35\x0D\x0A") + time.sleep(0.1) + pigeon.set_baud(460800) + def initialize_pigeon(pigeon: TTYPigeon) -> bool: # try initializing a few times for _ in range(10): try: - pigeon.set_baud(9600) - - # up baud rate - pigeon.send(b"\x24\x50\x55\x42\x58\x2C\x34\x31\x2C\x31\x2C\x30\x30\x30\x37\x2C\x30\x30\x30\x33\x2C\x34\x36\x30\x38\x30\x30\x2C\x30\x2A\x31\x35\x0D\x0A") - time.sleep(0.1) - pigeon.set_baud(460800) - - # other configuration messages - pigeon.send_with_ack(b"\xB5\x62\x06\x00\x14\x00\x03\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x1E\x7F") - pigeon.send_with_ack(b"\xB5\x62\x06\x00\x14\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x35") - pigeon.send_with_ack(b"\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xC0\x08\x00\x00\x00\x08\x07\x00\x01\x00\x01\x00\x00\x00\x00\x00\xF4\x80") - pigeon.send_with_ack(b"\xB5\x62\x06\x00\x14\x00\x04\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1D\x85") - pigeon.send_with_ack(b"\xB5\x62\x06\x00\x00\x00\x06\x18") - pigeon.send_with_ack(b"\xB5\x62\x06\x00\x01\x00\x01\x08\x22") - pigeon.send_with_ack(b"\xB5\x62\x06\x00\x01\x00\x03\x0A\x24") + + # setup port config + pigeon.send_with_ack(b"\xb5\x62\x06\x00\x14\x00\x03\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x1E\x7F") + pigeon.send_with_ack(b"\xb5\x62\x06\x00\x14\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x35") + pigeon.send_with_ack(b"\xb5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xC0\x08\x00\x00\x00\x08\x07\x00\x01\x00\x01\x00\x00\x00\x00\x00\xF4\x80") + pigeon.send_with_ack(b"\xb5\x62\x06\x00\x14\x00\x04\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1D\x85") + pigeon.send_with_ack(b"\xb5\x62\x06\x00\x00\x00\x06\x18") + pigeon.send_with_ack(b"\xb5\x62\x06\x00\x01\x00\x01\x08\x22") + pigeon.send_with_ack(b"\xb5\x62\x06\x00\x01\x00\x03\x0A\x24") + + # UBX-CFG-RATE (0x06 0x08) pigeon.send_with_ack(b"\xB5\x62\x06\x08\x06\x00\x64\x00\x01\x00\x00\x00\x79\x10") + + # UBX-CFG-NAV5 (0x06 0x24) pigeon.send_with_ack(b"\xB5\x62\x06\x24\x24\x00\x05\x00\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5A\x63") + + # UBX-CFG-ODO (0x06 0x1E) pigeon.send_with_ack(b"\xB5\x62\x06\x1E\x14\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3C\x37") pigeon.send_with_ack(b"\xB5\x62\x06\x39\x08\x00\xFF\xAD\x62\xAD\x1E\x63\x00\x00\x83\x0C") pigeon.send_with_ack(b"\xB5\x62\x06\x23\x28\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x56\x24") + + # UBX-CFG-NAV5 (0x06 0x24) pigeon.send_with_ack(b"\xB5\x62\x06\x24\x00\x00\x2A\x84") pigeon.send_with_ack(b"\xB5\x62\x06\x23\x00\x00\x29\x81") pigeon.send_with_ack(b"\xB5\x62\x06\x1E\x00\x00\x24\x72") pigeon.send_with_ack(b"\xB5\x62\x06\x39\x00\x00\x3F\xC3") + + # UBX-CFG-MSG (set message rate) pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x01\x07\x01\x13\x51") pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x02\x15\x01\x22\x70") pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x02\x13\x01\x20\x6C") @@ -224,13 +258,11 @@ def deinitialize_and_exit(pigeon: Optional[TTYPigeon]): set_power(False) sys.exit(0) -def main(): - assert TICI, "unsupported hardware for pigeond" +def create_pigeon() -> Tuple[TTYPigeon, messaging.PubMaster]: + pigeon = None # register exit handler - pigeon = None signal.signal(signal.SIGINT, lambda sig, frame: deinitialize_and_exit(pigeon)) - pm = messaging.PubMaster(['ubloxRaw']) # power cycle ublox @@ -240,15 +272,20 @@ def main(): time.sleep(0.5) pigeon = TTYPigeon() - r = initialize_pigeon(pigeon) - Params().put_bool("UbloxAvailable", r) + return pigeon, pm - # start receiving data - while True: +def run_receiving(pigeon: TTYPigeon, pm: messaging.PubMaster, duration: int = 0): + + start_time = time.monotonic() + def end_condition(): + return True if duration == 0 else time.monotonic() - start_time < duration + + while end_condition(): dat = pigeon.receive() if len(dat) > 0: if dat[0] == 0x00: cloudlog.warning("received invalid data from ublox, re-initing!") + init_baudrate(pigeon) initialize_pigeon(pigeon) continue @@ -260,5 +297,17 @@ def main(): # prevent locking up a CPU core if ublox disconnects time.sleep(0.001) + +def main(): + assert TICI, "unsupported hardware for pigeond" + + pigeon, pm = create_pigeon() + init_baudrate(pigeon) + r = initialize_pigeon(pigeon) + Params().put_bool("UbloxAvailable", r) + + # start receiving data + run_receiving(pigeon, pm) + if __name__ == "__main__": main() diff --git a/tools/gpstest/.gitignore b/tools/gpstest/.gitignore index b33aaa403c..f11597286e 100644 --- a/tools/gpstest/.gitignore +++ b/tools/gpstest/.gitignore @@ -1,2 +1,2 @@ LimeGPS/ -LimeSuite/ +LimeSuite/ \ No newline at end of file diff --git a/tools/gpstest/README.md b/tools/gpstest/README.md index 4125bf00af..5aff0ee3d7 100644 --- a/tools/gpstest/README.md +++ b/tools/gpstest/README.md @@ -1,20 +1,18 @@ # GPS test setup +Testing the GPS receiver using GPS spoofing. At the moment only +static location relpay is supported. # Usage ``` -# replaying a static location -./gpstest.sh -e -s - -# replaying a prerecorded route (NMEA cvs file) -./gpstest.sh -e -d +# on host, start gps signal simulation +./run_static_lime.py ``` -If `-e` is not provided the latest ephemeris file will be downloaded from +`run_static_lime.py` downloads the latest ephemeris file from https://cddis.nasa.gov/archive/gnss/data/daily/20xx/brdc/. -(TODO: add auto downloader) -# Hardware Setup +# Hardware Setup * [LimeSDR USB](https://wiki.myriadrf.org/LimeSDR-USB) * Asus AX58BT antenna diff --git a/tools/gpstest/fuzzy_testing.py b/tools/gpstest/fuzzy_testing.py new file mode 100755 index 0000000000..216e7d0dde --- /dev/null +++ b/tools/gpstest/fuzzy_testing.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +import sys +import time +import random +import datetime as dt +import subprocess as sp +import multiprocessing +import threading +from typing import Tuple, Any + +from laika.downloader import download_nav +from laika.gps_time import GPSTime +from laika.helpers import ConstellationId + +cache_dir = '/tmp/gpstest/' + + +def download_rinex(): + # TODO: check if there is a better way to get the full brdc file for LimeGPS + gps_time = GPSTime.from_datetime(dt.datetime.utcnow()) + utc_time = dt.datetime.utcnow() - dt.timedelta(1) + gps_time = GPSTime.from_datetime(dt.datetime(utc_time.year, utc_time.month, utc_time.day)) + return download_nav(gps_time, cache_dir, ConstellationId.GPS) + + +def exec_LimeGPS_bin(rinex_file: str, location: str, duration: int): + # this functions should never return, cause return means, timeout is + # reached or it crashed + try: + cmd = ["LimeGPS/LimeGPS", "-e", rinex_file, "-l", location] + sp.check_output(cmd, timeout=duration) + except sp.TimeoutExpired: + print("LimeGPS timeout reached!") + except Exception as e: + print(f"LimeGPS crashed: {str(e)}") + + +def run_lime_gps(rinex_file: str, location: str, duration: int): + print(f"LimeGPS {location} {duration}") + + p = multiprocessing.Process(target=exec_LimeGPS_bin, + args=(rinex_file, location, duration)) + p.start() + return p + + +def get_random_coords(lat, lon) -> Tuple[int, int]: + # jump around the world + # max values, lat: -90 to 90, lon: -180 to 180 + + lat_add = random.random()*20 + 10 + lon_add = random.random()*20 + 20 + + lat = ((lat + lat_add + 90) % 180) - 90 + lon = ((lon + lon_add + 180) % 360) - 180 + return round(lat, 5), round(lon, 5) + +def get_continuous_coords(lat, lon) -> Tuple[int, int]: + # continuously move around the world + + lat_add = random.random()*0.01 + lon_add = random.random()*0.01 + + lat = ((lat + lat_add + 90) % 180) - 90 + lon = ((lon + lon_add + 180) % 360) - 180 + return round(lat, 5), round(lon, 5) + +rc_p: Any = None +def exec_remote_checker(lat, lon, duration): + global rc_p + # TODO: good enough for testing + remote_cmd = "export PYTHONPATH=/data/pythonpath:/data/pythonpath/pyextra && " + remote_cmd += "cd /data/openpilot && " + remote_cmd += f"timeout {duration} /usr/local/pyenv/shims/python tools/gpstest/remote_checker.py " + remote_cmd += f"{lat} {lon}" + + ssh_cmd = ['ssh', '-i', '/home/batman/openpilot/xx/phone/key/id_rsa', + 'comma@192.168.60.130'] + ssh_cmd += [remote_cmd] + + rc_p = sp.Popen(ssh_cmd, stdout=sp.PIPE) + rc_p.wait() + rc_output = rc_p.stdout.read() + print(f"Checker Result: {rc_output.strip().decode('utf-8')}") + + +def run_remote_checker(spoof_proc, lat, lon, duration) -> bool: + checker_thread = threading.Thread(target=exec_remote_checker, args=(lat, lon, duration)) + checker_thread.start() + + tcnt = 0 + while True: + if not checker_thread.is_alive(): + # assume this only happens when the signal got matched + return True + + # the spoofing process has a timeout, kill checker if reached + if not spoof_proc.is_alive(): + rc_p.kill() + # spoofing process died, assume timeout + print("Spoofing process timeout") + return False + + print(f"Time elapsed: {tcnt}[s]", end = "\r") + time.sleep(1) + tcnt += 1 + + +def main(): + continuous_mode = False + if len(sys.argv) == 2 and sys.argv[1] == '-c': + print("Continuous Mode!") + continuous_mode = True + + rinex_file = download_rinex() + + duration = 60*3 # max runtime in seconds + lat, lon = get_random_coords(47.2020, 15.7403) + + while True: + # spoof random location + spoof_proc = run_lime_gps(rinex_file, f"{lat},{lon},100", duration) + start_time = time.monotonic() + + # remote checker runs blocking + if not run_remote_checker(spoof_proc, lat, lon, duration): + # location could not be matched by ublox module + pass + + end_time = time.monotonic() + spoof_proc.terminate() + + # -1 to count process startup + print(f"Time to get Signal: {round(end_time - start_time - 1, 4)}") + + if continuous_mode: + lat, lon = get_continuous_coords(lat, lon) + else: + lat, lon = get_random_coords(lat, lon) + +if __name__ == "__main__": + main() diff --git a/tools/gpstest/gpstest.sh b/tools/gpstest/gpstest.sh deleted file mode 100755 index dfb71fe563..0000000000 --- a/tools/gpstest/gpstest.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -LimeGPS_BIN=LimeGPS/LimeGPS -if test -f "$LimeGPS_BIN"; then - LD_PRELOAD=LimeSuite/builddir/src/libLimeSuite.so $LimeGPS_BIN $@ -else - echo "LimeGPS binary not found, run 'setup.sh' first" -fi diff --git a/tools/gpstest/patches/limeGPS/inc_ephem_array_size.patch b/tools/gpstest/patches/limeGPS/inc_ephem_array_size.patch new file mode 100644 index 0000000000..9a3525d346 --- /dev/null +++ b/tools/gpstest/patches/limeGPS/inc_ephem_array_size.patch @@ -0,0 +1,13 @@ +diff --git a/gpssim.h b/gpssim.h +index c30b227..2ae0802 100644 +--- a/gpssim.h ++++ b/gpssim.h +@@ -75,7 +75,7 @@ + #define SC08 (8) + #define SC16 (16) + +-#define EPHEM_ARRAY_SIZE (13) // for daily GPS broadcast ephemers file (brdc) ++#define EPHEM_ARRAY_SIZE (20) // for daily GPS broadcast ephemers file (brdc) + + /*! \brief Structure representing GPS time */ + typedef struct diff --git a/tools/gpstest/patches/limeGPS/makefile.patch b/tools/gpstest/patches/limeGPS/makefile.patch new file mode 100644 index 0000000000..f99ce551db --- /dev/null +++ b/tools/gpstest/patches/limeGPS/makefile.patch @@ -0,0 +1,11 @@ +diff --git a/makefile b/makefile +index 51bfabf..d0ea1eb 100644 +--- a/makefile ++++ b/makefile +@@ -1,5 +1,4 @@ + CC=gcc -O2 -Wall + + all: limegps.c gpssim.c +- $(CC) -o LimeGPS limegps.c gpssim.c -lm -lpthread -lLimeSuite +- ++ $(CC) -o LimeGPS limegps.c gpssim.c -lm -lpthread -lLimeSuite -I../LimeSuite/src -L../LimeSuite/builddir/src -Wl,-rpath="$(PWD)/../LimeSuite/builddir/src" diff --git a/tools/gpstest/patches/limeSuite/mcu_error.patch b/tools/gpstest/patches/limeSuite/mcu_error.patch new file mode 100644 index 0000000000..91790a4a2b --- /dev/null +++ b/tools/gpstest/patches/limeSuite/mcu_error.patch @@ -0,0 +1,13 @@ +diff --git a/src/lms7002m/LMS7002M_RxTxCalibrations.cpp b/src/lms7002m/LMS7002M_RxTxCalibrations.cpp +index 41a37044..ac29c6b6 100644 +--- a/src/lms7002m/LMS7002M_RxTxCalibrations.cpp ++++ b/src/lms7002m/LMS7002M_RxTxCalibrations.cpp +@@ -254,7 +254,7 @@ int LMS7002M::CalibrateTx(float_type bandwidth_Hz, bool useExtLoopback) + mcuControl->RunProcedure(useExtLoopback ? MCU_FUNCTION_CALIBRATE_TX_EXTLOOPB : MCU_FUNCTION_CALIBRATE_TX); + status = mcuControl->WaitForMCU(1000); + if(status != MCU_BD::MCU_NO_ERROR) +- return ReportError(EINVAL, "Tx Calibration: MCU error %i (%s)", status, MCU_BD::MCUStatusMessage(status)); ++ return -1; //ReportError(EINVAL, "Tx Calibration: MCU error %i (%s)", status, MCU_BD::MCUStatusMessage(status)); + } + + //sync registers to cache diff --git a/tools/gpstest/patches/limeSuite/reference_print.patch b/tools/gpstest/patches/limeSuite/reference_print.patch new file mode 100644 index 0000000000..5bd7cdf1ed --- /dev/null +++ b/tools/gpstest/patches/limeSuite/reference_print.patch @@ -0,0 +1,13 @@ +diff --git a/src/FPGA_common/FPGA_common.cpp b/src/FPGA_common/FPGA_common.cpp +index 4e81f33e..7381c475 100644 +--- a/src/FPGA_common/FPGA_common.cpp ++++ b/src/FPGA_common/FPGA_common.cpp +@@ -946,7 +946,7 @@ double FPGA::DetectRefClk(double fx3Clk) + + if (i == 0) + return -1; +- lime::info("Reference clock %1.2f MHz", clkTbl[i - 1] / 1e6); ++ //lime::info("Reference clock %1.2f MHz", clkTbl[i - 1] / 1e6); + return clkTbl[i - 1]; + } + diff --git a/tools/gpstest/remote_checker.py b/tools/gpstest/remote_checker.py new file mode 100644 index 0000000000..84f6c0c3d9 --- /dev/null +++ b/tools/gpstest/remote_checker.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import sys +import time +from typing import List + +import cereal.messaging as messaging +from selfdrive.manager.process_config import managed_processes + +DELTA = 0.001 +# assume running openpilot for now +procs: List[str] = []#"ubloxd", "pigeond"] + + +def main(): + if len(sys.argv) < 3: + print("args: ") + return + + sol_lat = float(sys.argv[1]) + sol_lon = float(sys.argv[2]) + + for p in procs: + managed_processes[p].start() + time.sleep(0.5) # give time to startup + + gps_sock = messaging.sub_sock('gpsLocationExternal', timeout=0.1) + + # analyze until the location changed + while True: + events = messaging.drain_sock(gps_sock) + for e in events: + lat = e.gpsLocationExternal.latitude + lon = e.gpsLocationExternal.longitude + + if abs(lat - sol_lat) < DELTA and abs(lon - sol_lon) < DELTA: + print("MATCH") + return + + for p in procs: + if not managed_processes[p].proc.is_alive(): + print(f"ERROR: '{p}' died") + return + + +if __name__ == "__main__": + main() + for p in procs: + managed_processes[p].stop() diff --git a/tools/gpstest/run_static_gps_signal.py b/tools/gpstest/run_static_gps_signal.py new file mode 100755 index 0000000000..3787038f13 --- /dev/null +++ b/tools/gpstest/run_static_gps_signal.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +import os +import sys +import random +import datetime as dt +import subprocess as sp +from typing import Tuple + +from laika.downloader import download_nav +from laika.gps_time import GPSTime +from laika.helpers import ConstellationId + +cache_dir = '/tmp/gpstest/' + + +def download_rinex(): + # TODO: check if there is a better way to get the full brdc file for LimeGPS + gps_time = GPSTime.from_datetime(dt.datetime.utcnow()) + utc_time = dt.datetime.utcnow() - dt.timedelta(1) + gps_time = GPSTime.from_datetime(dt.datetime(utc_time.year, utc_time.month, utc_time.day)) + return download_nav(gps_time, cache_dir, ConstellationId.GPS) + + +def get_random_coords(lat, lon) -> Tuple[int, int]: + # jump around the world + # max values, lat: -90 to 90, lon: -180 to 180 + + lat_add = random.random()*20 + 10 + lon_add = random.random()*20 + 20 + + lat = ((lat + lat_add + 90) % 180) - 90 + lon = ((lon + lon_add + 180) % 360) - 180 + return round(lat, 5), round(lon, 5) + + +def check_availability() -> bool: + cmd = ["LimeSuite/builddir/LimeUtil/LimeUtil", "--find"] + output = sp.check_output(cmd) + + if output.strip() == b"": + return False + + print(f"Device: {output.strip().decode('utf-8')}") + return True + + +def main(): + if not os.path.exists('LimeGPS'): + print("LimeGPS not found run 'setup.sh' first") + return + + if not os.path.exists('LimeSuite'): + print("LimeSuite not found run 'setup.sh' first") + return + + if not check_availability(): + print("No limeSDR device found!") + return + + rinex_file = download_rinex() + lat, lon = get_random_coords(47.2020, 15.7403) + + if len(sys.argv) == 3: + lat = float(sys.argv[1]) + lon = float(sys.argv[2]) + + try: + print(f"starting LimeGPS, Location: {lat},{lon}") + cmd = ["LimeGPS/LimeGPS", "-e", rinex_file, "-l", f"{lat},{lon},100"] + sp.check_output(cmd, stderr=sp.PIPE) + except KeyboardInterrupt: + print("stopping LimeGPS") + except Exception as e: + out_stderr = e.stderr.decode('utf-8')# pylint:disable=no-member + if "Device is busy." in out_stderr: + print("GPS simulation is already running, Device is busy!") + return + + print(f"LimeGPS crashed: {str(e)}") + print(f"stderr:\n{e.stderr.decode('utf-8')}")# pylint:disable=no-member + +if __name__ == "__main__": + main() diff --git a/tools/gpstest/setup.sh b/tools/gpstest/setup.sh index c893f6aba8..ddf41dd260 100755 --- a/tools/gpstest/setup.sh +++ b/tools/gpstest/setup.sh @@ -9,8 +9,9 @@ if [ ! -d LimeSuite ]; then cd LimeSuite # checkout latest version which has firmware updates available git checkout v20.10.0 + git apply ../patches/limeSuite/* mkdir builddir && cd builddir - cmake .. + cmake -DCMAKE_BUILD_TYPE=Release .. make -j4 cd ../.. fi @@ -18,8 +19,7 @@ fi if [ ! -d LimeGPS ]; then git clone https://github.com/osqzss/LimeGPS.git cd LimeGPS - sed -i 's/LimeSuite/LimeSuite -I..\/LimeSuite\/src -L..\/LimeSuite\/builddir\/src/' makefile + git apply ../patches/limeGPS/* make cd .. fi - diff --git a/tools/gpstest/test_gps.py b/tools/gpstest/test_gps.py new file mode 100644 index 0000000000..f5e19372f7 --- /dev/null +++ b/tools/gpstest/test_gps.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +import time +import unittest +import struct +import numpy as np + +import cereal.messaging as messaging +import selfdrive.sensord.pigeond as pd +from system.hardware import TICI +from selfdrive.test.helpers import with_processes + + +def read_events(service, duration_sec): + service_sock = messaging.sub_sock(service, timeout=0.1) + start_time_sec = time.monotonic() + events = [] + while time.monotonic() - start_time_sec < duration_sec: + events += messaging.drain_sock(service_sock) + time.sleep(0.1) + + assert len(events) != 0, f"No '{service}'events collected!" + return events + + +def verify_ubloxgnss_data(socket: messaging.SubSocket): + start_time = 0 + end_time = 0 + events = messaging.drain_sock(socket) + assert len(events) != 0, "no ublxGnss measurements" + + for event in events: + if event.ubloxGnss.which() != "measurementReport": + continue + + if start_time == 0: + start_time = event.logMonoTime + + if event.ubloxGnss.measurementReport.numMeas != 0: + end_time = event.logMonoTime + break + + assert end_time != 0, "no ublox measurements received!" + + ttfm = (end_time - start_time)/1e9 + assert ttfm < 35, f"Time to first measurement > 35s, {ttfm}" + + # check for satellite count in measurements + sat_count = [] + end_id = events.index(event)# pylint:disable=undefined-loop-variable + for event in events[end_id:]: + if event.ubloxGnss.which() == "measurementReport": + sat_count.append(event.ubloxGnss.measurementReport.numMeas) + + num_sat = int(sum(sat_count)/len(sat_count)) + assert num_sat > 8, f"Not enough satellites {num_sat} (TestBox setup!)" + + +def verify_gps_location(socket: messaging.SubSocket): + buf_lon = [0]*10 + buf_lat = [0]*10 + buf_i = 0 + events = messaging.drain_sock(socket) + assert len(events) != 0, "no gpsLocationExternal measurements" + + start_time = events[0].logMonoTime + end_time = 0 + for event in events: + buf_lon[buf_i % 10] = event.gpsLocationExternal.longitude + buf_lat[buf_i % 10] = event.gpsLocationExternal.latitude + buf_i += 1 + + if buf_i < 9: + continue + + if any([lat == 0 or lon == 0 for lat,lon in zip(buf_lat, buf_lon)]): + continue + + if np.std(buf_lon) < 1e-5 and np.std(buf_lat) < 1e-5: + end_time = event.logMonoTime + break + + assert end_time != 0, "GPS location never converged!" + + ttfl = (end_time - start_time)/1e9 + assert ttfl < 40, f"Time to first location > 40s, {ttfl}" + + hacc = events[-1].gpsLocationExternal.accuracy + vacc = events[-1].gpsLocationExternal.verticalAccuracy + assert hacc < 15, f"Horizontal accuracy too high, {hacc}" + assert vacc < 43, f"Vertical accuracy too high, {vacc}" + + +def verify_time_to_first_fix(pigeon): + # get time to first fix from nav status message + nav_status = b"" + while True: + pigeon.send(b"\xb5\x62\x01\x03\x00\x00\x04\x0d") + nav_status = pigeon.receive() + if nav_status[:4] == b"\xb5\x62\x01\x03": + break + + values = struct.unpack(" 40s, {ttff}" + + +class TestGPS(unittest.TestCase): + @classmethod + def setUpClass(cls): + if not TICI: + raise unittest.SkipTest + + def tearDown(self): + pd.set_power(False) + + @with_processes(['ubloxd']) + def test_ublox_reset(self): + + pigeon, pm = pd.create_pigeon() + pd.init_baudrate(pigeon) + assert pigeon.reset_device(), "Could not reset device!" + + pd.initialize_pigeon(pigeon) + + ugs = messaging.sub_sock("ubloxGnss", timeout=0.1) + gle = messaging.sub_sock("gpsLocationExternal", timeout=0.1) + + # receive some messages (restart after cold start takes up to 30seconds) + pd.run_receiving(pigeon, pm, 40) + + verify_ubloxgnss_data(ugs) + verify_gps_location(gle) + + # skip for now, this might hang for a while + #verify_time_to_first_fix(pigeon) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From f74fefaffad87471ba1d7fa351d7fd37b9505d4a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 11 Oct 2022 18:59:16 -0700 Subject: [PATCH 122/178] =?UTF-8?q?Hyundai:=20remove=2090=C2=B0=20steering?= =?UTF-8?q?=20lockout=20(#24108)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * avoid 90 degree fault * use 50 frames * no panda mods * clean up * absolutely no faults. zero. zilch. nada * fix initial value and comments * try glitching at double rate instead of two in a row * bump panda * cut for two frames * clean up * bump panda * clean up * not today! * bump panda to master * prefix and simple lat_active * prefix --- panda | 2 +- selfdrive/car/hyundai/carcontroller.py | 28 ++++++++++++++++++++++---- selfdrive/car/hyundai/hyundaican.py | 3 ++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/panda b/panda index 1303af2db2..d68b1b0a98 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 1303af2db29a72eee180b10c6097fa5b19c29207 +Subproject commit d68b1b0a98d5cefad438180e3c2ffcdcbcffdd76 diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 4a1f7bcb51..2b0d6b9648 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -10,6 +10,12 @@ from selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerPar VisualAlert = car.CarControl.HUDControl.VisualAlert LongCtrlState = car.CarControl.Actuators.LongControlState +# EPS faults if you apply torque while the steering angle is above 90 degrees for more than 1 second +# All slightly below EPS thresholds to avoid fault +MAX_ANGLE = 85 +MAX_ANGLE_FRAMES = 89 +MAX_ANGLE_CONSECUTIVE_FRAMES = 2 + def process_hud_alert(enabled, fingerprint, hud_control): sys_warning = (hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)) @@ -40,6 +46,7 @@ class CarController: self.CP = CP self.params = CarControllerParams(CP) self.packer = CANPacker(dbc_name) + self.angle_limit_counter = 0 self.frame = 0 self.apply_steer_last = 0 @@ -107,10 +114,23 @@ class CarController: if self.frame % 100 == 0: can_sends.append([0x7D0, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 0]) - can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, CC.latActive, - CS.lkas11, sys_warning, sys_state, CC.enabled, - hud_control.leftLaneVisible, hud_control.rightLaneVisible, - left_lane_warning, right_lane_warning)) + # Count up to MAX_ANGLE_FRAMES, at which point we need to cut torque to avoid a steering fault + if CC.latActive and abs(CS.out.steeringAngleDeg) >= MAX_ANGLE: + self.angle_limit_counter += 1 + else: + self.angle_limit_counter = 0 + + # Cut steer actuation bit for two frames and hold torque with induced temporary fault + torque_fault = CC.latActive and self.angle_limit_counter > MAX_ANGLE_FRAMES + lat_active = CC.latActive and not torque_fault + + if self.angle_limit_counter >= MAX_ANGLE_FRAMES + MAX_ANGLE_CONSECUTIVE_FRAMES: + self.angle_limit_counter = 0 + + can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, lat_active, + torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, + left_lane_warning, right_lane_warning)) if not self.CP.openpilotLongitudinalControl: if CC.cruiseControl.cancel: diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index f4fe8f1126..dcb8430976 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -4,7 +4,7 @@ from selfdrive.car.hyundai.values import CAR, CHECKSUM, CAMERA_SCC_CAR hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf) def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, - lkas11, sys_warning, sys_state, enabled, + torque_fault, lkas11, sys_warning, sys_state, enabled, left_lane, right_lane, left_lane_depart, right_lane_depart): values = lkas11 @@ -14,6 +14,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, values["CF_Lkas_LdwsRHWarning"] = right_lane_depart values["CR_Lkas_StrToqReq"] = apply_steer values["CF_Lkas_ActToi"] = steer_req + values["CF_Lkas_ToiFlt"] = torque_fault # seems to allow actuation on CR_Lkas_StrToqReq values["CF_Lkas_MsgCount"] = frame % 0x10 if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, From 6dbfb8e49bde77a5ebddab2c8e084dfb596efd92 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 11 Oct 2022 21:27:09 -0700 Subject: [PATCH 123/178] bump cereal --- cereal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cereal b/cereal index b1003dd012..3eca747334 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit b1003dd0128a451439d4fe98d098d90e994f81ed +Subproject commit 3eca747334ca2138bf35d70399d58d0706a3cbd2 From 0f94d81b7adffa9da5c4632fb5979b27695bbb53 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 11 Oct 2022 22:33:37 -0700 Subject: [PATCH 124/178] GM camera ACC: reduce LKAS faults on startup (#26039) * GM camera ACC: no faults on start up 2.0 And by 2.0 I mean we don't need to wait for blocked msg support to be merged first to merge this without regressing accidental single blocked msg count handling. * Send the camera counter + 1 * Keep updating the first counter until we get a message on the bus * Only update right before sending so sent_lka_steering_cmd is updated first * Update ref_commit --- selfdrive/car/gm/carcontroller.py | 17 ++++++++++------- selfdrive/car/gm/carstate.py | 19 +++++++++++++++---- selfdrive/test/process_replay/ref_commit | 2 +- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 977d20c5b3..7c883dacc0 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -21,7 +21,8 @@ class CarController: self.frame = 0 self.last_button_frame = 0 - self.lka_steering_cmd_counter_last = -1 + self.lka_steering_cmd_counter = 0 + self.sent_lka_steering_cmd = False self.lka_icon_status_last = (False, False) self.params = CarControllerParams() @@ -44,9 +45,14 @@ class CarController: # Steering (50Hz) # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the # next Panda loopback confirmation in the current CS frame. - if CS.lka_steering_cmd_counter != self.lka_steering_cmd_counter_last: - self.lka_steering_cmd_counter_last = CS.lka_steering_cmd_counter + if CS.loopback_lka_steering_cmd_updated: + self.lka_steering_cmd_counter += 1 + self.sent_lka_steering_cmd = True elif (self.frame % self.params.STEER_STEP) == 0: + # Initialize ASCMLKASteeringCmd counter using the camera + if not self.sent_lka_steering_cmd and self.CP.networkLocation == NetworkLocation.fwdCamera: + self.lka_steering_cmd_counter = CS.camera_lka_steering_cmd_counter + 1 + if CC.latActive: new_steer = int(round(actuators.steer * self.params.STEER_MAX)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) @@ -54,10 +60,7 @@ class CarController: apply_steer = 0 self.apply_steer_last = apply_steer - # GM EPS faults on any gap in received message counters. To handle transient OP/Panda safety sync issues at the - # moment of disengaging, increment the counter based on the last message known to pass Panda safety checks. - idx = (CS.lka_steering_cmd_counter + 1) % 4 - + idx = self.lka_steering_cmd_counter % 4 can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, CC.latActive)) if self.CP.openpilotLongitudinalControl: diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index f96a234dbd..6d1e2b9d44 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -15,7 +15,8 @@ class CarState(CarStateBase): super().__init__(CP) can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"] - self.lka_steering_cmd_counter = 0 + self.loopback_lka_steering_cmd_updated = False + self.camera_lka_steering_cmd_counter = 0 self.buttons_counter = 0 def update(self, pt_cp, cam_cp, loopback_cp): @@ -25,6 +26,11 @@ class CarState(CarStateBase): self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"] self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"] + # Variables used for avoiding LKAS faults + self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]) > 0 + if self.CP.networkLocation == NetworkLocation.fwdCamera: + self.camera_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] + ret.wheelSpeeds = self.get_wheel_speeds( pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"], pt_cp.vl["EBCMWheelSpdFront"]["FRWheelSpd"], @@ -59,7 +65,6 @@ class CarState(CarStateBase): ret.steeringTorque = pt_cp.vl["PSCMStatus"]["LKADriverAppldTrq"] ret.steeringTorqueEps = pt_cp.vl["PSCMStatus"]["LKATorqueDelivered"] ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD - self.lka_steering_cmd_counter = loopback_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] # 0 inactive, 1 active, 2 temporarily limited, 3 failed self.lkas_status = pt_cp.vl["PSCMStatus"]["LKATorqueDeliveredStatus"] @@ -94,8 +99,14 @@ class CarState(CarStateBase): signals = [] checks = [] if CP.networkLocation == NetworkLocation.fwdCamera: - signals.append(("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus")) - checks.append(("ASCMActiveCruiseControlStatus", 25)) + signals += [ + ("RollingCounter", "ASCMLKASteeringCmd"), + ("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"), + ] + checks += [ + ("ASCMLKASteeringCmd", 10), + ("ASCMActiveCruiseControlStatus", 25), + ] return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.CAMERA) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index db3684fb0b..6b64fcb5aa 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -02c6922762fef41c8188a5a4f1f3267b76651330 \ No newline at end of file +5fa6a3743f2678eef13267fb946d7a03f2af5824 From 5ad89425a7b4a888ae43228af92b8839f79ec9ec Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 11 Oct 2022 22:55:21 -0700 Subject: [PATCH 125/178] GM camera ACC: log stock aeb/fcw (#26017) * GM camera ACC: log aeb/fcw * order * fix stockAeb --- selfdrive/car/gm/carstate.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 6d1e2b9d44..11c521e863 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -92,6 +92,9 @@ class CarState(CarStateBase): if self.CP.networkLocation == NetworkLocation.fwdCamera: ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS + ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0 + ret.stockFcw = cam_cp.vl["ASCMActiveCruiseControlStatus"]["FCWAlert"] != 0 + return ret @staticmethod @@ -100,10 +103,13 @@ class CarState(CarStateBase): checks = [] if CP.networkLocation == NetworkLocation.fwdCamera: signals += [ + ("AEBCmdActive", "AEBCmd"), ("RollingCounter", "ASCMLKASteeringCmd"), + ("FCWAlert", "ASCMActiveCruiseControlStatus"), ("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"), ] checks += [ + ("AEBCmd", 10), ("ASCMLKASteeringCmd", 10), ("ASCMActiveCruiseControlStatus", 25), ] From 967f0cc9f8afb17c049d893bedc7bd696404082b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 12 Oct 2022 13:05:09 -0700 Subject: [PATCH 126/178] bump opendbc --- opendbc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc b/opendbc index c35e8139bf..04cc54d5e6 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit c35e8139bf9e9d87b9efb6764ab7e65983e8d33e +Subproject commit 04cc54d5e662aaf708f72cabb65507c7dbb5136d From aa3dc7acbe889b62576a2d1eb9c6b076b1cfa6cb Mon Sep 17 00:00:00 2001 From: Nelson Chen Date: Wed, 12 Oct 2022 13:31:08 -0700 Subject: [PATCH 127/178] RAV4 2022: Add missing engine FW (#26044) From 23e0360acaefab4d --- selfdrive/car/toyota/values.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 137db5612b..819b85d759 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -1363,6 +1363,7 @@ FW_VERSIONS = { ], (Ecu.engine, 0x700, None): [ b'\x01896634AA0000\x00\x00\x00\x00', + b'\x01896634AA0100\x00\x00\x00\x00', b'\x01896634AA1000\x00\x00\x00\x00', b'\x01896634A88000\x00\x00\x00\x00', b'\x01896634A89000\x00\x00\x00\x00', From 03a065160e176be451cce95073b71bbfce5b4d6a Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 12 Oct 2022 13:51:09 -0700 Subject: [PATCH 128/178] ci: only run one instance of each workflow (#26036) only allow one running workflow per event for each branch --- .github/workflows/selfdrive_tests.yaml | 5 +++++ .github/workflows/tools_tests.yaml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index d4971a4339..9c38ba0cbc 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -1,10 +1,15 @@ name: selfdrive + on: push: branches-ignore: - 'testing-closet*' pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + env: BASE_IMAGE: openpilot-base CL_BASE_IMAGE: openpilot-base-cl diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 173e208384..c5afaad16c 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -1,8 +1,13 @@ name: tools + on: push: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + env: BASE_IMAGE: openpilot-base CL_BASE_IMAGE: openpilot-base-cl From 0fa1588f6c0bf9c9f1bebde91e02699506389ecd Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 13 Oct 2022 04:55:17 +0800 Subject: [PATCH 129/178] Cabana: stable initial release (#26004) * increase form size & fix wrong charts number * set max axisy to 1.0 if no value * show 'close' button in floating window * alwasy show scroll bar * complete the logs * more * increase size to 50 * keep logs for all messages * more * rename signal * better height * avoid flicker * dont call setupdatesenabled * filter dbc files bye typing * remove all charts if dbc file changed * fix wrong idx * bolder dbc filename * update chart if signal has been edited * new signals signalAdded,signalUpdated * split class Parser into CanMessages and DBCManager * cleanup * updateState after set message * cleanup * emit msgUpdated * clear history log if selected range changed * always update time * change title layout * show selected range hide title bar if no charts less space between title and chart * custome historylogmodel for extreme fast update * move historylog to seperate file * 2 decimal * cleanup cleanup * left click on the chart to set start time * todo * show tooltip for header item&cleanup binaryview add hline to signal form * better paint * cleanup signals/slots * better range if min==max * set historylog's minheight to 300 * 3x faster,sortable message list. * zero copy in queued connection * proxymodel * clear log if loop to the begin * simplify history log * remove icon * remove assets * hide linemarker on initialization * rubber width may less than 0 * dont zoom char if selected range is too small * cleanup messageslist * don't zoom chart if selected range less than 500ms * typo * check boundary * check msg_id * capital first letter * move history log out of scrollarea * Show only one form at a time * auto scroll to header d * reduce msg size entire row clickable rename filter_msgs --- tools/cabana/SConscript | 9 +- tools/cabana/cabana.cc | 6 +- tools/cabana/canmessages.cc | 123 ++++++++++++++++ tools/cabana/canmessages.h | 84 +++++++++++ tools/cabana/chartswidget.cc | 174 ++++++++++++++--------- tools/cabana/chartswidget.h | 25 +++- tools/cabana/dbcmanager.cc | 117 ++++++++++++++++ tools/cabana/dbcmanager.h | 51 +++++++ tools/cabana/detailwidget.cc | 224 ++++++++++++++---------------- tools/cabana/detailwidget.h | 53 ++++--- tools/cabana/historylog.cc | 91 ++++++++++++ tools/cabana/historylog.h | 37 +++++ tools/cabana/mainwin.cc | 7 +- tools/cabana/mainwin.h | 1 - tools/cabana/messageswidget.cc | 132 ++++++++++++------ tools/cabana/messageswidget.h | 38 +++-- tools/cabana/parser.cc | 181 ------------------------ tools/cabana/parser.h | 95 ------------- tools/cabana/signaledit.cc | 69 ++++----- tools/cabana/signaledit.h | 20 ++- tools/cabana/videowidget.cc | 115 ++++++--------- tools/cabana/videowidget.h | 16 +-- tools/replay/main.cc | 2 - tools/replay/replay.h | 2 + tools/replay/tests/test_replay.cc | 1 - 25 files changed, 999 insertions(+), 674 deletions(-) create mode 100644 tools/cabana/canmessages.cc create mode 100644 tools/cabana/canmessages.h create mode 100644 tools/cabana/dbcmanager.cc create mode 100644 tools/cabana/dbcmanager.h create mode 100644 tools/cabana/historylog.cc create mode 100644 tools/cabana/historylog.h delete mode 100644 tools/cabana/parser.cc delete mode 100644 tools/cabana/parser.h diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index c87e2cdd94..b94741ea9c 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -1,5 +1,4 @@ -import os -Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', +Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', 'cereal', 'transformations', 'widgets', 'opendbc') base_frameworks = qt_env['FRAMEWORKS'] @@ -13,8 +12,6 @@ else: qt_libs = ['qt_util', 'Qt5Charts'] + base_libs if arch in ['x86_64', 'Darwin'] and GetOption('extras'): - qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] - - Import('replay_lib') cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs - qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'videowidget.cc', 'signaledit.cc', 'parser.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', + 'canmessages.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 20cd889023..88b175663f 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -1,14 +1,14 @@ #include #include +#include #include "selfdrive/ui/qt/util.h" #include "tools/cabana/mainwin.h" -const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; - int main(int argc, char *argv[]) { initApp(argc, argv); QApplication app(argc, argv); + app.setStyle(QStyleFactory::create("Fusion")); QCommandLineParser cmd_parser; cmd_parser.addHelpOption(); @@ -22,7 +22,7 @@ int main(int argc, char *argv[]) { } const QString route = args.empty() ? DEMO_ROUTE : args.first(); - Parser p(&app); + CANMessages p(&app); if (!p.loadRoute(route, cmd_parser.value("data_dir"), true)) { return 0; } diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc new file mode 100644 index 0000000000..a55a045981 --- /dev/null +++ b/tools/cabana/canmessages.cc @@ -0,0 +1,123 @@ +#include "tools/cabana/canmessages.h" + +#include + +Q_DECLARE_METATYPE(std::vector); + +CANMessages *can = nullptr; + +CANMessages::CANMessages(QObject *parent) : QObject(parent) { + can = this; + + qRegisterMetaType>(); + QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection); +} + +CANMessages::~CANMessages() { + replay->stop(); +} + +static bool event_filter(const Event *e, void *opaque) { + CANMessages *c = (CANMessages *)opaque; + return c->eventFilter(e); +} + +bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { + replay = new Replay(route, {"can", "roadEncodeIdx"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); + replay->installEventFilter(event_filter, this); + QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::segmentsMerged); + if (replay->load()) { + replay->start(); + return true; + } + return false; +} + +void CANMessages::process(QHash> *messages) { + for (auto it = messages->begin(); it != messages->end(); ++it) { + ++counters[it.key()]; + auto &msgs = can_msgs[it.key()]; + const auto &new_msgs = it.value(); + if (msgs.size() == CAN_MSG_LOG_SIZE || can_msgs[it.key()].size() == 0) { + msgs = std::move(new_msgs); + } else { + msgs.insert(msgs.begin(), std::make_move_iterator(new_msgs.begin()), std::make_move_iterator(new_msgs.end())); + while (msgs.size() >= CAN_MSG_LOG_SIZE) { + msgs.pop_back(); + } + } + } + delete messages; + + if (current_sec < begin_sec || current_sec > end_sec) { + // loop replay in selected range. + seekTo(begin_sec); + } else { + emit updated(); + } +} + +bool CANMessages::eventFilter(const Event *event) { + static double prev_update_sec = 0; + // drop packets when the GUI thread is calling seekTo. to make sure the current_sec is accurate. + if (!seeking && event->which == cereal::Event::Which::CAN) { + if (!received_msgs) { + received_msgs.reset(new QHash>); + received_msgs->reserve(1000); + } + + current_sec = (event->mono_time - replay->routeStartTime()) / (double)1e9; + auto can_events = event->event.getCan(); + for (const auto &c : can_events) { + QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16); + auto &list = (*received_msgs)[id]; + while (list.size() >= CAN_MSG_LOG_SIZE) { + list.pop_back(); + } + CanData &data = list.emplace_front(); + data.ts = current_sec; + data.bus_time = c.getBusTime(); + data.dat.append((char *)c.getDat().begin(), c.getDat().size()); + } + + if (current_sec < prev_update_sec || (current_sec - prev_update_sec) > 1.0 / FPS) { + prev_update_sec = current_sec; + // use pointer to avoid data copy in queued connection. + emit received(received_msgs.release()); + } + } + return true; +} + +void CANMessages::seekTo(double ts) { + seeking = true; + replay->seekTo(ts, false); + seeking = false; +} + +void CANMessages::setRange(double min, double max) { + if (begin_sec != min || end_sec != max) { + begin_sec = min; + end_sec = max; + is_zoomed = begin_sec != event_begin_sec || end_sec != event_end_sec; + emit rangeChanged(min, max); + } +} + +void CANMessages::segmentsMerged() { + auto events = replay->events(); + if (!events || events->empty()) return; + + auto it = std::find_if(events->begin(), events->end(), [=](const Event *e) { return e->which == cereal::Event::Which::CAN; }); + event_begin_sec = it == events->end() ? 0 : ((*it)->mono_time - replay->routeStartTime()) / (double)1e9; + event_end_sec = double(events->back()->mono_time - replay->routeStartTime()) / 1e9; + if (!is_zoomed) { + begin_sec = event_begin_sec; + end_sec = event_end_sec; + } + emit eventsMerged(); +} + +void CANMessages::resetRange() { + setRange(event_begin_sec, event_end_sec); +} diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h new file mode 100644 index 0000000000..a2af2a084c --- /dev/null +++ b/tools/cabana/canmessages.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +#include + +#include "tools/replay/replay.h" + +const int FPS = 10; +const int CAN_MSG_LOG_SIZE = 100; + +struct CanData { + double ts; + uint16_t bus_time; + QByteArray dat; +}; + +class CANMessages : public QObject { + Q_OBJECT + +public: + CANMessages(QObject *parent); + ~CANMessages(); + bool loadRoute(const QString &route, const QString &data_dir, bool use_qcam); + void seekTo(double ts); + void resetRange(); + void setRange(double min, double max); + bool eventFilter(const Event *event); + + inline std::pair range() const { return {begin_sec, end_sec}; } + inline double totalSeconds() const { return replay->totalSeconds(); } + inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; } + inline double currentSec() const { return current_sec; } + inline bool isZoomed() const { return is_zoomed; } + inline const std::deque &messages(const QString &id) { return can_msgs[id]; } + inline const CanData &lastMessage(const QString &id) { return can_msgs[id].front(); } + + inline const std::vector *events() const { return replay->events(); } + inline void setSpeed(float speed) { replay->setSpeed(speed); } + inline bool isPaused() const { return replay->isPaused(); } + inline void pause(bool pause) { replay->pause(pause); } + inline const std::vector> getTimeline() { return replay->getTimeline(); } + +signals: + void eventsMerged(); + void rangeChanged(double min, double max); + void updated(); + void received(QHash> *); + +public: + QMap> can_msgs; + std::unique_ptr>> received_msgs = nullptr; + QHash counters; + +protected: + void process(QHash> *); + void segmentsMerged(); + + std::atomic current_sec = 0.; + std::atomic seeking = false; + double begin_sec = 0; + double end_sec = 0; + double event_begin_sec = 0; + double event_end_sec = 0; + bool is_zoomed = false; + Replay *replay = nullptr; +}; + +inline QString toHex(const QByteArray &dat) { + return dat.toHex(' ').toUpper(); +} +inline char toHex(uint value) { + return "0123456789ABCDEF"[value & 0xF]; +} + +inline const QString &getColor(int i) { + static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; + return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; +} + +// A global pointer referring to the unique CANMessages object +extern CANMessages *can; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 5caa8d5a43..e8a27ae18c 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -7,25 +7,6 @@ #include #include -int64_t get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) { - int64_t ret = 0; - - int i = sig.msb / 8; - int bits = sig.size; - while (i >= 0 && i < data_size && bits > 0) { - int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8; - int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1; - int size = msb - lsb + 1; - - uint64_t d = (data[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); - ret |= d << (bits - size); - - bits -= size; - i = sig.is_little_endian ? i - 1 : i + 1; - } - return ret; -} - // ChartsWidget ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { @@ -35,18 +16,22 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { // title bar title_bar = new QWidget(this); QHBoxLayout *title_layout = new QHBoxLayout(title_bar); + title_layout->setContentsMargins(0, 0, 0, 0); title_label = new QLabel(tr("Charts")); title_layout->addWidget(title_label); title_layout->addStretch(); + range_label = new QLabel(); + title_layout->addWidget(range_label); + reset_zoom_btn = new QPushButton("⟲", this); reset_zoom_btn->setVisible(false); reset_zoom_btn->setFixedSize(30, 30); reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); title_layout->addWidget(reset_zoom_btn); - remove_all_btn = new QPushButton(tr("✖")); + remove_all_btn = new QPushButton("✖", this); remove_all_btn->setVisible(false); remove_all_btn->setToolTip(tr("Remove all charts")); remove_all_btn->setFixedSize(30, 30); @@ -54,7 +39,6 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { dock_btn = new QPushButton(); dock_btn->setFixedSize(30, 30); - updateDockButton(); title_layout->addWidget(dock_btn); main_layout->addWidget(title_bar, 0, Qt::AlignTop); @@ -74,53 +58,80 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { main_layout->addWidget(charts_scroll); - QObject::connect(parser, &Parser::showPlot, this, &ChartsWidget::addChart); - QObject::connect(parser, &Parser::hidePlot, this, &ChartsWidget::removeChart); - QObject::connect(parser, &Parser::signalRemoved, this, &ChartsWidget::removeChart); - QObject::connect(reset_zoom_btn, &QPushButton::clicked, parser, &Parser::resetRange); + updateTitleBar(); + + QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeChart); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); + QObject::connect(can, &CANMessages::rangeChanged, [this]() { updateTitleBar(); }); + QObject::connect(reset_zoom_btn, &QPushButton::clicked, can, &CANMessages::resetRange); QObject::connect(remove_all_btn, &QPushButton::clicked, this, &ChartsWidget::removeAll); - QObject::connect(dock_btn, &QPushButton::clicked, [=]() { + QObject::connect(dock_btn, &QPushButton::clicked, [this]() { emit dock(!docking); docking = !docking; - updateDockButton(); + updateTitleBar(); }); } -void ChartsWidget::updateDockButton() { +void ChartsWidget::updateTitleBar() { + if (!charts.size()) { + title_bar->setVisible(false); + return; + } + + title_label->setText(tr("Charts (%1)").arg(charts.size())); + + // show select range + if (can->isZoomed()) { + auto [min, max] = can->range(); + range_label->setText(tr("%1 - %2").arg(min, 0, 'f', 2).arg(max, 0, 'f', 2)); + range_label->setVisible(true); + reset_zoom_btn->setEnabled(true); + } else { + reset_zoom_btn->setEnabled(false); + range_label->setVisible(false); + } + dock_btn->setText(docking ? "⬈" : "⬋"); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); + remove_all_btn->setVisible(!charts.empty()); + reset_zoom_btn->setVisible(!charts.empty()); + title_bar->setVisible(true); } void ChartsWidget::addChart(const QString &id, const QString &sig_name) { - const QString char_name = id + sig_name; + const QString char_name = id + ":" + sig_name; if (charts.find(char_name) == charts.end()) { auto chart = new ChartWidget(id, sig_name, this); + QObject::connect(chart, &ChartWidget::remove, [=]() { + removeChart(id, sig_name); + }); charts_layout->insertWidget(0, chart); charts[char_name] = chart; } - remove_all_btn->setVisible(true); - reset_zoom_btn->setVisible(true); - title_label->setText(tr("Charts (%1)").arg(charts.size())); + updateTitleBar(); } void ChartsWidget::removeChart(const QString &id, const QString &sig_name) { - if (auto it = charts.find(id + sig_name); it != charts.end()) { + if (auto it = charts.find(id + ":" + sig_name); it != charts.end()) { it->second->deleteLater(); charts.erase(it); - if (charts.empty()) { - remove_all_btn->setVisible(false); - reset_zoom_btn->setVisible(false); - } } - title_label->setText(tr("Charts (%1)").arg(charts.size())); + updateTitleBar(); } void ChartsWidget::removeAll() { for (auto [_, chart] : charts) chart->deleteLater(); charts.clear(); - remove_all_btn->setVisible(false); - reset_zoom_btn->setVisible(false); + updateTitleBar(); +} + +bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { + if (obj != this && event->type() == QEvent::Close) { + emit dock_btn->clicked(); + return true; + } + return false; } // ChartWidget @@ -138,16 +149,14 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa header->setStyleSheet("background-color:white"); QHBoxLayout *header_layout = new QHBoxLayout(header); header_layout->setContentsMargins(11, 11, 11, 0); - QLabel *title = new QLabel(tr("%1 %2").arg(parser->getMsg(id)->name.c_str()).arg(id)); + QLabel *title = new QLabel(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); header_layout->addWidget(title); header_layout->addStretch(); QPushButton *remove_btn = new QPushButton("✖", this); remove_btn->setFixedSize(30, 30); remove_btn->setToolTip(tr("Remove chart")); - QObject::connect(remove_btn, &QPushButton::clicked, [=]() { - emit parser->hidePlot(id, sig_name); - }); + QObject::connect(remove_btn, &QPushButton::clicked, this, &ChartWidget::remove); header_layout->addWidget(remove_btn); chart_layout->addWidget(header); @@ -163,9 +172,8 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa chart->setTitleFont(font); chart->setMargins({0, 0, 0, 0}); chart->layout()->setContentsMargins(0, 0, 0, 0); - QObject::connect(dynamic_cast(chart->axisX()), &QValueAxis::rangeChanged, parser, &Parser::setRange); - chart_view = new QChartView(chart); + chart_view = new ChartView(chart); chart_view->setFixedHeight(300); chart_view->setRenderHint(QPainter::Antialiasing); chart_view->setRubberBand(QChartView::HorizontalRubberBand); @@ -184,26 +192,28 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa line_marker->raise(); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - QObject::connect(parser, &Parser::updated, this, &ChartWidget::updateState); - QObject::connect(parser, &Parser::rangeChanged, this, &ChartWidget::rangeChanged); - QObject::connect(parser, &Parser::eventsMerged, this, &ChartWidget::updateSeries); - + QObject::connect(can, &CANMessages::updated, this, &ChartWidget::updateState); + QObject::connect(can, &CANMessages::rangeChanged, this, &ChartWidget::rangeChanged); + QObject::connect(can, &CANMessages::eventsMerged, this, &ChartWidget::updateSeries); + QObject::connect(dynamic_cast(chart->axisX()), &QValueAxis::rangeChanged, can, &CANMessages::setRange); + QObject::connect(dbc(), &DBCManager::signalUpdated, [this](const QString &msg_id, const QString &sig_name) { + if (this->id == msg_id && this->sig_name == sig_name) + updateSeries(); + }); updateSeries(); } void ChartWidget::updateState() { auto chart = chart_view->chart(); auto axis_x = dynamic_cast(chart->axisX()); - int x = chart->plotArea().left() + chart->plotArea().width() * (parser->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); - if (line_marker_x != x) { - line_marker->setX(x); - line_marker_x = x; - } + + int x = chart->plotArea().left() + chart->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); + line_marker->setX(x); } void ChartWidget::updateSeries() { - const Signal *sig = parser->getSig(id, sig_name); - auto events = parser->replay->events(); + const Signal *sig = dbc()->signal(id, sig_name); + auto events = can->events(); if (!sig || !events) return; auto l = id.split(':'); @@ -212,18 +222,14 @@ void ChartWidget::updateSeries() { vals.clear(); vals.reserve(3 * 60 * 100); - uint64_t route_start_time = parser->replay->routeStartTime(); + uint64_t route_start_time = can->routeStartTime(); for (auto &evt : *events) { if (evt->which == cereal::Event::Which::CAN) { for (auto c : evt->event.getCan()) { if (bus == c.getSrc() && address == c.getAddress()) { auto dat = c.getDat(); - int64_t val = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sig); - if (sig->is_signed) { - val -= ((val >> (sig->size - 1)) & 0x1) ? (1ULL << sig->size) : 0; - } - double value = val * sig->factor + sig->offset; - double ts = (evt->mono_time - route_start_time) / (double)1e9; // seconds + double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sig); + double ts = (evt->mono_time / (double)1e9) - route_start_time; // seconds vals.push_back({ts, value}); } } @@ -231,7 +237,7 @@ void ChartWidget::updateSeries() { } QLineSeries *series = (QLineSeries *)chart_view->chart()->series()[0]; series->replace(vals); - auto [begin, end] = parser->range(); + auto [begin, end] = can->range(); chart_view->chart()->axisX()->setRange(begin, end); updateAxisY(); } @@ -247,6 +253,7 @@ void ChartWidget::rangeChanged(qreal min, qreal max) { // auto zoom on yaxis void ChartWidget::updateAxisY() { const auto axis_x = dynamic_cast(chart_view->chart()->axisX()); + const auto axis_y = dynamic_cast(chart_view->chart()->axisY()); // vals is a sorted list auto begin = std::lower_bound(vals.begin(), vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); if (begin == vals.end()) @@ -254,14 +261,45 @@ void ChartWidget::updateAxisY() { auto end = std::upper_bound(vals.begin(), vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); - chart_view->chart()->axisY()->setRange(min->y(), max->y()); + if (min->y() == max->y()) { + if (max->y() < 0) { + axis_y->setRange(max->y(), 0); + } else { + axis_y->setRange(0, max->y() == 0 ? 1 : max->y()); + } + } else { + axis_y->setRange(min->y(), max->y()); + } } +// ChartView + +void ChartView::mouseReleaseEvent(QMouseEvent *event) { + auto rubber = findChild(); + if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) { + auto [begin, end] = can->range(); + if (rubber->width() <= 0) { + double seek_to = begin + ((event->pos().x() - chart()->plotArea().x()) / chart()->plotArea().width()) * (end - begin); + can->seekTo(seek_to); + } else if (((double)rubber->width() / chart()->plotArea().width()) * (end - begin) < 0.5) { + // don't zoom if selected range is less than 0.5s + rubber->hide(); + event->accept(); + return; + } + } + // TODO: right-click to reset zoom + QChartView::mouseReleaseEvent(event); +} + + // LineMarker void LineMarker::setX(double x) { - x_pos = x; - update(); + if (x != x_pos) { + x_pos = x; + update(); + } } void LineMarker::paintEvent(QPaintEvent *event) { diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 81df2237bc..7dbf0f108b 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -9,7 +9,8 @@ #include #include -#include "tools/cabana/parser.h" +#include "tools/cabana/canmessages.h" +#include "tools/cabana/dbcmanager.h" using namespace QtCharts; @@ -22,7 +23,15 @@ public: private: void paintEvent(QPaintEvent *event) override; - double x_pos = 0.0; + double x_pos = -1; +}; + +class ChartView : public QChartView { + Q_OBJECT + +public: + ChartView(QChart *chart, QWidget *parent = nullptr) : QChartView(chart, parent) {} + void mouseReleaseEvent(QMouseEvent *event) override; }; class ChartWidget : public QWidget { @@ -32,6 +41,9 @@ public: ChartWidget(const QString &id, const QString &sig_name, QWidget *parent); inline QChart *chart() const { return chart_view->chart(); } +signals: + void remove(); + private: void updateState(); void addData(const CanData &can_data, const Signal &sig); @@ -41,9 +53,8 @@ private: QString id; QString sig_name; - QChartView *chart_view = nullptr; + ChartView *chart_view = nullptr; LineMarker *line_marker = nullptr; - double line_marker_x = 0.0; QList vals; }; @@ -54,7 +65,6 @@ public: ChartsWidget(QWidget *parent = nullptr); void addChart(const QString &id, const QString &sig_name); void removeChart(const QString &id, const QString &sig_name); - void removeAll(); inline bool hasChart(const QString &id, const QString &sig_name) { return charts.find(id + sig_name) != charts.end(); } @@ -64,10 +74,13 @@ signals: private: void updateState(); - void updateDockButton(); + void updateTitleBar(); + void removeAll(); + bool eventFilter(QObject *obj, QEvent *event); QWidget *title_bar; QLabel *title_label; + QLabel *range_label; bool docking = true; QPushButton *dock_btn; QPushButton *reset_zoom_btn; diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc new file mode 100644 index 0000000000..1cb6da7fb5 --- /dev/null +++ b/tools/cabana/dbcmanager.cc @@ -0,0 +1,117 @@ +#include "tools/cabana/dbcmanager.h" + +#include + +DBCManager::DBCManager(QObject *parent) : QObject(parent) {} + +DBCManager::~DBCManager() {} + +void DBCManager::open(const QString &dbc_file_name) { + dbc_name = dbc_file_name; + dbc = const_cast(dbc_lookup(dbc_name.toStdString())); + msg_map.clear(); + for (auto &msg : dbc->msgs) { + msg_map[msg.address] = &msg; + } + emit DBCFileChanged(); +} + +void save(const QString &dbc_file_name) { + // TODO: save DBC to file +} + +void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) { + auto m = const_cast(msg(id)); + if (m) { + m->name = name.toStdString(); + m->size = size; + } else { + uint32_t address = addressFromId(id); + dbc->msgs.push_back({.address = address, .name = name.toStdString(), .size = size}); + msg_map[address] = &dbc->msgs.back(); + } + emit msgUpdated(id); +} + +void DBCManager::addSignal(const QString &id, const Signal &sig) { + if (Msg *m = const_cast(msg(id))) { + m->sigs.push_back(sig); + emit signalAdded(id, QString::fromStdString(sig.name)); + } +} + +void DBCManager::updateSignal(const QString &id, const QString &sig_name, const Signal &sig) { + if (Signal *s = const_cast(signal(id, sig_name))) { + *s = sig; + emit signalUpdated(id, sig_name); + } +} + +void DBCManager::removeSignal(const QString &id, const QString &sig_name) { + if (Msg *m = const_cast(msg(id))) { + auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); + if (it != m->sigs.end()) { + m->sigs.erase(it); + emit signalRemoved(id, sig_name); + } + } +} + +const Signal *DBCManager::signal(const QString &id, const QString &sig_name) const { + if (auto m = msg(id)) { + auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return sig_name == s.name.c_str(); }); + if (it != m->sigs.end()) + return &(*it); + } + return nullptr; +} + +uint32_t DBCManager::addressFromId(const QString &id) { + return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16); +} + +DBCManager *dbc() { + static DBCManager dbc_manager(nullptr); + return &dbc_manager; +} + +// helper functions + +static QVector BIG_ENDIAN_START_BITS = []() { + QVector ret; + for (int i = 0; i < 64; i++) + for (int j = 7; j >= 0; j--) + ret.push_back(j + i * 8); + return ret; +}(); + +int bigEndianStartBitsIndex(int start_bit) { + return BIG_ENDIAN_START_BITS[start_bit]; +} + +int bigEndianBitIndex(int index) { + return BIG_ENDIAN_START_BITS.indexOf(index); +} + +double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) { + int64_t val = 0; + + int i = sig.msb / 8; + int bits = sig.size; + while (i >= 0 && i < data_size && bits > 0) { + int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8; + int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1; + int size = msb - lsb + 1; + + uint64_t d = (data[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); + val |= d << (bits - size); + + bits -= size; + i = sig.is_little_endian ? i - 1 : i + 1; + } + if (sig.is_signed) { + val -= ((val >> (sig.size - 1)) & 0x1) ? (1ULL << sig.size) : 0; + } + double value = val * sig.factor + sig.offset; + return value; +} diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h new file mode 100644 index 0000000000..06c071be82 --- /dev/null +++ b/tools/cabana/dbcmanager.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "opendbc/can/common_dbc.h" + +class DBCManager : public QObject { + Q_OBJECT + +public: + DBCManager(QObject *parent); + ~DBCManager(); + + void open(const QString &dbc_file_name); + void save(const QString &dbc_file_name); + + const Signal *signal(const QString &id, const QString &sig_name) const; + void addSignal(const QString &id, const Signal &sig); + void updateSignal(const QString &id, const QString &sig_name, const Signal &sig); + void removeSignal(const QString &id, const QString &sig_name); + + static uint32_t addressFromId(const QString &id); + inline static std::vector allDBCNames() { return get_dbc_names(); } + inline QString name() const { return dbc_name; } + + void updateMsg(const QString &id, const QString &name, uint32_t size); + inline const Msg *msg(const QString &id) const { return msg(addressFromId(id)); } + inline const Msg *msg(uint32_t address) const { + auto it = msg_map.find(address); + return it != msg_map.end() ? it->second : nullptr; + } + +signals: + void signalAdded(const QString &id, const QString &sig_name); + void signalRemoved(const QString &id, const QString &sig_name); + void signalUpdated(const QString &id, const QString &sig_name); + void msgUpdated(const QString &id); + void DBCFileChanged(); + +private: + QString dbc_name; + DBC *dbc = nullptr; + std::unordered_map msg_map; +}; + +// TODO: Add helper function in dbc.h +double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig); +int bigEndianStartBitsIndex(int start_bit); +int bigEndianBitIndex(int index); + +DBCManager *dbc(); diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 1b6552804e..7c1847230b 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -1,39 +1,29 @@ #include "tools/cabana/detailwidget.h" -#include #include #include #include +#include #include #include -#include - -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/qt/widgets/scrollview.h" - -inline const QString &getColor(int i) { - static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; - return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; -} // DetailWidget DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); - name_label = new QLabel(this); - name_label->setStyleSheet("font-weight:bold;"); - name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - name_label->setAlignment(Qt::AlignCenter); - main_layout->addWidget(name_label); - // title QHBoxLayout *title_layout = new QHBoxLayout(); + title_layout->addWidget(new QLabel("time:")); time_label = new QLabel(this); title_layout->addWidget(time_label); + time_label->setStyleSheet("font-weight:bold"); + title_layout->addStretch(); + name_label = new QLabel(this); + name_label->setStyleSheet("font-weight:bold;"); + title_layout->addWidget(name_label); title_layout->addStretch(); - edit_btn = new QPushButton(tr("Edit"), this); edit_btn->setVisible(false); title_layout->addWidget(edit_btn); @@ -41,79 +31,104 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { // binary view binary_view = new BinaryView(this); - main_layout->addWidget(binary_view); + main_layout->addWidget(binary_view, 0, Qt::AlignTop); + + // signal header + signals_header = new QWidget(this); + QHBoxLayout *signals_header_layout = new QHBoxLayout(signals_header); + signals_header_layout->addWidget(new QLabel(tr("Signals"))); + signals_header_layout->addStretch(); + QPushButton *add_sig_btn = new QPushButton(tr("Add signal"), this); + signals_header_layout->addWidget(add_sig_btn); + signals_header->setVisible(false); + main_layout->addWidget(signals_header); // scroll area - QHBoxLayout *signals_layout = new QHBoxLayout(); - signals_layout->addWidget(new QLabel(tr("Signals"))); - signals_layout->addStretch(); - add_sig_btn = new QPushButton(tr("Add signal"), this); - add_sig_btn->setVisible(false); - signals_layout->addWidget(add_sig_btn); - main_layout->addLayout(signals_layout); - + scroll = new ScrollArea(this); QWidget *container = new QWidget(this); + container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); QVBoxLayout *container_layout = new QVBoxLayout(container); signal_edit_layout = new QVBoxLayout(); signal_edit_layout->setSpacing(2); container_layout->addLayout(signal_edit_layout); - history_log = new HistoryLog(this); - container_layout->addWidget(history_log); - - QScrollArea *scroll = new QScrollArea(this); scroll->setWidget(container); scroll->setWidgetResizable(true); - scroll->setFrameShape(QFrame::NoFrame); scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scroll->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - main_layout->addWidget(scroll); + history_log = new HistoryLog(this); + main_layout->addWidget(history_log); + QObject::connect(add_sig_btn, &QPushButton::clicked, this, &DetailWidget::addSignal); QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); - QObject::connect(parser, &Parser::updated, this, &DetailWidget::updateState); + QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); } -void DetailWidget::setMsg(const CanData *c) { - can_data = c; - clearLayout(signal_edit_layout); - edit_btn->setVisible(true); +void DetailWidget::setMessage(const QString &message_id) { + msg_id = message_id; + for (auto f : signal_forms) { + f->deleteLater(); + } + signal_forms.clear(); - if (auto msg = parser->getMsg(can_data->address)) { - name_label->setText(msg->name.c_str()); - add_sig_btn->setVisible(true); + if (auto msg = dbc()->msg(msg_id)) { for (int i = 0; i < msg->sigs.size(); ++i) { - signal_edit_layout->addWidget(new SignalEdit(can_data->id, msg->sigs[i], getColor(i))); + auto form = new SignalEdit(i, msg_id, msg->sigs[i], getColor(i)); + signal_edit_layout->addWidget(form); + QObject::connect(form, &SignalEdit::showChart, this, &DetailWidget::showChart); + QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); + signal_forms.push_back(form); } + name_label->setText(msg->name.c_str()); + signals_header->setVisible(true); } else { name_label->setText(tr("untitled")); - add_sig_btn->setVisible(false); + signals_header->setVisible(false); } + edit_btn->setVisible(true); - binary_view->setMsg(can_data); - history_log->clear(); + binary_view->setMessage(msg_id); + history_log->setMessage(msg_id); } void DetailWidget::updateState() { - if (!can_data) return; + time_label->setText(QString::number(can->currentSec(), 'f', 3)); + if (msg_id.isEmpty()) return; - time_label->setText(QString("time: %1").arg(can_data->ts, 0, 'f', 3)); - binary_view->setData(can_data->dat); + binary_view->updateState(); history_log->updateState(); } void DetailWidget::editMsg() { - EditMessageDialog dlg(can_data->id, this); + EditMessageDialog dlg(msg_id, this); if (dlg.exec()) { - setMsg(can_data); + setMessage(msg_id); } } void DetailWidget::addSignal() { - AddSignalDialog dlg(can_data->id, this); + AddSignalDialog dlg(msg_id, this); if (dlg.exec()) { - setMsg(can_data); + setMessage(msg_id); + } +} + +void DetailWidget::showForm() { + SignalEdit *sender = qobject_cast(QObject::sender()); + if (sender->isFormVisible()) { + sender->setFormVisible(false); + } else { + for (auto f : signal_forms) { + f->setFormVisible(f == sender); + if (f == sender) { + // scroll to header + QTimer::singleShot(0, [=]() { + const QPoint p = f->mapTo(scroll, QPoint(0, 0)); + scroll->verticalScrollBar()->setValue(p.y() + scroll->verticalScrollBar()->value()); + }); + } + } } } @@ -121,6 +136,7 @@ void DetailWidget::addSignal() { BinaryView::BinaryView(QWidget *parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); table = new QTableWidget(this); table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); table->horizontalHeader()->hide(); @@ -131,9 +147,10 @@ BinaryView::BinaryView(QWidget *parent) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } -void BinaryView::setMsg(const CanData *can_data) { - const Msg *msg = parser->getMsg(can_data->address); - int row_count = msg ? msg->size : can_data->dat.size(); +void BinaryView::setMessage(const QString &message_id) { + msg_id = message_id; + const Msg *msg = dbc()->msg(msg_id); + int row_count = msg ? msg->size : can->lastMessage(msg_id).dat.size(); table->setRowCount(row_count); table->setColumnCount(9); @@ -151,8 +168,8 @@ void BinaryView::setMsg(const CanData *can_data) { } } + // set background color if (msg) { - // set background color for (int i = 0; i < msg->sigs.size(); ++i) { const auto &sig = msg->sigs[i]; int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit); @@ -162,80 +179,44 @@ void BinaryView::setMsg(const CanData *can_data) { } } - setFixedHeight(table->rowHeight(0) * table->rowCount() + 25); + table->setFixedHeight(table->rowHeight(0) * table->rowCount() + table->horizontalHeader()->height() + 2); + updateState(); } -void BinaryView::setData(const QByteArray &binary) { - std::string s; - for (int j = 0; j < binary.size(); ++j) { - s += std::bitset<8>(binary[j]).to_string(); - } +void BinaryView::updateState() { + if (msg_id.isEmpty()) return; + + const auto &binary = can->lastMessage(msg_id).dat; setUpdatesEnabled(false); char hex[3] = {'\0'}; for (int i = 0; i < binary.size(); ++i) { for (int j = 0; j < 8; ++j) { - table->item(i, j)->setText(QChar(s[i * 8 + j])); + table->item(i, j)->setText(QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0')); } - sprintf(&hex[0], "%02X", (unsigned char)binary[i]); + hex[0] = toHex(binary[i] >> 4); + hex[1] = toHex(binary[i] & 0xf); table->item(i, 8)->setText(hex); } setUpdatesEnabled(true); } -// HistoryLog - -HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - QLabel *title = new QLabel("TIME BYTES"); - main_layout->addWidget(title); - - QVBoxLayout *message_layout = new QVBoxLayout(); - for (int i = 0; i < std::size(labels); ++i) { - labels[i] = new QLabel(); - labels[i]->setVisible(false); - message_layout->addWidget(labels[i]); - } - main_layout->addLayout(message_layout); - main_layout->addStretch(); -} - -void HistoryLog::updateState() { - int i = 0; - for (; i < parser->history_log.size(); ++i) { - const auto &c = parser->history_log[i]; - auto label = labels[i]; - label->setVisible(true); - label->setText(QString("%1 %2").arg(c.ts, 0, 'f', 3).arg(toHex(c.dat))); - } - - for (; i < std::size(labels); ++i) { - labels[i]->setVisible(false); - } -} - -void HistoryLog::clear() { - setUpdatesEnabled(false); - for (auto l : labels) l->setVisible(false); - setUpdatesEnabled(true); -} - // EditMessageDialog -EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : id(id), QDialog(parent) { +EditMessageDialog::EditMessageDialog(const QString &msg_id, QWidget *parent) : msg_id(msg_id), QDialog(parent) { setWindowTitle(tr("Edit message")); QVBoxLayout *main_layout = new QVBoxLayout(this); QFormLayout *form_layout = new QFormLayout(); - form_layout->addRow("ID", new QLabel(id)); + form_layout->addRow("ID", new QLabel(msg_id)); - auto msg = const_cast(parser->getMsg(id)); + const auto msg = dbc()->msg(msg_id); name_edit = new QLineEdit(this); name_edit->setText(msg ? msg->name.c_str() : "untitled"); form_layout->addRow(tr("Name"), name_edit); size_spin = new QSpinBox(this); - size_spin->setValue(msg ? msg->size : parser->can_msgs[id].dat.size()); + size_spin->setValue(msg ? msg->size : can->lastMessage(msg_id).dat.size()); form_layout->addRow(tr("Size"), size_spin); main_layout->addLayout(form_layout); @@ -243,22 +224,33 @@ EditMessageDialog::EditMessageDialog(const QString &id, QWidget *parent) : id(id auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox); + setFixedWidth(parent->width() * 0.9); + connect(buttonBox, &QDialogButtonBox::accepted, this, &EditMessageDialog::save); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } void EditMessageDialog::save() { - if (size_spin->value() <= 0 || name_edit->text().isEmpty()) return; + const QString name = name_edit->text(); + if (size_spin->value() <= 0 || name_edit->text().isEmpty() || name == tr("untitled")) + return; - if (auto msg = const_cast(parser->getMsg(id))) { - msg->name = name_edit->text().toStdString(); - msg->size = size_spin->value(); - } else { - Msg m = {}; - m.address = Parser::addressFromId(id); - m.name = name_edit->text().toStdString(); - m.size = size_spin->value(); - parser->addNewMsg(m); - } + dbc()->updateMsg(msg_id, name, size_spin->value()); QDialog::accept(); } + +// ScrollArea + +bool ScrollArea::eventFilter(QObject *obj, QEvent *ev) { + if (obj == widget() && ev->type() == QEvent::Resize) { + int height = widget()->height() + 4; + setMinimumHeight(height > 480 ? 480 : height); + setMaximumHeight(height); + } + return QScrollArea::eventFilter(obj, ev); +} + +void ScrollArea::setWidget(QWidget *w) { + QScrollArea::setWidget(w); + w->installEventFilter(this); +} diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index b2e7cbf3b7..99fe321012 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -3,35 +3,28 @@ #include #include #include +#include #include #include #include #include "opendbc/can/common.h" #include "opendbc/can/common_dbc.h" -#include "tools/cabana/parser.h" +#include "tools/cabana/canmessages.h" +#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/historylog.h" #include "tools/cabana/signaledit.h" -class HistoryLog : public QWidget { - Q_OBJECT - -public: - HistoryLog(QWidget *parent); - void clear(); - void updateState(); - -private: - QLabel *labels[LOG_SIZE] = {}; -}; - class BinaryView : public QWidget { Q_OBJECT public: BinaryView(QWidget *parent); - void setMsg(const CanData *can_data); - void setData(const QByteArray &binary); + void setMessage(const QString &message_id); + void updateState(); +private: + QString msg_id; QTableWidget *table; }; @@ -39,14 +32,23 @@ class EditMessageDialog : public QDialog { Q_OBJECT public: - EditMessageDialog(const QString &id, QWidget *parent); + EditMessageDialog(const QString &msg_id, QWidget *parent); protected: void save(); + QString msg_id; QLineEdit *name_edit; QSpinBox *size_spin; - QString id; +}; + +class ScrollArea : public QScrollArea { + Q_OBJECT + +public: + ScrollArea(QWidget *parent) : QScrollArea(parent) {} + bool eventFilter(QObject *obj, QEvent *ev) override; + void setWidget(QWidget *w); }; class DetailWidget : public QWidget { @@ -54,17 +56,26 @@ class DetailWidget : public QWidget { public: DetailWidget(QWidget *parent); - void setMsg(const CanData *c); + void setMessage(const QString &message_id); + +signals: + void showChart(const QString &msg_id, const QString &sig_name); + +private slots: + void showForm(); private: - void updateState(); void addSignal(); void editMsg(); + void updateState(); - const CanData *can_data = nullptr; + QString msg_id; QLabel *name_label, *time_label; - QPushButton *edit_btn, *add_sig_btn; + QPushButton *edit_btn; QVBoxLayout *signal_edit_layout; + QWidget *signals_header; + QList signal_forms; HistoryLog *history_log; BinaryView *binary_view; + ScrollArea *scroll; }; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc new file mode 100644 index 0000000000..494e281cb1 --- /dev/null +++ b/tools/cabana/historylog.cc @@ -0,0 +1,91 @@ +#include "tools/cabana/historylog.h" + +#include +#include + +QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { + if (role == Qt::DisplayRole) { + const auto &can_msgs = can->messages(msg_id); + if (index.row() < can_msgs.size()) { + const auto &can_data = can_msgs[index.row()]; + auto msg = dbc()->msg(msg_id); + if (msg && index.column() < msg->sigs.size()) { + return get_raw_value((uint8_t *)can_data.dat.begin(), can_data.dat.size(), msg->sigs[index.column()]); + } else { + return toHex(can_data.dat); + } + } + } + return {}; +} + +void HistoryLogModel::setMessage(const QString &message_id) { + beginResetModel(); + msg_id = message_id; + const auto msg = dbc()->msg(message_id); + column_count = msg && !msg->sigs.empty() ? msg->sigs.size() : 1; + row_count = 0; + endResetModel(); + + updateState(); +} + +QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Horizontal) { + auto msg = dbc()->msg(msg_id); + if (msg && section < msg->sigs.size()) { + if (role == Qt::BackgroundRole) { + return QBrush(QColor(getColor(section))); + } else if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { + return QString::fromStdString(msg->sigs[section].name); + } + } + } else if (role == Qt::DisplayRole) { + const auto &can_msgs = can->messages(msg_id); + if (section < can_msgs.size()) { + return QString::number(can_msgs[section].ts, 'f', 2); + } + } + return {}; +} + +void HistoryLogModel::updateState() { + if (msg_id.isEmpty()) return; + + const auto &can_msgs = can->messages(msg_id); + int prev_row_count = row_count; + row_count = can_msgs.size(); + int delta = row_count - prev_row_count; + if (delta > 0) { + beginInsertRows({}, prev_row_count, row_count - 1); + endInsertRows(); + } else if (delta < 0) { + beginRemoveRows({}, row_count, prev_row_count - 1); + endRemoveRows(); + } + if (row_count > 0) { + emit dataChanged(index(0, 0), index(row_count - 1, column_count - 1)); + emit headerDataChanged(Qt::Vertical, 0, row_count - 1); + } +} + +HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + model = new HistoryLogModel(this); + table = new QTableView(this); + table->setModel(model); + table->horizontalHeader()->setStretchLastSection(true); + table->setEditTriggers(QAbstractItemView::NoEditTriggers); + table->setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); + table->verticalHeader()->setStyleSheet("QHeaderView::section {padding-left: 5px; padding-right: 5px;min-width:40px;}"); + main_layout->addWidget(table); +} + +void HistoryLog::setMessage(const QString &message_id) { + model->setMessage(message_id); +} + +void HistoryLog::updateState() { + model->updateState(); +} diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h new file mode 100644 index 0000000000..f3a9046bfa --- /dev/null +++ b/tools/cabana/historylog.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include "tools/cabana/canmessages.h" +#include "tools/cabana/dbcmanager.h" + +class HistoryLogModel : public QAbstractTableModel { +Q_OBJECT + +public: + HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {} + void setMessage(const QString &message_id); + void updateState(); + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } + +private: + QString msg_id; + int row_count = 0; + int column_count = 0; +}; + +class HistoryLog : public QWidget { + Q_OBJECT + +public: + HistoryLog(QWidget *parent); + void setMessage(const QString &message_id); + void updateState(); + +private: + QTableView *table; + HistoryLogModel *model; +}; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 49e6cd2cca..6fa24ea21d 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -1,5 +1,6 @@ #include "tools/cabana/mainwin.h" +#include #include #include #include @@ -30,13 +31,15 @@ MainWindow::MainWindow() : QWidget() { h_layout->addWidget(right_container); - QObject::connect(messages_widget, &MessagesWidget::msgChanged, detail_widget, &DetailWidget::setMsg); + QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); + QObject::connect(detail_widget, &DetailWidget::showChart, charts_widget, &ChartsWidget::addChart); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); } void MainWindow::dockCharts(bool dock) { charts_widget->setUpdatesEnabled(false); if (dock && floating_window) { + floating_window->removeEventFilter(charts_widget); r_layout->addWidget(charts_widget); floating_window->deleteLater(); floating_window = nullptr; @@ -44,7 +47,7 @@ void MainWindow::dockCharts(bool dock) { floating_window = new QWidget(nullptr); floating_window->setLayout(new QVBoxLayout()); floating_window->layout()->addWidget(charts_widget); - floating_window->setWindowFlags(Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint); + floating_window->installEventFilter(charts_widget); floating_window->setMinimumSize(QGuiApplication::primaryScreen()->size() / 2); floating_window->showMaximized(); } diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index bcd15e4d8e..b0d7c273da 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -3,7 +3,6 @@ #include "tools/cabana/chartswidget.h" #include "tools/cabana/detailwidget.h" #include "tools/cabana/messageswidget.h" -#include "tools/cabana/parser.h" #include "tools/cabana/videowidget.h" class MainWindow : public QWidget { diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 7de3507b3d..eaf84fbace 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,19 +1,32 @@ #include "tools/cabana/messageswidget.h" #include +#include #include +#include #include +#include #include +#include "tools/cabana/dbcmanager.h" + MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); + // DBC file selector QHBoxLayout *dbc_file_layout = new QHBoxLayout(); QComboBox *combo = new QComboBox(this); - auto dbc_names = get_dbc_names(); + auto dbc_names = dbc()->allDBCNames(); for (const auto &name : dbc_names) { combo->addItem(QString::fromStdString(name)); } + combo->setEditable(true); + combo->setCurrentText(QString()); + combo->setInsertPolicy(QComboBox::NoInsert); + combo->completer()->setCompletionMode(QCompleter::PopupCompletion); + QFont font; + font.setBold(true); + combo->lineEdit()->setFont(font); dbc_file_layout->addWidget(combo); dbc_file_layout->addStretch(); @@ -21,73 +34,104 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { dbc_file_layout->addWidget(save_btn); main_layout->addLayout(dbc_file_layout); - filter = new QLineEdit(this); + // message filter + QLineEdit *filter = new QLineEdit(this); filter->setPlaceholderText(tr("filter messages")); main_layout->addWidget(filter); - table_widget = new QTableWidget(this); + // message table + table_widget = new QTableView(this); + model = new MessageListModel(this); + QSortFilterProxyModel *proxy_model = new QSortFilterProxyModel(this); + proxy_model->setSourceModel(model); + proxy_model->setFilterCaseSensitivity(Qt::CaseInsensitive); + proxy_model->setDynamicSortFilter(false); + table_widget->setModel(proxy_model); table_widget->setSelectionBehavior(QAbstractItemView::SelectRows); table_widget->setSelectionMode(QAbstractItemView::SingleSelection); table_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); - table_widget->setColumnCount(4); + table_widget->setSortingEnabled(true); table_widget->setColumnWidth(0, 250); table_widget->setColumnWidth(1, 80); table_widget->setColumnWidth(2, 80); - table_widget->setHorizontalHeaderLabels({tr("Name"), tr("ID"), tr("Count"), tr("Bytes")}); table_widget->horizontalHeader()->setStretchLastSection(true); + table_widget->verticalHeader()->hide(); + table_widget->sortByColumn(0, Qt::AscendingOrder); main_layout->addWidget(table_widget); - QObject::connect(parser, &Parser::updated, this, &MessagesWidget::updateState); + // signals/slots + QObject::connect(filter, &QLineEdit::textChanged, proxy_model, &QSortFilterProxyModel::setFilterFixedString); + QObject::connect(can, &CANMessages::updated, model, &MessageListModel::updateState); + QObject::connect(combo, SIGNAL(activated(const QString &)), SLOT(dbcSelectionChanged(const QString &))); QObject::connect(save_btn, &QPushButton::clicked, [=]() { // TODO: save DBC to file }); - QObject::connect(combo, &QComboBox::currentTextChanged, [=](const QString &dbc) { - parser->openDBC(dbc); - }); - QObject::connect(table_widget, &QTableWidget::itemSelectionChanged, [=]() { - const CanData *c = &(parser->can_msgs[table_widget->selectedItems()[1]->text()]); - parser->setCurrentMsg(c->id); - emit msgChanged(c); + QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { + if (current.isValid()) { + emit msgSelectionChanged(table_widget->model()->data(current, Qt::UserRole).toString()); + } }); // For test purpose combo->setCurrentText("toyota_nodsu_pt_generated"); } -void MessagesWidget::updateState() { - auto getTableItem = [=](int row, int col) -> QTableWidgetItem * { - auto item = table_widget->item(row, col); - if (!item) { - item = new QTableWidgetItem(); - item->setFlags(item->flags() ^ Qt::ItemIsEditable); - table_widget->setItem(row, col, item); - } - return item; - }; +void MessagesWidget::dbcSelectionChanged(const QString &dbc_file) { + dbc()->open(dbc_file); + // update detailwidget + auto current = table_widget->selectionModel()->currentIndex(); + if (current.isValid()) { + emit msgSelectionChanged(table_widget->model()->data(current, Qt::UserRole).toString()); + } +} - table_widget->setRowCount(parser->can_msgs.size()); - int i = 0; - QString name, untitled = tr("untitled"); - const QString filter_str = filter->text(); - for (const auto &[_, c] : parser->can_msgs) { - if (auto msg = parser->getMsg(c.address)) { - name = msg->name.c_str(); - } else { - name = untitled; - } - if (!filter_str.isEmpty() && !name.contains(filter_str, Qt::CaseInsensitive)) { - table_widget->hideRow(i++); - continue; +// MessageListModel + +QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + return (QString[]){"Name", "ID", "Count", "Bytes"}[section]; + else if (orientation == Qt::Vertical && role == Qt::DisplayRole) { + // return QString::number(section); + } + return {}; +} + +QVariant MessageListModel::data(const QModelIndex &index, int role) const { + if (role == Qt::DisplayRole) { + auto it = std::next(can->can_msgs.begin(), index.row()); + if (it != can->can_msgs.end() && !it.value().empty()) { + const auto &d = it.value().front(); + const QString &msg_id = it.key(); + switch (index.column()) { + case 0: { + auto msg = dbc()->msg(msg_id); + QString name = msg ? msg->name.c_str() : "untitled"; + return name; + } + case 1: return msg_id; + case 2: return can->counters[msg_id]; + case 3: return toHex(d.dat); + } } + } else if (role == Qt::UserRole) { + return std::next(can->can_msgs.begin(), index.row()).key(); + } + return {}; +} - getTableItem(i, 0)->setText(name); - getTableItem(i, 1)->setText(c.id); - getTableItem(i, 2)->setText(QString::number(parser->counters[c.id])); - getTableItem(i, 3)->setText(toHex(c.dat)); - table_widget->showRow(i); - i++; +void MessageListModel::updateState() { + int prev_row_count = row_count; + row_count = can->can_msgs.size(); + int delta = row_count - prev_row_count; + if (delta > 0) { + beginInsertRows({}, prev_row_count, row_count - 1); + endInsertRows(); + } else if (delta < 0) { + beginRemoveRows({}, row_count, prev_row_count - 1); + endRemoveRows(); } - if (table_widget->currentRow() == -1) { - table_widget->selectRow(0); + + if (row_count > 0) { + emit dataChanged(index(0, 0), index(row_count - 1, 3)); } } diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 1dbb4a1af3..f6487ba2bd 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -1,24 +1,38 @@ #pragma once -#include -#include -#include +#include +#include -#include "tools/cabana/parser.h" +#include "tools/cabana/canmessages.h" + +class MessageListModel : public QAbstractTableModel { +Q_OBJECT + +public: + MessageListModel(QObject *parent) : QAbstractTableModel(parent) {} + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 4; } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } + void updateState(); + +private: + int row_count = 0; +}; class MessagesWidget : public QWidget { Q_OBJECT - public: +public: MessagesWidget(QWidget *parent); - public slots: - void updateState(); +public slots: + void dbcSelectionChanged(const QString &dbc_file); - signals: - void msgChanged(const CanData *id); +signals: + void msgSelectionChanged(const QString &message_id); - protected: - QLineEdit *filter; - QTableWidget *table_widget; +protected: + QTableView *table_widget; + MessageListModel *model; }; diff --git a/tools/cabana/parser.cc b/tools/cabana/parser.cc deleted file mode 100644 index f4bacbb86d..0000000000 --- a/tools/cabana/parser.cc +++ /dev/null @@ -1,181 +0,0 @@ -#include "tools/cabana/parser.h" - -#include - -#include "cereal/messaging/messaging.h" - -Parser *parser = nullptr; - -static bool event_filter(const Event *e, void *opaque) { - Parser *p = (Parser*)opaque; - return p->eventFilter(e); -} - -Parser::Parser(QObject *parent) : QObject(parent) { - parser = this; - - qRegisterMetaType>(); - QObject::connect(this, &Parser::received, this, &Parser::process, Qt::QueuedConnection); -} - -Parser::~Parser() { - replay->stop(); -} - -bool Parser::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { - replay = new Replay(route, {"can", "roadEncodeIdx"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); - replay->installEventFilter(event_filter, this); - QObject::connect(replay, &Replay::segmentsMerged, this, &Parser::segmentsMerged); - if (replay->load()) { - replay->start(); - return true; - } - return false; -} - -void Parser::openDBC(const QString &name) { - dbc_name = name; - dbc = const_cast(dbc_lookup(name.toStdString())); - counters.clear(); - msg_map.clear(); - for (auto &msg : dbc->msgs) { - msg_map[msg.address] = &msg; - } -} - -void Parser::process(std::vector msgs) { - static double prev_update_ts = 0; - - for (const auto &can_data : msgs) { - can_msgs[can_data.id] = can_data; - ++counters[can_data.id]; - - if (can_data.id == current_msg_id) { - while (history_log.size() >= LOG_SIZE) { - history_log.pop_back(); - } - history_log.push_front(can_data); - } - } - double now = millis_since_boot(); - if ((now - prev_update_ts) > 1000.0 / FPS) { - prev_update_ts = now; - emit updated(); - } - - if (current_sec < begin_sec || current_sec > end_sec) { - // loop replay in selected range. - seekTo(begin_sec); - } -} - -bool Parser::eventFilter(const Event *event) { - // drop packets when the GUI thread is calling seekTo. to make sure the current_sec is accurate. - if (!seeking && event->which == cereal::Event::Which::CAN) { - current_sec = (event->mono_time - replay->routeStartTime()) / (double)1e9; - - auto can = event->event.getCan(); - msgs_buf.clear(); - msgs_buf.reserve(can.size()); - - for (const auto &c : can) { - CanData &data = msgs_buf.emplace_back(); - data.address = c.getAddress(); - data.bus_time = c.getBusTime(); - data.source = c.getSrc(); - data.dat.append((char *)c.getDat().begin(), c.getDat().size()); - data.id = QString("%1:%2").arg(data.source).arg(data.address, 1, 16); - data.ts = current_sec; - } - emit received(msgs_buf); - } - return true; -} - -void Parser::seekTo(double ts) { - seeking = true; - replay->seekTo(ts, false); - seeking = false; -} - -void Parser::addNewMsg(const Msg &msg) { - dbc->msgs.push_back(msg); - msg_map[dbc->msgs.back().address] = &dbc->msgs.back(); -} - -void Parser::removeSignal(const QString &id, const QString &sig_name) { - Msg *msg = const_cast(getMsg(id)); - if (!msg) return; - - auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); - if (it != msg->sigs.end()) { - msg->sigs.erase(it); - emit signalRemoved(id, sig_name); - } -} - -uint32_t Parser::addressFromId(const QString &id) { - return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16); -} - -const Signal *Parser::getSig(const QString &id, const QString &sig_name) { - if (auto msg = getMsg(id)) { - auto it = std::find_if(msg->sigs.begin(), msg->sigs.end(), [&](auto &s) { return sig_name == s.name.c_str(); }); - if (it != msg->sigs.end()) { - return &(*it); - } - } - return nullptr; -} - -void Parser::setRange(double min, double max) { - if (begin_sec != min || end_sec != max) { - begin_sec = min; - end_sec = max; - is_zoomed = begin_sec != event_begin_sec || end_sec != event_end_sec; - emit rangeChanged(min, max); - } -} - -void Parser::segmentsMerged() { - auto events = replay->events(); - if (!events || events->empty()) return; - - auto it = std::find_if(events->begin(), events->end(), [=](const Event *e) { return e->which == cereal::Event::Which::CAN; }); - event_begin_sec = it == events->end() ? 0 : ((*it)->mono_time - replay->routeStartTime()) / (double)1e9; - event_end_sec = double(events->back()->mono_time - replay->routeStartTime()) / 1e9; - if (!is_zoomed) { - begin_sec = event_begin_sec; - end_sec = event_end_sec; - } - emit eventsMerged(); -} - -void Parser::resetRange() { - setRange(event_begin_sec, event_end_sec); -} - -void Parser::setCurrentMsg(const QString &id) { - current_msg_id = id; - history_log.clear(); -} - -// helper functions - -static QVector BIG_ENDIAN_START_BITS = []() { - QVector ret; - for (int i = 0; i < 64; i++) { - for (int j = 7; j >= 0; j--) { - ret.push_back(j + i * 8); - } - } - return ret; -}(); - -int bigEndianStartBitsIndex(int start_bit) { - return BIG_ENDIAN_START_BITS[start_bit]; -} - -int bigEndianBitIndex(int index) { - return BIG_ENDIAN_START_BITS.indexOf(index); -} diff --git a/tools/cabana/parser.h b/tools/cabana/parser.h deleted file mode 100644 index 1632fcf6a6..0000000000 --- a/tools/cabana/parser.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -#include "opendbc/can/common.h" -#include "opendbc/can/common_dbc.h" -#include "tools/replay/replay.h" - -const int FPS = 20; -const static int LOG_SIZE = 25; - -struct CanData { - QString id; - double ts; - uint32_t address; - uint16_t bus_time; - uint8_t source; - QByteArray dat; -}; - -class Parser : public QObject { - Q_OBJECT - -public: - Parser(QObject *parent); - ~Parser(); - static uint32_t addressFromId(const QString &id); - bool eventFilter(const Event *event); - bool loadRoute(const QString &route, const QString &data_dir, bool use_qcam); - void openDBC(const QString &name); - void saveDBC(const QString &name) {} - void addNewMsg(const Msg &msg); - void removeSignal(const QString &id, const QString &sig_name); - void seekTo(double ts); - const Signal *getSig(const QString &id, const QString &sig_name); - void setRange(double min, double max); - void resetRange(); - void setCurrentMsg(const QString &id); - inline std::pair range() const { return {begin_sec, end_sec}; } - inline double currentSec() const { return current_sec; } - inline bool isZoomed() const { return is_zoomed; } - inline const Msg *getMsg(const QString &id) { return getMsg(addressFromId(id)); } - inline const Msg *getMsg(uint32_t address) { - auto it = msg_map.find(address); - return it != msg_map.end() ? it->second : nullptr; - } - -signals: - void showPlot(const QString &id, const QString &name); - void hidePlot(const QString &id, const QString &name); - void signalRemoved(const QString &id, const QString &sig_name); - void eventsMerged(); - void rangeChanged(double min, double max); - void received(std::vector can); - void updated(); - -public: - Replay *replay = nullptr; - QHash counters; - std::map can_msgs; - QList history_log; - -protected: - void process(std::vector can); - void segmentsMerged(); - - std::atomic current_sec = 0.; - std::atomic seeking = false; - QString dbc_name; - double begin_sec = 0; - double end_sec = 0; - double event_begin_sec = 0; - double event_end_sec = 0; - bool is_zoomed = false; - DBC *dbc = nullptr; - std::map msg_map; - QString current_msg_id; - std::vector msgs_buf; -}; - -Q_DECLARE_METATYPE(std::vector); - -// TODO: Add helper function in dbc.h -int bigEndianStartBitsIndex(int start_bit); -int bigEndianBitIndex(int index); -inline QString toHex(const QByteArray &dat) { - return dat.toHex(' ').toUpper(); -} - -extern Parser *parser; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index c214adab09..3f48450195 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -3,13 +3,12 @@ #include #include #include -#include #include #include // SignalForm -SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { +SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start_bit), QWidget(parent) { QFormLayout *form_layout = new QFormLayout(this); name = new QLineEdit(sig.name.c_str()); @@ -33,7 +32,8 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { sign->setCurrentIndex(sig.is_signed ? 0 : 1); form_layout->addRow(tr("sign"), sign); - factor = new QSpinBox(); + factor = new QDoubleSpinBox(); + factor->setDecimals(3); factor->setValue(sig.factor); form_layout->addRow(tr("Factor"), factor); @@ -46,9 +46,11 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { form_layout->addRow(tr("Unit"), unit); comment = new QLineEdit(); form_layout->addRow(tr("Comment"), comment); - min_val = new QSpinBox(); + min_val = new QDoubleSpinBox(); + factor->setDecimals(3); form_layout->addRow(tr("Minimum value"), min_val); - max_val = new QSpinBox(); + max_val = new QDoubleSpinBox(); + factor->setDecimals(3); form_layout->addRow(tr("Maximum value"), max_val); val_desc = new QLineEdit(); form_layout->addRow(tr("Value descriptions"), val_desc); @@ -56,11 +58,11 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : QWidget(parent) { std::optional SignalForm::getSignal() { Signal sig = {}; + sig.start_bit = start_bit; sig.name = name->text().toStdString(); sig.size = size->text().toInt(); sig.offset = offset->text().toDouble(); sig.factor = factor->text().toDouble(); - sig.msb = msb->text().toInt(); sig.is_signed = sign->currentIndex() == 0; sig.is_little_endian = endianness->currentIndex() == 0; if (sig.is_little_endian) { @@ -75,30 +77,32 @@ std::optional SignalForm::getSignal() { // SignalEdit -SignalEdit::SignalEdit(const QString &id, const Signal &sig, const QString &color, QWidget *parent) : id(id), name_(sig.name.c_str()), QWidget(parent) { +SignalEdit::SignalEdit(int index, const QString &id, const Signal &sig, const QString &color, QWidget *parent) + : id(id), name_(sig.name.c_str()), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); // title QHBoxLayout *title_layout = new QHBoxLayout(); - QLabel *icon = new QLabel(">"); + icon = new QLabel(">"); + icon->setFixedSize(15, 30); icon->setStyleSheet("font-weight:bold"); title_layout->addWidget(icon); title = new ElidedLabel(this); - title->setText(sig.name.c_str()); + title->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + title->setText(QString("%1. %2").arg(index + 1).arg(sig.name.c_str())); title->setStyleSheet(QString("font-weight:bold; color:%1").arg(color)); title_layout->addWidget(title); - title_layout->addStretch(); plot_btn = new QPushButton("📈"); plot_btn->setToolTip(tr("Show Plot")); plot_btn->setFixedSize(30, 30); - QObject::connect(plot_btn, &QPushButton::clicked, [=]() { emit parser->showPlot(id, name_); }); + QObject::connect(plot_btn, &QPushButton::clicked, [=]() { emit showChart(id, name_); }); title_layout->addWidget(plot_btn); main_layout->addLayout(title_layout); - edit_container = new QWidget(this); - QVBoxLayout *v_layout = new QVBoxLayout(edit_container); + form_container = new QWidget(this); + QVBoxLayout *v_layout = new QVBoxLayout(form_container); form = new SignalForm(sig, this); v_layout->addWidget(form); @@ -110,24 +114,27 @@ SignalEdit::SignalEdit(const QString &id, const Signal &sig, const QString &colo h->addWidget(save_btn); v_layout->addLayout(h); - edit_container->setVisible(false); - main_layout->addWidget(edit_container); + form_container->setVisible(false); + main_layout->addWidget(form_container); + + QFrame* hline = new QFrame(); + hline->setFrameShape(QFrame::HLine); + hline->setFrameShadow(QFrame::Sunken); + main_layout->addWidget(hline); QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::save); - QObject::connect(title, &ElidedLabel::clicked, [=]() { - edit_container->isVisible() ? edit_container->hide() : edit_container->show(); - icon->setText(edit_container->isVisible() ? "▼" : ">"); - }); + QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); +} + +void SignalEdit::setFormVisible(bool visible) { + form_container->setVisible(visible); + icon->setText(visible ? "▼" : ">"); } void SignalEdit::save() { - if (auto sig = const_cast(parser->getSig(id, name_))) { - if (auto s = form->getSignal()) { - *sig = *s; - // TODO: reset the chart for sig - } - } + if (auto s = form->getSignal()) + dbc()->updateSignal(id, name_, *s); } void SignalEdit::remove() { @@ -137,7 +144,7 @@ void SignalEdit::remove() { msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); msgbox.setDefaultButton(QMessageBox::Cancel); if (msgbox.exec()) { - parser->removeSignal(id, name_); + dbc()->removeSignal(id, name_); deleteLater(); } } @@ -145,19 +152,19 @@ void SignalEdit::remove() { // AddSignalDialog AddSignalDialog::AddSignalDialog(const QString &id, QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Add signal to %1").arg(parser->getMsg(id)->name.c_str())); + setWindowTitle(tr("Add signal to %1").arg(dbc()->msg(id)->name.c_str())); QVBoxLayout *main_layout = new QVBoxLayout(this); Signal sig = {.name = "untitled"}; auto form = new SignalForm(sig, this); main_layout->addWidget(form); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox); + setFixedWidth(parent->width() * 0.9); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, [=]() { - if (auto msg = const_cast(parser->getMsg(id))) { - if (auto signal = form->getSignal()) { - msg->sigs.push_back(*signal); - } + if (auto signal = form->getSignal()) { + dbc()->addSignal(id, *signal); } QDialog::accept(); }); diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index b8140cc93b..00c13948b7 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -4,12 +4,15 @@ #include #include +#include #include #include #include #include "selfdrive/ui/qt/widgets/controls.h" -#include "tools/cabana/parser.h" + +#include "tools/cabana/canmessages.h" +#include "tools/cabana/dbcmanager.h" class SignalForm : public QWidget { Q_OBJECT @@ -19,17 +22,25 @@ public: std::optional getSignal(); QLineEdit *name, *unit, *comment, *val_desc; - QSpinBox *size, *msb, *lsb, *factor, *offset, *min_val, *max_val; + QSpinBox *size, *msb, *lsb, *offset; + QDoubleSpinBox *factor, *min_val, *max_val; QComboBox *sign, *endianness; + int start_bit = 0; }; class SignalEdit : public QWidget { Q_OBJECT public: - SignalEdit(const QString &id, const Signal &sig, const QString &color, QWidget *parent = nullptr); + SignalEdit(int index, const QString &id, const Signal &sig, const QString &color, QWidget *parent = nullptr); + void setFormVisible(bool show); + inline bool isFormVisible() const { return form_container->isVisible(); } void save(); +signals: + void showChart(const QString &msg_id, const QString &sig_name); + void showFormClicked(); + protected: void remove(); @@ -38,8 +49,9 @@ protected: QPushButton *plot_btn; ElidedLabel *title; SignalForm *form; - QWidget *edit_container; + QWidget *form_container; QPushButton *remove_btn; + QLabel *icon; }; class AddSignalDialog : public QDialog { diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 957747584c..193a6f8788 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -6,11 +6,10 @@ #include #include #include +#include #include #include -#include "tools/cabana/parser.h" - inline QString formatTime(int seconds) { return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh::mm::ss" : "mm::ss"); } @@ -25,18 +24,17 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { // slider controls QHBoxLayout *slider_layout = new QHBoxLayout(); - time_label = new QLabel("00:00"); + QLabel *time_label = new QLabel("00:00"); slider_layout->addWidget(time_label); slider = new Slider(this); slider->setSingleStep(0); slider->setMinimum(0); - slider->setMaximum(parser->replay->totalSeconds() * 1000); + slider->setMaximum(can->totalSeconds() * 1000); slider_layout->addWidget(slider); - total_time_label = new QLabel(formatTime(parser->replay->totalSeconds())); - slider_layout->addWidget(total_time_label); - + end_time_label = new QLabel(formatTime(can->totalSeconds())); + slider_layout->addWidget(end_time_label); main_layout->addLayout(slider_layout); // btn controls @@ -50,51 +48,39 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { for (float speed : {0.1, 0.5, 1., 2.}) { QPushButton *btn = new QPushButton(QString("%1x").arg(speed), this); btn->setCheckable(true); - QObject::connect(btn, &QPushButton::clicked, [=]() { parser->replay->setSpeed(speed); }); + QObject::connect(btn, &QPushButton::clicked, [=]() { can->setSpeed(speed); }); control_layout->addWidget(btn); group->addButton(btn); if (speed == 1.0) btn->setChecked(true); } - main_layout->addLayout(control_layout); - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - QObject::connect(parser, &Parser::rangeChanged, this, &VideoWidget::rangeChanged); - QObject::connect(parser, &Parser::updated, this, &VideoWidget::updateState); - QObject::connect(slider, &QSlider::sliderMoved, [=]() { time_label->setText(formatTime(slider->value() / 1000)); }); - QObject::connect(slider, &QSlider::sliderReleased, [this]() { setPosition(slider->value()); }); - QObject::connect(slider, &Slider::setPosition, this, &VideoWidget::setPosition); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + QObject::connect(can, &CANMessages::rangeChanged, this, &VideoWidget::rangeChanged); + QObject::connect(can, &CANMessages::updated, this, &VideoWidget::updateState); + QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); + QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); QObject::connect(play, &QPushButton::clicked, [=]() { - bool is_paused = parser->replay->isPaused(); + bool is_paused = can->isPaused(); play->setText(is_paused ? "⏸" : "▶"); - parser->replay->pause(!is_paused); + can->pause(!is_paused); }); } -void VideoWidget::setPosition(int value) { - time_label->setText(formatTime(value / 1000.0)); - parser->seekTo(value / 1000.0); -} - void VideoWidget::rangeChanged(double min, double max) { - if (!parser->isZoomed()) { + if (!can->isZoomed()) { min = 0; - max = parser->replay->totalSeconds(); + max = can->totalSeconds(); } - time_label->setText(formatTime(min)); - total_time_label->setText(formatTime(max)); + end_time_label->setText(formatTime(max)); slider->setMinimum(min * 1000); slider->setMaximum(max * 1000); - slider->setValue(parser->currentSec() * 1000); } void VideoWidget::updateState() { - if (!slider->isSliderDown()) { - double current_sec = parser->currentSec(); - time_label->setText(formatTime(current_sec)); - slider->setValue(current_sec * 1000); - } + if (!slider->isSliderDown()) + slider->setValue(can->currentSec() * 1000); } // Slider @@ -102,7 +88,7 @@ Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { QTimer *timer = new QTimer(this); timer->setInterval(2000); timer->callOnTimeout([this]() { - timeline = parser->replay->getTimeline(); + timeline = can->getTimeline(); update(); }); timer->start(); @@ -110,7 +96,7 @@ Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { void Slider::sliderChange(QAbstractSlider::SliderChange change) { if (change == QAbstractSlider::SliderValueChange) { - qreal x = width() * ((value() - minimum()) / double(maximum() - minimum())); + int x = width() * ((value() - minimum()) / double(maximum() - minimum())); if (x != slider_x) { slider_x = x; update(); @@ -121,45 +107,34 @@ void Slider::sliderChange(QAbstractSlider::SliderChange change) { } void Slider::paintEvent(QPaintEvent *ev) { - auto getPaintRange = [this](double begin, double end) -> std::pair { - double total_sec = maximum() - minimum(); - int start_pos = ((std::max((double)minimum(), (double)begin) - minimum()) / total_sec) * width(); - int end_pos = ((std::min((double)maximum(), (double)end) - minimum()) / total_sec) * width(); - return {start_pos, end_pos}; - }; + static const QColor colors[] = { + [(int)TimelineType::None] = QColor(111, 143, 175), + [(int)TimelineType::Engaged] = QColor(0, 163, 108), + [(int)TimelineType::UserFlag] = Qt::white, + [(int)TimelineType::AlertInfo] = Qt::green, + [(int)TimelineType::AlertWarning] = QColor(255, 195, 0), + [(int)TimelineType::AlertCritical] = QColor(199, 0, 57)}; QPainter p(this); - const int v_margin = 2; - p.fillRect(rect().adjusted(0, v_margin, 0, -v_margin), QColor(111, 143, 175)); - + QRect r = rect().adjusted(0, 4, 0, -4); + p.fillRect(r, colors[(int)TimelineType::None]); + double min = minimum() / 1000.0; + double max = maximum() / 1000.0; for (auto [begin, end, type] : timeline) { - begin *= 1000; - end *= 1000; - if (begin > maximum() || end < minimum()) continue; - - if (type == TimelineType::Engaged) { - auto [start_pos, end_pos] = getPaintRange(begin, end); - p.fillRect(QRect(start_pos, v_margin, end_pos - start_pos, height() - v_margin * 2), QColor(0, 163, 108)); - } + if (begin > max || end < min) + continue; + r.setLeft(((std::max(min, (double)begin) - min) / (max - min)) * width()); + r.setRight(((std::min(max, (double)end) - min) / (max - min)) * width()); + p.fillRect(r, colors[(int)type]); } - for (auto [begin, end, type] : timeline) { - begin *= 1000; - end *= 1000; - if (type == TimelineType::Engaged || begin > maximum() || end < minimum()) continue; - - auto [start_pos, end_pos] = getPaintRange(begin, end); - if (type == TimelineType::UserFlag) { - p.fillRect(QRect(start_pos, height() - v_margin - 3, end_pos - start_pos, 3), Qt::white); - } else { - QColor color(Qt::green); - if (type != TimelineType::AlertInfo) - color = type == TimelineType::AlertWarning ? QColor(255, 195, 0) : QColor(199, 0, 57); - - p.fillRect(QRect(start_pos, height() - v_margin - 3, end_pos - start_pos, 3), color); - } - } - p.setPen(QPen(QColor(88, 24, 69), 3)); - p.drawLine(QPoint{slider_x, 0}, QPoint{slider_x, height()}); + + QStyleOptionSlider opt; + opt.initFrom(this); + opt.minimum = minimum(); + opt.maximum = maximum(); + opt.subControls = QStyle::SC_SliderHandle; + opt.sliderPosition = value(); + style()->drawComplexControl(QStyle::CC_Slider, &opt, &p); } void Slider::mousePressEvent(QMouseEvent *e) { @@ -167,6 +142,6 @@ void Slider::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton && !isSliderDown()) { int value = minimum() + ((maximum() - minimum()) * e->x()) / width(); setValue(value); - emit setPosition(value); + emit sliderReleased(); } } diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 060565d322..e80e3b48f9 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -5,24 +5,19 @@ #include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "tools/replay/replay.h" +#include "tools/cabana/canmessages.h" class Slider : public QSlider { Q_OBJECT public: Slider(QWidget *parent); - void mousePressEvent(QMouseEvent* e) override; + void mousePressEvent(QMouseEvent *e) override; void sliderChange(QAbstractSlider::SliderChange change) override; - -signals: - void setPosition(int value); - -private: - void paintEvent(QPaintEvent *ev) override; - std::vector> timeline; + void paintEvent(QPaintEvent *ev) override; int slider_x = -1; + std::vector> timeline; }; class VideoWidget : public QWidget { @@ -34,9 +29,8 @@ public: protected: void rangeChanged(double min, double max); void updateState(); - void setPosition(int value); CameraViewWidget *cam_widget; - QLabel *time_label, *total_time_label; + QLabel *end_time_label; Slider *slider; }; diff --git a/tools/replay/main.cc b/tools/replay/main.cc index d3d6894877..40dace0c91 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -4,8 +4,6 @@ #include "tools/replay/consoleui.h" #include "tools/replay/replay.h" -const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; - int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 725bd1a27e..fbb36bd1ed 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -7,6 +7,8 @@ #include "tools/replay/camera.h" #include "tools/replay/route.h" +const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; + // one segment uses about 100M of memory constexpr int FORWARD_SEGS = 5; diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index d6482c3ca2..5b61b6b6f2 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -9,7 +9,6 @@ #include "tools/replay/replay.h" #include "tools/replay/util.h" -const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; const std::string TEST_RLOG_CHECKSUM = "5b966d4bb21a100a8c4e59195faeb741b975ccbe268211765efd1763d892bfb3"; From 47b19ff14891947bc5db02f3378b5eceba08ee85 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Wed, 12 Oct 2022 16:59:39 -0400 Subject: [PATCH 130/178] VW PQ: Volkswagen Sharan Mk2 / SEAT Alhambra Mk2 (#25839) * initial Sharan support * placeholder torque data * oops * add route * min speeds and PQ default delay --- selfdrive/car/tests/routes.py | 1 + selfdrive/car/torque_data/override.yaml | 1 + selfdrive/car/volkswagen/interface.py | 7 +++++++ selfdrive/car/volkswagen/values.py | 19 ++++++++++++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 82c0614493..ac56cc106b 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -175,6 +175,7 @@ routes = [ CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.PASSAT_MK8), CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.PASSAT_NMS), CarTestRoute("0cd0b7f7e31a3853|2021-11-03--19-30-22", VOLKSWAGEN.POLO_MK6), + CarTestRoute("064d1816e448f8eb|2022-09-29--15-32-34", VOLKSWAGEN.SHARAN_MK2), CarTestRoute("7d82b2f3a9115f1f|2021-10-21--15-39-42", VOLKSWAGEN.TAOS_MK1), CarTestRoute("2744c89a8dda9a51|2021-07-24--21-28-06", VOLKSWAGEN.TCROSS_MK1), CarTestRoute("2cef8a0b898f331a|2021-03-25--20-13-57", VOLKSWAGEN.TIGUAN_MK2), diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index b5d1a31193..889eeffb25 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -30,6 +30,7 @@ CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] +VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] HYUNDAI TUCSON HYBRID 4TH GEN: [2.5, 2.5, 0.0] # Dashcam or fallback configured as ideal car diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 821eef44c7..4724fe81bf 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -127,6 +127,13 @@ class CarInterface(CarInterfaceBase): ret.mass = 1230 + STD_CARGO_KG ret.wheelbase = 2.55 + elif candidate == CAR.SHARAN_MK2: + ret.mass = 1639 + STD_CARGO_KG + ret.wheelbase = 2.92 + ret.minEnableSpeed = 30 * CV.KPH_TO_MS + ret.minSteerSpeed = 50 * CV.KPH_TO_MS + ret.steerActuatorDelay = 0.2 + elif candidate == CAR.TAOS_MK1: ret.mass = 1498 + STD_CARGO_KG ret.wheelbase = 2.69 diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index bdd1b5089d..e0bcd02600 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -116,6 +116,7 @@ class CAR: PASSAT_MK8 = "VOLKSWAGEN PASSAT 8TH GEN" # Chassis 3G, Mk8 VW Passat and variants PASSAT_NMS = "VOLKSWAGEN PASSAT NMS" # Chassis A3, North America/China/Mideast NMS Passat, incl. facelift POLO_MK6 = "VOLKSWAGEN POLO 6TH GEN" # Chassis AW, Mk6 VW Polo + SHARAN_MK2 = "VOLKSWAGEN SHARAN 2ND GEN" # Chassis 7N, Mk2 Volkswagen Sharan and SEAT Alhambra TAOS_MK1 = "VOLKSWAGEN TAOS 1ST GEN" # Chassis B2, Mk1 VW Taos and Tharu TCROSS_MK1 = "VOLKSWAGEN T-CROSS 1ST GEN" # Chassis C1, Mk1 VW T-Cross SWB and LWB variants TIGUAN_MK2 = "VOLKSWAGEN TIGUAN 2ND GEN" # Chassis AD/BW, Mk2 VW Tiguan and variants @@ -135,7 +136,7 @@ class CAR: SKODA_OCTAVIA_MK3 = "SKODA OCTAVIA 3RD GEN" # Chassis NE, Mk3 Skoda Octavia and variants -PQ_CARS = {CAR.PASSAT_NMS} +PQ_CARS = {CAR.PASSAT_NMS, CAR.SHARAN_MK2} DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("vw_mqb_2010", None)) @@ -206,6 +207,10 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), ], + CAR.SHARAN_MK2: [ + VWCarInfo("Volkswagen Sharan 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("SEAT Alhambra 2018-20", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + ], CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), @@ -625,6 +630,18 @@ FW_VERSIONS = { b'\xf1\x872Q0907572R \xf1\x890372', ], }, + CAR.SHARAN_MK2: { + # TODO: Sharan Mk2 EPS and DQ250 auto trans both require KWP2000 support for fingerprinting + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906016HE\xf1\x894635', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x877N0959655D \xf1\x890016\xf1\x82\x0801100705----10--', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x877N0907572C \xf1\x890211\xf1\x82\x0153', + ], + }, CAR.TAOS_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027NJ\xf1\x891445', From ac88ad871a9fba9076eb4d02a57e34ad34839f59 Mon Sep 17 00:00:00 2001 From: Jason Shuler Date: Wed, 12 Oct 2022 17:02:01 -0400 Subject: [PATCH 131/178] GM: disable checks on loopback bus (#26015) * disabling checks on loopback bus * Apply suggestions from code review Co-authored-by: Shane Smiskol --- selfdrive/car/gm/carstate.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 11c521e863..a5f89c58b0 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -177,9 +177,7 @@ class CarState(CarStateBase): ] checks = [ - ("ASCMLKASteeringCmd", 10), # 10 Hz is the stock inactive rate (every 100ms). - # While active 50 Hz (every 20 ms) is normal - # EPS will tolerate around 200ms when active before faulting + ("ASCMLKASteeringCmd", 0), ] - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.LOOPBACK) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.LOOPBACK, enforce_checks=False) From 03f06b0e32c823df2321ca4e6b0077b5e112f734 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 12 Oct 2022 14:03:44 -0700 Subject: [PATCH 132/178] ci: don't cancel concurrent workflows for master branch (#26047) don't cancel for master branch --- .github/workflows/selfdrive_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 9c38ba0cbc..ab70b8e7ef 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -8,7 +8,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: BASE_IMAGE: openpilot-base From 3c0904a18f4acb852193de9956965df2520104d1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 12 Oct 2022 14:22:40 -0700 Subject: [PATCH 133/178] EV6 longitudinal (#26023) * ev6 long * update refs --- panda | 2 +- selfdrive/car/hyundai/carcontroller.py | 94 ++++++++++--------- selfdrive/car/hyundai/carstate.py | 40 +++++---- selfdrive/car/hyundai/hyundaicanfd.py | 109 +++++++++++++++++++++-- selfdrive/car/hyundai/interface.py | 66 ++++++++------ selfdrive/test/process_replay/ref_commit | 2 +- 6 files changed, 219 insertions(+), 94 deletions(-) diff --git a/panda b/panda index d68b1b0a98..ffb3109e28 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d68b1b0a98d5cefad438180e3c2ffcdcbcffdd76 +Subproject commit ffb3109e28296cc86f1892c4e0690856e28fb35a diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index 2b0d6b9648..bcbab2ab94 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -52,17 +52,15 @@ class CarController: self.apply_steer_last = 0 self.car_fingerprint = CP.carFingerprint self.last_button_frame = 0 - self.accel = 0 def update(self, CC, CS): actuators = CC.actuators hud_control = CC.hudControl - # Steering Torque - - # These cars have significantly more torque than most HKG. Limit to 70% of max. + # steering torque steer = actuators.steer if self.CP.carFingerprint in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): + # these cars have significantly more torque than most HKG; limit to 70% of max steer = clip(steer, -0.7, 0.7) new_steer = int(round(steer * self.params.STEER_MAX)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) @@ -72,48 +70,66 @@ class CarController: self.apply_steer_last = apply_steer + # accel + longitudinal + accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) + stopping = actuators.longControlState == LongCtrlState.stopping + set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_KPH if CS.is_metric else CV.MS_TO_MPH) + + # HUD messages sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint, hud_control) can_sends = [] + # *** common hyundai stuff *** + + # tester present - w/ no response (keeps relevant ECU disabled) + if self.frame % 100 == 0 and self.CP.openpilotLongitudinalControl: + addr, bus = 0x7d0, 0 + if self.CP.flags & HyundaiFlags.CANFD_HDA2.value: + addr, bus = 0x730, 5 + can_sends.append([addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus]) + + # CAN-FD platforms if self.CP.carFingerprint in CANFD_CAR: + hda2 = self.CP.flags & HyundaiFlags.CANFD_HDA2 + hda2_long = hda2 and self.CP.openpilotLongitudinalControl + # steering control - can_sends.append(hyundaicanfd.create_lkas(self.packer, self.CP, CC.enabled, CC.latActive, apply_steer)) + can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, CC.enabled, CC.latActive, apply_steer)) - # block LFA on HDA2 - if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.CANFD_HDA2): + # disable LFA on HDA2 + if self.frame % 5 == 0 and hda2: can_sends.append(hyundaicanfd.create_cam_0x2a4(self.packer, CS.cam_0x2a4)) # LFA and HDA icons - if self.frame % 2 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_HDA2): - can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, CC.enabled)) + if self.frame % 5 == 0 and (not hda2 or hda2_long): + can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CP, CC.enabled)) - # button presses - if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: - # cruise cancel - if CC.cruiseControl.cancel: - if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: - can_sends.append(hyundaicanfd.create_cruise_info(self.packer, CS.cruise_info_copy, True)) - self.last_button_frame = self.frame - else: - for _ in range(20): - can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.CANCEL)) - self.last_button_frame = self.frame - - # cruise standstill resume - elif CC.cruiseControl.resume: - if not (self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): - for _ in range(20): - can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.RES_ACCEL)) - self.last_button_frame = self.frame - else: - - # tester present - w/ no response (keeps radar disabled) if self.CP.openpilotLongitudinalControl: - if self.frame % 100 == 0: - can_sends.append([0x7D0, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 0]) - + can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) + if self.frame % 2 == 0: + can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, accel, stopping, CC.cruiseControl.override, + set_speed_in_units)) + else: + # button presses + if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: + # cruise cancel + if CC.cruiseControl.cancel: + if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: + self.last_button_frame = self.frame + else: + for _ in range(20): + can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.CANCEL)) + self.last_button_frame = self.frame + + # cruise standstill resume + elif CC.cruiseControl.resume: + if not (self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): + for _ in range(20): + can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.RES_ACCEL)) + self.last_button_frame = self.frame + else: # Count up to MAX_ANGLE_FRAMES, at which point we need to cut torque to avoid a steering fault if CC.latActive and abs(CS.out.steeringAngleDeg) >= MAX_ANGLE: self.angle_limit_counter += 1 @@ -143,18 +159,10 @@ class CarController: self.last_button_frame = self.frame if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl: - accel = actuators.accel - - #TODO unclear if this is needed + # TODO: unclear if this is needed jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0 - - accel = clip(accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) - - stopping = actuators.longControlState == LongCtrlState.stopping - set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_MPH if CS.clu11["CF_Clu_SPEED_UNIT"] == 1 else CV.MS_TO_KPH) can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2), hud_control.leadVisible, set_speed_in_units, stopping, CC.cruiseControl.override)) - self.accel = accel # 20 Hz LFA MFA message if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, @@ -173,7 +181,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = apply_steer / self.params.STEER_MAX - new_actuators.accel = self.accel + new_actuators.accel = accel self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 4d60afe1fd..4c2650c19f 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -30,6 +30,7 @@ class CarState(CarStateBase): else: # preferred and elect gear methods use same definition self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"] + self.is_metric = False self.brake_error = False self.buttons_counter = 0 @@ -45,8 +46,8 @@ class CarState(CarStateBase): ret = car.CarState.new_message() cp_cruise = cp_cam if self.CP.carFingerprint in CAMERA_SCC_CAR else cp - is_metric = cp.vl["CLU11"]["CF_Clu_SPEED_UNIT"] == 0 - speed_conv = CV.KPH_TO_MS if is_metric else CV.MPH_TO_MS + self.is_metric = cp.vl["CLU11"]["CF_Clu_SPEED_UNIT"] == 0 + speed_conv = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS ret.doorOpen = any([cp.vl["CGW1"]["CF_Gway_DrvDrSw"], cp.vl["CGW1"]["CF_Gway_AstDrSw"], cp.vl["CGW2"]["CF_Gway_RLDrSw"], cp.vl["CGW2"]["CF_Gway_RRDrSw"]]) @@ -69,7 +70,7 @@ class CarState(CarStateBase): self.cluster_speed_counter = 0 # mimic how dash converts to imperial - if not is_metric: + if not self.is_metric: self.cluster_speed = math.floor(self.cluster_speed * CV.KPH_TO_MPH + CV.KPH_TO_MPH) ret.vEgoCluster = self.cluster_speed * speed_conv @@ -187,16 +188,19 @@ class CarState(CarStateBase): ret.cruiseState.available = True ret.cruiseState.enabled = cp.vl["SCC1"]["CRUISE_ACTIVE"] == 1 - cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam - speed_factor = CV.MPH_TO_MS if cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] == 1 else CV.KPH_TO_MS - ret.cruiseState.speed = cp_cruise_info.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor - ret.cruiseState.standstill = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STANDSTILL"] == 1 + self.is_metric = cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] != 1 + if not self.CP.openpilotLongitudinalControl: + speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS + cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam + ret.cruiseState.speed = cp_cruise_info.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor + ret.cruiseState.standstill = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STANDSTILL"] == 1 cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" + self.prev_cruise_buttons = self.cruise_buttons[-1] self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"]) self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"]) self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"] - self.cruise_info_copy = copy.copy(cp_cruise_info.vl["CRUISE_INFO"]) + self.cruise_info_copy = {} if self.CP.flags & HyundaiFlags.CANFD_HDA2: self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) @@ -448,13 +452,18 @@ class CarState(CarStateBase): signals += [ ("ACCELERATOR_PEDAL", "ACCELERATOR"), ("GEAR", "ACCELERATOR"), - ("SET_SPEED", "CRUISE_INFO"), - ("CRUISE_STANDSTILL", "CRUISE_INFO"), ] checks += [ - ("CRUISE_INFO", 50), ("ACCELERATOR", 100), ] + if not CP.openpilotLongitudinalControl: + signals += [ + ("SET_SPEED", "CRUISE_INFO"), + ("CRUISE_STANDSTILL", "CRUISE_INFO"), + ] + checks += [ + ("CRUISE_INFO", 50), + ] else: signals += [ ("ACCELERATOR_PEDAL", "ACCELERATOR_ALT"), @@ -478,17 +487,14 @@ class CarState(CarStateBase): ("CRUISE_MAIN", "CRUISE_INFO"), ("CRUISE_STATUS", "CRUISE_INFO"), ("CRUISE_INACTIVE", "CRUISE_INFO"), - ("NEW_SIGNAL_2", "CRUISE_INFO"), + ("ZEROS_9", "CRUISE_INFO"), ("CRUISE_STANDSTILL", "CRUISE_INFO"), - ("NEW_SIGNAL_3", "CRUISE_INFO"), - ("BYTE11", "CRUISE_INFO"), + ("ZEROS_5", "CRUISE_INFO"), + ("DISTANCE_SETTING", "CRUISE_INFO"), ("SET_SPEED", "CRUISE_INFO"), ("NEW_SIGNAL_4", "CRUISE_INFO"), ] - signals += [(f"BYTE{i}", "CRUISE_INFO") for i in range(3, 7)] - signals += [(f"BYTE{i}", "CRUISE_INFO") for i in range(13, 31)] - checks = [ ("CRUISE_INFO", 50), ] diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index a53be7627d..9d4e4d4e0d 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -1,7 +1,17 @@ from selfdrive.car.hyundai.values import HyundaiFlags -def create_lkas(packer, CP, enabled, lat_active, apply_steer): +def get_e_can_bus(CP): + # On the CAN-FD platforms, the LKAS camera is on both A-CAN and E-CAN. HDA2 cars + # have a a different harness than the HDA1 and non-HDA variants in order to split + # a different bus, since the steering is done by different ECUs. + return 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4 + + +def create_steering_messages(packer, CP, enabled, lat_active, apply_steer): + + ret = [] + values = { "LKA_MODE": 2, "LKA_ICON": 2 if enabled else 1, @@ -14,8 +24,14 @@ def create_lkas(packer, CP, enabled, lat_active, apply_steer): "NEW_SIGNAL_2": 0, } - msg = "LKAS" if CP.flags & HyundaiFlags.CANFD_HDA2 else "LFA" - return packer.make_can_msg(msg, 4, values) + if CP.flags & HyundaiFlags.CANFD_HDA2: + if CP.openpilotLongitudinalControl: + ret.append(packer.make_can_msg("LFA", 5, values)) + ret.append(packer.make_can_msg("LKAS", 4, values)) + else: + ret.append(packer.make_can_msg("LFA", 4, values)) + + return ret def create_cam_0x2a4(packer, camera_values): camera_values.update({ @@ -36,11 +52,92 @@ def create_cruise_info(packer, cruise_info_copy, cancel): if cancel: values["CRUISE_STATUS"] = 0 values["CRUISE_INACTIVE"] = 1 - return packer.make_can_msg("CRUISE_INFO", 4, values) + return packer.make_can_msg("CRUISE_INFO", 5, values) -def create_lfahda_cluster(packer, enabled): +def create_lfahda_cluster(packer, CP, enabled): values = { "HDA_ICON": 1 if enabled else 0, "LFA_ICON": 2 if enabled else 0, } - return packer.make_can_msg("LFAHDA_CLUSTER", 4, values) + return packer.make_can_msg("LFAHDA_CLUSTER", get_e_can_bus(CP), values) + + +def create_acc_control(packer, CP, enabled, accel, stopping, gas_override, set_speed): + cruise_status = 0 if not enabled else (4 if gas_override else 2) + if not enabled or gas_override: + accel = 0 + values = { + "CRUISE_STATUS": cruise_status, + "CRUISE_INACTIVE": 0 if enabled else 1, + "CRUISE_MAIN": 1, + "CRUISE_STANDSTILL": 0, + "STOP_REQ": 1 if stopping else 0, + "ACCEL_REQ": accel, + "ACCEL_REQ2": accel, + "SET_SPEED": set_speed, + "DISTANCE_SETTING": 4, + + "ACC_ObjDist": 1, + "ObjValid": 1, + "OBJ_STATUS": 2, + "SET_ME_2": 0x2, + "SET_ME_3": 0x3, + "SET_ME_TMP_64": 0x64, + + "NEW_SIGNAL_9": 2, + "NEW_SIGNAL_10": 4, + } + + return packer.make_can_msg("CRUISE_INFO", get_e_can_bus(CP), values) + + + +def create_adrv_messages(packer, frame): + # messages needed to car happy after disabling + # the ADAS Driving ECU to do longitudinal control + + ret = [] + + values = { + } + ret.append(packer.make_can_msg("ADRV_0x51", 4, values)) + + if frame % 2 == 0: + values = { + 'AEB_SETTING': 0x1, # show AEB disabled icon + 'SET_ME_2': 0x2, + 'SET_ME_FF': 0xff, + 'SET_ME_FC': 0xfc, + 'SET_ME_9': 0x9, + } + ret.append(packer.make_can_msg("ADRV_0x160", 5, values)) + + if frame % 5 == 0: + values = { + 'SET_ME_1C': 0x1c, + 'SET_ME_FF': 0xff, + 'SET_ME_TMP_F': 0xf, + 'SET_ME_TMP_F_2': 0xf, + } + ret.append(packer.make_can_msg("ADRV_0x1ea", 5, values)) + + values = { + 'SET_ME_E1': 0xe1, + 'SET_ME_3A': 0x3a, + } + ret.append(packer.make_can_msg("ADRV_0x200", 5, values)) + + if frame % 20 == 0: + values = { + 'SET_ME_15': 0x15, + } + ret.append(packer.make_can_msg("ADRV_0x345", 5, values)) + + if frame % 100 == 0: + values = { + 'SET_ME_22': 0x22, + 'SET_ME_41': 0x41, + } + ret.append(packer.make_can_msg("ADRV_0x1da", 5, values)) + + return ret diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 2d960ed17f..024d7498fa 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -27,31 +27,24 @@ class CarInterface(CarInterfaceBase): ret.carName = "hyundai" ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None - # WARNING: disabling radar also disables AEB (and we show the same warning on the instrument cluster as if you manually disabled AEB) - ret.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR | CANFD_CAR) - ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable - - ret.pcmCruise = not ret.openpilotLongitudinalControl - # 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, CAR.ELANTRA_GT_I30} + if candidate in CANFD_CAR: + # detect HDA2 with LKAS message + if 0x50 in fingerprint[6]: + ret.flags |= HyundaiFlags.CANFD_HDA2.value + else: + # non-HDA2 + if 0x1cf not in fingerprint[4]: + ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value + ret.steerActuatorDelay = 0.1 # Default delay ret.steerLimitTimer = 0.4 tire_stiffness_factor = 1. - ret.stoppingControl = True - ret.startingState = True - ret.vEgoStarting = 0.1 - ret.startAccel = 2.0 - - ret.longitudinalTuning.kpV = [0.5] - ret.longitudinalTuning.kiV = [0.0] - - ret.longitudinalActuatorDelayLowerBound = 0.5 # s - ret.longitudinalActuatorDelayUpperBound = 0.5 # s if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): ret.lateralTuning.pid.kf = 0.00005 ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG @@ -291,20 +284,38 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.01]] - # panda safety config + # *** longitudinal control *** + if candidate in CANFD_CAR: + ret.longitudinalTuning.kpV = [0.1] + ret.longitudinalTuning.kiV = [0.0] + ret.longitudinalActuatorDelayLowerBound = 0.15 + ret.longitudinalActuatorDelayUpperBound = 0.5 + ret.experimentalLongitudinalAvailable = bool(ret.flags & HyundaiFlags.CANFD_HDA2) + else: + ret.longitudinalTuning.kpV = [0.5] + ret.longitudinalTuning.kiV = [0.0] + ret.longitudinalActuatorDelayLowerBound = 0.5 + ret.longitudinalActuatorDelayUpperBound = 0.5 + ret.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR) + ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable + ret.pcmCruise = not ret.openpilotLongitudinalControl + + ret.stoppingControl = True + ret.startingState = True + ret.vEgoStarting = 0.1 + ret.startAccel = 2.0 + + # *** panda safety config *** if candidate in CANFD_CAR: ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput), get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd)] - # detect HDA2 with LKAS message - if 0x50 in fingerprint[6]: - ret.flags |= HyundaiFlags.CANFD_HDA2.value + if ret.openpilotLongitudinalControl: + ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_LONG + if ret.flags & HyundaiFlags.CANFD_HDA2: ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 - else: - # non-HDA2 - if 0x1cf not in fingerprint[4]: - ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value - ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS + if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS: + ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS else: ret.enableBsm = 0x58b in fingerprint[0] @@ -342,7 +353,10 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, logcan, sendcan): if CP.openpilotLongitudinalControl: - disable_ecu(logcan, sendcan, addr=0x7d0, com_cont_req=b'\x28\x83\x01') + addr, bus = 0x7d0, 0 + if CP.flags & HyundaiFlags.CANFD_HDA2.value: + addr, bus = 0x730, 5 + disable_ecu(logcan, sendcan, bus=bus, addr=addr, com_cont_req=b'\x28\x83\x01') def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 6b64fcb5aa..4577d3cddf 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -5fa6a3743f2678eef13267fb946d7a03f2af5824 +27022484e3f4c0265ee7243154659b2697de3af7 \ No newline at end of file From 80259f377f8b1aa3a337cccbc3b00bd41898cf44 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 12 Oct 2022 14:23:12 -0700 Subject: [PATCH 134/178] Ford: cleanup and fix button press (#26033) * cleanup * use Veh_V_ActlBrk for vEgoRaw * remove unused CarState.yaw_data * less resume spam * set steering ramp rate * match DBC range * add LCA/TJA notes --- selfdrive/car/ford/carcontroller.py | 50 ++++++++++++++++------------- selfdrive/car/ford/carstate.py | 8 ++--- selfdrive/car/ford/fordcan.py | 29 ++++++++++------- selfdrive/car/ford/interface.py | 9 +++--- selfdrive/car/ford/values.py | 29 ++++++++--------- 5 files changed, 67 insertions(+), 58 deletions(-) diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 592d8586ca..f18014601c 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -3,7 +3,7 @@ from cereal import car from common.numpy_fast import clip, interp from opendbc.can.packer import CANPacker from selfdrive.car.ford import fordcan -from selfdrive.car.ford.values import CarControllerParams +from selfdrive.car.ford.values import CANBUS, CarControllerParams VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -16,9 +16,9 @@ def apply_ford_steer_angle_limits(apply_angle, apply_angle_last, vEgo): apply_angle = clip(apply_angle, (apply_angle_last - max_angle_diff), (apply_angle_last + max_angle_diff)) # absolute limit (LatCtlPath_An_Actl) - apply_path_angle = math.radians(apply_angle) / CarControllerParams.STEER_RATIO - apply_path_angle = clip(apply_path_angle, -0.4995, 0.5240) - apply_angle = math.degrees(apply_path_angle) * CarControllerParams.STEER_RATIO + apply_path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO + apply_path_angle = clip(apply_path_angle, -0.5, 0.5235) + apply_angle = math.degrees(apply_path_angle) * CarControllerParams.LKAS_STEER_RATIO return apply_angle @@ -47,40 +47,46 @@ class CarController: ### acc buttons ### if CC.cruiseControl.cancel: can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True)) - elif CC.cruiseControl.resume: + can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main)) + elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0: can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True)) - - # if stock lane centering is active or in standby, toggle it off + can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main)) + # 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 - if (self.frame % 200) == 0 and CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0: + elif CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0 and (self.frame % CarControllerParams.ACC_UI_STEP) == 0: can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, tja_toggle=True)) ### lateral control ### if CC.latActive: + lca_rq = 1 apply_angle = apply_ford_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo) else: - apply_angle = CS.out.steeringAngleDeg + lca_rq = 0 + apply_angle = 0. # send steering commands at 20Hz if (self.frame % CarControllerParams.LKAS_STEER_STEP) == 0: - lca_rq = 1 if CC.latActive else 0 - # use LatCtlPath_An_Actl to actuate steering - # path angle is the car wheel angle, not the steering wheel angle - path_angle = math.radians(apply_angle) / CarControllerParams.STEER_RATIO - - # ramp rate: 0=Slow, 1=Medium, 2=Fast, 3=Immediately - # TODO: try slower ramp speed when driver torque detected - ramp_type = 3 + path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO + + # set slower ramp type when small steering angle change + # 0=Slow, 1=Medium, 2=Fast, 3=Immediately + steer_change = abs(CS.out.steeringAngleDeg - actuators.steeringAngleDeg) + if steer_change < 2.0: + ramp_type = 0 + elif steer_change < 4.0: + ramp_type = 1 + elif steer_change < 6.0: + ramp_type = 2 + else: + ramp_type = 3 precision = 1 # 0=Comfortable, 1=Precise (the stock system always uses comfortable) - offset_roll_compensation_curvature = clip(self.VM.calc_curvature(0, CS.out.vEgo, -CS.yaw_data["VehYaw_W_Actl"]), -0.02, 0.02094) - self.apply_angle_last = apply_angle - can_sends.append(fordcan.create_lka_command(self.packer, apply_angle, 0)) + can_sends.append(fordcan.create_lka_command(self.packer, 0, 0)) can_sends.append(fordcan.create_tja_command(self.packer, lca_rq, ramp_type, precision, - 0, path_angle, 0, offset_roll_compensation_curvature)) + 0, path_angle, 0, 0)) ### ui ### @@ -99,7 +105,7 @@ class CarController: self.steer_alert_last = steer_alert new_actuators = actuators.copy() - new_actuators.steeringAngleDeg = apply_angle + new_actuators.steeringAngleDeg = self.apply_angle_last self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index a7ea19effc..2276b1208a 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -20,7 +20,7 @@ class CarState(CarStateBase): ret = car.CarState.new_message() # car speed - ret.vEgoRaw = cp.vl["EngVehicleSpThrottle2"]["Veh_V_ActlEng"] * CV.KPH_TO_MS + ret.vEgoRaw = cp.vl["BrakeSysFeatures"]["Veh_V_ActlBrk"] * CV.KPH_TO_MS ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.yawRate = cp.vl["Yaw_Data_FD1"]["VehYaw_W_Actl"] ret.standstill = cp.vl["DesiredTorqBrk"]["VehStop_D_Stat"] == 1 @@ -85,8 +85,6 @@ class CarState(CarStateBase): # Stock values from IPMA so that we can retain some stock functionality self.acc_tja_status_stock_values = cp_cam.vl["ACCDATA_3"] self.lkas_status_stock_values = cp_cam.vl["IPMA_Data"] - # Use stock sensor values - self.yaw_data = cp.vl["Yaw_Data_FD1"] return ret @@ -94,7 +92,7 @@ class CarState(CarStateBase): def get_can_parser(CP): signals = [ # sig_name, sig_address - ("Veh_V_ActlEng", "EngVehicleSpThrottle2"), # ABS vehicle speed (kph) + ("Veh_V_ActlBrk", "BrakeSysFeatures"), # ABS vehicle speed (kph) ("VehYaw_W_Actl", "Yaw_Data_FD1"), # ABS vehicle yaw rate (rad/s) ("VehStop_D_Stat", "DesiredTorqBrk"), # ABS vehicle stopped ("PrkBrkStatus", "DesiredTorqBrk"), # ABS park brake status @@ -156,7 +154,7 @@ class CarState(CarStateBase): checks = [ # sig_address, frequency - ("EngVehicleSpThrottle2", 50), + ("BrakeSysFeatures", 50), ("Yaw_Data_FD1", 100), ("DesiredTorqBrk", 50), ("EngVehicleSpThrottle", 100), diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index b42561df21..373ce096c6 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -8,8 +8,7 @@ def create_lka_command(packer, angle_deg: float, curvature: float): """ Creates a CAN message for the Ford LKAS Command. - This command can apply "Lane Keeping Aid" manoeuvres, which are subject to the - PSCM lockout. + This command can apply "Lane Keeping Aid" manoeuvres, which are subject to the PSCM lockout. Frequency is 20Hz. """ @@ -30,12 +29,20 @@ def create_tja_command(packer, lca_rq: int, ramp_type: int, precision: int, path """ Creates a CAN message for the Ford TJA/LCA Command. - This command can apply "Lane Centering" manoeuvres: continuous lane centering - for traffic jam assist and highway driving. It is not subject to the PSCM - lockout. + This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam + assist and highway driving. It is not subject to the PSCM lockout. - The PSCM should be configured to accept TJA/LCA commands before these - commands will be processed. This can be done using tools such as Forscan. + Ford lane centering command uses a third order polynomial to describe the road centerline. The + polynomial is defined by the following coefficients: + c0: lateral offset between the vehicle and the centerline + c1: heading angle between the vehicle and the centerline + c2: curvature of the centerline + c3: rate of change of curvature of the centerline + As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and + speed, the steering angle cannot be easily controlled. + + The PSCM should be configured to accept TJA/LCA commands before these commands will be processed. + This can be done using tools such as Forscan. Frequency is 20Hz. """ @@ -47,7 +54,7 @@ def create_tja_command(packer, lca_rq: int, ramp_type: int, precision: int, path "LatCtlRampType_D_Rq": ramp_type, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3] "LatCtlPrecision_D_Rq": precision, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3] "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter - "LatCtlPath_An_Actl": path_angle, # Path angle [-0.4995|0.5240] radians + "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians "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 } @@ -108,8 +115,8 @@ def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bo def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict): """ - Creates a CAN message for the Ford IPC adaptive cruise, forward collision - warning and traffic jam assist status. + Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam + assist status. Stock functionality is maintained by passing through unmodified signals. @@ -141,7 +148,7 @@ def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, sto return packer.make_can_msg("ACCDATA_3", CANBUS.main, values) -def create_button_command(packer, stock_values: dict, cancel = False, resume = False, tja_toggle = False, bus = CANBUS.camera): +def create_button_command(packer, stock_values: dict, cancel = False, resume = False, tja_toggle = False, bus: int = CANBUS.camera): """ 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 7d4c9eb94c..4943db076f 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -5,8 +5,7 @@ from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter from selfdrive.car.interfaces import CarInterfaceBase - -EventName = car.CarEvent.EventName +CarParams = car.CarParams class CarInterface(CarInterfaceBase): @@ -19,10 +18,10 @@ class CarInterface(CarInterfaceBase): ret.carName = "ford" ret.dashcamOnly = True - ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.ford)] + ret.safetyConfigs = [get_safety_config(CarParams.SafetyModel.ford)] # Angle-based steering - ret.steerControlType = car.CarParams.SteerControlType.angle + ret.steerControlType = CarParams.SteerControlType.angle ret.steerActuatorDelay = 0.4 ret.steerLimitTimer = 1.0 tire_stiffness_factor = 1.0 @@ -43,7 +42,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 1350 + STD_CARGO_KG else: - raise ValueError(f"Unsupported car: ${candidate}") + raise ValueError(f"Unsupported car: {candidate}") # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1 found_ecus = [fw.ecu for fw in car_fw] diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 5820b5c9fd..7b3140fbbf 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,4 +1,4 @@ -from collections import namedtuple +from collections import defaultdict, namedtuple from dataclasses import dataclass from enum import Enum from typing import Dict, List, Union @@ -22,19 +22,17 @@ class CarControllerParams: LKAS_UI_STEP = 100 # Message: ACCDATA_3 ACC_UI_STEP = 5 + # Message: Steering_Data_FD1, but send twice as fast + BUTTONS_STEP = 10 / 2 - STEER_RATIO = 2.75 - STEER_DRIVER_ALLOWANCE = 0.8 + LKAS_STEER_RATIO = 2.75 # Approximate ratio between LatCtlPath_An_Actl and steering angle in radians + # TODO: remove this once we understand how the EPS calculates the steering angle better + STEER_DRIVER_ALLOWANCE = 0.8 # Driver intervention threshold in Nm RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15]) RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4]) -class RADAR: - DELPHI_ESR = 'ford_fusion_2018_adas' - DELPHI_MRR = 'FORD_CADS' - - class CANBUS: main = 0 radar = 1 @@ -47,6 +45,14 @@ class CAR: FOCUS_MK4 = "FORD FOCUS 4TH GEN" +class RADAR: + DELPHI_ESR = 'ford_fusion_2018_adas' + DELPHI_MRR = 'FORD_CADS' + + +DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR)) + + @dataclass class FordCarInfo(CarInfo): package: str = "Co-Pilot360 Assist+" @@ -143,10 +149,3 @@ FW_VERSIONS = { ], }, } - - -DBC = { - CAR.ESCAPE_MK4: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR), - CAR.EXPLORER_MK6: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR), - CAR.FOCUS_MK4: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR), -} From 23e78da6a49b8aa43c7d60226aa466a8599be700 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 12 Oct 2022 14:34:43 -0700 Subject: [PATCH 135/178] ci: fix tools workflow --- .github/workflows/tools_tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index c5afaad16c..71f6ed50bc 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -6,7 +6,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} - cancel-in-progress: true + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} env: BASE_IMAGE: openpilot-base From 077f0e0a4433e07330e2c1e22bc1774da1d964fc Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 12 Oct 2022 15:11:03 -0700 Subject: [PATCH 136/178] ci: disable concurrency for master branch (#26052) disable concurrency for master branch --- .github/workflows/selfdrive_tests.yaml | 4 ++-- .github/workflows/tools_tests.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index ab70b8e7ef..cd34c6d27c 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -7,8 +7,8 @@ on: pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} - cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }} + cancel-in-progress: true env: BASE_IMAGE: openpilot-base diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 71f6ed50bc..9dc5c05837 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -5,8 +5,8 @@ on: pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} - cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }} + cancel-in-progress: true env: BASE_IMAGE: openpilot-base From 8b5ebbddf684bc7566ae41447face9f2ab053d40 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 12 Oct 2022 15:14:02 -0700 Subject: [PATCH 137/178] build extras together (#26051) * build extras together * don't check here either --- SConstruct | 9 +++++---- tools/cabana/SConscript | 7 +++---- tools/replay/SConscript | 17 ++++++++--------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/SConstruct b/SConstruct index e015218f2a..23ab37dc1e 100644 --- a/SConstruct +++ b/SConstruct @@ -431,11 +431,12 @@ SConscript(['selfdrive/sensord/SConscript']) SConscript(['selfdrive/ui/SConscript']) SConscript(['selfdrive/navd/SConscript']) -SConscript(['tools/replay/SConscript']) +if arch in ['x86_64', 'Darwin'] or GetOption('extras'): + SConscript(['tools/replay/SConscript']) -opendbc = abspath([File('opendbc/can/libdbc.so')]) -Export('opendbc') -SConscript(['tools/cabana/SConscript']) + opendbc = abspath([File('opendbc/can/libdbc.so')]) + Export('opendbc') + SConscript(['tools/cabana/SConscript']) if GetOption('test'): SConscript('panda/tests/safety/SConscript') diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index b94741ea9c..8dbd4f1d1c 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -11,7 +11,6 @@ else: base_libs.append('OpenCL') qt_libs = ['qt_util', 'Qt5Charts'] + base_libs -if arch in ['x86_64', 'Darwin'] and GetOption('extras'): - cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs - qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', - 'canmessages.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs +qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', + 'canmessages.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 9985375688..4ddeb662e0 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -12,15 +12,14 @@ else: base_libs.append('OpenCL') qt_libs = ['qt_util'] + base_libs -if arch in ['x86_64', 'Darwin'] or GetOption('extras'): - qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] +qt_env['CXXFLAGS'] += ["-Wno-deprecated-declarations"] - replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc"] +replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc"] - replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=qt_libs, FRAMEWORKS=base_frameworks) - Export('replay_lib') - replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + qt_libs - qt_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) +replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=qt_libs, FRAMEWORKS=base_frameworks) +Export('replay_lib') +replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv', 'ncurses'] + qt_libs +qt_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) - if GetOption('test'): - qt_env.Program('tests/test_replay', ['tests/test_runner.cc', 'tests/test_replay.cc'], LIBS=[replay_libs]) +if GetOption('test'): + qt_env.Program('tests/test_replay', ['tests/test_runner.cc', 'tests/test_replay.cc'], LIBS=[replay_libs]) From 549452f84d0f06a92d5c03b710a300acefe8673d Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Wed, 12 Oct 2022 15:14:45 -0700 Subject: [PATCH 138/178] rawgpsd: log + skip unknown responses (#26043) * skip DIAG_EVENT_REPORT_F events * . Co-authored-by: Kurt Nistelberger --- selfdrive/sensord/rawgps/rawgpsd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index 95f8dab1c2..c823ede076 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -180,7 +180,8 @@ def main() -> NoReturn: while 1: opcode, payload = diag.recv() if opcode != DIAG_LOG_F: - cloudlog.exception(f"Unhandled opcode: {opcode}") + cloudlog.error(f"Unhandled opcode: {opcode}") + continue assert opcode == DIAG_LOG_F (pending_msgs, log_outer_length), inner_log_packet = unpack_from(' Date: Wed, 12 Oct 2022 15:40:19 -0700 Subject: [PATCH 139/178] add CAN-FD non-ISO mode support (#25947) CAN FD non-ISO support Co-authored-by: Adeeb Shihadeh --- panda | 2 +- selfdrive/boardd/boardd.cc | 1 + selfdrive/boardd/panda.cc | 4 ++++ selfdrive/boardd/panda.h | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/panda b/panda index ffb3109e28..2f3e2825e5 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit ffb3109e28296cc86f1892c4e0690856e28fb35a +Subproject commit 2f3e2825e5ecc8074b4ee9cb9a70df635d09fd10 diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 3e39985c29..77e86f9458 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -407,6 +407,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector cs[j].setCanDataSpeed(can_health.can_data_speed); cs[j].setCanfdEnabled(can_health.canfd_enabled); cs[j].setBrsEnabled(can_health.brs_enabled); + cs[j].setCanfdNonIso(can_health.canfd_non_iso); } // Convert faults bitset to capnp list diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 7ddf15f9ce..0b8630b0c0 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -360,6 +360,10 @@ void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) { usb_write(0xf9, bus, (speed * 10)); } +void Panda::set_canfd_non_iso(uint16_t bus, bool non_iso) { + usb_write(0xfc, bus, non_iso); +} + static uint8_t len_to_dlc(uint8_t len) { if (len <= 8) { return len; diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index a4afbdac1a..c7a0e7a6c1 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -91,6 +91,7 @@ class Panda { void send_heartbeat(bool engaged); void set_can_speed_kbps(uint16_t bus, uint16_t speed); void set_data_speed_kbps(uint16_t bus, uint16_t speed); + void set_canfd_non_iso(uint16_t bus, bool non_iso); void can_send(capnp::List::Reader can_data_list); bool can_receive(std::vector& out_vec); From 135270f65cbde80c06aee1773b9ca802bd32f419 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Wed, 12 Oct 2022 19:27:23 -0400 Subject: [PATCH 140/178] Allow car port to define enable buttons (#25793) * Allow car port to define enable buttons * simplify * oops --- selfdrive/car/__init__.py | 15 +-------------- selfdrive/car/interfaces.py | 14 +++++++++++--- selfdrive/car/volkswagen/interface.py | 4 +++- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 8ff39ceaeb..491c551b1b 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -3,7 +3,7 @@ import capnp from cereal import car from common.numpy_fast import clip -from typing import Dict, List +from typing import Dict # kg of standard extra cargo to count for drive, gas, etc... STD_CARGO_KG = 136. @@ -32,19 +32,6 @@ def create_button_event(cur_but: int, prev_but: int, buttons_dict: Dict[int, cap return be -def create_button_enable_events(buttonEvents: capnp.lib.capnp._DynamicListBuilder, pcm_cruise: bool = False) -> List[int]: - events = [] - for b in buttonEvents: - # do enable on both accel and decel buttons - if not pcm_cruise: - if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed: - events.append(EventName.buttonEnable) - # do disable on button down - if b.type == ButtonType.cancel and b.pressed: - events.append(EventName.buttonCancel) - return events - - def gen_empty_fingerprint(): return {i: {} for i in range(0, 8)} diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index a789fc6cad..4647a04244 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -10,11 +10,12 @@ from common.conversions import Conversions as CV from common.kalman.simple_kalman import KF1D from common.numpy_fast import interp from common.realtime import DT_CTRL -from selfdrive.car import apply_hysteresis, create_button_enable_events, gen_empty_fingerprint +from selfdrive.car import apply_hysteresis, gen_empty_fingerprint from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_deadzone from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel +ButtonType = car.CarState.ButtonEvent.Type GearShifter = car.CarState.GearShifter EventName = car.CarEvent.EventName TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, float, float, bool], float] @@ -210,7 +211,8 @@ class CarInterfaceBase(ABC): def apply(self, c: car.CarControl) -> Tuple[car.CarControl.Actuators, List[bytes]]: pass - def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True): + def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True, + enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise)): events = Events() if cs_out.doorOpen: @@ -244,7 +246,13 @@ class CarInterfaceBase(ABC): events.add(EventName.steerOverride) # Handle button presses - events.events.extend(create_button_enable_events(cs_out.buttonEvents, pcm_cruise=self.CP.pcmCruise)) + for b in cs_out.buttonEvents: + # Enable OP long on falling edge of enable buttons (defaults to accelCruise and decelCruise, overridable per-port) + if not self.CP.pcmCruise and (b.type in enable_buttons and not b.pressed): + events.add(EventName.buttonEnable) + # Disable on rising edge of cancel for both stock and OP long + if b.type == ButtonType.cancel and b.pressed: + events.add(EventName.buttonCancel) # Handle permanent and temporary steering faults self.steering_unpressed = 0 if cs_out.steeringPressed else self.steering_unpressed + 1 diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 4724fe81bf..870f3ab163 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -6,6 +6,7 @@ from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter +ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -218,7 +219,8 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_ext, self.CP.transmissionType) events = self.create_common_events(ret, extra_gears=[GearShifter.eco, GearShifter.sport, GearShifter.manumatic], - pcm_enable=not self.CS.CP.openpilotLongitudinalControl) + pcm_enable=not self.CS.CP.openpilotLongitudinalControl, + enable_buttons=(ButtonType.setCruise, ButtonType.resumeCruise)) # Low speed steer alert hysteresis logic if self.CP.minSteerSpeed > 0. and ret.vEgo < (self.CP.minSteerSpeed + 1.): From b65fad9e8f232167bcc0fc9139bd632c573abb60 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 13 Oct 2022 08:04:53 +0800 Subject: [PATCH 141/178] cabana: keep scrollarea frame (#26056) add frame back --- tools/cabana/chartswidget.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index e8a27ae18c..2fc3fecb2a 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -53,7 +53,6 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { QScrollArea *charts_scroll = new QScrollArea(this); charts_scroll->setWidgetResizable(true); charts_scroll->setWidget(charts_container); - charts_scroll->setFrameShape(QFrame::NoFrame); charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); main_layout->addWidget(charts_scroll); From bb61081b70e787d3defe18c74720eb4d5dbb3114 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Wed, 12 Oct 2022 20:46:26 -0400 Subject: [PATCH 142/178] VW MQB: DBC updates and cleanup (#26053) * VW MQB: DBC updates and cleanup * bump opendbc after merge --- opendbc | 2 +- selfdrive/car/volkswagen/carcontroller.py | 2 +- selfdrive/car/volkswagen/carstate.py | 9 +++++---- selfdrive/car/volkswagen/pqcan.py | 2 +- selfdrive/car/volkswagen/values.py | 1 + 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/opendbc b/opendbc index 04cc54d5e6..dde0ff6f44 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 04cc54d5e662aaf708f72cabb65507c7dbb5136d +Subproject commit dde0ff6f4456c167df204bf5ac1e3f2979c844c9 diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index 816933f2f0..fff5548671 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -76,7 +76,7 @@ class CarController: stopping = actuators.longControlState == LongCtrlState.stopping starting = actuators.longControlState == LongCtrlState.starting can_sends.extend(self.CCS.create_acc_accel_control(self.packer_pt, CANBUS.pt, CS.acc_type, CC.longActive, accel, - acc_control, stopping, starting, CS.out.cruiseState.standstill)) + acc_control, stopping, starting, CS.esp_hold_confirmation)) # **** HUD Controls ***************************************************** # diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 3e99ca8252..5dc4543c0d 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -110,20 +110,21 @@ class CarState(CarStateBase): ret.cruiseState.available = True ret.cruiseState.enabled = False elif pt_cp.vl["TSK_06"]["TSK_Status"] in (3, 4, 5): - # ACC okay and enabled, currently regulating speed (3) or driver accel override (4) or overrun coast-down (5) + # ACC okay and enabled, currently regulating speed (3) or driver accel override (4) or brake only (5) ret.cruiseState.available = True ret.cruiseState.enabled = True else: # ACC okay but disabled (1), or a radar visibility or other fault/disruption (6 or 7) ret.cruiseState.available = False ret.cruiseState.enabled = False - ret.cruiseState.standstill = bool(pt_cp.vl["ESP_21"]["ESP_Haltebestaetigung"]) + self.esp_hold_confirmation = bool(pt_cp.vl["ESP_21"]["ESP_Haltebestaetigung"]) + ret.cruiseState.standstill = self.CP.pcmCruise and self.esp_hold_confirmation ret.accFaulted = pt_cp.vl["TSK_06"]["TSK_Status"] in (6, 7) # Update ACC setpoint. When the setpoint is zero or there's an error, the # radar sends a set-speed of ~90.69 m/s / 203mph. if self.CP.pcmCruise: - ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw"] * CV.KPH_TO_MS + ret.cruiseState.speed = ext_cp.vl["ACC_02"]["ACC_Wunschgeschw_02"] * CV.KPH_TO_MS if ret.cruiseState.speed > 90: ret.cruiseState.speed = 0 @@ -478,7 +479,7 @@ class CarState(CarStateBase): class MqbExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ - ("ACC_Wunschgeschw", "ACC_02"), # ACC set speed + ("ACC_Wunschgeschw_02", "ACC_02"), # ACC set speed ("AWV2_Freigabe", "ACC_10"), # FCW brake jerk release ("ANB_Teilbremsung_Freigabe", "ACC_10"), # AEB partial braking release ("ANB_Zielbremsung_Freigabe", "ACC_10"), # AEB target braking release diff --git a/selfdrive/car/volkswagen/pqcan.py b/selfdrive/car/volkswagen/pqcan.py index 30f3fcf62d..0bcbf6abb3 100644 --- a/selfdrive/car/volkswagen/pqcan.py +++ b/selfdrive/car/volkswagen/pqcan.py @@ -59,7 +59,7 @@ def acc_hud_status_value(main_switch_on, acc_faulted, long_active): return hud_status -def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, stopping, starting, standstill): +def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, stopping, starting, esp_hold): commands = [] values = { diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index e0bcd02600..8c679c7406 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -241,6 +241,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { ], } + # All supported cars should return FW from the engine, srs, eps, and fwdRadar. Cars # with a manual trans won't return transmission firmware, but all other cars will. # From b3324418034fcf09123b614ee2429a4e5bc9d7a5 Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Wed, 12 Oct 2022 17:47:30 -0700 Subject: [PATCH 143/178] locationd: Fix GPS sensor times with fixed offsets (#25920) * Rewind to qcom time * Fix test * Typo * init unix_time fix * add gps sensor_time_offsets * remove all clocks code and add todo * :emove clocks in unit test * update refs * update refs Co-authored-by: nuwandavek --- selfdrive/locationd/locationd.cc | 28 +++++++++++++--------- selfdrive/locationd/locationd.h | 2 +- selfdrive/locationd/models/live_kf.cc | 2 +- selfdrive/locationd/test/test_locationd.py | 1 + selfdrive/test/process_replay/ref_commit | 2 +- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 9608f4003b..e156af5d64 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -20,6 +20,11 @@ const double VALID_POS_STD = 50.0; // m const double MAX_RESET_TRACKER = 5.0; const double SANE_GPS_UNCERTAINTY = 1500.0; // m +// TODO: GPS sensor time offsets are empirically calculated +// They should be replaced with synced time from a real clock +const double GPS_LOCATION_SENSOR_TIME_OFFSET = 0.630; // s +const double GPS_LOCATION_EXTERNAL_SENSOR_TIME_OFFSET = 0.095; // s + static VectorXd floatlist2vector(const capnp::List::Reader& floatlist) { VectorXd res(floatlist.size()); for (int i = 0; i < floatlist.size(); i++) { @@ -257,7 +262,7 @@ void Localizer::input_fake_gps_observations(double current_time) { this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); } -void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::Reader& log) { +void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::Reader& log, const double sensor_time_offset) { // ignore the message if the fix is invalid bool gps_invalid_flag = (log.getFlags() % 2 == 0); bool gps_unreasonable = (Vector2d(log.getAccuracy(), log.getVerticalAccuracy()).norm() >= SANE_GPS_UNCERTAINTY); @@ -265,13 +270,15 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK)); bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK); - if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane){ + if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) { this->determine_gps_mode(current_time); return; } + + double sensor_time = current_time - sensor_time_offset; // Process message - this->last_gps_fix = current_time; + this->last_gps_fix = sensor_time; this->gps_mode = true; Geodetic geodetic = { log.getLatitude(), log.getLongitude(), log.getAltitude() }; this->converter = std::make_unique(geodetic); @@ -300,14 +307,14 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R if (ecef_vel.norm() > 5.0 && orientation_error.norm() > 1.0) { LOGE("Locationd vs ubloxLocation orientation difference too large, kalman reset"); this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); - this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_ORIENTATION_FROM_GPS, { initial_pose_ecef_quat }); + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_ORIENTATION_FROM_GPS, { initial_pose_ecef_quat }); } else if (gps_est_error > 100.0) { LOGE("Locationd vs ubloxLocation position difference too large, kalman reset"); this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); } - this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); - this->kf->predict_and_observe(current_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); } void Localizer::handle_car_state(double current_time, const cereal::CarState::Reader& log) { @@ -443,9 +450,9 @@ void Localizer::handle_msg(const cereal::Event::Reader& log) { } else if (log.isGyroscope()) { this->handle_sensor(t, log.getGyroscope()); } else if (log.isGpsLocation()) { - this->handle_gps(t, log.getGpsLocation()); + this->handle_gps(t, log.getGpsLocation(), GPS_LOCATION_SENSOR_TIME_OFFSET); } else if (log.isGpsLocationExternal()) { - this->handle_gps(t, log.getGpsLocationExternal()); + this->handle_gps(t, log.getGpsLocationExternal(), GPS_LOCATION_EXTERNAL_SENSOR_TIME_OFFSET); } else if (log.isCarState()) { this->handle_car_state(t, log.getCarState()); } else if (log.isCameraOdometry()) { @@ -497,9 +504,8 @@ int Localizer::locationd_thread() { } else { gps_location_socket = "gpsLocation"; } - const std::initializer_list service_list = {gps_location_socket, - "cameraOdometry", "liveCalibration", "carState", "carParams", - "accelerometer", "gyroscope"}; + const std::initializer_list service_list = {gps_location_socket, "cameraOdometry", "liveCalibration", + "carState", "carParams", "accelerometer", "gyroscope"}; PubMaster pm({"liveLocationKalman"}); // TODO: remove carParams once we're always sending at 100Hz diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index b17ab04b23..280296b06c 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -46,7 +46,7 @@ public: void handle_msg_bytes(const char *data, const size_t size); void handle_msg(const cereal::Event::Reader& log); void handle_sensor(double current_time, const cereal::SensorEventData::Reader& log); - void handle_gps(double current_time, const cereal::GpsLocationData::Reader& log); + void handle_gps(double current_time, const cereal::GpsLocationData::Reader& log, const double sensor_time_offset); void handle_car_state(double current_time, const cereal::CarState::Reader& log); void handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log); void handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log); diff --git a/selfdrive/locationd/models/live_kf.cc b/selfdrive/locationd/models/live_kf.cc index 5ff0f26995..f8c03365e1 100755 --- a/selfdrive/locationd/models/live_kf.cc +++ b/selfdrive/locationd/models/live_kf.cc @@ -44,7 +44,7 @@ LiveKalman::LiveKalman() { // init filter this->filter = std::make_shared(this->name, get_mapmat(this->Q), get_mapvec(this->initial_x), get_mapmat(initial_P), this->dim_state, this->dim_state_err, 0, 0, 0, std::vector(), - std::vector{3}, std::vector(), 0.2); + std::vector{3}, std::vector(), 0.8); } void LiveKalman::init_state(VectorXd& state, VectorXd& covs_diag, double filter_time) { diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index e32861cfae..7569530211 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -53,6 +53,7 @@ class TestLocationdProc(unittest.TestCase): msg.gpsLocationExternal.vNED = [0.0, 0.0, 0.0] msg.gpsLocationExternal.latitude = self.lat msg.gpsLocationExternal.longitude = self.lon + msg.gpsLocationExternal.unixTimestampMillis = t * 1e6 msg.gpsLocationExternal.altitude = self.alt elif name == 'cameraOdometry': msg.cameraOdometry.rot = [0.0, 0.0, 0.0] diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 4577d3cddf..e01fe2ac3c 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -27022484e3f4c0265ee7243154659b2697de3af7 \ No newline at end of file +14bc91c326f75ce48337720668a1744184c46994 \ No newline at end of file From 8809116a2674be71de45996b9f88bda46468694b Mon Sep 17 00:00:00 2001 From: Kurt Nistelberger Date: Wed, 12 Oct 2022 18:55:35 -0700 Subject: [PATCH 144/178] remove null effect assert --- selfdrive/sensord/rawgps/rawgpsd.py | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index c823ede076..5a6827a759 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -182,7 +182,6 @@ def main() -> NoReturn: if opcode != DIAG_LOG_F: cloudlog.error(f"Unhandled opcode: {opcode}") continue - assert opcode == DIAG_LOG_F (pending_msgs, log_outer_length), inner_log_packet = unpack_from(' 0: From b31932382d574b4db3c2b9b280d5410823adad1c Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Thu, 13 Oct 2022 10:31:26 +0800 Subject: [PATCH 145/178] cabana: increase replay's segment cache limit & add setting dialog (#26019) * increase replay's segment cache limit * todo * add settings dialog blank line typo --- tools/cabana/.gitignore | 1 + tools/cabana/canmessages.cc | 36 ++++++++++++++++++++++--- tools/cabana/canmessages.h | 19 +++++++++++-- tools/cabana/mainwin.cc | 54 +++++++++++++++++++++++++++++++++++++ tools/cabana/mainwin.h | 12 +++++++++ tools/replay/replay.cc | 4 +-- tools/replay/replay.h | 5 +++- 7 files changed, 122 insertions(+), 9 deletions(-) diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 0c21d5530d..88ffab2717 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -2,3 +2,4 @@ moc_* *.moc _cabana +settings diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index a55a045981..b717424ece 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -1,9 +1,11 @@ #include "tools/cabana/canmessages.h" #include +#include Q_DECLARE_METATYPE(std::vector); +Settings settings; CANMessages *can = nullptr; CANMessages::CANMessages(QObject *parent) : QObject(parent) { @@ -11,6 +13,7 @@ CANMessages::CANMessages(QObject *parent) : QObject(parent) { qRegisterMetaType>(); QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection); + QObject::connect(&settings, &Settings::changed, this, &CANMessages::settingChanged); } CANMessages::~CANMessages() { @@ -24,6 +27,7 @@ static bool event_filter(const Event *e, void *opaque) { bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { replay = new Replay(route, {"can", "roadEncodeIdx"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); + replay->setSegmentCacheLimit(settings.cached_segment_limit); replay->installEventFilter(event_filter, this); QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::segmentsMerged); if (replay->load()) { @@ -38,11 +42,11 @@ void CANMessages::process(QHash> *messages) { ++counters[it.key()]; auto &msgs = can_msgs[it.key()]; const auto &new_msgs = it.value(); - if (msgs.size() == CAN_MSG_LOG_SIZE || can_msgs[it.key()].size() == 0) { + if (new_msgs.size() == settings.can_msg_log_size || msgs.empty()) { msgs = std::move(new_msgs); } else { msgs.insert(msgs.begin(), std::make_move_iterator(new_msgs.begin()), std::make_move_iterator(new_msgs.end())); - while (msgs.size() >= CAN_MSG_LOG_SIZE) { + while (msgs.size() >= settings.can_msg_log_size) { msgs.pop_back(); } } @@ -71,7 +75,7 @@ bool CANMessages::eventFilter(const Event *event) { for (const auto &c : can_events) { QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16); auto &list = (*received_msgs)[id]; - while (list.size() >= CAN_MSG_LOG_SIZE) { + while (list.size() >= settings.can_msg_log_size) { list.pop_back(); } CanData &data = list.emplace_front(); @@ -80,7 +84,7 @@ bool CANMessages::eventFilter(const Event *event) { data.dat.append((char *)c.getDat().begin(), c.getDat().size()); } - if (current_sec < prev_update_sec || (current_sec - prev_update_sec) > 1.0 / FPS) { + if (current_sec < prev_update_sec || (current_sec - prev_update_sec) > 1.0 / settings.fps) { prev_update_sec = current_sec; // use pointer to avoid data copy in queued connection. emit received(received_msgs.release()); @@ -121,3 +125,27 @@ void CANMessages::segmentsMerged() { void CANMessages::resetRange() { setRange(event_begin_sec, event_end_sec); } + +void CANMessages::settingChanged() { + replay->setSegmentCacheLimit(settings.cached_segment_limit); +} + +// Settings + +Settings::Settings() { + load(); +} + +void Settings::save() { + QSettings s("settings", QSettings::IniFormat); + s.setValue("fps", fps); + s.setValue("log_size", can_msg_log_size); + s.setValue("cached_segment", cached_segment_limit); +} + +void Settings::load() { + QSettings s("settings", QSettings::IniFormat); + fps = s.value("fps", 10).toInt(); + can_msg_log_size = s.value("log_size", 100).toInt(); + cached_segment_limit = s.value("cached_segment", 3.).toInt(); +} diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index a2af2a084c..3d33f801a7 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -8,8 +8,21 @@ #include "tools/replay/replay.h" -const int FPS = 10; -const int CAN_MSG_LOG_SIZE = 100; +class Settings : public QObject { + Q_OBJECT + +public: + Settings(); + void save(); + void load(); + + int fps = 10; + int can_msg_log_size = 100; + int cached_segment_limit = 3; + +signals: + void changed(); +}; struct CanData { double ts; @@ -57,6 +70,7 @@ public: protected: void process(QHash> *); void segmentsMerged(); + void settingChanged(); std::atomic current_sec = 0.; std::atomic seeking = false; @@ -82,3 +96,4 @@ inline const QString &getColor(int i) { // A global pointer referring to the unique CANMessages object extern CANMessages *can; +extern Settings settings; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 6fa24ea21d..c9d9c85141 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -1,7 +1,9 @@ #include "tools/cabana/mainwin.h" #include +#include #include +#include #include #include @@ -23,6 +25,9 @@ MainWindow::MainWindow() : QWidget() { right_container->setFixedWidth(640); r_layout = new QVBoxLayout(right_container); + QPushButton *settings_btn = new QPushButton("Settings"); + r_layout->addWidget(settings_btn, 0, Qt::AlignRight); + video_widget = new VideoWidget(this); r_layout->addWidget(video_widget, 0, Qt::AlignTop); @@ -34,6 +39,7 @@ MainWindow::MainWindow() : QWidget() { QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); QObject::connect(detail_widget, &DetailWidget::showChart, charts_widget, &ChartsWidget::addChart); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); + QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption); } void MainWindow::dockCharts(bool dock) { @@ -59,3 +65,51 @@ void MainWindow::closeEvent(QCloseEvent *event) { floating_window->deleteLater(); QWidget::closeEvent(event); } + +void MainWindow::setOption() { + SettingsDlg dlg(this); + dlg.exec(); +} + +// SettingsDlg + +SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Settings")); + QVBoxLayout *main_layout = new QVBoxLayout(this); + QFormLayout *form_layout = new QFormLayout(); + + fps = new QSpinBox(this); + fps->setRange(10, 100); + fps->setSingleStep(10); + fps->setValue(settings.fps); + form_layout->addRow("FPS", fps); + + log_size = new QSpinBox(this); + log_size->setRange(50, 500); + log_size->setSingleStep(10); + log_size->setValue(settings.can_msg_log_size); + form_layout->addRow(tr("Log size"), log_size); + + cached_segment = new QSpinBox(this); + cached_segment->setRange(3, 60); + cached_segment->setSingleStep(1); + cached_segment->setValue(settings.cached_segment_limit); + form_layout->addRow(tr("Cached segments limit"), cached_segment); + + main_layout->addLayout(form_layout); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + main_layout->addWidget(buttonBox); + + setFixedWidth(360); + connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDlg::save); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +void SettingsDlg::save() { + settings.fps = fps->value(); + settings.can_msg_log_size = log_size->value(); + settings.cached_segment_limit = cached_segment->value(); + settings.save(); + accept(); +} diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index b0d7c273da..14c4b1dfa9 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -14,6 +14,7 @@ public: protected: void closeEvent(QCloseEvent *event) override; + void setOption(); VideoWidget *video_widget; MessagesWidget *messages_widget; @@ -22,3 +23,14 @@ protected: QWidget *floating_window = nullptr; QVBoxLayout *r_layout; }; + +class SettingsDlg : public QDialog { + Q_OBJECT + +public: + SettingsDlg(QWidget *parent); + void save(); + QSpinBox *fps; + QSpinBox *log_size ; + QSpinBox *cached_segment; +}; diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 80e58b47a3..1337a4ef2c 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -211,7 +211,7 @@ void Replay::queueSegment() { SegmentMap::iterator cur, end; cur = end = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first)); - for (int i = 0; end != segments_.end() && i <= FORWARD_SEGS; ++i) { + for (int i = 0; end != segments_.end() && i <= segment_cache_limit + FORWARD_FETCH_SEGS; ++i) { ++end; } // load one segment at a time @@ -250,7 +250,7 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap:: // merge 3 segments in sequence. std::vector segments_need_merge; size_t new_events_size = 0; - for (auto it = begin; it != end && it->second && it->second->isLoaded() && segments_need_merge.size() < 3; ++it) { + for (auto it = begin; it != end && it->second && it->second->isLoaded() && segments_need_merge.size() < segment_cache_limit; ++it) { segments_need_merge.push_back(it->first); new_events_size += it->second->log->events.size(); } diff --git a/tools/replay/replay.h b/tools/replay/replay.h index fbb36bd1ed..88c285125a 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -10,7 +10,7 @@ const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; // one segment uses about 100M of memory -constexpr int FORWARD_SEGS = 5; +constexpr int FORWARD_FETCH_SEGS = 3; enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, @@ -57,6 +57,8 @@ public: filter_opaque = opaque; event_filter = filter; } + inline int segmentCacheLimit() const { return segment_cache_limit; } + inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(3, n); } inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } @@ -131,4 +133,5 @@ protected: float speed_ = 1.0; replayEventFilter event_filter = nullptr; void *filter_opaque = nullptr; + int segment_cache_limit = 3; }; From 1d8fc4d21caf55368209b7bbafa34327962a6a97 Mon Sep 17 00:00:00 2001 From: jp-solutionz <47436082+jp-solutionz@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:48:22 +1300 Subject: [PATCH 146/178] joystickd: add controller mapping (#25986) * Correct default controller mapping. Current config maps steering to right trigger (ABS_RZ) when using a default xinput controller: https://inputs.readthedocs.io/en/latest/user/hardwaresupport.html This results in default full left steering angle (1) requiring right trigger to return to centre (0) or right (-1). It appears the intended mapping for steering is right thumbstick (ABS_RX). Cancel button is also non-existent on default xinput controller. May be X button (BTN_NORTH) or Right Trigger (ABS_RZ). Tested on Xbox One Controller via USB Cable, Logitech F710 and GameSir T4 Pro. * Update joystickd.py Fixed comment * gamepad configuration * gamepad arg Co-authored-by: Cameron Clough --- tools/joystick/joystickd.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index 8374cf8a74..b31dab83fe 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -38,13 +38,20 @@ class Keyboard: class Joystick: - def __init__(self): + def __init__(self, gamepad=False): # TODO: find a way to get this from API, perhaps "inputs" doesn't support it - self.min_axis_value = {'ABS_Y': 0., 'ABS_RZ': 0.} - self.max_axis_value = {'ABS_Y': 255., 'ABS_RZ': 255.} - self.cancel_button = 'BTN_TRIGGER' - self.axes_values = {'ABS_Y': 0., 'ABS_RZ': 0.} # gb, steer - self.axes_order = ['ABS_Y', 'ABS_RZ'] + if gamepad: + self.cancel_button = 'BTN_NORTH' # (BTN_NORTH=X, ABS_RZ=Right Trigger) + accel_axis = 'ABS_Y' + steer_axis = 'ABS_RX' + else: + self.cancel_button = 'BTN_TRIGGER' + accel_axis = 'ABS_Y' + steer_axis = 'ABS_RZ' + self.min_axis_value = {accel_axis: 0., steer_axis: 0.} + self.max_axis_value = {accel_axis: 255., steer_axis: 255.} + self.axes_values = {accel_axis: 0., steer_axis: 0.} + self.axes_order = [accel_axis, steer_axis] self.cancel = False def update(self): @@ -80,9 +87,8 @@ def send_thread(joystick): requests.get("http://"+os.environ["WEB"]+":5000/control/%f/%f" % tuple([joystick.axes_values[a] for a in joystick.axes_order][::-1]), timeout=None) rk.keep_time() -def joystick_thread(use_keyboard): +def joystick_thread(joystick): Params().put_bool('JoystickDebugMode', True) - joystick = Keyboard() if use_keyboard else Joystick() threading.Thread(target=send_thread, args=(joystick,), daemon=True).start() while True: joystick.update() @@ -92,6 +98,7 @@ if __name__ == '__main__': 'openpilot must be offroad before starting joysticked.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--keyboard', action='store_true', help='Use your keyboard instead of a joystick') + parser.add_argument('--gamepad', action='store_true', help='Use gamepad configuration instead of joystick') args = parser.parse_args() if not Params().get_bool("IsOffroad") and "ZMQ" not in os.environ and "WEB" not in os.environ: @@ -108,4 +115,5 @@ if __name__ == '__main__': else: print('Using joystick, make sure to run cereal/messaging/bridge on your device if running over the network!') - joystick_thread(args.keyboard) + joystick = Keyboard() if args.keyboard else Joystick(args.gamepad) + joystick_thread(joystick) From c782380fc1cfcbc7b521f14db37a360aef6b53ec Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 12 Oct 2022 21:54:08 -0700 Subject: [PATCH 147/178] Hyundai: share panda flags with CAN-FD platform (#26058) * Hyundai: share panda flags with CAN-FD platform * move that * only set bit * bump panda * panda master * regen + update refs for new param --- panda | 2 +- selfdrive/car/hyundai/interface.py | 19 +++++++------------ selfdrive/car/hyundai/values.py | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- selfdrive/test/process_replay/regen.py | 4 +++- .../test/process_replay/test_processes.py | 2 +- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/panda b/panda index 2f3e2825e5..622ce923e9 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 2f3e2825e5ecc8074b4ee9cb9a70df635d09fd10 +Subproject commit 622ce923e901c634aab4c29be68638e38b0fcc16 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 024d7498fa..13e93c7d06 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -310,8 +310,6 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput), get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd)] - if ret.openpilotLongitudinalControl: - ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_LONG if ret.flags & HyundaiFlags.CANFD_HDA2: ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS: @@ -325,18 +323,16 @@ class CarInterface(CarInterfaceBase): else: ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)] - # set appropriate safety param for gas signal - if candidate in HYBRID_CAR: - ret.safetyConfigs[0].safetyParam = 2 - elif candidate in EV_CAR: - ret.safetyConfigs[0].safetyParam = 1 - - if ret.openpilotLongitudinalControl: - ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_LONG - if candidate in CAMERA_SCC_CAR: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC + if ret.openpilotLongitudinalControl: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_LONG + if candidate in HYBRID_CAR: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_HYBRID_GAS + elif candidate in EV_CAR: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS + ret.centerToFront = ret.wheelbase * 0.4 # TODO: get actual value, for now starting with reasonable value for @@ -347,7 +343,6 @@ 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) - return ret @staticmethod diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 68a16b096e..ecda51816d 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1383,7 +1383,7 @@ CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN} 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} # 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.KONA_EV_2022} +EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5} # 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, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index e01fe2ac3c..6a8f5a273c 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -14bc91c326f75ce48337720668a1744184c46994 \ No newline at end of file +1e4bb3f620bddbe6ead966d6f2dd7db3fd730308 \ No newline at end of file diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index eee3745f8e..2653a0126a 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -30,10 +30,11 @@ def replay_panda_states(s, msgs): rk = Ratekeeper(service_list[s].frequency, print_delay_threshold=None) smsgs = [m for m in msgs if m.which() in ['pandaStates', 'pandaStateDEPRECATED']] - # TODO: new safety params from flags, remove after getting new routes for Toyota + # TODO: safety param migration should be handled automatically safety_param_migration = { "TOYOTA PRIUS 2017": EPS_SCALE["TOYOTA PRIUS 2017"] | Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL, "TOYOTA RAV4 2017": EPS_SCALE["TOYOTA RAV4 2017"] | Panda.FLAG_TOYOTA_ALT_BRAKE, + "KIA EV6 2022": Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_CANFD_HDA2, } # Migrate safety param base on carState @@ -56,6 +57,7 @@ def replay_panda_states(s, msgs): pm.send(s, new_m) else: new_m = m.as_builder() + new_m.pandaStates[-1].safetyParam = safety_param new_m.logMonoTime = int(sec_since_boot() * 1e9) pm.send(s, new_m) diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 38ed0e07ad..683387dce8 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -40,7 +40,7 @@ source_segments = [ segments = [ ("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"), ("HYUNDAI", "regenBE53A59065B|2022-09-27--16-52-03--0"), - ("HYUNDAI2", "regen11AA43BCA5F|2022-09-27--15-39-54--0"), + ("HYUNDAI2", "regenFA8B5CA9840|2022-10-12--21-47-06--0"), ("TOYOTA", "regen929C5790007|2022-09-27--16-27-47--0"), ("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"), ("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--0"), From 4ee0b8196f85b29b735c3319924f3c40cebc86e5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 12 Oct 2022 21:54:53 -0700 Subject: [PATCH 148/178] Hyundai: fix alt CAN-FD cancel (#26057) --- selfdrive/car/hyundai/carcontroller.py | 1 + selfdrive/car/hyundai/carstate.py | 4 +++- selfdrive/car/hyundai/hyundaicanfd.py | 11 ++++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index bcbab2ab94..a5d995df25 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -117,6 +117,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.CP, 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 4c2650c19f..3752fdebef 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -34,6 +34,8 @@ class CarState(CarStateBase): self.brake_error = False self.buttons_counter = 0 + self.cruise_info = {} + # On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz self.cluster_speed = 0 self.cluster_speed_counter = CLUSTER_SAMPLE_RATE @@ -194,13 +196,13 @@ class CarState(CarStateBase): cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam ret.cruiseState.speed = cp_cruise_info.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor ret.cruiseState.standstill = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STANDSTILL"] == 1 + self.cruise_info = copy.copy(cp_cruise_info.vl["CRUISE_INFO"]) cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" self.prev_cruise_buttons = self.cruise_buttons[-1] self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"]) self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"]) self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"] - self.cruise_info_copy = {} if self.CP.flags & HyundaiFlags.CANFD_HDA2: self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index 9d4e4d4e0d..f2cbafdcf0 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -47,12 +47,13 @@ def create_buttons(packer, cnt, btn): } return packer.make_can_msg("CRUISE_BUTTONS", 5, values) -def create_cruise_info(packer, cruise_info_copy, cancel): +def create_acc_cancel(packer, CP, cruise_info_copy): values = cruise_info_copy - if cancel: - values["CRUISE_STATUS"] = 0 - values["CRUISE_INACTIVE"] = 1 - return packer.make_can_msg("CRUISE_INFO", 5, values) + values.update({ + "CRUISE_STATUS": 0, + "CRUISE_INACTIVE": 1, + }) + return packer.make_can_msg("CRUISE_INFO", get_e_can_bus(CP), values) def create_lfahda_cluster(packer, CP, enabled): values = { From 82bd082dcd3e9f38a0f7a766242b6979a611a567 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 12 Oct 2022 22:12:24 -0700 Subject: [PATCH 149/178] Hyundai: split alt gas pressed signals by EV and ICE (#26061) --- panda | 2 +- selfdrive/car/hyundai/carstate.py | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/panda b/panda index 622ce923e9..de380961fc 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 622ce923e901c634aab4c29be68638e38b0fcc16 +Subproject commit de380961fcfece68137ea0c4f5dc07f2763a4aaf diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 3752fdebef..948634e974 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -154,7 +154,7 @@ class CarState(CarStateBase): def update_canfd(self, cp, cp_cam): ret = car.CarState.new_message() - if self.CP.flags & HyundaiFlags.CANFD_HDA2: + if self.CP.carFingerprint in EV_CAR: ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. else: ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023. @@ -450,22 +450,22 @@ class CarState(CarStateBase): ("DOORS_SEATBELTS", 4), ] - if CP.flags & HyundaiFlags.CANFD_HDA2: + if CP.flags & HyundaiFlags.CANFD_HDA2 and not CP.openpilotLongitudinalControl: + signals += [ + ("SET_SPEED", "CRUISE_INFO"), + ("CRUISE_STANDSTILL", "CRUISE_INFO"), + ] + checks += [ + ("CRUISE_INFO", 50), + ] + + if CP.carFingerprint in EV_CAR: signals += [ ("ACCELERATOR_PEDAL", "ACCELERATOR"), - ("GEAR", "ACCELERATOR"), ] checks += [ ("ACCELERATOR", 100), ] - if not CP.openpilotLongitudinalControl: - signals += [ - ("SET_SPEED", "CRUISE_INFO"), - ("CRUISE_STANDSTILL", "CRUISE_INFO"), - ] - checks += [ - ("CRUISE_INFO", 50), - ] else: signals += [ ("ACCELERATOR_PEDAL", "ACCELERATOR_ALT"), From 6a602ed41dd0b3712c2440b768220b18b1fcfd10 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 12 Oct 2022 22:12:52 -0700 Subject: [PATCH 150/178] bump panda --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index de380961fc..c39528d299 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit de380961fcfece68137ea0c4f5dc07f2763a4aaf +Subproject commit c39528d299aae6a1ebbdbccddeae55bc76a534e3 From 7f3c070061da864bd18820ba5589e4ee7fbae0db Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Wed, 12 Oct 2022 22:28:07 -0700 Subject: [PATCH 151/178] Hyundai: add missing Elantra Hybrid 2023 FW versions (#26055) * add Hyundai Elantra HEV 2023 fw 8dcf421697cd2cb0|2022-10-12--16-12-21--0 VIN: KMHLN4AJ5PU042417 * add 2023 to docs * delete * fix fingerprint Co-authored-by: Shane Smiskol --- docs/CARS.md | 2 +- selfdrive/car/hyundai/values.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 82ef37ae8d..f5cc5acaeb 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -55,7 +55,7 @@ A supported vehicle is one that just works when you install a comma three. All s |Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |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)](##)|Hyundai B| |Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| -|Hyundai|Elantra Hybrid 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |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)](##)|Hyundai J| |Hyundai|Ioniq 5 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| |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)](##)|Hyundai C| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index ecda51816d..1a8fb14eec 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -109,7 +109,7 @@ class HyundaiCarInfo(CarInfo): CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.ELANTRA: HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b), CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), - CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), + CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), CAR.ELANTRA_GT_I30: [ HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e), HyundaiCarInfo("Hyundai i30 2019", harness=Harness.hyundai_e), @@ -1218,26 +1218,29 @@ FW_VERSIONS = { ], }, CAR.ELANTRA_HEV_2021: { - (Ecu.fwdCamera, 0x7c4, None) : [ + (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', + b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426', ], - (Ecu.fwdRadar, 0x7d0, None) : [ + (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\000CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', ], - (Ecu.eps, 0x7d4, None) :[ + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103', b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', ], - (Ecu.transmission, 0x7e1, None) :[ + (Ecu.transmission, 0x7e1, None): [ b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000', b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', ], - (Ecu.engine, 0x7e0, None) : [ + (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', ] }, CAR.KONA_HEV: { From bf5f9adcc853c9fef6bdcfed027a588aa1940384 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 12 Oct 2022 22:34:40 -0700 Subject: [PATCH 152/178] boardd: don't multiplex OBD port on external pandas (#26062) don't multiplex on ext pandas --- selfdrive/boardd/boardd.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 77e86f9458..2d613b68ce 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -113,8 +113,9 @@ bool safety_setter_thread(std::vector pandas) { } // set to ELM327 for fingerprinting - for (Panda *panda : pandas) { - panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327); + for (int i = 0; i < pandas.size(); i++) { + const uint16_t safety_param = (i > 0) ? 1U : 0U; + pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); } Params p = Params(); From 649eaf273ff245dbf8279d86d4acb9ea36a9d5d2 Mon Sep 17 00:00:00 2001 From: hoomoose <94947902+hoomoose@users.noreply.github.com> Date: Thu, 13 Oct 2022 10:34:59 -0600 Subject: [PATCH 153/178] Hyundai: support HDA1 EV6 and Ioniq 5 (#25723) it's hda1 time Co-authored-by: Adeeb Shihadeh --- selfdrive/car/hyundai/values.py | 10 ++++++++-- selfdrive/car/tests/routes.py | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 1a8fb14eec..2d5e6792ee 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -141,7 +141,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { ], CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), - CAR.IONIQ_5: HyundaiCarInfo("Hyundai Ioniq 5 2022", "Highway Driving Assist II", harness=Harness.hyundai_q), + CAR.IONIQ_5: [ + HyundaiCarInfo("Hyundai Ioniq 5 2022 (without HDA II)" , "Highway Driving Assist", harness=Harness.hyundai_k), + HyundaiCarInfo("Hyundai Ioniq 5 2022 (with HDA II)", "Highway Driving Assist II", harness=Harness.hyundai_q), + ], CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n), # Kia @@ -171,7 +174,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { ], CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), - CAR.KIA_EV6: HyundaiCarInfo("Kia EV6 2022", "Highway Driving Assist II", harness=Harness.hyundai_p), + CAR.KIA_EV6: [ + HyundaiCarInfo("Kia EV6 2022 (without HDA II)", "Highway Driving Assist", harness=Harness.hyundai_l), + HyundaiCarInfo("Kia EV6 2022 (with HDA II)", "Highway Driving Assist II", harness=Harness.hyundai_p) + ], # Genesis CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f), diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index ac56cc106b..85787ae88d 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -110,7 +110,8 @@ routes = [ CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER), - CarTestRoute("d824e27e8c60172c|2022-05-19--16-15-28", HYUNDAI.KIA_EV6), + CarTestRoute("d824e27e8c60172c|2022-05-19--16-15-28", HYUNDAI.KIA_EV6), # HDA2 + CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1 CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021), CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV), CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV), From 40dc05db6db4b5944b9fc43b506148a1c854d423 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 13 Oct 2022 10:40:46 -0700 Subject: [PATCH 154/178] update car compatibility docs changes from 649eaf2 --- docs/CARS.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index f5cc5acaeb..77d6b890db 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 206 Supported Cars +# 208 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| @@ -57,7 +57,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |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)](##)|Hyundai J| -|Hyundai|Ioniq 5 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| +|Hyundai|Ioniq 5 2022 (with HDA II)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| +|Hyundai|Ioniq 5 2022 (without HDA II)|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |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)](##)|Hyundai C| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |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)](##)|Hyundai C| @@ -83,7 +84,8 @@ A supported vehicle is one that just works when you install a comma three. All s |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)](##)|FCA| |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)](##)|FCA| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Kia|EV6 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| +|Kia|EV6 2022 (with HDA II)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| +|Kia|EV6 2022 (without HDA II)|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| |Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|Niro EV 2019|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| From 41f520c2540a869b6904fd8aa0eb494513b38042 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 13 Oct 2022 11:43:20 -0700 Subject: [PATCH 155/178] don't build cabana unless extras (#26072) don't build cabana on device this caused CI to fail for xx since qt libs aren't installed in CI docker --- SConstruct | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SConstruct b/SConstruct index 23ab37dc1e..b87327aa20 100644 --- a/SConstruct +++ b/SConstruct @@ -434,9 +434,10 @@ SConscript(['selfdrive/navd/SConscript']) if arch in ['x86_64', 'Darwin'] or GetOption('extras'): SConscript(['tools/replay/SConscript']) - opendbc = abspath([File('opendbc/can/libdbc.so')]) - Export('opendbc') - SConscript(['tools/cabana/SConscript']) + if arch in ['x86_64', 'Darwin'] and GetOption('extras'): + opendbc = abspath([File('opendbc/can/libdbc.so')]) + Export('opendbc') + SConscript(['tools/cabana/SConscript']) if GetOption('test'): SConscript('panda/tests/safety/SConscript') From 2082248b73afedbcddd9f5a83ab4974811454f2a Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 13 Oct 2022 12:48:24 -0700 Subject: [PATCH 156/178] Revert "don't build cabana unless extras (#26072)" This reverts commit 41f520c2540a869b6904fd8aa0eb494513b38042. --- SConstruct | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/SConstruct b/SConstruct index b87327aa20..23ab37dc1e 100644 --- a/SConstruct +++ b/SConstruct @@ -434,10 +434,9 @@ SConscript(['selfdrive/navd/SConscript']) if arch in ['x86_64', 'Darwin'] or GetOption('extras'): SConscript(['tools/replay/SConscript']) - if arch in ['x86_64', 'Darwin'] and GetOption('extras'): - opendbc = abspath([File('opendbc/can/libdbc.so')]) - Export('opendbc') - SConscript(['tools/cabana/SConscript']) + opendbc = abspath([File('opendbc/can/libdbc.so')]) + Export('opendbc') + SConscript(['tools/cabana/SConscript']) if GetOption('test'): SConscript('panda/tests/safety/SConscript') From 5f7d9a519e98b446b14866a8920dd0493b4dce26 Mon Sep 17 00:00:00 2001 From: Vivek Aithal Date: Thu, 13 Oct 2022 13:21:35 -0700 Subject: [PATCH 157/178] regen: Refactor log migrate functions to avoid needing azure keys (#26049) * refactor migrate fns to avoid needing to use azure keys on import * move azure key init behind a function * resolve comments --- selfdrive/test/process_replay/regen.py | 3 ++- selfdrive/test/update_ci_routes.py | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index 2653a0126a..c9f9c6c362 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -224,13 +224,14 @@ def migrate_sensorEvents(lr, old_logtime=False): m_dat.sensor = evt.sensor m_dat.type = evt.type m_dat.source = evt.source + if old_logtime: + m_dat.timestamp = evt.timestamp setattr(m_dat, evt.which(), getattr(evt, evt.which())) all_msgs.append(m.as_reader()) return all_msgs - def regen_segment(lr, frs=None, outdir=FAKEDATA, disable_tqdm=False): lr = migrate_carparams(list(lr)) lr = migrate_sensorEvents(list(lr)) diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index a1e8c35f6b..201ffb745a 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -14,11 +14,16 @@ SOURCES = [ (_DATA_ACCOUNT_CI, "commadataci"), ] -DEST_KEY = azureutil.get_user_token(_DATA_ACCOUNT_CI, "openpilotci") -SOURCE_KEYS = [azureutil.get_user_token(account, bucket) for account, bucket in SOURCES] -SERVICE = BlockBlobService(_DATA_ACCOUNT_CI, sas_token=DEST_KEY) + +def get_azure_keys(): + dest_key = azureutil.get_user_token(_DATA_ACCOUNT_CI, "openpilotci") + source_keys = [azureutil.get_user_token(account, bucket) for account, bucket in SOURCES] + service = BlockBlobService(_DATA_ACCOUNT_CI, sas_token=dest_key) + return dest_key, source_keys, service + def upload_route(path, exclude_patterns=None): + dest_key, _, _ = get_azure_keys() if exclude_patterns is None: exclude_patterns = ['*/dcamera.hevc'] @@ -29,27 +34,28 @@ def upload_route(path, exclude_patterns=None): "azcopy", "copy", f"{path}/*", - f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{destpath}?{DEST_KEY}", + f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{destpath}?{dest_key}", "--recursive=false", "--overwrite=false", ] + [f"--exclude-pattern={p}" for p in exclude_patterns] subprocess.check_call(cmd) def sync_to_ci_public(route): + dest_key, source_keys, service = get_azure_keys() key_prefix = route.replace('|', '/') dongle_id = key_prefix.split('/')[0] - if next(azureutil.list_all_blobs(SERVICE, "openpilotci", prefix=key_prefix), None) is not None: + if next(azureutil.list_all_blobs(service, "openpilotci", prefix=key_prefix), None) is not None: return True print(f"Uploading {route}") - for (source_account, source_bucket), source_key in zip(SOURCES, SOURCE_KEYS): + for (source_account, source_bucket), source_key in zip(SOURCES, source_keys): print(f"Trying {source_account}/{source_bucket}") cmd = [ "azcopy", "copy", f"https://{source_account}.blob.core.windows.net/{source_bucket}/{key_prefix}?{source_key}", - f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{dongle_id}?{DEST_KEY}", + f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{dongle_id}?{dest_key}", "--recursive=true", "--overwrite=false", "--exclude-pattern=*/dcamera.hevc", From cc6dd18cf0115458f28135b10e3d935ee4119e04 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 14 Oct 2022 04:24:55 +0800 Subject: [PATCH 158/178] Cabana: display the (x,y) values while MouseMove on the chart (#26064) --- tools/cabana/chartswidget.cc | 122 ++++++++++++++++++++++------------- tools/cabana/chartswidget.h | 41 ++++++------ 2 files changed, 96 insertions(+), 67 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 2fc3fecb2a..e704dea95a 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -136,8 +135,7 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { // ChartWidget ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *parent) : id(id), sig_name(sig_name), QWidget(parent) { - QStackedLayout *stacked = new QStackedLayout(this); - stacked->setStackingMode(QStackedLayout::StackAll); + QVBoxLayout *main_layout = new QVBoxLayout(this); QWidget *chart_widget = new QWidget(this); QVBoxLayout *chart_layout = new QVBoxLayout(chart_widget); @@ -159,9 +157,23 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa header_layout->addWidget(remove_btn); chart_layout->addWidget(header); + chart_view = new ChartView(id, sig_name, this); + chart_view->setFixedHeight(300); + chart_layout->addWidget(chart_view); + chart_layout->addStretch(); + + main_layout->addWidget(chart_widget); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); +} + +// ChartView + +ChartView::ChartView(const QString &id, const QString &sig_name, QWidget *parent) + : id(id), sig_name(sig_name), QChartView(nullptr, parent) { QLineSeries *series = new QLineSeries(); series->setUseOpenGL(true); - auto chart = new QChart(); + QChart *chart = new QChart(); chart->setTitle(sig_name); chart->addSeries(series); chart->createDefaultAxes(); @@ -172,28 +184,26 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa chart->setMargins({0, 0, 0, 0}); chart->layout()->setContentsMargins(0, 0, 0, 0); - chart_view = new ChartView(chart); - chart_view->setFixedHeight(300); - chart_view->setRenderHint(QPainter::Antialiasing); - chart_view->setRubberBand(QChartView::HorizontalRubberBand); - if (auto rubber = chart_view->findChild()) { + track_line = new QGraphicsLineItem(chart); + track_line->setPen(QPen(Qt::gray, 1, Qt::DashLine)); + value_text = new QGraphicsSimpleTextItem(chart); + value_text->setBrush(Qt::gray); + line_marker = new QGraphicsLineItem(chart); + line_marker->setPen(QPen(Qt::black, 2)); + + setChart(chart); + + setRenderHint(QPainter::Antialiasing); + setRubberBand(QChartView::HorizontalRubberBand); + if (auto rubber = findChild()) { QPalette pal; pal.setBrush(QPalette::Base, QColor(0, 0, 0, 80)); rubber->setPalette(pal); } - chart_layout->addWidget(chart_view); - chart_layout->addStretch(); - stacked->addWidget(chart_widget); - line_marker = new LineMarker(this); - stacked->addWidget(line_marker); - line_marker->setAttribute(Qt::WA_TransparentForMouseEvents, true); - line_marker->raise(); - - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - QObject::connect(can, &CANMessages::updated, this, &ChartWidget::updateState); - QObject::connect(can, &CANMessages::rangeChanged, this, &ChartWidget::rangeChanged); - QObject::connect(can, &CANMessages::eventsMerged, this, &ChartWidget::updateSeries); + QObject::connect(can, &CANMessages::updated, this, &ChartView::updateState); + QObject::connect(can, &CANMessages::rangeChanged, this, &ChartView::rangeChanged); + QObject::connect(can, &CANMessages::eventsMerged, this, &ChartView::updateSeries); QObject::connect(dynamic_cast(chart->axisX()), &QValueAxis::rangeChanged, can, &CANMessages::setRange); QObject::connect(dbc(), &DBCManager::signalUpdated, [this](const QString &msg_id, const QString &sig_name) { if (this->id == msg_id && this->sig_name == sig_name) @@ -202,15 +212,13 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa updateSeries(); } -void ChartWidget::updateState() { - auto chart = chart_view->chart(); - auto axis_x = dynamic_cast(chart->axisX()); - - int x = chart->plotArea().left() + chart->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); - line_marker->setX(x); +void ChartView::updateState() { + auto axis_x = dynamic_cast(chart()->axisX()); + int x = chart()->plotArea().left() + chart()->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); + line_marker->setLine(x, 0, x, height()); } -void ChartWidget::updateSeries() { +void ChartView::updateSeries() { const Signal *sig = dbc()->signal(id, sig_name); auto events = can->events(); if (!sig || !events) return; @@ -234,15 +242,16 @@ void ChartWidget::updateSeries() { } } } - QLineSeries *series = (QLineSeries *)chart_view->chart()->series()[0]; + QLineSeries *series = (QLineSeries *)chart()->series()[0]; series->replace(vals); + series->setPointLabelsColor(Qt::black); auto [begin, end] = can->range(); - chart_view->chart()->axisX()->setRange(begin, end); + chart()->axisX()->setRange(begin, end); updateAxisY(); } -void ChartWidget::rangeChanged(qreal min, qreal max) { - auto axis_x = dynamic_cast(chart_view->chart()->axisX()); +void ChartView::rangeChanged(qreal min, qreal max) { + auto axis_x = dynamic_cast(chart()->axisX()); if (axis_x->min() != min || axis_x->max() != max) { axis_x->setRange(min, max); } @@ -250,9 +259,9 @@ void ChartWidget::rangeChanged(qreal min, qreal max) { } // auto zoom on yaxis -void ChartWidget::updateAxisY() { - const auto axis_x = dynamic_cast(chart_view->chart()->axisX()); - const auto axis_y = dynamic_cast(chart_view->chart()->axisY()); +void ChartView::updateAxisY() { + const auto axis_x = dynamic_cast(chart()->axisX()); + const auto axis_y = dynamic_cast(chart()->axisY()); // vals is a sorted list auto begin = std::lower_bound(vals.begin(), vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); if (begin == vals.end()) @@ -271,7 +280,17 @@ void ChartWidget::updateAxisY() { } } -// ChartView +void ChartView::enterEvent(QEvent *event) { + track_line->setVisible(true); + value_text->setVisible(true); + QChartView::enterEvent(event); +} + +void ChartView::leaveEvent(QEvent *event) { + track_line->setVisible(false); + value_text->setVisible(false); + QChartView::leaveEvent(event); +} void ChartView::mouseReleaseEvent(QMouseEvent *event) { auto rubber = findChild(); @@ -289,20 +308,31 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { } // TODO: right-click to reset zoom QChartView::mouseReleaseEvent(event); + line_marker->setVisible(true); } +void ChartView::mouseMoveEvent(QMouseEvent *ev) { + auto rubber = findChild(); + bool show = !(rubber && rubber->isVisible()); -// LineMarker + if (show) { + const auto plot_area = chart()->plotArea(); + float x = std::clamp((float)ev->pos().x(), (float)plot_area.left(), (float)plot_area.right()); + track_line->setLine(x, plot_area.top(), x, plot_area.bottom()); -void LineMarker::setX(double x) { - if (x != x_pos) { - x_pos = x; - update(); + auto [begin, end] = can->range(); + double sec = begin + ((x - plot_area.x()) / plot_area.width()) * (end - begin); + auto value = std::lower_bound(vals.begin(), vals.end(), sec, [](auto &p, double x) { return p.x() < x; }); + value_text->setPos(x + 6, plot_area.bottom() - 25); + if (value != vals.end()) { + value_text->setText(QString("(%1, %2)").arg(value->x(), 0, 'f', 3).arg(value->y())); + } else { + value_text->setText("(--, --)"); + } } -} -void LineMarker::paintEvent(QPaintEvent *event) { - QPainter p(this); - p.setPen(QPen(Qt::black, 2)); - p.drawLine(QPointF{x_pos, 50.}, QPointF{x_pos, (qreal)height() - 11}); + value_text->setVisible(show); + track_line->setVisible(show); + line_marker->setVisible(show); + QChartView::mouseMoveEvent(ev); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 7dbf0f108b..af12560cc9 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -14,24 +16,29 @@ using namespace QtCharts; -class LineMarker : public QWidget { -Q_OBJECT +class ChartView : public QChartView { + Q_OBJECT public: - LineMarker(QWidget *parent) : QWidget(parent) {} - void setX(double x); + ChartView(const QString &id, const QString &sig_name, QWidget *parent = nullptr); private: - void paintEvent(QPaintEvent *event) override; - double x_pos = -1; -}; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *ev) override; + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; -class ChartView : public QChartView { - Q_OBJECT + void updateSeries(); + void rangeChanged(qreal min, qreal max); + void updateAxisY(); + void updateState(); -public: - ChartView(QChart *chart, QWidget *parent = nullptr) : QChartView(chart, parent) {} - void mouseReleaseEvent(QMouseEvent *event) override; + QGraphicsLineItem *track_line; + QGraphicsSimpleTextItem *value_text; + QGraphicsLineItem *line_marker; + QList vals; + QString id; + QString sig_name; }; class ChartWidget : public QWidget { @@ -44,18 +51,10 @@ public: signals: void remove(); -private: - void updateState(); - void addData(const CanData &can_data, const Signal &sig); - void updateSeries(); - void rangeChanged(qreal min, qreal max); - void updateAxisY(); - +protected: QString id; QString sig_name; ChartView *chart_view = nullptr; - LineMarker *line_marker = nullptr; - QList vals; }; class ChartsWidget : public QWidget { From a397418ef82957289779cbfbcf9cb138d9f2cd3f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Fri, 14 Oct 2022 04:25:21 +0800 Subject: [PATCH 159/178] Cabana: scrollable binary view (#26065) --- tools/cabana/detailwidget.cc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 7c1847230b..531cd4c665 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -141,17 +141,14 @@ BinaryView::BinaryView(QWidget *parent) { table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); table->horizontalHeader()->hide(); table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); main_layout->addWidget(table); table->setColumnCount(9); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } void BinaryView::setMessage(const QString &message_id) { msg_id = message_id; const Msg *msg = dbc()->msg(msg_id); - int row_count = msg ? msg->size : can->lastMessage(msg_id).dat.size(); - + const int row_count = msg ? msg->size : can->lastMessage(msg_id).dat.size(); table->setRowCount(row_count); table->setColumnCount(9); for (int i = 0; i < table->rowCount(); ++i) { @@ -178,8 +175,7 @@ void BinaryView::setMessage(const QString &message_id) { } } } - - table->setFixedHeight(table->rowHeight(0) * table->rowCount() + table->horizontalHeader()->height() + 2); + table->setFixedHeight(table->rowHeight(0) * std::min(row_count, 8) + 2); updateState(); } From 982ea83cf9accc6aefd05169c420f457b21dcc0a Mon Sep 17 00:00:00 2001 From: Mitchell Goff Date: Thu, 13 Oct 2022 14:27:59 -0700 Subject: [PATCH 160/178] Added updateZoom function to map_renderer, plus custom style.json (#25997) * Added updateZoom function to map_renderer, plus custom style.json * Render 512x512 maps * Define STYLE_PATH in navd sconscript --- selfdrive/navd/SConscript | 2 ++ selfdrive/navd/map_renderer.cc | 27 ++++++++++++++++++++++----- selfdrive/navd/map_renderer.h | 1 + selfdrive/navd/map_renderer.py | 3 ++- selfdrive/navd/style.json | 1 + 5 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 selfdrive/navd/style.json diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript index 4fbe41e80b..b10684eef3 100644 --- a/selfdrive/navd/SConscript +++ b/selfdrive/navd/SConscript @@ -11,6 +11,8 @@ if arch in ['larch64', 'x86_64']: rpath = [Dir(f"#third_party/mapbox-gl-native-qt/{arch}").srcnode().abspath] qt_env["RPATH"] += rpath + style_path = File("style.json").abspath + qt_env['CXXFLAGS'].append(f'-DSTYLE_PATH=\\"{style_path}\\"') qt_libs = ["qt_widgets", "qt_util", "qmapboxgl"] + base_libs nav_src = ["main.cc", "map_renderer.cc"] diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index d0770cfb48..f85916a4ca 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -1,14 +1,16 @@ #include "selfdrive/navd/map_renderer.h" +#include #include #include #include +#include "common/util.h" #include "common/timing.h" #include "selfdrive/ui/qt/maps/map_helpers.h" -const float ZOOM = 13.5; // Don't go below 13 or features will start to disappear -const int WIDTH = 256; +const float DEFAULT_ZOOM = 13.5; // Don't go below 13 or features will start to disappear +const int WIDTH = 512; const int HEIGHT = WIDTH; const int NUM_VIPC_BUFFERS = 4; @@ -35,9 +37,10 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set QOpenGLFramebufferObjectFormat fbo_format; fbo.reset(new QOpenGLFramebufferObject(WIDTH, HEIGHT, fbo_format)); + std::string style = util::read_file(STYLE_PATH); m_map.reset(new QMapboxGL(nullptr, m_settings, fbo->size(), 1)); - m_map->setCoordinateZoom(QMapbox::Coordinate(0, 0), ZOOM); - m_map->setStyleUrl("mapbox://styles/commaai/ckvmksrpd4n0a14pfdo5heqzr"); + m_map->setCoordinateZoom(QMapbox::Coordinate(0, 0), DEFAULT_ZOOM); + m_map->setStyleJson(style.c_str()); m_map->createRenderer(); m_map->resize(fbo->size()); @@ -82,6 +85,15 @@ void MapRenderer::msgUpdate() { } } +void MapRenderer::updateZoom(float zoom) { + if (m_map.isNull()) { + return; + } + + m_map->setZoom(zoom); + update(); +} + void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { if (m_map.isNull()) { return; @@ -185,7 +197,7 @@ void MapRenderer::initLayers() { nav["source"] = "navSource"; m_map->addLayer(nav, "road-intersection"); m_map->setPaintProperty("navLayer", "line-color", QColor("grey")); - m_map->setPaintProperty("navLayer", "line-width", 3); + m_map->setPaintProperty("navLayer", "line-width", 5); m_map->setLayoutProperty("navLayer", "line-cap", "round"); } } @@ -210,6 +222,11 @@ extern "C" { return new MapRenderer(settings, false); } + void map_renderer_update_zoom(MapRenderer *inst, float zoom) { + inst->updateZoom(zoom); + QApplication::processEvents(); + } + void map_renderer_update_position(MapRenderer *inst, float lat, float lon, float bearing) { inst->updatePosition({lat, lon}, bearing); QApplication::processEvents(); diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index 855dc91894..921d871632 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -47,6 +47,7 @@ private: QTimer* timer; public slots: + void updateZoom(float zoom); void updatePosition(QMapbox::Coordinate position, float bearing); void updateRoute(QList coordinates); void msgUpdate(); diff --git a/selfdrive/navd/map_renderer.py b/selfdrive/navd/map_renderer.py index 9000622928..079bb028ce 100755 --- a/selfdrive/navd/map_renderer.py +++ b/selfdrive/navd/map_renderer.py @@ -9,7 +9,7 @@ from cffi import FFI from common.ffi_wrapper import suffix from common.basedir import BASEDIR -HEIGHT = WIDTH = 256 +HEIGHT = WIDTH = 512 def get_ffi(): @@ -18,6 +18,7 @@ def get_ffi(): ffi = FFI() ffi.cdef(""" void* map_renderer_init(char *maps_host, char *token); +void map_renderer_update_zoom(void *inst, float zoom); void map_renderer_update_position(void *inst, float lat, float lon, float bearing); void map_renderer_update_route(void *inst, char *polyline); void map_renderer_update(void *inst); diff --git a/selfdrive/navd/style.json b/selfdrive/navd/style.json new file mode 100644 index 0000000000..06bb750d1f --- /dev/null +++ b/selfdrive/navd/style.json @@ -0,0 +1 @@ +{"version": 8, "name": "Navigation Model", "metadata": {"mapbox:type": "default", "mapbox:origin": "monochrome-dark-v1", "mapbox:sdk-support": {"android": "10.0.0", "ios": "10.0.0", "js": "2.3.0"}, "mapbox:autocomposite": true, "mapbox:groups": {"Transit, transit-labels": {"name": "Transit, transit-labels", "collapsed": true}, "Administrative boundaries, admin": {"name": "Administrative boundaries, admin", "collapsed": true}, "Transit, bridges": {"name": "Transit, bridges", "collapsed": true}, "Transit, surface": {"name": "Transit, surface", "collapsed": true}, "Road network, bridges": {"name": "Road network, bridges", "collapsed": false}, "Land, water, & sky, water": {"name": "Land, water, & sky, water", "collapsed": true}, "Road network, tunnels": {"name": "Road network, tunnels", "collapsed": false}, "Road network, road-labels": {"name": "Road network, road-labels", "collapsed": true}, "Buildings, built": {"name": "Buildings, built", "collapsed": true}, "Natural features, natural-labels": {"name": "Natural features, natural-labels", "collapsed": true}, "Road network, surface": {"name": "Road network, surface", "collapsed": false}, "Land, water, & sky, built": {"name": "Land, water, & sky, built", "collapsed": true}, "Place labels, place-labels": {"name": "Place labels, place-labels", "collapsed": true}, "Point of interest labels, poi-labels": {"name": "Point of interest labels, poi-labels", "collapsed": true}, "Road network, tunnels-case": {"name": "Road network, tunnels-case", "collapsed": true}, "Transit, built": {"name": "Transit, built", "collapsed": true}, "Road network, surface-icons": {"name": "Road network, surface-icons", "collapsed": false}, "Land, water, & sky, land": {"name": "Land, water, & sky, land", "collapsed": true}}}, "center": [-117.19189443261149, 32.756553679559985], "zoom": 12.932776547838778, "bearing": 0, "pitch": 0.5017568344510897, "sources": {"composite": {"url": "mapbox://mapbox.mapbox-streets-v8", "type": "vector", "maxzoom": 13}}, "sprite": "mapbox://sprites/commaai/ckvmksrpd4n0a14pfdo5heqzr/bkx9h9tjdf3xedbnjvfo5xnbv", "glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf", "layers": [{"id": "land", "type": "background", "layout": {"visibility": "none"}, "paint": {"background-color": "rgb(252, 252, 252)"}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, land"}}, {"minzoom": 5, "layout": {"visibility": "none"}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, land"}, "filter": ["==", ["get", "class"], "national_park"], "type": "fill", "source": "composite", "id": "national-park", "paint": {"fill-color": "rgb(240, 240, 240)", "fill-opacity": ["interpolate", ["linear"], ["zoom"], 5, 0, 6, 0.5, 10, 0.5]}, "source-layer": "landuse_overlay"}, {"minzoom": 5, "layout": {"visibility": "none"}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, land"}, "filter": ["match", ["get", "class"], ["park", "airport", "glacier", "pitch", "sand", "facility"], true, false], "type": "fill", "source": "composite", "id": "landuse", "paint": {"fill-color": "rgb(240, 240, 240)", "fill-opacity": ["interpolate", ["linear"], ["zoom"], 5, 0, 6, ["match", ["get", "class"], "glacier", 0.5, 1]]}, "source-layer": "landuse"}, {"id": "waterway-shadow", "type": "line", "source": "composite", "source-layer": "waterway", "minzoom": 8, "layout": {"line-cap": ["step", ["zoom"], "butt", 11, "round"], "line-join": "round", "visibility": "none"}, "paint": {"line-color": "rgb(204, 204, 204)", "line-width": ["interpolate", ["exponential", 1.3], ["zoom"], 9, ["match", ["get", "class"], ["canal", "river"], 0.1, 0], 20, ["match", ["get", "class"], ["canal", "river"], 8, 3]], "line-translate": ["interpolate", ["exponential", 1.2], ["zoom"], 7, ["literal", [0, 0]], 16, ["literal", [-1, -1]]], "line-translate-anchor": "viewport", "line-opacity": ["interpolate", ["linear"], ["zoom"], 8, 0, 8.5, 1]}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, water"}}, {"id": "water-shadow", "type": "fill", "source": "composite", "source-layer": "water", "layout": {"visibility": "none"}, "paint": {"fill-color": "rgb(204, 204, 204)", "fill-translate": ["interpolate", ["exponential", 1.2], ["zoom"], 7, ["literal", [0, 0]], 16, ["literal", [-1, -1]]], "fill-translate-anchor": "viewport"}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, water"}}, {"id": "waterway", "type": "line", "source": "composite", "source-layer": "waterway", "minzoom": 8, "layout": {"line-cap": ["step", ["zoom"], "butt", 11, "round"], "line-join": "round", "visibility": "none"}, "paint": {"line-color": "rgb(224, 224, 224)", "line-width": ["interpolate", ["exponential", 1.3], ["zoom"], 9, ["match", ["get", "class"], ["canal", "river"], 0.1, 0], 20, ["match", ["get", "class"], ["canal", "river"], 8, 3]], "line-opacity": ["interpolate", ["linear"], ["zoom"], 8, 0, 8.5, 1]}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, water"}}, {"id": "water", "type": "fill", "source": "composite", "source-layer": "water", "layout": {"visibility": "none"}, "paint": {"fill-color": "rgb(224, 224, 224)"}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, water"}}, {"minzoom": 13, "layout": {"visibility": "none"}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, built"}, "filter": ["all", ["==", ["geometry-type"], "Polygon"], ["==", ["get", "class"], "land"]], "type": "fill", "source": "composite", "id": "land-structure-polygon", "paint": {"fill-color": "rgb(252, 252, 252)"}, "source-layer": "structure"}, {"minzoom": 13, "layout": {"line-cap": "round", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "land-and-water", "mapbox:group": "Land, water, & sky, built"}, "filter": ["all", ["==", ["geometry-type"], "LineString"], ["==", ["get", "class"], "land"]], "type": "line", "source": "composite", "id": "land-structure-line", "paint": {"line-width": ["interpolate", ["exponential", 1.99], ["zoom"], 14, 0.75, 20, 40], "line-color": "rgb(252, 252, 252)"}, "source-layer": "structure"}, {"minzoom": 11, "layout": {"visibility": "none"}, "metadata": {"mapbox:featureComponent": "transit", "mapbox:group": "Transit, built"}, "filter": ["all", ["==", ["geometry-type"], "Polygon"], ["match", ["get", "type"], ["runway", "taxiway", "helipad"], true, false]], "type": "fill", "source": "composite", "id": "aeroway-polygon", "paint": {"fill-color": "rgb(255, 255, 255)", "fill-opacity": ["interpolate", ["linear"], ["zoom"], 11, 0, 11.5, 1]}, "source-layer": "aeroway"}, {"minzoom": 9, "layout": {"visibility": "none"}, "metadata": {"mapbox:featureComponent": "transit", "mapbox:group": "Transit, built"}, "filter": ["==", ["geometry-type"], "LineString"], "type": "line", "source": "composite", "id": "aeroway-line", "paint": {"line-color": "rgb(255, 255, 255)", "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 9, ["match", ["get", "type"], "runway", 1, 0.5], 18, ["match", ["get", "type"], "runway", 80, 20]]}, "source-layer": "aeroway"}, {"minzoom": 13, "layout": {"visibility": "none"}, "metadata": {"mapbox:featureComponent": "buildings", "mapbox:group": "Buildings, built"}, "filter": ["all", ["!=", ["get", "type"], "building:part"], ["==", ["get", "underground"], "false"]], "type": "line", "source": "composite", "id": "building-outline", "paint": {"line-color": "rgb(227, 227, 227)", "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 15, 0.75, 20, 3], "line-opacity": ["interpolate", ["linear"], ["zoom"], 15, 0, 16, 1]}, "source-layer": "building"}, {"minzoom": 13, "layout": {"visibility": "none"}, "metadata": {"mapbox:featureComponent": "buildings", "mapbox:group": "Buildings, built"}, "filter": ["all", ["!=", ["get", "type"], "building:part"], ["==", ["get", "underground"], "false"]], "type": "fill", "source": "composite", "id": "building", "paint": {"fill-color": ["interpolate", ["linear"], ["zoom"], 15, "rgb(242, 242, 242)", 16, "rgb(242, 242, 242)"], "fill-opacity": ["interpolate", ["linear"], ["zoom"], 15, 0, 16, 1], "fill-outline-color": "rgb(227, 227, 227)"}, "source-layer": "building"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels-case"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["step", ["zoom"], ["match", ["get", "class"], ["street", "street_limited", "primary_link", "track"], true, false], 1, ["match", ["get", "class"], ["street", "street_limited", "track", "primary_link", "secondary_link", "tertiary_link", "service"], true, false]], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-street-minor-low", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 2, "track", 1, 0.5], 18, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 18, 12]], "line-color": "rgb(235, 235, 235)", "line-opacity": ["step", ["zoom"], 1, 14, 0]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels-case"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["step", ["zoom"], ["match", ["get", "class"], ["street", "street_limited", "primary_link", "track"], true, false], 1, ["match", ["get", "class"], ["street", "street_limited", "track", "primary_link", "secondary_link", "tertiary_link", "service"], true, false]], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-street-minor-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.75, 20, 2], "line-color": "rgb(255, 255, 255)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 2, "track", 1, 0.5], 18, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 18, 12]], "line-opacity": ["step", ["zoom"], 0, 14, 1], "line-dasharray": [3, 3]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels-case"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["match", ["get", "class"], ["primary", "secondary", "tertiary"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-primary-secondary-tertiary-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 10, ["match", ["get", "class"], "primary", 1, 0.75], 18, 2], "line-color": "rgb(255, 255, 255)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, ["match", ["get", "class"], "primary", 0.75, 0.1], 18, ["match", ["get", "class"], "primary", 32, 26]], "line-dasharray": [3, 3]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels-case"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["match", ["get", "class"], ["motorway_link", "trunk_link"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-major-link-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.75, 20, 2], "line-color": "rgb(255, 255, 255)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-dasharray": [3, 3]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels-case"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["match", ["get", "class"], ["motorway", "trunk"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-motorway-trunk-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 10, 1, 18, 2], "line-color": "rgb(255, 255, 255)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32], "line-dasharray": [3, 3]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels-case"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["==", ["get", "class"], "construction"], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-construction", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 14, 2, 18, 18], "line-color": "rgb(235, 235, 235)", "line-dasharray": ["step", ["zoom"], ["literal", [0.4, 0.8]], 15, ["literal", [0.3, 0.6]], 16, ["literal", [0.2, 0.3]], 17, ["literal", [0.2, 0.25]], 18, ["literal", [0.15, 0.15]]]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["match", ["get", "class"], ["motorway_link", "trunk_link"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-major-link", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-color": "rgb(235, 235, 235)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["step", ["zoom"], ["match", ["get", "class"], ["street", "street_limited", "primary_link", "track"], true, false], 1, ["match", ["get", "class"], ["street", "street_limited", "track", "primary_link", "secondary_link", "tertiary_link", "service"], true, false]], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-street-minor", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 2, "track", 1, 0.5], 18, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 18, 12]], "line-color": "rgb(235, 235, 235)", "line-opacity": ["step", ["zoom"], 0, 14, 1]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["match", ["get", "class"], ["primary", "secondary", "tertiary"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-primary-secondary-tertiary", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, ["match", ["get", "class"], "primary", 0.75, 0.1], 18, ["match", ["get", "class"], "primary", 32, 26]], "line-color": "rgb(235, 235, 235)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, tunnels"}, "filter": ["all", ["==", ["get", "structure"], "tunnel"], ["match", ["get", "class"], ["motorway", "trunk"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "tunnel-motorway-trunk", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32], "line-color": "rgb(235, 235, 235)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {"icon-image": "turning-circle-outline", "icon-size": ["interpolate", ["exponential", 1.5], ["zoom"], 14, 0.122, 18, 0.969, 20, 1], "icon-allow-overlap": true, "icon-ignore-placement": true, "icon-padding": 0, "icon-rotation-alignment": "map", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["==", ["geometry-type"], "Point"], ["match", ["get", "class"], ["turning_circle", "turning_loop"], true, false]], "type": "symbol", "source": "composite", "id": "turning-feature-outline", "paint": {}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["step", ["zoom"], ["==", ["get", "class"], "track"], 1, ["match", ["get", "class"], ["track", "secondary_link", "tertiary_link", "service"], true, false]], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-minor-low", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 14, ["match", ["get", "class"], "track", 1, 0.5], 18, 12], "line-color": "rgb(255, 255, 255)", "line-opacity": ["step", ["zoom"], 1, 14, 0]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["step", ["zoom"], ["==", ["get", "class"], "track"], 1, ["match", ["get", "class"], ["track", "secondary_link", "tertiary_link", "service"], true, false]], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-minor-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.75, 20, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 14, ["match", ["get", "class"], "track", 1, 0.5], 18, 12], "line-opacity": ["step", ["zoom"], 0, 14, 1]}, "source-layer": "road"}, {"minzoom": 11, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["match", ["get", "class"], ["street", "street_limited", "primary_link"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-street-low", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-color": "rgb(255, 255, 255)", "line-opacity": ["step", ["zoom"], 1, 14, 0]}, "source-layer": "road"}, {"minzoom": 11, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["match", ["get", "class"], ["street", "street_limited", "primary_link"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-street-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.75, 20, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-opacity": ["step", ["zoom"], 0, 14, 1]}, "source-layer": "road"}, {"minzoom": 8, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["match", ["get", "class"], ["secondary", "tertiary"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-secondary-tertiary-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 10, 0.75, 18, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.1, 18, 26], "line-opacity": ["step", ["zoom"], 0, 10, 1]}, "source-layer": "road"}, {"minzoom": 7, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["==", ["get", "class"], "primary"], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-primary-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 10, 1, 18, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32], "line-opacity": ["step", ["zoom"], 0, 10, 1]}, "source-layer": "road"}, {"minzoom": 10, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["match", ["get", "class"], ["motorway_link", "trunk_link"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-major-link-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.75, 20, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-opacity": ["step", ["zoom"], 0, 11, 1]}, "source-layer": "road"}, {"minzoom": 5, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["match", ["get", "class"], ["motorway", "trunk"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-motorway-trunk-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 10, 1, 18, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32], "line-opacity": ["step", ["zoom"], ["match", ["get", "class"], "motorway", 1, 0], 6, 1]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["==", ["get", "class"], "construction"], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-construction", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 14, 2, 18, 18], "line-color": "rgb(255, 255, 255)", "line-dasharray": ["step", ["zoom"], ["literal", [0.4, 0.8]], 15, ["literal", [0.3, 0.6]], 16, ["literal", [0.2, 0.3]], 17, ["literal", [0.2, 0.25]], 18, ["literal", [0.15, 0.15]]]}, "source-layer": "road"}, {"minzoom": 10, "layout": {"line-cap": ["step", ["zoom"], "butt", 13, "round"], "line-join": ["step", ["zoom"], "miter", 13, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["match", ["get", "class"], ["motorway_link", "trunk_link"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-major-link", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-color": "rgb(255, 255, 255)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["step", ["zoom"], ["==", ["get", "class"], "track"], 1, ["match", ["get", "class"], ["track", "secondary_link", "tertiary_link", "service"], true, false]], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-minor", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 14, ["match", ["get", "class"], "track", 1, 0.5], 18, 12], "line-color": "rgb(255, 255, 255)", "line-opacity": ["step", ["zoom"], 0, 14, 1]}, "source-layer": "road"}, {"minzoom": 11, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["match", ["get", "class"], ["street", "street_limited", "primary_link"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-street", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-color": "rgb(255, 255, 255)", "line-opacity": ["step", ["zoom"], 0, 14, 1]}, "source-layer": "road"}, {"minzoom": 8, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["match", ["get", "class"], ["secondary", "tertiary"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-secondary-tertiary", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.1, 18, 26], "line-color": "rgb(255, 255, 255)"}, "source-layer": "road"}, {"minzoom": 6, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}, "filter": ["all", ["==", ["get", "class"], "primary"], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "road-primary", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32], "line-color": "rgb(255, 255, 255)"}, "source-layer": "road"}, {"id": "road-motorway-trunk", "type": "line", "source": "composite", "source-layer": "road", "filter": ["all", ["match", ["get", "class"], ["motorway", "trunk"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false], ["==", ["geometry-type"], "LineString"]], "layout": {"line-cap": ["step", ["zoom"], "butt", 13, "round"], "line-join": ["step", ["zoom"], "miter", 13, "round"]}, "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32], "line-color": "rgb(255, 255, 255)"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface"}}, {"minzoom": 13, "layout": {"line-join": "round", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "transit", "mapbox:group": "Transit, surface"}, "filter": ["all", ["match", ["get", "class"], ["major_rail", "minor_rail"], true, false], ["match", ["get", "structure"], ["none", "ford"], true, false]], "type": "line", "source": "composite", "id": "road-rail", "paint": {"line-color": ["interpolate", ["linear"], ["zoom"], 13, "rgb(242, 242, 242)", 17, "rgb(227, 227, 227)"], "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 14, 0.5, 20, 1]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"icon-image": "turning-circle", "icon-size": ["interpolate", ["exponential", 1.5], ["zoom"], 14, 0.095, 18, 1], "icon-allow-overlap": true, "icon-ignore-placement": true, "icon-padding": 0, "icon-rotation-alignment": "map", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, surface-icons"}, "filter": ["all", ["==", ["geometry-type"], "Point"], ["match", ["get", "class"], ["turning_circle", "turning_loop"], true, false]], "type": "symbol", "source": "composite", "id": "turning-feature", "paint": {}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["step", ["zoom"], ["match", ["get", "class"], ["street", "street_limited", "primary_link", "track"], true, false], 1, ["match", ["get", "class"], ["street", "street_limited", "track", "primary_link", "secondary_link", "tertiary_link", "service"], true, false]], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-street-minor-low", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 2, "track", 1, 0.5], 18, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 18, 12]], "line-color": "rgb(255, 255, 255)", "line-opacity": ["step", ["zoom"], 1, 14, 0]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["step", ["zoom"], ["match", ["get", "class"], ["street", "street_limited", "primary_link", "track"], true, false], 1, ["match", ["get", "class"], ["street", "street_limited", "track", "primary_link", "secondary_link", "tertiary_link", "service"], true, false]], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-street-minor-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.75, 20, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 2, "track", 1, 0.5], 18, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 18, 12]], "line-opacity": ["step", ["zoom"], 0, 14, 1]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["match", ["get", "class"], ["primary", "secondary", "tertiary"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-primary-secondary-tertiary-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 10, ["match", ["get", "class"], "primary", 1, 0.75], 18, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, ["match", ["get", "class"], "primary", 0.75, 0.1], 18, ["match", ["get", "class"], "primary", 32, 26]], "line-opacity": ["step", ["zoom"], 0, 10, 1]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["match", ["get", "class"], ["motorway_link", "trunk_link"], true, false], ["<=", ["get", "layer"], 1], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-major-link-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.75, 20, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["match", ["get", "class"], ["motorway", "trunk"], true, false], ["<=", ["get", "layer"], 1], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-motorway-trunk-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 10, 1, 18, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32]}, "source-layer": "road"}, {"minzoom": 13, "layout": {}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["==", ["get", "class"], "construction"], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-construction", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 14, 2, 18, 18], "line-color": "rgb(255, 255, 255)", "line-dasharray": ["step", ["zoom"], ["literal", [0.4, 0.8]], 15, ["literal", [0.3, 0.6]], 16, ["literal", [0.2, 0.3]], 17, ["literal", [0.2, 0.25]], 18, ["literal", [0.15, 0.15]]]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": "round", "line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["match", ["get", "class"], ["motorway_link", "trunk_link"], true, false], ["<=", ["get", "layer"], 1], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-major-link", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-color": "rgb(255, 255, 255)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["step", ["zoom"], ["match", ["get", "class"], ["street", "street_limited", "primary_link", "track"], true, false], 1, ["match", ["get", "class"], ["street", "street_limited", "track", "primary_link", "secondary_link", "tertiary_link", "service"], true, false]], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-street-minor", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 2, "track", 1, 0.5], 18, ["match", ["get", "class"], ["street", "street_limited", "primary_link"], 18, 12]], "line-color": "rgb(255, 255, 255)", "line-opacity": ["step", ["zoom"], 0, 14, 1]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["match", ["get", "class"], ["primary", "secondary", "tertiary"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-primary-secondary-tertiary", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, ["match", ["get", "class"], "primary", 0.75, 0.1], 18, ["match", ["get", "class"], "primary", 32, 26]], "line-color": "rgb(255, 255, 255)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": "round", "line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["match", ["get", "class"], ["motorway", "trunk"], true, false], ["<=", ["get", "layer"], 1], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-motorway-trunk", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32], "line-color": "rgb(255, 255, 255)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], [">=", ["get", "layer"], 2], ["match", ["get", "class"], ["motorway_link", "trunk_link"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-major-link-2-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.75, 20, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], [">=", ["get", "layer"], 2], ["match", ["get", "class"], ["motorway", "trunk"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-motorway-trunk-2-case", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 10, 1, 18, 2], "line-color": "rgb(242, 242, 242)", "line-gap-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32]}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": "round", "line-join": "round"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], [">=", ["get", "layer"], 2], ["match", ["get", "class"], ["motorway_link", "trunk_link"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-major-link-2", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 0.5, 14, 2, 18, 18], "line-color": "rgb(255, 255, 255)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-cap": ["step", ["zoom"], "butt", 14, "round"], "line-join": ["step", ["zoom"], "miter", 14, "round"]}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], [">=", ["get", "layer"], 2], ["match", ["get", "class"], ["motorway", "trunk"], true, false], ["==", ["geometry-type"], "LineString"]], "type": "line", "source": "composite", "id": "bridge-motorway-trunk-2", "paint": {"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 5, 0.75, 18, 32], "line-color": "rgb(255, 255, 255)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {"line-join": "round", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "transit", "mapbox:group": "Transit, bridges"}, "filter": ["all", ["==", ["get", "structure"], "bridge"], ["match", ["get", "class"], ["major_rail", "minor_rail"], true, false]], "type": "line", "source": "composite", "id": "bridge-rail", "paint": {"line-color": "rgb(227, 227, 227)", "line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 14, 0.5, 20, 1]}, "source-layer": "road"}, {"minzoom": 7, "layout": {"line-join": "bevel", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "admin-boundaries", "mapbox:group": "Administrative boundaries, admin"}, "filter": ["all", ["==", ["get", "admin_level"], 1], ["==", ["get", "maritime"], "false"], ["match", ["get", "worldview"], ["all", "US"], true, false]], "type": "line", "source": "composite", "id": "admin-1-boundary-bg", "paint": {"line-color": ["interpolate", ["linear"], ["zoom"], 8, "rgb(227, 227, 227)", 16, "rgb(227, 227, 227)"], "line-width": ["interpolate", ["linear"], ["zoom"], 7, 3.75, 12, 5.5], "line-opacity": ["interpolate", ["linear"], ["zoom"], 7, 0, 8, 0.75], "line-dasharray": [1, 0], "line-blur": ["interpolate", ["linear"], ["zoom"], 3, 0, 8, 3]}, "source-layer": "admin"}, {"minzoom": 1, "layout": {"visibility": "none"}, "metadata": {"mapbox:featureComponent": "admin-boundaries", "mapbox:group": "Administrative boundaries, admin"}, "filter": ["all", ["==", ["get", "admin_level"], 0], ["==", ["get", "maritime"], "false"], ["match", ["get", "worldview"], ["all", "US"], true, false]], "type": "line", "source": "composite", "id": "admin-0-boundary-bg", "paint": {"line-width": ["interpolate", ["linear"], ["zoom"], 3, 3.5, 10, 8], "line-color": "rgb(227, 227, 227)", "line-opacity": ["interpolate", ["linear"], ["zoom"], 3, 0, 4, 0.5], "line-blur": ["interpolate", ["linear"], ["zoom"], 3, 0, 10, 2]}, "source-layer": "admin"}, {"minzoom": 2, "layout": {"line-join": "round", "line-cap": "round", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "admin-boundaries", "mapbox:group": "Administrative boundaries, admin"}, "filter": ["all", ["==", ["get", "admin_level"], 1], ["==", ["get", "maritime"], "false"], ["match", ["get", "worldview"], ["all", "US"], true, false]], "type": "line", "source": "composite", "id": "admin-1-boundary", "paint": {"line-dasharray": ["step", ["zoom"], ["literal", [2, 0]], 7, ["literal", [2, 2, 6, 2]]], "line-width": ["interpolate", ["linear"], ["zoom"], 7, 0.75, 12, 1.5], "line-opacity": ["interpolate", ["linear"], ["zoom"], 2, 0, 3, 1], "line-color": ["interpolate", ["linear"], ["zoom"], 3, "rgb(224, 224, 224)", 7, "rgb(184, 184, 184)"]}, "source-layer": "admin"}, {"minzoom": 1, "layout": {"line-join": "round", "line-cap": "round", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "admin-boundaries", "mapbox:group": "Administrative boundaries, admin"}, "filter": ["all", ["==", ["get", "admin_level"], 0], ["==", ["get", "disputed"], "false"], ["==", ["get", "maritime"], "false"], ["match", ["get", "worldview"], ["all", "US"], true, false]], "type": "line", "source": "composite", "id": "admin-0-boundary", "paint": {"line-color": "rgb(184, 184, 184)", "line-width": ["interpolate", ["linear"], ["zoom"], 3, 0.5, 10, 2], "line-dasharray": [10, 0]}, "source-layer": "admin"}, {"minzoom": 1, "layout": {"line-join": "round", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "admin-boundaries", "mapbox:group": "Administrative boundaries, admin"}, "filter": ["all", ["==", ["get", "disputed"], "true"], ["==", ["get", "admin_level"], 0], ["==", ["get", "maritime"], "false"], ["match", ["get", "worldview"], ["all", "US"], true, false]], "type": "line", "source": "composite", "id": "admin-0-boundary-disputed", "paint": {"line-color": "rgb(184, 184, 184)", "line-width": ["interpolate", ["linear"], ["zoom"], 3, 0.5, 10, 2], "line-dasharray": ["step", ["zoom"], ["literal", [3.25, 3.25]], 6, ["literal", [2.5, 2.5]], 7, ["literal", [2, 2.25]], 8, ["literal", [1.75, 2]]]}, "source-layer": "admin"}, {"minzoom": 10, "layout": {"text-size": ["interpolate", ["linear"], ["zoom"], 10, ["match", ["get", "class"], ["motorway", "trunk", "primary", "secondary", "tertiary"], 10, ["motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link", "street", "street_limited"], 9, 6.5], 18, ["match", ["get", "class"], ["motorway", "trunk", "primary", "secondary", "tertiary"], 16, ["motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link", "street", "street_limited"], 14, 13]], "text-max-angle": 30, "text-font": ["DIN Pro Regular", "Arial Unicode MS Regular"], "symbol-placement": "line", "text-padding": 1, "visibility": "none", "text-rotation-alignment": "map", "text-pitch-alignment": "viewport", "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "text-letter-spacing": 0.01}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, road-labels"}, "filter": ["step", ["zoom"], ["match", ["get", "class"], ["motorway", "trunk", "primary", "secondary", "tertiary"], true, false], 1, ["match", ["get", "class"], ["motorway", "trunk", "primary", "secondary", "tertiary", "street", "street_limited"], true, false], 2, ["match", ["get", "class"], ["path", "pedestrian", "golf", "ferry", "aerialway"], false, true]], "type": "symbol", "source": "composite", "id": "road-label", "paint": {"text-color": "rgb(128, 128, 128)", "text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 1, "text-halo-blur": 1}, "source-layer": "road"}, {"minzoom": 13, "layout": {"text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "icon-image": "intersection", "icon-text-fit": "both", "icon-text-fit-padding": [1, 2, 1, 2], "text-size": ["interpolate", ["exponential", 1.2], ["zoom"], 15, 9, 18, 12], "text-font": ["DIN Pro Bold", "Arial Unicode MS Bold"], "visibility": "none"}, "metadata": {"mapbox:featureComponent": "road-network", "mapbox:group": "Road network, road-labels"}, "filter": ["all", ["==", ["get", "class"], "intersection"], ["has", "name"]], "type": "symbol", "source": "composite", "id": "road-intersection", "paint": {"text-color": "rgb(153, 153, 153)"}, "source-layer": "road"}, {"minzoom": 13, "layout": {"text-font": ["DIN Pro Italic", "Arial Unicode MS Regular"], "text-max-angle": 30, "symbol-spacing": ["interpolate", ["linear", 1], ["zoom"], 15, 250, 17, 400], "text-size": ["interpolate", ["linear"], ["zoom"], 13, 12, 18, 16], "symbol-placement": "line", "text-pitch-alignment": "viewport", "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "visibility": "none"}, "metadata": {"mapbox:featureComponent": "natural-features", "mapbox:group": "Natural features, natural-labels"}, "filter": ["all", ["match", ["get", "class"], ["canal", "river", "stream"], ["match", ["get", "worldview"], ["all", "US"], true, false], ["disputed_canal", "disputed_river", "disputed_stream"], ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], ["==", ["geometry-type"], "LineString"]], "type": "symbol", "source": "composite", "id": "waterway-label", "paint": {"text-color": "rgb(150, 150, 150)"}, "source-layer": "natural_label"}, {"minzoom": 4, "layout": {"text-size": ["step", ["zoom"], ["step", ["get", "sizerank"], 18, 5, 12], 17, ["step", ["get", "sizerank"], 18, 13, 12]], "text-max-angle": 30, "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "text-font": ["DIN Pro Medium", "Arial Unicode MS Regular"], "symbol-placement": "line-center", "text-pitch-alignment": "viewport", "visibility": "none"}, "metadata": {"mapbox:featureComponent": "natural-features", "mapbox:group": "Natural features, natural-labels"}, "filter": ["all", ["match", ["get", "class"], ["glacier", "landform"], ["match", ["get", "worldview"], ["all", "US"], true, false], ["disputed_glacier", "disputed_landform"], ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], ["==", ["geometry-type"], "LineString"], ["<=", ["get", "filterrank"], 1]], "type": "symbol", "source": "composite", "id": "natural-line-label", "paint": {"text-halo-width": 0.5, "text-halo-color": "rgb(255, 255, 255)", "text-halo-blur": 0.5, "text-color": "rgb(128, 128, 128)"}, "source-layer": "natural_label"}, {"minzoom": 4, "layout": {"text-size": ["step", ["zoom"], ["step", ["get", "sizerank"], 18, 5, 12], 17, ["step", ["get", "sizerank"], 18, 13, 12]], "icon-image": "", "text-font": ["DIN Pro Medium", "Arial Unicode MS Regular"], "text-offset": ["literal", [0, 0]], "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "visibility": "none"}, "metadata": {"mapbox:featureComponent": "natural-features", "mapbox:group": "Natural features, natural-labels"}, "filter": ["all", ["match", ["get", "class"], ["dock", "glacier", "landform", "water_feature", "wetland"], ["match", ["get", "worldview"], ["all", "US"], true, false], ["disputed_dock", "disputed_glacier", "disputed_landform", "disputed_water_feature", "disputed_wetland"], ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], ["==", ["geometry-type"], "Point"], ["<=", ["get", "filterrank"], 1]], "type": "symbol", "source": "composite", "id": "natural-point-label", "paint": {"icon-opacity": ["step", ["zoom"], ["step", ["get", "sizerank"], 0, 5, 1], 17, ["step", ["get", "sizerank"], 0, 13, 1]], "text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 0.5, "text-halo-blur": 0.5, "text-color": "rgb(128, 128, 128)"}, "source-layer": "natural_label"}, {"id": "water-line-label", "type": "symbol", "metadata": {"mapbox:featureComponent": "natural-features", "mapbox:group": "Natural features, natural-labels"}, "source": "composite", "source-layer": "natural_label", "filter": ["all", ["match", ["get", "class"], ["bay", "ocean", "reservoir", "sea", "water"], ["match", ["get", "worldview"], ["all", "US"], true, false], ["disputed_bay", "disputed_ocean", "disputed_reservoir", "disputed_sea", "disputed_water"], ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], ["==", ["geometry-type"], "LineString"]], "layout": {"text-size": ["interpolate", ["linear"], ["zoom"], 7, ["step", ["get", "sizerank"], 20, 6, 18, 12, 12], 10, ["step", ["get", "sizerank"], 15, 9, 12], 18, ["step", ["get", "sizerank"], 15, 9, 14]], "text-max-angle": 30, "text-letter-spacing": ["match", ["get", "class"], "ocean", 0.25, ["sea", "bay"], 0.15, 0], "text-font": ["DIN Pro Italic", "Arial Unicode MS Regular"], "symbol-placement": "line-center", "text-pitch-alignment": "viewport", "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "visibility": "none"}, "paint": {"text-color": "rgb(150, 150, 150)"}}, {"id": "water-point-label", "type": "symbol", "source": "composite", "source-layer": "natural_label", "filter": ["all", ["match", ["get", "class"], ["bay", "ocean", "reservoir", "sea", "water"], ["match", ["get", "worldview"], ["all", "US"], true, false], ["disputed_bay", "disputed_ocean", "disputed_reservoir", "disputed_sea", "disputed_water"], ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], ["==", ["geometry-type"], "Point"]], "layout": {"text-line-height": 1.3, "text-size": ["interpolate", ["linear"], ["zoom"], 7, ["step", ["get", "sizerank"], 20, 6, 15, 12, 12], 10, ["step", ["get", "sizerank"], 15, 9, 12]], "text-font": ["DIN Pro Italic", "Arial Unicode MS Regular"], "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "text-letter-spacing": ["match", ["get", "class"], "ocean", 0.25, ["bay", "sea"], 0.15, 0.01], "text-max-width": ["match", ["get", "class"], "ocean", 4, "sea", 5, ["bay", "water"], 7, 10], "visibility": "none"}, "paint": {"text-color": "rgb(150, 150, 150)"}, "metadata": {"mapbox:featureComponent": "natural-features", "mapbox:group": "Natural features, natural-labels"}}, {"minzoom": 6, "layout": {"text-size": ["step", ["zoom"], ["step", ["get", "sizerank"], 18, 5, 12], 17, ["step", ["get", "sizerank"], 18, 13, 12]], "icon-image": "", "text-font": ["DIN Pro Medium", "Arial Unicode MS Regular"], "text-offset": [0, 0], "text-anchor": ["step", ["zoom"], ["step", ["get", "sizerank"], "center", 5, "top"], 17, ["step", ["get", "sizerank"], "center", 13, "top"]], "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "visibility": "none"}, "metadata": {"mapbox:featureComponent": "point-of-interest-labels", "mapbox:group": "Point of interest labels, poi-labels"}, "filter": ["<=", ["get", "filterrank"], ["+", ["step", ["zoom"], 1, 2, 3, 4, 5], 1]], "type": "symbol", "source": "composite", "id": "poi-label", "paint": {"text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 0.5, "text-halo-blur": 0.5, "text-color": ["step", ["zoom"], ["step", ["get", "sizerank"], "rgb(184, 184, 184)", 5, "rgb(161, 161, 161)"], 17, ["step", ["get", "sizerank"], "rgb(184, 184, 184)", 13, "rgb(161, 161, 161)"]]}, "source-layer": "poi_label"}, {"minzoom": 8, "layout": {"text-line-height": 1.1, "text-size": ["step", ["get", "sizerank"], 18, 9, 12], "icon-image": ["get", "maki"], "text-font": ["DIN Pro Medium", "Arial Unicode MS Regular"], "visibility": "none", "text-offset": [0, 0.75], "text-rotation-alignment": "viewport", "text-anchor": "top", "text-field": ["step", ["get", "sizerank"], ["coalesce", ["get", "name_en"], ["get", "name"]], 15, ["get", "ref"]], "text-letter-spacing": 0.01, "text-max-width": 9}, "metadata": {"mapbox:featureComponent": "transit", "mapbox:group": "Transit, transit-labels"}, "filter": ["match", ["get", "class"], ["military", "civil"], ["match", ["get", "worldview"], ["all", "US"], true, false], ["disputed_military", "disputed_civil"], ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], "type": "symbol", "source": "composite", "id": "airport-label", "paint": {"text-color": "rgb(128, 128, 128)", "text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 1}, "source-layer": "airport_label"}, {"minzoom": 10, "layout": {"text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "text-transform": "uppercase", "text-font": ["DIN Pro Regular", "Arial Unicode MS Regular"], "text-letter-spacing": ["match", ["get", "type"], "suburb", 0.15, 0.1], "text-max-width": 7, "text-padding": 3, "text-size": ["interpolate", ["cubic-bezier", 0.5, 0, 1, 1], ["zoom"], 11, ["match", ["get", "type"], "suburb", 11, 10.5], 15, ["match", ["get", "type"], "suburb", 15, 14]], "visibility": "none"}, "metadata": {"mapbox:featureComponent": "place-labels", "mapbox:group": "Place labels, place-labels"}, "maxzoom": 15, "filter": ["all", ["match", ["get", "class"], "settlement_subdivision", ["match", ["get", "worldview"], ["all", "US"], true, false], "disputed_settlement_subdivision", ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], ["<=", ["get", "filterrank"], 4]], "type": "symbol", "source": "composite", "id": "settlement-subdivision-label", "paint": {"text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 1, "text-color": "rgb(179, 179, 179)", "text-halo-blur": 0.5}, "source-layer": "place_label"}, {"minzoom": 3, "layout": {"text-line-height": 1.1, "text-size": ["interpolate", ["cubic-bezier", 0.2, 0, 0.9, 1], ["zoom"], 3, ["step", ["get", "symbolrank"], 12, 9, 11, 10, 10.5, 12, 9.5, 14, 8.5, 16, 6.5, 17, 4], 13, ["step", ["get", "symbolrank"], 23, 9, 21, 10, 19, 11, 17, 12, 16, 13, 15, 15, 13]], "text-radial-offset": ["step", ["zoom"], ["match", ["get", "capital"], 2, 0.6, 0.55], 8, 0], "icon-image": ["step", ["zoom"], ["case", ["==", ["get", "capital"], 2], "border-dot-13", ["step", ["get", "symbolrank"], "dot-11", 9, "dot-10", 11, "dot-9"]], 8, ""], "text-font": ["DIN Pro Regular", "Arial Unicode MS Regular"], "text-justify": "auto", "visibility": "none", "text-anchor": ["step", ["zoom"], ["get", "text_anchor"], 8, "center"], "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "text-max-width": 7}, "metadata": {"mapbox:featureComponent": "place-labels", "mapbox:group": "Place labels, place-labels"}, "maxzoom": 13, "filter": ["all", ["<=", ["get", "filterrank"], 3], ["match", ["get", "class"], "settlement", ["match", ["get", "worldview"], ["all", "US"], true, false], "disputed_settlement", ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], ["step", ["zoom"], [">", ["get", "symbolrank"], 6], 1, [">=", ["get", "symbolrank"], 7], 2, [">=", ["get", "symbolrank"], 8], 3, [">=", ["get", "symbolrank"], 10], 4, [">=", ["get", "symbolrank"], 11], 5, [">=", ["get", "symbolrank"], 13], 6, [">=", ["get", "symbolrank"], 15]]], "type": "symbol", "source": "composite", "id": "settlement-minor-label", "paint": {"text-color": ["step", ["get", "symbolrank"], "rgb(128, 128, 128)", 11, "rgb(161, 161, 161)", 16, "rgb(184, 184, 184)"], "text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 1, "text-halo-blur": 1}, "source-layer": "place_label"}, {"minzoom": 3, "layout": {"text-line-height": 1.1, "text-size": ["interpolate", ["cubic-bezier", 0.2, 0, 0.9, 1], ["zoom"], 3, ["step", ["get", "symbolrank"], 13, 6, 12], 6, ["step", ["get", "symbolrank"], 16, 6, 15, 7, 14], 8, ["step", ["get", "symbolrank"], 18, 9, 17, 10, 15], 15, ["step", ["get", "symbolrank"], 23, 9, 22, 10, 20, 11, 18, 12, 16, 13, 15, 15, 13]], "text-radial-offset": ["step", ["zoom"], ["match", ["get", "capital"], 2, 0.6, 0.55], 8, 0], "icon-image": ["step", ["zoom"], ["case", ["==", ["get", "capital"], 2], "border-dot-13", ["step", ["get", "symbolrank"], "dot-11", 9, "dot-10", 11, "dot-9"]], 8, ""], "text-font": ["DIN Pro Medium", "Arial Unicode MS Regular"], "text-justify": ["step", ["zoom"], ["match", ["get", "text_anchor"], ["left", "bottom-left", "top-left"], "left", ["right", "bottom-right", "top-right"], "right", "center"], 8, "center"], "visibility": "none", "text-anchor": ["step", ["zoom"], ["get", "text_anchor"], 8, "center"], "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "text-max-width": 7}, "metadata": {"mapbox:featureComponent": "place-labels", "mapbox:group": "Place labels, place-labels"}, "maxzoom": 15, "filter": ["all", ["<=", ["get", "filterrank"], 3], ["match", ["get", "class"], "settlement", ["match", ["get", "worldview"], ["all", "US"], true, false], "disputed_settlement", ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], ["step", ["zoom"], false, 1, ["<=", ["get", "symbolrank"], 6], 2, ["<", ["get", "symbolrank"], 7], 3, ["<", ["get", "symbolrank"], 8], 4, ["<", ["get", "symbolrank"], 10], 5, ["<", ["get", "symbolrank"], 11], 6, ["<", ["get", "symbolrank"], 13], 7, ["<", ["get", "symbolrank"], 15], 8, [">=", ["get", "symbolrank"], 11], 9, [">=", ["get", "symbolrank"], 15]]], "type": "symbol", "source": "composite", "id": "settlement-major-label", "paint": {"text-color": ["step", ["get", "symbolrank"], "rgb(128, 128, 128)", 11, "rgb(161, 161, 161)", 16, "rgb(184, 184, 184)"], "text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 1, "text-halo-blur": 1}, "source-layer": "place_label"}, {"minzoom": 3, "layout": {"text-size": ["interpolate", ["cubic-bezier", 0.85, 0.7, 0.65, 1], ["zoom"], 4, ["step", ["get", "symbolrank"], 10, 6, 9.5, 7, 9], 9, ["step", ["get", "symbolrank"], 21, 6, 16, 7, 13]], "text-transform": "uppercase", "text-font": ["DIN Pro Bold", "Arial Unicode MS Bold"], "text-field": ["step", ["zoom"], ["step", ["get", "symbolrank"], ["coalesce", ["get", "name_en"], ["get", "name"]], 5, ["coalesce", ["get", "abbr"], ["get", "name_en"], ["get", "name"]]], 5, ["coalesce", ["get", "name_en"], ["get", "name"]]], "text-letter-spacing": 0.15, "text-max-width": 6, "visibility": "none"}, "metadata": {"mapbox:featureComponent": "place-labels", "mapbox:group": "Place labels, place-labels"}, "maxzoom": 9, "filter": ["match", ["get", "class"], "state", ["match", ["get", "worldview"], ["all", "US"], true, false], "disputed_state", ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], "type": "symbol", "source": "composite", "id": "state-label", "paint": {"text-color": "rgb(184, 184, 184)", "text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 1}, "source-layer": "place_label"}, {"minzoom": 1, "layout": {"text-line-height": 1.1, "text-size": ["interpolate", ["cubic-bezier", 0.2, 0, 0.7, 1], ["zoom"], 1, ["step", ["get", "symbolrank"], 11, 4, 9, 5, 8], 9, ["step", ["get", "symbolrank"], 22, 4, 19, 5, 17]], "text-radial-offset": ["step", ["zoom"], 0.6, 8, 0], "icon-image": "", "text-font": ["DIN Pro Medium", "Arial Unicode MS Regular"], "text-justify": ["step", ["zoom"], ["match", ["get", "text_anchor"], ["left", "bottom-left", "top-left"], "left", ["right", "bottom-right", "top-right"], "right", "center"], 7, "auto"], "visibility": "none", "text-field": ["coalesce", ["get", "name_en"], ["get", "name"]], "text-max-width": 6}, "metadata": {"mapbox:featureComponent": "place-labels", "mapbox:group": "Place labels, place-labels"}, "maxzoom": 10, "filter": ["match", ["get", "class"], "country", ["match", ["get", "worldview"], ["all", "US"], true, false], "disputed_country", ["all", ["==", ["get", "disputed"], "true"], ["match", ["get", "worldview"], ["all", "US"], true, false]], false], "type": "symbol", "source": "composite", "id": "country-label", "paint": {"icon-opacity": ["step", ["zoom"], ["case", ["has", "text_anchor"], 1, 0], 7, 0], "text-color": "rgb(128, 128, 128)", "text-halo-color": "rgb(255, 255, 255)", "text-halo-width": 1.25}, "source-layer": "place_label"}], "created": "2021-11-05T16:12:04.822Z", "modified": "2021-11-25T13:58:04.167Z", "id": "ckvmksrpd4n0a14pfdo5heqzr", "owner": "commaai", "visibility": "private", "protected": false, "draft": false} \ No newline at end of file From 7e9961b9ac8c8e22197148d3741164c072200d3c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 13 Oct 2022 17:40:07 -0700 Subject: [PATCH 161/178] FPv2: support collecting versions for specific ecus (#25699) * Add VMCU address for EV6 * Rename vmcu * add to tests add to tests * rename to more generic name * more explicit * remove print * Like this much better, removes subtle fingerprinting problems * clean up * add test and clean up * remove hyundai stuffs * global * Fpv2Config class * fix missing fw versions from import order * unused * revert for now * test for fpv2 configs with subtests * subtests don't work that way * remove this * . * intersection * print ecus * shorter * fix typing * use config --- selfdrive/car/chrysler/values.py | 9 ++++----- selfdrive/car/fw_query_definitions.py | 4 +++- selfdrive/car/fw_versions.py | 5 +++++ selfdrive/car/tests/test_fw_fingerprint.py | 7 +++++++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 7180ace524..c703ef6cb8 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -166,14 +166,13 @@ FW_QUERY_CONFIG = FwQueryConfig( bus=0, ), ], + extra_ecus=[ + (Ecu.hcp, 0x7e2, None), # manages transmission on hybrids + (Ecu.abs, 0x7e4, None), # alt address for abs on hybrids + ], ) FW_VERSIONS = { - CAR.PACIFICA_2019_HYBRID: { - (Ecu.hcp, 0x7e2, None): [], - (Ecu.abs, 0x7e4, None): [], - }, - CAR.RAM_1500: { (Ecu.combinationMeter, 0x742, None): [ b'68294063AH', diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index c3b74da920..c7e4d4eb30 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -2,7 +2,7 @@ import capnp from dataclasses import dataclass, field import struct -from typing import Dict, List +from typing import Dict, List, Optional, Tuple import panda.python.uds as uds @@ -64,3 +64,5 @@ class FwQueryConfig: requests: List[Request] # Overrides and removes from essential ecus for specific models and ecus (exact matching) 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) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 7e03f4b020..d3e8eae0de 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -213,6 +213,11 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=Fa def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, debug=False, progress=False): versions = VERSIONS.copy() + + # 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]} diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index f0d2744a98..ed323b0563 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -44,6 +44,13 @@ class TestFwFingerprint(unittest.TestCase): 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}") + def test_data_collection_ecus(self): + for brand, config in FW_QUERY_CONFIGS.items(): + for car_model, ecus in VERSIONS[brand].items(): + bad_ecus = set(ecus).intersection(config.extra_ecus) + with self.subTest(car_model=car_model): + self.assertFalse(len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}') + def test_blacklisted_ecus(self): blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu for car_model, ecus in FW_VERSIONS.items(): From a02f42959c330d7df45c623b08c285e43c19726d Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Thu, 13 Oct 2022 19:45:42 -0700 Subject: [PATCH 162/178] Hyundai docs: fix model name/year formatting (#26074) * fix formatting so that model years can be parsed * generate car docs --- docs/CARS.md | 8 ++++---- selfdrive/car/hyundai/values.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 77d6b890db..40eef06102 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -57,8 +57,8 @@ A supported vehicle is one that just works when you install a comma three. All s |Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |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)](##)|Hyundai J| -|Hyundai|Ioniq 5 2022 (with HDA II)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| -|Hyundai|Ioniq 5 2022 (without HDA II)|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| +|Hyundai|Ioniq 5 (with HDA II) 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| +|Hyundai|Ioniq 5 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |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)](##)|Hyundai C| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |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)](##)|Hyundai C| @@ -84,8 +84,8 @@ A supported vehicle is one that just works when you install a comma three. All s |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)](##)|FCA| |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)](##)|FCA| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Kia|EV6 2022 (with HDA II)|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| -|Kia|EV6 2022 (without HDA II)|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| +|Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| +|Kia|EV6 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| |Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|Niro EV 2019|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 2d5e6792ee..a24992f90f 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -142,8 +142,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), CAR.IONIQ_5: [ - HyundaiCarInfo("Hyundai Ioniq 5 2022 (without HDA II)" , "Highway Driving Assist", harness=Harness.hyundai_k), - HyundaiCarInfo("Hyundai Ioniq 5 2022 (with HDA II)", "Highway Driving Assist II", harness=Harness.hyundai_q), + HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022" , "Highway Driving Assist", harness=Harness.hyundai_k), + HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022", "Highway Driving Assist II", harness=Harness.hyundai_q), ], CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n), @@ -175,8 +175,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), CAR.KIA_EV6: [ - HyundaiCarInfo("Kia EV6 2022 (without HDA II)", "Highway Driving Assist", harness=Harness.hyundai_l), - HyundaiCarInfo("Kia EV6 2022 (with HDA II)", "Highway Driving Assist II", harness=Harness.hyundai_p) + HyundaiCarInfo("Kia EV6 (without HDA II) 2022", "Highway Driving Assist", harness=Harness.hyundai_l), + HyundaiCarInfo("Kia EV6 (with HDA II) 2022", "Highway Driving Assist II", harness=Harness.hyundai_p) ], # Genesis From ed87c0f95a42a304c1ebc2892c1c740f147c78c1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 13 Oct 2022 19:50:09 -0700 Subject: [PATCH 163/178] CAN-FD HKG: add ADAS Driving ECU query (#26075) add ADAS Driving ECU query --- selfdrive/car/hyundai/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index a24992f90f..d3a4b8bbb1 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -299,6 +299,9 @@ FW_QUERY_CONFIG = FwQueryConfig( [HYUNDAI_VERSION_RESPONSE], ), ], + extra_ecus=[ + (Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms + ], ) FW_VERSIONS = { From 422167cdf96b52eaaf001f27b447f23055e31709 Mon Sep 17 00:00:00 2001 From: Matt Webb Date: Thu, 13 Oct 2022 20:51:57 -0700 Subject: [PATCH 164/178] HKG: add missing FW versions for 2022 Kia EV6 Light (#26071) Fingerprint 2022 Kia EV6 Light --- selfdrive/car/hyundai/values.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index d3a4b8bbb1..77759f1fa7 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1322,11 +1322,13 @@ FW_VERSIONS = { }, CAR.KIA_EV6: { (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00CV IEB \x02 101!\x10\x18 58520-CV100', b'\xf1\x00CV IEB \x03 101!\x10\x18 58520-CV100', b'\xf1\x8758520CV100\xf1\x00CV IEB \x02 101!\x10\x18 58520-CV100', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CV1 MDPS R 1.00 1.04 57700-CV000 1B30', + b'\xf1\x00CV1 MDPS R 1.00 1.05 57700-CV000 2425', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', @@ -1334,6 +1336,7 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027', ], }, From 5d00e5cc71106c6c4a9b25e3a2873e9e0606544c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 14 Oct 2022 15:53:24 -0700 Subject: [PATCH 165/178] GM: remove brake scaling (#26080) * Don't add a weird factor to ret.brake * update refs --- selfdrive/car/gm/carstate.py | 4 ++-- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index a5f89c58b0..f90b130d93 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -50,8 +50,8 @@ class CarState(CarStateBase): # that the brake is being intermittently pressed without user interaction. # To avoid a cruise fault we need to match the ECM's brake pressed signal and threshold # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf - ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"] / 0xd0 - ret.brakePressed = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"] >= 8 + ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"] + ret.brakePressed = ret.brake >= 8 # Regen braking is braking if self.CP.transmissionType == TransmissionType.direct: diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 6a8f5a273c..99257fa930 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -1e4bb3f620bddbe6ead966d6f2dd7db3fd730308 \ No newline at end of file +1066a612bcaab6103d44ab93ad68397a85d37833 \ No newline at end of file From c0840e0c33257b7ee86ace2c9a0b985ae61ca71c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 14 Oct 2022 16:50:07 -0700 Subject: [PATCH 166/178] Fix refs --- selfdrive/test/process_replay/ref_commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 99257fa930..864b7019a0 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -1066a612bcaab6103d44ab93ad68397a85d37833 \ No newline at end of file +e5a86c14e2318f2dd218b3985cdbea6f875f7d83 From 91a7bb4ea3f01f40a7d8e88e857d60ef090dad2c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 14 Oct 2022 16:54:13 -0700 Subject: [PATCH 167/178] GM camera ACC: raise brake pressed threshold (#26081) * Different brake pressed thresholds * comment * bump to master --- panda | 2 +- selfdrive/car/gm/carstate.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/panda b/panda index c39528d299..5962bcd08a 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit c39528d299aae6a1ebbdbccddeae55bc76a534e3 +Subproject commit 5962bcd08ae4e7c5193535efb45cc9c39da52f54 diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index f90b130d93..21eb440d7f 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -51,7 +51,12 @@ class CarState(CarStateBase): # To avoid a cruise fault we need to match the ECM's brake pressed signal and threshold # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"] - ret.brakePressed = ret.brake >= 8 + if self.CP.networkLocation != NetworkLocation.fwdCamera: + ret.brakePressed = ret.brake >= 8 + else: + # While car is braking, cancel button causes ECM to enter a soft disable state with a fault status. + # Match ECM threshold at a standstill to allow the camera to cancel earlier + ret.brakePressed = ret.brake >= 20 # Regen braking is braking if self.CP.transmissionType == TransmissionType.direct: From 4e82f68de2641e628f0df1aa82e5a5e935c8cce6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 14 Oct 2022 22:21:53 -0700 Subject: [PATCH 168/178] GM camera ACC: prevent fault when engaging at a stop (#26079) * prevent bolt fault * comment * only for camera ACC * fixup alert * bump cereal to master * use new name * Update selfdrive/car/gm/interface.py * Update selfdrive/car/gm/interface.py * Update selfdrive/car/gm/interface.py * only care about prevent engagement when we look at PCM --- cereal | 2 +- selfdrive/car/gm/interface.py | 8 +++++++- selfdrive/controls/lib/events.py | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cereal b/cereal index 3eca747334..5766e645f2 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 3eca747334ca2138bf35d70399d58d0706a3cbd2 +Subproject commit 5766e645f2ee2a131b145fb1ea9e3b7c55a4a740 diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 22ea83759a..248828e757 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -212,7 +212,13 @@ class CarInterface(CarInterfaceBase): if ret.cruiseState.standstill: events.add(EventName.resumeRequired) if ret.vEgo < self.CP.minSteerSpeed: - events.add(car.CarEvent.EventName.belowSteerSpeed) + events.add(EventName.belowSteerSpeed) + + if self.CP.networkLocation == NetworkLocation.fwdCamera and self.CP.pcmCruise: + # The ECM has a higher brake pressed threshold than the camera, causing an + # ACC fault when you engage at a stop with your foot partially on the brake + if ret.vEgoRaw < 0.1 and ret.brake < 20: + events.add(EventName.gmAccFaultedTemp) ret.events = events.to_msg() diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 91f1748ecb..5bfe89b31c 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -811,6 +811,10 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.NO_ENTRY: NoEntryAlert("Cruise Faulted"), }, + EventName.gmAccFaultedTemp: { + ET.NO_ENTRY: NoEntryAlert("Cruise Temporarily Faulted"), + }, + EventName.controlsMismatch: { ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Controls Mismatch"), ET.NO_ENTRY: NoEntryAlert("Controls Mismatch"), From deac907cb44230f641fde5ca1ba22d7c5e236d90 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 16 Oct 2022 02:36:09 +0800 Subject: [PATCH 169/178] Cabana: right click on the chart to reset zoom (#26088) --- tools/cabana/chartswidget.cc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index e704dea95a..5dafd8ec37 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -305,8 +305,14 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { event->accept(); return; } + } else if (event->button() == Qt::RightButton) { + // reset zoom + if (can->isZoomed()) { + can->resetRange(); + event->accept(); + return; + } } - // TODO: right-click to reset zoom QChartView::mouseReleaseEvent(event); line_marker->setVisible(true); } From df6c135cfaabe830d2447800d49daac445660c64 Mon Sep 17 00:00:00 2001 From: AlexandreSato <66435071+AlexandreSato@users.noreply.github.com> Date: Sat, 15 Oct 2022 16:15:47 -0300 Subject: [PATCH 170/178] Multilang: Update pt-BR translation. (#26089) * update pt-BR translations * fix some cutoff texts * update pt-BR translations --- selfdrive/ui/translations/main_pt-BR.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 6a772a1f69..8f59bf4715 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -60,11 +60,11 @@ Cellular Metered - + Plano de Dados Limitado Prevent large data uploads when on a metered connection - + Evite grandes uploads de dados quando estiver em uma conexão limitada @@ -476,7 +476,7 @@ trabalho definido PRIME FEATURES: - APRIMORAMENTOS PRIME: + BENEFÍCIOS PRIME: Remote access From 553068f8c3afd9fd2c3a3a412dd20a8e03c6c58f Mon Sep 17 00:00:00 2001 From: Jason Wen <47793918+sunnyhaibin@users.noreply.github.com> Date: Sat, 15 Oct 2022 15:27:34 -0400 Subject: [PATCH 171/178] Hyundai: CAN-FD Hybrid gas pressed signal (#26086) * Hyundai: Gate 0x105 behind hybrid CAN-FD only * Update values.py * bump panda Co-authored-by: Adeeb Shihadeh --- panda | 2 +- selfdrive/car/hyundai/carstate.py | 4 ++-- selfdrive/car/hyundai/values.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/panda b/panda index 5962bcd08a..62868c36a8 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 5962bcd08ae4e7c5193535efb45cc9c39da52f54 +Subproject commit 62868c36a80d1f44064da7b47423f0ef331f64e9 diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 948634e974..5da1dd72c8 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -156,7 +156,7 @@ class CarState(CarStateBase): if self.CP.carFingerprint in EV_CAR: ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. - else: + elif self.CP.carFingerprint in HYBRID_CAR: ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023. ret.gasPressed = ret.gas > 1e-5 ret.brakePressed = cp.vl["BRAKE"]["BRAKE_PRESSED"] == 1 @@ -466,7 +466,7 @@ class CarState(CarStateBase): checks += [ ("ACCELERATOR", 100), ] - else: + elif CP.carFingerprint in HYBRID_CAR: signals += [ ("ACCELERATOR_PEDAL", "ACCELERATOR_ALT"), ] diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 77759f1fa7..734e56c033 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1397,7 +1397,7 @@ CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_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} # these cars use a different gas signal +HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN} # 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.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5} # these cars require a special panda safety mode due to missing counters and checksums in the messages From bf5a6565c0f2d6791606ff906f6ddb4cba27fb18 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 15 Oct 2022 16:05:52 -0700 Subject: [PATCH 172/178] cabana: misc touchups (#26092) * fix time formatting * disable vertical resize * Update tools/cabana/videowidget.cc --- tools/cabana/detailwidget.cc | 2 +- tools/cabana/videowidget.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 531cd4c665..4127b6c3ed 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -1,4 +1,3 @@ - #include "tools/cabana/detailwidget.h" #include @@ -138,6 +137,7 @@ BinaryView::BinaryView(QWidget *parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); table = new QTableWidget(this); + table->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); table->horizontalHeader()->hide(); table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 193a6f8788..9e2129afaf 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -11,7 +11,7 @@ #include inline QString formatTime(int seconds) { - return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh::mm::ss" : "mm::ss"); + return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); } VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { From b654ebdd25bb3903e94ca5cc6f55d53a256f5c06 Mon Sep 17 00:00:00 2001 From: HaraldSchafer Date: Sat, 15 Oct 2022 20:04:35 -0700 Subject: [PATCH 173/178] Refactor model: no klblock (#26035) * ff138dc0-d097-4818-b40e-dba5ba89d5d6/449 13274b7d-b546-4b91-a587-33b4af7dec6a/700 * b1bb39be-c6ce-4744-8e63-92969fda6bfc/449 f3ebfba1-f686-448f-be9b-b4d5010be91c/700 * model ref Co-authored-by: Yassine Yousfi --- selfdrive/modeld/models/supercombo.onnx | 4 ++-- selfdrive/test/process_replay/model_replay_ref_commit | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index aee0ac37ff..59b8883d2a 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4d37af666344af6bb218e0b939b1152ad3784c15ac79e37bcf0124643c8286a -size 58539563 +oid sha256:022a830c39267f378f45204682060c93e3aa304bbd8cfa6b2dfe4fa8f419102d +size 56972617 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 2446ec061d..b3e9c8c488 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -c171250d2cc013b3eca1cda4fb62f3d0dda28d4d +bfb0a2a52212d2aa1619d999aaae97fa7f7ff788 From e25ea8529698053f4c1b915b7e014ebdc499e56a Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 16 Oct 2022 22:55:53 +0800 Subject: [PATCH 174/178] Cabana: complete edit functions (#26097) complete forms --- tools/cabana/chartswidget.cc | 105 ++++++++-------- tools/cabana/chartswidget.h | 25 ++-- tools/cabana/dbcmanager.cc | 22 ++-- tools/cabana/dbcmanager.h | 7 +- tools/cabana/detailwidget.cc | 215 ++++++++++++++++++--------------- tools/cabana/detailwidget.h | 42 +++---- tools/cabana/historylog.cc | 5 +- tools/cabana/messageswidget.cc | 20 +-- tools/cabana/signaledit.cc | 74 +++++------- tools/cabana/signaledit.h | 29 ++--- 10 files changed, 260 insertions(+), 284 deletions(-) diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 5dafd8ec37..d0f5356fac 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -1,7 +1,6 @@ #include "tools/cabana/chartswidget.h" #include -#include #include #include #include @@ -14,6 +13,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { // title bar title_bar = new QWidget(this); + title_bar->setVisible(false); QHBoxLayout *title_layout = new QHBoxLayout(title_bar); title_layout->setContentsMargins(0, 0, 0, 0); title_label = new QLabel(tr("Charts")); @@ -25,13 +25,11 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { title_layout->addWidget(range_label); reset_zoom_btn = new QPushButton("⟲", this); - reset_zoom_btn->setVisible(false); reset_zoom_btn->setFixedSize(30, 30); reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); title_layout->addWidget(reset_zoom_btn); remove_all_btn = new QPushButton("✖", this); - remove_all_btn->setVisible(false); remove_all_btn->setToolTip(tr("Remove all charts")); remove_all_btn->setFixedSize(30, 30); title_layout->addWidget(remove_all_btn); @@ -56,10 +54,20 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { main_layout->addWidget(charts_scroll); - updateTitleBar(); - - QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeChart); QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); + QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeChart); + QObject::connect(dbc(), &DBCManager::signalUpdated, [this](const Signal *sig) { + if (auto it = charts.find(sig); it != charts.end()) { + it.value()->chart_view->updateSeries(); + } + }); + QObject::connect(dbc(), &DBCManager::msgUpdated, [this](const QString &id) { + for (auto chart : charts) { + if (chart->id == id) + chart->updateTitle(); + } + }); + QObject::connect(can, &CANMessages::rangeChanged, [this]() { updateTitleBar(); }); QObject::connect(reset_zoom_btn, &QPushButton::clicked, can, &CANMessages::resetRange); QObject::connect(remove_all_btn, &QPushButton::clicked, this, &ChartsWidget::removeAll); @@ -71,54 +79,43 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { } void ChartsWidget::updateTitleBar() { - if (!charts.size()) { - title_bar->setVisible(false); - return; - } - - title_label->setText(tr("Charts (%1)").arg(charts.size())); + title_bar->setVisible(!charts.isEmpty()); + if (charts.isEmpty()) return; // show select range + range_label->setVisible(can->isZoomed()); + reset_zoom_btn->setEnabled(can->isZoomed()); if (can->isZoomed()) { auto [min, max] = can->range(); range_label->setText(tr("%1 - %2").arg(min, 0, 'f', 2).arg(max, 0, 'f', 2)); - range_label->setVisible(true); - reset_zoom_btn->setEnabled(true); - } else { - reset_zoom_btn->setEnabled(false); - range_label->setVisible(false); } + title_label->setText(tr("Charts (%1)").arg(charts.size())); dock_btn->setText(docking ? "⬈" : "⬋"); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); - remove_all_btn->setVisible(!charts.empty()); - reset_zoom_btn->setVisible(!charts.empty()); - title_bar->setVisible(true); } -void ChartsWidget::addChart(const QString &id, const QString &sig_name) { - const QString char_name = id + ":" + sig_name; - if (charts.find(char_name) == charts.end()) { - auto chart = new ChartWidget(id, sig_name, this); - QObject::connect(chart, &ChartWidget::remove, [=]() { - removeChart(id, sig_name); - }); +void ChartsWidget::addChart(const QString &id, const Signal *sig) { + if (!charts.contains(sig)) { + auto chart = new ChartWidget(id, sig, this); + QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(sig); }); charts_layout->insertWidget(0, chart); - charts[char_name] = chart; + charts.insert(sig, chart); } updateTitleBar(); } -void ChartsWidget::removeChart(const QString &id, const QString &sig_name) { - if (auto it = charts.find(id + ":" + sig_name); it != charts.end()) { - it->second->deleteLater(); - charts.erase(it); +void ChartsWidget::removeChart(const Signal *sig) { + auto it = charts.find(sig); + if (it != charts.end()) { + it.value()->deleteLater(); + charts.remove(sig); } updateTitleBar(); } void ChartsWidget::removeAll() { - for (auto [_, chart] : charts) + for (auto chart : charts) chart->deleteLater(); charts.clear(); updateTitleBar(); @@ -134,19 +131,16 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { // ChartWidget -ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *parent) : id(id), sig_name(sig_name), QWidget(parent) { +ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent) : id(id), signal(sig), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); - - QWidget *chart_widget = new QWidget(this); - QVBoxLayout *chart_layout = new QVBoxLayout(chart_widget); - chart_layout->setSpacing(0); - chart_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); + main_layout->setContentsMargins(0, 0, 0, 0); QWidget *header = new QWidget(this); header->setStyleSheet("background-color:white"); QHBoxLayout *header_layout = new QHBoxLayout(header); header_layout->setContentsMargins(11, 11, 11, 0); - QLabel *title = new QLabel(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); + title = new QLabel(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); header_layout->addWidget(title); header_layout->addStretch(); @@ -155,26 +149,28 @@ ChartWidget::ChartWidget(const QString &id, const QString &sig_name, QWidget *pa remove_btn->setToolTip(tr("Remove chart")); QObject::connect(remove_btn, &QPushButton::clicked, this, &ChartWidget::remove); header_layout->addWidget(remove_btn); - chart_layout->addWidget(header); + main_layout->addWidget(header); - chart_view = new ChartView(id, sig_name, this); + chart_view = new ChartView(id, sig, this); chart_view->setFixedHeight(300); - chart_layout->addWidget(chart_view); - chart_layout->addStretch(); - - main_layout->addWidget(chart_widget); + main_layout->addWidget(chart_view); + main_layout->addStretch(); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); } +void ChartWidget::updateTitle() { + title->setText(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); +} + // ChartView -ChartView::ChartView(const QString &id, const QString &sig_name, QWidget *parent) - : id(id), sig_name(sig_name), QChartView(nullptr, parent) { +ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) + : id(id), signal(sig), QChartView(nullptr, parent) { QLineSeries *series = new QLineSeries(); series->setUseOpenGL(true); QChart *chart = new QChart(); - chart->setTitle(sig_name); + chart->setTitle(sig->name.c_str()); chart->addSeries(series); chart->createDefaultAxes(); chart->legend()->hide(); @@ -205,10 +201,7 @@ ChartView::ChartView(const QString &id, const QString &sig_name, QWidget *parent QObject::connect(can, &CANMessages::rangeChanged, this, &ChartView::rangeChanged); QObject::connect(can, &CANMessages::eventsMerged, this, &ChartView::updateSeries); QObject::connect(dynamic_cast(chart->axisX()), &QValueAxis::rangeChanged, can, &CANMessages::setRange); - QObject::connect(dbc(), &DBCManager::signalUpdated, [this](const QString &msg_id, const QString &sig_name) { - if (this->id == msg_id && this->sig_name == sig_name) - updateSeries(); - }); + updateSeries(); } @@ -219,9 +212,9 @@ void ChartView::updateState() { } void ChartView::updateSeries() { - const Signal *sig = dbc()->signal(id, sig_name); + chart()->setTitle(signal->name.c_str()); auto events = can->events(); - if (!sig || !events) return; + if (!events) return; auto l = id.split(':'); int bus = l[0].toInt(); @@ -235,7 +228,7 @@ void ChartView::updateSeries() { for (auto c : evt->event.getCan()) { if (bus == c.getSrc() && address == c.getAddress()) { auto dat = c.getDat(); - double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *sig); + double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *signal); double ts = (evt->mono_time / (double)1e9) - route_start_time; // seconds vals.push_back({ts, value}); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index af12560cc9..a3c470e960 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -9,7 +9,6 @@ #include #include #include -#include #include "tools/cabana/canmessages.h" #include "tools/cabana/dbcmanager.h" @@ -20,7 +19,8 @@ class ChartView : public QChartView { Q_OBJECT public: - ChartView(const QString &id, const QString &sig_name, QWidget *parent = nullptr); + ChartView(const QString &id, const Signal *sig, QWidget *parent = nullptr); + void updateSeries(); private: void mouseReleaseEvent(QMouseEvent *event) override; @@ -28,7 +28,6 @@ private: void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; - void updateSeries(); void rangeChanged(qreal min, qreal max); void updateAxisY(); void updateState(); @@ -38,22 +37,23 @@ private: QGraphicsLineItem *line_marker; QList vals; QString id; - QString sig_name; + const Signal *signal; }; class ChartWidget : public QWidget { Q_OBJECT public: - ChartWidget(const QString &id, const QString &sig_name, QWidget *parent); - inline QChart *chart() const { return chart_view->chart(); } + ChartWidget(const QString &id, const Signal *sig, QWidget *parent); + void updateTitle(); signals: void remove(); -protected: +public: QString id; - QString sig_name; + const Signal *signal; + QLabel *title; ChartView *chart_view = nullptr; }; @@ -62,11 +62,8 @@ class ChartsWidget : public QWidget { public: ChartsWidget(QWidget *parent = nullptr); - void addChart(const QString &id, const QString &sig_name); - void removeChart(const QString &id, const QString &sig_name); - inline bool hasChart(const QString &id, const QString &sig_name) { - return charts.find(id + sig_name) != charts.end(); - } + void addChart(const QString &id, const Signal *sig); + void removeChart(const Signal *sig); signals: void dock(bool floating); @@ -85,5 +82,5 @@ private: QPushButton *reset_zoom_btn; QPushButton *remove_all_btn; QVBoxLayout *charts_layout; - std::map charts; + QHash charts; }; diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index 1cb6da7fb5..5b1bddcabe 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -36,14 +36,17 @@ void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size void DBCManager::addSignal(const QString &id, const Signal &sig) { if (Msg *m = const_cast(msg(id))) { m->sigs.push_back(sig); - emit signalAdded(id, QString::fromStdString(sig.name)); + emit signalAdded(&m->sigs.back()); } } void DBCManager::updateSignal(const QString &id, const QString &sig_name, const Signal &sig) { - if (Signal *s = const_cast(signal(id, sig_name))) { - *s = sig; - emit signalUpdated(id, sig_name); + if (Msg *m = const_cast(msg(id))) { + auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); + if (it != m->sigs.end()) { + *it = sig; + emit signalUpdated(&(*it)); + } } } @@ -51,21 +54,12 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) { if (Msg *m = const_cast(msg(id))) { auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); if (it != m->sigs.end()) { + emit signalRemoved(&(*it)); m->sigs.erase(it); - emit signalRemoved(id, sig_name); } } } -const Signal *DBCManager::signal(const QString &id, const QString &sig_name) const { - if (auto m = msg(id)) { - auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return sig_name == s.name.c_str(); }); - if (it != m->sigs.end()) - return &(*it); - } - return nullptr; -} - uint32_t DBCManager::addressFromId(const QString &id) { return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16); } diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 06c071be82..1f890a39db 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -14,7 +14,6 @@ public: void open(const QString &dbc_file_name); void save(const QString &dbc_file_name); - const Signal *signal(const QString &id, const QString &sig_name) const; void addSignal(const QString &id, const Signal &sig); void updateSignal(const QString &id, const QString &sig_name, const Signal &sig); void removeSignal(const QString &id, const QString &sig_name); @@ -31,9 +30,9 @@ public: } signals: - void signalAdded(const QString &id, const QString &sig_name); - void signalRemoved(const QString &id, const QString &sig_name); - void signalUpdated(const QString &id, const QString &sig_name); + void signalAdded(const Signal *sig); + void signalRemoved(const Signal *sig); + void signalUpdated(const Signal *sig); void msgUpdated(const QString &id); void DBCFileChanged(); diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 4127b6c3ed..021e1b73cf 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -3,9 +3,9 @@ #include #include #include +#include #include #include -#include // DetailWidget @@ -32,60 +32,52 @@ DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { binary_view = new BinaryView(this); main_layout->addWidget(binary_view, 0, Qt::AlignTop); - // signal header - signals_header = new QWidget(this); - QHBoxLayout *signals_header_layout = new QHBoxLayout(signals_header); - signals_header_layout->addWidget(new QLabel(tr("Signals"))); - signals_header_layout->addStretch(); - QPushButton *add_sig_btn = new QPushButton(tr("Add signal"), this); - signals_header_layout->addWidget(add_sig_btn); - signals_header->setVisible(false); - main_layout->addWidget(signals_header); - - // scroll area + // signals + signals_container = new QWidget(this); + signals_container->setLayout(new QVBoxLayout); + signals_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + scroll = new ScrollArea(this); - QWidget *container = new QWidget(this); - container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - QVBoxLayout *container_layout = new QVBoxLayout(container); - signal_edit_layout = new QVBoxLayout(); - signal_edit_layout->setSpacing(2); - container_layout->addLayout(signal_edit_layout); - - scroll->setWidget(container); + scroll->setWidget(signals_container); scroll->setWidgetResizable(true); scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); main_layout->addWidget(scroll); + // history log history_log = new HistoryLog(this); main_layout->addWidget(history_log); - QObject::connect(add_sig_btn, &QPushButton::clicked, this, &DetailWidget::addSignal); QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); + QObject::connect(binary_view, &BinaryView::cellsSelected, this, &DetailWidget::addSignal); QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &DetailWidget::dbcMsgChanged); } void DetailWidget::setMessage(const QString &message_id) { - msg_id = message_id; - for (auto f : signal_forms) { - f->deleteLater(); + if (msg_id != message_id) { + msg_id = message_id; + dbcMsgChanged(); } - signal_forms.clear(); +} + +void DetailWidget::dbcMsgChanged() { + if (msg_id.isEmpty()) return; + qDeleteAll(signals_container->findChildren()); + QString msg_name = tr("untitled"); if (auto msg = dbc()->msg(msg_id)) { for (int i = 0; i < msg->sigs.size(); ++i) { - auto form = new SignalEdit(i, msg_id, msg->sigs[i], getColor(i)); - signal_edit_layout->addWidget(form); - QObject::connect(form, &SignalEdit::showChart, this, &DetailWidget::showChart); + auto form = new SignalEdit(i, msg_id, msg->sigs[i]); + signals_container->layout()->addWidget(form); + QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]]() { emit showChart(msg_id, sig); }); QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); - signal_forms.push_back(form); + QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); + QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); } - name_label->setText(msg->name.c_str()); - signals_header->setVisible(true); - } else { - name_label->setText(tr("untitled")); - signals_header->setVisible(false); + msg_name = msg->name.c_str(); } edit_btn->setVisible(true); + name_label->setText(msg_name); binary_view->setMessage(msg_id); history_log->setMessage(msg_id); @@ -99,60 +91,90 @@ void DetailWidget::updateState() { history_log->updateState(); } -void DetailWidget::editMsg() { - EditMessageDialog dlg(msg_id, this); - if (dlg.exec()) { - setMessage(msg_id); +void DetailWidget::showForm() { + SignalEdit *sender = qobject_cast(QObject::sender()); + for (auto f : signals_container->findChildren()) { + f->setFormVisible(f == sender && !f->isFormVisible()); + if (f == sender) { + QTimer::singleShot(0, [=]() { scroll->ensureWidgetVisible(f); }); + } } } -void DetailWidget::addSignal() { - AddSignalDialog dlg(msg_id, this); +void DetailWidget::editMsg() { + auto msg = dbc()->msg(msg_id); + QString name = msg ? msg->name.c_str() : "untitled"; + int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); + EditMessageDialog dlg(msg_id, name, size, this); if (dlg.exec()) { - setMessage(msg_id); + dbc()->updateMsg(msg_id, dlg.name_edit->text(), dlg.size_spin->value()); + dbcMsgChanged(); } } -void DetailWidget::showForm() { - SignalEdit *sender = qobject_cast(QObject::sender()); - if (sender->isFormVisible()) { - sender->setFormVisible(false); - } else { - for (auto f : signal_forms) { - f->setFormVisible(f == sender); - if (f == sender) { - // scroll to header - QTimer::singleShot(0, [=]() { - const QPoint p = f->mapTo(scroll, QPoint(0, 0)); - scroll->verticalScrollBar()->setValue(p.y() + scroll->verticalScrollBar()->value()); - }); - } +void DetailWidget::addSignal(int start_bit, int size) { + if (dbc()->msg(msg_id)) { + AddSignalDialog dlg(msg_id, start_bit, size, this); + if (dlg.exec()) { + dbc()->addSignal(msg_id, dlg.form->getSignal()); + dbcMsgChanged(); } } } +void DetailWidget::saveSignal() { + SignalEdit *sig_form = qobject_cast(QObject::sender()); + auto s = sig_form->form->getSignal(); + dbc()->updateSignal(msg_id, sig_form->sig_name, s); + // update binary view and history log + binary_view->setMessage(msg_id); + history_log->setMessage(msg_id); +} + +void DetailWidget::removeSignal() { + SignalEdit *sig_form = qobject_cast(QObject::sender()); + QString text = tr("Are you sure you want to remove signal '%1'").arg(sig_form->sig_name); + if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove signal"), text)) { + dbc()->removeSignal(msg_id, sig_form->sig_name); + dbcMsgChanged(); + } +} + // BinaryView -BinaryView::BinaryView(QWidget *parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setContentsMargins(0, 0, 0, 0); - table = new QTableWidget(this); - table->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); - table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - table->horizontalHeader()->hide(); - table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - main_layout->addWidget(table); - table->setColumnCount(9); +BinaryView::BinaryView(QWidget *parent) : QTableWidget(parent) { + horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); + horizontalHeader()->hide(); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setColumnCount(9); + + // replace selection model + auto old_model = selectionModel(); + setSelectionModel(new BinarySelectionModel(model())); + delete old_model; +} + +void BinaryView::mouseReleaseEvent(QMouseEvent *event) { + QTableWidget::mouseReleaseEvent(event); + + if (auto items = selectedItems(); !items.isEmpty()) { + int start_bit = items.first()->row() * 8 + items.first()->column(); + int size = items.back()->row() * 8 + items.back()->column() - start_bit + 1; + emit cellsSelected(start_bit, size); + } } void BinaryView::setMessage(const QString &message_id) { msg_id = message_id; + if (msg_id.isEmpty()) return; + const Msg *msg = dbc()->msg(msg_id); - const int row_count = msg ? msg->size : can->lastMessage(msg_id).dat.size(); - table->setRowCount(row_count); - table->setColumnCount(9); - for (int i = 0; i < table->rowCount(); ++i) { - for (int j = 0; j < table->columnCount(); ++j) { + int row_count = msg ? msg->size : can->lastMessage(msg_id).dat.size(); + setRowCount(row_count); + setColumnCount(9); + for (int i = 0; i < rowCount(); ++i) { + for (int j = 0; j < columnCount(); ++j) { auto item = new QTableWidgetItem(); item->setFlags(item->flags() ^ Qt::ItemIsEditable); item->setTextAlignment(Qt::AlignCenter); @@ -160,8 +182,9 @@ void BinaryView::setMessage(const QString &message_id) { QFont font; font.setBold(true); item->setFont(font); + item->setFlags(item->flags() ^ Qt::ItemIsSelectable); } - table->setItem(i, j, item); + setItem(i, j, item); } } @@ -170,71 +193,73 @@ void BinaryView::setMessage(const QString &message_id) { for (int i = 0; i < msg->sigs.size(); ++i) { const auto &sig = msg->sigs[i]; int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit); - for (int j = start; j <= start + sig.size - 1; ++j) { - table->item(j / 8, j % 8)->setBackground(QColor(getColor(i))); + for (int j = start; j <= std::min(start + sig.size - 1, rowCount() * columnCount() - 1); ++j) { + item(j / 8, j % 8)->setBackground(QColor(getColor(i))); } } } - table->setFixedHeight(table->rowHeight(0) * std::min(row_count, 8) + 2); + + setFixedHeight(rowHeight(0) * std::min(row_count, 8) + 2); + clearSelection(); updateState(); } void BinaryView::updateState() { - if (msg_id.isEmpty()) return; - const auto &binary = can->lastMessage(msg_id).dat; - setUpdatesEnabled(false); char hex[3] = {'\0'}; for (int i = 0; i < binary.size(); ++i) { for (int j = 0; j < 8; ++j) { - table->item(i, j)->setText(QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0')); + item(i, j)->setText(QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0')); } hex[0] = toHex(binary[i] >> 4); hex[1] = toHex(binary[i] & 0xf); - table->item(i, 8)->setText(hex); + item(i, 8)->setText(hex); } setUpdatesEnabled(true); } +void BinarySelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { + QItemSelection new_selection = selection; + if (auto indexes = selection.indexes(); !indexes.isEmpty()) { + auto [begin_idx, end_idx] = (QModelIndex[]){indexes.first(), indexes.back()}; + for (int row = begin_idx.row(); row <= end_idx.row(); ++row) { + int left_col = (row == begin_idx.row()) ? begin_idx.column() : 0; + int right_col = (row == end_idx.row()) ? end_idx.column() : 7; + new_selection.merge({model()->index(row, left_col), model()->index(row, right_col)}, command); + } + } + QItemSelectionModel::select(new_selection, command); +} + // EditMessageDialog -EditMessageDialog::EditMessageDialog(const QString &msg_id, QWidget *parent) : msg_id(msg_id), QDialog(parent) { +EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Edit message")); QVBoxLayout *main_layout = new QVBoxLayout(this); QFormLayout *form_layout = new QFormLayout(); form_layout->addRow("ID", new QLabel(msg_id)); - const auto msg = dbc()->msg(msg_id); - name_edit = new QLineEdit(this); - name_edit->setText(msg ? msg->name.c_str() : "untitled"); + name_edit = new QLineEdit(title, this); form_layout->addRow(tr("Name"), name_edit); size_spin = new QSpinBox(this); - size_spin->setValue(msg ? msg->size : can->lastMessage(msg_id).dat.size()); + // TODO: limit the maximum? + size_spin->setMinimum(1); + size_spin->setValue(size); form_layout->addRow(tr("Size"), size_spin); main_layout->addLayout(form_layout); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox); - setFixedWidth(parent->width() * 0.9); - connect(buttonBox, &QDialogButtonBox::accepted, this, &EditMessageDialog::save); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } -void EditMessageDialog::save() { - const QString name = name_edit->text(); - if (size_spin->value() <= 0 || name_edit->text().isEmpty() || name == tr("untitled")) - return; - - dbc()->updateMsg(msg_id, name, size_spin->value()); - QDialog::accept(); -} - // ScrollArea bool ScrollArea::eventFilter(QObject *obj, QEvent *ev) { diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 99fe321012..db174873f7 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -1,12 +1,7 @@ #pragma once -#include -#include -#include #include #include -#include -#include #include "opendbc/can/common.h" #include "opendbc/can/common_dbc.h" @@ -15,29 +10,32 @@ #include "tools/cabana/historylog.h" #include "tools/cabana/signaledit.h" -class BinaryView : public QWidget { - Q_OBJECT +class BinarySelectionModel : public QItemSelectionModel { +public: + BinarySelectionModel(QAbstractItemModel *model = nullptr) : QItemSelectionModel(model) {} + void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override; +}; +class BinaryView : public QTableWidget { + Q_OBJECT public: - BinaryView(QWidget *parent); + BinaryView(QWidget *parent = nullptr); + void mouseReleaseEvent(QMouseEvent *event) override; void setMessage(const QString &message_id); void updateState(); +signals: + void cellsSelected(int start_bit, int size); private: QString msg_id; - QTableWidget *table; }; class EditMessageDialog : public QDialog { Q_OBJECT public: - EditMessageDialog(const QString &msg_id, QWidget *parent); + EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent); -protected: - void save(); - - QString msg_id; QLineEdit *name_edit; QSpinBox *size_spin; }; @@ -57,24 +55,24 @@ class DetailWidget : public QWidget { public: DetailWidget(QWidget *parent); void setMessage(const QString &message_id); + void dbcMsgChanged(); signals: - void showChart(const QString &msg_id, const QString &sig_name); - -private slots: - void showForm(); + void showChart(const QString &msg_id, const Signal *sig); + void removeChart(const Signal *sig); private: - void addSignal(); + void addSignal(int start_bit, int size); + void saveSignal(); + void removeSignal(); void editMsg(); + void showForm(); void updateState(); QString msg_id; QLabel *name_label, *time_label; QPushButton *edit_btn; - QVBoxLayout *signal_edit_layout; - QWidget *signals_header; - QList signal_forms; + QWidget *signals_container; HistoryLog *history_log; BinaryView *binary_view; ScrollArea *scroll; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 494e281cb1..5a77b5aa9e 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -52,9 +52,8 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i void HistoryLogModel::updateState() { if (msg_id.isEmpty()) return; - const auto &can_msgs = can->messages(msg_id); int prev_row_count = row_count; - row_count = can_msgs.size(); + row_count = can->messages(msg_id).size(); int delta = row_count - prev_row_count; if (delta > 0) { beginInsertRows({}, prev_row_count, row_count - 1); @@ -64,7 +63,7 @@ void HistoryLogModel::updateState() { endRemoveRows(); } if (row_count > 0) { - emit dataChanged(index(0, 0), index(row_count - 1, column_count - 1)); + emit dataChanged(index(0, 0), index(row_count - 1, column_count - 1), {Qt::DisplayRole}); emit headerDataChanged(Qt::Vertical, 0, row_count - 1); } } diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index eaf84fbace..f10cbf44b4 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -68,7 +68,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { }); QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { if (current.isValid()) { - emit msgSelectionChanged(table_widget->model()->data(current, Qt::UserRole).toString()); + emit msgSelectionChanged(current.data(Qt::UserRole).toString()); } }); @@ -78,11 +78,8 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { void MessagesWidget::dbcSelectionChanged(const QString &dbc_file) { dbc()->open(dbc_file); - // update detailwidget - auto current = table_widget->selectionModel()->currentIndex(); - if (current.isValid()) { - emit msgSelectionChanged(table_widget->model()->data(current, Qt::UserRole).toString()); - } + // TODO: reset model? + table_widget->sortByColumn(0, Qt::AscendingOrder); } // MessageListModel @@ -90,9 +87,6 @@ void MessagesWidget::dbcSelectionChanged(const QString &dbc_file) { QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) return (QString[]){"Name", "ID", "Count", "Bytes"}[section]; - else if (orientation == Qt::Vertical && role == Qt::DisplayRole) { - // return QString::number(section); - } return {}; } @@ -100,17 +94,15 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { auto it = std::next(can->can_msgs.begin(), index.row()); if (it != can->can_msgs.end() && !it.value().empty()) { - const auto &d = it.value().front(); const QString &msg_id = it.key(); switch (index.column()) { case 0: { auto msg = dbc()->msg(msg_id); - QString name = msg ? msg->name.c_str() : "untitled"; - return name; + return msg ? msg->name.c_str() : "untitled"; } case 1: return msg_id; case 2: return can->counters[msg_id]; - case 3: return toHex(d.dat); + case 3: return toHex(it.value().front().dat); } } } else if (role == Qt::UserRole) { @@ -132,6 +124,6 @@ void MessageListModel::updateState() { } if (row_count > 0) { - emit dataChanged(index(0, 0), index(row_count - 1, 3)); + emit dataChanged(index(0, 0), index(row_count - 1, 3), {Qt::DisplayRole}); } } diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 3f48450195..2da3e2eec2 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -3,7 +3,6 @@ #include #include #include -#include #include // SignalForm @@ -15,13 +14,10 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start form_layout->addRow(tr("Name"), name); size = new QSpinBox(); + size->setMinimum(1); size->setValue(sig.size); form_layout->addRow(tr("Size"), size); - msb = new QSpinBox(); - msb->setValue(sig.msb); - form_layout->addRow(tr("Most significant bit"), msb); - endianness = new QComboBox(); endianness->addItems({"Little", "Big"}); endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1); @@ -56,7 +52,8 @@ SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start form_layout->addRow(tr("Value descriptions"), val_desc); } -std::optional SignalForm::getSignal() { +Signal SignalForm::getSignal() { + // TODO: Check if the size is valid, and no duplicate name Signal sig = {}; sig.start_bit = start_bit; sig.name = name->text().toStdString(); @@ -72,17 +69,17 @@ std::optional SignalForm::getSignal() { sig.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(sig.start_bit) + sig.size - 1); sig.msb = sig.start_bit; } - return (sig.name.empty() || sig.size <= 0) ? std::nullopt : std::optional(sig); + return sig; } // SignalEdit -SignalEdit::SignalEdit(int index, const QString &id, const Signal &sig, const QString &color, QWidget *parent) - : id(id), name_(sig.name.c_str()), QWidget(parent) { +SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal &sig, QWidget *parent) + : sig_name(sig.name.c_str()), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - // title + // title bar QHBoxLayout *title_layout = new QHBoxLayout(); icon = new QLabel(">"); icon->setFixedSize(15, 30); @@ -90,24 +87,25 @@ SignalEdit::SignalEdit(int index, const QString &id, const Signal &sig, const QS title_layout->addWidget(icon); title = new ElidedLabel(this); title->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - title->setText(QString("%1. %2").arg(index + 1).arg(sig.name.c_str())); - title->setStyleSheet(QString("font-weight:bold; color:%1").arg(color)); + title->setText(QString("%1. %2").arg(index + 1).arg(sig_name)); + title->setStyleSheet(QString("font-weight:bold; color:%1").arg(getColor(index))); title_layout->addWidget(title); - plot_btn = new QPushButton("📈"); + QPushButton *plot_btn = new QPushButton("📈"); plot_btn->setToolTip(tr("Show Plot")); plot_btn->setFixedSize(30, 30); - QObject::connect(plot_btn, &QPushButton::clicked, [=]() { emit showChart(id, name_); }); + QObject::connect(plot_btn, &QPushButton::clicked, this, &SignalEdit::showChart); title_layout->addWidget(plot_btn); main_layout->addLayout(title_layout); + // signal form form_container = new QWidget(this); QVBoxLayout *v_layout = new QVBoxLayout(form_container); form = new SignalForm(sig, this); v_layout->addWidget(form); QHBoxLayout *h = new QHBoxLayout(); - remove_btn = new QPushButton(tr("Remove Signal")); + QPushButton *remove_btn = new QPushButton(tr("Remove Signal")); h->addWidget(remove_btn); h->addStretch(); QPushButton *save_btn = new QPushButton(tr("Save")); @@ -117,13 +115,19 @@ SignalEdit::SignalEdit(int index, const QString &id, const Signal &sig, const QS form_container->setVisible(false); main_layout->addWidget(form_container); - QFrame* hline = new QFrame(); + // bottom line + QFrame *hline = new QFrame(); hline->setFrameShape(QFrame::HLine); hline->setFrameShadow(QFrame::Sunken); main_layout->addWidget(hline); QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); - QObject::connect(save_btn, &QPushButton::clicked, this, &SignalEdit::save); + QObject::connect(save_btn, &QPushButton::clicked, [=]() { + QString new_name = form->getSignal().name.c_str(); + title->setText(QString("%1. %2").arg(index + 1).arg(new_name)); + emit save(); + sig_name = new_name; + }); QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); } @@ -132,40 +136,24 @@ void SignalEdit::setFormVisible(bool visible) { icon->setText(visible ? "▼" : ">"); } -void SignalEdit::save() { - if (auto s = form->getSignal()) - dbc()->updateSignal(id, name_, *s); -} - -void SignalEdit::remove() { - QMessageBox msgbox; - msgbox.setText(tr("Remove signal")); - msgbox.setInformativeText(tr("Are you sure you want to remove signal '%1'").arg(name_)); - msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - msgbox.setDefaultButton(QMessageBox::Cancel); - if (msgbox.exec()) { - dbc()->removeSignal(id, name_); - deleteLater(); - } -} - // AddSignalDialog -AddSignalDialog::AddSignalDialog(const QString &id, QWidget *parent) : QDialog(parent) { +AddSignalDialog::AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Add signal to %1").arg(dbc()->msg(id)->name.c_str())); QVBoxLayout *main_layout = new QVBoxLayout(this); - Signal sig = {.name = "untitled"}; - auto form = new SignalForm(sig, this); + + Signal sig = { + .name = "untitled", + .start_bit = bigEndianBitIndex(start_bit), + .is_little_endian = false, + .size = size, + }; + form = new SignalForm(sig, this); main_layout->addWidget(form); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); main_layout->addWidget(buttonBox); setFixedWidth(parent->width() * 0.9); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - connect(buttonBox, &QDialogButtonBox::accepted, [=]() { - if (auto signal = form->getSignal()) { - dbc()->addSignal(id, *signal); - } - QDialog::accept(); - }); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); } diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index 00c13948b7..f31408657f 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -15,14 +13,12 @@ #include "tools/cabana/dbcmanager.h" class SignalForm : public QWidget { - Q_OBJECT - public: SignalForm(const Signal &sig, QWidget *parent); - std::optional getSignal(); + Signal getSignal(); QLineEdit *name, *unit, *comment, *val_desc; - QSpinBox *size, *msb, *lsb, *offset; + QSpinBox *size, *offset; QDoubleSpinBox *factor, *min_val, *max_val; QComboBox *sign, *endianness; int start_bit = 0; @@ -32,31 +28,26 @@ class SignalEdit : public QWidget { Q_OBJECT public: - SignalEdit(int index, const QString &id, const Signal &sig, const QString &color, QWidget *parent = nullptr); + SignalEdit(int index, const QString &msg_id, const Signal &sig, QWidget *parent = nullptr); void setFormVisible(bool show); inline bool isFormVisible() const { return form_container->isVisible(); } - void save(); + QString sig_name; + SignalForm *form; signals: - void showChart(const QString &msg_id, const QString &sig_name); + void showChart(); void showFormClicked(); - -protected: void remove(); + void save(); - QString id; - QString name_; - QPushButton *plot_btn; +protected: ElidedLabel *title; - SignalForm *form; QWidget *form_container; - QPushButton *remove_btn; QLabel *icon; }; class AddSignalDialog : public QDialog { - Q_OBJECT - public: - AddSignalDialog(const QString &id, QWidget *parent); + AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent); + SignalForm *form; }; From e3268d88c5226afaf5a300a775b8989bcff18b20 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 16 Oct 2022 23:24:34 +0800 Subject: [PATCH 175/178] cabana: use monospace font for hex string (#26102) specify monospace font for hex string --- tools/cabana/detailwidget.cc | 3 ++- tools/cabana/historylog.cc | 7 ++++++- tools/cabana/messageswidget.cc | 5 +++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 021e1b73cf..a9899ec650 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -1,6 +1,7 @@ #include "tools/cabana/detailwidget.h" #include +#include #include #include #include @@ -179,7 +180,7 @@ void BinaryView::setMessage(const QString &message_id) { item->setFlags(item->flags() ^ Qt::ItemIsEditable); item->setTextAlignment(Qt::AlignCenter); if (j == 8) { - QFont font; + QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); font.setBold(true); item->setFont(font); item->setFlags(item->flags() ^ Qt::ItemIsSelectable); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 5a77b5aa9e..cbb3b6e882 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -1,20 +1,25 @@ #include "tools/cabana/historylog.h" +#include #include #include QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { + auto msg = dbc()->msg(msg_id); if (role == Qt::DisplayRole) { const auto &can_msgs = can->messages(msg_id); if (index.row() < can_msgs.size()) { const auto &can_data = can_msgs[index.row()]; - auto msg = dbc()->msg(msg_id); if (msg && index.column() < msg->sigs.size()) { return get_raw_value((uint8_t *)can_data.dat.begin(), can_data.dat.size(), msg->sigs[index.column()]); } else { return toHex(can_data.dat); } } + } else if (role == Qt::FontRole) { + if (index.column() == 0 && !(msg && msg->sigs.size() > 0)) { + return QFontDatabase::systemFont(QFontDatabase::FixedFont); + } } return {}; } diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index f10cbf44b4..3c9af67ea6 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -107,6 +108,10 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { } } else if (role == Qt::UserRole) { return std::next(can->can_msgs.begin(), index.row()).key(); + } else if (role == Qt::FontRole) { + if (index.column() == 3) { + return QFontDatabase::systemFont(QFontDatabase::FixedFont); + } } return {}; } From d4f9343a2c1f5345dec07a93b25594b588cd9963 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 17 Oct 2022 03:30:23 +0800 Subject: [PATCH 176/178] Cabana: load from high quality video by default (#26100) --- tools/cabana/cabana.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 88b175663f..1096487973 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -14,6 +14,7 @@ int main(int argc, char *argv[]) { cmd_parser.addHelpOption(); cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); cmd_parser.addOption({"demo", "use a demo route instead of providing your own"}); + cmd_parser.addOption({"qcam", "load qcamera"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); cmd_parser.process(app); const QStringList args = cmd_parser.positionalArguments(); @@ -23,7 +24,7 @@ int main(int argc, char *argv[]) { const QString route = args.empty() ? DEMO_ROUTE : args.first(); CANMessages p(&app); - if (!p.loadRoute(route, cmd_parser.value("data_dir"), true)) { + if (!p.loadRoute(route, cmd_parser.value("data_dir"), cmd_parser.isSet("qcam"))) { return 0; } MainWindow w; From d109dda720b502a94f914c1a29ffdea6d01fbc6f Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 17 Oct 2022 03:31:26 +0800 Subject: [PATCH 177/178] Cabana: click on video to play/pause (#26099) --- tools/cabana/videowidget.cc | 19 ++++++++++--------- tools/cabana/videowidget.h | 3 +++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 9e2129afaf..b6fe8de3e2 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -39,9 +38,9 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { // btn controls QHBoxLayout *control_layout = new QHBoxLayout(); - QPushButton *play = new QPushButton("⏸"); - play->setStyleSheet("font-weight:bold"); - control_layout->addWidget(play); + play_btn = new QPushButton("⏸"); + play_btn->setStyleSheet("font-weight:bold"); + control_layout->addWidget(play_btn); QButtonGroup *group = new QButtonGroup(this); group->setExclusive(true); @@ -61,11 +60,13 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QObject::connect(can, &CANMessages::updated, this, &VideoWidget::updateState); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); - QObject::connect(play, &QPushButton::clicked, [=]() { - bool is_paused = can->isPaused(); - play->setText(is_paused ? "⏸" : "▶"); - can->pause(!is_paused); - }); + QObject::connect(cam_widget, &CameraViewWidget::clicked, [this]() { pause(!can->isPaused()); }); + QObject::connect(play_btn, &QPushButton::clicked, [=]() { pause(!can->isPaused()); }); +} + +void VideoWidget::pause(bool pause) { + play_btn->setText(!pause ? "⏸" : "▶"); + can->pause(pause); } void VideoWidget::rangeChanged(double min, double max) { diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index e80e3b48f9..fd896f1e11 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -29,8 +30,10 @@ public: protected: void rangeChanged(double min, double max); void updateState(); + void pause(bool pause); CameraViewWidget *cam_widget; QLabel *end_time_label; + QPushButton *play_btn; Slider *slider; }; From 00494a44f4fb8f9e18ce82e22bf40fbe6bc1a805 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 16 Oct 2022 15:54:36 -0700 Subject: [PATCH 178/178] CI speedup (#26096) * CI speedup * use the new stuff * push * no regressions * try that * don't let this slip * fix modeld tests * fix linter * modernize prebuilt * cleanup * fix those * increase a bit --- .github/workflows/prebuilt.yaml | 8 +- .github/workflows/selfdrive_tests.yaml | 109 +++++++++--------- .github/workflows/setup/action.yaml | 8 +- .github/workflows/tools_tests.yaml | 45 +++++--- .pre-commit-config.yaml | 1 + release/build_devel.sh | 4 +- selfdrive/modeld/tests/__init__.py | 0 .../modeld/{test => tests}/dmon_lag/repro.cc | 0 .../{test => tests}/snpe_benchmark/.gitignore | 0 .../snpe_benchmark/benchmark.cc | 1 + .../snpe_benchmark/benchmark.sh | 0 .../modeld/{test => tests}/test_modeld.py | 0 .../modeld/{test => tests}/tf_test/build.sh | 0 .../modeld/{test => tests}/tf_test/main.cc | 0 .../{test => tests}/tf_test/pb_loader.py | 0 .../{test => tests}/timing/benchmark.py | 0 16 files changed, 95 insertions(+), 81 deletions(-) create mode 100644 selfdrive/modeld/tests/__init__.py rename selfdrive/modeld/{test => tests}/dmon_lag/repro.cc (100%) rename selfdrive/modeld/{test => tests}/snpe_benchmark/.gitignore (100%) rename selfdrive/modeld/{test => tests}/snpe_benchmark/benchmark.cc (99%) rename selfdrive/modeld/{test => tests}/snpe_benchmark/benchmark.sh (100%) rename selfdrive/modeld/{test => tests}/test_modeld.py (100%) rename selfdrive/modeld/{test => tests}/tf_test/build.sh (100%) rename selfdrive/modeld/{test => tests}/tf_test/main.cc (100%) rename selfdrive/modeld/{test => tests}/tf_test/pb_loader.py (100%) rename selfdrive/modeld/{test => tests}/timing/benchmark.py (100%) diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index b659d4ceee..c8b4c51e38 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -2,7 +2,6 @@ name: prebuilt on: schedule: - cron: '0 * * * *' - workflow_dispatch: env: @@ -11,9 +10,7 @@ env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: | - docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true - docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true - docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . jobs: build_prebuilt: @@ -37,8 +34,7 @@ jobs: - name: Build Docker image run: | eval "$BUILD" - docker pull $DOCKER_REGISTRY/$IMAGE_NAME:latest || true - docker build --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f Dockerfile.openpilot . + DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f Dockerfile.openpilot . - name: Push to container registry run: | $DOCKER_LOGIN diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index cd34c6d27c..d84779103b 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -18,15 +18,12 @@ env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: | - docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true - docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true - docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c BUILD_CL: | - docker pull $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest || true - docker build --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl . + DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl . RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c UNIT_TEST: coverage run --append -m unittest discover @@ -41,13 +38,10 @@ jobs: steps: - uses: actions/checkout@v3 with: - fetch-depth: 0 submodules: true - name: Build devel run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh - uses: ./.github/workflows/setup - with: - save-cache: true - name: Check submodules if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' run: release/check-submodules.sh @@ -72,18 +66,20 @@ jobs: build_all: name: build all runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 30 steps: - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/workflows/setup + with: + save-cache: true - name: Build openpilot with all flags run: ${{ env.RUN }} "scons -j$(nproc) --extras && release/check-dirty.sh" - name: Cleanup scons cache run: | ${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \ - scons -j$(nproc) --extras --cache-populate" + scons -j$(nproc) --cache-populate" #build_mac: # name: build macos @@ -145,7 +141,7 @@ jobs: docker_push: name: docker push runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 22 if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' needs: static_analysis # hack to ensure slow tests run first since this and static_analysis are fast steps: @@ -154,12 +150,14 @@ jobs: submodules: true - name: Build Docker image run: eval "$BUILD" + timeout-minutes: 13 - name: Push to container registry run: | $DOCKER_LOGIN docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest - name: Build CL Docker image run: eval "$BUILD_CL" + timeout-minutes: 4 - name: Push to container registry run: | $DOCKER_LOGIN @@ -168,7 +166,7 @@ jobs: static_analysis: name: static analysis runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -176,21 +174,23 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: pre-commit + timeout-minutes: 5 run: ${{ env.RUN }} "pre-commit run --all" valgrind: name: valgrind runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/workflows/setup + - name: Build openpilot + run: ${{ env.RUN }} "scons -j$(nproc)" - name: Run valgrind run: | - ${{ env.RUN }} "scons -j$(nproc) && \ - python selfdrive/test/test_valgrind_replay.py" + ${{ env.RUN }} "python selfdrive/test/test_valgrind_replay.py" - name: Print logs if: always() run: cat selfdrive/test/valgrind_logs.txt @@ -198,16 +198,18 @@ jobs: unit_tests: name: unit tests runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 30 steps: - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/workflows/setup + - name: Build openpilot + run: ${{ env.RUN }} "scons -j$(nproc)" - name: Run unit tests + timeout-minutes: 15 run: | ${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \ - scons -j$(nproc) && \ $UNIT_TEST common && \ $UNIT_TEST opendbc/can && \ $UNIT_TEST selfdrive/boardd && \ @@ -220,7 +222,6 @@ jobs: $UNIT_TEST selfdrive/athena && \ $UNIT_TEST selfdrive/thermald && \ $UNIT_TEST system/hardware/tici && \ - $UNIT_TEST selfdrive/modeld && \ $UNIT_TEST tools/lib/tests && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ @@ -239,7 +240,7 @@ jobs: process_replay: name: process replay runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 25 steps: - uses: actions/checkout@v3 with: @@ -251,10 +252,12 @@ jobs: with: path: /tmp/comma_download_cache key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit') }} + - name: Build openpilot + run: | + ${{ env.RUN }} "scons -j$(nproc)" - name: Run replay run: | - ${{ env.RUN }} "scons -j$(nproc) && \ - CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ + ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ coverage xml" - name: Print diff if: always() @@ -268,15 +271,14 @@ jobs: - name: Upload reference logs if: ${{ failure() && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} run: | - ${{ env.RUN }} "scons -j$(nproc) && \ - CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" + ${{ env.RUN }} "CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v2 - model_replay_onnx: - name: model replay onnx + test_modeld: + name: model tests runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -285,29 +287,41 @@ jobs: - name: Build Docker image # Sim docker is needed to get the OpenCL drivers run: eval "$BUILD_CL" - - name: Run replay + - name: Build openpilot + run: | + ${{ env.RUN }} "scons -j$(nproc)" + - name: Run model replay with ONNX + timeout-minutes: 2 + run: | + ${{ env.RUN_CL }} "ONNXCPU=1 CI=1 coverage run selfdrive/test/process_replay/model_replay.py && \ + coverage xml" + - name: Run unit tests + timeout-minutes: 5 run: | - ${{ env.RUN_CL }} "scons -j$(nproc) && \ - ONNXCPU=1 CI=1 coverage run \ - selfdrive/test/process_replay/model_replay.py -j$(nproc) && \ + ${{ env.RUN_CL }} "$UNIT_TEST selfdrive/modeld && \ coverage xml" + - name: "Upload coverage to Codecov" + uses: codecov/codecov-action@v2 test_longitudinal: name: longitudinal runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/workflows/setup + - name: Build openpilot + run: | + ${{ env.RUN }} "scons -j$(nproc)" - name: Test longitudinal run: | ${{ env.RUN }} "mkdir -p selfdrive/test/out && \ - scons -j$(nproc) && \ cd selfdrive/test/longitudinal_maneuvers && \ coverage run ./test_longitudinal.py && \ coverage xml" + timeout-minutes: 2 - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v2 - uses: actions/upload-artifact@v2 @@ -320,7 +334,7 @@ jobs: test_cars: name: cars runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 20 strategy: fail-fast: false matrix: @@ -336,10 +350,12 @@ jobs: with: path: /tmp/comma_download_cache key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }} + - name: Build openpilot + run: ${{ env.RUN }} "scons -j$(nproc)" - name: Test car models + timeout-minutes: 12 run: | - ${{ env.RUN }} "scons -j$(nproc) && \ - coverage run -m pytest selfdrive/car/tests/test_models.py && \ + ${{ env.RUN }} "coverage run -m pytest selfdrive/car/tests/test_models.py && \ coverage xml && \ chmod -R 777 /tmp/comma_download_cache" env: @@ -348,29 +364,10 @@ jobs: - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v2 - docs: - name: build docs - runs-on: ubuntu-20.04 - timeout-minutes: 50 - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - name: Build docker container - run: | - docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true - docker pull $DOCKER_REGISTRY/openpilot-docs:latest || true - DOCKER_BUILDKIT=1 docker build --cache-from $DOCKER_REGISTRY/openpilot-docs:latest -t $DOCKER_REGISTRY/openpilot-docs:latest -f docs/docker/Dockerfile . - - name: Push docker container - if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' - run: | - $DOCKER_LOGIN - docker push $DOCKER_REGISTRY/openpilot-docs:latest - car_docs_diff: - name: comment on PR with car docs diff + name: PR comments runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 20 if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index 79c4c890ab..186a9d9095 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -15,9 +15,9 @@ runs: # build cache - id: date shell: bash - run: echo "::set-output name=date::$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d')" + run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - shell: bash - run: echo "${{ steps.date.outputs.date }}" + run: echo "$CACHE_COMMIT_DATE" - shell: bash run: echo "CACHE_SKIP_SAVE=true" >> $GITHUB_ENV if: github.ref != 'refs/heads/master' || inputs.save-cache == 'false' @@ -27,9 +27,9 @@ runs: uses: actions/cache@03e00da99d75a2204924908e1cca7902cafce66b with: path: /tmp/scons_cache - key: scons-${{ steps.date.outputs.date }}-${{ github.sha }} + key: scons-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | - scons-${{ steps.date.outputs.date }}- + scons-${{ env.CACHE_COMMIT_DATE }}- scons- # build our docker image diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 9dc5c05837..549a2f4195 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -2,6 +2,8 @@ name: tools on: push: + branches-ignore: + - 'testing-closet*' pull_request: concurrency: @@ -15,21 +17,20 @@ env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: | - docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true - docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true - docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . + + RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c + BUILD_CL: | - docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base_cl) || true - docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true - docker build --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl . - RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e \ - GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/comma_download_cache:/tmp/comma_download_cache $BASE_IMAGE /bin/sh -c + DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl . + RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c + jobs: plotjuggler: name: plotjuggler runs-on: ubuntu-20.04 - timeout-minutes: 30 + timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -37,6 +38,7 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Unit test + timeout-minutes: 2 run: | ${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \ apt-get update && \ @@ -47,7 +49,7 @@ jobs: simulator: name: simulator runs-on: ubuntu-20.04 - timeout-minutes: 50 + timeout-minutes: 30 env: IMAGE_NAME: openpilot-sim if: github.repository == 'commaai/openpilot' @@ -61,12 +63,29 @@ jobs: run: eval "$BUILD" - name: Build base cl image run: eval "$BUILD_CL" - - name: Pull latest simulator image - run: docker pull $DOCKER_REGISTRY/$IMAGE_NAME:latest || true - name: Build simulator image - run: docker build --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim . + run: DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim . - name: Push to container registry if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' run: | $DOCKER_LOGIN docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest + + docs: + name: build docs + runs-on: ubuntu-20.04 + timeout-minutes: 25 + steps: + - uses: actions/checkout@v3 + with: + submodules: true + - name: Build docker container + run: | + DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/openpilot-docs:latest -t $DOCKER_REGISTRY/openpilot-docs:latest -f docs/docker/Dockerfile . + - name: Push docker container + if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' + run: | + $DOCKER_LOGIN + docker push $DOCKER_REGISTRY/openpilot-docs:latest + + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 25b8490f92..85c24b911b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,6 +53,7 @@ repos: types: [python] exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' args: + - -j0 - -rn - -sn - --rcfile=.pylintrc diff --git a/release/build_devel.sh b/release/build_devel.sh index 7108334808..668ac0de19 100755 --- a/release/build_devel.sh +++ b/release/build_devel.sh @@ -22,8 +22,8 @@ pre-commit uninstall || true echo "[-] bringing master-ci and devel in sync T=$SECONDS" cd $TARGET_DIR -git fetch origin master-ci -git fetch origin devel +git fetch --depth 1 origin master-ci +git fetch --depth 1 origin devel git checkout -f --track origin/master-ci git reset --hard master-ci diff --git a/selfdrive/modeld/tests/__init__.py b/selfdrive/modeld/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/modeld/test/dmon_lag/repro.cc b/selfdrive/modeld/tests/dmon_lag/repro.cc similarity index 100% rename from selfdrive/modeld/test/dmon_lag/repro.cc rename to selfdrive/modeld/tests/dmon_lag/repro.cc diff --git a/selfdrive/modeld/test/snpe_benchmark/.gitignore b/selfdrive/modeld/tests/snpe_benchmark/.gitignore similarity index 100% rename from selfdrive/modeld/test/snpe_benchmark/.gitignore rename to selfdrive/modeld/tests/snpe_benchmark/.gitignore diff --git a/selfdrive/modeld/test/snpe_benchmark/benchmark.cc b/selfdrive/modeld/tests/snpe_benchmark/benchmark.cc similarity index 99% rename from selfdrive/modeld/test/snpe_benchmark/benchmark.cc rename to selfdrive/modeld/tests/snpe_benchmark/benchmark.cc index 1e2072eea1..021e065d81 100644 --- a/selfdrive/modeld/test/snpe_benchmark/benchmark.cc +++ b/selfdrive/modeld/tests/snpe_benchmark/benchmark.cc @@ -102,6 +102,7 @@ void get_testframe(int index, std::unique_ptr &input) { fread(frame_buffer, length, 1, pFile); // std::cout << *(frame_buffer+length/4-1) << std::endl; std::copy(frame_buffer, frame_buffer+(length/4), input->begin()); + fclose(pFile); } void SaveITensor(const std::string& path, const zdl::DlSystem::ITensor* tensor) diff --git a/selfdrive/modeld/test/snpe_benchmark/benchmark.sh b/selfdrive/modeld/tests/snpe_benchmark/benchmark.sh similarity index 100% rename from selfdrive/modeld/test/snpe_benchmark/benchmark.sh rename to selfdrive/modeld/tests/snpe_benchmark/benchmark.sh diff --git a/selfdrive/modeld/test/test_modeld.py b/selfdrive/modeld/tests/test_modeld.py similarity index 100% rename from selfdrive/modeld/test/test_modeld.py rename to selfdrive/modeld/tests/test_modeld.py diff --git a/selfdrive/modeld/test/tf_test/build.sh b/selfdrive/modeld/tests/tf_test/build.sh similarity index 100% rename from selfdrive/modeld/test/tf_test/build.sh rename to selfdrive/modeld/tests/tf_test/build.sh diff --git a/selfdrive/modeld/test/tf_test/main.cc b/selfdrive/modeld/tests/tf_test/main.cc similarity index 100% rename from selfdrive/modeld/test/tf_test/main.cc rename to selfdrive/modeld/tests/tf_test/main.cc diff --git a/selfdrive/modeld/test/tf_test/pb_loader.py b/selfdrive/modeld/tests/tf_test/pb_loader.py similarity index 100% rename from selfdrive/modeld/test/tf_test/pb_loader.py rename to selfdrive/modeld/tests/tf_test/pb_loader.py diff --git a/selfdrive/modeld/test/timing/benchmark.py b/selfdrive/modeld/tests/timing/benchmark.py similarity index 100% rename from selfdrive/modeld/test/timing/benchmark.py rename to selfdrive/modeld/tests/timing/benchmark.py