Compare commits

..

322 Commits

Author SHA1 Message Date
royjr ca15ab474c fix radar 2026-07-20 20:21:02 -04:00
royjr c6d62ffdaf Merge branch 'master' into ccnc-port-radar-tracks 2026-07-20 20:06:50 -04:00
royjr a4fcf02b36 Update opendbc_repo 2026-07-20 19:53:05 -04:00
royjr f5ca90a065 Update ui.py 2026-07-20 00:03:50 -04:00
royjr 66a664ed42 more dbc updates 2026-07-19 23:03:40 -04:00
royjr c569b11472 dbc updates 2026-07-19 18:25:58 -04:00
royjr 3d3e6e37b2 Update opendbc_repo 2026-07-18 20:42:50 -04:00
royjr c33e7c1294 cache 2026-07-18 08:41:34 -04:00
royjr 3e38f8d6fe Update plannerd.py 2026-07-18 02:19:54 -04:00
royjr 9a510fd556 why drift 2026-07-17 18:19:49 -04:00
royjr bd062ec236 stuff dont ask 2026-07-17 17:59:12 -04:00
royjr 17784f72c1 Update opendbc_repo 2026-07-17 16:02:47 -04:00
royjr 5a1855b69a Update SIGNALS.md 2026-07-17 15:56:55 -04:00
royjr ccefe172bd Update find_consecutive_can_ranges.py 2026-07-17 15:56:31 -04:00
royjr 037a602a14 RADAR_3A5_3C4 2026-07-17 15:56:02 -04:00
royjr 1b5b4be457 hud 2026-07-17 14:33:40 -04:00
royjr 892efd4fd2 better op 2026-07-17 13:02:10 -04:00
royjr a9063dc09e better 2026-07-17 12:36:57 -04:00
royjr 1a9546247a live 2026-07-17 12:29:56 -04:00
royjr 287015e4fb no imp 2026-07-17 11:50:40 -04:00
royjr 575c4b03cf Update ui.py 2026-07-17 11:26:42 -04:00
royjr 30a607faee Update ui.py 2026-07-17 11:01:31 -04:00
royjr aeacb8387c motionState 2026-07-17 09:09:18 -04:00
royjr b7ac1a1bf6 Update ui.py 2026-07-17 08:51:22 -04:00
royjr 80472a73e3 Create ui.py 2026-07-16 13:28:13 -04:00
royjr 78c265a653 Create find_consecutive_can_ranges.py 2026-07-16 12:33:13 -04:00
royjr bdf9b955d1 old 2026-07-16 11:59:55 -04:00
royjr 629c31e71f lint 2026-07-16 10:35:00 -04:00
royjr 13e2da2e5a no hud 2026-07-16 02:20:35 -04:00
royjr ab073dcbb4 Update opendbc_repo 2026-07-16 02:13:45 -04:00
royjr aeb9b412fe Merge branch 'ccnc-port' into ccnc-port-radar-tracks 2026-07-16 01:32:19 -04:00
royjr b0322f5722 Update opendbc_repo 2026-07-16 01:32:10 -04:00
royjr aa636e75c8 Merge branch 'master' into ccnc-port 2026-07-16 00:23:49 -04:00
royjr 5ff3c3bd8d Update opendbc_repo 2026-07-16 00:23:43 -04:00
royjr 2e9e07b2e0 better RADAR_3A5_3C4 2026-07-15 22:53:48 -04:00
royjr e1a87d7f5b RADAR_3A5_3C4 2026-07-15 21:32:25 -04:00
royjr c177a15b60 freq age 2026-07-15 21:00:51 -04:00
royjr a159575710 Update opendbc_repo 2026-07-15 19:57:15 -04:00
royjr 3ae153d464 Update opendbc_repo 2026-07-15 19:55:14 -04:00
royjr e4057bd700 fix 2026-07-15 19:48:49 -04:00
royjr c796e0df0a fixes 2026-07-15 18:59:20 -04:00
royjr 3c58d5f527 fixes 2026-07-15 18:51:26 -04:00
royjr efb028a50f hud 2026-07-15 18:42:44 -04:00
royjr a09d9fba76 lead 2026-07-15 18:29:00 -04:00
royjr 9de5782527 neu 2026-07-15 17:43:42 -04:00
royjr 13d0810727 better tracks 2026-07-15 17:38:53 -04:00
royjr 20c543b9ba Update opendbc_repo 2026-07-15 16:14:47 -04:00
royjr a11d11c0d3 non mici 2026-07-15 16:14:09 -04:00
royjr 6ad21b7da8 cleanup 2026-07-15 16:14:09 -04:00
royjr 4d0caff9d9 cleanup 2026-07-15 16:14:09 -04:00
royjr e3e59eeebf Update opendbc_repo 2026-07-15 14:46:15 -04:00
royjr cf292b99b8 pop 2026-07-15 14:34:02 -04:00
royjr 1ec96f97a7 small white 2026-07-15 14:33:31 -04:00
royjr 1252095f45 nofade 2026-07-15 14:32:31 -04:00
royjr 6d204f173e revert proc 2026-07-15 14:30:10 -04:00
royjr a4de865768 Update opendbc_repo 2026-07-15 14:19:37 -04:00
royjr cf0c6e7d4b bright 2026-07-15 14:12:36 -04:00
royjr adf6c0f3d4 clean 2026-07-15 13:56:10 -04:00
royjr 44a865813b ego 2026-07-15 13:52:39 -04:00
royjr 28bdc052e0 more 2026-07-15 13:48:25 -04:00
royjr fcdfeb1bec doppler 2026-07-15 13:44:54 -04:00
royjr 6ad7a58d12 proclag 2026-07-15 13:40:53 -04:00
royjr c8404ffa39 lagless 2026-07-15 13:35:07 -04:00
royjr 6ee7563c53 Update opendbc_repo 2026-07-15 13:16:34 -04:00
royjr f69666c774 Update opendbc_repo 2026-07-15 12:13:51 -04:00
royjr 8485176a0f Update opendbc_repo 2026-07-14 16:20:46 -04:00
royjr f33d861e85 RadarTracks 2026-07-14 15:50:25 -04:00
royjr c58e18794f pill click always 2026-07-14 15:43:40 -04:00
royjr 23bda78e7a better toggle 2026-07-14 15:39:14 -04:00
royjr 768cd4a0ad remove RadarTracks 2026-07-14 15:26:05 -04:00
royjr dd90c9fa49 ui 2026-07-14 15:19:18 -04:00
royjr 6999e13130 ui 2026-07-14 15:16:11 -04:00
royjr 1282c32869 we are one 2026-07-14 15:12:40 -04:00
royjr 65ed51d908 better count 2026-07-14 15:11:31 -04:00
royjr 0a222d406f live 2026-07-14 15:06:41 -04:00
royjr 572ebe35ec ui 2026-07-14 14:54:36 -04:00
royjr 9a7d28c672 big 2026-07-14 14:49:53 -04:00
royjr 27d348d364 Update augmented_road_view.py 2026-07-14 14:38:31 -04:00
royjr f42d5b8c16 ui 2026-07-14 14:31:52 -04:00
royjr 20f98519dd Update toggles.py 2026-07-14 14:26:25 -04:00
royjr 57d75160a5 Update model_renderer.py 2026-07-14 10:50:43 -04:00
royjr 5435a5b97c Update opendbc_repo 2026-07-14 10:42:42 -04:00
royjr 002e6d11f3 Merge branch 'hyundai-radar-tracks' into ccnc-port-radar-tracks 2026-07-14 10:42:03 -04:00
royjr 1e11b52290 Update opendbc_repo 2026-07-14 00:40:02 -04:00
royjr 76e0204025 Update opendbc_repo 2026-07-14 00:37:13 -04:00
royjr b1b658c506 Update opendbc_repo 2026-07-14 00:32:24 -04:00
royjr cc88f2cbd6 Update opendbc_repo 2026-07-10 22:00:25 -04:00
royjr be8abfe8f8 Update opendbc_repo 2026-07-10 21:57:26 -04:00
royjr 453e1d9514 Update opendbc_repo 2026-07-08 00:33:38 -04:00
royjr 98a40ed15c Update opendbc_repo 2026-07-07 23:43:44 -04:00
royjr 267db8b817 Update opendbc_repo 2026-07-07 23:40:07 -04:00
royjr ee0ac199a4 Update opendbc_repo 2026-07-03 08:08:29 -04:00
royjr b9ce161fff Update opendbc_repo 2026-07-01 22:00:01 -04:00
royjr d5ed828eaa Update opendbc_repo 2026-07-01 19:30:06 -04:00
royjr 9fca585f2a Merge branch 'master' into ccnc-port 2026-06-30 22:14:33 -04:00
royjr bb1259303e Update opendbc_repo 2026-06-30 22:14:23 -04:00
royjr 8562e39b0b Merge branch 'master' into hyundai-radar-tracks 2026-06-30 21:55:22 -04:00
royjr e135051ca8 Merge branch 'master' into ccnc-port 2026-06-13 22:38:22 -04:00
royjr 12bef55d8a Update opendbc_repo 2026-06-13 22:12:00 -04:00
royjr be4223fe13 visuals-radar-tracks
commit 1e8d692804
Merge: 5d5dc6c50c 097dd9b5f2
Author: royjr <royjr96@gmail.com>
Date:   Tue Jun 9 00:13:12 2026 -0400

    Merge branch 'master' into visuals-radar-tracks

commit 5d5dc6c50c
Author: royjr <royjr96@gmail.com>
Date:   Tue Jun 9 00:12:06 2026 -0400

    yaml

commit 5c64fe0695
Merge: 37eeb121cd 066ba92e77
Author: royjr <royjr96@gmail.com>
Date:   Mon Jun 8 23:19:24 2026 -0400

    Merge branch 'master' into visuals-radar-tracks

commit 37eeb121cd
Merge: 19a5202f4c 1658898498
Author: royjr <royjr96@gmail.com>
Date:   Fri Mar 27 16:27:10 2026 -0700

    Merge branch 'master' into visuals-radar-tracks

commit 19a5202f4c
Merge: 920ef49b32 a17a38d8c3
Author: royjr <royjr96@gmail.com>
Date:   Mon Mar 2 02:44:58 2026 -0500

    Merge branch 'master' into visuals-radar-tracks

commit 920ef49b32
Author: royjr <royjr96@gmail.com>
Date:   Thu Feb 5 01:01:07 2026 -0500

    no return

commit c7a37ca89e
Merge: 45ca076aef 5c12a7cfc3
Author: royjr <royjr96@gmail.com>
Date:   Thu Feb 5 00:52:12 2026 -0500

    Merge branch 'master' into visuals-radar-tracks

commit 45ca076aef
Merge: 70cf5e6d52 19a7d1d5d7
Author: royjr <royjr96@gmail.com>
Date:   Wed Dec 31 15:26:12 2025 -0500

    Merge branch 'master' into visuals-radar-tracks

commit 70cf5e6d52
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 12:03:57 2025 -0500

    Update params_metadata.json

commit f4c71e4918
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 10:05:17 2025 -0500

    lint

commit 8408241495
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 02:23:10 2025 -0500

    Update toggles.py

commit 640988673e
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 02:22:16 2025 -0500

    mici toggle

commit 1429975f1e
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 02:22:01 2025 -0500

    Revert "force enable for now"

    This reverts commit efd499448a.

commit efd499448a
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 00:42:38 2025 -0500

    force enable for now

commit f7e636f089
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 00:42:19 2025 -0500

    use params

commit 00187dc59e
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 00:37:19 2025 -0500

    use checks

commit cde6368cf7
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 00:30:15 2025 -0500

    cleaner

commit 08b56f9b33
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 00:28:12 2025 -0500

    better sp

commit 78efe3a476
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 00:21:08 2025 -0500

    init sp

commit 54578675c2
Author: royjr <royjr96@gmail.com>
Date:   Mon Dec 29 00:19:17 2025 -0500

    init
2026-06-09 00:15:59 -04:00
royjr 2b492098e9 Update opendbc_repo 2026-06-09 00:14:58 -04:00
royjr 00fee3ee88 Merge branch 'master' into hyundai-radar-tracks 2026-06-09 00:14:36 -04:00
royjr 930ab1a84d fixes 2026-06-08 22:37:07 -04:00
royjr 0c77f1b41b auto_radar 2026-06-08 22:26:08 -04:00
royjr 9fd4e5b9f8 Merge branch 'master' into hyundai-radar-tracks 2026-06-08 22:20:43 -04:00
royjr 818dd7c4e3 Update opendbc_repo 2026-06-08 22:20:39 -04:00
royjr 2f7a45e6c8 Merge branch 'master' into ccnc-port 2026-06-08 22:01:57 -04:00
royjr 936ebfc12b Update opendbc_repo 2026-06-08 22:01:44 -04:00
royjr aa0c9dc0eb Merge branch 'master' into ccnc-port 2026-06-04 09:52:35 -04:00
royjr 7476a866e7 Update opendbc_repo 2026-06-04 09:52:06 -04:00
royjr 610d857e33 Merge branch 'master' into ccnc-port 2026-05-28 08:30:40 -04:00
royjr d2f47407d0 Update opendbc_repo 2026-05-12 21:03:18 -04:00
royjr db75ec76ea Merge branch 'master' into ccnc-port 2026-05-12 20:44:57 -04:00
royjr 24066465d7 Update opendbc_repo 2026-05-12 20:44:48 -04:00
royjr a7abbd6e25 Update opendbc_repo 2026-04-20 18:22:12 -04:00
royjr 878982447c Merge branch 'master' into ccnc-port 2026-04-19 12:06:46 -04:00
royjr 576527a36b Merge branch 'master' into ccnc-port 2026-04-17 05:42:35 -04:00
royjr ef8c35da24 Update opendbc_repo 2026-04-17 05:41:21 -04:00
royjr 85688b1040 Merge branch 'master' into ccnc-port 2026-04-16 19:48:39 -04:00
royjr 0fb2199130 Update opendbc_repo 2026-04-16 19:47:57 -04:00
royjr d48d756c1d Update opendbc_repo 2026-04-08 00:56:54 -04:00
royjr 2ed298a0c9 Merge branch 'master' into ccnc-port 2026-04-08 00:51:13 -04:00
royjr d68f038949 Update opendbc_repo 2026-04-08 00:51:11 -04:00
royjr 7231571e57 Merge branch 'master' into ccnc-port 2026-04-03 23:34:00 -04:00
royjr b37f1419d3 Update opendbc_repo 2026-04-03 23:33:19 -04:00
royjr 9fe6d20249 Merge branch 'master' into hyundai-radar-tracks 2026-03-27 16:11:57 -07:00
royjr e31b28d0b3 Update opendbc_repo 2026-03-27 16:11:51 -07:00
royjr cd85a66790 Merge branch 'master' into ccnc-port 2026-03-26 00:53:07 -04:00
royjr 305ea87daf Update opendbc_repo 2026-03-26 00:52:44 -04:00
royjr 4bbfc793e0 Merge branch 'master' into ccnc-port 2026-03-15 15:44:59 -04:00
royjr d5d983676e Update opendbc_repo 2026-03-13 16:41:05 -04:00
royjr de8a96a398 Merge branch 'master' into ccnc-port 2026-03-13 16:41:00 -04:00
royjr 0cbf45f699 Merge branch 'master' into ccnc-port 2026-03-11 23:28:59 -04:00
royjr 06498600bf Update opendbc_repo 2026-03-11 23:28:33 -04:00
royjr 0d68a3a2ab Merge branch 'master' into ccnc-port 2026-03-09 19:58:32 -04:00
royjr 9e85a85059 Update opendbc_repo 2026-03-09 19:58:18 -04:00
royjr 0373c327c0 Update opendbc_repo 2026-03-02 10:10:38 -05:00
royjr efe9e5c200 Update opendbc_repo 2026-03-02 02:15:05 -05:00
royjr 8a249a45dc Update opendbc_repo 2026-03-02 02:06:16 -05:00
royjr bdbefe67f6 Update opendbc_repo 2026-03-02 01:40:31 -05:00
royjr 05649045b7 Update opendbc_repo 2026-03-01 23:02:25 -05:00
royjr 0ee21708ac Merge branch 'master' into hyundai-radar-tracks 2026-03-01 22:39:12 -05:00
royjr 1492941483 Update opendbc_repo 2026-03-01 22:39:07 -05:00
royjr 675bb166ad Merge branch 'master' into ccnc-port 2026-03-01 17:18:11 -05:00
royjr 1b717a7e88 Merge branch 'master' into ccnc-port 2026-03-01 13:23:30 -05:00
royjr 86f55a8ba9 Update opendbc_repo 2026-03-01 13:23:24 -05:00
royjr 8794178ccf Merge branch 'master' into hyundai-radar-tracks 2026-02-28 17:02:32 -05:00
royjr 86303e7a3c Update opendbc_repo 2026-02-28 17:01:42 -05:00
royjr 629392d2f7 Update opendbc_repo 2026-02-27 16:31:59 -05:00
royjr bc414bdc8b Update opendbc_repo 2026-02-26 23:53:26 -05:00
royjr 7ca5649f2c Merge branch 'master' into ccnc-port 2026-02-26 23:52:46 -05:00
royjr 641ee8fa87 Update opendbc_repo 2026-02-26 23:52:27 -05:00
royjr e189678033 Update opendbc_repo 2026-02-25 21:25:16 -05:00
royjr 99bae4f475 Merge branch 'master' into hyundai-radar-tracks 2026-02-25 21:24:58 -05:00
royjr 56c276158c Merge branch 'master' into ccnc-port 2026-02-24 14:12:12 -05:00
royjr c65308a8bd Update opendbc_repo 2026-02-24 14:12:01 -05:00
royjr 994e526460 Merge branch 'master' into ccnc-port 2026-02-18 12:06:05 -05:00
royjr 1defae36b7 Update opendbc_repo 2026-02-18 12:05:53 -05:00
royjr 8f029fd0ef Merge branch 'master' into ccnc-port 2026-02-13 23:01:41 -05:00
royjr ddb46284dc Update opendbc_repo 2026-02-13 23:01:16 -05:00
royjr ef7046c2ed Merge branch 'master' into hyundai-radar-toggle 2026-02-13 22:28:03 -05:00
royjr 734121b517 Update opendbc_repo 2026-02-13 22:27:58 -05:00
royjr 9effc754d9 Merge branch 'master' into ccnc-port 2026-02-06 01:16:06 -05:00
royjr e49ffc2a2d Update opendbc_repo 2026-02-06 01:15:59 -05:00
royjr b738769c22 Update opendbc_repo 2026-02-05 22:59:22 -05:00
royjr 0bfd9e5cf2 Merge branch 'master' into hyundai-radar-toggle 2026-02-05 22:50:16 -05:00
royjr 20fadbcaa8 Update opendbc_repo 2026-02-05 22:50:08 -05:00
royjr 2cacd0b3e5 Merge branch 'master' into ccnc-port 2026-01-24 12:50:18 -05:00
royjr c4b8859dff Update opendbc_repo 2026-01-24 12:50:11 -05:00
royjr 8fb0953205 Merge branch 'master' into ccnc-port 2026-01-11 22:17:38 -05:00
royjr 63d1c8835f Merge branch 'master' into ccnc-port 2026-01-10 13:03:48 -05:00
royjr 17a185606d Merge branch 'master' into ccnc-port 2026-01-09 16:40:32 -05:00
royjr 0eeb052241 hyundai_radar toggle mici 2025-12-29 02:20:27 -05:00
royjr edc2d16ae4 Update params_metadata.json 2025-12-28 17:47:10 -05:00
royjr e72fa9453d Merge branch 'master' into hyundai-radar-toggle 2025-12-28 17:20:20 -05:00
royjr 1c56f0b0a3 Update opendbc_repo 2025-12-28 17:19:47 -05:00
royjr da10131392 Merge branch 'master' into ccnc-port 2025-12-28 17:18:51 -05:00
royjr 7107c2ba14 Merge branch 'master' into ccnc-port 2025-12-23 12:13:48 -05:00
royjr 95b6e877ac Update opendbc_repo 2025-12-23 12:13:29 -05:00
royjr eb02c6570e Update opendbc_repo 2025-12-21 15:43:04 -05:00
royjr 80d15212e2 Merge branch 'master' into hyundai-radar-toggle 2025-12-20 11:47:50 -05:00
royjr ab2863a859 Update opendbc_repo 2025-12-20 11:45:59 -05:00
royjr 1be8ae31c4 Merge branch 'master' into ccnc-port 2025-12-19 01:04:42 -05:00
royjr 04dcd38856 Update opendbc_repo 2025-12-19 01:04:30 -05:00
royjr 22ccf0d72f Merge branch 'master' into ccnc-port 2025-12-15 17:02:50 -05:00
royjr 3c969bb627 Merge branch 'master' into ccnc-port 2025-12-13 23:22:22 -05:00
royjr 20f8011feb Update opendbc_repo 2025-12-13 23:22:11 -05:00
royjr 9cf17e74a1 Merge branch 'master' into ccnc-port 2025-12-12 23:19:56 -05:00
royjr 2c4efdf557 Merge branch 'master' into ccnc-port 2025-12-07 13:29:48 -05:00
royjr 4cd3d3c16c Merge branch 'master' into ccnc-port 2025-12-02 12:56:21 -05:00
royjr 637f3ae9c8 Merge branch 'master' into ccnc-port 2025-12-01 14:41:03 -05:00
royjr 464ee80f71 Merge branch 'master' into ccnc-port 2025-11-26 00:27:53 -05:00
royjr 2743a04613 Merge branch 'master' into ccnc-port 2025-11-24 18:44:13 -05:00
royjr 7f9978d001 Merge branch 'master' into ccnc-port 2025-11-22 00:21:15 -05:00
royjr 4b83961c67 Merge branch 'master' into ccnc-port 2025-11-21 16:23:22 -05:00
royjr c00eaf428a Update opendbc_repo 2025-11-21 16:23:01 -05:00
royjr 0a9993e8d4 Merge branch 'master' into ccnc-port 2025-11-19 16:49:59 -05:00
royjr 0af214a985 Update opendbc_repo 2025-11-19 16:49:51 -05:00
royjr af43385e3a Merge branch 'master' into ccnc-port 2025-11-11 10:19:51 -05:00
royjr 0ab2b8c590 Update opendbc_repo 2025-11-07 19:59:50 -05:00
royjr 67ab18a0de Merge branch 'master' into ccnc-port 2025-11-07 19:23:51 -05:00
royjr e87dc15b30 Update opendbc_repo 2025-11-07 19:23:37 -05:00
royjr 192d08516c Merge branch 'master' into ccnc-port 2025-11-02 19:23:00 -05:00
royjr 3cf001c59c Update opendbc_repo 2025-11-02 19:22:49 -05:00
royjr f2ccd021da Merge branch 'master' into ccnc-port 2025-11-02 14:07:54 -05:00
royjr c9fc900f64 Update opendbc_repo 2025-11-02 14:07:44 -05:00
royjr 3c37c5ce5d Update opendbc_repo 2025-10-30 11:28:49 -04:00
royjr 7c45889e4e Merge branch 'master' into ccnc-port 2025-10-30 11:27:50 -04:00
royjr 2aabb7aee8 Merge branch 'master' into ccnc-port 2025-10-24 14:16:09 -04:00
royjr 3859e9962f Update opendbc_repo 2025-10-24 14:15:51 -04:00
royjr 810efbab72 Merge branch 'master' into ccnc-port 2025-10-18 07:33:11 -04:00
royjr ec27bec326 Update opendbc_repo 2025-10-18 07:32:33 -04:00
royjr 250d553157 Merge branch 'master' into ccnc-port 2025-10-14 21:57:32 -04:00
royjr cea54a0ca8 Update opendbc_repo 2025-10-14 21:57:26 -04:00
royjr 8e72d783bd Update opendbc_repo 2025-10-13 22:41:21 -04:00
royjr 1b0dc103dc Merge branch 'master' into ccnc-port 2025-10-11 23:51:46 -04:00
royjr 6c364d292b Update opendbc_repo 2025-10-11 23:51:31 -04:00
royjr bcdec2ce84 Merge branch 'master' into ccnc-port 2025-10-10 17:29:05 -04:00
royjr 3deaeb3759 Merge branch 'master' into ccnc-port 2025-10-10 15:02:32 -04:00
royjr c669f0984a Update opendbc_repo 2025-10-10 15:02:17 -04:00
royjr 743aa8e736 Merge branch 'master' into hyundai-radar-toggle 2025-10-10 00:09:54 -04:00
royjr 5847256371 Update opendbc_repo 2025-10-10 00:09:37 -04:00
royjr c13f909390 Update opendbc_repo 2025-10-09 01:30:54 -04:00
royjr f1de835d17 Update opendbc_repo 2025-10-09 01:11:07 -04:00
royjr 76ddb20cd5 Merge branch 'master' into hyundai-radar-toggle 2025-10-09 01:10:40 -04:00
royjr ede7f70ddc Merge branch 'master' into hyundai-radar-toggle 2025-10-08 23:04:21 -04:00
royjr 46c3047fa7 Update opendbc_repo 2025-10-08 23:04:16 -04:00
royjr 46dd946740 Merge branch 'master' into ccnc-port 2025-10-07 01:36:06 -04:00
royjr 9da4b3653e Update opendbc_repo 2025-10-07 01:35:58 -04:00
royjr 4e21ae7c50 Update opendbc_repo 2025-10-05 06:21:56 -04:00
royjr bb91e92237 Update opendbc_repo 2025-10-05 06:01:15 -04:00
royjr 14b4c4f85b Update opendbc_repo 2025-10-02 09:20:32 -04:00
royjr 0660b542c3 Merge branch 'master' into ccnc-port 2025-10-01 16:01:32 -04:00
royjr 2b893b90c9 Update opendbc_repo 2025-10-01 16:01:26 -04:00
royjr f5139178ed Merge branch 'master' into ccnc-port 2025-09-30 14:39:57 -04:00
royjr fb43b755f2 Update opendbc_repo 2025-09-30 14:39:50 -04:00
royjr cf5b5c666d Merge branch 'master' into hyundai-radar-toggle 2025-09-30 13:57:57 -04:00
royjr 03b56d6f09 Update opendbc_repo 2025-09-30 13:57:52 -04:00
royjr 07f5b967d8 Merge branch 'master' into ccnc-port 2025-09-24 21:30:29 -04:00
royjr ea19c7d3bb Update opendbc_repo 2025-09-24 21:30:17 -04:00
royjr e461842cbb Merge branch 'master' into ccnc-port 2025-09-23 05:55:02 -04:00
royjr a73c9659d5 Update opendbc_repo 2025-09-23 05:54:50 -04:00
royjr ab180ce1e5 Update opendbc_repo 2025-09-22 23:13:42 -04:00
royjr e0d8bc88b2 Update opendbc_repo 2025-09-22 00:33:12 -04:00
royjr 1dc45adb1c Merge branch 'master' into hyundai-radar-toggle 2025-09-22 00:28:40 -04:00
royjr c3bd613885 Merge branch 'master' into hyundai-radar-toggle 2025-09-20 01:56:28 -04:00
royjr 55edb4efcb Update opendbc_repo 2025-09-20 01:56:23 -04:00
royjr cb796fbc76 Merge branch 'master' into ccnc-port 2025-09-18 20:10:43 -04:00
royjr 6bf75fc557 Update opendbc_repo 2025-09-18 20:10:37 -04:00
royjr 9a1fc28819 Merge branch 'master' into ccnc-port 2025-09-18 13:54:36 -04:00
royjr 0741d05e92 Update opendbc_repo 2025-09-18 13:54:23 -04:00
royjr 1ad008107d Merge branch 'master' into ccnc-port 2025-09-15 01:40:27 -04:00
royjr feebd9df93 Reapply "UI: Developer UI (#1233)"
This reverts commit 15e5d2efb9.
2025-09-15 01:40:21 -04:00
royjr c2e5ced3e5 Update opendbc_repo 2025-09-15 01:39:51 -04:00
royjr 15e5d2efb9 Revert "UI: Developer UI (#1233)"
This reverts commit 1bb4ca2547.
2025-09-12 02:10:29 -04:00
royjr a3929d0b54 Merge branch 'master' into ccnc-port 2025-09-12 01:40:29 -04:00
royjr 794f8f9991 Update opendbc_repo 2025-09-08 09:27:31 -04:00
royjr 68fa5e3f21 Merge branch 'master' into ccnc-port 2025-09-07 13:13:37 -04:00
royjr 86c6cc1f48 Merge branch 'master' into ccnc-port 2025-09-03 22:22:12 -04:00
royjr eb7ffbf093 Update opendbc_repo 2025-09-03 10:14:58 -04:00
royjr 3919095752 Update opendbc_repo 2025-09-03 10:05:35 -04:00
royjr 74d63be1c3 Merge branch 'master' into ccnc-port 2025-09-03 09:50:55 -04:00
royjr 8894486a1a Update opendbc_repo 2025-09-03 09:50:49 -04:00
royjr 810599315d Merge branch 'master' into ccnc-port 2025-08-31 16:53:30 -04:00
royjr 6f3ab810c8 Update opendbc_repo 2025-08-31 16:53:24 -04:00
royjr 230f78b8d3 Merge branch 'master' into ccnc-port 2025-08-26 12:14:46 -04:00
royjr f1affec088 Update opendbc_repo 2025-08-26 12:14:22 -04:00
royjr 7ff9b28c94 Merge branch 'master' into hyundai-radar-toggle 2025-08-24 16:57:46 -04:00
royjr 5f9d340d7b Update opendbc_repo 2025-08-24 16:57:37 -04:00
royjr 97d8ef242c Merge branch 'master' into ccnc-port 2025-08-24 15:12:57 -04:00
royjr a63fff9b45 Update opendbc_repo 2025-08-24 15:12:46 -04:00
royjr cb3893daaa Merge branch 'master' into ccnc-port 2025-08-23 10:33:24 -04:00
royjr e82b47cb70 Update opendbc_repo 2025-08-22 12:03:48 -04:00
royjr 10b5e58558 Merge branch 'master' into hyundai-radar-toggle 2025-08-22 11:47:43 -04:00
royjr 116936341f Update opendbc_repo 2025-08-22 11:41:48 -04:00
royjr 29f60df74b Merge branch 'master' into ccnc-port 2025-08-22 11:18:27 -04:00
royjr c6c072e1f4 Update opendbc_repo 2025-08-22 11:18:18 -04:00
royjr 8d5bd92d51 Update opendbc_repo 2025-08-14 22:13:26 -04:00
royjr 4453dba6ed Update opendbc_repo 2025-08-13 23:03:43 -04:00
royjr cc20313815 fix param 2025-08-13 20:38:33 -04:00
royjr 3b30b93bbb Merge branch 'master' into hyundai-radar-toggle 2025-08-13 18:34:52 -04:00
royjr 5d9de36d40 Update opendbc_repo 2025-08-13 18:31:10 -04:00
royjr d101cbb83e Update opendbc_repo 2025-08-13 16:00:04 -04:00
royjr 1536d59633 Update opendbc_repo 2025-08-13 15:28:37 -04:00
royjr dc99b865ae Merge branch 'master' into ccnc-port 2025-08-13 12:31:14 -04:00
royjr e59bc027ff Merge branch 'master' into ccnc-port 2025-08-13 11:58:05 -04:00
royjr cf7e5efaca Update opendbc_repo 2025-08-13 11:57:57 -04:00
royjr 4b44f2eb31 Merge branch 'master' into ccnc-port 2025-08-10 09:18:24 -04:00
royjr 107d2ab400 Update opendbc_repo 2025-08-10 09:18:15 -04:00
royjr 5432d9062c Merge branch 'master' into ccnc-port 2025-08-04 11:52:29 -04:00
royjr f533f6c843 Merge branch 'master' into ccnc-port 2025-08-02 06:53:54 -04:00
royjr 58e9ac763c Update opendbc_repo 2025-08-02 06:53:43 -04:00
royjr cb50d54169 Merge branch 'master-new' into ccnc-port 2025-07-24 20:23:46 -04:00
royjr bd5de4ed0a Merge branch 'master-new' into ccnc-port 2025-07-20 23:49:36 -04:00
royjr 0d4073fadb Merge branch 'master-new' into ccnc-port 2025-07-19 23:18:26 -04:00
royjr ebc70dcb52 Merge branch 'master-new' into ccnc-port 2025-07-19 14:37:17 -04:00
royjr 4d0426999e Update opendbc_repo 2025-07-19 14:36:59 -04:00
royjr 286da42573 Merge branch 'master-new' into ccnc-port 2025-07-16 23:48:23 -04:00
royjr 8a836710a9 Update opendbc_repo 2025-07-16 23:48:06 -04:00
royjr 5d515bcf33 Merge branch 'master-new' into ccnc-port 2025-07-07 05:35:55 -04:00
royjr 1c7f6d5133 Update opendbc_repo 2025-07-03 21:24:01 -04:00
royjr 05d57c7aeb Update opendbc_repo 2025-07-01 18:30:32 -04:00
royjr a91b97b89a Set RADAR_OFF flag for Hyundai when radar is off
Adds logic to set the RADAR_OFF flag in CP_SP.flags when the Hyundai radar type is set to OFF. This ensures the correct flag is applied for vehicles with radar disabled.
2025-07-01 02:09:53 -04:00
royjr ec4e2ec3c1 Update opendbc_repo 2025-07-01 02:09:48 -04:00
royjr 1f49367380 Merge branch 'master-new' into hyundai-radar-toggle 2025-07-01 01:10:33 -04:00
royjr 040f81fc91 Update opendbc_repo 2025-07-01 01:10:02 -04:00
royjr e4b0eaf352 Update opendbc_repo 2025-06-28 19:30:28 -04:00
royjr a710276472 Merge branch 'master-new' into ccnc-port 2025-06-28 19:22:59 -04:00
royjr af086db671 Merge branch 'master-new' into ccnc-port 2025-06-28 12:13:26 -04:00
royjr 0d9eb0e25e Update opendbc_repo submodule to latest commit
Advanced the opendbc_repo submodule to commit d309f7ec96e37267c94d12fc4bfe2672ad505b06. This pulls in the latest changes from the opendbc repository.
2025-06-26 14:03:56 -04:00
royjr 0616caed6d Merge branch 'master-new' into ccnc-port 2025-06-25 19:33:53 -04:00
royjr 095337b3c1 Update opendbc_repo 2025-06-25 19:33:38 -04:00
royjr 1edec2d22c Update opendbc_repo 2025-06-14 16:14:02 -04:00
royjr affabb9ee0 Update opendbc_repo 2025-06-14 15:55:07 -04:00
royjr dc27e8711c Update opendbc_repo 2025-06-14 14:54:32 -04:00
royjr cf7329a264 Merge branch 'master-new' into ccnc-port 2025-06-11 21:36:06 -04:00
royjr 5ee5ecd820 Merge branch 'master-new' into ccnc-port 2025-06-08 23:25:13 -04:00
royjr b064f730dd Update opendbc_repo 2025-06-08 17:54:43 -04:00
royjr 5253b33b7a buttonClicked 2025-06-03 01:34:18 -04:00
royjr fc46a0ed7f Update opendbc_repo 2025-06-03 01:18:21 -04:00
royjr 8056a797c7 Merge branch 'master-new' into hyundai-radar-toggle 2025-06-03 01:17:26 -04:00
royjr a492625927 init 2025-05-20 01:34:50 -04:00
274 changed files with 9746 additions and 4646 deletions
-8
View File
@@ -93,14 +93,6 @@ jobs:
const { owner, repo } = context.repo;
const upstream = `${owner}/${repo}`;
const closed = context.payload.pull_request;
if (closed) {
if (closed.head.repo?.full_name === upstream) {
await github.rest.git.deleteRef({ owner, repo, ref: `heads/${closed.head.ref}` }).catch(console.log);
}
return;
}
for await (const response of github.paginate.iterator(github.rest.pulls.list, {
owner,
repo,
-5
View File
@@ -166,11 +166,6 @@ env = Environment(
tools=["default", "cython", "compilation_db", "rednose_filter"],
toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"],
)
# SCons' Darwin linker tool doesn't define the variables used to expand RPATH.
if arch == "Darwin":
env["RPATHPREFIX"] = "-Wl,-rpath,"
env["RPATHSUFFIX"] = ""
env["_RPATH"] = "${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}"
if arch != "larch64":
env['_LIBFLAGS'] = _libflags
+1 -1
View File
@@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="18.5"
export AGNOS_VERSION="18.4"
fi
export STAGING_ROOT="/data/safe_staging"
+11
View File
@@ -371,6 +371,17 @@ struct CarControlSP @0xa5cd762cd951a455 {
leadOne @2 :LeadData;
leadTwo @3 :LeadData;
intelligentCruiseButtonManagement @4 :IntelligentCruiseButtonManagement;
radarTracks @5 :List(RadarTrack);
radarTracksActive @6 :Bool;
struct RadarTrack {
trackId @0 :UInt64;
dRel @1 :Float32;
yRel @2 :Float32;
vRel @3 :Float32;
motionState @4 :UInt8;
age @5 :UInt16;
}
struct Param {
key @0 :Text;
+1 -3
View File
@@ -812,7 +812,6 @@ struct SelfdriveState {
promptDistracted @8;
preAlert @9;
complete @10;
}
enum OpenpilotState @0xdbe58b96d2d1ac61 {
@@ -2161,8 +2160,7 @@ struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 {
struct DriverMonitoringState {
lockout @0 :Bool;
lockoutCount @15 :Int8;
lockoutMinutesRemaining @11 :Int8;
lockoutRecoveryPercent @11 :Int8;
alert3Count @12 :Int8;
noResponseCount @13 :Int8;
noResponseForceDecel @14 :Bool;
+24 -55
View File
@@ -7,43 +7,12 @@ import os
import capnp
import time
from typing import Union
from typing import Optional, List, Union, Dict
from openpilot.cereal import log
from openpilot.cereal.services import SERVICE_LIST
from openpilot.common.utils import MovingAverage
__all__ = (
"NO_TRAVERSAL_LIMIT",
"Context",
"FrequencyTracker",
"IpcError",
"MultiplePublishersError",
"Poller",
"PubMaster",
"PubSocket",
"SocketEventHandle",
"SubMaster",
"SubSocket",
"delete_fake_prefix",
"drain_sock",
"drain_sock_raw",
"fake_event_handle",
"get_fake_prefix",
"log_from_bytes",
"new_message",
"pub_sock",
"recv_one",
"recv_one_or_none",
"recv_one_retry",
"recv_sock",
"reset_context",
"set_fake_prefix",
"sub_sock",
"toggle_fake_events",
"wait_for_one_event",
)
NO_TRAVERSAL_LIMIT = 2**64-1
@@ -53,8 +22,8 @@ def pub_sock(endpoint: str) -> PubSocket:
return msgq.pub_sock(endpoint, segment_size)
def sub_sock(endpoint: str, poller: Poller | None = None, addr: str = "127.0.0.1",
conflate: bool = False, timeout: int | None = None) -> SubSocket:
def sub_sock(endpoint: str, poller: Optional[Poller] = None, addr: str = "127.0.0.1",
conflate: bool = False, timeout: Optional[int] = None) -> SubSocket:
service = SERVICE_LIST.get(endpoint)
segment_size = service.queue_size if service else 0
return msgq.sub_sock(endpoint, poller=poller, addr=addr, conflate=conflate,
@@ -70,7 +39,7 @@ def log_from_bytes(dat: bytes, struct: capnp.lib.capnp._StructModule = log.Event
return msg
def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
def new_message(service: Optional[str], size: Optional[int] = None, **kwargs) -> capnp.lib.capnp._DynamicStructBuilder:
args = {
'valid': False,
'logMonoTime': int(time.monotonic() * 1e9),
@@ -85,14 +54,14 @@ def new_message(service: str | None, size: int | None = None, **kwargs) -> capnp
return dat
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> list[capnp.lib.capnp._DynamicStructReader]:
def drain_sock(sock: SubSocket, wait_for_one: bool = False) -> List[capnp.lib.capnp._DynamicStructReader]:
"""Receive all message currently available on the queue"""
msgs = drain_sock_raw(sock, wait_for_one=wait_for_one)
return [log_from_bytes(m) for m in msgs]
# TODO: print when we drop packets?
def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicStructReader | None:
def recv_sock(sock: SubSocket, wait: bool = False) -> Optional[capnp.lib.capnp._DynamicStructReader]:
"""Same as drain sock, but only returns latest message. Consider using conflate instead."""
dat = None
@@ -113,14 +82,14 @@ def recv_sock(sock: SubSocket, wait: bool = False) -> capnp.lib.capnp._DynamicSt
return dat
def recv_one(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
def recv_one(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
dat = sock.receive()
if dat is not None:
dat = log_from_bytes(dat)
return dat
def recv_one_or_none(sock: SubSocket) -> capnp.lib.capnp._DynamicStructReader | None:
def recv_one_or_none(sock: SubSocket) -> Optional[capnp.lib.capnp._DynamicStructReader]:
dat = sock.receive(non_blocking=True)
if dat is not None:
dat = log_from_bytes(dat)
@@ -179,27 +148,27 @@ class FrequencyTracker:
class SubMaster:
def __init__(self, services: list[str], poll: str | None = None,
ignore_alive: list[str] | None = None, ignore_avg_freq: list[str] | None = None,
ignore_valid: list[str] | None = None, addr: str = "127.0.0.1", frequency: float | None = None):
def __init__(self, services: List[str], poll: Optional[str] = None,
ignore_alive: Optional[List[str]] = None, ignore_avg_freq: Optional[List[str]] = None,
ignore_valid: Optional[List[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None):
self.frame = -1
self.services = services
self.seen = dict.fromkeys(services, False)
self.updated = dict.fromkeys(services, False)
self.recv_time = dict.fromkeys(services, 0.0)
self.recv_frame = dict.fromkeys(services, 0)
self.seen = {s: False for s in services}
self.updated = {s: False for s in services}
self.recv_time = {s: 0. for s in services}
self.recv_frame = {s: 0 for s in services}
self.sock = {}
self.data = {}
self.logMonoTime = dict.fromkeys(services, 0)
self.logMonoTime = {s: 0 for s in services}
# zero-frequency / on-demand services are always alive and presumed valid; all others must pass checks
on_demand = {s: SERVICE_LIST[s].frequency <= 1e-5 for s in services}
self.static_freq_services = {s for s in services if not on_demand[s]}
self.static_freq_services = set(s for s in services if not on_demand[s])
self.alive = {s: on_demand[s] for s in services}
self.freq_ok = {s: on_demand[s] for s in services}
self.valid = {s: on_demand[s] for s in services}
self.freq_tracker: dict[str, FrequencyTracker] = {}
self.freq_tracker: Dict[str, FrequencyTracker] = {}
self.poller = Poller()
polled_services = set([poll, ] if poll is not None else services)
self.non_polled_services = set(services) - polled_services
@@ -242,7 +211,7 @@ class SubMaster:
msgs.append(recv_one_or_none(self.sock[s]))
self.update_msgs(time.monotonic(), msgs)
def update_msgs(self, cur_time: float, msgs: list[capnp.lib.capnp._DynamicStructReader]) -> None:
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
self.frame += 1
self.updated = dict.fromkeys(self.services, False)
for msg in msgs:
@@ -265,21 +234,21 @@ class SubMaster:
self.alive[s] = (cur_time - self.recv_time[s]) < (10. / SERVICE_LIST[s].frequency) or (self.seen[s] and self.simulation)
self.freq_ok[s] = self.freq_tracker[s].valid or self.simulation
def all_alive(self, service_list: list[str] | None = None) -> bool:
def all_alive(self, service_list: Optional[List[str]] = None) -> bool:
return all(self.alive[s] for s in (service_list or self.services) if s not in self.ignore_alive)
def all_freq_ok(self, service_list: list[str] | None = None) -> bool:
def all_freq_ok(self, service_list: Optional[List[str]] = None) -> bool:
return all(self.freq_ok[s] for s in (service_list or self.services) if self._check_avg_freq(s))
def all_valid(self, service_list: list[str] | None = None) -> bool:
def all_valid(self, service_list: Optional[List[str]] = None) -> bool:
return all(self.valid[s] for s in (service_list or self.services) if s not in self.ignore_valid)
def all_checks(self, service_list: list[str] | None = None) -> bool:
def all_checks(self, service_list: Optional[List[str]] = None) -> bool:
return self.all_alive(service_list) and self.all_freq_ok(service_list) and self.all_valid(service_list)
class PubMaster:
def __init__(self, services: list[str]):
def __init__(self, services: List[str]):
self.sock = {}
for s in services:
self.sock[s] = pub_sock(s)
@@ -1,3 +1,4 @@
import os
import capnp
import multiprocessing
import numbers
@@ -5,6 +6,7 @@ import random
import threading
import time
from openpilot.common.parameterized import parameterized
import pytest
from openpilot.cereal import log
from opendbc.car.structs import car
@@ -22,6 +24,10 @@ def random_socks(num_socks=10):
def random_bytes(length=1000):
return bytes([random.randrange(0xFF) for _ in range(length)])
def zmq_sleep(t=1):
if "ZMQ" in os.environ:
time.sleep(t)
# TODO: this should take any capnp struct and returrn a msg with random populated data
def random_carstate():
@@ -47,6 +53,16 @@ def delayed_send(delay, sock, dat):
class TestMessaging:
def setUp(self):
# TODO: ZMQ tests are too slow; all sleeps will need to be
# replaced with logic to block on the necessary condition
if "ZMQ" in os.environ:
pytest.skip()
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep()
@parameterized.expand(events)
def test_new_message(self, evt):
try:
@@ -73,6 +89,7 @@ class TestMessaging:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=1000)
zmq_sleep()
# no wait and no msgs in queue
msgs = func(sub_sock)
@@ -93,6 +110,7 @@ class TestMessaging:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=100)
zmq_sleep()
# no wait and no msg in queue, socket should timeout
recvd = messaging.recv_sock(sub_sock)
@@ -111,6 +129,7 @@ class TestMessaging:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=1000)
zmq_sleep()
# no msg in queue, socket should timeout
recvd = messaging.recv_one(sub_sock)
@@ -123,10 +142,12 @@ class TestMessaging:
assert isinstance(recvd, capnp._DynamicStructReader)
assert_carstate(msg.carState, recvd.carState)
@pytest.mark.xfail(condition="ZMQ" in os.environ, reason='ZMQ detected')
def test_recv_one_or_none(self):
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock)
zmq_sleep()
# no msg in queue, socket shouldn't block
recvd = messaging.recv_one_or_none(sub_sock)
@@ -144,13 +165,16 @@ class TestMessaging:
sock_timeout = 0.1
pub_sock = messaging.pub_sock(sock)
sub_sock = messaging.sub_sock(sock, timeout=round(sock_timeout*1000))
zmq_sleep()
# wait 5 socket timeouts and make sure it's still retrying
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
p.start()
time.sleep(sock_timeout*5)
assert p.is_alive()
p.terminate()
# this test doesn't work with ZMQ since multiprocessing interrupts it
if "ZMQ" not in os.environ:
# wait 5 socket timeouts and make sure it's still retrying
p = multiprocessing.Process(target=messaging.recv_one_retry, args=(sub_sock,))
p.start()
time.sleep(sock_timeout*5)
assert p.is_alive()
p.terminate()
# wait 5 socket timeouts before sending
msg = random_carstate()
@@ -1,16 +1,21 @@
import random
import time
from typing import cast
from collections.abc import Sized
from typing import Sized, cast
import openpilot.cereal.messaging as messaging
from openpilot.cereal.messaging.tests.test_messaging import events, random_sock, random_socks, \
random_bytes, random_carstate, assert_carstate
random_bytes, random_carstate, assert_carstate, \
zmq_sleep
from openpilot.cereal.services import SERVICE_LIST
class TestSubMaster:
def setup_method(self):
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
def test_init(self):
sm = messaging.SubMaster(events)
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
@@ -37,6 +42,7 @@ class TestSubMaster:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sm = messaging.SubMaster([sock,])
zmq_sleep()
msg = random_carstate()
pub_sock.send(msg.to_bytes())
@@ -48,6 +54,7 @@ class TestSubMaster:
sock = "carState"
pub_sock = messaging.pub_sock(sock)
sm = messaging.SubMaster([sock,])
zmq_sleep()
for i in range(10):
msg = messaging.new_message(sock)
@@ -84,13 +91,21 @@ class TestSubMaster:
for service, (max_freq, min_freq) in checks.items():
if max_freq is not None:
assert min_freq is not None
assert sm._check_avg_freq(service)
assert sm.freq_tracker[service].max_freq == max_freq*1.2
assert sm.freq_tracker[service].min_freq == min_freq*0.8
else:
assert not sm._check_avg_freq(service)
def test_alive(self):
pass
def test_ignore_alive(self):
pass
def test_valid(self):
pass
# SubMaster should always conflate
def test_conflate(self):
sock = "carState"
@@ -109,6 +124,11 @@ class TestSubMaster:
class TestPubMaster:
def setup_method(self):
# ZMQ pub socket takes too long to die
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
def test_init(self):
messaging.PubMaster(events)
@@ -116,6 +136,7 @@ class TestPubMaster:
socks = random_socks()
pm = messaging.PubMaster(socks)
sub_socks = {s: messaging.sub_sock(s, conflate=True, timeout=1000) for s in socks}
zmq_sleep()
# PubMaster accepts either a capnp msg builder or bytes
for capnp in [True, False]:
+4 -2
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
from enum import IntEnum
from typing import Optional
# TODO: this should be automatically determined using the capnp schema
@@ -10,7 +11,7 @@ class QueueSize(IntEnum):
class Service:
def __init__(self, should_log: bool, frequency: float, decimation: int | None = None,
def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] = None,
queue_size: QueueSize = QueueSize.SMALL):
self.should_log = should_log
self.frequency = frequency
@@ -124,7 +125,8 @@ def build_header():
for k, v in SERVICE_LIST.items():
should_log = "true" if v.should_log else "false"
decimation = -1 if v.decimation is None else v.decimation
h += f' {{ "{k}", {{"{k}", {should_log}, {v.frequency:f}, {decimation:d}, {v.queue_size:d}}}}},\n'
h += ' { "%s", {"%s", %s, %f, %d, %d}},\n' % \
(k, k, should_log, v.frequency, decimation, v.queue_size)
h += "};\n"
h += "#endif\n"
-1
View File
@@ -38,7 +38,6 @@ class BaseApi:
}
if payload_extra is not None:
payload.update(payload_extra)
assert self.private_key is not None
token = jwt.encode(payload, self.private_key, algorithm=self.jwt_algorithm)
if isinstance(token, bytes):
token = token.decode('utf8')
+8 -9
View File
@@ -42,26 +42,25 @@ def get_existing_chunks(path):
class ChunkStream(io.RawIOBase):
def __init__(self, paths):
self._paths = iter(paths)
self._f = None
self._buf = memoryview(b'')
def readable(self):
return True
def readinto(self, b):
n = 0
view = memoryview(b)
while n < len(b):
if self._f is None:
if not self._buf:
p = next(self._paths, None)
if p is None:
break
self._f = open(p, 'rb')
count = self._f.readinto(view[n:])
if not count:
self._f.close()
self._f = None
with open(p, 'rb') as f:
self._buf = memoryview(f.read())
continue
n += count
take = min(len(b) - n, len(self._buf))
b[n:n + take] = self._buf[:take]
self._buf = self._buf[take:]
n += take
return n
def open_file_chunked(path):
+10 -10
View File
@@ -56,28 +56,28 @@
},
{
"name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39.img.xz",
"hash": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
"hash_raw": "19ff57b68e219e4503fcaca716967098d5d0a1de8af833f04dbf13b99aeb4d39",
"url": "https://commadist.azureedge.net/agnosupdate/boot-8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8.img.xz",
"hash": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
"hash_raw": "8806802b195a5b1396a3ae8dd92a8b7711dc522f6aceafd820e871bae5c8a6d8",
"size": 17487872,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "ddfe93cc6a8531af92ee331d9bbaeae2f1d933bdb38e579769dc9fe7998eb626"
"ondevice_hash": "edca8bee1531e66953d107eeceeed2dc7b3ca46417e49d55508f94e58bf95db8"
},
{
"name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img.xz",
"hash": "4dc41c2c072f5f5d5cd484cd6173049cd96acfb9a67bc20049775585fe881539",
"hash_raw": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img.xz",
"hash": "78acfe16a7b62a3a91fc7a81f40a693e4468cec1c69df7d0b1e550aacc646113",
"hash_raw": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
"size": 4718592000,
"sparse": true,
"full_check": false,
"has_ab": true,
"ondevice_hash": "cf1229630b7a2b8497705bca4ba947dbf0c217418ff4febff571aa4f4a878134",
"ondevice_hash": "743142c5a898f27b2a1029cca42c8a5d5d1fc0096414422b850fe84c8d0b8342",
"alt": {
"hash": "a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724",
"url": "https://commadist.azureedge.net/agnosupdate/system-a396dd98ffd49614fb198d1b022a0c7a6d0a1e563c20ce11b0a975105ab50724.img",
"hash": "ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f",
"url": "https://commadist.azureedge.net/agnosupdate/system-ef0d879302cb29e72110e9c8d3f947c830fd7d37c8192744fc9dbea1af78501f.img",
"size": 4718592000
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ class StreamingDecompressor:
def __init__(self, url: str) -> None:
self.buf = b""
self.req = requests.get(url, stream=True, headers={'Accept-Encoding': 'identity'}, timeout=60)
self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}, timeout=60)
self.it = self.req.iter_content(chunk_size=1024 * 1024)
self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO)
self.eof = False
+8 -8
View File
@@ -339,7 +339,7 @@ class Tici(HardwareBase):
# Ensure fan gpio is enabled so fan runs until shutdown, also turned on at boot by the ABL
gpio_init(GPIO.SOM_ST_IO, True)
gpio_set(GPIO.SOM_ST_IO, True)
gpio_set(GPIO.SOM_ST_IO, 1)
# *** IRQ config ***
@@ -389,21 +389,21 @@ class Tici(HardwareBase):
gpio_init(GPIO.STM_RST_N, True)
gpio_init(GPIO.STM_BOOT0, True)
gpio_set(GPIO.STM_RST_N, True)
gpio_set(GPIO.STM_BOOT0, False)
gpio_set(GPIO.STM_RST_N, 1)
gpio_set(GPIO.STM_BOOT0, 0)
time.sleep(0.01)
gpio_set(GPIO.STM_RST_N, False)
gpio_set(GPIO.STM_RST_N, 0)
def recover_internal_panda(self):
gpio_init(GPIO.STM_RST_N, True)
gpio_init(GPIO.STM_BOOT0, True)
gpio_set(GPIO.STM_RST_N, True)
gpio_set(GPIO.STM_BOOT0, True)
gpio_set(GPIO.STM_RST_N, 1)
gpio_set(GPIO.STM_BOOT0, 1)
time.sleep(0.01)
gpio_set(GPIO.STM_RST_N, False)
gpio_set(GPIO.STM_RST_N, 0)
time.sleep(0.01)
gpio_set(GPIO.STM_BOOT0, False)
gpio_set(GPIO.STM_BOOT0, 0)
def booted(self):
# this normally boots within 8s, but on rare occasions takes 30+s
+1 -1
View File
@@ -52,7 +52,7 @@ PPPD_CMD = [
"novj", "novjccomp", "ipcp-accept-local", "ipcp-accept-remote", "nomagic",
"user", '""', "password", '""',
]
INITIAL_STATE: dict[str, object] = {
INITIAL_STATE = {
"seconds_since_boot": 0,
"state": "INITIALIZING",
"connected": False, "ip_address": "",
+1 -1
View File
@@ -37,7 +37,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DoShutdown", {CLEAR_ON_MANAGER_START, BOOL}},
{"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}},
{"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}},
{"DriverLockoutCount", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, INT, "0"}},
{"AlphaLongitudinalEnabled", {PERSISTENT | DEVELOPMENT_ONLY | BACKUP, BOOL}},
{"ExperimentalMode", {PERSISTENT | BACKUP, BOOL}},
{"ExperimentalModeConfirmed", {PERSISTENT | BACKUP, BOOL}},
@@ -178,6 +177,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RadarTracks", {PERSISTENT | BACKUP, INT, "0"}},
{"RainbowMode", {PERSISTENT | BACKUP, BOOL, "0"}},
{"RocketFuel", {PERSISTENT | BACKUP, BOOL, "0"}},
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
+5 -7
View File
@@ -1,13 +1,11 @@
import numpy as np
from collections.abc import Sequence
Gain = int | float | tuple[Sequence[float], Sequence[float]] | list[list[float]]
from numbers import Number
class PIDController:
def __init__(self, k_p: Gain, k_i: Gain, k_d: Gain = 0., pos_limit=1e308, neg_limit=-1e308, rate=100):
self._k_p = ([0], [k_p]) if isinstance(k_p, (int, float)) else k_p
self._k_i = ([0], [k_i]) if isinstance(k_i, (int, float)) else k_i
self._k_d = ([0], [k_d]) if isinstance(k_d, (int, float)) else k_d
def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100):
self._k_p: list[list[float]] = [[0], [k_p]] if isinstance(k_p, Number) else k_p
self._k_i: list[list[float]] = [[0], [k_i]] if isinstance(k_i, Number) else k_i
self._k_d: list[list[float]] = [[0], [k_d]] if isinstance(k_d, Number) else k_d
self.set_limits(pos_limit, neg_limit)
+2 -2
View File
@@ -27,7 +27,7 @@ class SwaglogRotatingFileHandler(BaseRotatingHandler):
self.log_files = self.get_existing_logfiles()
log_indexes = [f.split(".")[-1] for f in self.log_files]
self.last_file_idx = max([int(i) for i in log_indexes if i.isdigit()] or [-1])
self.last_rollover = 0.0
self.last_rollover = None
self.doRollover()
def _open(self):
@@ -39,7 +39,7 @@ class SwaglogRotatingFileHandler(BaseRotatingHandler):
return stream
def get_existing_logfiles(self):
log_files = []
log_files = list()
base_dir = os.path.dirname(self.base_filename)
for fn in os.listdir(base_dir):
fp = os.path.join(base_dir, fn)
+3 -3
View File
@@ -112,14 +112,14 @@ class TestParams:
def test_params_default_value(self):
self.params.remove("LanguageSetting")
self.params.remove("LongitudinalPersonality")
self.params.remove("LiveParametersV2")
self.params.remove("LiveParameters")
assert self.params.get("LanguageSetting") is None
assert self.params.get("LanguageSetting", return_default=False) is None
assert isinstance(self.params.get("LanguageSetting", return_default=True), str)
assert isinstance(self.params.get("LongitudinalPersonality", return_default=True), int)
assert self.params.get("LiveParametersV2") is None
assert self.params.get("LiveParametersV2", return_default=True) is None
assert self.params.get("LiveParameters") is None
assert self.params.get("LiveParameters", return_default=True) is None
def test_params_get_type(self):
# json
+2 -1
View File
@@ -52,7 +52,7 @@ _ar_ox_config = DeviceCameraConfig(CameraConfig(1928, 1208, 2648.0), _ar_ox_fish
_os_config = DeviceCameraConfig(CameraConfig(2688 // 2, 1520 // 2, 1522.0 * 3 / 4), _os_fisheye, _os_fisheye)
_neo_config = DeviceCameraConfig(CameraConfig(1164, 874, 910.0), CameraConfig(816, 612, 650.0), _NoneCameraConfig())
DEVICE_CAMERAS: dict[tuple[str, str], DeviceCameraConfig] = {
DEVICE_CAMERAS = {
# A "device camera" is defined by a device type and sensor
# sensor type was never set on eon/neo/two
@@ -176,3 +176,4 @@ def img_from_device(pt_device):
pt_img = pt_view/pt_view[:, 2:3]
return pt_img.reshape(input_shape)[:, :2]
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3027da9834adf9c71177bc3086f8de3018ce22e4725b694d635723c5b3c860cd
size 97120
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ca29ff46fbd6d00cc02596de8c9abfbf1cacdc6a7b2f98b27a74d24c790da004
size 52374
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6f47633b5082b911e79dd13fc2d1f4c2a9d44d2fe0860f3d390ffc4e3d772f7d
size 97120
oid sha256:42bd04a57b527c787a0555503e02a203f7d672c12d448769a3f41f17befbf013
size 48044
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:538271820d618046009bb456e930598f2736dd1a6174c11925ec3912581b05c1
size 52374
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:634fad6590295ef2e73d5f7e5aecbb5dd245a0f7692f1e300c2ebac95aabb8e3
size 73026
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a380fe0a022856b302841a0dd19b71eab318c6b08ec851f128942758a7f1631a
size 97120
oid sha256:b1e177499d9439367179cc57a6301b6162393972e3a136cc35c5fdac026bf10a
size 48044
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cc9e67cfaba77e8e4f4049f0add9238660e4cf8f886c6915600d8f47a30c98db
size 97120
oid sha256:2c0af7f5fe57bb36ab96fae868e20feca763541c97a61b3a3a84a0e7fcb81163
size 83350
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad19268e4aaaeac8dd21f6b26c16a121e7b3f50bba867748e7226727643ae682
size 144642
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:412ef25d2fb103c1ebd55c667313a5921493305fb4e1f4e1dafc08d3b95d86ab
size 73026
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:081d64ca28a84a59ff5e6aea4732865b8687585938b8a287bc7e6a49d0646508
size 97120
oid sha256:4af81cbf1d96a42cc351878b015298aee82874b46baaf1a615ca91ec36c0ced6
size 83228
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1bb440f424c989c06e203bfba0b32d39aede8f6c78a9b335e5e778460202b601
size 73026
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a390831afca3bfc6ea3c2739b872ebf866e70df8ae30653f8587e5cd3993959
size 68306
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:67e636d072703e6b1233a12344c0a6304fd43d64dbb31c66b71c2c8870a339c1
size 153764
+17
View File
@@ -182,6 +182,8 @@ class Car:
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode")
self.radar_tracks = int(self.params.get("RadarTracks", return_default=True))
self._applied_radar_tracks = getattr(self.RI, "radar_mode", self.radar_tracks)
# card is driven by can recv, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None)
@@ -199,6 +201,8 @@ class Car:
CS, CS_SP = self.CI.update(can_list)
CS_SP = convert_to_capnp(CS_SP)
self._update_radar_tracks()
# Update radar tracks from CAN
RD: structs.RadarDataT | None = self.RI.update(can_list)
@@ -225,6 +229,18 @@ class Car:
return CS, CS_SP, RD
def _update_radar_tracks(self) -> None:
if self.CP.brand != "hyundai" or self.radar_tracks == self._applied_radar_tracks:
return
if self.RI.set_radar_mode(self.radar_tracks):
self.CP_SP_capnp = convert_to_capnp(self.CP_SP)
cp_sp_bytes = self.CP_SP_capnp.to_bytes()
self.params.put("CarParamsSP", cp_sp_bytes)
self.params.put("CarParamsSPCache", cp_sp_bytes)
self.params.put("CarParamsSPPersistent", cp_sp_bytes)
self._applied_radar_tracks = self.radar_tracks
def state_publish(self, CS: car.CarState, CS_SP: custom.CarStateSP, RD: structs.RadarDataT | None):
"""carState and carParams publish loop"""
@@ -303,6 +319,7 @@ class Car:
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
self.radar_tracks = int(self.params.get("RadarTracks", return_default=True))
# sunnypilot
self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl")
+55 -13
View File
@@ -49,19 +49,61 @@ def convert_to_capnp(struct: structs.CarParamsSP | structs.CarStateSP) -> capnp.
def convert_carControlSP(struct: capnp.lib.capnp._DynamicStructReader) -> structs.CarControlSP:
# TODO: recursively handle any car struct as needed
def remove_deprecated(s: dict) -> dict:
return {k: v for k, v in s.items() if not k.endswith('DEPRECATED')}
def convert_lead(src) -> structs.LeadData:
lead_is_default = not any((
src.dRel, src.yRel, src.vRel, src.aRel, src.vLead, src.aLeadDEPRECATED, src.dPath, src.vLat,
src.vLeadK, src.aLeadK, src.fcw, src.status, src.aLeadTau, src.modelProb, src.radar,
src.radarTrackId != -1,
))
if lead_is_default:
return structs.LeadData()
struct_dict = struct.to_dict()
struct_dataclass = structs.CarControlSP(**remove_deprecated({k: v for k, v in struct_dict.items() if not isinstance(k, dict)}))
return structs.LeadData(
dRel=src.dRel,
yRel=src.yRel,
vRel=src.vRel,
aRel=src.aRel,
vLead=src.vLead,
dPath=src.dPath,
vLat=src.vLat,
vLeadK=src.vLeadK,
aLeadK=src.aLeadK,
fcw=src.fcw,
status=src.status,
aLeadTau=src.aLeadTau,
modelProb=src.modelProb,
radar=src.radar,
radarTrackId=src.radarTrackId,
)
struct_dataclass.mads = structs.ModularAssistiveDrivingSystem(**remove_deprecated(struct_dict.get('mads', {})))
# struct_dataclass.params = [structs.CarControlSP.Param(**remove_deprecated(p)) for p in struct_dict.get('params', [])]
struct_dataclass.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
struct_dataclass.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
struct_dataclass.intelligentCruiseButtonManagement = structs.IntelligentCruiseButtonManagement(
**remove_deprecated(struct_dict.get('intelligentCruiseButtonManagement', {}))
return structs.CarControlSP(
mads=structs.ModularAssistiveDrivingSystem(
state=str(struct.mads.state),
enabled=struct.mads.enabled,
active=struct.mads.active,
available=struct.mads.available,
),
params=[
{"key": param.key, "type": str(param.type), "value": bytes(param.value)}
for param in struct.params
],
leadOne=convert_lead(struct.leadOne),
leadTwo=convert_lead(struct.leadTwo),
intelligentCruiseButtonManagement=structs.IntelligentCruiseButtonManagement(
state=str(struct.intelligentCruiseButtonManagement.state),
sendButton=str(struct.intelligentCruiseButtonManagement.sendButton),
vTarget=struct.intelligentCruiseButtonManagement.vTarget,
),
radarTracks=[
structs.CarControlSP.RadarTrack(
trackId=track.trackId,
dRel=track.dRel,
yRel=track.yRel,
vRel=track.vRel,
motionState=track.motionState,
age=track.age,
)
for track in struct.radarTracks
],
radarTracksActive=struct.radarTracksActive,
)
return struct_dataclass
@@ -5,7 +5,7 @@ from openpilot.common.parameterized import parameterized
from openpilot.cereal import custom
from opendbc.car.structs import car
from opendbc.car import DT_CTRL
from opendbc.car import DT_CTRL, structs
from opendbc.car.structs import CarParams
from opendbc.car.tests.test_car_interfaces import get_fuzzy_car_interface
from opendbc.car.mock.values import CAR as MOCK
@@ -22,6 +22,45 @@ from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfac
MAX_EXAMPLES = int(os.environ.get('MAX_EXAMPLES', '60'))
def legacy_convert_car_control_sp(struct):
def remove_deprecated(values):
return {k: v for k, v in values.items() if not k.endswith('DEPRECATED')}
struct_dict = struct.to_dict()
result = structs.CarControlSP(**remove_deprecated(struct_dict))
result.mads = structs.ModularAssistiveDrivingSystem(**remove_deprecated(struct_dict.get('mads', {})))
result.leadOne = structs.LeadData(**remove_deprecated(struct_dict.get('leadOne', {})))
result.leadTwo = structs.LeadData(**remove_deprecated(struct_dict.get('leadTwo', {})))
result.radarTracks = [structs.CarControlSP.RadarTrack(**track) for track in struct_dict.get('radarTracks', [])]
result.intelligentCruiseButtonManagement = structs.IntelligentCruiseButtonManagement(
**remove_deprecated(struct_dict.get('intelligentCruiseButtonManagement', {}))
)
return result
def test_convert_car_control_sp_matches_legacy_conversion():
msg = custom.CarControlSP.new_message(
mads={"state": "enabled", "enabled": True, "active": True, "available": True},
params=[{"key": "test", "type": "int", "value": b"42"}],
leadOne={
"dRel": 12.5, "yRel": -0.25, "vRel": -1.5, "aRel": 0.1, "vLead": 20.0,
"dPath": 0.5, "vLat": 0.2, "vLeadK": 20.1, "aLeadK": 0.05, "fcw": True,
"status": True, "aLeadTau": 1.5, "modelProb": 0.9, "radar": True, "radarTrackId": 7,
},
leadTwo={"dRel": 25.0, "status": True, "radarTrackId": 9},
intelligentCruiseButtonManagement={"state": "holding", "sendButton": "increase", "vTarget": 27.0},
radarTracks=[
{"trackId": 7, "dRel": 12.5, "yRel": -0.25, "vRel": -1.5, "motionState": 2, "age": 4},
{"trackId": 9, "dRel": 25.0, "yRel": 3.0, "vRel": 0.5, "motionState": 1, "age": 8},
],
radarTracksActive=True,
).as_reader()
assert convert_carControlSP(msg) == legacy_convert_car_control_sp(msg)
default_msg = custom.CarControlSP.new_message().as_reader()
assert convert_carControlSP(default_msg) == legacy_convert_car_control_sp(default_msg)
class TestCarInterfaces:
# FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause
# many generated examples to overrun when max_examples > ~20, don't use it
+4 -5
View File
@@ -3,7 +3,7 @@ import copy
import os
import pytest
import random
import unittest
import unittest # noqa: TID251
from collections import defaultdict, Counter
import hypothesis.strategies as st
from hypothesis import Phase, given, settings
@@ -22,7 +22,6 @@ from openpilot.selfdrive.pandad import can_capnp_to_list
from openpilot.selfdrive.test.helpers import read_segment_list
from openpilot.common.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT
from openpilot.tools.lib.logreader import LogReader, LogsUnavailable, openpilotci_source, internal_source, comma_api_source
from openpilot.tools.lib.file_sources import Source
from openpilot.tools.lib.route import SegmentName
SafetyModel = car.CarParams.SafetyModel
@@ -132,7 +131,7 @@ class TestCarModelBase(unittest.TestCase):
segment_range = f"{cls.test_route.route}/{seg}"
try:
sources: list[Source] = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source]
sources = [internal_source] if len(INTERNAL_SEG_LIST) else [openpilotci_source, comma_api_source]
lr = LogReader(segment_range, sources=sources, sort_by_time=True)
return cls.get_testing_data_from_logreader(lr)
except (LogsUnavailable, AssertionError):
@@ -258,7 +257,7 @@ class TestCarModelBase(unittest.TestCase):
# Don't check relay malfunction on disabled routes (relay closed),
# or before fingerprinting is done (elm327 and noOutput)
if self.car_safety_mode_frame is not None and t / 1e4 > self.car_safety_mode_frame:
if self.openpilot_enabled and t / 1e4 > self.car_safety_mode_frame:
self.assertFalse(self.safety.get_relay_malfunction())
else:
self.safety.set_relay_malfunction(False)
@@ -452,7 +451,7 @@ class TestCarModelBase(unittest.TestCase):
# TODO: remove this exception once this mismatch is resolved
brake_pressed = CS.brakePressed
if CS.brakePressed and not self.safety.get_brake_pressed_prev():
if self.CP.carFingerprint in (HONDA.HONDA_PILOT, HONDA.HONDA_RIDGELINE) and CS.deprecated.brake > 0.05:
if self.CP.carFingerprint in (HONDA.HONDA_PILOT, HONDA.HONDA_RIDGELINE) and CS.brakeDEPRECATED > 0.05:
brake_pressed = False
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
@@ -15,9 +15,6 @@ MAX_LATERAL_JERK = 5.0 # m/s^3
MAX_LATERAL_ACCEL_NO_ROLL = 3.0 # m/s^2
def should_stop(v_ego: float, a_target: float) -> bool:
return bool(v_ego < 0.25 and a_target < 0.1)
def clamp(val, min_val, max_val):
clamped_val = float(np.clip(val, min_val, max_val))
return clamped_val, clamped_val != val
@@ -43,7 +40,7 @@ def clip_curvature(v_ego, prev_curvature, new_curvature, roll) -> tuple[float, b
return float(new_curvature), limited_accel or limited_max_curv
def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL):
def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.3):
if len(speeds) == len(t_idxs):
v_now = speeds[0]
a_now = accels[0]
@@ -56,7 +53,8 @@ def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL):
v_now = 0.0
v_target = 0.0
a_target = 0.0
return a_target, should_stop(v_now, a_target)
should_stop = (v_now < vEgoStopping and a_target < 0.1)
return a_target, should_stop
def curv_from_psis(psi_target, psi_rate, vego, action_t):
vego = np.clip(vego, MIN_SPEED, np.inf)
@@ -10,7 +10,7 @@ CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
LongCtrlState = car.CarControl.Actuators.LongControlState
def long_control_state_trans(CP_SP, active, long_control_state,
def long_control_state_trans(CP, CP_SP, active, long_control_state, v_ego,
should_stop, brake_pressed, cruise_standstill):
# Gas Interceptor
cruise_standstill = cruise_standstill and not CP_SP.enableGasInterceptor
@@ -26,17 +26,22 @@ def long_control_state_trans(CP_SP, active, long_control_state,
if long_control_state == LongCtrlState.off:
if not starting_condition:
long_control_state = LongCtrlState.stopping
elif CP.startingState:
long_control_state = LongCtrlState.starting
else:
long_control_state = LongCtrlState.pid
elif long_control_state == LongCtrlState.stopping:
if starting_condition:
if starting_condition and CP.startingState:
long_control_state = LongCtrlState.starting
elif starting_condition:
long_control_state = LongCtrlState.pid
elif long_control_state == LongCtrlState.pid:
elif long_control_state in [LongCtrlState.starting, LongCtrlState.pid]:
if should_stop:
long_control_state = LongCtrlState.stopping
elif v_ego > CP.vEgoStarting:
long_control_state = LongCtrlState.pid
return long_control_state
class LongControl:
@@ -57,7 +62,7 @@ class LongControl:
self.pid.neg_limit = accel_limits[0]
self.pid.pos_limit = accel_limits[1]
self.long_control_state = long_control_state_trans(self.CP_SP, active, self.long_control_state,
self.long_control_state = long_control_state_trans(self.CP, self.CP_SP, active, self.long_control_state, CS.vEgo,
should_stop, CS.brakePressed,
CS.cruiseState.standstill)
if self.long_control_state == LongCtrlState.off:
@@ -68,8 +73,11 @@ class LongControl:
output_accel = self.last_output_accel
if output_accel > self.CP.stopAccel:
output_accel = min(output_accel, 0.0)
# TODO: can we just go straight to stopAccel?
output_accel -= 1.0 * DT_CTRL # m/s^2/s while trying to stop
output_accel -= self.CP.stoppingDecelRate * DT_CTRL
self.reset()
elif self.long_control_state == LongCtrlState.starting:
output_accel = self.CP.startAccel
self.reset()
else: # LongCtrlState.pid
@@ -137,7 +137,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
action_t = self.CP.longitudinalActuatorDelay + DT_MDL
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
action_t=action_t)
action_t=action_t, vEgoStopping=self.CP.vEgoStopping)
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop
+2 -1
View File
@@ -2,6 +2,7 @@
from openpilot.cereal import custom
from opendbc.car.structs import car
from openpilot.common.gps import get_gps_location_service
from openpilot.common.hardware import HARDWARE
from openpilot.common.params import Params
from openpilot.common.realtime import Priority, config_realtime_process
from openpilot.common.swaglog import cloudlog
@@ -11,7 +12,7 @@ import openpilot.cereal.messaging as messaging
def main():
config_realtime_process(5, Priority.CTRL_LOW)
config_realtime_process(6 if HARDWARE.get_device_type() == "mici" else 5, Priority.CTRL_LOW)
cloudlog.info("plannerd is waiting for CarParams")
params = Params()
+14 -2
View File
@@ -26,6 +26,8 @@ SPEED, ACCEL = 0, 1 # Kalman filter states enum
# stationary qualification parameters
V_EGO_STATIONARY = 4. # no stationary object flag below this speed
DBC_MOTION_STATIONARY = 1
DBC_MOTION_MOVING = 2
RADAR_TO_CAMERA = 1.52 # RADAR is ~ 1.5m ahead from center of mesh frame
@@ -191,8 +193,14 @@ def get_custom_yrel(CP: structs.CarParams, CP_SP: structs.CarParamsSP, lead_dict
return lead_dict
def radar_point_eligible_for_fusion(CP: structs.CarParams, CP_SP: structs.CarParamsSP,
point: car.RadarData.RadarPoint) -> bool:
use_dbc_motion = CP.brand == "hyundai" and CP_SP.flags & HyundaiFlagsSP.RADAR_FULL_RADAR
return not use_dbc_motion or point.motionState in (DBC_MOTION_STATIONARY, DBC_MOTION_MOVING)
class RadarD:
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParams, delay: float = 0.0):
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP, delay: float = 0.0):
self.CP = CP
self.CP_SP = CP_SP
@@ -220,7 +228,11 @@ class RadarD:
self.v_ego_hist.append(self.v_ego)
self.last_v_ego_frame = sm.recv_frame['carState']
ar_pts = {pt.trackId: [pt.dRel, pt.yRel, pt.vRel] for pt in rr.points}
ar_pts = {
pt.trackId: [pt.dRel, pt.yRel, pt.vRel]
for pt in rr.points
if radar_point_eligible_for_fusion(self.CP, self.CP_SP, pt)
}
# *** remove missing points from meta data ***
for ids in list(self.tracks.keys()):
@@ -1,43 +1,60 @@
from openpilot.cereal import custom
from opendbc.car.structs import car
from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState, long_control_state_trans
class TestLongControlStateTransition:
def test_stay_stopped(self):
CP = car.CarParams.new_message()
CP_SP = custom.CarParamsSP.new_message()
active = True
current_state = LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=True, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=True, cruise_standstill=False)
assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=True)
assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid
active = False
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.off
def test_engage():
CP = car.CarParams.new_message()
CP_SP = custom.CarParamsSP.new_message()
active = True
current_state = LongCtrlState.off
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=True, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=True, cruise_standstill=False)
assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=True)
assert next_state == LongCtrlState.stopping
next_state = long_control_state_trans(CP_SP, active, current_state,
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid
def test_starting():
CP = car.CarParams.new_message(startingState=True, vEgoStarting=0.5)
CP_SP = custom.CarParamsSP.new_message()
active = True
current_state = LongCtrlState.starting
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=0.1,
should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.starting
next_state = long_control_state_trans(CP, CP_SP, active, current_state, v_ego=1.0,
should_stop=False, brake_pressed=False, cruise_standstill=False)
assert next_state == LongCtrlState.pid
@@ -0,0 +1,29 @@
from opendbc.car import structs
from opendbc.car.structs import car
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP
from openpilot.cereal import custom
from openpilot.selfdrive.controls.radard import radar_point_eligible_for_fusion
def radar_point(motion_state: int):
point = car.RadarData.RadarPoint.new_message()
point.motionState = motion_state
return point
def test_hyundai_full_radar_fuses_only_classified_points():
CP = structs.CarParams(brand="hyundai")
CP_SP = custom.CarParamsSP.new_message()
CP_SP.flags = HyundaiFlagsSP.RADAR_FULL_RADAR.value
assert radar_point_eligible_for_fusion(CP, CP_SP, radar_point(1))
assert radar_point_eligible_for_fusion(CP, CP_SP, radar_point(2))
assert not radar_point_eligible_for_fusion(CP, CP_SP, radar_point(0))
assert not radar_point_eligible_for_fusion(CP, CP_SP, radar_point(255))
def test_radar_motion_filter_does_not_affect_other_modes_or_brands():
CP_SP = custom.CarParamsSP.new_message()
assert radar_point_eligible_for_fusion(structs.CarParams(brand="hyundai"), CP_SP, radar_point(0))
assert radar_point_eligible_for_fusion(structs.CarParams(brand="toyota"), CP_SP, radar_point(0))
@@ -7,6 +7,8 @@ from opendbc.car.lateral import get_friction, FRICTION_THRESHOLD
from openpilot.common.realtime import DT_MDL
from openpilot.selfdrive.locationd.torqued import TorqueEstimator, MIN_BUCKET_POINTS, POINTS_PER_BUCKET, STEER_BUCKET_BOUNDS
np.random.seed(0)
LA_ERR_STD = 1.0
INPUT_NOISE_STD = 0.08
V_EGO = 30.0
@@ -56,17 +58,16 @@ def simulate_straight_road_msgs(est):
for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)):
est.handle_log(t, which, msg)
class TestTorquedLatAccelOffset:
def test_estimated_offset(self):
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels)
msg = est.get_msg()
# TODO add lataccelfactor and friction check when we have more accurate estimates
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
def test_estimated_offset():
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE_BIASED, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels)
msg = est.get_msg()
# TODO add lataccelfactor and friction check when we have more accurate estimates
assert abs(msg.liveTorqueParameters.latAccelOffsetRaw - TORQUE_TUNE_BIASED.latAccelOffset) < 0.1
def test_straight_road_roll_bias(self):
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels)
simulate_straight_road_msgs(est)
msg = est.get_msg()
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)
def test_straight_road_roll_bias():
steer_torques, lat_accels = generate_inputs(TORQUE_TUNE, la_err_std=LA_ERR_STD, input_noise_std=INPUT_NOISE_STD)
est = get_warmed_up_estimator(steer_torques, lat_accels)
simulate_straight_road_msgs(est)
msg = est.get_msg()
assert (msg.liveTorqueParameters.latAccelOffsetRaw < -0.05) and np.isfinite(msg.liveTorqueParameters.latAccelOffsetRaw)
+3 -5
View File
@@ -1,5 +1,4 @@
import numpy as np
from collections.abc import Sequence
from typing import Any
from functools import cache
@@ -69,8 +68,7 @@ class NPQueue:
class PointBuckets:
def __init__(self, x_bounds: list[tuple[float, float]], min_points: Sequence[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None:
self._rng = np.random.default_rng()
def __init__(self, x_bounds: list[tuple[float, float]], min_points: list[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None:
self.x_bounds = x_bounds
self.buckets = {bounds: NPQueue(maxlen=points_per_bucket, rowsize=rowsize) for bounds in x_bounds}
self.buckets_min_points = dict(zip(x_bounds, min_points, strict=True))
@@ -100,9 +98,9 @@ class PointBuckets:
points = np.vstack([x.arr for x in self.buckets.values()])
if num_points is None:
return points
return points[self._rng.choice(np.arange(len(points)), min(len(points), num_points), replace=False)]
return points[np.random.choice(np.arange(len(points)), min(len(points), num_points), replace=False)]
def load_points(self, points: Sequence[Sequence[float]]) -> None:
def load_points(self, points: list[list[float]]) -> None:
for point in points:
self.add_point(*point)
+1 -1
View File
@@ -66,7 +66,7 @@ class LocationEstimator:
self.observations = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds}
self.observation_errors = {kind: np.zeros(3, dtype=np.float32) for kind in obs_kinds}
def reset(self, t: float | None, x_initial: np.ndarray = PoseKalman.initial_x, P_initial: np.ndarray = PoseKalman.initial_P):
def reset(self, t: float, x_initial: np.ndarray = PoseKalman.initial_x, P_initial: np.ndarray = PoseKalman.initial_P):
self.kf.init_state(x_initial, covs=P_initial, filter_time=t)
def _validate_sensor_source(self, source: log.SensorEventData.SensorSource):
+21
View File
@@ -200,6 +200,25 @@ def check_valid_with_hysteresis(current_valid: bool, val: float, threshold: floa
return current_valid
# TODO: Remove this function after few releases (added in 0.9.9)
def migrate_cached_vehicle_params_if_needed(params: Params):
last_parameters_data_old = params.get("LiveParameters")
last_parameters_data = params.get("LiveParametersV2")
if last_parameters_data_old is None or last_parameters_data is not None:
return
try:
last_parameters_msg = messaging.new_message('liveParameters')
last_parameters_msg.liveParameters.valid = True
last_parameters_msg.liveParameters.steerRatio = last_parameters_data_old['steerRatio']
last_parameters_msg.liveParameters.stiffnessFactor = last_parameters_data_old['stiffnessFactor']
last_parameters_msg.liveParameters.angleOffsetAverageDeg = last_parameters_data_old['angleOffsetAverageDeg']
params.put("LiveParametersV2", last_parameters_msg.to_bytes(), block=True)
except Exception as e:
cloudlog.error(f"Failed to perform parameter migration: {e}")
params.remove("LiveParameters")
def retrieve_initial_vehicle_params(params: Params, CP: car.CarParams, replay: bool, debug: bool):
last_parameters_data = params.get("LiveParametersV2")
last_carparams_data = params.get("CarParamsPrevRoute")
@@ -254,6 +273,8 @@ def main():
params = Params()
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
migrate_cached_vehicle_params_if_needed(params)
steer_ratio, stiffness_factor, angle_offset_deg, pInitial = retrieve_initial_vehicle_params(params, CP, REPLAY, DEBUG)
learner = VehicleParamsLearner(CP, steer_ratio, stiffness_factor, np.radians(angle_offset_deg), pInitial)
@@ -81,7 +81,6 @@ class TestLagd:
assert retrieve_initial_lag(params, CP) is None
def test_ncc(self):
rng = np.random.default_rng()
lag_frames = random.randint(1, 19)
desired_sig = np.sin(np.arange(0.0, 10.0, 0.1))
@@ -92,15 +91,15 @@ class TestLagd:
assert np.argmax(corr) == lag_frames
# add some noise
desired_sig += rng.normal(0, 0.05, len(desired_sig))
actual_sig += rng.normal(0, 0.05, len(actual_sig))
desired_sig += np.random.normal(0, 0.05, len(desired_sig))
actual_sig += np.random.normal(0, 0.05, len(actual_sig))
corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
# mask out 40% of the values, and make them noise
mask = rng.choice([True, False], size=len(desired_sig), p=[0.6, 0.4])
desired_sig[~mask] = rng.normal(0, 1, size=np.sum(~mask))
actual_sig[~mask] = rng.normal(0, 1, size=np.sum(~mask))
mask = np.random.choice([True, False], size=len(desired_sig), p=[0.6, 0.4])
desired_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
actual_sig[~mask] = np.random.normal(0, 1, size=np.sum(~mask))
corr = masked_normalized_cross_correlation(desired_sig, actual_sig, mask, 200)[len(desired_sig) - 1:len(desired_sig) + 20]
assert np.argmax(corr) in range(lag_frames - MAX_ERR_FRAMES, lag_frames + MAX_ERR_FRAMES + 1)
@@ -37,9 +37,9 @@ def get_select_fields_data(logs):
def sig_smooth(signal):
return masked_symmetric_moving_average(signal, np.ones_like(signal), 5, 1.0)
def get_nested_keys(msg, keys):
val = msg
val = None
for key in keys:
val = getattr(val, key) if isinstance(key, str) else val[key]
val = getattr(msg if val is None else val, key) if isinstance(key, str) else val[key]
return val
lp = [x.livePose for x in logs if x.which() == 'livePose']
data = defaultdict(list)
@@ -2,7 +2,7 @@ import random
import numpy as np
from openpilot.cereal import messaging
from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params
from openpilot.selfdrive.locationd.paramsd import retrieve_initial_vehicle_params, migrate_cached_vehicle_params_if_needed
from openpilot.selfdrive.locationd.models.car_kf import CarKalman
from openpilot.selfdrive.locationd.test.test_locationd_scenarios import TEST_ROUTE
from openpilot.selfdrive.test.process_replay.migration import migrate, migrate_carParams
@@ -30,9 +30,38 @@ class TestParamsd:
params.put("LiveParametersV2", msg.to_bytes(), block=True)
params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True)
migrate_cached_vehicle_params_if_needed(params) # this is not tested here but should not mess anything up or throw an error
sr, sf, offset, p_init = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True)
np.testing.assert_allclose(sr, msg.liveParameters.steerRatio)
np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor)
np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg)
np.testing.assert_equal(p_init.shape, CarKalman.P_initial.shape)
np.testing.assert_allclose(np.diagonal(p_init), msg.liveParameters.debugFilterState.std)
# TODO Remove this test after the support for old format is removed
def test_read_saved_params_old_format(self):
params = Params()
lr = migrate(LogReader(TEST_ROUTE), [migrate_carParams])
CP = next(m for m in lr if m.which() == "carParams").carParams
msg = get_random_live_parameters(CP)
params.put("LiveParameters", msg.liveParameters.to_dict(), block=True)
params.put("CarParamsPrevRoute", CP.as_builder().to_bytes(), block=True)
params.remove("LiveParametersV2")
migrate_cached_vehicle_params_if_needed(params)
sr, sf, offset, _ = retrieve_initial_vehicle_params(params, CP, replay=True, debug=True)
np.testing.assert_allclose(sr, msg.liveParameters.steerRatio)
np.testing.assert_allclose(sf, msg.liveParameters.stiffnessFactor)
np.testing.assert_allclose(offset, msg.liveParameters.angleOffsetAverageDeg)
assert params.get("LiveParametersV2") is not None
def test_read_saved_params_corrupted_old_format(self):
params = Params()
params.put("LiveParameters", {}, block=True)
params.remove("LiveParametersV2")
migrate_cached_vehicle_params_if_needed(params)
assert params.get("LiveParameters") is None
assert params.get("LiveParametersV2") is None
@@ -2,25 +2,24 @@ from opendbc.car.structs import car
from openpilot.selfdrive.locationd.torqued import TorqueEstimator
class TestTorqued:
def test_cal_percent(self):
est = TorqueEstimator(car.CarParams())
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == 0
def test_cal_percent():
est = TorqueEstimator(car.CarParams())
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == 0
for (low, high), min_pts in zip(est.filtered_points.buckets.keys(),
est.filtered_points.buckets_min_points.values(), strict=True):
for _ in range(int(min_pts)):
est.filtered_points.add_point((low + high) / 2.0, 0.0)
for (low, high), min_pts in zip(est.filtered_points.buckets.keys(),
est.filtered_points.buckets_min_points.values(), strict=True):
for _ in range(int(min_pts)):
est.filtered_points.add_point((low + high) / 2.0, 0.0)
# enough bucket points, but not enough total points
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2
# enough bucket points, but not enough total points
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == (len(est.filtered_points) / est.min_points_total * 100 + 100) / 2
# add enough points to bucket with most capacity
key = list(est.filtered_points.buckets)[0]
for _ in range(est.min_points_total - len(est.filtered_points)):
est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0)
# add enough points to bucket with most capacity
key = list(est.filtered_points.buckets)[0]
for _ in range(est.min_points_total - len(est.filtered_points)):
est.filtered_points.add_point((key[0] + key[1]) / 2.0, 0.0)
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == 100
msg = est.get_msg()
assert msg.liveTorqueParameters.calPerc == 100
+4 -5
View File
@@ -62,14 +62,14 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
self.lag = 0.0
self.track_all_points = track_all_points # for offline analysis, without max lateral accel or max steer torque filters
if decimated:
self.min_bucket_points: list[float] = (MIN_BUCKET_POINTS / 10).tolist()
self.min_bucket_points = MIN_BUCKET_POINTS / 10
self.min_points_total = MIN_POINTS_TOTAL_QLOG
self.fit_points = FIT_POINTS_TOTAL_QLOG
self.factor_sanity = FACTOR_SANITY_QLOG
self.friction_sanity = FRICTION_SANITY_QLOG
else:
self.min_bucket_points = MIN_BUCKET_POINTS.tolist()
self.min_bucket_points = MIN_BUCKET_POINTS
self.min_points_total = MIN_POINTS_TOTAL
self.fit_points = FIT_POINTS_TOTAL
self.factor_sanity = FACTOR_SANITY
@@ -120,10 +120,9 @@ class TorqueEstimator(ParameterEstimator, TorqueEstimatorExt):
'latAccelOffset': cache_ltp.latAccelOffsetFiltered,
'frictionCoefficient': cache_ltp.frictionCoefficientFiltered
}
cached_points: list[list[float]] = [list(point) for point in cache_ltp.points]
initial_params['points'] = cached_points
initial_params['points'] = cache_ltp.points
self.decay = cache_ltp.decay
self.filtered_points.load_points(cached_points)
self.filtered_points.load_points(initial_params['points'])
cloudlog.info("restored torque params from cache")
except Exception:
cloudlog.exception("failed to restore cached torque params")
+2 -6
View File
@@ -13,7 +13,6 @@ from collections import namedtuple
import numpy as np
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
def _patch_tinygrad_fetch_fw():
import hashlib
@@ -243,7 +242,7 @@ def compile_jit(jit, make_random_inputs, input_keys, make_queues):
SEED = 42
def random_inputs_run(fn, seed, test_val=None, test_buffers=None, expect_match=True):
input_queues, npy = make_queues(Device.DEFAULT)
rng = np.random.default_rng(seed)
np.random.seed(seed)
Tensor.manual_seed(seed)
testing = test_val is not None or test_buffers is not None
@@ -251,7 +250,7 @@ def compile_jit(jit, make_random_inputs, input_keys, make_queues):
for i in range(n_runs):
for v in npy.values():
v[:] = rng.standard_normal(v.shape).astype(v.dtype)
v[:] = np.random.randn(*v.shape).astype(v.dtype)
Device.default.synchronize()
random_inputs = make_random_inputs()
st = time.perf_counter()
@@ -312,9 +311,6 @@ if __name__ == "__main__":
p.add_argument('--frame-skip', type=int, required=True)
args = p.parse_args()
if 'USB+AMD' in os.environ.get('DEV', ''):
wait_usbgpu_link()
model_path = read_file_chunked_to_disk(args.onnx)
model_w, model_h = args.model_size
+1 -4
View File
@@ -25,7 +25,6 @@ from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_drivi
from openpilot.common.file_chunker import open_file_chunked, get_manifest_path
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
@@ -95,7 +94,7 @@ class ModelState(ModelStateBase):
self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
self.full_frames: dict[str, Tensor] = {}
self._blob_cache: dict[tuple[str, int], Tensor] = {}
self._blob_cache: dict[int, Tensor] = {}
self.parser = Parser()
self.frame_buf_params = {k: get_nv12_info(cam_w, cam_h) for k in ('img', 'big_img')}
self.run_policy = jits['run_policy']
@@ -174,8 +173,6 @@ def main(demo=False):
if use_extra_client:
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
if USBGPU:
wait_usbgpu_link()
st = time.monotonic()
cloudlog.warning("loading model")
model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU)
@@ -41,7 +41,7 @@ class Parser:
raw = outs[name]
outs[name] = sigmoid(raw)
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=()):
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None):
if self.check_missing(outs, name):
return
raw = outs[name]
-34
View File
@@ -1,34 +0,0 @@
import time
from pathlib import Path
from openpilot.common.swaglog import cloudlog
from openpilot.common.hardware.usb import CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID, usb_devices, controller, read_int
STABLE_SECONDS = 2.0
STABLE_THRESHOLD = 5.0 # link errors per second
def _chestnut_portli() -> Path | None:
for device in usb_devices():
if read_int(device / "idVendor", 16) == CHESTNUT_VENDOR_ID and \
read_int(device / "idProduct", 16) == CHESTNUT_PRODUCT_ID:
ctrl = controller(device)
if ctrl is not None and (ctrl / "portli").exists():
return ctrl / "portli"
return None
def wait_usbgpu_link(timeout: float = 30.0) -> None:
portli = _chestnut_portli()
if portli is None:
return
t0 = time.monotonic()
while time.monotonic() - t0 < timeout:
start = read_int(portli, 0)
time.sleep(STABLE_SECONDS)
rate = (read_int(portli, 0) - start) / STABLE_SECONDS
if rate <= STABLE_THRESHOLD:
return
cloudlog.warning(f"usbgpu link not stable: {rate:.0f} errors/s")
cloudlog.error("usbgpu link never stabilized")
+11 -19
View File
@@ -41,7 +41,7 @@ class DRIVER_MONITOR_SETTINGS:
# lockout specs
self._MAX_ALERT_3 = 2
self._MAX_NO_RESPONSE = 1
self._LOCKOUT_TIMES = [int(60 * n_min / DT_DMON) for n_min in [1, 5, 15, 30]]
self._LOCKOUT_TIME = int(1800 / DT_DMON)
self._TIMEOUT_RECOVERY_FACTOR_MAX = 5.
self._TIMEOUT_RECOVERY_FACTOR_MIN = 1.25
@@ -152,10 +152,7 @@ class DriverMonitoring:
self.cnt_since_alert_3 = 0
self.no_response_timeout = int(self.settings._NO_RESPONSE_TIMEOUT / DT_DMON)
self.no_response_cnt = 0
self.lockout_active = Params().get_bool("DriverTooDistracted")
self.lockout_count = Params().get("DriverLockoutCount") or 0
self.lockout_duration = self.settings._LOCKOUT_TIMES[min(max(self.lockout_count - 1, 0), len(self.settings._LOCKOUT_TIMES) - 1)]
self.lockout_time_elapsed = 0
self.lockout_time = 0
self.step_change = 0.
self.active_policy = MonitoringPolicy.vision
self.driver_interacting = False
@@ -166,6 +163,7 @@ class DriverMonitoring:
self.threshold_alert_2 = 0.
self.dcam_uncertain_cnt = 0
self.dcam_reset_cnt = 0
self.too_distracted = Params().get_bool("DriverTooDistracted")
self._reset_awareness()
self._set_policy(MonitoringPolicy.vision)
@@ -312,20 +310,16 @@ class DriverMonitoring:
self.driver_interacting = driver_engaged
if self.alert_3_cnt >= self.settings._MAX_ALERT_3 or self.no_response_cnt >= self.settings._MAX_NO_RESPONSE:
if not self.lockout_active:
self.lockout_count += 1
self.lockout_duration = self.settings._LOCKOUT_TIMES[min(self.lockout_count - 1, len(self.settings._LOCKOUT_TIMES) - 1)]
Params().put("DriverLockoutCount", self.lockout_count)
self.lockout_active = True
self.too_distracted = True
if self.lockout_active:
self.lockout_time_elapsed += 1
if self.lockout_time_elapsed > self.lockout_duration:
self.lockout_active = False
if self.too_distracted:
self.lockout_time += 1
if self.lockout_time > self.settings._LOCKOUT_TIME:
self.too_distracted = False
self.alert_3_cnt = 0
self.cnt_since_alert_3 = 0
self.no_response_cnt = 0
self.lockout_time_elapsed = 0
self.lockout_time = 0
always_on_valid = self.always_on and not wrong_gear
if (self.driver_interacting and self.awareness > 0 and self.active_policy == MonitoringPolicy.wheeltouch) or \
@@ -385,10 +379,8 @@ class DriverMonitoring:
dat = messaging.new_message('driverMonitoringState', valid=valid)
dm = dat.driverMonitoringState
dm.lockout = self.lockout_active
dm.lockoutCount = self.lockout_count
if self.lockout_active:
dm.lockoutMinutesRemaining = max(1, round((self.lockout_duration - self.lockout_time_elapsed) * DT_DMON / 60.))
dm.lockout = self.too_distracted
dm.lockoutRecoveryPercent = to_percent(self.lockout_time / self.settings._LOCKOUT_TIME)
dm.alert3Count = self.alert_3_cnt
dm.noResponseCount = self.no_response_cnt
dm.noResponseForceDecel = self.alert_level == AlertLevel.three and self.cnt_since_alert_3 >= self.no_response_timeout
@@ -86,17 +86,21 @@ class TestMonitoring:
# engaged, distracted past red and beyond the no-response window -> unavailability response + lockout
def test_distracted_lockout(self):
alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false)
s = d_status.settings
assert alert_lvls[int(DISTRACTED_SECONDS_TO_RED / DT_DMON)] == 3
assert d_status.lockout_active
assert d_status.lockout_time_elapsed > 0
assert d_status.lockout_count >= 1
assert d_status.alert_3_cnt == 1
assert d_status.no_response_cnt == s._MAX_NO_RESPONSE
assert d_status.too_distracted
assert d_status.lockout_time > 0
# no face -> wheeltouch red, sustained past the no-response timeout -> unavailability response + lockout
def test_invisible_lockout(self):
_, d_status = self._run_seq(always_no_face, always_false, always_true, always_false)
s = d_status.settings
assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch
assert d_status.lockout_active
assert d_status.lockout_count >= 1
assert d_status.alert_3_cnt == 1
assert d_status.no_response_cnt == s._MAX_NO_RESPONSE
assert d_status.too_distracted
# engaged, no face detected the whole time, no action
def test_fully_invisible_driver(self):
@@ -271,7 +275,7 @@ def test_run_step_engagement(selfdrive_enabled, lat_active, steering, gas,
captured['op_engaged'] = op_engaged
return orig(driver_engaged, op_engaged, lowspeed, wrong_gear)
object.__setattr__(dm, '_update_events', spy)
dm._update_events = spy
dm.run_step(sm, demo=False)
assert captured['op_engaged'] == expected_op_engaged
assert captured['driver_engaged'] == expected_driver_engaged
@@ -60,7 +60,7 @@ class TestPandad:
def test_in_reset(self):
gpio_init(GPIO.STM_RST_N, True)
gpio_set(GPIO.STM_RST_N, True)
gpio_set(GPIO.STM_RST_N, 1)
assert not Panda.list()
self._run_test()
@@ -38,10 +38,10 @@ class TestBoarddSpi:
total_recv_count = 0
total_sent_count = 0
sent_msgs = {bus: [] for bus in range(3)}
sent_msgs = {bus: list() for bus in range(3)}
st = time.monotonic()
ts = {s: [] for s in socks.keys()}
ts = {s: list() for s in socks.keys()}
for _ in range(int(os.getenv("TEST_TIME", "20"))):
# send some CAN messages
if not JUNGLE_SPAM:
+4 -2
View File
@@ -7,8 +7,9 @@ from opendbc.car.structs import car
import openpilot.cereal.messaging as messaging
from openpilot.common.constants import CV
from openpilot.common.git import get_short_branch
from openpilot.common.realtime import DT_CTRL
from openpilot.common.realtime import DT_CTRL, DT_DMON
from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER
from openpilot.selfdrive.monitoring.policy import DRIVER_MONITOR_SETTINGS
from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER
from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION
from openpilot.common.hardware import HARDWARE
@@ -24,6 +25,7 @@ VisualAlert = car.CarControl.HUDControl.VisualAlert
AudibleAlert = log.SelfdriveState.AudibleAlert
EventName = log.OnroadEvent.EventName
DMON_LOCKOUT_TIME = DRIVER_MONITOR_SETTINGS()._LOCKOUT_TIME
# get event name from enum
EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()}
@@ -99,7 +101,7 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag
def too_distracted_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert:
if sm['driverMonitoringState'].lockout:
mins_left = sm['driverMonitoringState'].lockoutMinutesRemaining
mins_left = max(1, round((100 - sm['driverMonitoringState'].lockoutRecoveryPercent) / 100 * DMON_LOCKOUT_TIME * DT_DMON / 60.))
return NoEntryAlert("Too Distracted", f"{mins_left} minute{'s' if mins_left != 1 else ''} Left", priority=Priority.HIGH)
return NoEntryAlert("Pay Attention to Engage", priority=Priority.HIGH)
+1 -1
View File
@@ -645,7 +645,7 @@ class SelfdriveD(CruiseHelper):
def main():
config_realtime_process(4, Priority.CTRL_HIGH)
config_realtime_process(6 if HARDWARE.get_device_type() == "mici" else 4, Priority.CTRL_HIGH)
s = SelfdriveD()
s.run()
@@ -13,11 +13,11 @@ ALL_STATES = tuple(State.schema.enumerants.values())
ENABLE_EVENT_TYPES = (ET.ENABLE, ET.PRE_ENABLE, ET.OVERRIDE_LATERAL, ET.OVERRIDE_LONGITUDINAL)
def make_event(event_types: list[str | None]):
EVENTS[0] = {}
def make_event(event_types):
event = {}
for ev in event_types:
if ev is not None:
EVENTS[0][ev] = NormalPermanentAlert("alert")
event[ev] = NormalPermanentAlert("alert")
EVENTS[0] = event
return 0
+1 -1
View File
@@ -7,7 +7,7 @@ from openpilot.common.utils import tabulate
DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496"
MB = 1024 * 1024
TABULATE_OPTS = {"tablefmt": "simple_grid", "stralign": "center", "numalign": "center"}
TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center")
def _get_procs():
@@ -297,7 +297,7 @@ def migrate_carOutput(msgs):
co = messaging.new_message('carOutput')
co.valid = msg.valid
co.logMonoTime = msg.logMonoTime
co.carOutput.actuatorsOutput = msg.carControl.deprecated.actuatorsOutput
co.carOutput.actuatorsOutput = msg.carControl.actuatorsOutputDEPRECATED
add_ops.append(as_reader(co))
return [], add_ops, []
@@ -323,10 +323,10 @@ def migrate_pandaStates(msgs):
safety_param = safety_param_migration[fingerprint].value
elif len(CP.safetyConfigs):
safety_param = CP.safetyConfigs[0].safetyParam
if CP.safetyConfigs[0].deprecated.safetyParam != 0:
safety_param = CP.safetyConfigs[0].deprecated.safetyParam
if CP.safetyConfigs[0].safetyParamDEPRECATED != 0:
safety_param = CP.safetyConfigs[0].safetyParamDEPRECATED
else:
safety_param = CP.deprecated.safetyParam
safety_param = CP.safetyParamDEPRECATED
ops = []
for index, msg in msgs:
@@ -216,7 +216,8 @@ class ProcessContainer:
def _start_process(self):
if self.capture is not None:
self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher) # ty: ignore[invalid-assignment] # intentional wrapper
self.process.launcher = LauncherWithCapture(self.capture, self.process.launcher)
self.process.prepare()
self.process.start()
def start(
@@ -634,10 +635,10 @@ def replay_process(
fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None,
captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False
) -> list[capnp._DynamicStructReader]:
if isinstance(cfg, ProcessConfig):
cfgs = [cfg]
else:
if isinstance(cfg, Iterable):
cfgs = list(cfg)
else:
cfgs = [cfg]
all_msgs = migrate_all(lr,
manager_states=True,
+4 -7
View File
@@ -204,10 +204,7 @@ class FaceAnimator:
frames_back = round(rewind_elapsed / self._animation.frame_duration)
frame_index = self._rewind_from - frames_back
if frame_index <= 0:
if self._next is None:
self._rewinding = False
return self._animation.frames[0]
return self._switch_to_next(now, self._next)
return self._switch_to_next(now)
return self._animation.frames[frame_index]
# Play starting frames first (once)
@@ -226,7 +223,7 @@ class FaceAnimator:
if self._next is not None:
if frame_index == 0 and (len(self._animation.frames) == 1 or self._seen_nonzero):
return self._switch_to_next(now, self._next)
return self._switch_to_next(now)
# No natural return to frame 0 — start rewinding
if self._animation.mode in (AnimationMode.ONCE_FORWARD, AnimationMode.REPEAT_FORWARD):
self._rewinding = True
@@ -235,8 +232,8 @@ class FaceAnimator:
return self._animation.frames[frame_index]
def _switch_to_next(self, now: float, animation: Animation) -> list[tuple[int, int]]:
self._animation = animation
def _switch_to_next(self, now: float) -> list[tuple[int, int]]:
self._animation = self._next
self._next = None
self._rewinding = False
self._seen_nonzero = False
+2 -2
View File
@@ -34,10 +34,10 @@ class MainLayout(Widget):
# Initialize layouts
self._home_layout = HomeLayout()
self._home_body_layout = BodyLayout()
self._layouts: dict[MainState, Widget] = {
self._layouts = {
MainState.HOME: self._home_layout,
MainState.SETTINGS: SettingsLayout(),
MainState.ONROAD: AugmentedRoadView(),
MainState.ONROAD: AugmentedRoadView(radar_tracks_settings_callback=lambda: self.open_settings(PanelType.TOGGLES)),
}
self._sidebar_rect = rl.Rectangle(0, 0, 0, 0)
@@ -105,6 +105,7 @@ class DeviceLayout(Widget):
self._params.remove("CalibrationParams")
self._params.remove("LiveTorqueParameters")
self._params.remove("LiveParameters")
self._params.remove("LiveParametersV2")
self._params.remove("LiveDelay")
self._params.put_bool("OnroadCycleRequested", True, block=True)
@@ -1,5 +1,5 @@
import pyray as rl
from dataclasses import dataclass, field
from dataclasses import dataclass
from enum import IntEnum
from collections.abc import Callable
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
@@ -43,7 +43,7 @@ class PanelType(IntEnum):
class PanelInfo:
name: str
instance: Widget
button_rect: rl.Rectangle = field(default_factory=lambda: rl.Rectangle(0, 0, 0, 0))
button_rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0)
class SettingsLayout(Widget):
@@ -106,7 +106,18 @@ class TogglesLayout(Widget):
icon="speed_limit.png"
)
self._radar_tracks_setting = None
self._toggles = {}
if gui_app.sunnypilot_ui():
self._radar_tracks_setting = multiple_button_item(
lambda: tr("Radar Tracks"),
"",
buttons=[lambda: tr("Off"), lambda: tr("Lead Only"), lambda: tr("Full Radar")],
button_width=250,
callback=self._set_radar_tracks,
selected_index=self._params.get("RadarTracks", return_default=True),
)
self._toggles["RadarTracks"] = self._radar_tracks_setting
self._locked_toggles = set()
for param, (title, desc, icon, needs_restart) in self._toggle_defs.items():
toggle = toggle_item(
@@ -203,6 +214,8 @@ class TogglesLayout(Widget):
# refresh toggles from params to mirror external changes
for param in self._toggle_defs:
self._toggles[param].action_item.set_state(self._params.get_bool(param))
if self._radar_tracks_setting is not None:
self._radar_tracks_setting.action_item.set_selected_button(self._params.get("RadarTracks", return_default=True))
# these toggles need restart, block while engaged
for toggle_def in self._toggle_defs:
@@ -247,3 +260,6 @@ class TogglesLayout(Widget):
def _set_longitudinal_personality(self, button_index: int):
self._params.put("LongitudinalPersonality", button_index, block=True)
def _set_radar_tracks(self, button_index: int):
self._params.put("RadarTracks", button_index, block=True)
+1 -1
View File
@@ -68,7 +68,7 @@ class Sidebar(Widget, SidebarSP):
def __init__(self):
Widget.__init__(self)
SidebarSP.__init__(self)
self._net_type = NETWORK_TYPES[NetworkType.none]
self._net_type = NETWORK_TYPES.get(NetworkType.none)
self._net_strength = 0
self._temp_status = MetricData(tr_noop("TEMP"), tr_noop("GOOD"), Colors.GOOD)
+1 -1
View File
@@ -251,7 +251,7 @@ class MiciHomeLayout(Widget):
self._egpu_icon.set_visible(ui_state.usbgpu and ui_state.usbgpu_compiled)
self._egpu_icon_gray.set_visible(ui_state.usbgpu and not ui_state.usbgpu_compiled)
self._mic_icon.set_visible(ui_state.recording_audio)
self._body_icon.set_visible(bool(ui_state.is_body))
self._body_icon.set_visible(ui_state.is_body)
footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48)
self._status_bar_layout.render(footer_rect)
+3 -2
View File
@@ -32,7 +32,8 @@ class MiciMainLayout(Scroller):
self._home_layout = MiciHomeLayout()
self._alerts_layout = MiciOffroadAlerts()
self._settings_layout = SettingsLayout()
self._car_onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked)
self._car_onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked,
radar_tracks_settings_callback=self._settings_layout.open_toggles)
self._body_onroad_layout = BodyLayout()
# Initialize widget rects
@@ -148,4 +149,4 @@ class MiciMainLayout(Scroller):
def _on_body_changed(self):
self._car_onroad_layout.set_visible(not ui_state.is_body)
self._body_onroad_layout.set_visible(bool(ui_state.is_body))
self._body_onroad_layout.set_visible(ui_state.is_body)
@@ -250,12 +250,12 @@ class MiciOffroadAlerts(Scroller):
{alert_data.key: self.params.get(alert_data.key) for alert_data in self.sorted_alerts})
time.sleep(REFRESH_INTERVAL)
def _refresh(self, pending_params: dict) -> int:
def _refresh(self) -> int:
"""Refresh alerts from params and return active count."""
active_count = 0
# Handle UpdateAvailable alert specially
update_available = pending_params["UpdateAvailable"]
update_available = self._pending_params["UpdateAvailable"]
update_alert_data = next((alert_data for alert_data in self.sorted_alerts if alert_data.key == "UpdateAvailable"), None)
if update_alert_data:
@@ -263,7 +263,7 @@ class MiciOffroadAlerts(Scroller):
version_string = ""
# Get new version description and parse version and date
new_desc = pending_params["UpdaterNewDescription"] or ""
new_desc = self._pending_params["UpdaterNewDescription"] or ""
if new_desc:
# format: "version / branch / commit / date"
parts = new_desc.split(" / ")
@@ -284,7 +284,7 @@ class MiciOffroadAlerts(Scroller):
continue # Skip, already handled above
text = ""
alert_json = pending_params[alert_data.key]
alert_json = self._pending_params[alert_data.key]
if alert_json:
text = alert_json.get("text", "").replace("%1", alert_json.get("extra", ""))
@@ -311,9 +311,8 @@ class MiciOffroadAlerts(Scroller):
def _update_state(self):
"""Periodically refresh alerts."""
# Refresh alerts when thread updates params
pending_params = self._pending_params
if pending_params is not None:
self._refresh(pending_params)
if self._pending_params is not None:
self._refresh()
self._pending_params = None
def _render(self, rect: rl.Rectangle):
@@ -16,7 +16,7 @@ from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.html_render import HtmlRenderer
from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
@@ -160,7 +160,7 @@ class DeviceLayoutMici(NavScroller):
def __init__(self):
super().__init__()
self._fcc_dialog: MiciFccModal | None = None
self._fcc_dialog: HtmlModal | None = None
def power_off_callback():
ui_state.params.put_bool("DoShutdown", True, block=True)
@@ -172,6 +172,7 @@ class DeviceLayoutMici(NavScroller):
params = ui_state.params
params.remove("CalibrationParams")
params.remove("LiveTorqueParameters")
params.remove("LiveParameters")
params.remove("LiveParametersV2")
params.remove("LiveDelay")
params.put_bool("OnroadCycleRequested", True, block=True)
@@ -20,9 +20,9 @@ class SettingsLayout(NavScroller):
super().__init__()
self._params = Params()
toggles_panel = TogglesLayoutMici()
self._toggles_panel = TogglesLayoutMici()
toggles_btn = SettingsBigButton("toggles", "", gui_app.texture("icons_mici/settings.png", 64, 64))
toggles_btn.set_click_callback(lambda: gui_app.push_widget(toggles_panel))
toggles_btn.set_click_callback(lambda: gui_app.push_widget(self._toggles_panel))
network_panel = NetworkLayoutMici()
network_btn = SettingsBigButton("network", "", gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 76, 56))
@@ -56,3 +56,8 @@ class SettingsLayout(NavScroller):
])
self._font_medium = gui_app.font(FontWeight.MEDIUM)
def open_toggles(self) -> None:
if not gui_app.widget_in_stack(self):
gui_app.push_widget(self)
gui_app.push_widget(self._toggles_panel)
@@ -21,8 +21,10 @@ class TogglesLayoutMici(NavScroller):
record_front = BigParamControl("record & upload driver camera", "RecordFront", toggle_callback=restart_needed_callback)
record_mic = BigParamControl("record & upload mic audio", "RecordAudio", toggle_callback=restart_needed_callback)
enable_openpilot = BigParamControl("enable sunnypilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback)
radar_tracks = BigMultiParamToggle("radar tracks", "RadarTracks", ["off", "lead only", "full radar"])
self._scroller.add_widgets([
radar_tracks,
self._personality_toggle,
self._experimental_btn,
is_metric_toggle,
@@ -35,6 +37,7 @@ class TogglesLayoutMici(NavScroller):
# Toggle lists
self._refresh_toggles = (
("RadarTracks", radar_tracks),
("ExperimentalMode", self._experimental_btn),
("IsMetric", is_metric_toggle),
("IsLdwEnabled", ldw_toggle),
@@ -11,6 +11,7 @@ from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer
from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer
from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall
from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import RadarTracksStatus
from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets import Widget
@@ -134,9 +135,11 @@ class BookmarkIcon(Widget):
class AugmentedRoadView(CameraView):
def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD):
def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD,
radar_tracks_settings_callback=None):
super().__init__("camerad", stream_type)
self._bookmark_callback = bookmark_callback
self._radar_tracks_status = RadarTracksStatus(radar_tracks_settings_callback)
self._set_placeholder_color(rl.BLACK)
self.device_camera: DeviceCameraConfig | None = None
@@ -170,6 +173,13 @@ class AugmentedRoadView(CameraView):
def _update_state(self):
super()._update_state()
if ui_state.sm.updated["liveTracks"]:
self._radar_tracks_status.update(
ui_state.sm["liveTracks"], ui_state.sm.valid["liveTracks"], ui_state.radar_tracks, ui_state.sm["carState"].vEgo,
)
elif not ui_state.sm.alive["liveTracks"]:
self._radar_tracks_status.reset()
# update offroad label
if ui_state.panda_type == log.PandaState.PandaType.unknown:
self._offroad_label.set_text("system booting")
@@ -179,6 +189,9 @@ class AugmentedRoadView(CameraView):
self._offroad_label.set_text("start the car to\nuse sunnypilot")
def _handle_mouse_release(self, mouse_pos: MousePos):
if self._radar_tracks_status.handle_mouse(mouse_pos):
return
# Don't trigger click callback if bookmark was triggered
if not self._bookmark_icon.interacting():
super()._handle_mouse_release(mouse_pos)
@@ -221,6 +234,8 @@ class AugmentedRoadView(CameraView):
# Fade out bottom of overlays for looks
rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, rl.WHITE)
self._radar_tracks_status.render(self._content_rect)
alert_to_render, not_animating_out = self._alert_renderer.will_render()
# Hide DMoji when disengaged unless AlwaysOnDM is enabled
@@ -219,7 +219,7 @@ class CameraView(Widget):
[0.0, 0.0, 1.0]
])
def _render(self, rect: rl.Rectangle, /):
def _render(self, rect: rl.Rectangle):
if self._switching:
self._handle_switch()
@@ -14,6 +14,7 @@ from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.model_renderer import LANE_LINE_COLORS_SP, ModelRendererSP
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import draw_radar_lead_connectors, radar_lead_track_colors
CLIP_MARGIN = 500
MIN_DRAW_DISTANCE = 10.0
@@ -38,7 +39,6 @@ LANE_LINE_COLORS = {
**LANE_LINE_COLORS_SP,
}
@dataclass
class ModelPoints:
raw_points: np.ndarray = field(default_factory=lambda: np.empty((0, 3), dtype=np.float32))
@@ -50,6 +50,9 @@ class LeadVehicle:
glow: list[tuple[float, float]] = field(default_factory=list)
chevron: list[tuple[float, float]] = field(default_factory=list)
fill_alpha: int = 0
position: tuple[float, float] | None = None
radar_track_id: int = -1
radar: bool = False
class ModelRenderer(Widget, ModelRendererSP):
@@ -132,11 +135,12 @@ class ModelRenderer(Widget, ModelRendererSP):
model = sm['modelV2']
radar_state = sm['radarState'] if sm.valid['radarState'] else None
lead_one = radar_state.leadOne if radar_state else None
render_lead_indicator = self._longitudinal_control and radar_state is not None
render_lead_indicator = ui_state.radar_tracks != 0 and radar_state is not None
# Update model data when needed
model_updated = sm.updated['modelV2']
if model_updated or sm.updated['radarState'] or self._transform_dirty:
transform_updated = self._transform_dirty
if model_updated or sm.updated['radarState'] or transform_updated:
if model_updated:
self._update_raw_points(model)
@@ -154,8 +158,27 @@ class ModelRenderer(Widget, ModelRendererSP):
self._draw_lane_lines()
self._draw_path(sm)
# if render_lead_indicator and radar_state:
# self._draw_lead_indicator()
if sm.valid['liveTracks'] and sm.recv_frame['liveTracks'] >= ui_state.started_frame:
if (sm.updated['liveTracks'] or sm.updated['liveCalibration'] or transform_updated or
not self.radar_tracks.projection_initialized):
self.radar_tracks.update_radar_tracks(
sm['liveTracks'], self._map_to_screen, self._path_offset_z,
)
highlighted_tracks = radar_lead_track_colors(radar_state) if render_lead_indicator else {}
matched_positions = self.radar_tracks.draw_cached_radar_tracks(
screen_offset=(self._rect.x, self._rect.y),
highlighted_tracks=highlighted_tracks,
)
if render_lead_indicator:
draw_radar_lead_connectors(
self._lead_vehicles, matched_positions, highlighted_tracks,
screen_offset=(self._rect.x, self._rect.y),
)
else:
self.radar_tracks.clear_projection()
if render_lead_indicator:
self._draw_lead_indicator()
def _update_raw_points(self, model):
"""Update raw 3D points from model data"""
@@ -185,7 +208,11 @@ class ModelRenderer(Widget, ModelRendererSP):
z = self._path.raw_points[idx, 2] if idx < len(self._path.raw_points) else 0.0
point = self._map_to_screen(d_rel, -y_rel + self._camera_offset, z + self._path_offset_z)
if point:
self._lead_vehicles[i] = self._update_lead_vehicle(d_rel, v_rel, point, self._rect)
lead_vehicle = self._update_lead_vehicle(d_rel, v_rel, point, self._rect)
lead_vehicle.position = lead_vehicle.chevron[1]
lead_vehicle.radar_track_id = int(lead_data.radarTrackId)
lead_vehicle.radar = lead_data.radar
self._lead_vehicles[i] = lead_vehicle
def _update_model(self, lead, path_x_array):
"""Update model visualization data based on model message"""
@@ -380,8 +407,10 @@ class ModelRenderer(Widget, ModelRendererSP):
if not lead.glow or not lead.chevron:
continue
rl.draw_triangle_fan(lead.glow, len(lead.glow), rl.Color(218, 202, 37, 255))
rl.draw_triangle_fan(lead.chevron, len(lead.chevron), rl.Color(201, 34, 49, lead.fill_alpha))
offset_glow = [(x + self._rect.x, y + self._rect.y) for x, y in lead.glow]
offset_chevron = [(x + self._rect.x, y + self._rect.y) for x, y in lead.chevron]
rl.draw_triangle_fan(offset_glow, len(offset_glow), rl.Color(218, 202, 37, 255))
rl.draw_triangle_fan(offset_chevron, len(offset_chevron), rl.Color(201, 34, 49, lead.fill_alpha))
@staticmethod
def _get_path_length_idx(pos_x_array: np.ndarray, path_height: float) -> int:
@@ -41,81 +41,80 @@ def get_child_widgets(widget) -> list:
return children
class TestWidgetLeaks:
@pytest.mark.skip(reason="segfaults")
def test_dialogs_do_not_leak(self):
import pyray as rl
rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN)
from openpilot.system.ui.lib.application import gui_app
@pytest.mark.skip(reason="segfaults")
def test_dialogs_do_not_leak():
import pyray as rl
rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN)
from openpilot.system.ui.lib.application import gui_app
# mici dialogs
from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow
from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog
from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog
from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal
# mici dialogs
from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow
from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog
from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog
from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal
# tici dialogs
from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.html_render import HtmlModal
from openpilot.system.ui.widgets.keyboard import Keyboard
# tici dialogs
from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog
from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow
from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.html_render import HtmlModal
from openpilot.system.ui.widgets.keyboard import Keyboard
gui_app.init_window("ref-test")
gui_app.init_window("ref-test")
leaked_widgets = set()
leaked_widgets = set()
for ctor in (
# mici
MiciDriverCameraDialog, MiciPairingDialog,
lambda: MiciTrainingGuide(lambda: None),
lambda: MiciOnboardingWindow(lambda: None),
lambda: BigDialog("test", "test"),
lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None),
lambda: BigInputDialog("test"),
lambda: MiciFccModal(text="test"),
# tici
TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard,
lambda: ConfirmDialog("test", "ok"),
lambda: MultiOptionDialog("test", ["a", "b"]),
lambda: HtmlModal(text="test"),
):
widget = ctor()
all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]]
for ctor in (
# mici
MiciDriverCameraDialog, MiciPairingDialog,
lambda: MiciTrainingGuide(lambda: None),
lambda: MiciOnboardingWindow(lambda: None),
lambda: BigDialog("test", "test"),
lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None),
lambda: BigInputDialog("test"),
lambda: MiciFccModal(text="test"),
# tici
TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard,
lambda: ConfirmDialog("test", "ok"),
lambda: MultiOptionDialog("test", ["a", "b"]),
lambda: HtmlModal(text="test"),
):
widget = ctor()
all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]]
del widget
del widget
for ref in all_refs:
if ref() is not None:
obj = ref()
name = f"{type(obj).__module__}.{type(obj).__qualname__}"
leaked_widgets.add(name)
for ref in all_refs:
if ref() is not None:
obj = ref()
name = f"{type(obj).__module__}.{type(obj).__qualname__}"
leaked_widgets.add(name)
print(f"\n=== Widget {name} alive after del")
print(" Referrers:")
for r in gc.get_referrers(obj):
if r is obj:
continue
print(f"\n=== Widget {name} alive after del")
print(" Referrers:")
for r in gc.get_referrers(obj):
if r is obj:
continue
if hasattr(r, '__self__') and r.__self__ is not obj:
print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}")
elif hasattr(r, '__func__'):
print(f" method: {r.__name__}")
else:
print(f" {type(r).__module__}.{type(r).__qualname__}")
del obj
if hasattr(r, '__self__') and r.__self__ is not obj:
print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}")
elif hasattr(r, '__func__'):
print(f" method: {r.__name__}")
else:
print(f" {type(r).__module__}.{type(r).__qualname__}")
del obj
gui_app.close()
gui_app.close()
unexpected = leaked_widgets - KNOWN_LEAKS
assert not unexpected, f"New leaked widgets: {unexpected}"
unexpected = leaked_widgets - KNOWN_LEAKS
assert not unexpected, f"New leaked widgets: {unexpected}"
fixed = KNOWN_LEAKS - leaked_widgets
assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}"
fixed = KNOWN_LEAKS - leaked_widgets
assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}"
if __name__ == "__main__":
TestWidgetLeaks().test_dialogs_do_not_leak()
test_dialogs_do_not_leak()
@@ -375,7 +375,6 @@ class GreyBigButton(BigButton):
class BigMultiParamToggle(BigMultiToggle):
def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None,
select_callback: Callable | None = None):
assert Params is not None
super().__init__(text, options, toggle_callback, select_callback)
self._param = param
@@ -393,7 +392,6 @@ class BigMultiParamToggle(BigMultiToggle):
class BigParamControl(BigToggle):
def __init__(self, text: str, param: str, toggle_callback: Callable | None = None):
assert Params is not None
super().__init__(text, "", toggle_callback=toggle_callback)
self.param = param
self.params = Params()
@@ -411,7 +409,6 @@ class BigParamControl(BigToggle):
class BigCircleParamControl(BigCircleToggle):
def __init__(self, icon: rl.Texture, param: str, toggle_callback: Callable | None = None,
icon_offset: tuple[int, int] = (0, 0)):
assert Params is not None
super().__init__(icon, toggle_callback, icon_offset=icon_offset)
self._param = param
self.params = Params()
@@ -18,6 +18,7 @@ if gui_app.sunnypilot_ui():
from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP, AugmentedRoadViewSP
from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import RadarTracksStatus
from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus
OpState = log.SelfdriveState.OpenpilotState
@@ -39,7 +40,7 @@ INF_POINT = np.array([1000.0, 0.0, 0.0])
class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD):
def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD, radar_tracks_settings_callback=None):
CameraView.__init__(self, "camerad", stream_type)
AugmentedRoadViewSP.__init__(self)
self._set_placeholder_color(BORDER_COLORS[UIStatus.DISENGAGED])
@@ -51,12 +52,24 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
self._matrix_cache_key = (0, 0.0, 0.0, stream_type)
self._cached_matrix: np.ndarray | None = None
self._content_rect = rl.Rectangle()
self._radar_tracks_status = RadarTracksStatus(radar_tracks_settings_callback, right_margin=240) if gui_app.sunnypilot_ui() else None
self.model_renderer = ModelRenderer()
self._hud_renderer = HudRenderer()
self.alert_renderer = AlertRenderer()
self.driver_state_renderer = DriverStateRenderer()
def _update_state(self):
super()._update_state()
if self._radar_tracks_status is None:
return
if ui_state.sm.updated["liveTracks"]:
self._radar_tracks_status.update(
ui_state.sm["liveTracks"], ui_state.sm.valid["liveTracks"], ui_state.radar_tracks, ui_state.sm["carState"].vEgo,
)
elif not ui_state.sm.alive["liveTracks"]:
self._radar_tracks_status.reset()
def _render(self, rect):
# Only render when system is started to avoid invalid data access
if not ui_state.started:
@@ -90,6 +103,8 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
# Draw all UI overlays
self.model_renderer.render(self._content_rect)
AugmentedRoadViewSP.update_fade_out_bottom_overlay(self, self._content_rect)
if self._radar_tracks_status is not None:
self._radar_tracks_status.render(self._content_rect)
self._hud_renderer.render(self._content_rect)
self.alert_renderer.render(self._content_rect)
self.driver_state_renderer.render(self._content_rect)
@@ -103,7 +118,9 @@ class AugmentedRoadView(CameraView, AugmentedRoadViewSP):
# Draw colored border based on driving state
self._draw_border(rect)
def _handle_mouse_press(self, _):
def _handle_mouse_press(self, mouse_pos):
if self._radar_tracks_status is not None and self._radar_tracks_status.handle_mouse(mouse_pos):
return
if not self._hud_renderer.user_interacting() and self._click_callback is not None:
self._click_callback()
@@ -13,6 +13,7 @@ from openpilot.system.ui.lib.shader_polygon import draw_polygon, Gradient
from openpilot.system.ui.widgets import Widget
from openpilot.selfdrive.ui.sunnypilot.onroad.model_renderer import ChevronMetrics, ModelRendererSP
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import draw_radar_lead_connectors, radar_lead_track_colors
CLIP_MARGIN = 500
MIN_DRAW_DISTANCE = 10.0
@@ -42,6 +43,9 @@ class LeadVehicle:
glow: list[tuple[float, float]] = field(default_factory=list)
chevron: list[tuple[float, float]] = field(default_factory=list)
fill_alpha: int = 0
position: tuple[float, float] | None = None
radar_track_id: int = -1
radar: bool = False
class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP):
@@ -115,11 +119,12 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP):
model = sm['modelV2']
radar_state = sm['radarState'] if sm.valid['radarState'] else None
lead_one = radar_state.leadOne if radar_state else None
render_lead_indicator = self._longitudinal_control and radar_state is not None
render_lead_indicator = ui_state.radar_tracks != 0 and radar_state is not None
# Update model data when needed
model_updated = sm.updated['modelV2']
if model_updated or sm.updated['radarState'] or self._transform_dirty:
transform_updated = self._transform_dirty
if model_updated or sm.updated['radarState'] or transform_updated:
if model_updated:
self._update_raw_points(model)
@@ -136,6 +141,21 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP):
self._draw_lane_lines()
self._draw_path(sm)
if sm.valid['liveTracks'] and sm.recv_frame['liveTracks'] >= ui_state.started_frame:
if (sm.updated['liveTracks'] or sm.updated['liveCalibration'] or transform_updated or
not self.radar_tracks.projection_initialized):
self.radar_tracks.update_radar_tracks(
sm['liveTracks'], self._map_to_screen, self._path_offset_z,
)
highlighted_tracks = radar_lead_track_colors(radar_state) if render_lead_indicator else {}
matched_positions = self.radar_tracks.draw_cached_radar_tracks(
highlighted_tracks=highlighted_tracks,
)
if render_lead_indicator:
draw_radar_lead_connectors(self._lead_vehicles, matched_positions, highlighted_tracks)
else:
self.radar_tracks.clear_projection()
if render_lead_indicator and radar_state:
self._draw_lead_indicator()
self.chevron_metrics.draw_lead_status(sm, radar_state, self._rect, self._lead_vehicles)
@@ -168,7 +188,11 @@ class ModelRenderer(Widget, ChevronMetrics, ModelRendererSP):
z = self._path.raw_points[idx, 2] if idx < len(self._path.raw_points) else 0.0
point = self._map_to_screen(d_rel, -y_rel + self._camera_offset, z + self._path_offset_z)
if point:
self._lead_vehicles[i] = self._update_lead_vehicle(d_rel, v_rel, point, self._rect)
lead_vehicle = self._update_lead_vehicle(d_rel, v_rel, point, self._rect)
lead_vehicle.position = lead_vehicle.chevron[1]
lead_vehicle.radar_track_id = int(lead_data.radarTrackId)
lead_vehicle.radar = lead_data.radar
self._lead_vehicles[i] = lead_vehicle
def _update_model(self, lead, path_x_array):
"""Update model visualization data based on model message"""
+6 -21
View File
@@ -24,7 +24,7 @@ ALERT_RAMP_TIME = 4 # seconds to ramp to max volume for warningImmediate
SELFDRIVE_STATE_TIMEOUT = 5 # 5 seconds
FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES)
AMBIENT_DB = 26 # DB where MIN_VOLUME is applied
AMBIENT_DB = 24 # DB where MIN_VOLUME is applied
DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
VOLUME_BASE = 20
@@ -48,15 +48,14 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME),
AudibleAlert.refuse: ("refuse.wav", 1, MAX_VOLUME),
AudibleAlert.prompt: ("warning.wav", 1, MAX_VOLUME),
AudibleAlert.promptRepeat: ("warning.wav", None, MAX_VOLUME),
AudibleAlert.promptDistracted: ("dm_warning.wav", None, MAX_VOLUME),
AudibleAlert.prompt: ("prompt.wav", 1, MAX_VOLUME),
AudibleAlert.promptRepeat: ("prompt.wav", None, MAX_VOLUME),
AudibleAlert.promptDistracted: ("prompt_distracted.wav", None, MAX_VOLUME),
AudibleAlert.preAlert: ("pre_alert.wav", 1, MAX_VOLUME),
AudibleAlert.complete: ("complete.wav", 1, MAX_VOLUME),
AudibleAlert.warningSoft: ("critical.wav", None, MAX_VOLUME),
AudibleAlert.warningImmediate: ("dm_critical.wav", None, MAX_VOLUME),
AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
**sound_list_sp,
}
@@ -90,7 +89,6 @@ class Soundd(QuietMode):
self.ramp_start_time = 0.
self.selfdrive_timeout_alert = False
self.pending_stop = False
self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False)
@@ -129,10 +127,6 @@ class Soundd(QuietMode):
self.current_sound_frame += frames_to_write
current_sound_frame = self.current_sound_frame % len(sound_data)
loops = self.current_sound_frame // len(sound_data)
if self.pending_stop and current_sound_frame == 0:
self.current_alert = AudibleAlert.none
self.pending_stop = False
break
return ret * self.current_volume
@@ -143,15 +137,6 @@ class Soundd(QuietMode):
def update_alert(self, new_alert):
current_alert_played_once = self.current_alert == AudibleAlert.none or self.current_sound_frame >= len(self.loaded_sounds[self.current_alert])
# let looping sounds finish the current loop instead of cutting off mid tone
if new_alert == AudibleAlert.none and self.current_alert != AudibleAlert.none and sound_list[self.current_alert][1] is None:
if current_alert_played_once:
self.pending_stop = True
else:
self.current_alert = AudibleAlert.none
self.current_sound_frame = 0
return
self.pending_stop = False
if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once):
if new_alert == AudibleAlert.warningImmediate:
self.ramp_start_volume = self.current_volume
@@ -238,7 +238,7 @@ class ModelsLayout(Widget):
self.lagd_toggle.action_item.set_state(live_delay)
self.delay_control.set_visible(not live_delay and advanced_controls)
new_step = int(round(100 / CV.MPH_TO_KPH)) if ui_state.is_metric else 100
if self.lane_turn_value_control.action_item is not None and self.lane_turn_value_control.action_item.value_change_step != new_step:
if self.lane_turn_value_control.action_item.value_change_step != new_step:
self.lane_turn_value_control.action_item.value_change_step = new_step
self._update_lagd_description(live_delay)
@@ -38,8 +38,8 @@ class NetworkUISP(NetworkUI):
self.scan_button.set_text(tr("Scan"))
self.scan_button.set_enabled(True)
def _render(self, _):
super()._render(_)
def _render(self, rect: rl.Rectangle):
super()._render(rect)
if self._current_panel == PanelType.WIFI:
self.scan_button.set_position(self._rect.x, self._rect.y + 20)
@@ -37,7 +37,7 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller
OP.PANEL_COLOR = rl.Color(10, 10, 10, 255)
ICON_SIZE = 70
OP.PanelType = IntEnum( # type: ignore[assignment] # ty: ignore[invalid-assignment]
OP.PanelType = IntEnum(
"PanelType",
[es.name for es in OP.PanelType] + [
"SUNNYLINK",
@@ -180,18 +180,20 @@ class SettingsLayoutSP(OP.SettingsLayout):
self._sidebar_scroller.render(nav_rect)
return
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
def _handle_mouse_release(self, mouse_pos: MousePos) -> bool:
# Check close button
if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect):
if self._close_callback:
self._close_callback()
return
return True
# Check navigation buttons
for panel_type, panel_info in self._panels.items():
if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect) and self._sidebar_scroller.scroll_panel.is_touch_valid():
self.set_current_panel(panel_type)
return
return True
return False
def show_event(self):
super().show_event()
@@ -138,7 +138,7 @@ class ModelsLayoutMici(NavScroller):
self._show_selection_view(btns, self._show_folders)
def _reset_main_view(self):
self._scroller._items = self.main_items # type: ignore[assignment] # ty: ignore[invalid-assignment]
self._scroller._items = self.main_items
self.set_back_callback(self.original_back_callback)
self._scroller.scroll_panel.set_offset(0)
self._scroller.scroll_to(0)
@@ -16,9 +16,6 @@ class SunnylinkConsentPage(NavScroller):
def __init__(self, on_accept: Callable | None = None, on_decline: Callable | None = None):
super().__init__()
assert on_accept is not None and callable(on_accept)
assert on_decline is not None and callable(on_decline)
self._accept_button = BigConfirmationCircleButton("enable\nsunnylink", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64),
on_accept, exit_on_confirm=False)
@@ -7,6 +7,7 @@ See the LICENSE.md file in the root directory for more details.
import pyray as rl
from openpilot.selfdrive.ui.ui_state import UIStatus
from openpilot.selfdrive.ui.sunnypilot.onroad.rainbow_path import RainbowPath
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import RadarTracks
LANE_LINE_COLORS_SP = {
UIStatus.LAT_ONLY: rl.Color(0, 255, 64, 255),
@@ -17,3 +18,4 @@ LANE_LINE_COLORS_SP = {
class ModelRendererSP:
def __init__(self):
self.rainbow_path = RainbowPath()
self.radar_tracks = RadarTracks()
@@ -6,9 +6,12 @@ See the LICENSE.md file in the root directory for more details.
"""
from openpilot.selfdrive.ui.sunnypilot.onroad.chevron_metrics import ChevronMetrics
from openpilot.selfdrive.ui.sunnypilot.onroad.rainbow_path import RainbowPath
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import RadarTracks
class ModelRendererSP:
def __init__(self):
self.rainbow_path = RainbowPath()
self.chevron_metrics = ChevronMetrics()
self.radar_tracks = RadarTracks()
@@ -0,0 +1,339 @@
"""
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
This file is part of sunnypilot and is licensed under the MIT License.
See the LICENSE.md file in the root directory for more details.
"""
from dataclasses import dataclass
import math
import pyray as rl
from opendbc.car.hyundai.radar_interface import RADAR_235_248, RADAR_3A5_3C4
from openpilot.system.ui.lib.application import FontWeight
from openpilot.system.ui.widgets.label import UnifiedLabel
NEUTRAL_COLOR = (255, 255, 255)
DBC_MOVING_COLOR = (190, 125, 255)
DBC_UNKNOWN_COLOR = (154, 168, 184)
DBC_MOTION_STATIONARY = 1
DBC_MOTION_MOVING = 2
LEAD_TRACK_COLORS = (
rl.Color(255, 215, 0, 255),
rl.Color(255, 140, 0, 220),
)
@dataclass(frozen=True)
class ProjectedRadarTrack:
x: float
y: float
radius: float
color: object
source_index: int
camera_object: bool
track_id: int
def is_preferred_radar_source(source) -> bool:
return source.startAddress == RADAR_3A5_3C4.start_addr and source.endAddress == RADAR_3A5_3C4.end_addr
def radar_source_sort_key(source) -> tuple[bool, int, int, int]:
return (
not is_preferred_radar_source(source),
int(source.startAddress),
int(source.endAddress),
int(source.bus),
)
def sorted_radar_sources(live_tracks):
return sorted(live_tracks.trackSources, key=radar_source_sort_key)
def radar_track_source_index(track, sources) -> int:
address = int(track.sourceAddress)
bus = int(track.sourceBus)
if address != 0:
return next((
index for index, source in enumerate(sources)
if source.startAddress <= address <= source.endAddress and source.bus == bus
), 0)
return 0
def is_camera_object_source(source) -> bool:
return source.startAddress == RADAR_235_248.start_addr and source.endAddress == RADAR_235_248.end_addr
def radar_track_source(track, sources):
address = int(track.sourceAddress)
bus = int(track.sourceBus)
if address == 0:
return None
return next((
source for source in sources
if source.startAddress <= address <= source.endAddress and source.bus == bus
), None)
def radar_source_label(source) -> str:
prefix = "CAM " if is_camera_object_source(source) else ""
return f"{prefix}{source.startAddress:X}-{source.endAddress:X}"
def draw_radar_source_marker(center: rl.Vector2, radius: float, color: rl.Color, source_index: int,
camera_object: bool = False) -> None:
if camera_object:
rl.draw_poly(center, 3, radius, -90.0, color)
return
if source_index <= 0:
rl.draw_circle(int(center.x), int(center.y), radius, color)
return
sides = (4, 3, 5, 6)[(source_index - 1) % 4]
rotation = 45.0 if sides == 4 else -90.0
rl.draw_poly(center, sides, radius, rotation, color)
def radar_track_display(motion_state: int) -> tuple[rl.Color, bool]:
"""Color tracks exclusively from the radar's DBC motion classification."""
if motion_state == DBC_MOTION_STATIONARY:
return rl.Color(*NEUTRAL_COLOR, 255), True
if motion_state == DBC_MOTION_MOVING:
return rl.Color(*DBC_MOVING_COLOR, 255), False
return rl.Color(*DBC_UNKNOWN_COLOR, 255), False
def radar_lead_track_colors(radar_state) -> dict[int, rl.Color]:
highlighted_tracks = {}
if radar_state is None:
return highlighted_tracks
for lead, color in zip((radar_state.leadOne, radar_state.leadTwo), LEAD_TRACK_COLORS, strict=True):
if lead.present and lead.radar and lead.radarTrackId >= 0:
highlighted_tracks.setdefault(int(lead.radarTrackId), color)
return highlighted_tracks
def draw_radar_lead_connectors(lead_vehicles, matched_positions, highlighted_tracks, screen_offset=(0, 0)) -> None:
for lead in lead_vehicles:
if not lead.radar or lead.position is None or lead.radar_track_id not in matched_positions:
continue
radar_position = matched_positions[lead.radar_track_id]
lead_position = (lead.position[0] + screen_offset[0], lead.position[1] + screen_offset[1])
if math.dist(lead_position, radar_position) < 4:
continue
rl.draw_line_ex(
rl.Vector2(*lead_position), rl.Vector2(*radar_position), 2,
highlighted_tracks[lead.radar_track_id],
)
def format_radar_tracks_onroad_columns(live_tracks, v_ego: float = 0.0) -> tuple[str, str, str, str, str, str]:
sources = sorted_radar_sources(live_tracks)
if not sources:
return "", "none", "", "", "", ""
range_text = "\n".join(radar_source_label(source) for source in sources)
count_text = "\n".join(str(source.trackCount) for source in sources)
motion_states = [int(track.motionState) for track in live_tracks.points]
moving_count = sum(state == DBC_MOTION_MOVING for state in motion_states)
stationary_count = sum(state == DBC_MOTION_STATIONARY for state in motion_states)
unknown_count = len(motion_states) - moving_count - stationary_count
return range_text, count_text, str(moving_count), str(stationary_count), str(unknown_count), ""
class RadarTracksStatus:
HORIZONTAL_PADDING = 8
COLUMN_GAP = 8
SOURCE_MARKER_WIDTH = 18
def __init__(self, settings_callback=None, right_margin: int = 12):
self._settings_callback = settings_callback
self._right_margin = right_margin
self._rect = rl.Rectangle()
text_args = {
"font_size": 26,
"font_weight": FontWeight.SEMI_BOLD,
"alignment": rl.GuiTextAlignment.TEXT_ALIGN_RIGHT,
"alignment_vertical": rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP,
"wrap_text": False,
}
self._labels = (
UnifiedLabel("", text_color=rl.Color(0, 255, 64, 255), **text_args),
UnifiedLabel("none", text_color=rl.Color(0, 255, 64, 255), **text_args),
UnifiedLabel("", text_color=rl.Color(*DBC_MOVING_COLOR, 255), **text_args),
UnifiedLabel("", text_color=rl.Color(*NEUTRAL_COLOR, 255), **text_args),
UnifiedLabel("", text_color=rl.Color(*DBC_UNKNOWN_COLOR, 255), **text_args),
UnifiedLabel("", text_color=rl.Color(*DBC_UNKNOWN_COLOR, 255), **text_args),
)
self._status = ("", "none", "", "", "", "")
self._status_colors: tuple[tuple[int, int, int], ...] = ()
self._source_count = 0
self._layout_key: tuple[str, str, str, str, str, str, int] | None = None
self._column_widths = [0, 36, 0, 0, 0, 0]
self._width = 52
self._height = 42
def update(self, live_tracks, valid: bool, radar_mode: int, v_ego: float = 0.0) -> None:
if live_tracks.radarTracksAvailable and radar_mode != 2:
status = ("", "radar detected\ntap to enable", "", "", "", "")
status_colors = ()
source_count = 0
else:
status = format_radar_tracks_onroad_columns(live_tracks, v_ego) if valid else ("", "none", "", "", "", "")
status_colors = (DBC_MOVING_COLOR, NEUTRAL_COLOR, DBC_UNKNOWN_COLOR, DBC_UNKNOWN_COLOR)
source_count = len(live_tracks.trackSources) if valid else 0
self._set_status(status, status_colors, source_count)
def reset(self) -> None:
self._set_status(("", "none", "", "", "", ""), (), 0)
def handle_mouse(self, mouse_pos) -> bool:
if self._settings_callback is None or not rl.check_collision_point_rec(mouse_pos, self._rect):
return False
self._settings_callback()
return True
def render(self, content_rect: rl.Rectangle) -> None:
self._update_layout(int(content_rect.width - 40))
self._rect = rl.Rectangle(
content_rect.x + content_rect.width - self._width - self._right_margin,
content_rect.y + 8,
self._width,
self._height,
)
rl.draw_rectangle_rounded(self._rect, 0.5, 8, rl.Color(0, 0, 0, 170))
x = self._rect.x + self.HORIZONTAL_PADDING
active_columns = [
(column_index, label, width)
for column_index, (label, width) in enumerate(zip(self._labels, self._column_widths, strict=True))
if width
]
for active_index, (column_index, label, width) in enumerate(active_columns):
label_x = x
label_width = width
if column_index == 0 and self._source_count:
marker_height = (self._rect.height - 10) / self._source_count
for source_index in range(self._source_count):
draw_radar_source_marker(
rl.Vector2(x + 6, self._rect.y + 5 + marker_height * (source_index + 0.5)),
5.0, rl.Color(0, 255, 64, 255), source_index,
)
label_x += self.SOURCE_MARKER_WIDTH
label_width -= self.SOURCE_MARKER_WIDTH
label.render(rl.Rectangle(label_x, self._rect.y + 5, label_width, self._rect.height - 10))
x += width + (self.COLUMN_GAP if active_index < len(active_columns) - 1 else 0)
def _set_status(self, status: tuple[str, str, str, str, str, str],
status_colors: tuple[tuple[int, int, int], ...], source_count: int) -> None:
if status == self._status and status_colors == self._status_colors and source_count == self._source_count:
return
self._status = status
self._status_colors = status_colors
self._source_count = source_count
for label, text in zip(self._labels, status, strict=True):
label.set_text(text)
for label, color in zip(self._labels[2:], status_colors, strict=False):
label.set_text_color(rl.Color(*color, 255))
self._layout_key = None
def _update_layout(self, max_inner_width: int) -> None:
layout_key = (*self._status, max_inner_width)
if layout_key == self._layout_key:
return
for label in self._labels:
label.get_content_height(max_inner_width)
self._column_widths = [
math.ceil(label.text_width) if text else 0
for label, text in zip(self._labels, self._status, strict=True)
]
if self._column_widths[0] and self._source_count:
self._column_widths[0] += self.SOURCE_MARKER_WIDTH
self._column_widths[1] = max(36, self._column_widths[1])
for index in (2, 3, 4):
if self._column_widths[index]:
self._column_widths[index] = max(36, self._column_widths[index])
active_widths = [width for width in self._column_widths if width]
inner_width = sum(active_widths) + self.COLUMN_GAP * (len(active_widths) - 1)
self._width = inner_width + self.HORIZONTAL_PADDING * 2
self._height = max(
42,
*(label.get_content_height(max(width, 1)) + 10
for label, width in zip(self._labels, self._column_widths, strict=True) if width),
)
self._layout_key = layout_key
class RadarTracks:
def __init__(self):
self._projected_tracks: tuple[ProjectedRadarTrack, ...] = ()
self._projection_initialized = False
@property
def projection_initialized(self) -> bool:
return self._projection_initialized
def clear_projection(self) -> None:
self._projected_tracks = ()
self._projection_initialized = False
def update_radar_tracks(self, live_tracks, map_to_screen, path_offset_z, track_size=7) -> None:
projected_tracks = []
sources = sorted_radar_sources(live_tracks)
for track in live_tracks.points:
d_rel, y_rel, v_rel = track.dRel, track.yRel, track.vRel
if not (math.isfinite(d_rel) and math.isfinite(y_rel) and math.isfinite(v_rel)):
continue
motion_state = int(track.motionState)
if motion_state not in (DBC_MOTION_STATIONARY, DBC_MOTION_MOVING):
continue
pt = map_to_screen(d_rel, -y_rel, path_offset_z)
if pt is None:
continue
color, stationary = radar_track_display(motion_state)
radius = max(1, track_size - 5) if stationary else track_size
source = radar_track_source(track, sources)
projected_tracks.append(ProjectedRadarTrack(
x=pt[0],
y=pt[1],
radius=radius,
color=color,
source_index=radar_track_source_index(track, sources),
camera_object=source is not None and is_camera_object_source(source),
track_id=int(track.trackId),
))
self._projected_tracks = tuple(projected_tracks)
self._projection_initialized = True
def draw_cached_radar_tracks(self, screen_offset=(0, 0), highlighted_tracks=None):
highlighted_tracks = highlighted_tracks or {}
highlighted_positions = {}
for track in self._projected_tracks:
x, y = track.x + screen_offset[0], track.y + screen_offset[1]
highlight_color = highlighted_tracks.get(track.track_id)
if highlight_color is not None:
center = rl.Vector2(int(x), int(y))
rl.draw_ring(center, track.radius + 2, track.radius + 5, 0, 360, 24, highlight_color)
highlighted_positions[track.track_id] = (x, y)
draw_radar_source_marker(
rl.Vector2(x, y), track.radius, track.color, track.source_index, track.camera_object,
)
return highlighted_positions
def draw_radar_tracks(self, live_tracks, map_to_screen, path_offset_z, track_size=7, screen_offset=(0, 0), v_ego=0.0,
highlighted_tracks=None):
self.update_radar_tracks(live_tracks, map_to_screen, path_offset_z, track_size)
return self.draw_cached_radar_tracks(screen_offset, highlighted_tracks)
@@ -198,7 +198,7 @@ class SpeedLimitRenderer(Widget, SpeedLimitAlertRenderer):
self._draw_ahead_info(sign_rect)
def _draw_sign_main(self, rect, alpha=1.0):
speed_limit_warning_enabled = ui_state.speed_limit_mode is not None and ui_state.speed_limit_mode >= SpeedLimitMode.warning
speed_limit_warning_enabled = ui_state.speed_limit_mode >= SpeedLimitMode.warning
has_limit = self.speed_limit_valid or self.speed_limit_last_valid
is_overspeed = has_limit and round(self.speed_limit_final_last) < round(self.speed)
@@ -0,0 +1,324 @@
from types import SimpleNamespace
from opendbc.car.structs import car
from openpilot.selfdrive.ui.sunnypilot.onroad import radar_tracks
from openpilot.selfdrive.ui.sunnypilot.onroad.radar_tracks import draw_radar_lead_connectors, format_radar_tracks_onroad_columns, \
radar_lead_track_colors, radar_track_display
def color_tuple(color):
return color.r, color.g, color.b, color.a
def test_dbc_motion_colors():
assert color_tuple(radar_track_display(2)[0]) == (190, 125, 255, 255)
assert not radar_track_display(2)[1]
assert color_tuple(radar_track_display(1)[0]) == (255, 255, 255, 255)
assert radar_track_display(1)[1]
def test_unknown_dbc_motion_uses_neutral_dbc_color():
color, stationary = radar_track_display(0)
assert color_tuple(color) == (*radar_tracks.DBC_UNKNOWN_COLOR, 255)
assert not stationary
def test_radar_lead_track_colors_only_highlight_radar_matches():
radar_state = SimpleNamespace(
leadOne=SimpleNamespace(present=True, radar=True, radarTrackId=7),
leadTwo=SimpleNamespace(present=True, radar=False, radarTrackId=9),
)
colors = radar_lead_track_colors(radar_state)
assert list(colors) == [7]
assert color_tuple(colors[7]) == color_tuple(radar_tracks.LEAD_TRACK_COLORS[0])
def test_draw_radar_lead_connectors_applies_screen_offset(monkeypatch):
lead = SimpleNamespace(radar=True, position=(10, 20), radar_track_id=7)
color = radar_tracks.LEAD_TRACK_COLORS[0]
drawn = []
monkeypatch.setattr(
radar_tracks.rl, "draw_line_ex",
lambda start, end, width, line_color: drawn.append(
((start.x, start.y), (end.x, end.y), width, color_tuple(line_color))
),
)
draw_radar_lead_connectors(
[lead], {7: (120, 30)}, {7: color}, screen_offset=(100, 5),
)
assert drawn == [((110, 25), (120, 30), 2, color_tuple(color))]
def test_format_radar_tracks_columns_none():
live_tracks = car.RadarData.new_message()
assert format_radar_tracks_onroad_columns(live_tracks) == ("", "none", "", "", "", "")
def test_format_radar_tracks_columns_range_and_count():
live_tracks = car.RadarData.new_message()
live_tracks.trackSources = [{"startAddress": 0x3A5, "endAddress": 0x3C4, "bus": 1, "trackCount": 2}]
points = live_tracks.init("points", 2)
points[0].motionState = 2
points[1].motionState = 1
assert format_radar_tracks_onroad_columns(live_tracks) == ("3A5-3C4", "2", "1", "1", "0", "")
def test_format_camera_objects_are_not_labeled_as_radar():
live_tracks = car.RadarData.new_message()
live_tracks.trackSources = [{"startAddress": 0x235, "endAddress": 0x248, "bus": 1, "trackCount": 3}]
points = live_tracks.init("points", 3)
for point in points:
point.motionState = 2
assert format_radar_tracks_onroad_columns(live_tracks) == ("CAM 235-248", "3", "3", "0", "0", "")
def test_format_radar_tracks_columns_stacks_all_ranges_with_preferred_first():
live_tracks = car.RadarData.new_message()
live_tracks.trackSources = [
{"startAddress": 0x500, "endAddress": 0x51F, "bus": 2, "trackCount": 3},
{"startAddress": 0x3A5, "endAddress": 0x3C4, "bus": 1, "trackCount": 2},
]
points = live_tracks.init("points", 3)
points[0].motionState = 2
points[0].sourceAddress = 0x3A5
points[1].motionState = 1
points[1].sourceAddress = 0x3A6
points[2].motionState = 0
points[2].sourceAddress = 0x500
assert format_radar_tracks_onroad_columns(live_tracks) == (
"3A5-3C4\n500-51F",
"2\n3",
"1",
"1",
"1",
"",
)
def test_format_radar_tracks_columns_shows_non_motion_source():
live_tracks = car.RadarData.new_message()
live_tracks.trackSources = [{"startAddress": 0x500, "endAddress": 0x51F, "bus": 1, "trackCount": 4}]
points = live_tracks.init("points", 4)
for point, v_rel in zip(points, (-5.0, 0.2, -20.0, 5.0), strict=True):
point.vRel = v_rel
point.motionState = 0
point.sourceAddress = 0x500
assert format_radar_tracks_onroad_columns(live_tracks, v_ego=20.0) == ("500-51F", "4", "0", "0", "4", "")
def test_format_radar_tracks_columns_shows_64_track_source():
live_tracks = car.RadarData.new_message()
live_tracks.trackSources = [{"startAddress": 0x500, "endAddress": 0x53F, "bus": 1, "trackCount": 7}]
assert format_radar_tracks_onroad_columns(live_tracks) == ("500-53F", "7", "0", "0", "0", "")
def test_draw_radar_tracks_applies_screen_offset(monkeypatch):
live_tracks = car.RadarData.new_message()
points = live_tracks.init("points", 1)
points[0].dRel = 10
points[0].yRel = 1
points[0].vRel = 2
points[0].motionState = radar_tracks.DBC_MOTION_MOVING
drawn_circles = []
monkeypatch.setattr(radar_tracks.rl, "draw_circle", lambda x, y, size, color: drawn_circles.append((x, y, size)))
radar_tracks.RadarTracks().draw_radar_tracks(
live_tracks,
lambda d_rel, y_rel, z: (20, 30),
path_offset_z=1.2,
track_size=3,
screen_offset=(100, 7),
)
assert drawn_circles == [(120, 37, 3)]
def test_draw_radar_tracks_hides_unknown_motion(monkeypatch):
live_tracks = car.RadarData.new_message()
point = live_tracks.init("points", 1)[0]
point.dRel = 10
point.yRel = 1
point.vRel = -5
point.motionState = 0
drawn_colors = []
monkeypatch.setattr(radar_tracks.rl, "draw_circle", lambda x, y, size, color: drawn_colors.append(color_tuple(color)))
radar_tracks.RadarTracks().draw_radar_tracks(live_tracks, lambda d_rel, y_rel, z: (20, 30), path_offset_z=1.2)
assert drawn_colors == []
def test_draw_radar_tracks_hides_unknown_motion_from_other_source(monkeypatch):
live_tracks = car.RadarData.new_message()
point = live_tracks.init("points", 1)[0]
point.dRel = 10
point.yRel = 1
point.vRel = -5
point.motionState = 0
point.sourceAddress = 0x500
drawn_circles = []
monkeypatch.setattr(radar_tracks.rl, "draw_circle", lambda *args: drawn_circles.append(args))
radar_tracks.RadarTracks().draw_radar_tracks(
live_tracks, lambda d_rel, y_rel, z: (20, 30), path_offset_z=1.2,
)
assert drawn_circles == []
def test_draw_radar_tracks_uses_source_shapes_with_preferred_circle(monkeypatch):
live_tracks = car.RadarData.new_message()
live_tracks.trackSources = [
{"startAddress": 0x500, "endAddress": 0x51F, "bus": 1, "trackCount": 1},
{"startAddress": 0x3A5, "endAddress": 0x3C4, "bus": 1, "trackCount": 1},
]
points = live_tracks.init("points", 2)
for point, address in zip(points, (0x500, 0x3A5), strict=True):
point.dRel = address
point.yRel = 1
point.vRel = 2
point.motionState = radar_tracks.DBC_MOTION_MOVING
point.sourceAddress = address
point.sourceBus = 1
circles = []
polygons = []
monkeypatch.setattr(radar_tracks.rl, "draw_circle", lambda x, y, radius, color: circles.append((x, radius)))
monkeypatch.setattr(
radar_tracks.rl, "draw_poly",
lambda center, sides, radius, rotation, color: polygons.append((center.x, sides, radius, rotation)),
)
radar_tracks.RadarTracks().draw_radar_tracks(
live_tracks, lambda d_rel, y_rel, z: (d_rel, 30), path_offset_z=1.2, track_size=6,
)
assert circles == [(0x3A5, 6)]
assert polygons == [(0x500, 4, 6, 45.0)]
def test_draw_camera_objects_uses_triangle(monkeypatch):
live_tracks = car.RadarData.new_message()
live_tracks.trackSources = [{"startAddress": 0x235, "endAddress": 0x248, "bus": 1, "trackCount": 1}]
point = live_tracks.init("points", 1)[0]
point.dRel = 25
point.yRel = 1
point.vRel = 2
point.motionState = radar_tracks.DBC_MOTION_MOVING
point.sourceAddress = 0x235
point.sourceBus = 1
polygons = []
monkeypatch.setattr(
radar_tracks.rl, "draw_poly",
lambda center, sides, radius, rotation, color: polygons.append((center.x, sides, radius, rotation)),
)
radar_tracks.RadarTracks().draw_radar_tracks(
live_tracks, lambda d_rel, y_rel, z: (d_rel, 30), path_offset_z=1.2, track_size=6,
)
assert polygons == [(25, 3, 6, -90.0)]
def test_draw_radar_tracks_shrinks_stationary_dots(monkeypatch):
live_tracks = car.RadarData.new_message()
point = live_tracks.init("points", 1)[0]
point.dRel = 10
point.yRel = 1
point.vRel = -20
point.motionState = 1
drawn_sizes = []
monkeypatch.setattr(radar_tracks.rl, "draw_circle", lambda x, y, size, color: drawn_sizes.append(size))
radar_tracks.RadarTracks().draw_radar_tracks(
live_tracks, lambda d_rel, y_rel, z: (20, 30), path_offset_z=1.2, track_size=6, v_ego=20,
)
assert drawn_sizes == [1]
def test_draw_radar_tracks_keeps_matched_speed_dots_large(monkeypatch):
live_tracks = car.RadarData.new_message()
point = live_tracks.init("points", 1)[0]
point.dRel = 10
point.yRel = 1
point.vRel = 0.5
point.motionState = radar_tracks.DBC_MOTION_MOVING
drawn_sizes = []
monkeypatch.setattr(radar_tracks.rl, "draw_circle", lambda x, y, size, color: drawn_sizes.append(size))
radar_tracks.RadarTracks().draw_radar_tracks(
live_tracks, lambda d_rel, y_rel, z: (20, 30), path_offset_z=1.2, track_size=6, v_ego=20,
)
assert drawn_sizes == [6]
def test_draw_radar_tracks_highlights_and_returns_matched_track(monkeypatch):
live_tracks = car.RadarData.new_message()
points = live_tracks.init("points", 2)
for track_id, point in enumerate(points, start=10):
point.trackId = track_id
point.dRel = track_id
point.yRel = 1
point.vRel = 2
point.motionState = radar_tracks.DBC_MOTION_MOVING
highlight_color = radar_tracks.rl.Color(255, 215, 0, 255)
drawn_rings = []
monkeypatch.setattr(radar_tracks.rl, "draw_circle", lambda *args: None)
monkeypatch.setattr(
radar_tracks.rl,
"draw_ring",
lambda center, inner, outer, start, end, segments, color: drawn_rings.append(
((center.x, center.y), inner, outer, color_tuple(color))
),
)
matched_positions = radar_tracks.RadarTracks().draw_radar_tracks(
live_tracks, lambda d_rel, y_rel, z: (d_rel, 30), path_offset_z=1.2,
screen_offset=(100, 7), highlighted_tracks={11: highlight_color},
)
assert drawn_rings == [((111, 37), 9, 12, (255, 215, 0, 255))]
assert matched_positions == {11: (111, 37)}
def test_cached_radar_tracks_only_reproject_on_update(monkeypatch):
live_tracks = car.RadarData.new_message()
point = live_tracks.init("points", 1)[0]
point.trackId = 7
point.dRel = 10
point.yRel = 1
point.vRel = 0
point.motionState = radar_tracks.DBC_MOTION_MOVING
projected = []
drawn = []
def map_to_screen(d_rel, y_rel, z):
projected.append((d_rel, y_rel, z))
return (20, 30)
monkeypatch.setattr(
radar_tracks.rl, "draw_circle",
lambda x, y, radius, color: drawn.append((x, y)),
)
renderer = radar_tracks.RadarTracks()
renderer.update_radar_tracks(live_tracks, map_to_screen, path_offset_z=1.2)
renderer.draw_cached_radar_tracks(screen_offset=(100, 7))
renderer.draw_cached_radar_tracks(screen_offset=(200, 9))
assert projected == [(10, -1, 1.2)]
assert drawn == [(120, 37), (220, 39)]
@@ -44,6 +44,7 @@ class UIStateSP:
self.custom_interactive_timeout: int = 0
self.developer_ui = None
self.hide_v_ego_ui: bool = False
self.radar_tracks: int = self.params.get("RadarTracks", return_default=True)
self.onroad_brightness: int = 0
self.onroad_brightness_timer: int = 0
self.onroad_brightness_timer_param: int = 0
@@ -152,6 +153,7 @@ class UIStateSP:
self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True)
self.developer_ui = self.params.get("DevUIInfo")
self.hide_v_ego_ui = self.params.get_bool("HideVEgoUI")
self.radar_tracks = self.params.get("RadarTracks", return_default=True)
self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True)))
self.onroad_brightness_timer_param = self.params.get("OnroadScreenOffTimer", return_default=True)
self.rainbow_path = self.params.get_bool("RainbowMode")
@@ -224,7 +226,8 @@ class UIStateSP:
class DeviceSP:
def _set_awake(self, on: bool, _ui_state=None):
@staticmethod
def _set_awake(on: bool, _ui_state):
if _ui_state.boot_offroad_mode == 1 and not on:
_ui_state.params.put_bool("OffroadMode", True)
@@ -357,7 +357,7 @@ def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None:
params = Params()
main_layout._alerts_layout._pending_params = ({"UpdaterNewDescription": params.get("UpdaterNewDescription")} |
{alert_data.key: params.get(alert_data.key) for alert_data in main_layout._alerts_layout.sorted_alerts})
main_layout._alerts_layout._update_state()
main_layout._alerts_layout._refresh()
swipe_right(width, wait_after=WAIT_SHORT) # open alerts
script.setup(setup_offroad_alerts_and_refresh) # show alerts

Some files were not shown because too many files have changed in this diff Show More