Compare commits

..

312 Commits

Author SHA1 Message Date
firestar5683 f7d982fbb4 Connect Galaxy live RoadScore controls to automatic playback 2026-09-22 21:46:19 -05:00
firestar5683 1dfbcffe82 Seek local demo video and music together from timeline slider 2026-09-22 21:09:34 -05:00
firestar5683 407f783a38 Add local Mac demo shortcut and simplify unpaired controls 2026-09-22 21:03:38 -05:00
firestar5683 e9e50cfeb1 Avoid Mac replay and UI publisher port collisions 2026-09-20 13:59:28 -07:00
firestar5683 dda425f7c6 Keep native demo playing through Galaxy status timeouts 2026-09-20 13:28:17 -07:00
firestar5683 9bb2802732 Document separate gold route5 option and audio-first playback 2026-09-20 13:18:12 -07:00
firestar5683 a8e95bd4c7 Add explicit staged music over a verified saved replay 2026-09-20 13:14:17 -07:00
firestar5683 4010fabe8c Let healthy comma audio finish after paired Mac replay exits 2026-09-20 13:13:39 -07:00
firestar5683 cc96be16f6 Give prepared native Bluetooth playback more output headroom 2026-09-20 13:03:55 -07:00
firestar5683 c7e50791b2 Clarify native playback priming and one-way Mac following 2026-09-20 13:03:14 -07:00
firestar5683 920b7af296 Compact exported cue history while preserving audible selection 2026-09-20 13:01:42 -07:00
firestar5683 87c871ca07 Capture saved replay screens only for explicit mirror mode 2026-09-20 12:54:42 -07:00
firestar5683 054d204d4f Record prepared playback callback timing and source clock failures 2026-09-20 12:53:19 -07:00
firestar5683 41a8673e8d Prime saved native replay before starting its source clock 2026-09-20 12:52:10 -07:00
firestar5683 ebfc9d55f0 Clarify audience-facing RoadScore reaction labels 2026-09-20 12:51:50 -07:00
firestar5683 fdd92bc6d1 Document additional saved routes and validation limits 2026-09-20 12:28:53 -07:00
firestar5683 c4732e7f6a Restore recorded turn icon after replay signal override 2026-09-20 12:20:31 -07:00
firestar5683 75c2a8b8a1 Drain verified prepared PCM tails after terminal replay model 2026-09-20 12:08:03 -07:00
firestar5683 f2dbf78a75 Validate final prepared clock residual after bounded recovery 2026-09-20 12:02:35 -07:00
firestar5683 2deecbe21f Finish prepared audio crossfade before bounded DAC correction 2026-09-20 12:01:08 -07:00
firestar5683 3bd12e9262 Document saved demo launch and manual signal override behavior 2026-09-20 11:59:15 -07:00
firestar5683 c883e70ed1 Let manual demo signals replace recorded turn guidance and pulses 2026-09-20 11:57:27 -07:00
firestar5683 1ccc3d7be2 Crossfade bounded Bluetooth DAC estimate reversals in saved playback 2026-09-20 11:54:23 -07:00
firestar5683 562833fe5f Check local replay ownership before starting a paired hardware session 2026-09-20 11:45:56 -07:00
firestar5683 a4f33b01d8 Reserve Mac replay ownership before showcase preparation 2026-09-20 11:45:54 -07:00
firestar5683 4a47400e68 Record complete paired playback and fullscreen validation limits 2026-09-20 11:42:30 -07:00
firestar5683 e076480db8 Document fullscreen controls for native Mac showcase 2026-09-20 11:39:48 -07:00
firestar5683 7fba9b7855 Expose fullscreen on the paired native demo command 2026-09-20 11:38:49 -07:00
firestar5683 fa8c0501b4 Add isolated fullscreen Mac showcase with native aspect ratio 2026-09-20 11:38:30 -07:00
firestar5683 eeffb8ed62 Allow Bluetooth DAC estimate settling during silent clock calibration 2026-09-20 11:34:40 -07:00
firestar5683 f0950133f6 Accept advancing DAC batches with repeated CoreAudio current timestamps 2026-09-20 11:31:09 -07:00
firestar5683 2ff7ed501c Calibrate prepared audio from silent callback timestamps 2026-09-20 11:29:45 -07:00
firestar5683 2bbee419f2 Calibrate native output clock from advancing silent pre-roll callbacks 2026-09-20 11:28:35 -07:00
firestar5683 9f58b3acdd Describe independent native Mac replay and Galaxy synchronization 2026-09-20 11:26:01 -07:00
firestar5683 018283e9b1 Hold and follow isolated muted Mac replay with stable output clock 2026-09-20 11:25:11 -07:00
firestar5683 529047a829 Validate paired route identity and allow owned Mac cleanup to finish 2026-09-20 11:24:17 -07:00
firestar5683 1c76f4916f Coordinate native saved replay on Mac and comma without browser mirroring 2026-09-20 11:23:27 -07:00
firestar5683 c9f102ee47 Anchor output DAC time to a measured stable stream clock 2026-09-20 11:22:59 -07:00
firestar5683 fb43bca7fb Finish owned native demo cleanup despite repeated stop signals 2026-09-20 11:21:25 -07:00
firestar5683 a9698a88be Document validated saved showcase and independent fallback 2026-09-20 11:17:53 -07:00
firestar5683 0d8314fb2b Use native turn prompt wording in replay display 2026-09-20 11:17:14 -07:00
firestar5683 9bad642cae Add bounded fresh-plan Mac song auditions 2026-09-20 11:09:06 -07:00
firestar5683 0fbe1da65e Reserve output while a saved demo is playing 2026-09-20 11:09:06 -07:00
firestar5683 a64c8a7849 Make signal motifs clear and retain existing Galaxy readiness contract 2026-09-20 11:08:20 -07:00
firestar5683 2abbb8d7ed Make optional demo signal motif clearer without bypassing beat checks 2026-09-20 11:07:48 -07:00
firestar5683 71bbdb089f Launch saved paired demo with actual screen mirror and preserve signal cues 2026-09-20 11:06:58 -07:00
firestar5683 8824508b69 Capture opt-in native replay screen on bounded JPEG worker 2026-09-20 11:05:51 -07:00
firestar5683 357db29eb4 Recover bounded prepared audio clock stalls with a short crossfade 2026-09-20 11:00:16 -07:00
firestar5683 93a5378533 Reuse prepared core replay on comma without model startup 2026-09-20 11:00:16 -07:00
firestar5683 0f52f4c70e Guard followed route identity and expose current prepared playhead 2026-09-20 11:00:16 -07:00
firestar5683 0c3a94db23 Follow fresh Galaxy replay controls on the prepared Mac demo 2026-09-20 11:00:16 -07:00
firestar5683 c20f6760d8 Add session-owned saved demo launch and playhead status in Galaxy 2026-09-20 10:55:42 -07:00
firestar5683 17debc8057 Remember explicit paired demo controls and local page port 2026-09-20 10:50:08 -07:00
firestar5683 0fa28bcda1 Deepen disengaged RoadScore containment slightly 2026-09-20 10:44:01 -07:00
firestar5683 99bc1e75ea Place RoadScore badge between native speed groups 2026-09-20 10:35:47 -07:00
firestar5683 0dec20423e Add opt-in paired comma controls to prepared Mac showcase 2026-09-20 10:35:47 -07:00
firestar5683 ebbe0366e6 Verify staged curve cut survives the full presentation stack 2026-09-20 10:34:45 -07:00
firestar5683 02ef51119e Connect prepared Mac showcase and scheduled curve cut-drop treatment 2026-09-20 10:25:55 -07:00
firestar5683 f7f9cf047c Add opt-in asynchronous replay control forwarding helper 2026-09-20 10:23:35 -07:00
firestar5683 4de3e304c0 Record prepared showcase startup and presentation evidence 2026-09-20 10:19:16 -07:00
firestar5683 4d4f1066f6 Add opt-in beat-locked curve build cut and drop presentation 2026-09-20 10:19:16 -07:00
firestar5683 0a4f37fe07 Add interactive local Mac showcase from prepared ACE core 2026-09-20 10:18:02 -07:00
firestar5683 d8d619e420 Stage a measured showcase curve build with a musical bass return 2026-09-20 10:10:02 -07:00
firestar5683 85cd88056b Make replay signal cues audible and strengthen engagement contrast 2026-09-20 10:02:00 -07:00
firestar5683 4b3d4fd5dd Show native steer prompts for isolated replay signal simulation 2026-09-20 10:01:59 -07:00
firestar5683 cd789bb0d0 Allow bounded batches of showcase seed auditions 2026-09-20 09:56:47 -07:00
firestar5683 ac5a4c871c Add verified Mac seed auditions for the native showcase bank 2026-09-20 09:48:55 -07:00
firestar5683 f2ca259cbf Update operator guide for native replay engagement and signal controls 2026-09-20 09:43:31 -07:00
firestar5683 1a3f4bde4e Drive native replay turn arrows and clarify independent simulation resets 2026-09-20 09:40:28 -07:00
firestar5683 a0b239ff38 Connect replay demo engagement and signals to native UI and music 2026-09-20 09:37:21 -07:00
firestar5683 fd33d57163 Keep resident workers from retaining native session lock 2026-09-20 09:30:35 -07:00
firestar5683 0c17953c51 Add independent replay signal controls alongside engagement simulation 2026-09-20 09:26:56 -07:00
firestar5683 366598f66f Document the staged single-route showcase and presenter flow 2026-09-20 09:08:33 -07:00
firestar5683 3935e397c8 Refresh replay demo controls and restore bench device address 2026-09-19 22:33:44 -07:00
firestar5683 aaf5e7208b Add session-bound replay-only musical engagement controls 2026-09-19 22:33:38 -07:00
firestar5683 1c35cd521d Publish replay engagement commands atomically to the audio callback 2026-09-19 22:32:33 -07:00
firestar5683 67445fc009 Add session-scoped replay-only musical engagement simulation 2026-09-19 22:30:46 -07:00
firestar5683 7e2df0f81c Collect actual passive startup flags for observer policy 2026-09-19 22:05:45 -07:00
firestar5683 0f35039b27 Bind passive observer health to real nonactuating evidence and mode identity 2026-09-19 22:05:45 -07:00
firestar5683 803de7b645 Recognize verified passive ELM327 diagnostic startup without waiving faults 2026-09-19 22:05:45 -07:00
firestar5683 bd572108ab Add pinned nonactuating passive observer evidence policy 2026-09-19 22:01:08 -07:00
firestar5683 31be93b22c Accept native false Params values in live model placement check 2026-09-19 21:53:52 -07:00
firestar5683 b108b958bd Update event comma address for the live car connection 2026-09-19 21:46:14 -07:00
firestar5683 4bf6623187 Compare parked evidence timestamps against the cereal boot clock 2026-09-19 21:36:02 -07:00
firestar5683 c24b04aa69 Add bounded read-only parked car evidence collector 2026-09-19 21:36:02 -07:00
firestar5683 95c38b7ace Require modeld runtime GPU flags for local driving placement 2026-09-19 21:32:35 -07:00
firestar5683 3a673caa2a Collect read-only live car health against explicit measured baseline 2026-09-19 21:32:35 -07:00
firestar5683 4e5de2d4d4 Require fresh live playback and explicit verified diagnostic promotion 2026-09-19 21:28:49 -07:00
firestar5683 927245b9c3 Implement persistent live target lifecycle and bounded parked diagnostic 2026-09-19 21:28:49 -07:00
firestar5683 138da36707 Require fresh valid live inputs before starting RoadScore audio 2026-09-19 21:26:23 -07:00
firestar5683 f4ae6f1bf0 Connect Galaxy live toggle to gated owned service with unconditional stop 2026-09-19 21:18:20 -07:00
firestar5683 b24ef8aa6f Keep live playback state unknown when supervisor status fails 2026-09-19 21:18:00 -07:00
firestar5683 ec380c1e59 Add fail-closed live supervisor policy and Galaxy controller boundary 2026-09-19 21:17:25 -07:00
firestar5683 87bc35bc5b Invalidate stale curve payoff labels and process waiting blocks once 2026-09-19 21:06:37 -07:00
firestar5683 87145f595b Integrate live curve contrast and audible reaction labels 2026-09-19 21:06:07 -07:00
firestar5683 270169eedb Gate short startup tests with verified resident buffer handoff 2026-09-19 21:05:06 -07:00
firestar5683 9c7582d73d Add causal curve contrast and shorten warning quantization delay 2026-09-19 21:05:06 -07:00
firestar5683 1f2356b0bc Bound stronger signal shaker to available output headroom 2026-09-19 20:45:23 -07:00
firestar5683 b86a1b9475 Give meaningful warnings bounded beat-locked fill priority 2026-09-19 20:45:05 -07:00
firestar5683 84a3887a34 Prioritize meaningful alerts and enable accepted stop presentation in normal replay 2026-09-19 20:45:05 -07:00
firestar5683 a653ed1c33 Hide lead metrics and stopped timer only in RoadScore replay UI 2026-09-19 20:41:46 -07:00
firestar5683 8c62473e4f Anchor visual music cues to rendered DAC time and version residual offsets 2026-09-19 20:29:36 -07:00
firestar5683 0db08c6c33 Schedule native music cues on audible timestamps with fast atomic status relay 2026-09-19 20:29:36 -07:00
firestar5683 c2da7800fa Render road-video comparisons for stopped-motion presentation 2026-09-19 20:25:09 -07:00
firestar5683 060bf034e2 Treat tiny signed speed noise as stopped during motion presentation 2026-09-19 20:23:09 -07:00
firestar5683 dcab5ca96d Expose stopped-motion candidate through normal presentation selection 2026-09-19 20:15:13 -07:00
firestar5683 c7863cc1d0 Keep native replay display visible through audio drain 2026-09-19 20:14:40 -07:00
firestar5683 cf3042bd1c Publish audio drain completion before replay UI teardown 2026-09-19 20:14:03 -07:00
firestar5683 eb1decf8df Add optional causal stopped-to-moving low-pass lift 2026-09-19 20:13:17 -07:00
firestar5683 e238891d4b Wire stopped-motion presentation into optional normal runtime policy 2026-09-19 20:13:16 -07:00
firestar5683 25311001a9 Avoid visual beat bias during rhythmic audio measurement 2026-09-19 19:50:13 -07:00
firestar5683 4108aae3e8 Refine coarse Bluetooth offset with branch-aware rhythmic taps 2026-09-19 19:49:36 -07:00
firestar5683 af16e5290a Guide coarse chime placement followed by rhythmic timing refinement 2026-09-19 19:48:11 -07:00
firestar5683 ae87804add Extend timing alignment test to three measures 2026-09-19 19:46:40 -07:00
firestar5683 a134503c61 Require complete marker sequence before calibration calculation 2026-09-19 19:39:00 -07:00
firestar5683 5f8c535bc1 Measure Bluetooth tap offsets with isolated irregular markers 2026-09-19 19:38:35 -07:00
firestar5683 2c7f4992bb Guide isolated chime calibration and calculate completed taps automatically 2026-09-19 19:37:23 -07:00
firestar5683 e5e97e7720 Distinguish calibration downbeats with octave-separated clicks 2026-09-19 19:21:50 -07:00
firestar5683 53ccae81d8 Keep calibration taps responsive with direct real Params safety reads 2026-09-19 19:12:11 -07:00
firestar5683 c08dce7dc8 Preserve calibration child failures and expose bounded diagnostics to Galaxy 2026-09-19 19:12:11 -07:00
firestar5683 be635accd4 Keep calibration clock synchronization free of blocking safety checks 2026-09-19 19:05:39 -07:00
firestar5683 c08eb1dc96 Preserve same-origin RoadScore controls through Galaxy tunnel 2026-09-19 18:48:27 -07:00
firestar5683 784a75012f Add two-bar metronome count-in and fullscreen touch or Space calibration 2026-09-19 18:37:11 -07:00
firestar5683 a3031c6d11 Record user accepted demo and preserve baseline before ending changes 2026-09-19 18:22:57 -07:00
firestar5683 c6ac0d4a27 Read Galaxy status from verified current resident ACE worker identity 2026-09-19 18:07:32 -07:00
firestar5683 0ed635026d Apply per-speaker calibration only to displayed music cues 2026-09-19 18:07:32 -07:00
firestar5683 8c954bfe7b Add attended Galaxy Bluetooth timing calibration using exact selected BlueALSA output 2026-09-19 18:07:31 -07:00
firestar5683 1cf81cc950 Record converted playback codec in validation metadata 2026-09-19 18:03:57 -07:00
firestar5683 026655a126 Add verified full-resolution local playback video conversion 2026-09-19 18:03:57 -07:00
firestar5683 01efd58031 Support H264 transport streams in validated playback caches 2026-09-19 17:50:07 -07:00
firestar5683 875ec2ebd0 Select complete validated derived playback caches automatically 2026-09-19 17:49:45 -07:00
firestar5683 cced52e5c4 Recognize H264 road video in prepared local replay caches 2026-09-19 17:47:02 -07:00
firestar5683 e7b3b28849 Add explicit bounded slice threading for software replay decoding 2026-09-19 17:39:47 -07:00
firestar5683 4dcde78413 Record measured warm startup and remaining HD replay blocker 2026-09-19 17:39:14 -07:00
firestar5683 86b6077c8c Expose bounded sanitized receiver failure details in normal launcher 2026-09-19 17:38:37 -07:00
firestar5683 2dc61b6022 Retain segment snapshot while checking startup readiness 2026-09-19 17:28:34 -07:00
firestar5683 45b59daed5 Prime native RoadScore replay before starting its clock 2026-09-19 17:28:21 -07:00
firestar5683 67a7c148b4 Prime native replay camera and initial cache before starting source clock 2026-09-19 17:28:20 -07:00
firestar5683 209796b88c Remove stale unmeasured startup estimate from replay launcher 2026-09-19 17:18:10 -07:00
firestar5683 89613c7c59 Allow authenticated resident handoff past composer availability guard 2026-09-19 17:17:15 -07:00
firestar5683 249eb88b3a Bind dedicated Bluetooth PCM directly instead of late-overridden ALSA defaults 2026-09-19 17:15:40 -07:00
firestar5683 b04995247d Use validated warm native preparation and preserve Bluetooth evidence 2026-09-19 17:15:40 -07:00
firestar5683 19cc31dfcf Route audible native RoadScore to selected Bluetooth output 2026-09-19 17:10:02 -07:00
firestar5683 00904a83b5 Bind process-local BlueALSA output to selected real Bluetooth speaker 2026-09-19 17:09:43 -07:00
firestar5683 41c1db1b13 Show matching native preparation buffer while waiting for replay 2026-09-19 16:58:36 -07:00
firestar5683 e3c425c3cd Allow bounded explicit local startup buffer validation candidate 2026-09-19 16:58:36 -07:00
firestar5683 5b35a5d58d Publish fresh resident preparation identity before sampling 2026-09-19 16:58:36 -07:00
firestar5683 ca2c16b7bc Reject resident audio reuse without session lease opt-in 2026-09-19 16:58:36 -07:00
firestar5683 a23492f627 Add opt-in isolated cached-plan resident session reset 2026-09-19 16:58:35 -07:00
firestar5683 19b8cc33e3 Bind idle resident handoffs to fresh session and conditioning identity 2026-09-19 16:58:35 -07:00
firestar5683 f64f6d150a Bind preparation progress to current worker and launch 2026-09-19 16:49:16 -07:00
firestar5683 7df35a84f6 Record standalone native validation in progress 2026-09-19 16:46:43 -07:00
firestar5683 0c58ea40fa Keep owned native preparation screen awake until playback or failure 2026-09-19 16:46:24 -07:00
firestar5683 0cb88cf9d7 Archive refreshed rhythm timeline provenance 2026-09-19 16:45:44 -07:00
firestar5683 02671ef14c Start late apex breaths smoothly at unity gain 2026-09-19 16:44:29 -07:00
firestar5683 c5fac1e881 Refresh cue timing from each generated passage outside audio callback 2026-09-19 16:44:29 -07:00
firestar5683 a2958ff9f0 Use provisioned native Python from plain onroad shell launch 2026-09-19 16:40:31 -07:00
firestar5683 6ba28be318 Verify cached plans bind actual fresh latent tails in native preparation 2026-09-19 16:38:01 -07:00
firestar5683 211818204b Record first accepted audio and preparation buffer milestones 2026-09-19 16:35:45 -07:00
firestar5683 b5348dd031 Prepare and validate local current-planner bank for fresh standalone native sampling 2026-09-19 16:34:22 -07:00
firestar5683 70d17654b3 Restore persistent launch paths early and preserve native presentation evidence 2026-09-19 16:34:22 -07:00
firestar5683 85850f1fdc Show rendered presentation cues and record recovery checkpoint 2026-09-19 16:34:22 -07:00
firestar5683 9eb394aa23 Accept versioned conservative presentation policy in normal launch 2026-09-19 16:32:43 -07:00
firestar5683 1e64e46113 Bind standalone native sessions to validated current conditioning bank 2026-09-19 16:32:43 -07:00
firestar5683 28902c7d68 Validate archived FLAC without optional imports and anchor visible home badge 2026-09-19 16:32:43 -07:00
firestar5683 0d2d2e0be8 Add sparse beat-aligned alert accents and assess short openings 2026-09-19 16:32:43 -07:00
firestar5683 083314d54d Preserve archived replay origins, audio tails and capture boundaries 2026-09-19 16:29:07 -07:00
firestar5683 97aada4e01 Resolve private local route favorites in normal RoadScore launch 2026-09-19 16:19:31 -07:00
firestar5683 70395cb543 Preserve native render evidence through overlay drawing and audit hidden states 2026-09-19 15:50:55 -07:00
firestar5683 ca9154562d Let Prism hooks vary in length and melodic contour 2026-09-19 15:34:32 -07:00
firestar5683 ed1cafe4e7 Replace stale preparation status when supervised transport exits 2026-09-19 15:31:05 -07:00
firestar5683 16273ef228 Place startup RoadScore badge below home text at bottom right 2026-09-19 15:26:19 -07:00
firestar5683 17d97f92f6 Update event device address for current network 2026-09-19 15:26:19 -07:00
firestar5683 f2a16ec721 Reconnect persistent event assets after checkout replacement 2026-09-19 15:26:19 -07:00
firestar5683 a7551f9df4 Add bounded cold and warm preparation-only comparison command 2026-09-19 14:52:36 -07:00
firestar5683 6995c688db Release unused host diffusion weights in preparation-only service 2026-09-19 14:52:36 -07:00
firestar5683 ffa87f71ca Add reproducible continuation seam retry audition 2026-09-19 14:50:47 -07:00
firestar5683 8462589630 Reject hook continuation leading silence without flattening musical breaks 2026-09-19 14:50:06 -07:00
firestar5683 08f442b98a Update .gitignore 2026-09-19 14:32:02 -07:00
firestar5683 03636629dc Compare revised openings at fixed gain with matched seed and gold reference 2026-09-19 14:25:51 -07:00
firestar5683 9ae89a4cd5 Interleave offline seed checks to surface composition feedback early 2026-09-19 14:19:01 -07:00
firestar5683 d1c4c5ea34 Restore Gold groove language and strong role energy in fresh semantic plans 2026-09-19 14:18:17 -07:00
firestar5683 664156978a Default fresh normal ACE sessions to the 100 W event operating point 2026-09-19 14:11:43 -07:00
firestar5683 2f3f17b0ac Validate fresh hook sessions offline with bounded Mac model residency 2026-09-19 14:10:41 -07:00
firestar5683 04c4638211 Preserve normal demo power, audible status, raw capture and measured mux timing 2026-09-19 14:10:40 -07:00
firestar5683 7ce10a275c Label partial auditions and allow expected assembly crossfades 2026-09-19 14:03:23 -07:00
firestar5683 10b1da6bd6 Add local all-session hook audio review and fault screening 2026-09-19 14:01:03 -07:00
firestar5683 e5f344a04f Isolate semantic planner environment and report preparation-inclusive latency 2026-09-19 14:00:09 -07:00
firestar5683 2b6fd43fa4 Verify completed semantic capture across upstream generation thread boundary 2026-09-19 13:57:43 -07:00
firestar5683 ec51783921 Bind normal RoadScore sessions to causal host semantic plans and native sampling 2026-09-19 13:56:06 -07:00
firestar5683 bad78c6750 Release duplicate Torch diffusion weights before loading host VAE 2026-09-19 13:54:44 -07:00
firestar5683 ea1fb3d230 Load semantic planner before diffusion model to limit initialization peak 2026-09-19 13:51:31 -07:00
firestar5683 9aac0bec15 Discover complete preparation caches for ordinary route replay 2026-09-19 13:49:35 -07:00
firestar5683 634f5f88be Resolve conservative normal presentation without altering frozen cohorts 2026-09-19 13:49:01 -07:00
firestar5683 6bed029ba9 Expose actual rendered cue and engagement state for honest UI 2026-09-19 13:37:03 -07:00
firestar5683 cc291d08bd Refine native RoadScore hierarchy and show only played cues 2026-09-19 13:36:21 -07:00
firestar5683 8d95c0d369 Add session-specific hook planning cache and host adapter contract 2026-09-19 13:34:34 -07:00
firestar5683 42fc6b8573 Add offline cached-route telemetry and pacing inspection tools 2026-09-19 13:31:46 -07:00
firestar5683 299ed93ae3 Specify coherent Prism hook development and isolated host planning 2026-09-19 13:31:46 -07:00
firestar5683 1132379613 Add optional confidence-gated musical presentation candidates 2026-09-19 13:30:59 -07:00
firestar5683 7b6863589b Stabilize RoadScore identity and contextual cue presentation 2026-09-19 13:30:58 -07:00
firestar5683 a6095e614a Separate steering safety warning from rotating wheel bounds 2026-09-19 13:30:58 -07:00
firestar5683 bf252d0ae5 Match native HUD typography and reserve control space for RoadScore 2026-09-19 13:30:58 -07:00
firestar5683 23fc53a5d7 Choose and persist fresh session seeds for normal RoadScore launches 2026-09-19 13:30:37 -07:00
firestar5683 a12d1b7854 Add opt-in unity gold-core ACE rendering mode 2026-09-19 12:42:02 -07:00
firestar5683 aa0846a7b5 Show actual musical road cues and yield score ribbon to native alerts 2026-09-19 11:56:17 -07:00
firestar5683 24db15765d Center score ribbon above camera with native drawn music cues 2026-09-19 11:56:17 -07:00
firestar5683 11530357c9 Replace RoadScore panel with compact camera-edge ribbon 2026-09-19 11:56:17 -07:00
firestar5683 8798b9d94f Integrate full-speed event policy with isolated judging versions 2026-09-19 11:10:41 -07:00
firestar5683 d3ee54e034 Harden judging cache retries and export private integration handoffs 2026-09-19 11:06:54 -07:00
firestar5683 6bfd34eab1 Prepare private community route normalization, caching and blind judging 2026-09-19 11:06:53 -07:00
firestar5683 90ddd700d5 Require frozen policy and ready authorized judging handoffs 2026-09-19 11:06:53 -07:00
firestar5683 f1b79537b8 Fit RoadScore panel to comma four and preserve gesture visibility 2026-09-19 11:06:53 -07:00
firestar5683 e8eae47837 Polish RoadScore overlay states and document the demo 2026-09-19 11:06:53 -07:00
firestar5683 90bd992ced Report partial log corruption in route characterization 2026-09-18 22:49:47 -07:00
firestar5683 31f2d0f612 Add offline route-richness reporting separate from music judging 2026-09-18 22:46:02 -07:00
firestar5683 408f7fb803 Sequence private judging with frozen configuration and fault stops 2026-09-18 22:43:17 -07:00
firestar5683 5d5519e678 Respect submitted time ranges in private judging audits 2026-09-18 22:37:13 -07:00
firestar5683 576211c420 Build anonymous local listening review with fixed excerpt rules 2026-09-18 22:35:59 -07:00
firestar5683 bbd748c5dd Add private single-attempt judging orchestration 2026-09-18 22:34:18 -07:00
firestar5683 963d069778 Make ACE session sampling reproducible for private judging 2026-09-18 22:32:11 -07:00
firestar5683 f75a98181f Record first clean event Chestnut replay and add acceptance audit 2026-09-18 22:28:52 -07:00
firestar5683 0766138ca2 RoadScore 2026-09-19 00:22:33 -05:00
firestarsdog b990a776b2 Curve radial menu corner gradient 2026-09-18 22:26:26 -04:00
firestarsdog 88cbf88756 TV Set 2026-09-18 22:17:55 -04:00
whoisdomi 373c411baa Model Stuff 2026-09-18 19:47:39 -05:00
Prabhaav Pillai 990e68804c fix recording and revert nav tab 2026-09-18 20:04:20 -04:00
firestarsdog b295a57281 mici ux 2026-09-18 18:12:53 -04:00
firestarsdog 673ca37396 Small UI UX 2026-09-18 03:42:24 -04:00
firestar5683 44beb5b778 EV6 2026-09-17 08:33:35 -05:00
firestar5683 00ac287223 EV6 2026-09-17 08:33:10 -05:00
firestar5683 09b53ccf9f hackathon 2026-09-16 13:12:42 -05:00
firestar5683 0fee545400 build 2026-09-16 12:20:32 -05:00
firestar5683 14370fe9cf dopa 2026-09-16 12:19:59 -05:00
Prabhaav Pillai 7316871e62 firefox friendly :) 2026-09-16 00:34:47 -04:00
firestar5683 239121b0e1 build 2026-09-15 20:25:19 -05:00
firestar5683 26de11932a In&Out 2026-09-15 20:23:01 -05:00
firestar5683 1f8b955a0f niro 2026-09-15 16:10:15 -05:00
firestar5683 b41b0ab95f build 2026-09-15 15:00:36 -05:00
firestar5683 a8d1f2318e whoopity scoop 2026-09-15 15:00:02 -05:00
firestar5683 dac7140410 fingerprint 2026-09-15 14:01:39 -05:00
firestar5683 0cf86c5c3b build 2026-09-15 13:07:18 -05:00
firestar5683 d3ec77b0b4 lunch time 2026-09-15 13:05:50 -05:00
firestar5683 814af739d0 Keep screen settings in Galaxy
Leave the existing UI-state consumer in place so Galaxy values still control the display, but remove the native settings pages and tests. Hide the new brightness and wake-choice controls behind Galaxy Developer Mode while preserving current defaults.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-15 12:53:17 -05:00
AngusBell97 68b75fc51e Reuse the UI carState reader for standby button wake 2026-09-15 12:27:02 -05:00
AngusBell97 7ff3682aba Verify native screen timeout and toggle saves 2026-09-15 12:27:02 -05:00
AngusBell97 91052ea0e1 Make standby button wake optional and retain ignition wake 2026-09-15 12:27:02 -05:00
AngusBell97 20f38f3d8e Use general standby wakes and six event selections 2026-09-15 12:27:02 -05:00
AngusBell97 cdaf33529a Add configurable screen brightness and standby wakes 2026-09-15 12:26:49 -05:00
firestar5683 6e37c0917c build 2026-09-15 12:17:10 -05:00
firestar5683 1db1ff9b91 waffles 2026-09-15 12:15:34 -05:00
Prabhaav Pillai 3ba36ed4fc GalaxySelect refactor, update tests, rename components to be more accurate, Discord Support button 2026-09-15 01:06:17 -04:00
Prabhaav Pillai cbe6f39030 Refactor navigation components and enhance slider functionality with fine scrubbing feature 2026-09-15 00:16:49 -04:00
firestarsdog 6aa9abf046 Purple RainX 2026-09-14 21:10:16 -04:00
firestarsdog 9332886242 SLC: Man with a slow hand 2026-09-14 17:45:36 -04:00
firestar5683 c3e4ec630f fix 2026-09-14 15:41:25 -05:00
firestarsdog 65c8581db3 SLC: Fix ghost confirmation/simplify 2026-09-14 16:07:33 -04:00
firestar5683 9136e13fdf net 2026-09-14 14:58:55 -05:00
firestar5683 9e5a3e288b booty 2026-09-14 14:32:33 -05:00
firestar5683 2878d13c3d nav 2026-09-14 14:11:41 -05:00
firestar5683 16ec6bc5c9 backpack 2026-09-14 13:38:29 -05:00
firestar5683 1d6d0cb5ba yas 2026-09-14 13:20:38 -05:00
firestar5683 200ac08499 astrobot 2026-09-14 13:13:33 -05:00
firestar5683 71649a2ac1 multi comma 2026-09-14 13:03:17 -05:00
firestar5683 fc852fed06 nav 2026-09-14 12:21:59 -05:00
firestarsdog 7222b29a88 SLC : Raise Max with Higher Confirmations on 2026-09-14 13:11:43 -04:00
firestar5683 2d3f483f3a galaxy 2026-09-14 12:11:26 -05:00
firestar5683 b4dbc18a14 mario 2026-09-14 11:39:02 -05:00
Zikeji 8d73b7b679 Harden tethering NAT activation 2026-09-14 11:09:15 -05:00
Zikeji b640bbc20e Ensure WAN NAT for Wi-Fi tethering hotspot
AGNOS kernels (4.9, CONFIG_NF_TABLES not set — verified in upstream AGNOS
boot image) cannot run NetworkManager's shared-mode firewall rules, so
tethered clients get DHCP but no WAN access.

Idempotently apply masquerade/forward rules via iptables-legacy on every
hotspot activation path (UI toggle, autoconnect fallback, boot restore),
replacing a manually re-installed systemd service after each AGNOS update.
2026-09-14 11:07:05 -05:00
AngusBell97 9d0ab7a849 Align personality registry test with Dom defaults 2026-09-14 10:52:38 -05:00
AngusBell97 6f3d863ecd Align following presets with Dom defaults 2026-09-14 10:48:32 -05:00
AngusBell97 ab6c97fef8 Remember custom personality graphs and correct default resets 2026-09-14 10:48:32 -05:00
firestar5683 a51205e302 software 2026-09-14 10:45:36 -05:00
firestar5683 64f8b75551 ferd 2026-09-14 10:10:26 -05:00
firestar5683 497b906121 G70 2026-09-14 10:08:00 -05:00
whoisdomi 04ba07e706 C3/C3X Aggressive Fan Curve + Toggle
C3 and C3X Cooling Curve. 10 - 16 C cooler than stock curve.
2026-09-14 09:48:15 -05:00
firestar5683 ad1c970cdd fix maps 2026-09-14 09:45:49 -05:00
firestarsdog 7a7b391656 stop lying on my chestnut 2026-09-13 22:04:29 -05:00
firestar5683 688631b6bd cyanara 2026-09-13 21:57:33 -05:00
AngusBell97 9677a3bd78 Harden Galaxy version picker 2026-09-13 21:40:03 -05:00
AngusBell97 4d585dbcbc Preserve standard OS updates and rollback in version picker 2026-09-13 21:32:41 -05:00
AngusBell97 f88ef758ca Reduce history requests and protect hidden local edits 2026-09-13 21:32:41 -05:00
AngusBell97 c4f46c51b2 Add branch and historical version selection to Galaxy 2026-09-13 21:32:40 -05:00
firestar5683 6b8bb279d4 build 2026-09-13 17:58:26 -05:00
AngusBell97 c51b96879a Keep Tesla steering diagnostics in custom cereal
Move the fork-specific diagnostics out of the stock CarOutput schema and into StarPilot reserved messaging. Preserve the legacy saturation fallback when custom diagnostics are unavailable or stale.

Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-13 17:49:18 -05:00
firestar5683 524cffa19c suburban cam 2026-09-13 17:23:07 -05:00
firestar5683 549c12cd1b build 2026-09-13 16:53:12 -05:00
AngusBell97 f6664f5466 Fix Tesla cooperative steering saturation warnings
(cherry picked from commit 9136f1cb62)
2026-09-13 16:46:46 -05:00
firestar5683 151b07462c new support 2026-09-13 16:24:10 -05:00
firestar5683 3506c2561b build 2026-09-13 15:40:54 -05:00
firestar5683 5bead81598 hercules 2026-09-13 15:34:21 -05:00
firestar5683 01ae511879 Clean up Galaxy model management integration
Co-authored-by: AngusBell97 <124716116+AngusBell97@users.noreply.github.com>
2026-09-13 15:30:44 -05:00
AngusBell97 8ccaacb919 Improve model selection and downloads in Galaxy
(cherry picked from commit da099820c7)
2026-09-13 15:23:38 -05:00
AngusBell97 38c98cf9f7 Include supported settings in Galaxy diagnostic reports
(cherry picked from commit 0466650e4a)
2026-09-13 15:23:38 -05:00
Danny 737c8499eb Remove Short Route ID
(cherry picked from commit b9f23f8ce0)
2026-09-13 15:23:38 -05:00
Danny 12251b663a Show Recording Dates and Search
(cherry picked from commit 3b3d2649ae)
2026-09-13 15:23:38 -05:00
firestar5683 ed008e2bf3 buildy 2026-09-13 14:24:36 -05:00
firestar5683 024465323b ribbit 2026-09-13 14:23:08 -05:00
firestar5683 32381eb182 Reapply "joplin"
This reverts commit e517a83541.
2026-09-13 14:04:45 -05:00
firestar5683 3f4d1e4fcc Reapply "g70"
This reverts commit eb088bccfa.
2026-09-13 14:04:42 -05:00
firestarsdog 50a8d1abdb Prevent rejected SLC limit auto-application 2026-09-13 00:07:11 -04:00
firestarsdog 185c501809 Refactorious III 2026-09-12 00:16:28 -04:00
firestarsdog eb088bccfa Revert "g70"
This reverts commit c7b9a77782.
2026-09-11 22:30:34 -04:00
firestarsdog d0585de42d Revert "build"
This reverts commit 07dc300d5b.
2026-09-11 22:30:32 -04:00
firestarsdog e517a83541 Revert "joplin"
This reverts commit 59d3c4dd66.
2026-09-11 22:30:29 -04:00
firestar5683 c7b9a77782 g70 2026-09-11 18:14:08 -05:00
firestar5683 07dc300d5b build 2026-09-11 16:35:33 -05:00
firestar5683 59d3c4dd66 joplin 2026-09-11 16:34:46 -05:00
firestar5683 d6712e2a10 Roadhouse 2026-09-11 10:01:15 -05:00
firestar5683 be263a6fa1 ravbob 2026-09-10 22:26:20 -05:00
firestar5683 f1cb143bd7 neck is red 2026-09-10 22:22:56 -05:00
927 changed files with 50628 additions and 2659 deletions
+3
View File
@@ -134,3 +134,6 @@ Pipfile
!rednose_repo/rednose/helpers/ekf_sym_pyx.so
!panda/board/obj/
!panda/board/obj/**
# Private hackathon context and community route submissions
/ROADSCORE_COMMA_HACK_7_CONTEXT.md
+11
View File
@@ -14,6 +14,7 @@ using Car = import "car.capnp";
struct StarPilotCarControl @0x81c2f05a394cf4af {
hudControl @0 :HUDControl;
steeringLimitInfo @1 :SteeringLimitInfo;
struct HUDControl {
audibleAlert @0 :AudibleAlert;
@@ -49,6 +50,16 @@ struct StarPilotCarControl @0x81c2f05a394cf4af {
uwu @22;
}
}
struct SteeringLimitInfo {
valid @0 :Bool;
modelLimitErrorDeg @1 :Float32;
resumeLimitErrorDeg @2 :Float32;
cooperativeLimitErrorDeg @3 :Float32;
cooperativeOffsetDeg @4 :Float32;
monoTime @5 :UInt64;
combinedLimitErrorDeg @6 :Float32;
}
}
struct StarPilotCarParams @0xaedffd8f31e7b55d {
Binary file not shown.
+23 -4
View File
@@ -152,7 +152,8 @@ class FrequencyTracker:
class SubMaster:
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):
ignore_valid: Optional[list[str]] = None, addr: str = "127.0.0.1", frequency: Optional[float] = None,
drain_services: list[str] | None = None):
self.frame = -1
self.services = services
self.seen = {s: False for s in services}
@@ -160,6 +161,9 @@ class SubMaster:
self.recv_time = {s: 0. for s in services}
self.recv_frame = {s: 0 for s in services}
self.sock = {}
self.drained = {s: [] for s in (drain_services or [])}
if not self.drained.keys() <= set(services):
raise ValueError("Drained services must be subscribed")
self.data = {}
self.logMonoTime = {s: 0 for s in services}
@@ -187,7 +191,7 @@ class SubMaster:
for s in services:
p = self.poller if s not in self.non_polled_services else None
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=True)
self.sock[s] = sub_sock(s, poller=p, addr=addr, conflate=s not in self.drained)
try:
data = new_message(s)
@@ -207,14 +211,28 @@ class SubMaster:
def _check_avg_freq(self, s: str) -> bool:
return SERVICE_LIST[s].frequency > 0.99 and (s not in self.ignore_average_freq) and (s not in self.ignore_alive)
def _recv_socket(self, sock):
message = recv_one_or_none(sock)
if not self.drained or message is None:
return message
# Native Poller returns fresh socket wrappers; identify the service by data.
service = message.which()
if service not in self.drained:
return message
# Preserve event edges for observers, but update state/frequency only once.
self.drained[service] = [message, *drain_sock(sock)]
return self.drained[service][-1]
def update(self, timeout: int = 100) -> None:
for service in self.drained:
self.drained[service] = []
msgs = []
for sock in self.poller.poll(timeout):
msgs.append(recv_one_or_none(sock))
msgs.append(self._recv_socket(sock))
# non-blocking receive for non-polled sockets
for s in self.non_polled_services:
msgs.append(recv_one_or_none(self.sock[s]))
msgs.append(self._recv_socket(self.sock[s]))
self.update_msgs(time.monotonic(), msgs)
def update_msgs(self, cur_time: float, msgs: List[capnp.lib.capnp._DynamicStructReader]) -> None:
@@ -262,6 +280,7 @@ class SubMaster:
ignore_valid=self.ignore_valid,
addr=self.addr,
frequency=None if self.poll is not None else self.update_freq,
drain_services=list(self.drained),
)
@@ -1,5 +1,6 @@
import random
import time
import pytest
from typing import Sized, cast
import cereal.messaging as messaging
@@ -16,6 +17,29 @@ class TestSubMaster:
# sleep to prevent multiple publishers error between tests
zmq_sleep(3)
@pytest.mark.parametrize("poll", [None, "deviceState"])
def test_drain_preserves_short_events_with_native_socket_wrappers(self, poll):
pub = messaging.PubMaster(["carState", "deviceState"])
sm = messaging.SubMaster(["carState", "deviceState"], poll=poll, drain_services=["carState"])
zmq_sleep()
pressed = messaging.new_message("carState", valid=True)
button = pressed.carState.init("buttonEvents", 1)[0]
button.type, button.pressed = "accelCruise", True
pub.send("carState", pressed)
latest = messaging.new_message("carState", valid=True)
latest.carState.vEgo = 12.0
pub.send("carState", latest)
pub.send("deviceState", messaging.new_message("deviceState", valid=True))
sm.update(1000)
assert len(sm.drained["carState"]) == 2
assert sm.drained["carState"][0].carState.buttonEvents[0].pressed
assert sm["carState"].vEgo == 12.0 and not sm["carState"].buttonEvents
assert sm.logMonoTime["carState"] == latest.logMonoTime
assert sm.frame == 0 and all(sm.updated.values())
sm.update(0)
assert sm.drained["carState"] == []
assert sm.frame == 1 and not any(sm.updated.values())
def test_init(self):
sm = messaging.SubMaster(events)
for p in [sm.updated, sm.recv_time, sm.recv_frame, sm.alive,
Binary file not shown.
+15
View File
@@ -318,6 +318,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}},
{"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}},
{"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}},
{"GalaxyDeviceName", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
{"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "1", "1", 0, SETTINGS_SIMPLE}},
{"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}},
{"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
@@ -444,6 +445,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"IncreaseFollowingRain", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"IncreaseFollowingRainStorm", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"IncreaseFollowingSnow", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"AggressiveCoolingEnabled", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"IncreaseThermalLimits", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"IssueReported", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
{"KonikDongleId", {PERSISTENT, STRING, "", "", 0}},
@@ -623,6 +625,10 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"RotatingWheel", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"ScreenBrightness", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessOnroad", {PERSISTENT, INT, "101", "101", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessManual", {PERSISTENT, INT, "100", "100", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessOnroadManual", {PERSISTENT, INT, "100", "100", 2, SETTINGS_SIMPLE}},
{"ScreenBrightnessOnroadOffset", {PERSISTENT, INT, "0", "0", 2, SETTINGS_SIMPLE}},
{"ScreenManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}},
{"ScreenRecorder", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"ScreenTimeout", {PERSISTENT, INT, "30", "30", 2, SETTINGS_SIMPLE}},
@@ -632,6 +638,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"SafeModeBackup", {PERSISTENT, JSON, "{}", "{}"}},
{"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}},
{"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2, SETTINGS_SIMPLE}},
{"ShowBrakeStatus", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2, SETTINGS_SIMPLE}},
{"ShowCCMStatus", {PERSISTENT, BOOL, "0", "0", 2}},
{"ShowCPU", {PERSISTENT, BOOL, "0", "0", 3}},
@@ -693,6 +700,14 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"StandardJerkSpeed", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"StandardJerkSpeedDecrease", {PERSISTENT, FLOAT, "100.0", "100.0", 3}},
{"StandbyMode", {PERSISTENT, BOOL, "0", "0", 1, SETTINGS_SIMPLE}},
{"StandbyWakeButton", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"StandbyButtonPressTime", {CLEAR_ON_MANAGER_START | DONT_LOG, INT, "0", "0"}},
{"StandbyWakeEngage", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeDisengage", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeInfoAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeWarningAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeCriticalAlert", {PERSISTENT, BOOL, "1", "1", 2, SETTINGS_SIMPLE}},
{"StandbyWakeTurnSignal", {PERSISTENT, BOOL, "0", "0", 2, SETTINGS_SIMPLE}},
{"StartAccel", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
{"StartAccelStock", {PERSISTENT, FLOAT, "0.0", "0.0", 3}},
{"StartupMessageBottom", {PERSISTENT, STRING, "Always keep hands on wheel and eyes on road", "Always keep hands on wheel and eyes on road", 0}},
Binary file not shown.
+80
View File
@@ -0,0 +1,80 @@
# Custom personality graphs
Each personality keeps its own Custom acceleration, braking and following curve.
Selecting a named preset changes the active selection without deleting Custom
points. Selecting Custom again restores those points, including after a reload
or restart. If a category has never had Custom points, it is initialized from
the current selection, as before.
The existing **Reset to default** button, below each Custom graph's numeric
points in New Galaxy's Advanced section, replaces only that category's Custom
curve. It leaves the category set to Custom. The server resolves the reset
values; the dashed **Dom default** line uses the same resolver.
Defaults are Dom's configured base curves sampled at the editor's 10 mph
points. They include Traffic's dedicated acceleration and braking, following
settings, global tuning switches and powertrain overrides. Where gear mapping
is enabled, the reference uses normal gear. Live Eco/Sport gear, weather,
lead/stop and overspeed adjustments remain on the existing controller paths.
Sampling cannot reproduce every native breakpoint or between-point value;
resetting a Custom graph is not the same as delegating to the Dom-default
runtime path.
Dom-default points outside the ordinary editor range (such as Traffic braking
at 0.35 m/s², configured Traffic following at 0.5 seconds or truck acceleration
at 6 m/s²) remain visible and are preserved when another point is edited.
New point edits still use the existing authoring bounds. This does not expand
braking authority or change acceleration/braking preset definitions.
## Following presets
Named following presets now match Dom's factory following settings with custom
personalities enabled. Close follows Aggressive, Medium follows Standard and
Far follows Relaxed. The presets are available in every personality.
| Preset | Previous curve | Revised curve |
| --- | --- | --- |
| Close | 1.25 s at every speed | 1.25 s through 45 mph, falling to 1.0 s at 70 mph |
| Medium | 1.45 s at every speed | 1.45 s through 45 mph, falling to 1.2 s at 70 mph |
| Far | 1.75 s at every speed | 1.6 s through 45 mph, falling to 1.4 s at 70 mph |
| Traffic | No named preset | 0.75 s at rest, rising to 1.6 s at 25 m/s (55.92 mph) |
Interpolation is linear between the stated breakpoints and constant outside
them. Named presets use the exact native speed axes at runtime. First-use
Custom conversion samples them onto the existing 10 mph editor grid.
Existing v1/v2 Close, Medium and Far selections keep their old fixed headways
as `legacy_close`, `legacy_medium` and `legacy_far`. Both Galaxy pickers show
the selected compatibility entry as **Previous Close**, **Previous Medium**
or **Previous Far**. Explicitly selecting a current preset adopts its new curve.
The previous entry disappears when it is no longer selected.
Existing `dom_default` selections continue to inherit configured settings;
they are not silently converted to fixed named presets. Fresh profiles also
retain this inheritance. The named curves match untouched factory settings;
users' changed global following values can still differ from them.
Acceleration and braking presets are unchanged. Standard acceleration and Eco
braking match the normal factory defaults for Aggressive, Standard and Relaxed
when named-preset and global powertrain tuning agree. Named presets use detected
EV/truck tuning; the Dom-default resolver respects the global tuning switches,
so these can differ. Traffic retains its dedicated acceleration/braking defaults;
this change adds only its named following preset.
## Storage compatibility
Profile document version 3 retains `curve` and optional `legacyCurve` while
`preset` is a named preset or `dom_default`. These retained values are dormant;
only Custom uses them. An actual graph edit or reset retires preserved v1
interpolation for that category; a preset switch or unchanged submission does
not.
Valid v2 documents retain their runtime meaning and are upgraded on the next
normal write, including the fixed following compatibility names above.
Version 1 keeps its existing explicit, verified migration flow. Reads never
rewrite Params. Category conflict detection, off-road checks and atomic profile
document writes still apply to edits and resets.
Older builds do not understand v3 documents. Retain a compatible settings
backup before rolling back to one of those builds. Curves discarded before
this change cannot be recovered automatically.
+6
View File
@@ -3,4 +3,10 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# RoadScore owns its replay/audio lifecycle only when explicitly requested.
for argument in "$@"; do
if [[ "$argument" == "--roadscore" ]]; then
exec "${ROOT_DIR}/roadscore/onroad" "$@"
fi
done
exec "${ROOT_DIR}/scripts/host_tool_runner.sh" onroad "$@"
+1
View File
@@ -1,3 +1,4 @@
include opendbc/car/car.capnp
include opendbc/car/include/c++.capnp
include opendbc/dbc/hyundai_kia_ray_pedal.dbc
recursive-include opendbc/safety *.h
+3 -1
View File
@@ -13,7 +13,7 @@ from opendbc.car.subaru.subarucan import subaru_checksum
from opendbc.car.chrysler.chryslercan import chrysler_checksum, fca_giorgio_checksum
from opendbc.car.hyundai.hyundaicanfd import hkg_can_fd_checksum
from opendbc.car.volkswagen.mlbcan import volkswagen_mlb_checksum
from opendbc.car.volkswagen.mqbcan import volkswagen_mqb_meb_checksum, xor_checksum
from opendbc.car.volkswagen.mqbcan import volkswagen_meb_alt_crc_checksum, volkswagen_mqb_meb_checksum, xor_checksum
from opendbc.car.tesla.teslacan import tesla_checksum
from opendbc.car.body.bodycan import body_checksum
from opendbc.car.psa.psacan import psa_checksum
@@ -196,6 +196,8 @@ def get_checksum_state(dbc_name: str) -> ChecksumState | None:
return ChecksumState(8, -1, 7, -1, False, SignalType.TOYOTA_CHECKSUM, toyota_checksum)
elif dbc_name.startswith(("hyundai_canfd_generated", "hyundai_radar_210_21f_generated")):
return ChecksumState(16, -1, 0, -1, True, SignalType.HKG_CAN_FD_CHECKSUM, hkg_can_fd_checksum)
elif dbc_name.startswith("vw_meb_2024"):
return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_meb_alt_crc_checksum)
elif dbc_name.startswith(("vw_mqb", "vw_mqbevo", "vw_meb")):
return ChecksumState(8, 4, 0, 0, True, SignalType.VOLKSWAGEN_MQB_MEB_CHECKSUM, volkswagen_mqb_meb_checksum)
elif dbc_name.startswith("vw_mlb"):
+1
View File
@@ -90,6 +90,7 @@ class Bus(StrEnum):
main = auto()
party = auto()
ap_party = auto()
ap_pt = auto()
def rate_limit(new_value, last_value, dw_step, up_step):
+36
View File
@@ -56,6 +56,20 @@ GM_CANDIDATE_PREFIXES = ("CHEVROLET_", "GMC_", "CADILLAC_", "BUICK_", "HOLDEN_")
GM_CORE_FINGERPRINT_MSGS = frozenset((190, 201, 209, 211, 241))
GM_CAMERA_BUS = 2
GM_VOLT_CAMERA_MSG = 0x320
GM_SUBURBAN_CAMERA_VIN_PREFIX = "1GNSKJKJ"
GM_SUBURBAN_CAMERA_PT_SIGNATURE = {
190: 6,
201: 8,
209: 7,
211: 2,
241: 6,
304: 1,
320: 3,
}
GM_CAMERA_DIAGNOSTIC_MESSAGES = {
0x24b: 8,
0x64b: 8,
}
def _normalize_forced_candidate(candidate: str | None) -> str | None:
@@ -152,6 +166,24 @@ def _normalize_gm_volt_candidate(candidate: str | None, fingerprints: dict[int,
return candidate
def _normalize_gm_suburban_camera_candidate(candidate: str | None, fingerprints: dict[int, dict], vin: str | None) -> str | None:
"""Resolve the 2019 Suburban camera-harness variant when CAN is shared with Yukon."""
if candidate not in (None, "GMC_YUKON", "GMC_YUKON_CC"):
return candidate
if not isinstance(vin, str) or not vin.startswith(GM_SUBURBAN_CAMERA_VIN_PREFIX):
return candidate
powertrain = fingerprints.get(0, {})
camera = fingerprints.get(GM_CAMERA_BUS, {})
if not all(powertrain.get(address) == length for address, length in GM_SUBURBAN_CAMERA_PT_SIGNATURE.items()):
return candidate
if not all(camera.get(address) == length for address, length in GM_CAMERA_DIAGNOSTIC_MESSAGES.items()):
return candidate
return "CHEVROLET_SUBURBAN_CAMERA"
def _is_gm_candidate(candidate: str | None) -> bool:
return isinstance(candidate, str) and candidate.startswith(GM_CANDIDATE_PREFIXES)
@@ -307,6 +339,10 @@ def get_car(can_recv: CanRecvCallable, can_send: CanSendCallable, set_obd_multip
stored_candidate = _normalize_forced_candidate(params.get("CarModel"))
cached_candidate = _normalize_forced_candidate(getattr(cached_params, "carFingerprint", None))
if candidate is None and stored_candidate is None and cached_candidate is None:
candidate = _normalize_gm_suburban_camera_candidate(candidate, fingerprints, vin)
fingerprinted_candidate = candidate
if candidate is None:
gm_fallback_candidate = _get_gm_stored_candidate_fallback(fingerprints, stored_candidate, cached_candidate)
if gm_fallback_candidate is not None:
+5 -2
View File
@@ -1159,9 +1159,12 @@ class CarController(CarControllerBase):
if should_send_cc_button_spam(self.CP, CC, CS):
if self.CP.carFingerprint != CAR.CADILLAC_XT4_CC:
# Using extend instead of append since the message is only sent intermittently
lead_visible = bool(getattr(CS, "openpilot_lead_visible", CC.hudControl.leadVisible))
longitudinal_adjustment_active = bool(getattr(
CS, "openpilot_longitudinal_adjustment_active", CC.hudControl.leadVisible,
))
can_sends.extend(gmcan.create_gm_cc_spam_command(
self.packer_pt, self, CS, actuators, starpilot_toggles, lead_visible=lead_visible,
self.packer_pt, self, CS, actuators, starpilot_toggles,
longitudinal_adjustment_active=longitudinal_adjustment_active,
))
else:
if (CS.out.cruiseState.enabled and CC.enabled and self.frame % 52 == 0 and
@@ -212,6 +212,10 @@ FINGERPRINTS.update({
CAR.CADILLAC_ESCALADE_ASCM: FINGERPRINTS[CAR.CADILLAC_ESCALADE],
CAR.CADILLAC_ESCALADE_ESV_2019_ASCM: [{**fp, SASCM_ADDRESS: 8} for fp in FINGERPRINTS[CAR.CADILLAC_ESCALADE_ESV_2019]],
CAR.CHEVROLET_SUBURBAN: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
CAR.CHEVROLET_SUBURBAN_ASCM: FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC],
# The camera-harness Suburban shares the observed CAN map with the 2019 Yukon;
# VIN and camera-bus diagnostics disambiguate it during live fingerprinting.
CAR.CHEVROLET_SUBURBAN_CAMERA: FINGERPRINTS[CAR.GMC_YUKON],
CAR.GMC_YUKON_CC: FINGERPRINTS[CAR.GMC_YUKON],
CAR.CADILLAC_XT6: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
CAR.CADILLAC_XT5: FINGERPRINTS[CAR.CHEVROLET_TRAVERSE],
+25 -6
View File
@@ -32,7 +32,7 @@ VOLT_CC_CARS = {
CAR.CHEVROLET_VOLT_CC,
}
VOLT_CC_FREE_REQUEST_DEADBAND_MPH = 5.0
VOLT_CC_LEAD_REQUEST_DEADBAND_MPH = 2.0
VOLT_CC_ACTIVE_REQUEST_DEADBAND_MPH = 2.0
def malibu_phase_map_for_button(button):
@@ -341,15 +341,34 @@ def stabilize_bolt_cc_button(controller, CP, requested_button):
return requested_button
def _create_volt_cc_spam_command(CS, actuators, ms_convert, lead_visible):
def _create_volt_cc_spam_command(CS, actuators, ms_convert, longitudinal_adjustment_active):
accel = float(actuators.accel)
speed_setpoint = int(round(CS.out.cruiseState.speed * ms_convert))
ego_speed = CS.out.vEgo * ms_convert
requested_setpoint = (CS.out.vEgo * 1.01 + 3 * accel) * ms_convert
deadband_mph = VOLT_CC_LEAD_REQUEST_DEADBAND_MPH if lead_visible else VOLT_CC_FREE_REQUEST_DEADBAND_MPH
deadband_mph = (
VOLT_CC_ACTIVE_REQUEST_DEADBAND_MPH if longitudinal_adjustment_active
else VOLT_CC_FREE_REQUEST_DEADBAND_MPH
)
request_deadband = deadband_mph * (CV.MPH_TO_KPH if ms_convert == CV.MS_TO_KPH else 1.0)
if abs(requested_setpoint - speed_setpoint) <= request_deadband:
target_setpoint = None
v_cruise_kph = float(getattr(CS.out, "vCruise", 0.0))
if 0.0 < v_cruise_kph < 255.0:
is_metric = ms_convert == CV.MS_TO_KPH
target_setpoint = int(round(v_cruise_kph if is_metric else v_cruise_kph * CV.KPH_TO_MPH))
moving_toward_target = target_setpoint is not None and (
(accel > 0.0 and speed_setpoint < target_setpoint) or
(accel < 0.0 and speed_setpoint > target_setpoint)
)
if target_setpoint is not None and accel > 0.0 and speed_setpoint >= target_setpoint:
return CruiseButtons.INIT, float("inf")
if (target_setpoint is not None and accel < 0.0 and speed_setpoint <= target_setpoint and
not longitudinal_adjustment_active):
return CruiseButtons.INIT, float("inf")
if not moving_toward_target and abs(requested_setpoint - speed_setpoint) <= request_deadband:
return CruiseButtons.INIT, float("inf")
if accel == 0.0:
@@ -369,7 +388,7 @@ def _create_volt_cc_spam_command(CS, actuators, ms_convert, lead_visible):
return CruiseButtons.RES_ACCEL, rate
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles, lead_visible=False):
def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggles, longitudinal_adjustment_active=False):
accel = actuators.accel
v_ego = CS.out.vEgo
cruise_btn = CruiseButtons.INIT
@@ -384,7 +403,7 @@ def create_gm_cc_spam_command(packer, controller, CS, actuators, starpilot_toggl
comparison_setpoint = projected_setpoint if bolt_cc else desired_setpoint
if CS.CP.carFingerprint in VOLT_CC_CARS:
cruise_btn, rate = _create_volt_cc_spam_command(CS, actuators, ms_convert, lead_visible)
cruise_btn, rate = _create_volt_cc_spam_command(CS, actuators, ms_convert, longitudinal_adjustment_active)
else:
if CS.CP.minEnableSpeed - (desired_setpoint / ms_convert) > 3.25:
cruise_btn = CruiseButtons.CANCEL
+2 -2
View File
@@ -306,7 +306,7 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= GMSafetyFlags.HW_CAM_LONG.value
elif is_camera_acc:
ret.alphaLongitudinalAvailable = (candidate not in CC_ONLY_CAR) and not ret.enableGasInterceptorDEPRECATED
ret.alphaLongitudinalAvailable = candidate not in (CC_ONLY_CAR | ALT_ACCS) and not ret.enableGasInterceptorDEPRECATED
ret.networkLocation = NetworkLocation.fwdCamera
ret.radarUnavailable = True
ret.pcmCruise = not ret.enableGasInterceptorDEPRECATED
@@ -522,7 +522,7 @@ class CarInterface(CarInterfaceBase):
ret.steerActuatorDelay = 0.2
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate in (CAR.CHEVROLET_SUBURBAN, CAR.CHEVROLET_SUBURBAN_CC):
elif candidate in (CAR.CHEVROLET_SUBURBAN, CAR.CHEVROLET_SUBURBAN_ASCM, CAR.CHEVROLET_SUBURBAN_CAMERA, CAR.CHEVROLET_SUBURBAN_CC):
ret.steerActuatorDelay = 0.2
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
+199 -11
View File
@@ -28,7 +28,7 @@ import opendbc.car.gm.interface as gm_interface
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.gps import CHEVROLET_BOLT_GPS_CARS, CHEVROLET_BOLT_GPS_MESSAGES, get_car_gps_config, parse_chevrolet_bolt_can_gps
from opendbc.car.gm.fingerprints import FINGERPRINTS
from opendbc.car.gm.values import ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
from opendbc.car.gm.values import ALT_ACCS, ASCM_INT, CAMERA_ACC_CAR, CAR, CC_ONLY_CAR, DBC, GM_RX_OFFSET, CarControllerParams, CruiseButtons, GMFlags, GMSafetyFlags
from opendbc.safety import ALTERNATIVE_EXPERIENCE
from openpilot.common.params import Params
@@ -268,6 +268,94 @@ class TestGMCarState:
class TestGMInterface:
def test_suburban_obd_and_ascm_integrations_remain_separate(self):
fingerprint = _empty_fingerprint()
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN][0].copy()
assert CAR.CHEVROLET_SUBURBAN_ASCM in ASCM_INT
assert FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_ASCM] == FINGERPRINTS[CAR.CHEVROLET_SUBURBAN]
obd_params = interfaces[CAR.CHEVROLET_SUBURBAN].get_params(
CAR.CHEVROLET_SUBURBAN,
fingerprint,
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
ascm_params = interfaces[CAR.CHEVROLET_SUBURBAN_ASCM].get_params(
CAR.CHEVROLET_SUBURBAN_ASCM,
fingerprint,
[],
alpha_long=True,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert obd_params.networkLocation == structs.CarParams.NetworkLocation.gateway
assert obd_params.openpilotLongitudinalControl
assert not obd_params.pcmCruise
assert obd_params.safetyConfigs[0].safetyParam == 0
assert ascm_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
assert not ascm_params.flags & GMFlags.SASCM.value
assert not ascm_params.alphaLongitudinalAvailable
assert not ascm_params.openpilotLongitudinalControl
assert ascm_params.pcmCruise
assert ascm_params.safetyConfigs[0].safetyParam == GMSafetyFlags.HW_CAM.value | GMSafetyFlags.HW_ASCM_INT.value
assert ascm_params.lateralTuning.torque.latAccelFactor == pytest.approx(obd_params.lateralTuning.torque.latAccelFactor)
assert ascm_params.lateralTuning.torque.friction == pytest.approx(obd_params.lateralTuning.torque.friction)
def test_suburban_camera_harness_preserves_stock_acc(self):
fingerprint = _empty_fingerprint()
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN][0].copy()
fingerprint[2] = fingerprint[0].copy()
assert CAR.CHEVROLET_SUBURBAN_CAMERA in CAMERA_ACC_CAR
assert CAR.CHEVROLET_SUBURBAN_CAMERA in ALT_ACCS
assert CAR.CHEVROLET_SUBURBAN_CAMERA not in CC_ONLY_CAR
assert CAR.CHEVROLET_SUBURBAN_CAMERA not in ASCM_INT
assert all(fp[CAMERA_DIAGNOSTIC_ADDRESS] == 8 for fp in FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CAMERA])
assert all(fp[CAMERA_DIAGNOSTIC_ADDRESS + GM_RX_OFFSET] == 8 for fp in FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CAMERA])
camera_params = interfaces[CAR.CHEVROLET_SUBURBAN_CAMERA].get_params(
CAR.CHEVROLET_SUBURBAN_CAMERA,
fingerprint,
[],
alpha_long=True,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert camera_params.networkLocation == structs.CarParams.NetworkLocation.fwdCamera
assert camera_params.pcmCruise
assert not camera_params.alphaLongitudinalAvailable
assert not camera_params.openpilotLongitudinalControl
assert camera_params.safetyConfigs[0].safetyParam == GMSafetyFlags.HW_CAM.value
def test_suburban_cc_remains_no_acc_gateway_profile(self):
fingerprint = _empty_fingerprint()
fingerprint[0] = FINGERPRINTS[CAR.CHEVROLET_SUBURBAN_CC][0].copy()
cc_params = interfaces[CAR.CHEVROLET_SUBURBAN_CC].get_params(
CAR.CHEVROLET_SUBURBAN_CC,
fingerprint,
[],
alpha_long=False,
is_release=False,
docs=False,
starpilot_toggles=_test_starpilot_toggles(),
)
assert cc_params.networkLocation == structs.CarParams.NetworkLocation.gateway
assert cc_params.openpilotLongitudinalControl
assert not cc_params.pcmCruise
assert cc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_CC_LONG.value
assert cc_params.safetyConfigs[0].safetyParam & GMSafetyFlags.FLAG_GM_NO_ACC.value
def test_lacrosse_obd_and_ascm_integrations_remain_separate(self):
obd_params = interfaces[CAR.BUICK_LACROSSE].get_params(
CAR.BUICK_LACROSSE,
@@ -947,7 +1035,7 @@ class TestGMCarController:
assert len(msgs) == 1
def test_volt_cc_redneck_holds_when_pseudo_speed_request_is_within_deadband(self):
def test_volt_cc_redneck_does_not_raise_stock_setpoint_above_max(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
@@ -960,7 +1048,7 @@ class TestGMCarController:
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
@@ -970,11 +1058,61 @@ class TestGMCarController:
)
assert msgs == []
assert controller.apply_speed == 99
assert controller.apply_speed == 100
def test_volt_cc_redneck_uses_smaller_request_deadband_with_lead(self):
def test_volt_cc_redneck_tracks_max_inside_request_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(2.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=52.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=52.0 * CV.KPH_TO_MS),
vCruise=60.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.1), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 53
def test_volt_cc_redneck_tracks_max_down_inside_request_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=68.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=68.0 * CV.KPH_TO_MS),
vCruise=60.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
)
assert len(msgs) == 1
assert controller.apply_speed == 67
def test_volt_cc_redneck_holds_small_decel_request_at_max(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
@@ -985,17 +1123,67 @@ class TestGMCarController:
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=99.0 * CV.KPH_TO_MS),
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=0.5), SimpleNamespace(is_metric=True), lead_visible=True,
packer, controller, cs, SimpleNamespace(accel=-0.1), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 100
def test_volt_cc_redneck_holds_strong_decel_request_at_max_during_free_cruise(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-1.2), SimpleNamespace(is_metric=True),
)
assert msgs == []
assert controller.apply_speed == 100
def test_volt_cc_redneck_brakes_for_active_lead_inside_free_road_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
controller = SimpleNamespace(frame=int(3.0 / DT_CTRL), last_button_frame=0, apply_speed=0, malibu_button_phase=0)
cs = SimpleNamespace(
CP=SimpleNamespace(
carFingerprint=CAR.CHEVROLET_VOLT_CC,
flags=GMFlags.NO_CAMERA.value,
networkLocation=structs.CarParams.NetworkLocation.gateway,
minEnableSpeed=0.0,
),
buttons_counter=2,
out=SimpleNamespace(
vEgo=100.0 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=100.0 * CV.KPH_TO_MS),
vCruise=100.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-0.5), SimpleNamespace(is_metric=True), longitudinal_adjustment_active=True,
)
assert len(msgs) == 1
assert controller.apply_speed == 100
assert controller.apply_speed == 99
def test_volt_cc_redneck_accelerates_when_pseudo_speed_request_exceeds_deadband(self):
packer = CANPacker(DBC[CAR.CHEVROLET_VOLT_CC][Bus.pt])
@@ -1043,12 +1231,12 @@ class TestGMCarController:
out=SimpleNamespace(
vEgo=50.7 * CV.KPH_TO_MS,
cruiseState=SimpleNamespace(speed=49.0 * CV.KPH_TO_MS),
vCruise=50.0,
vCruise=49.0,
),
)
msgs = gmcan.create_gm_cc_spam_command(
packer, controller, cs, SimpleNamespace(accel=-1.36), SimpleNamespace(is_metric=True),
packer, controller, cs, SimpleNamespace(accel=-1.36), SimpleNamespace(is_metric=True), longitudinal_adjustment_active=True,
)
assert len(msgs) == 1
+12 -2
View File
@@ -357,6 +357,14 @@ class CAR(Platforms):
[GMCarDocs("Chevrolet Suburban Premier 2016-20")],
CarSpecs(mass=2731, wheelbase=3.302, steerRatio=17.3, centerToFrontRatio=0.49),
)
CHEVROLET_SUBURBAN_ASCM = GMPlatformConfig(
[GMCarDocs("Chevrolet Suburban Premier ASCM Harness 2016-20", "Adaptive Cruise Control (ACC) & LKAS")],
CHEVROLET_SUBURBAN.specs,
)
CHEVROLET_SUBURBAN_CAMERA = GMPlatformConfig(
[GMCarDocs("Chevrolet Suburban Premier Camera Harness 2016-20", "Adaptive Cruise Control (ACC) & LKAS")],
CHEVROLET_SUBURBAN.specs,
)
GMC_YUKON_CC = GMPlatformConfig(
[GMCarDocs("GMC Yukon No-ACC 2019-20")],
CarSpecs(mass=2541, wheelbase=2.95, steerRatio=16.3, centerToFrontRatio=0.4),
@@ -547,6 +555,7 @@ CAMERA_ACC_CAR = {
CAR.CHEVROLET_SILVERADO,
CAR.CHEVROLET_EQUINOX,
CAR.CHEVROLET_TRAILBLAZER,
CAR.CHEVROLET_SUBURBAN_CAMERA,
CAR.CHEVROLET_VOLT_CAMERA,
CAR.CHEVROLET_BLAZER,
CAR.CHEVROLET_TRAX,
@@ -554,7 +563,7 @@ CAMERA_ACC_CAR = {
}
# Alt ASCMActiveCruiseControlStatus
ALT_ACCS = {CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
ALT_ACCS = {CAR.CHEVROLET_SUBURBAN_CAMERA, CAR.GMC_YUKON, CAR.GMC_YUKON_CC}
# We're integrated at the Safety Data Gateway Module on these cars
SDGM_CAR = {
@@ -593,9 +602,10 @@ CC_REGEN_PADDLE_CAR = {
}
CAMERA_ACC_CAR.update(CC_ONLY_CAR)
# ASCM-INT paths are only enabled when SASCM (0x2FF) is detected at runtime
# ASCM-intercept variants preserve stock ACC. SASCM (0x2FF) enables alpha-long where supported.
ASCM_INT = {
CAR.CHEVROLET_VOLT_ASCM,
CAR.CHEVROLET_SUBURBAN_ASCM,
CAR.GMC_ACADIA_ASCM,
CAR.CHEVROLET_MALIBU_ASCM,
CAR.CADILLAC_ESCALADE_ASCM,
@@ -4,19 +4,20 @@ from dataclasses import dataclass
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
import numpy as np
from opendbc.can import CANPacker
from opendbc.car import Bus, DT_CTRL, make_tester_present_msg, rate_limit, structs
from opendbc.car import Bus, DT_CTRL, create_gas_interceptor_command, make_tester_present_msg, rate_limit, structs
from opendbc.car.common.filter_simple import FirstOrderFilter
from opendbc.car.lateral import apply_driver_steer_torque_limits, apply_steer_angle_limits_vm, common_fault_avoidance, get_max_angle_delta_vm, get_max_angle_vm
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.hyundai import hyundaicanfd, hyundaican
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.lead_data import CanLeadDataState
from opendbc.car.hyundai.values import HyundaiFlags, HyundaiSafetyFlags, HyundaiStarPilotFlags, Buttons, CarControllerParams, CAR, CANFD_ANGLE_LONGITUDINAL_CAR, \
CANFD_RADAR_ECU_KEEPALIVE_CAR, CANFD_ALT_BUTTONS_RESUME_CAR, kia_ev6_gt_line_longitudinal_tuning, \
KIA_EV6_GT_LINE_LONG_TUNING_TESTING_GROUND_ID
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.vehicle_model import VehicleModel
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import get_hyundai_canfd_scc_jerk_limits
from openpilot.selfdrive.controls.lib.longitudinal_vehicle_tunes import get_hyundai_canfd_scc_jerk_limits, shape_hyundai_canfd_scc_accel
from openpilot.starpilot.common.testing_grounds import testing_ground
VisualAlert = structs.CarControl.HUDControl.VisualAlert
@@ -41,6 +42,9 @@ IONIQ_6_RESPONSE_MULTIPLIER = 1.2
IONIQ_6_CANFD_SCC_ACCEL_STEP = (6.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
IONIQ_6_CANFD_SCC_DECEL_STEP = (15.0 / 50.0) * IONIQ_6_RESPONSE_MULTIPLIER
EV9_CANFD_SCC_DECEL_STEP = 10.0 / 50.0
RAY_PEDAL_COMMAND_CAP = 0.35 # Ray firmware voltage scaling is route-derived; validate before raising.
RAY_PEDAL_RATE_UP = 0.012 # per 25 Hz command (0.30 normalized pedal per second)
RAY_PEDAL_RATE_DOWN = 0.06
GENESIS_G90_STOP_HOLD_SPEED_BP = [0.0, 0.03, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
GENESIS_G90_STOP_HOLD_ACCEL_V = [-0.10, -0.10, -0.12, -0.18, -0.30, -0.50, -0.75, -1.00, -1.40, -1.80]
GENESIS_G90_STOP_HOLD_RELAX_SPEED_BP = [0.0, 0.08, 0.16, 0.3, 0.5, 0.8, 1.2, 2.0, 3.0]
@@ -474,6 +478,7 @@ class CarController(CarControllerBase):
self._ioniq_6_lane_change_ui_frames = 0
self._ioniq_6_long_tuning = Ioniq6LongitudinalTuningState()
self._genesis_g90_long_tuning = GenesisG90LongitudinalTuningState()
self._can_lead_data = CanLeadDataState()
self._dash_lat_disengage_blink_frame = 0
self._dash_lat_disengage_init = False
self._dash_prev_lat_active = False
@@ -483,6 +488,9 @@ class CarController(CarControllerBase):
CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS
)
self._ray_lfa_packer = CANPacker("hyundai_kia_ray_lfa") if self._ray_lfa_8byte else None
self._ray_pedal = CP.carFingerprint == CAR.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED
self._ray_pedal_packer = CANPacker("hyundai_kia_ray_pedal") if self._ray_pedal else None
self._ray_pedal_gas_last = 0.0
def _update_dash_icon_state(self, CC):
if CC.latActive:
@@ -503,7 +511,9 @@ class CarController(CarControllerBase):
return lka_icon, lfa_icon
def _get_canfd_scc_lead_state(self, CC, CS, now_nanos):
openpilot_lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or CC.hudControl.leadVisible)
openpilot_lead_visible = bool(
getattr(CS, "openpilot_lead_visible", False) or getattr(CC.hudControl, "leadVisible", False)
)
openpilot_lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
openpilot_lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -16.4, 34.7))
stock_camera_lead_fresh = now_nanos - getattr(CS, "stock_camera_lead_ts", 0) <= CANFD_CAMERA_LEAD_STALE_NS
@@ -757,6 +767,13 @@ class CarController(CarControllerBase):
can_canfd_blended = bool(self.CP.flags & HyundaiFlags.CAN_CANFD_BLENDED)
blended_hda2 = can_canfd_blended and bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING)
longitudinal_active = bool(self.long_active_ecu and getattr(CC, "longActive", False))
lead_visible = bool(getattr(CS, "openpilot_lead_visible", False) or getattr(hud_control, "leadVisible", False))
lead_distance = float(np.clip(getattr(CS, "openpilot_lead_distance", 0.0), 0.0, 204.7))
lead_rel_speed = float(np.clip(getattr(CS, "openpilot_lead_rel_speed", 0.0), -170.0, 239.5))
if lead_visible and lead_distance <= CANFD_LEAD_MIN_DISTANCE:
lead_distance = CANFD_FALLBACK_LEAD_DISTANCE
lead_rel_speed = 0.0
lead_data = self._can_lead_data.update(lead_distance, lead_rel_speed, lead_visible)
# HUD messages
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
@@ -765,6 +782,7 @@ class CarController(CarControllerBase):
if blended_hda2:
can_sends.extend(hyundaicanfd.create_steering_messages(
self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, 0.0,
lka_icon=lka_icon,
longitudinal_active=longitudinal_active,
))
if self.long_active_ecu:
@@ -775,6 +793,7 @@ class CarController(CarControllerBase):
left_lane_warning, right_lane_warning, CS.msg_364,
include_alerts=False,
counter_mod=0xF,
fcw_opt_usm=2 if apply_steer_req or lka_icon == 3 else 1,
))
if self.frame % 5 == 0:
can_sends.append(hyundaicanfd.create_suppress_lfa(
@@ -800,7 +819,11 @@ class CarController(CarControllerBase):
if not self.long_active_ecu:
if self.cancel_counter > CANCEL_BUTTON_DELAY_FRAMES:
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
elif CC.cruiseControl.resume:
elif self._ray_pedal and CC.longActive and CS.out.cruiseState.enabled:
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
can_sends.append(hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.CANCEL, self.CP))
self.last_button_frame = self.frame
elif CC.cruiseControl.resume and not self._ray_pedal:
# send resume at a max freq of 10Hz
if (self.frame - self.last_button_frame) * DT_CTRL > 0.1:
# send 25 messages at a time to increases the likelihood of resume being accepted
@@ -808,7 +831,24 @@ class CarController(CarControllerBase):
if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15:
self.last_button_frame = self.frame
else:
can_sends.extend(self._create_can_redneck_button_messages(CS))
if not self._ray_pedal:
can_sends.extend(self._create_can_redneck_button_messages(CS))
if self._ray_pedal and self.frame % 4 == 0:
pedal_ready = CS.ray_pedal_valid and CS.ray_pedal_state == 0
pedal_active = (CC.longActive and pedal_ready and not CC.cruiseControl.override and
not CS.out.gasPressed and not CS.out.brakePressed and
not CS.out.cruiseState.enabled and CS.out.vEgo >= self.CP.minEnableSpeed)
if pedal_active:
target = float(np.clip(accel / CarControllerParams.ACCEL_MAX * RAY_PEDAL_COMMAND_CAP,
0.0, RAY_PEDAL_COMMAND_CAP))
self._ray_pedal_gas_last = rate_limit(
target, self._ray_pedal_gas_last, -RAY_PEDAL_RATE_DOWN, RAY_PEDAL_RATE_UP,
)
else:
self._ray_pedal_gas_last = 0.0
can_sends.append(create_gas_interceptor_command(
self._ray_pedal_packer, self._ray_pedal_gas_last, (self.frame // 4) & 0xF))
if self.long_active_ecu and can_canfd_blended:
if blended_hda2:
@@ -836,7 +876,7 @@ class CarController(CarControllerBase):
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
hud_control, set_speed_in_units, stopping,
CC.cruiseControl.override, use_fca, self.CP,
main_cruise_enabled))
main_cruise_enabled, lead_data))
# 20 Hz LFA MFA message
if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.SEND_LFA.value or (self.long_active_ecu and blended_hda2)):
@@ -993,7 +1033,9 @@ class CarController(CarControllerBase):
left_sound_active=left_warning.sound_active, right_sound_active=right_warning.sound_active,
)
else:
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame)
adrv_messages = hyundaicanfd.create_adrv_messages(self.packer, self.CAN, self.frame,
car_fingerprint=self.CP.carFingerprint,
drive_gear=drive_gear)
can_sends.extend(adrv_messages)
# The front radar treats ADAS_DRV's 0x100 broadcast as its host heartbeat
# and stops publishing object tracks when it disappears.
@@ -1022,14 +1064,23 @@ class CarController(CarControllerBase):
CC.leftBlinker,
CC.rightBlinker))
if self.frame % 2 == 0:
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
if self.CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN:
scc_jerk_limits = get_hyundai_canfd_scc_jerk_limits(self.CP)
scc_jerk_limits = get_hyundai_canfd_scc_jerk_limits(self.CP, stopping, accel)
raw_accel = accel
accel = shape_hyundai_canfd_scc_accel(
self.CP, CC.enabled, CC.cruiseControl.override, stopping, accel, self.accel_last,
)
acc_kwargs = {
"direct_accel": True,
"raw_accel": raw_accel,
"jerk_upper": scc_jerk_limits[0],
"jerk_lower": scc_jerk_limits[1],
"lead_distance": lead_distance,
"lead_rel_speed": lead_rel_speed,
"lead_visible": lead_visible,
}
else:
lead_visible, lead_distance, lead_rel_speed = self._get_canfd_scc_lead_state(CC, CS, now_nanos)
acc_kwargs = {
"main_mode_acc": int(CS.out.cruiseState.available),
"direct_accel": True,
@@ -138,6 +138,9 @@ class CarState(CarStateBase):
self.buttons_counter = 0
self.main_cruise_on = False
self.main_cruise_tracking = bool(getattr(FPCP, "flags", 0) & HyundaiStarPilotFlags.MAIN_CRUISE_STATE_TRACKING)
if CP.carFingerprint == CAR.KIA_RAY_EV:
self.ray_pedal_state = 5
self.ray_pedal_valid = False
self.cruise_info = {}
self.msg_161 = {}
@@ -300,6 +303,7 @@ class CarState(CarStateBase):
cp = can_parsers[Bus.pt]
cp_cam = can_parsers[Bus.cam]
cp_alt = can_parsers.get(Bus.alt)
cp_pedal = can_parsers.get(Bus.party)
if self.CP.flags & HyundaiFlags.CANFD:
return self.update_canfd(can_parsers)
@@ -393,6 +397,11 @@ class CarState(CarStateBase):
ret.espDisabled = cp.vl["TCS11"]["TCS_PAS"] == 1
ret.espActive = cp.vl["TCS11"]["ABS_ACT"] == 1
ret.accFaulted = False if no_scc else cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED
if self.CP.carFingerprint == CAR.KIA_RAY_EV and self.CP.enableGasInterceptorDEPRECATED:
self.ray_pedal_valid = bool(cp_pedal is not None and cp_pedal.can_valid and
cp_pedal.ts_nanos["GAS_SENSOR"]["STATE"] > 0)
self.ray_pedal_state = int(cp_pedal.vl["GAS_SENSOR"]["STATE"]) if cp_pedal is not None else 5
ret.accFaulted = not self.ray_pedal_valid or self.ray_pedal_state != 0
if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV | HyundaiFlags.FCEV):
if self.CP.flags & HyundaiFlags.FCEV:
@@ -404,6 +413,12 @@ class CarState(CarStateBase):
else:
ret.gasPressed = bool(cp.vl["EMS16"]["CF_Ems_AclAct"])
if self.CP.carFingerprint == CAR.KIA_RAY_EV and self.CP.enableGasInterceptorDEPRECATED and self.ray_pedal_valid:
driver_pedal = cp_pedal.vl_raw["GAS_SENSOR"]
track1 = int.from_bytes(driver_pedal[:2], "big")
track2 = int.from_bytes(driver_pedal[2:4], "big")
ret.gasPressed = track1 > 272 or track2 > 513
# Gear Selection via Cluster - For those Kia/Hyundai which are not fully discovered, we can use the Cluster Indicator for Gear Selection,
# as this seems to be standard over all cars, but is not the preferred method.
if self.CP.flags & (HyundaiFlags.HYBRID | HyundaiFlags.EV):
@@ -748,4 +763,6 @@ class CarState(CarStateBase):
}
if CP.carFingerprint in ALT_BUS_LDA_BUTTON_CARS:
parsers[Bus.alt] = CANParser(DBC[CP.carFingerprint][Bus.pt], [("CLU13", 0)], 1)
if CP.carFingerprint == CAR.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED:
parsers[Bus.party] = CANParser("hyundai_kia_ray_pedal", [("GAS_SENSOR", 50)], 0)
return parsers
+12 -9
View File
@@ -1,5 +1,6 @@
import crcmod
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.lead_data import CanLeadData
from opendbc.car.hyundai.values import CAR, HyundaiFlags
hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf)
@@ -51,7 +52,7 @@ def create_lkas11(packer, frame, CP, apply_torque, steer_req,
# FcwOpt_USM 2 = Green car + lanes
# FcwOpt_USM 1 = White car + lanes
# FcwOpt_USM 0 = No car + lanes
values["CF_Lkas_FcwOpt_USM"] = lka_icon if CP.carFingerprint == CAR.GENESIS_G70_2020 else 2 if enabled else 1
values["CF_Lkas_FcwOpt_USM"] = lka_icon
# SysWarning 4 = keep hands on wheel
# SysWarning 5 = keep hands on wheel (red)
@@ -128,13 +129,13 @@ def create_lkas11_can_canfd_blended(packer, frame, CP, apply_steer, steer_req,
torque_fault, lkas11, sys_warning, sys_state, enabled,
left_lane, right_lane,
left_lane_depart, right_lane_depart, msg_364,
include_alerts=True, counter_mod=0x10):
include_alerts=True, counter_mod=0x10, fcw_opt_usm=None):
bus = CanBus(CP).ECAN
values = {
"CF_Lkas_LdwsActivemode": int(left_lane) + (int(right_lane) << 1),
"CF_Lkas_LdwsLHWarning": left_lane_depart,
"CF_Lkas_LdwsRHWarning": right_lane_depart,
"CF_Lkas_FcwOpt_USM": 2 if enabled else 1,
"CF_Lkas_FcwOpt_USM": (2 if enabled else 1) if fcw_opt_usm is None else fcw_opt_usm,
"CR_Lkas_StrToqReq": apply_steer,
"CF_Lkas_ActToi": steer_req,
"CF_Lkas_ToiFlt": torque_fault,
@@ -317,19 +318,20 @@ def create_acc_commands_can_canfd_blended_hda2(packer, enabled, accel, accel_las
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, set_speed, stopping, long_override, use_fca, CP,
main_cruise_enabled=True):
main_cruise_enabled=True, lead_data: CanLeadData | None = None):
commands = []
lead_data = lead_data or CanLeadData()
scc11_values = {
"MainMode_ACC": int(bool(main_cruise_enabled)),
"TauGapSet": hud_control.leadDistanceBars,
"VSetDis": set_speed if enabled else 0,
"AliveCounterACC": idx % 0x10,
"ObjValid": 1, # close lead makes controls tighter
"ACC_ObjStatus": 1, # close lead makes controls tighter
"ObjValid": int(lead_data.lead_visible),
"ACC_ObjStatus": int(lead_data.lead_visible),
"ACC_ObjLatPos": 0,
"ACC_ObjRelSpd": 0,
"ACC_ObjDist": 1, # close lead makes controls tighter
"ACC_ObjRelSpd": lead_data.lead_rel_speed,
"ACC_ObjDist": int(lead_data.lead_distance),
}
commands.append(packer.make_can_msg("SCC11", 0, scc11_values))
@@ -357,7 +359,8 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, hud_control, se
"JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values
"JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values
"ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage
"ObjGap": 2 if hud_control.leadVisible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead
"ObjGap": lead_data.object_gap, # 5: >30 m, 4: 25-30 m, 3: 20-25 m, 2: <20 m, 0: no lead
"ObjDistStat": lead_data.object_rel_gap,
}
commands.append(packer.make_can_msg("SCC14", 0, scc14_values))
@@ -8,6 +8,34 @@ from opendbc.car.crc import CRC16_XMODEM
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CANFD_ALT_BUTTONS_RESUME_CAR
_adrv_0x51_templates: dict[CAR, bytes] = {}
def cache_adrv_0x51_template(car_fingerprint: CAR, dat: bytes | None) -> None:
if car_fingerprint != CAR.KIA_EV6:
return
if dat is None:
_adrv_0x51_templates.pop(car_fingerprint, None)
elif len(dat) == 32 and any(dat[3:]):
_adrv_0x51_templates[car_fingerprint] = bytes(dat)
def create_adrv_0x51(packer, CAN, frame: int, car_fingerprint: CAR | None = None, drive_gear: bool = False):
template = _adrv_0x51_templates.get(car_fingerprint)
if template is None:
return packer.make_can_msg("ADRV_0x51", CAN.ACAN, {})
# EV6 MRR30 tracks stop when the ADAS takeover replaces this platform payload with zeros.
dat = bytearray(template)
dat[2] = (template[2] + frame + 1) & 0xFF
dat[3] = (dat[3] & ~0x1) | int(drive_gear)
crc = hkg_can_fd_checksum(0x51, None, dat)
dat[0] = crc & 0xFF
dat[1] = (crc >> 8) & 0xFF
return CanData(0x51, bytes(dat), CAN.ACAN)
def _set_value(msg: bytearray, sig, ival: int) -> None:
i = sig.lsb // 8
bits = sig.size
@@ -123,7 +151,12 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
else:
lkas_values = copy.copy(control_values)
lkas_values["LKA_AVAILABLE"] = 0
if CP.carFingerprint in (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN):
if CP.carFingerprint in (
CAR.KIA_CARNIVAL_4TH_GEN,
CAR.KIA_CARNIVAL_2025,
CAR.KIA_CARNIVAL_HEV_4TH_GEN,
CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN,
):
lkas_values["DAMP_FACTOR"] = 100
if lfa_base_values:
@@ -699,13 +732,13 @@ def create_ioniq_6_cluster_lane_change_messages(CAN, frame, side=None):
def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control,
main_mode_acc=1, jerk_lower=None, jerk_upper=None, direct_accel=False,
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None):
lead_distance=None, lead_rel_speed=None, lead_visible=None, cruise_info=None, raw_accel=None):
jerk = 5
jn = jerk / 50
if not enabled or gas_override:
a_val, a_raw = 0, 0
elif direct_accel:
a_raw = accel
a_raw = accel if raw_accel is None else raw_accel
a_val = accel
else:
a_raw = accel
@@ -783,15 +816,13 @@ def create_fca_warning_light(packer, CAN, frame):
return ret
def create_adrv_messages(packer, CAN, frame, blended_hda2=False):
def create_adrv_messages(packer, CAN, frame, blended_hda2=False, car_fingerprint=None, drive_gear=False):
# messages needed to car happy after disabling
# the ADAS Driving ECU to do longitudinal control
ret = []
values = {
}
ret.append(packer.make_can_msg("ADRV_0x51", CAN.ACAN, values))
ret.append(create_adrv_0x51(packer, CAN, frame, car_fingerprint, drive_gear))
if blended_hda2:
return ret
+29 -1
View File
@@ -2,6 +2,7 @@ import time
# Provenance: portions of HKG angle integration are adapted from sunnypilot/opendbc's
# hkg-angle-steering-2025 branch at cc4b08625. See CREDITS.md and THIRD_PARTY_NOTICES.md.
from opendbc.car import get_safety_config, structs, uds
from opendbc.car.hyundai import hyundaicanfd
from opendbc.car.hyundai.hyundaicanfd import CanBus
from opendbc.car.hyundai.values import HyundaiFlags, CAR, CarControllerParams, \
CANFD_UNSUPPORTED_LONGITUDINAL_CAR, \
@@ -43,6 +44,7 @@ ENABLE_BUTTONS = (ButtonType.accelCruise, ButtonType.decelCruise, ButtonType.can
ECU_DISABLE_TIMESTAMP = 0.0
KONA_NON_SCC_FCA_RADAR_ADDR = 0x602
KIA_EV9_ACCEL_MAX = 2.2
RAY_PEDAL_SENSOR_ADDR = 0x201
def apply_platform_longitudinal_params(ret: structs.CarParams) -> None:
@@ -302,6 +304,18 @@ class CarInterface(CarInterfaceBase):
elif ret.flags & HyundaiFlags.FCEV:
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.FCEV_GAS.value
if (candidate == CAR.KIA_RAY_EV and fingerprint[0].get(RAY_PEDAL_SENSOR_ADDR) == 6 and
ret.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS and
ret.safetyConfigs[-1].safetyParam & HyundaiStarPilotSafetyFlags.HAS_LDA_BUTTON):
ret.enableGasInterceptorDEPRECATED = True
ret.alphaLongitudinalAvailable = True
ret.openpilotLongitudinalControl = True
ret.pcmCruise = False
ret.radarUnavailable = True
ret.autoResumeSng = False
ret.minEnableSpeed = 5.0 # pedal-only: no commanded friction brake/standstill hold
ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.LONG.value
# Car specific configuration overrides
if candidate == CAR.GENESIS_G90:
@@ -378,11 +392,25 @@ class CarInterface(CarInterfaceBase):
skip_disable_ecu = True
if not skip_disable_ecu:
disable_can_recv = can_recv
if CP.carFingerprint == CAR.KIA_EV6 and can_recv is not None:
hyundaicanfd.cache_adrv_0x51_template(CP.carFingerprint, None)
base_can_recv = can_recv
adrv_bus = CanBus(CP).ACAN
def disable_can_recv(*args, **kwargs):
packets = base_can_recv(*args, **kwargs)
for packet in packets or []:
for msg in packet:
if msg.src == adrv_bus and msg.address == 0x51:
hyundaicanfd.cache_adrv_0x51_template(CP.carFingerprint, msg.dat)
return packets
# Try ECU disable. If it succeeds (IGN-ON mode), enable longitudinal.
# If it fails (READY mode returns NRC 0x22, or timeout), strip LONG safety flag
# so panda forwards stock SCC messages normally (lateral-only mode).
ecu_log(f"=== ECU DISABLE attempt: addr=0x{addr:x}, bus={bus} ===")
ecu_disabled = disable_ecu(can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
ecu_disabled = disable_ecu(disable_can_recv, can_send, bus=bus, addr=addr, com_cont_req=communication_control,
reset=bool(CP.flags & HyundaiFlags.CAN_CANFD_BLENDED))
if CP.carFingerprint in (CAR.HYUNDAI_IONIQ_6, CAR.HYUNDAI_IONIQ_5_PE):
@@ -0,0 +1,63 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class CanLeadData:
object_gap: int = 0
lead_distance: float = 0.0
lead_rel_speed: float = 0.0
lead_visible: bool = False
@property
def object_rel_gap(self) -> int:
return 0 if self.lead_distance == 0 else 2 if self.lead_rel_speed < -0.2 else 1
def _hysteresis_update(current, new_value, counter, threshold):
if new_value == current:
return current, 0
counter += 1
return (new_value, 0) if counter >= threshold else (current, counter)
class CanLeadDataState:
LEAD_HYSTERESIS_FRAMES = 50
def __init__(self):
self._lead_on_counter = 0
self._lead_off_counter = 0
self._gap_counter = 0
self._lead_visible = False
self._object_gap = 0
@staticmethod
def _get_object_gap(lead_distance: float) -> int:
if lead_distance == 0:
return 0
if lead_distance < 20:
return 2
if lead_distance < 25:
return 3
if lead_distance < 30:
return 4
return 5
def update(self, lead_distance: float, lead_rel_speed: float, lead_visible: bool) -> CanLeadData:
counter = self._lead_on_counter if lead_visible else self._lead_off_counter
self._lead_visible, counter = _hysteresis_update(
self._lead_visible, lead_visible, counter, self.LEAD_HYSTERESIS_FRAMES,
)
if lead_visible:
self._lead_on_counter = counter
self._lead_off_counter = 0
else:
self._lead_off_counter = counter
self._lead_on_counter = 0
object_gap = self._get_object_gap(lead_distance)
self._object_gap, self._gap_counter = _hysteresis_update(
self._object_gap, object_gap, self._gap_counter, self.LEAD_HYSTERESIS_FRAMES,
)
return CanLeadData(self._object_gap, lead_distance, lead_rel_speed, self._lead_visible)
@@ -27,6 +27,7 @@ from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, dec
from opendbc.car.hyundai.interface import CarInterface, KIA_EV9_ACCEL_MAX, get_communication_control_request
from opendbc.car.hyundai import hyundaican, hyundaicanfd
from opendbc.car.hyundai.hyundaicanfd import CanBus, hkg_can_fd_checksum
from opendbc.car.hyundai.lead_data import CanLeadData, CanLeadDataState
from opendbc.car.hyundai.radar_interface import MRREVO14F_RADAR_START_ADDR, MRR30_RADAR_START_ADDR, MRR35_RADAR_START_ADDR, \
RADAR_START_ADDR, RadarInterface, get_radar_track_config, radar_tracks_available
from opendbc.car.hyundai.values import CAMERA_SCC_CAR, CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, DATELESS_FUZZY_CARS, \
@@ -148,6 +149,60 @@ class TestHyundaiFingerprint:
assert get_communication_control_request(CAR.HYUNDAI_IONIQ_6) == radar_keepalive_request
def test_ev6_adrv_0x51_replays_factory_payload(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.KIA_EV6
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CANFD_LKA_STEERING | HyundaiFlags.EV)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
can_bus = CanBus(CP)
factory = bytes.fromhex("88ed2e091700ffff5e0d0000012006ff021c2200000000000800000010000000")
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, factory)
try:
address, dat, bus = hyundaicanfd.create_adrv_0x51(packer, can_bus, 7, CAR.KIA_EV6, drive_gear=True)
_, parked_dat, _ = hyundaicanfd.create_adrv_0x51(packer, can_bus, 8, CAR.KIA_EV6, drive_gear=False)
_, other_dat, _ = hyundaicanfd.create_adrv_0x51(packer, can_bus, 7, CAR.HYUNDAI_IONIQ_6)
finally:
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, None)
assert address == 0x51
assert bus == can_bus.ACAN
assert dat[2] == (factory[2] + 8) & 0xFF
assert dat[3:] == factory[3:]
assert int.from_bytes(dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(dat))
assert parked_dat[3] == factory[3] & ~0x1
assert parked_dat[4:] == factory[4:]
assert int.from_bytes(parked_dat[:2], "little") == hkg_can_fd_checksum(address, None, bytearray(parked_dat))
assert other_dat[3:] == bytes(29)
def test_ev6_init_captures_factory_adrv_0x51(self, monkeypatch):
fingerprint = gen_empty_fingerprint()
fingerprint[CanBus(None, fingerprint).CAM][0x50] = 16
radar_config = get_radar_track_config(CAR.KIA_EV6)
fingerprint[radar_config.bus][radar_config.start_addr] = radar_config.expected_length
car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")]
CP = CarInterface.get_params(CAR.KIA_EV6, fingerprint, car_fw, True, False, False, get_test_toggles())
factory = bytes.fromhex("6b657d090900e1ff000000000020ffff00000000000000000800000010000000")
def can_recv(*, wait_for_one=True):
msg = SimpleNamespace(address=0x51, src=CanBus(CP).ACAN, dat=factory)
return [[msg]]
def fake_disable_ecu(capturing_can_recv, *_args, **_kwargs):
capturing_can_recv(wait_for_one=True)
return True
monkeypatch.setattr("opendbc.car.hyundai.interface.disable_ecu", fake_disable_ecu)
CarInterface.init(CP, can_recv, None)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
try:
_, dat, _ = hyundaicanfd.create_adrv_0x51(packer, CanBus(CP), 0, CAR.KIA_EV6, drive_gear=True)
finally:
hyundaicanfd.cache_adrv_0x51_template(CAR.KIA_EV6, None)
assert dat[3:] == factory[3:]
def test_carnival_hev_low_speed_torque_rate_limits(self):
CP = CarInterface.get_params(CAR.KIA_CARNIVAL_HEV_4TH_GEN, gen_empty_fingerprint(), [],
False, False, False, None)
@@ -726,6 +781,45 @@ class TestHyundaiFingerprint:
} <= msg_addrs_buses
assert (0x364, 1) not in msg_addrs_buses
def test_palisade_telluride_hda2_aol_keeps_lkas_status_after_long_cancel(self):
fingerprint = gen_empty_fingerprint()
fingerprint[2][0x50] = 16
car_fw = [CarParams.CarFw(ecu=Ecu.adas, fwVersion=b"", address=0x730, brand="hyundai")]
CP = CarInterface.get_params(CAR.HYUNDAI_PALISADE_2023, fingerprint, car_fw, True, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 1
hud_control = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
leftLaneVisible=True,
rightLaneVisible=True,
leftLaneDepart=False,
rightLaneDepart=False,
leadDistanceBars=3,
leadVisible=False,
)
lfa_block_msg = {f"BYTE{i}": 0 for i in range(3, 24) if i != 7}
lfa_block_msg["COUNTER"] = 0
CS = SimpleNamespace(lfa_block_msg=lfa_block_msg, redneck_send_button=Buttons.NONE, lkas11={}, msg_364={},
out=SimpleNamespace(vEgoRaw=5.0))
CC = SimpleNamespace(enabled=False, latActive=True, longActive=False,
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False))
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
adas_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], 0)
ecan_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 1)
for steering_requested, icon, expected_status in ((True, 2, 2), (False, 1, 1)):
msgs = controller.create_can_msgs(steering_requested, 16 if steering_requested else 0, False,
0.0, 0.0, False, hud_control, actuators, CS, CC, icon, icon)
adas_parser.update([(1, [msg for msg in msgs if msg[0] == 0x50])])
ecan_parser.update([(1, [msg for msg in msgs if msg[0] == 0x340])])
assert adas_parser.vl["LKAS"]["LKA_ICON"] == icon
assert adas_parser.vl["LKAS"]["STEER_REQ"] == int(steering_requested)
assert ecan_parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == expected_status
assert ecan_parser.vl["LKAS11"]["CF_Lkas_ActToi"] == int(steering_requested)
assert not any(msg[0] == 0x364 for msg in msgs)
def test_g70_aol_uses_active_lkas_icon(self):
CP = CarInterface.get_params(CAR.GENESIS_G70_2020, gen_empty_fingerprint(), [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
@@ -748,6 +842,48 @@ class TestHyundaiFingerprint:
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 2
@pytest.mark.parametrize(("candidate", "expected_status"), (
(CAR.KIA_NIRO_PHEV_2022, 2),
(CAR.KIA_NIRO_HEV_2021, 2),
))
def test_classic_niro_aol_keeps_active_lkas_status(self, candidate, expected_status):
CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False, get_test_toggles())
controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 1
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0)], 0)
hud_control = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
leftLaneVisible=True,
rightLaneVisible=True,
leftLaneDepart=False,
rightLaneDepart=False,
leadVisible=False,
)
CS = SimpleNamespace(lkas11=parser.vl["LKAS11"])
CC = SimpleNamespace(
enabled=False,
latActive=True,
longActive=False,
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False),
)
actuators = SimpleNamespace(longControlState=LongCtrlState.off)
msgs = controller.create_can_msgs(True, 156, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 2, 0)
lkas11 = next(msg for msg in msgs if msg[0] == 0x340)
parser.update([(1, [lkas11])])
assert parser.vl["LKAS11"]["CF_Lkas_ActToi"] == 1
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == expected_status
CC.latActive = False
msgs = controller.create_can_msgs(False, 0, False, 0.0, 0.0, False, hud_control, actuators, CS, CC, 1, 0)
lkas11 = next(msg for msg in msgs if msg[0] == 0x340)
parser.update([(2, [lkas11])])
assert parser.vl["LKAS11"]["CF_Lkas_ActToi"] == 0
assert parser.vl["LKAS11"]["CF_Lkas_FcwOpt_USM"] == 1
def test_kona_non_scc_uses_no_individual_lane_lkas_status(self):
CP = CarInterface.get_params(CAR.HYUNDAI_KONA_NON_SCC, gen_empty_fingerprint(), [], False, False, False, None)
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
@@ -2535,7 +2671,7 @@ class TestHyundaiFingerprint:
assert parser.vl["LKAS_ALT"]["ADAS_ACIAnglTqRedcGainVal"] == pytest.approx(0.0)
assert parser.vl["LKAS_ALT"]["ADAS_StrAnglReqVal"] == pytest.approx(8.5)
def test_gv70_electrified_uses_generic_lkas_status_payload(self):
def test_gv70_electrified_uses_clean_damped_lkas_status_payload(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
@@ -2580,11 +2716,11 @@ class TestHyundaiFingerprint:
parser.update([(1, lkas_msgs)])
assert parser.can_valid
assert parser.vl["LKAS"]["HAS_LANE_SAFETY"] == 0
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 0
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 100
assert parser.vl["LKAS"]["TORQUE_REQUEST"] == 0
assert parser.vl["LKAS"]["STEER_REQ"] == 1
assert parser.vl["LKAS"]["STEER_MODE"] == 0
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 0
assert parser.vl["LKAS"]["STEER_MODE"] == 2
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 3
lfa_parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LFA", 0)], can_bus.ECAN)
lfa_msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 0, 0.0)
@@ -2608,6 +2744,27 @@ class TestHyundaiFingerprint:
if controller.packer.dbc.addr_to_msg[addr].name in ("LFA", "LKAS")]
assert steering_names == [("LFA", can_bus.ECAN), ("LKAS", can_bus.ACAN)]
def test_gv70_electrified_stock_long_uses_damped_lkas_request(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.EV | HyundaiFlags.CANFD_LKA_STEERING)
CP.openpilotLongitudinalControl = False
controller = CarController(DBC[CP.carFingerprint], CP)
can_bus = CanBus(CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS", 0)], can_bus.ACAN)
msgs = hyundaicanfd.create_steering_messages(controller.packer, CP, can_bus, True, True, 123, 0.0)
assert [(controller.packer.dbc.addr_to_msg[addr].name, bus) for addr, _, bus in msgs] == [("LKAS", can_bus.ACAN)]
parser.update([(1, msgs)])
assert parser.can_valid
assert parser.vl["LKAS"]["TORQUE_REQUEST"] == 123
assert parser.vl["LKAS"]["STEER_REQ"] == 1
assert parser.vl["LKAS"]["HAS_LANE_SAFETY"] == 0
assert parser.vl["LKAS"]["DAMP_FACTOR"] == 100
assert parser.vl["LKAS"]["STEER_MODE"] == 2
assert parser.vl["LKAS"]["NEW_SIGNAL_2"] == 3
@pytest.mark.parametrize(("car", "powertrain_flag"), [
(CAR.HYUNDAI_IONIQ_5, HyundaiFlags.EV),
(CAR.HYUNDAI_IONIQ_6, HyundaiFlags.EV),
@@ -2684,10 +2841,11 @@ class TestHyundaiFingerprint:
actuators=SimpleNamespace(longControlState=LongCtrlState.pid),
cruiseControl=SimpleNamespace(override=False, cancel=False, resume=False),
leftBlinker=False, rightBlinker=False,
hudControl=SimpleNamespace(leadDistanceBars=3),
hudControl=SimpleNamespace(leadDistanceBars=3, leadVisible=True),
)
cs = SimpleNamespace(
stock_lfa_msg=None, stock_lkas_msg=None,
openpilot_lead_visible=True, openpilot_lead_distance=37.5, openpilot_lead_rel_speed=-1.3,
out=SimpleNamespace(steeringAngleDeg=0.0, gearShifter=structs.CarState.GearShifter.drive),
)
@@ -2700,7 +2858,8 @@ class TestHyundaiFingerprint:
parser.update([(1, scc_msgs)])
assert parser.can_valid
assert parser.vl["SCC_CONTROL"]["MainMode_ACC"] == 1
assert parser.vl["SCC_CONTROL"]["ACC_ObjDist"] == pytest.approx(1.0)
assert parser.vl["SCC_CONTROL"]["ACC_ObjDist"] == pytest.approx(37.5)
assert parser.vl["SCC_CONTROL"]["ACC_ObjRelSpd"] == pytest.approx(-1.3)
assert parser.vl["SCC_CONTROL"]["ObjValid"] == 0
assert parser.vl["SCC_CONTROL"]["aReqValue"] == pytest.approx(-0.1)
assert parser.vl["SCC_CONTROL"]["aReqRaw"] == pytest.approx(-1.0)
@@ -2995,6 +3154,50 @@ class TestHyundaiFingerprint:
assert parser.vl["SCC14"]["ComfortBandUpper"] == pytest.approx(0.0)
assert parser.vl["SCC14"]["ComfortBandLower"] == pytest.approx(0.0)
assert parser.vl["SCC14"]["JerkLowerLimit"] == pytest.approx(5.0)
assert parser.vl["SCC11"]["ObjValid"] == 0
assert parser.vl["SCC11"]["ACC_ObjDist"] == 0
assert parser.vl["SCC14"]["ObjGap"] == 0
assert parser.vl["SCC14"]["ObjDistStat"] == 0
def test_can_acc_commands_show_approaching_lead(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.HYUNDAI_ELANTRA_2021
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("SCC11", 0), ("SCC14", 0)], 0)
lead_data = CanLeadData(object_gap=4, lead_distance=27.5, lead_rel_speed=-1.3, lead_visible=True)
msgs = hyundaican.create_acc_commands(packer, enabled=True, accel=0.0, upper_jerk=1.0, idx=3,
hud_control=SimpleNamespace(leadDistanceBars=3), set_speed=42,
stopping=False, long_override=False, use_fca=False, CP=CP,
lead_data=lead_data)
parser.update([(1, msgs)])
assert parser.can_valid
assert parser.vl["SCC11"]["ObjValid"] == 1
assert parser.vl["SCC11"]["ACC_ObjStatus"] == 1
assert parser.vl["SCC11"]["ACC_ObjDist"] == pytest.approx(27.0)
assert parser.vl["SCC11"]["ACC_ObjRelSpd"] == pytest.approx(-1.3)
assert parser.vl["SCC14"]["ObjGap"] == 4
assert parser.vl["SCC14"]["ObjDistStat"] == 2
def test_can_lead_data_hysteresis_and_distance_bands(self):
state = CanLeadDataState()
for _ in range(state.LEAD_HYSTERESIS_FRAMES - 1):
lead_data = state.update(18.0, -0.5, True)
assert not lead_data.lead_visible
assert lead_data.object_gap == 0
lead_data = state.update(18.0, -0.5, True)
assert lead_data.lead_visible
assert lead_data.object_gap == 2
assert lead_data.object_rel_gap == 2
for _ in range(state.LEAD_HYSTERESIS_FRAMES):
lead_data = state.update(32.0, 0.5, True)
assert lead_data.object_gap == 5
assert lead_data.object_rel_gap == 1
def test_can_acc_commands_follow_sonata_main_cruise_state(self):
CP = CarParams.new_message()
@@ -0,0 +1,170 @@
from types import SimpleNamespace
import pytest
from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, gen_empty_fingerprint
from opendbc.car.hyundai.carcontroller import CarController
from opendbc.car.hyundai.carstate import CarState
from opendbc.car.hyundai.interface import CarInterface
from opendbc.car.hyundai.values import CAR, DBC, HyundaiSafetyFlags
from opendbc.car.structs import CarControl
def ray_fingerprint(sensor_length=6, lfa_length=8):
fingerprint = gen_empty_fingerprint()
fingerprint[0][0x201] = sensor_length
fingerprint[0][0x391] = 8
fingerprint[2][0x485] = lfa_length
return fingerprint
@pytest.mark.parametrize(("candidate", "fingerprint", "has_pedal"), [
(CAR.KIA_RAY_EV, ray_fingerprint(), True),
(CAR.KIA_RAY_EV, ray_fingerprint(sensor_length=8), False),
(CAR.KIA_RAY_EV, ray_fingerprint(lfa_length=4), False),
(CAR.HYUNDAI_KONA_EV_NON_SCC, ray_fingerprint(), False),
])
def test_ray_pedal_fingerprint_isolation(candidate, fingerprint, has_pedal):
CP = CarInterface.get_params(candidate, fingerprint, [], False, False, False, None)
assert CP.enableGasInterceptorDEPRECATED is has_pedal
assert CP.openpilotLongitudinalControl is has_pedal
if has_pedal:
assert not CP.pcmCruise
assert CP.safetyConfigs[-1].safetyParam == 0x9405
assert CP.minEnableSpeed == 5.0
assert not CP.autoResumeSng
FPCP = CarInterface.get_starpilot_params(candidate, fingerprint, [], CP, SimpleNamespace())
assert FPCP.canUsePedal
assert not FPCP.pcmCruiseSpeed
assert not FPCP.redneckCruiseAvailable
else:
assert CP.pcmCruise
assert not (CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.LONG)
def test_ray_pedal_safety_signature_is_unique_across_hyundai_platforms():
for candidate in CAR:
for alpha_long in (False, True):
CP = CarInterface.get_params(candidate, ray_fingerprint(), [],
alpha_long, False, False, None)
has_ray_signature = (CP.safetyConfigs[-1].safetyParam & ~(32 | 128 | 2048)) == 0x9405
assert has_ray_signature is (candidate == CAR.KIA_RAY_EV)
assert CP.enableGasInterceptorDEPRECATED is (candidate == CAR.KIA_RAY_EV)
def test_ray_pedal_parser_validates_actual_route_frames():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
parser = CarState(CP, None).get_can_parsers(CP)[Bus.party]
assert parser.dbc_name == "hyundai_kia_ray_pedal"
# Consecutive bus-0 GAS_SENSOR frames from Sept. 15 Ray rlog segment 4.
samples = [bytes.fromhex(s) for s in (
"01f403d55de8", "01f603d55ef1", "01f403d55f51", "01f603d3503f",
"01f903d551ab", "01f903d552a4", "01f703d55370",
)]
for idx, dat in enumerate(samples):
parser.update([(1_000_000_000 + idx * 20_000_000, [(0x201, dat, 0)])])
assert parser.can_valid
assert parser.vl["GAS_SENSOR"]["STATE"] == 5 # FAULT_TIMEOUT: no 0x200 was sent
prior = parser.vl_raw["GAS_SENSOR"]
bad = bytearray(samples[-1])
bad[-1] ^= 1
parser.update([(1_160_000_000, [(0x201, bytes(bad), 0)])])
assert parser.vl_raw["GAS_SENSOR"] == prior
def test_ray_pedal_fault_clears_only_with_healthy_sensor_state():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
state = CarState(CP, None)
parsers = state.get_can_parsers(CP)
packer = CANPacker("hyundai_kia_ray_pedal")
sensor = packer.make_can_msg("GAS_SENSOR", 0, {
"INTERCEPTOR_GAS": 0, "INTERCEPTOR_GAS2": 0,
"STATE": 0, "COUNTER_PEDAL": 1,
})
for parser in parsers.values():
parser.update([(1_000_000_000, [sensor])])
ret, _ = state.update(parsers, SimpleNamespace())
assert state.ray_pedal_valid
assert state.ray_pedal_state == 0
assert not ret.accFaulted
def test_ray_driver_override_uses_physical_interceptor_tracks():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
state = CarState(CP, None)
parsers = state.get_can_parsers(CP)
native_gas = (0x371, bytes.fromhex("004e008000ae0700"), 0)
physical_rest = (0x201, bytes.fromhex("010801f30cef"), 0)
for parser in parsers.values():
parser.update([(1_000_000_000, [native_gas, physical_rest])])
ret, _ = state.update(parsers, SimpleNamespace())
assert state.ray_pedal_valid
assert not ret.gasPressed
packer = CANPacker("hyundai_kia_ray_pedal")
physical_press = packer.make_can_msg("GAS_SENSOR", 0, {
"INTERCEPTOR_GAS": (310 - 264) * 0.672,
"INTERCEPTOR_GAS2": (593 - 497) * 0.332,
"STATE": 0, "COUNTER_PEDAL": 13,
})
for parser in parsers.values():
parser.update([(1_020_000_000, [physical_press])])
ret, _ = state.update(parsers, SimpleNamespace())
assert ret.gasPressed
def test_ray_without_pedal_keeps_native_gas_detection():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(sensor_length=8), [], False, False, False, None)
assert not CP.enableGasInterceptorDEPRECATED
state = CarState(CP, None)
parsers = state.get_can_parsers(CP)
assert Bus.party not in parsers
native_gas = (0x371, bytes.fromhex("004e008000ae0700"), 0)
for parser in parsers.values():
parser.update([(1_000_000_000, [native_gas])])
ret, _ = state.update(parsers, SimpleNamespace())
assert ret.gasPressed
def test_ray_controller_heartbeats_and_only_actuates_when_ready():
CP = CarInterface.get_params(CAR.KIA_RAY_EV, ray_fingerprint(), [], False, False, False, None)
controller = CarController(DBC[CP.carFingerprint], CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS11", 0), ("CLU11", 0)], 0)
CS = SimpleNamespace(
lkas11=parser.vl["LKAS11"], clu11=parser.vl["CLU11"],
out=SimpleNamespace(vEgo=12.0, gasPressed=False, brakePressed=False,
cruiseState=SimpleNamespace(enabled=False)),
ray_pedal_valid=True, ray_pedal_state=5, is_metric=True,
)
CC = SimpleNamespace(
enabled=True, longActive=True, latActive=True,
cruiseControl=SimpleNamespace(cancel=False, resume=False, override=False),
)
hud = SimpleNamespace(
visualAlert=CarControl.HUDControl.VisualAlert.none,
leftLaneVisible=True, rightLaneVisible=True,
leftLaneDepart=False, rightLaneDepart=False,
)
actuators = SimpleNamespace(longControlState=CarControl.Actuators.LongControlState.pid)
def pedal_msg(accel, frame):
controller.frame = frame
messages = controller.create_can_msgs(True, 0, False, 0.0, accel, False,
hud, actuators, CS, CC, 2, 0)
return next(dat for addr, dat, bus in messages if addr == 0x200 and bus == 0)
assert pedal_msg(2.0, 0)[:4] == bytes(4) # fault timeout: heartbeat only
CS.ray_pedal_state = 0
assert pedal_msg(2.0, 4)[:4] != bytes(4)
CS.out.gasPressed = True
assert pedal_msg(2.0, 8)[:4] == bytes(4)
CS.out.gasPressed = False
assert pedal_msg(-1.0, 12)[:4] == bytes(4) # decel = EV lift/regen, not gas
CS.out.cruiseState.enabled = True
controller.frame = 16
messages = controller.create_can_msgs(True, 0, False, 0.0, 2.0, False,
hud, actuators, CS, CC, 2, 0)
assert next(dat for addr, dat, bus in messages if addr == 0x200 and bus == 0)[:4] == bytes(4)
assert any(addr == 0x4F1 and bus == 0 for addr, _, bus in messages) # cancel stock CC
+6 -1
View File
@@ -233,6 +233,9 @@ class CarInterfaceBase(ABC):
fp_ret.flags |= int(HondaStarPilotFlags.HAS_CAMERA_MESSAGES)
elif platform in HYUNDAI:
if candidate == HYUNDAI.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED:
fp_ret.canUsePedal = True
fp_ret.pcmCruiseSpeed = False
if candidate in CANFD_CAR:
hda2 = Ecu.adas in [fw.ecu for fw in car_fw]
CAN = CanBus(None, fingerprint, bool(CP.flags & HyundaiFlags.CANFD_LKA_STEERING))
@@ -244,7 +247,9 @@ class CarInterfaceBase(ABC):
if candidate != HYUNDAI.KIA_RAY_EV and not (CP.flags & HyundaiFlags.CANFD) and 0x53E in fingerprint[2]:
fp_ret.flags |= HyundaiStarPilotFlags.HAS_LKAS12.value
fp_ret.redneckCruiseAvailable = bool(CP.flags & HyundaiFlags.NON_SCC) and not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS)
fp_ret.redneckCruiseAvailable = (bool(CP.flags & HyundaiFlags.NON_SCC) and
not bool(CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS) and
not (candidate == HYUNDAI.KIA_RAY_EV and CP.enableGasInterceptorDEPRECATED))
if fp_ret.redneckCruiseAvailable and params.get_bool("RedneckCruise"):
fp_ret.pcmCruiseSpeed = False
CP.openpilotLongitudinalControl = True
@@ -130,14 +130,12 @@ class CarController(CarControllerBase):
self.angle_override_confirm_frames = 0
self.angle_handoff_active = False
def _angle_manual_handoff(self, CS, lat_active, use_steering_pressed=False):
def _angle_manual_handoff(self, CS, lat_active):
if not lat_active:
self._reset_angle_handoff()
return False
driver_override = self._update_angle_driver_override(CS)
if use_steering_pressed:
driver_override = driver_override or getattr(CS.out, "steeringPressed", False)
steering_rate = abs(getattr(CS.out, "steeringRateDeg", 0.0))
if driver_override:
self.angle_handoff_active = True
@@ -216,33 +214,23 @@ class CarController(CarControllerBase):
else:
self.ascent_aol_arm_frames = _ASCENT_AOL_ARM_FRAMES if lkas_available else 0
manual_handoff = self._angle_manual_handoff(
CS, lkas_available, use_steering_pressed=self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023,
)
if self.CP.carFingerprint == CAR.SUBARU_OUTBACK_2023:
manual_handoff = False
else:
manual_handoff = self._angle_manual_handoff(CS, lkas_available)
lkas_active = lkas_available and not manual_handoff
if lkas_active and not self.angle_lkas_active:
self.apply_steer_last = CS.out.steeringAngleDeg
if self.CP.carFingerprint in (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023):
apply_steer = apply_std_steer_angle_limits(
CC.actuators.steeringAngleDeg,
self.apply_steer_last,
CS.out.vEgoRaw,
CS.out.steeringAngleDeg,
lkas_active,
self.p.FIXED_ANGLE_LIMITS,
)
else:
apply_steer = apply_steer_angle_limits_vm(
steer_target,
self.apply_steer_last,
CS.out.vEgoRaw,
CS.out.steeringAngleDeg,
lkas_active,
self.p,
self.VM,
)
apply_steer = apply_std_steer_angle_limits(
CC.actuators.steeringAngleDeg,
self.apply_steer_last,
CS.out.vEgoRaw,
CS.out.steeringAngleDeg,
lkas_active,
self.p.FIXED_ANGLE_LIMITS,
)
self.apply_steer_last = apply_steer
self.angle_lkas_active = lkas_active
return subarucan.create_steering_control_angle(self.packer, apply_steer, lkas_active, self.angle_bus)
@@ -380,9 +368,11 @@ class CarController(CarControllerBase):
CC.longActive, hud_control.leadVisible,
self.status_bus))
can_sends.append(subarucan.create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus))
can_sends.append(subarucan.create_es_lkas_state(
self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
hud_control.leftLaneDepart, hud_control.rightLaneDepart, self.status_bus,
))
if self.CP.flags & SubaruFlags.SEND_INFOTAINMENT:
can_sends.append(subarucan.create_es_infotainment(self.packer, self.frame // 10, CS.es_infotainment_msg,
@@ -643,8 +643,8 @@ def test_angle_controller_uses_fixed_angle_rate_limits(platform):
assert CS.out.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < -25.0
@pytest.mark.parametrize("platform", (CAR.SUBARU_ASCENT_2023, CAR.SUBARU_OUTBACK_2023))
def test_angle_controller_reengages_immediately_after_manual_steering_stops(platform):
def test_ascent_angle_controller_reengages_immediately_after_manual_steering_stops():
platform = CAR.SUBARU_ASCENT_2023
CP = CarInterface.get_non_essential_params(platform)
controller = CarController({}, CP)
CC = SimpleNamespace(enabled=True, latActive=True, actuators=SimpleNamespace(steeringAngleDeg=-10.0))
@@ -775,7 +775,7 @@ def test_ascent_angle_controller_does_not_delay_normal_engagement():
def test_lkas_hud_state_uses_angle_request_state():
update_source = inspect.getsource(CarController.update)
assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, self._lkas_status_active(CC)" in update_source
assert "CS.es_lkas_state_msg, self._lkas_status_active(CC), hud_control.visualAlert" in update_source
assert "create_es_lkas_state(self.packer, self.frame // 10, CS.es_lkas_state_msg, CC.enabled" not in update_source
@@ -795,7 +795,7 @@ def test_lkas_hud_active_bit_follows_lateral_state(enabled, expected):
assert parser.vl["ES_LKAS_State"]["LKAS_ACTIVE"] == expected
def test_outback_manual_steering_releases_angle_request_before_lkas_fault():
def test_outback_manual_steering_keeps_cooperative_angle_request():
CP = CarInterface.get_non_essential_params(CAR.SUBARU_OUTBACK_2023)
controller = CarController({}, CP)
CC = SimpleNamespace(
@@ -807,19 +807,22 @@ def test_outback_manual_steering_releases_angle_request_before_lkas_fault():
vEgoRaw=0.9,
steeringAngleDeg=-57.0,
steeringRateDeg=-45.0,
steeringTorque=-127.0,
steeringTorque=0.0,
steeringPressed=True,
gearShifter=structs.CarState.GearShifter.drive,
standstill=False,
))
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("ES_LKAS_ANGLE", 0)], CanBus.main)
msg = controller.lateral_angle(CC, CS)
parser.update([(1, [msg])])
for frame, steering_torque in enumerate((-79.0, -81.0, -170.0, -250.0, -250.0, 79.0, 81.0, 170.0, 250.0, 250.0), start=1):
CS.out.steeringTorque = steering_torque
CS.out.steeringPressed = abs(steering_torque) > 80.0
msg = controller.lateral_angle(CC, CS)
parser.update([(frame, [msg])])
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 0
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] == pytest.approx(CS.out.steeringAngleDeg)
assert not controller._lkas_status_active(CC)
assert parser.vl["ES_LKAS_ANGLE"]["LKAS_Request"] == 1
assert CC.actuators.steeringAngleDeg < parser.vl["ES_LKAS_ANGLE"]["LKAS_Output"] < CS.out.steeringAngleDeg
assert controller._lkas_status_active(CC)
def test_ascent_hud_waits_for_angle_request():
@@ -5,9 +5,10 @@ from opendbc.car.lateral import apply_steer_angle_limits_vm
from opendbc.car.interfaces import CarControllerBase
from opendbc.car.tesla.coop_steering import CooperativeSteeringController
from opendbc.car.tesla.teslacan import TeslaCAN
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
from opendbc.car.tesla.preap.carcontroller import PreAPLongController, init_preap_can
from opendbc.car.tesla.preap.stock_cc_spoofer import StockCCSpoofer
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags
from opendbc.car.tesla.values import CANBUS, CAR, CarControllerParams, TeslaSafetyFlags, LEGACY_CARS
from opendbc.car.vehicle_model import VehicleModel
def get_safety_CP():
@@ -24,6 +25,7 @@ class CarController(CarControllerBase):
self.coop_enabled = CP.carFingerprint == CAR.TESLA_MODEL_3 and any(
config.safetyParam & TeslaSafetyFlags.COOP_STEERING.value for config in CP.safetyConfigs
)
self._clear_steering_limit_info()
self.packer = CANPacker(dbc_names[Bus.party])
self.tesla_can = TeslaCAN(self.packer)
self.preap_long = None
@@ -38,9 +40,37 @@ class CarController(CarControllerBase):
self.stock_cc = StockCCSpoofer()
from opendbc.car.tesla.interface import CarInterface
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_PREAP))
elif CP.carFingerprint in LEGACY_CARS:
self.packers = {
CANBUS.party: CANPacker(dbc_names[Bus.party]),
}
self.tesla_can = TeslaCANRaven(self.packers)
from opendbc.car.tesla.interface import CarInterface
self.VM = VehicleModel(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1))
def _clear_steering_limit_info(self):
self.steering_limit_info_valid = False
self.model_limit_error_deg = 0.0
self.resume_limit_error_deg = 0.0
self.cooperative_limit_error_deg = 0.0
self.cooperative_offset_deg = 0.0
self.steering_limit_mono_time = 0
self.combined_limit_error_deg = 0.0
def get_steering_limit_info(self) -> dict[str, bool | float | int]:
return {
"valid": self.steering_limit_info_valid,
"modelLimitErrorDeg": self.model_limit_error_deg,
"resumeLimitErrorDeg": self.resume_limit_error_deg,
"cooperativeLimitErrorDeg": self.cooperative_limit_error_deg,
"cooperativeOffsetDeg": self.cooperative_offset_deg,
"monoTime": self.steering_limit_mono_time,
"combinedLimitErrorDeg": self.combined_limit_error_deg,
}
def update(self, CC, CS, now_nanos, starpilot_toggles):
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
self._clear_steering_limit_info()
return self._update_preap(CC, CS)
actuators = CC.actuators
@@ -48,8 +78,12 @@ class CarController(CarControllerBase):
# Preserve the stock controller path unless cooperative steering is explicitly enabled.
lat_active = CC.latActive and (not CS.out.steeringDisengage if self.coop_enabled else CS.hands_on_level < 3)
if not (self.coop_enabled and lat_active):
self._clear_steering_limit_info()
if self.frame % 2 == 0:
requested_angle = actuators.steeringAngleDeg
# Angular rate limit based on speed
self.apply_angle_last = apply_steer_angle_limits_vm(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgoRaw, CS.out.steeringAngleDeg,
lat_active, CarControllerParams, self.VM)
@@ -57,9 +91,34 @@ class CarController(CarControllerBase):
self.apply_angle_command_last, lat_active = self.coop_steer.update(
self.apply_angle_last, lat_active, self.coop_enabled, CS, self.VM,
)
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
if self.frame % 10 == 0:
if self.coop_enabled and lat_active:
model_limit_error_deg = abs(requested_angle - self.apply_angle_last)
resume_limit_error_deg = self.coop_steer.resume_limit_error_deg
cooperative_limit_error_deg = self.coop_steer.cooperative_limit_error_deg
cooperative_offset_deg = self.coop_steer.cooperative_offset_deg
combined_limit_error_deg = abs(requested_angle + cooperative_offset_deg - self.apply_angle_command_last)
limit_values = (model_limit_error_deg, resume_limit_error_deg, cooperative_limit_error_deg,
cooperative_offset_deg, combined_limit_error_deg)
if all(np.isfinite(value) for value in limit_values):
self.steering_limit_info_valid = True
self.model_limit_error_deg = model_limit_error_deg
self.resume_limit_error_deg = resume_limit_error_deg
self.cooperative_limit_error_deg = cooperative_limit_error_deg
self.cooperative_offset_deg = cooperative_offset_deg
self.steering_limit_mono_time = now_nanos
self.combined_limit_error_deg = combined_limit_error_deg
else:
self._clear_steering_limit_info()
if self.CP.carFingerprint in LEGACY_CARS:
cntr = (self.frame // 2) % 16
can_sends.append(self.tesla_can.create_steering_control(cntr, self.apply_angle_command_last, lat_active))
else:
can_sends.append(self.tesla_can.create_steering_control(self.apply_angle_command_last, lat_active))
if self.frame % 10 == 0 and self.CP.carFingerprint not in LEGACY_CARS:
can_sends.append(self.tesla_can.create_steering_allowed())
# Longitudinal control
@@ -68,13 +127,21 @@ class CarController(CarControllerBase):
state = 13 if CC.cruiseControl.cancel else 4 # 4=ACC_ON, 13=ACC_CANCEL_GENERIC_SILENT
accel = float(np.clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX))
cntr = (self.frame // 4) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, self.frame, CS.out.vEgo, CS.out.gasPressed))
if self.CP.carFingerprint in LEGACY_CARS:
hw1_accel = accel if CC.longActive and not CC.cruiseControl.cancel else 0.
hw1_active = CC.longActive and not CC.cruiseControl.cancel
can_sends.append(self.tesla_can.create_longitudinal_command(state, hw1_accel, cntr, CS.out.vEgo, hw1_active, CS.out.gasPressed))
else:
can_sends.append(self.tesla_can.create_longitudinal_command(state, accel, cntr, self.frame, CS.out.vEgo, CS.out.gasPressed))
else:
# Increment counter so cancel is prioritized even without openpilot longitudinal
if CC.cruiseControl.cancel:
cntr = (CS.das_control["DAS_controlCounter"] + 1) % 8
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, self.frame, CS.out.vEgo, False))
if self.CP.carFingerprint in LEGACY_CARS:
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, CS.out.vEgo, False, CS.out.gasPressed))
else:
can_sends.append(self.tesla_can.create_longitudinal_command(13, 0, cntr, self.frame, CS.out.vEgo, False))
# TODO: HUD control
new_actuators = actuators.as_builder()
+103 -3
View File
@@ -4,7 +4,10 @@ from opendbc.can import CANDefine, CANParser
from opendbc.car import Bus, structs
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import CarStateBase
from opendbc.car.tesla.values import DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags, CAR
from opendbc.car.tesla.values import (
DBC, CANBUS, GEAR_MAP, STEER_DISENGAGE_THRESHOLD, STEER_THRESHOLD, TeslaSafetyFlags,
CAR, LEGACY_CARS,
)
from opendbc.car.tesla.preap.carstate import get_preap_can_parsers, update_preap
from opendbc.car.tesla.preap.engagement import PreAPEngagement
from opendbc.car.tesla.preap.nap_conf import nap_conf
@@ -25,8 +28,19 @@ class CarState(CarStateBase):
def __init__(self, CP, FPCP):
super().__init__(CP, FPCP)
self.can_define = CANDefine(DBC[CP.carFingerprint][Bus.party])
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
self.can_define.dv["DI_torque2"]["DI_gear"]
if CP.carFingerprint in LEGACY_CARS:
self.can_define_party = CANDefine(DBC[CP.carFingerprint][Bus.party])
self.can_define_pt = CANDefine(DBC[CP.carFingerprint][Bus.pt])
self.can_define_chassis = CANDefine(DBC[CP.carFingerprint][Bus.chassis])
self.can_defines = {
**self.can_define_party.dv,
**self.can_define_pt.dv,
**self.can_define_chassis.dv,
}
self.shifter_values = self.can_defines["DI_torque2"]["DI_gear"]
else:
self.shifter_values = self.can_define.dv["DI_systemStatus"]["DI_gear"] if CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP else \
self.can_define.dv["DI_torque2"]["DI_gear"]
self.autopark = False
self.autopark_prev = False
@@ -75,6 +89,8 @@ class CarState(CarStateBase):
def update(self, can_parsers, starpilot_toggles) -> structs.CarState:
if self.CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
return update_preap(self, can_parsers)
if self.CP.carFingerprint in LEGACY_CARS:
return self.update_legacy(can_parsers)
cp_party = can_parsers[Bus.party]
cp_ap_party = can_parsers[Bus.ap_party]
@@ -173,10 +189,94 @@ class CarState(CarStateBase):
return ret, fp_ret
def update_legacy(self, can_parsers):
cp_party = can_parsers[Bus.party]
cp_ap_party = can_parsers[Bus.ap_party]
cp_pt = can_parsers[Bus.pt]
cp_ap_pt = can_parsers[Bus.ap_pt]
cp_chassis = can_parsers[Bus.chassis]
ret = structs.CarState()
fp_ret = custom.StarPilotCarState.new_message()
# Vehicle speed
ret.vEgoRaw = cp_chassis.vl["ESP_B"]["ESP_vehicleSpeed"] * CV.KPH_TO_MS
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
# Gas and brake
ret.gasPressed = cp_pt.vl["DI_torque1"]["DI_pedalPos"] > 0
ret.brake = 0
ret.brakePressed = cp_chassis.vl["BrakeMessage"]["driverBrakeStatus"] != 1
# Steering wheel and EPAS status
epas_status = cp_chassis.vl["EPAS_sysStatus"]
self.hands_on_level = epas_status["EPAS_handsOnLevel"]
ret.steeringAngleDeg = -epas_status["EPAS_internalSAS"]
ret.steeringRateDeg = -cp_chassis.vl["STW_ANGLHP_STAT"]["StW_AnglHP_Spd"]
ret.steeringTorque = -epas_status["EPAS_torsionBarTorque"]
ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > STEER_THRESHOLD, 5)
eac_status = self.can_defines["EPAS_sysStatus"]["EPAS_eacStatus"].get(int(epas_status["EPAS_eacStatus"]), None)
ret.steerFaultPermanent = eac_status == "EAC_FAULT"
ret.steerFaultTemporary = eac_status == "EAC_INHIBITED"
eac_error_code = self.can_defines["EPAS_sysStatus"]["EPAS_eacErrorCode"].get(int(epas_status["EPAS_eacErrorCode"]), None)
ret.steeringDisengage = self.hands_on_level >= 3 or (
eac_status == "EAC_INHIBITED" and eac_error_code == "EAC_ERROR_HIGH_ANGLE_RATE_SAFETY"
)
# Cruise
cruise_state = self.can_defines["DI_state"]["DI_cruiseState"].get(int(cp_chassis.vl["DI_state"]["DI_cruiseState"]), None)
speed_units = self.can_defines["DI_state"]["DI_speedUnits"].get(int(cp_chassis.vl["DI_state"]["DI_speedUnits"]), None)
cruise_enabled = cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")
ret.cruiseState.enabled = cruise_enabled
if speed_units == "KPH":
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_hw1CruiseSet"] * CV.KPH_TO_MS, 1e-3)
elif speed_units == "MPH":
ret.cruiseState.speed = max(cp_chassis.vl["DI_state"]["DI_hw1CruiseSet"] * CV.MPH_TO_MS, 1e-3)
ret.cruiseState.available = cruise_state == "STANDBY" or ret.cruiseState.enabled
ret.cruiseState.standstill = False
ret.standstill = ret.vEgoRaw < 0.1
ret.accFaulted = cruise_state == "FAULT"
# Gear, body state, and safety state
ret.gearShifter = GEAR_MAP[self.can_defines["DI_torque2"]["DI_gear"].get(
int(cp_chassis.vl["DI_torque2"]["DI_gear"]), "DI_GEAR_INVALID")]
doors = ("DOOR_STATE_FL", "DOOR_STATE_FR", "DOOR_STATE_RL", "DOOR_STATE_RR", "DOOR_STATE_FrontTrunk", "BOOT_STATE")
ret.doorOpen = any(
self.can_defines["GTW_carState"][door].get(int(cp_chassis.vl["GTW_carState"][door]), "OPEN") == "OPEN"
for door in doors
)
ret.leftBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorLStatus"] == 1
ret.rightBlinker = cp_chassis.vl["GTW_carState"]["BC_indicatorRStatus"] == 1
_ = cp_chassis.vl["SDM1"]
_ = cp_chassis.vl["RCM_status"]
sd_time = cp_chassis.ts_nanos["SDM1"]["SDM_bcklDrivStatus"]
rcm_time = cp_chassis.ts_nanos["RCM_status"]["RCM_buckleDriverStatus"]
if sd_time and cp_chassis._last_update_nanos - sd_time <= 1_000_000_000:
ret.seatbeltUnlatched = cp_chassis.vl["SDM1"]["SDM_bcklDrivStatus"] != 1
elif rcm_time and cp_chassis._last_update_nanos - rcm_time <= 1_000_000_000:
ret.seatbeltUnlatched = cp_chassis.vl["RCM_status"]["RCM_buckleDriverStatus"] != 1
else:
ret.seatbeltUnlatched = True
ret.stockAeb = cp_ap_pt.vl["DAS_control"]["DAS_aebEvent"] == 1
ret.stockLkas = cp_ap_party.vl["DAS_steeringControl"]["DAS_steeringControlType"] == 2
self.das_control = copy.copy(cp_ap_pt.vl["DAS_control"])
return ret, fp_ret
@staticmethod
def get_can_parsers(CP):
if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP:
return get_preap_can_parsers(CP)
if CP.carFingerprint in LEGACY_CARS:
return {
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party),
Bus.pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CANBUS.party),
Bus.ap_pt: CANParser(DBC[CP.carFingerprint][Bus.pt], [], CANBUS.autopilot_party),
Bus.chassis: CANParser(DBC[CP.carFingerprint][Bus.chassis], [("SDM1", 0), ("RCM_status", 0)], CANBUS.party),
}
return {
Bus.party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.party),
Bus.ap_party: CANParser(DBC[CP.carFingerprint][Bus.party], [], CANBUS.autopilot_party)
@@ -62,6 +62,9 @@ class CooperativeSteeringController:
self.angle_override = 0.0
self.resume_rate_limiter_delta = SteerRateLimiter()
self.resume_rate_limiter = SteerRateLimiter()
self.resume_limit_error_deg = 0.0
self.cooperative_limit_error_deg = 0.0
self.cooperative_offset_deg = 0.0
def reset_override_state(self, apply_angle: float) -> None:
self.apply_angle_last = apply_angle
@@ -105,19 +108,25 @@ class CooperativeSteeringController:
return self.resume_rate_limiter.update(apply_angle, angle_rate_delta)
def update(self, apply_angle: float, lat_active: bool, enabled: bool, CS, VM: VehicleModel) -> tuple[float, bool]:
self.resume_limit_error_deg = 0.0
self.cooperative_limit_error_deg = 0.0
self.cooperative_offset_deg = 0.0
if not enabled:
self.reset_resume_state(apply_angle)
self.reset_override_state(apply_angle)
return apply_angle, lat_active
requested_angle = apply_angle
apply_angle = self.apply_resume_rate_limit(lat_active, apply_angle)
self.resume_limit_error_deg = abs(requested_angle - apply_angle)
if not lat_active:
self.reset_override_state(apply_angle)
return apply_angle, False
apply_angle_delta = apply_angle - self.apply_angle_last
self.apply_angle_last = apply_angle
apply_angle += self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
self.cooperative_offset_deg = self.update_override_angle(apply_angle_delta, CS.out.steeringTorque, CS.out.vEgo, VM)
apply_angle += self.cooperative_offset_deg
limited_angle = apply_steer_angle_limits_vm(
apply_angle,
@@ -129,5 +138,6 @@ class CooperativeSteeringController:
VM,
)
self.coop_apply_angle_last = limited_angle
self.cooperative_limit_error_deg = abs(apply_angle - limited_angle)
self.unwind_override_angle(apply_angle - limited_angle)
return limited_angle, True
@@ -5,6 +5,12 @@ from opendbc.car.tesla.values import CAR
Ecu = CarParams.Ecu
FW_VERSIONS = {
CAR.TESLA_MODEL_S_HW1: {
(Ecu.eps, 0x730, None): [
b'1016704-00-HAA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'\x10\x00A',
],
},
CAR.TESLA_MODEL_3: {
(Ecu.eps, 0x730, None): [
b'TeM3_E014p10_0.0.0 (16),E014.17.00',
+17 -2
View File
@@ -1,9 +1,9 @@
from opendbc.car import get_safety_config, structs
from opendbc.car import Bus, get_safety_config, structs
from opendbc.car.interfaces import CarInterfaceBase
from opendbc.car.tesla.carcontroller import CarController
from opendbc.car.tesla.carstate import CarState
from opendbc.car.tesla.radar_interface import RadarInterface
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR
from opendbc.car.tesla.values import TeslaSafetyFlags, CAR, DBC, LEGACY_CARS
from opendbc.car.tesla.preap.interface import get_preap_accel_limits, get_preap_params
@@ -32,6 +32,21 @@ class CarInterface(CarInterfaceBase):
if candidate == CAR.TESLA_MODEL_S_PREAP:
return get_preap_params(ret)
if candidate in LEGACY_CARS:
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla, TeslaSafetyFlags.FLAG_HW1.value)]
ret.steerLimitTimer = 0.4
ret.steerActuatorDelay = 0.1
ret.steerAtStandstill = True
ret.steerControlType = structs.CarParams.SteerControlType.angle
ret.radarUnavailable = Bus.radar not in DBC[candidate]
ret.radarTimeStepDEPRECATED = 0.125
ret.alphaLongitudinalAvailable = True
if alpha_long:
ret.openpilotLongitudinalControl = True
ret.safetyConfigs[0].safetyParam |= TeslaSafetyFlags.LONG_CONTROL.value
return ret
ret.safetyConfigs = [get_safety_config(structs.CarParams.SafetyModel.tesla)]
ret.steerLimitTimer = 0.4
@@ -16,7 +16,7 @@ class RadarInterface(RadarInterfaceBase):
def __init__(self, CP):
super().__init__(CP)
self.radar_off_can = CP.radarUnavailable or CP.carFingerprint != CAR.TESLA_MODEL_S_PREAP
self.radar_off_can = CP.radarUnavailable or Bus.radar not in DBC[CP.carFingerprint]
self.updated_messages: set[int] = set()
self.track_id = 0
self.radar_offset = float(nap_conf.radar_offset) if CP.carFingerprint == CAR.TESLA_MODEL_S_PREAP else 0.0
@@ -0,0 +1,53 @@
from opendbc.car.common.conversions import Conversions as CV
from opendbc.car.interfaces import V_CRUISE_MAX
from opendbc.car.tesla.values import CANBUS, CarControllerParams
class TeslaCANRaven:
"""CAN commands used by the legacy Model S/X powertrain and EPAS buses."""
def __init__(self, packers):
self.packers = packers
self.CCP = CarControllerParams
self.jerk_upper = self.CCP.JERK_LIMIT_MAX
self.jerk_lower = self.CCP.JERK_LIMIT_MIN
@staticmethod
def checksum(msg_id, dat):
return ((msg_id & 0xFF) + ((msg_id >> 8) & 0xFF) + sum(dat)) & 0xFF
def create_steering_control(self, counter, angle, enabled):
values = {
"DAS_steeringControlCounter": counter,
"DAS_steeringAngleRequest": -angle,
"DAS_steeringHapticRequest": 0,
"DAS_steeringControlType": 1 if enabled else 0,
}
data = self.packers[CANBUS.party].make_can_msg("DAS_steeringControl", CANBUS.party, values)[1]
values["DAS_steeringControlChecksum"] = self.checksum(0x488, data[:3])
return self.packers[CANBUS.party].make_can_msg("DAS_steeringControl", CANBUS.party, values)
def create_longitudinal_command(self, acc_state, accel, counter, v_ego, active, gas_pressed):
set_speed = max(v_ego * CV.MS_TO_KPH, 0)
if active:
set_speed = 0 if accel < 0 else V_CRUISE_MAX
if gas_pressed:
self.jerk_upper = self.jerk_lower = 0.0
else:
self.jerk_lower = max(self.jerk_lower - self.CCP.JERK_RAMP_RATE, self.CCP.JERK_LIMIT_MIN)
self.jerk_upper = min(self.jerk_upper + self.CCP.JERK_RAMP_RATE, self.CCP.JERK_LIMIT_MAX)
values = {
"DAS_setSpeed": set_speed,
"DAS_accState": acc_state,
"DAS_aebEvent": 0,
"DAS_jerkMin": self.jerk_lower,
"DAS_jerkMax": self.jerk_upper,
"DAS_accelMin": accel,
"DAS_accelMax": max(accel, 0),
"DAS_controlCounter": counter,
}
data = self.packers[CANBUS.party].make_can_msg("DAS_control", CANBUS.party, values)[1]
values["DAS_controlChecksum"] = self.checksum(0x2b9, data[:7])
return self.packers[CANBUS.party].make_can_msg("DAS_control", CANBUS.party, values)
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""Offline AP1/HW1 CAN, radar, controller, and panda-safety replay.
Usage: PYTHONPATH=. python -m opendbc.car.tesla.tests.replay_hw1_route PATH_TO_RLOGS
The supplied Pre-AP recording contains stock AP commands copied to bus 0 while
ELM327/old firmware forwarded traffic. The counterfactual run skips those copies:
the HW1 safety mode blocks stock 0x488/0x2b9 forwarding on bus 2. This is NOT a
physical HW1 drive; it cannot validate engagement or steering actuation on-car.
"""
import argparse
from collections import Counter
from pathlib import Path
from cereal import custom
from openpilot.tools.lib.logreader import LogReader
from opendbc.car import Bus, structs
from opendbc.car.tesla.carcontroller import CarController
from opendbc.car.tesla.carstate import CarState
from opendbc.car.tesla.interface import CarInterface
from opendbc.car.tesla.radar_interface import RadarInterface
from opendbc.car.tesla.values import CAR, DBC, TeslaSafetyFlags
from opendbc.safety.tests.libsafety import libsafety_py
def replay(paths: list[Path], simulate_active: bool = False):
fp = {0: {0x201: 5}, 1: {}, 2: {}}
cp = CarInterface.get_params(CAR.TESLA_MODEL_S_HW1, fp, [], True, False, False, None)
assert cp.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.tesla
assert cp.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value | TeslaSafetyFlags.LONG_CONTROL.value
safety = libsafety_py.libsafety
assert safety.set_safety_hooks(int(structs.CarParams.SafetyModel.tesla), cp.safetyConfigs[0].safetyParam) == 0
safety.init_tests()
parsers = CarState.get_can_parsers(cp)
cs = CarState(cp, custom.StarPilotCarParams.new_message())
controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp)
active_controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp) if simulate_active else None
radar = RadarInterface(cp)
stats = Counter()
first_rejected = []
active_rejected = []
last_ap_command: dict[tuple[int, bytes], int] = {}
suppressed_examples = []
for path in paths:
for event in LogReader(str(path)):
if event.which() != "can":
continue
t = event.logMonoTime
frames = [(x.address, bytes(x.dat), x.src) for x in event.can]
stock_in_event = {(a, d) for a, d, b in frames if b == 2 and a in (0x488, 0x2b9)}
for a, d, b in frames:
if b == 2 and a in (0x488, 0x2b9):
last_ap_command[(a, d)] = t
if b == 0 and a in (0x488, 0x2b9):
seen = last_ap_command.get((a, d), -1)
if (a, d) in stock_in_event or (0 <= t - seen < 250_000_000):
stats["suppressed_bus0_stock_copies"] += 1
continue
stats["unmatched_bus0_stock_commands"] += 1
if len(suppressed_examples) < 5:
suppressed_examples.append((path.name, t, hex(a), d.hex()))
if b < 128:
stats["physical_rx"] += 1
if not safety.safety_rx_hook(libsafety_py.make_CANPacket(a, b, d)):
stats["rx_rejected"] += 1
if b == 2 and a in (0x488, 0x2b9):
stats["stock_forward_blocked"] += safety.safety_fwd_hook(b, a) == -1
safety.set_timer((t // 1000) & 0xffffffff)
safety.safety_tick_current_safety_config()
stats["safety_invalid_ticks"] += not safety.safety_config_valid()
stats["relay_malfunction_ticks"] += safety.get_relay_malfunction()
stats["controls_allowed_ticks"] += safety.get_controls_allowed()
batch = [(t, frames)]
for parser in parsers.values():
parser.update(batch)
stats["invalid_car_parser_ticks"] += not parser.can_valid
out, _ = cs.update(parsers, None)
cs.out = out
stats["carstate_faulted_ticks"] += out.accFaulted
stats["seatbelt_unlatched_ticks"] += out.seatbeltUnlatched
stats["steering_inhibited_ticks"] += out.steerFaultTemporary
stats["cruise_engaged_ticks"] += out.cruiseState.enabled
radar_data = radar.update(batch)
if radar_data is not None:
stats["radar_updates"] += 1
stats["radar_points"] += len(radar_data.points)
stats["radar_error_updates"] += radar_data.errors.canError or radar_data.errors.radarFault
cc = structs.CarControl.new_message()
cc.actuators.steeringAngleDeg = out.steeringAngleDeg
cc.actuators.accel = 0.
# Do not fabricate engagement on the actual faulted/standby route.
_, sends = controller.update(cc.as_reader(), cs, t, None)
for a, d, b in sends:
stats["generated_tx"] += 1
stats[f"generated_{hex(a)}"] += 1
if not safety.safety_tx_hook(libsafety_py.make_CANPacket(a, b, d)):
stats["tx_rejected"] += 1
if len(first_rejected) < 5:
first_rejected.append((path.name, t, hex(a), d.hex(), out.steeringAngleDeg, safety.get_relay_malfunction()))
if active_controller is not None:
# A synthetic gate test only. This recording never engaged cruise, so
# enabling controls here does NOT represent an actual car-state transition.
eligible = (not (out.steerFaultTemporary or out.steerFaultPermanent or out.steeringDisengage or out.accFaulted or
out.gasPressed or out.brakePressed or out.stockAeb or out.stockLkas) and out.vEgoRaw > 2.)
simulated = structs.CarControl.new_message()
simulated.latActive = eligible
simulated.longActive = eligible
simulated.actuators.steeringAngleDeg = out.steeringAngleDeg
simulated.actuators.accel = 0.5 if eligible else 0.
_, active_sends = active_controller.update(simulated.as_reader(), cs, t, None)
if eligible:
stats["simulated_eligible_ticks"] += 1
safety.set_controls_allowed(True)
for a, d, b in active_sends:
stats["simulated_tx"] += 1
stats[f"simulated_{hex(a)}"] += 1
if not safety.safety_tx_hook(libsafety_py.make_CANPacket(a, b, d)):
stats["simulated_tx_rejected"] += 1
if len(active_rejected) < 5:
active_rejected.append((path.name, t, hex(a), d.hex(), out.steeringAngleDeg, out.vEgoRaw))
safety.set_controls_allowed(False)
stats["can_events"] += 1
print(f"{path.name}: {dict(stats)}", flush=True)
print(f"unmatched bus-0 command examples: {suppressed_examples}")
print(f"rejected TX examples: {first_rejected}")
print(f"rejected synthetic-active TX examples: {active_rejected}")
print(f"final: {dict(stats)}")
return stats
if __name__ == "__main__":
argp = argparse.ArgumentParser(description=__doc__)
argp.add_argument("rlogs", type=Path, help="directory containing segment rlog.zst files")
argp.add_argument("--simulate-active", action="store_true", help="force safety engagement only on healthy standby samples")
args = argp.parse_args()
files = sorted(args.rlogs.glob("*.rlog.zst"))
if not files:
argp.error("no *.rlog.zst files found")
replay(files, args.simulate_active)
@@ -1,3 +1,4 @@
import math
from types import SimpleNamespace
import pytest
@@ -73,3 +74,110 @@ def test_safety_flag_is_model_3_only(candidate, enabled, expected):
if candidate != CAR.TESLA_MODEL_S_PREAP:
assert CarController(DBC[candidate], params).coop_enabled is expected
def assert_finite_nonnegative_limit_errors(controller):
errors = (controller.resume_limit_error_deg, controller.cooperative_limit_error_deg)
assert all(math.isfinite(error) and error >= 0.0 for error in errors)
assert math.isfinite(controller.cooperative_offset_deg)
def test_zero_torque_has_zero_cooperative_diagnostics(vehicle_model):
controller = CooperativeSteeringController()
angle, lat_active = controller.update(0.0, True, True, make_car_state(), vehicle_model)
assert angle == 0.0
assert lat_active
assert controller.resume_limit_error_deg == 0.0
assert controller.cooperative_limit_error_deg == 0.0
assert controller.cooperative_offset_deg == 0.0
assert_finite_nonnegative_limit_errors(controller)
def test_steady_light_torque_reports_offset_without_real_limiting(vehicle_model):
controller = CooperativeSteeringController()
for _ in range(100):
controller.update(0.0, True, True, make_car_state(torque=0.9), vehicle_model)
assert controller.cooperative_offset_deg > 2.5
assert controller.resume_limit_error_deg < 2.5
assert controller.cooperative_limit_error_deg < 2.5
assert_finite_nonnegative_limit_errors(controller)
def test_torque_reversal_updates_signed_offset_without_negative_errors(vehicle_model):
controller = CooperativeSteeringController()
for _ in range(100):
controller.update(0.0, True, True, make_car_state(torque=0.9), vehicle_model)
for _ in range(200):
controller.update(0.0, True, True, make_car_state(torque=-0.9), vehicle_model)
assert controller.cooperative_offset_deg < -2.5
assert_finite_nonnegative_limit_errors(controller)
def test_release_reports_gradual_offset_unwind(vehicle_model):
controller = CooperativeSteeringController()
for _ in range(100):
controller.update(0.0, True, True, make_car_state(torque=1.5), vehicle_model)
offsets = []
for _ in range(100):
controller.update(0.0, True, True, make_car_state(), vehicle_model)
offsets.append(controller.cooperative_offset_deg)
assert offsets[0] > offsets[-1] >= 0.0
assert all(next_offset <= offset for offset, next_offset in zip(offsets, offsets[1:]))
assert offsets[-1] == pytest.approx(0.0, abs=1e-6)
assert_finite_nonnegative_limit_errors(controller)
def test_resume_ramp_reports_resume_limiting(vehicle_model):
controller = CooperativeSteeringController()
controller.reset_resume_state(0.0)
controller.reset_override_state(0.0)
controller.update(20.0, True, True, make_car_state(), vehicle_model)
assert controller.resume_limit_error_deg > 2.5
assert controller.cooperative_limit_error_deg < 2.5
assert controller.cooperative_offset_deg == 0.0
def test_final_limiter_reports_cooperative_target_clipping(vehicle_model):
controller = CooperativeSteeringController()
controller.reset_resume_state(20.0)
controller.reset_override_state(0.0)
controller.update(20.0, True, True, make_car_state(), vehicle_model)
assert controller.resume_limit_error_deg == 0.0
assert controller.cooperative_limit_error_deg > 2.5
assert controller.cooperative_offset_deg == 0.0
def test_final_limiter_remains_visible_with_light_torque(vehicle_model):
controller = CooperativeSteeringController()
controller.reset_resume_state(20.0)
controller.reset_override_state(0.0)
controller.update(20.0, True, True, make_car_state(torque=-0.9), vehicle_model)
assert abs(controller.cooperative_offset_deg) > 0.0
assert controller.cooperative_limit_error_deg > 2.5
def test_diagnostics_reset_on_disabled_update(vehicle_model):
controller = CooperativeSteeringController()
controller.reset_resume_state(20.0)
controller.update(20.0, True, True, make_car_state(), vehicle_model)
assert controller.cooperative_limit_error_deg > 2.5
controller.update(4.0, True, False, make_car_state(torque=2.0), vehicle_model)
assert controller.resume_limit_error_deg == 0.0
assert controller.cooperative_limit_error_deg == 0.0
assert controller.cooperative_offset_deg == 0.0
@@ -0,0 +1,228 @@
import json
import math
from pathlib import Path
from types import SimpleNamespace
import pytest
import cereal.messaging as messaging
from cereal import car
from opendbc.car import gen_empty_fingerprint
from opendbc.car.tesla.carcontroller import CarController
from opendbc.car.tesla.interface import CarInterface
from opendbc.car.tesla.values import CAR, DBC
BASELINE_SHA = "a80064be4fdf4b8765a5e9dec44d8a5c266f48a8"
BASELINE_SOURCE_SHA256 = {
"opendbc_repo/opendbc/car/tesla/coop_steering.py": "9c9d60bbfae2aaa0d8c1fca203a9fa19a14a19feef5aa89e85ecaa6f6502a9f0",
"opendbc_repo/opendbc/car/tesla/carcontroller.py": "1ef3cf646bc4b3c398bd12010e624b90e083426152d632fafb5b2e93fd3d8661",
}
BASELINE_FIXTURE = Path(__file__).parent / "fixtures" / "coop_steering_baseline_a80064be.json"
def make_params(candidate=CAR.TESLA_MODEL_3, cooperative=True):
toggles = SimpleNamespace(tesla_cooperative_steering=cooperative, trailer_load_kg=0.0)
return CarInterface.get_params(candidate, gen_empty_fingerprint(), [], False, False, False, toggles)
def make_car_state(torque=0.0, speed=15.0, angle=0.0, steering_disengage=False):
return SimpleNamespace(
out=SimpleNamespace(
steeringTorque=torque,
steeringAngleDeg=angle,
steeringDisengage=steering_disengage,
vEgo=speed,
vEgoRaw=speed,
gasPressed=False,
),
hands_on_level=0,
das_control={"DAS_controlCounter": 0},
)
def make_control(requested_angle=0.0, lat_active=True):
control = car.CarControl.new_message()
control.latActive = lat_active
control.actuators.steeringAngleDeg = requested_angle
return control.as_reader()
def make_controller(candidate=CAR.TESLA_MODEL_3, cooperative=True):
params = make_params(candidate, cooperative)
return CarController(DBC[candidate], params)
def run_frame(controller, requested_angle=0.0, torque=0.0, speed=15.0, measured_angle=0.0,
lat_active=True, steering_disengage=False, now_nanos=1_000_000_000):
return controller.update(
make_control(requested_angle, lat_active),
make_car_state(torque, speed, measured_angle, steering_disengage),
now_nanos,
SimpleNamespace(),
)
def get_limit_info(controller):
return SimpleNamespace(**controller.get_steering_limit_info())
def legacy_actuator_dict(actuators):
return actuators.to_dict()
def test_steering_limit_info_defaults_to_invalid():
controller = make_controller()
info = get_limit_info(controller)
assert not info.valid
assert info.monoTime == 0
def test_steering_limit_info_round_trips_through_custom_message():
message = messaging.new_message("starpilotCarControl", valid=True)
info = message.starpilotCarControl.steeringLimitInfo
info.valid = True
info.modelLimitErrorDeg = 1.25
info.resumeLimitErrorDeg = 0.5
info.cooperativeLimitErrorDeg = 2.0
info.cooperativeOffsetDeg = -4.5
info.monoTime = 1_234_567_890
info.combinedLimitErrorDeg = 3.75
restored = messaging.log_from_bytes(message.to_bytes())
restored_info = restored.starpilotCarControl.steeringLimitInfo
assert restored_info.valid
assert restored_info.modelLimitErrorDeg == 1.25
assert restored_info.resumeLimitErrorDeg == 0.5
assert restored_info.cooperativeLimitErrorDeg == 2.0
assert restored_info.cooperativeOffsetDeg == -4.5
assert restored_info.monoTime == 1_234_567_890
assert restored_info.combinedLimitErrorDeg == 3.75
def test_active_cooperative_controller_reports_diagnostics():
controller = make_controller()
requested_angle = 20.0
now_nanos = 1_234_567_890
actuators, _ = run_frame(controller, requested_angle, torque=0.9, measured_angle=0.0, now_nanos=now_nanos)
info = get_limit_info(controller)
assert info.valid
assert info.monoTime == now_nanos
assert info.modelLimitErrorDeg == pytest.approx(abs(requested_angle - controller.apply_angle_last), abs=1e-5)
assert info.resumeLimitErrorDeg == pytest.approx(controller.coop_steer.resume_limit_error_deg, abs=1e-5)
assert info.cooperativeLimitErrorDeg == pytest.approx(controller.coop_steer.cooperative_limit_error_deg, abs=1e-5)
assert info.cooperativeOffsetDeg == pytest.approx(controller.coop_steer.cooperative_offset_deg, abs=1e-5)
assert info.combinedLimitErrorDeg == pytest.approx(
abs(requested_angle + info.cooperativeOffsetDeg - actuators.steeringAngleDeg), abs=1e-5,
)
assert info.modelLimitErrorDeg > 2.5
assert info.cooperativeOffsetDeg > 0.0
assert info.combinedLimitErrorDeg > 2.5
errors = (info.modelLimitErrorDeg, info.resumeLimitErrorDeg,
info.cooperativeLimitErrorDeg, info.combinedLimitErrorDeg)
assert all(math.isfinite(error) and error >= 0.0 for error in errors)
def test_cooperative_offset_alone_does_not_become_limiter_error():
controller = make_controller()
actuators = None
for frame in range(200):
actuators, _ = run_frame(controller, torque=0.9, now_nanos=1_000_000_000 + frame * 10_000_000)
assert actuators is not None
info = get_limit_info(controller)
assert info.valid
assert info.cooperativeOffsetDeg > 2.5
assert info.modelLimitErrorDeg < 2.5
assert info.resumeLimitErrorDeg < 2.5
assert info.cooperativeLimitErrorDeg < 2.5
assert info.combinedLimitErrorDeg < 2.5
def test_combined_error_keeps_two_same_direction_small_limits_visible():
controller = make_controller()
# Prime the resume limiter to the first-stage output for this literal input.
controller.coop_steer.reset_resume_state(-0.9954867959022522)
actuators, _ = run_frame(controller, -2.5, torque=-1.5, speed=12.5, measured_angle=0.0)
info = get_limit_info(controller)
assert info.modelLimitErrorDeg == pytest.approx(1.5045133, abs=1e-5)
assert info.resumeLimitErrorDeg == pytest.approx(0.0, abs=1e-5)
assert info.cooperativeLimitErrorDeg == pytest.approx(1.5045133, abs=1e-5)
assert info.modelLimitErrorDeg < 2.5
assert info.cooperativeLimitErrorDeg < 2.5
assert info.combinedLimitErrorDeg == pytest.approx(3.0090265, abs=1e-5)
assert info.combinedLimitErrorDeg > 2.5
def test_intervening_100hz_frame_retains_matching_50hz_sample():
controller = make_controller()
first, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_000_000_000)
first_info = controller.get_steering_limit_info()
second, _ = run_frame(controller, -40.0, torque=-1.5, now_nanos=1_010_000_000)
assert controller.get_steering_limit_info() == first_info
assert controller.get_steering_limit_info()["monoTime"] == 1_000_000_000
def test_inactive_interval_clears_sample_until_next_steering_update():
controller = make_controller()
active, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_000_000_000)
assert get_limit_info(controller).valid
inactive, _ = run_frame(controller, 8.0, torque=0.9, lat_active=False, now_nanos=1_010_000_000)
assert not get_limit_info(controller).valid
assert get_limit_info(controller).monoTime == 0
resumed, _ = run_frame(controller, 8.0, torque=0.9, now_nanos=1_020_000_000)
assert get_limit_info(controller).valid
assert get_limit_info(controller).monoTime == 1_020_000_000
@pytest.mark.parametrize(("candidate", "cooperative", "steering_disengage"), (
(CAR.TESLA_MODEL_3, False, False),
(CAR.TESLA_MODEL_Y, True, False),
(CAR.TESLA_MODEL_3, True, True),
))
def test_diagnostics_invalid_when_not_in_supported_active_path(candidate, cooperative, steering_disengage):
controller = make_controller(candidate, cooperative)
actuators, _ = run_frame(controller, torque=1.5, steering_disengage=steering_disengage)
info = get_limit_info(controller)
assert not info.valid
assert info.monoTime == 0
def test_actual_actuators_and_steering_can_match_pinned_baseline_fixture():
fixture = json.loads(BASELINE_FIXTURE.read_text())
assert fixture["metadata"] == {
"schemaVersion": 1,
"baselineSha": BASELINE_SHA,
"baselineSourceSha256": BASELINE_SOURCE_SHA256,
"frameCount": 386,
}
candidate = make_controller()
for expected in fixture["frames"]:
inputs = expected["input"]
candidate_actuators, candidate_can = run_frame(
candidate,
inputs["requestedAngleDeg"],
inputs["torqueNm"],
inputs["speedMps"],
inputs["measuredAngleDeg"],
inputs["latActive"],
inputs["steeringDisengage"],
inputs["nowNanos"],
)
assert legacy_actuator_dict(candidate_actuators) == expected["actuators"]
assert [[address, data.hex(), bus] for address, data, bus in candidate_can] == expected["can"]
@@ -0,0 +1,134 @@
import pytest
from cereal import custom
from opendbc.can import CANPacker, CANParser
from opendbc.car import Bus, structs
from opendbc.car.fw_versions import match_fw_to_car
from opendbc.car.tesla.carcontroller import CarController
from opendbc.car.tesla.carstate import CarState
from opendbc.car.tesla.fingerprints import FW_VERSIONS
from opendbc.car.tesla.interface import CarInterface
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
from opendbc.car.tesla.values import CANBUS, CAR, DBC, TeslaSafetyFlags
def test_hw1_is_distinct_from_preap_and_does_not_change_can_buses():
preap = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_PREAP)
hw1 = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
assert preap.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.teslaPreAP
assert hw1.safetyConfigs[0].safetyModel == structs.CarParams.SafetyModel.tesla
assert hw1.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value
assert hw1.radarTimeStepDEPRECATED == pytest.approx(0.125)
assert preap.radarTimeStepDEPRECATED == pytest.approx(0.05)
assert CANBUS.party == 0 and CANBUS.autopilot_party == 2
assert CarState.get_can_parsers(hw1)[Bus.ap_pt].bus == 2
assert CarState.get_can_parsers(hw1)[Bus.chassis].message_states[0x211].ignore_alive
assert CarState.get_can_parsers(preap)[Bus.party].bus == 0
def test_hw1_requires_explicit_alpha_long_for_acceleration():
ret = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
hw1 = CarInterface._get_params(ret, CAR.TESLA_MODEL_S_HW1, {0: {0x201: 5}}, [], True, False, False)
assert hw1.openpilotLongitudinalControl
assert hw1.safetyConfigs[0].safetyParam == TeslaSafetyFlags.FLAG_HW1.value | TeslaSafetyFlags.LONG_CONTROL.value
def test_ap1_eps_fw_matches_hw1_without_matching_preap():
version = FW_VERSIONS[CAR.TESLA_MODEL_S_HW1][(structs.CarParams.Ecu.eps, 0x730, None)][0]
fw = structs.CarParams.CarFw(ecu=structs.CarParams.Ecu.eps, address=0x730, brand="tesla", fwVersion=version)
exact, candidates = match_fw_to_car([fw], "", log=False)
assert exact and candidates == {CAR.TESLA_MODEL_S_HW1}
def test_hw1_display_and_cruise_bytes_do_not_change_preap_signals():
# Captured bus-0 DI_state (0x368) from the AP1 route: display 9 MPH, set speed 10 MPH.
parser = CANParser("tesla_can", [(0x368, 0)], 0)
parser.message_states[0x368].ignore_counter = True
frames = [(0x368, bytes.fromhex("84185e3009980a2d"), 0)]
parser.update([(1_000_000_000, frames)])
parser.update([(2_000_000_000, frames)])
state = parser.vl["DI_state"]
assert state["DI_hw1DigitalSpeed"] == 9
assert state["DI_hw1CruiseSet"] == 10
assert state["DI_digitalSpeed"] == 10
assert state["DI_cruiseSet"] != 10
def test_hw1_packer_emits_bus_zero_with_matching_checksums():
packer = CANPacker(DBC[CAR.TESLA_MODEL_S_HW1][Bus.party])
tesla_can = TeslaCANRaven({CANBUS.party: packer})
for msg, expected_addr, checksum_index in (
(tesla_can.create_steering_control(0, 0, False), 0x488, 3),
(tesla_can.create_longitudinal_command(13, 0, 0, 10, False, False), 0x2b9, 7),
):
addr, data, bus = msg
assert addr == expected_addr and bus == 0
assert data[checksum_index] == TeslaCANRaven.checksum(addr, data[:checksum_index])
def test_hw1_cancel_clears_acceleration_and_does_not_request_max_speed():
cp = CarInterface._get_params(CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1),
CAR.TESLA_MODEL_S_HW1, {0: {}}, [], True, False, False)
controller = CarController(DBC[CAR.TESLA_MODEL_S_HW1], cp)
state = CarState(cp, custom.StarPilotCarParams.new_message())
state.out.vEgo = 10.
cc = structs.CarControl.new_message()
cc.longActive = True
cc.cruiseControl.cancel = True
cc.actuators.accel = 2.
_, sends = controller.update(cc.as_reader(), state, 0, None)
_, data, bus = next(msg for msg in sends if msg[0] == 0x2b9)
assert bus == 0
parser = CANParser("tesla_can", [(0x2b9, 0)], 0)
parser.update([(1_000_000_000, [(0x2b9, data, bus)])])
decoded = parser.vl["DAS_control"]
assert decoded["DAS_accState"] == 13
assert decoded["DAS_accelMin"] == pytest.approx(0, abs=0.05)
assert decoded["DAS_accelMax"] == pytest.approx(0, abs=0.05)
assert decoded["DAS_setSpeed"] != 200
def test_hw1_carstate_uses_ap1_powertrain_and_chassis():
cp = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
parsers = CarState.get_can_parsers(cp)
frames = [
(0x155, bytes.fromhex("000000000005e308"), 0), # ESP speed 15.07 kph
(0x368, bytes.fromhex("84185e3009980a2d"), 0),
(0x201, bytes.fromhex("5444008df2"), 0),
]
for parser in parsers.values():
for addr in (0x155, 0x368, 0x201):
_ = parser.vl[addr]
parser.message_states[addr].ignore_counter = True
parser.message_states[addr].ignore_checksum = True
parser.update([(1_000_000_000, frames)])
parser.update([(2_000_000_000, frames)])
state = CarState(cp, custom.StarPilotCarParams.new_message())
ret, _ = state.update(parsers, None)
assert not ret.cruiseState.enabled
assert ret.cruiseState.speed == pytest.approx(10 * 0.44704)
assert ret.vEgoRaw == pytest.approx(15.07 / 3.6)
assert not ret.seatbeltUnlatched
# A stale belt frame cannot allow an engagement indefinitely.
for parser in parsers.values():
parser.update([(4_000_000_000, [])])
ret, _ = state.update(parsers, None)
assert ret.seatbeltUnlatched
def test_hw1_can_use_rcm_buckle_when_sdm1_is_absent():
cp = CarInterface.get_non_essential_params(CAR.TESLA_MODEL_S_HW1)
parsers = CarState.get_can_parsers(cp)
packer = CANPacker(DBC[CAR.TESLA_MODEL_S_HW1][Bus.chassis])
addr, data, bus = packer.make_can_msg("RCM_status", 0, {"RCM_buckleDriverStatus": 1})
assert addr == 0x211 and bus == 0
frames = [(addr, data, bus)]
for parser in parsers.values():
_ = parser.vl["RCM_status"]
parser.update([(1_000_000_000, frames)])
state = CarState(cp, custom.StarPilotCarParams.new_message())
out, _ = state.update(parsers, None)
assert not out.seatbeltUnlatched
+16
View File
@@ -70,6 +70,16 @@ class CAR(Platforms):
Bus.radar: 'tesla_radar_bosch_generated',
},
)
TESLA_MODEL_S_HW1 = TeslaPlatformConfig(
[CarDocs("Tesla Model S (with HW1) 2014-16", "All", support_type=SupportType.COMMUNITY, support_link="#community")],
CarSpecs(mass=2100., wheelbase=2.960, steerRatio=15.0),
{
Bus.chassis: 'tesla_can',
Bus.party: 'tesla_can',
Bus.pt: 'tesla_can',
Bus.radar: 'tesla_radar_bosch_generated',
},
)
FW_QUERY_CONFIG = FwQueryConfig(
@@ -125,10 +135,14 @@ class CarControllerParams:
ACCEL_MAX = 2.0 # m/s^2
ACCEL_MIN = -3.48 # m/s^2
JERK_LIMIT_MAX = 4.9 # m/s^3, ACC faults at 5.0
JERK_LIMIT_MIN = -4.9 # m/s^3, ACC faults at 5.0
JERK_RAMP_RATE = JERK_LIMIT_MAX * 0.002
class TeslaSafetyFlags(IntFlag):
LONG_CONTROL = 1
FLAG_EXTERNAL_PANDA = 4
FLAG_HW1 = 8
COOP_STEERING = 256
@@ -157,5 +171,7 @@ class CruiseButtons:
DBC = CAR.create_dbc_map()
LEGACY_CARS = (CAR.TESLA_MODEL_S_HW1,)
STEER_THRESHOLD = 1
STEER_DISENGAGE_THRESHOLD = 5.0
+2
View File
@@ -35,6 +35,8 @@ non_tested_cars = [
GM.CHEVROLET_MALIBU_ASCM,
GM.CHEVROLET_MALIBU_SDGM,
GM.CHEVROLET_SUBURBAN,
GM.CHEVROLET_SUBURBAN_ASCM,
GM.CHEVROLET_SUBURBAN_CAMERA,
GM.CHEVROLET_TRAX,
GM.CHEVROLET_VOLT_ASCM,
GM.CHEVROLET_VOLT_CAMERA,
@@ -2,7 +2,7 @@ from types import SimpleNamespace
import pytest
from opendbc.car.can_definitions import CanData
from opendbc.car.car_helpers import FRAME_FINGERPRINT, _apply_starpilot_access_policy, _get_gm_stored_candidate_fallback, can_fingerprint
from opendbc.car.car_helpers import FRAME_FINGERPRINT, _apply_starpilot_access_policy, _get_gm_stored_candidate_fallback, _normalize_gm_suburban_camera_candidate, can_fingerprint
from opendbc.car.fingerprints import _FINGERPRINTS as FINGERPRINTS
from opendbc.car.gm.values import CAR as GM
from opendbc.car.toyota.values import CAR as TOYOTA
@@ -116,3 +116,14 @@ class TestCanFingerprint:
candidate = _apply_starpilot_access_policy("CHEVROLET_VOLT_CC", SimpleNamespace(block_user=True))
assert candidate == "CHEVROLET_VOLT_CC"
def test_gm_suburban_camera_variant_uses_vin_and_camera_bus_signature(self):
fingerprints = {
0: {190: 6, 201: 8, 209: 7, 211: 2, 241: 6, 304: 1, 320: 3},
2: {0x24b: 8, 0x64b: 8},
}
assert _normalize_gm_suburban_camera_candidate(None, fingerprints, "1GNSKJKJXKR148371") == "CHEVROLET_SUBURBAN_CAMERA"
assert _normalize_gm_suburban_camera_candidate("GMC_YUKON", fingerprints, "1GNSKJKJXKR148371") == "CHEVROLET_SUBURBAN_CAMERA"
assert _normalize_gm_suburban_camera_candidate(None, fingerprints, "1GNSKCKC5KR255194") is None
assert _normalize_gm_suburban_camera_candidate(None, {0: fingerprints[0], 2: {0x320: 3}}, "1GNSKJKJXKR148371") is None
@@ -26,6 +26,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"TESLA_MODEL_3" = [nan, 2.5, nan]
"TESLA_MODEL_Y" = [nan, 2.5, nan]
"TESLA_MODEL_X" = [nan, 2.5, nan]
"TESLA_MODEL_S_HW1" = [nan, 2.5, nan]
# Guess
"VOLKSWAGEN_ID3_MK1" = [nan, 2.5, nan]
@@ -90,6 +90,8 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"]
"CHEVROLET_MALIBU" = "CHEVROLET_VOLT"
"CHEVROLET_MALIBU_ASCM" = "CHEVROLET_VOLT"
"CHEVROLET_MALIBU_SDGM" = "CHEVROLET_VOLT"
"CHEVROLET_SUBURBAN_ASCM" = "CHEVROLET_SUBURBAN"
"CHEVROLET_SUBURBAN_CAMERA" = "CHEVROLET_SUBURBAN"
"HOLDEN_ASTRA" = "CHEVROLET_VOLT"
"CHEVROLET_VOLT_CC" = "CHEVROLET_VOLT"
"CHEVROLET_VOLT_CAMERA" = "CHEVROLET_VOLT"
@@ -46,7 +46,7 @@ TOYOTA_AUTO_HOLD_ACTIVATION_FRAMES = 100
# LKA limits
# EPS faults if you apply torque while the steering rate is above 100 deg/s for too long
MAX_STEER_RATE = 100 # deg/s
MAX_STEER_RATE_FRAMES = 18 # tx control frames needed before torque can be cut
MAX_STEER_RATE_FRAMES = 17 # tx control frames needed before torque can be cut
# EPS allows user torque above threshold for 50 frames before permanently faulting
MAX_USER_TORQUE = 500
@@ -77,6 +77,14 @@ def should_bypass_toyota_long_pid(CP, starpilot_toggles=None) -> bool:
) or highlander_sdsu)
def get_toyota_lat_active(car_fingerprint, requested_active: bool, steering_torque: float,
steering_pressed: bool) -> bool:
if not requested_active or abs(steering_torque) >= MAX_USER_TORQUE:
return False
return not (car_fingerprint == CAR.TOYOTA_COROLLA_TSS2 and steering_pressed)
def supports_toyota_auto_hold(CP, auto_hold_enabled: bool) -> bool:
return (
auto_hold_enabled and
@@ -335,7 +343,8 @@ class CarController(CarControllerBase):
stopping = actuators.longControlState == LongCtrlState.stopping
hud_control = CC.hudControl
pcm_cancel_cmd = CC.cruiseControl.cancel
lat_active = CC.latActive and abs(CS.out.steeringTorque) < MAX_USER_TORQUE
lat_active = get_toyota_lat_active(self.CP.carFingerprint, CC.latActive,
CS.out.steeringTorque, CS.out.steeringPressed)
if len(CC.orientationNED) == 3:
self.pitch.update(CC.orientationNED[1])
@@ -11,6 +11,7 @@ from opendbc.car.toyota import toyotacan
from opendbc.car.toyota.carcontroller import CarController, get_camry_hybrid_feedforward, get_long_tune, get_prius_feedforward, \
get_prius_positive_feedforward_scale, \
get_rav4_interceptor_pedal_scale, \
get_toyota_lat_active, \
limit_interceptor_pcm_accel, \
limit_interceptor_stopping_accel, limit_no_lead_cruise_sign_flip, \
limit_prius_stopping_accel, should_bypass_toyota_long_pid, supports_toyota_auto_hold, \
@@ -734,6 +735,15 @@ class TestToyotaFingerprint:
class TestToyotaCarController:
def test_corolla_tss2_hands_off_immediately_when_driver_is_steering(self):
assert not get_toyota_lat_active(CAR.TOYOTA_COROLLA_TSS2, True, 117, True)
def test_corolla_tss2_stays_active_without_driver_input(self):
assert get_toyota_lat_active(CAR.TOYOTA_COROLLA_TSS2, True, 99, False)
def test_toyota_driver_handoff_behavior_is_corolla_only(self):
assert get_toyota_lat_active(CAR.TOYOTA_RAV4_TSS2, True, 117, True)
@staticmethod
def _make_controller(*, standstill_req=False, last_standstill=False):
controller = CarController.__new__(CarController)
@@ -185,6 +185,29 @@ def volkswagen_mqb_meb_checksum(address: int, sig, d: bytearray) -> int:
return crc ^ 0xFF
def volkswagen_mqb_meb_dyn_len_checksum(address: int, sig, d: bytearray, length: int, const: list[int]) -> int:
d = d[:length]
crc = 0xFF
for i in range(1, len(d)):
crc ^= d[i]
crc = CRC8H2F[crc]
counter = d[1] & 0x0F
crc ^= const[counter]
crc = CRC8H2F[crc]
return crc ^ 0xFF
def volkswagen_meb_alt_crc_checksum(address: int, sig, d: bytearray) -> int:
entry = VOLKSWAGEN_MEB_ALT_CRC_CONSTANTS.get(address)
if entry:
length, const = entry
checksum = volkswagen_mqb_meb_dyn_len_checksum(address, sig, d, length, const)
if checksum == d[0]:
return checksum
return volkswagen_mqb_meb_checksum(address, sig, d)
def xor_checksum(address: int, sig, d: bytearray, initial_value: int = 0) -> int:
checksum = initial_value
checksum_byte = sig.start_bit // 8
@@ -258,3 +281,19 @@ VOLKSWAGEN_MQB_MEB_CONSTANTS: dict[int, list[int]] = {
0x65D: [0xAC, 0xB3, 0xAB, 0xEB, 0x7A, 0xE1, 0x3B, 0xF7,
0x73, 0xBA, 0x7C, 0x9E, 0x06, 0x5F, 0x02, 0xD9], # ESP_20
}
VOLKSWAGEN_MEB_ALT_CRC_CONSTANTS: dict[int, tuple[int, list[int]]] = {
0x0DB: (42, [0x09, 0xFA, 0xCA, 0x8E, 0x62, 0xD5, 0xD1, 0xF0,
0x31, 0xA0, 0xAF, 0xDA, 0x4D, 0x1A, 0x0A, 0x97]), # AWV_03
0xFC: (60, [0x69, 0xDC, 0xF9, 0x64, 0x6A, 0xCE, 0x55, 0x2C,
0xC4, 0x38, 0x8F, 0xD1, 0xC6, 0x43, 0xB4, 0xB1]), # ESC_51
0x102: (44, [0xD7, 0x12, 0x85, 0x7E, 0x0B, 0x34, 0xFA, 0x16,
0x7A, 0x25, 0x2D, 0x8F, 0x04, 0x8E, 0x5D, 0x35]), # ESC_50
0x10B: (44, [0x2C, 0xB1, 0x1A, 0x75, 0xBB, 0x65, 0x79, 0x47,
0x81, 0x2B, 0xCC, 0x96, 0x17, 0xDB, 0xC0, 0x94]), # Motor_51
0x139: (28, [0x96, 0x92, 0x95, 0xB5, 0x6E, 0xE3, 0xBD, 0xB4,
0xFA, 0xAE, 0xBE, 0xCB, 0xCF, 0xA5, 0x77, 0xEF]), # VMM_02
0x13D: (28, [0x18, 0x71, 0x10, 0x8D, 0xD7, 0xAA, 0xB0, 0x78,
0xAC, 0x12, 0xAE, 0x0C, 0xDD, 0xF1, 0x85, 0x68]), # QFK_01
}
@@ -8,7 +8,7 @@ from opendbc.car import Bus
from opendbc.car.structs import CarParams
from opendbc.car.volkswagen.interface import CarInterface
from opendbc.car.volkswagen.fingerprints import FW_VERSIONS
from opendbc.car.volkswagen.mqbcan import volkswagen_mqb_meb_checksum
from opendbc.car.volkswagen.mqbcan import volkswagen_meb_alt_crc_checksum, volkswagen_mqb_meb_checksum
from opendbc.car.volkswagen.radar_interface import RadarInterface
from opendbc.car.volkswagen.values import CAR, DBC, FW_QUERY_CONFIG, WMI, CanBus, VolkswagenFlags, VolkswagenSafetyFlags
@@ -75,6 +75,18 @@ class TestVolkswagenPlatformConfigs:
data = bytearray.fromhex(data_hex)
assert volkswagen_mqb_meb_checksum(0x25D, None, data) == data[0]
@pytest.mark.parametrize(("address", "data_hex"), (
(0x0DB, "bb0ffcf0fefe0000fd0fffc0ff0000000200000000000000010000000000000000000000000000000000000000000000"),
(0x0FC, "650b1f007ef0b10c0000000000000000ffff1019191c1cfefe0000000000000000e0fff40140ffeb7f0748e481af421f00000000000000000000000000000000"),
(0x102, "9f0e7cfa010500000020cb0402000000b703a00000ec0f00000000002cd3ff1f0020a60000000020000000007d5256ab"),
(0x10B, "9d06000000007efe000000010000ff01feff000000000000000000000090240000000000000000000000000000000000"),
(0x139, "ac0e850b0890132000d019800000000000000000000000003002000500000000"),
(0x13D, "2412111101d1060000d0d410d106000000000000000000000000000000000000"),
))
def test_meb_gen2_checksum(self, address, data_hex):
data = bytearray.fromhex(data_hex)
assert volkswagen_meb_alt_crc_checksum(address, None, data) == data[0]
def test_meb_camera_radar_tracks(self):
cp = self._get_meb_params(CAR.SKODA_ENYAQ_MK1)
radar = RadarInterface(cp)
@@ -1480,6 +1480,7 @@ BO_ 905 SCC14: 8 SCC
SG_ JerkLowerLimit : 19|7@1+ (0.1,0) [0|12.7] "m/s^3" ESC
SG_ ACCMode : 32|3@1+ (1,0) [0|7] "" CLU,HUD,LDWS_LKAS,ESC
SG_ ObjGap : 56|8@1+ (1,0) [0|255] "" CLU,HUD,ESC
SG_ ObjDistStat : 42|2@1+ (1,0) [0|3] "" XXX
BO_ 1157 LFAHDA_MFC: 8 XXX
SG_ HDA_USM : 0|2@1+ (1,0) [0|3] "" XXX
@@ -1671,6 +1672,7 @@ VAL_ 871 CF_Lvr_IsgState 0 "enabled" 1 "activated" 2 "unknown" 3 "disabled";
VAL_ 871 CF_Lvr_Gear 12 "T" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P";
VAL_ 882 Elect_Gear_Shifter 4 "S" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P";
VAL_ 905 ACCMode 0 "off" 1 "enabled" 2 "driver_override" 3 "off_maybe_fault" 4 "cancelled";
VAL_ 905 ObjDistStat 0 "no_object" 1 "receding" 2 "approaching";
VAL_ 909 CF_VSM_Warn 2 "FCW" 3 "AEB";
VAL_ 916 ACCEnable 0 "SCC ready" 1 "SCC temp fault" 2 "SCC permanent fault" 3 "SCC permanent fault, communication issue";
VAL_ 1056 SCCInfoDisplay 0 "No Message" 2 "Cruise Control" 3 "Lost Lead" 4 "Standstill";
@@ -1480,6 +1480,7 @@ BO_ 905 SCC14: 8 SCC
SG_ JerkLowerLimit : 19|7@1+ (0.1,0) [0|12.7] "m/s^3" ESC
SG_ ACCMode : 32|3@1+ (1,0) [0|7] "" CLU,HUD,LDWS_LKAS,ESC
SG_ ObjGap : 56|8@1+ (1,0) [0|255] "" CLU,HUD,ESC
SG_ ObjDistStat : 42|2@1+ (1,0) [0|3] "" XXX
BO_ 1157 LFAHDA_MFC: 4 XXX
SG_ HDA_USM : 0|2@1+ (1,0) [0|3] "" XXX
@@ -1671,6 +1672,7 @@ VAL_ 871 CF_Lvr_IsgState 0 "enabled" 1 "activated" 2 "unknown" 3 "disabled";
VAL_ 871 CF_Lvr_Gear 12 "T" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P";
VAL_ 882 Elect_Gear_Shifter 4 "S" 5 "D" 8 "S" 6 "N" 7 "R" 0 "P";
VAL_ 905 ACCMode 0 "off" 1 "enabled" 2 "driver_override" 3 "off_maybe_fault" 4 "cancelled";
VAL_ 905 ObjDistStat 0 "no_object" 1 "receding" 2 "approaching";
VAL_ 909 CF_VSM_Warn 2 "FCW" 3 "AEB";
VAL_ 916 ACCEnable 0 "SCC ready" 1 "SCC temp fault" 2 "SCC permanent fault" 3 "SCC permanent fault, communication issue";
VAL_ 1056 SCCInfoDisplay 0 "No Message" 2 "Cruise Control" 3 "Lost Lead" 4 "Standstill";
@@ -0,0 +1,23 @@
VERSION ""
NS_ :
BS_:
BU_: INTERCEPTOR NEO
BO_ 512 GAS_COMMAND: 6 NEO
SG_ GAS_COMMAND : 7|16@0+ (0.672,-177.408) [0|255] "" INTERCEPTOR
SG_ GAS_COMMAND2 : 23|16@0+ (0.332,-165.004) [0|255] "" INTERCEPTOR
SG_ ENABLE : 39|1@0+ (1,0) [0|1] "" INTERCEPTOR
SG_ COUNTER_PEDAL : 35|4@0+ (1,0) [0|15] "" INTERCEPTOR
SG_ CHECKSUM_PEDAL : 47|8@0+ (1,0) [0|255] "" INTERCEPTOR
BO_ 513 GAS_SENSOR: 6 INTERCEPTOR
SG_ INTERCEPTOR_GAS : 7|16@0+ (0.672,-177.408) [0|255] "" NEO
SG_ INTERCEPTOR_GAS2 : 23|16@0+ (0.332,-165.004) [0|255] "" NEO
SG_ STATE : 39|4@0+ (1,0) [0|15] "" NEO
SG_ COUNTER_PEDAL : 35|4@0+ (1,0) [0|15] "" NEO
SG_ CHECKSUM_PEDAL : 47|8@0+ (1,0) [0|255] "" NEO
VAL_ 513 STATE 5 "FAULT_TIMEOUT" 4 "FAULT_STARTUP" 3 "FAULT_SCE" 2 "FAULT_SEND" 1 "FAULT_BAD_CHECKSUM" 0 "NO_FAULT" ;
CM_ "Kia Ray EV comma pedal uses the standard 0x200/0x201 rolling counter and CRC8 protocol. Channel scaling is Ray-only, estimated from the September 15 route: sensor rest raw 264/497, slopes about 3.79/7.68 raw counts per native E_EMS11 pedal unit, and 100 native units mapped to 255 comma pedal command units. Confirm against installed Ray firmware before increasing the command cap.";
+5 -1
View File
@@ -217,6 +217,9 @@ BO_ 513 SDM1: 5 GTW
SG_ SDM_bcklPassStatus : 3|2@0+ (1,0) [0|3] "" NEO
SG_ SDM_bcklDrivStatus : 5|2@0+ (1,0) [0|3] "" NEO
BO_ 529 RCM_status: 8 RCM
SG_ RCM_buckleDriverStatus : 15|2@0+ (1,0) [0|3] "" GTW,OCS,DAS
BO_ 532 EPB_epasControl: 3 EPB
SG_ EPB_epasControlChecksum : 23|8@0+ (1,0) [0|255] "" NEO,EPAS
SG_ EPB_epasControlCounter : 11|4@0+ (1,0) [0|15] "" NEO,EPAS
@@ -256,9 +259,11 @@ BO_ 872 DI_state: 8 DI
SG_ DI_immobilizerState : 28|3@1+ (1,0) [0|0] "" NEO
SG_ DI_speedUnits : 31|1@1+ (1,0) [0|1] "" NEO
SG_ DI_cruiseSet : 32|9@1+ (0.5,0) [0|255.5] "speed" NEO
SG_ DI_hw1DigitalSpeed : 32|8@1+ (1,0) [0|250] "speed" NEO
SG_ DI_aebState : 41|3@1+ (1,0) [0|0] "" NEO
SG_ DI_stateCounter : 44|4@1+ (1,0) [0|0] "" NEO
SG_ DI_digitalSpeed : 48|8@1+ (1,0) [0|250] "" NEO
SG_ DI_hw1CruiseSet : 48|8@1+ (1,0) [0|250] "speed" NEO
SG_ DI_stateChecksum : 56|8@1+ (1,0) [0|0] "" NEO
BO_ 109 SBW_RQ_SCCM: 4 STW
@@ -906,4 +911,3 @@ VAL_ 1001 DAS_turnIndicatorRequestReason 6 "DAS_ACTIVE_COMMANDED_LANE_CHANGE" 5
VAL_ 1160 DAS_steeringAngleRequest 16384 "ZERO_ANGLE" ;
VAL_ 1160 DAS_steeringControlType 1 "ANGLE_CONTROL" 3 "DISABLED" 0 "NONE" 2 "RESERVED" ;
VAL_ 1160 DAS_steeringHapticRequest 1 "ACTIVE" 0 "IDLE" ;
@@ -384,3 +384,4 @@ extern const safety_hooks rivian_hooks;
extern const safety_hooks psa_hooks;
extern const safety_hooks volvo_hooks;
extern const safety_hooks tesla_preap_hooks;
extern const safety_hooks tesla_legacy_hooks;
+71 -1
View File
@@ -67,6 +67,9 @@ const LongitudinalLimits HYUNDAI_LONG_LIMITS = {
#define HYUNDAI_NON_SCC_EV_ADDR_CHECK \
{.msg = {{0x592U, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
#define HYUNDAI_RAY_PEDAL_ADDR_CHECK \
{.msg = {{0x201U, 0, 6, 50U, .max_counter = 15U, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \
static const CanMsg HYUNDAI_TX_MSGS[] = {
HYUNDAI_COMMON_TX_MSGS(0, false)
};
@@ -75,6 +78,11 @@ static const CanMsg HYUNDAI_REFRESH_TX_MSGS[] = {
HYUNDAI_COMMON_TX_MSGS(0, true)
};
static const CanMsg HYUNDAI_RAY_PEDAL_TX_MSGS[] = {
HYUNDAI_COMMON_TX_MSGS(0, true)
{0x200, 0, 6, .check_relay = false}, // comma pedal only, not Hyundai EMS20
};
static const CanMsg HYUNDAI_LONG_TX_MSGS[] = {
HYUNDAI_LONG_COMMON_TX_MSGS(0, false)
{0x38D, 0, 8, .check_relay = false}, // FCA11 Bus 0
@@ -90,6 +98,7 @@ static const CanMsg HYUNDAI_LONG_REFRESH_TX_MSGS[] = {
};
static bool hyundai_legacy = false;
static bool hyundai_ray_pedal = false;
static bool hyundai_can_canfd_blended_hda2 = false;
static bool hyundai_acc_main_on_rx_prev = false;
@@ -120,6 +129,8 @@ static uint8_t hyundai_get_counter(const CANPacket_t *msg) {
cnt = byte_421 & 0xFU;
} else if (msg->addr == 0x4F1U) {
cnt = (msg->data[3] >> 4) & 0xFU;
} else if (hyundai_ray_pedal && (msg->addr == 0x201U)) {
cnt = msg->data[4] & 0xFU;
} else {
}
return cnt;
@@ -136,11 +147,24 @@ static uint32_t hyundai_get_checksum(const CANPacket_t *msg) {
chksum = msg->data[6] & 0xFU;
} else if (msg->addr == 0x421U) {
chksum = hyundai_can_canfd_blended ? msg->data[0] : msg->data[7] >> 4;
} else if (hyundai_ray_pedal && (msg->addr == 0x201U)) {
chksum = msg->data[5];
} else {
}
return chksum;
}
static uint8_t hyundai_ray_pedal_checksum(const CANPacket_t *msg) {
uint8_t crc = 0xFFU;
for (int i = 4; i >= 0; i--) {
crc ^= msg->data[i];
for (int j = 0; j < 8; j++) {
crc = (crc & 0x80U) ? (uint8_t)((crc << 1U) ^ 0xD5U) : (uint8_t)(crc << 1U);
}
}
return crc;
}
static void hyundai_rx_all_hook(const CANPacket_t *msg) {
if ((msg->addr == 0x53EU) && (msg->bus == 2U) && (GET_LEN(msg) == 6U)) {
hyundai_has_lkas12 = true;
@@ -148,6 +172,10 @@ static void hyundai_rx_all_hook(const CANPacket_t *msg) {
}
static uint32_t hyundai_compute_checksum(const CANPacket_t *msg) {
if (hyundai_ray_pedal && (msg->addr == 0x201U)) {
return hyundai_ray_pedal_checksum(msg);
}
uint8_t chksum = 0;
if (msg->addr == 0x386U) {
// count the bits
@@ -231,7 +259,11 @@ static void hyundai_rx_hook(const CANPacket_t *msg) {
}
// gas press, different for EV, hybrid, and ICE models
if ((msg->addr == 0x371U) && hyundai_ev_gas_signal) {
if ((msg->addr == 0x201U) && hyundai_ray_pedal) {
const uint16_t track1 = ((uint16_t)msg->data[0] << 8U) | msg->data[1];
const uint16_t track2 = ((uint16_t)msg->data[2] << 8U) | msg->data[3];
gas_pressed = (track1 > 272U) || (track2 > 513U);
} else if ((msg->addr == 0x371U) && hyundai_ev_gas_signal && !hyundai_ray_pedal) {
gas_pressed = (((msg->data[4] & 0x7FU) << 1) | (msg->data[3] >> 7)) != 0U;
} else if ((msg->addr == 0x371U) && hyundai_hybrid_gas_signal) {
gas_pressed = msg->data[7] != 0U;
@@ -291,6 +323,22 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
bool tx = true;
if (hyundai_ray_pedal && (msg->addr == 0x200U)) {
const uint16_t track1 = ((uint16_t)msg->data[0] << 8U) | msg->data[1];
const uint16_t track2 = ((uint16_t)msg->data[2] << 8U) | msg->data[3];
const bool enabled = (msg->data[4] & 0x80U) != 0U;
const int expected_track2 = 497 + (2 * ((int)track1 - 264));
if ((msg->data[4] & 0x70U) != 0U ||
(msg->data[5] != hyundai_ray_pedal_checksum(msg)) ||
(enabled && (track1 < 264U || track1 > 397U || track2 < 497U || track2 > 766U ||
SAFETY_ABS((int)track2 - expected_track2) > 40)) ||
(!enabled && ((track1 != 0U) || (track2 != 0U))) ||
longitudinal_interceptor_checks(msg) ||
(enabled && (!get_longitudinal_allowed() || brake_pressed_prev))) {
tx = false;
}
}
if ((msg->addr == 0x53EU) && !hyundai_has_lkas12) {
tx = false;
}
@@ -390,6 +438,10 @@ static bool hyundai_tx_hook(const CANPacket_t *msg) {
return tx;
}
static bool hyundai_fwd_hook(int bus_num, int addr) {
return (bus_num == 2) && (addr == 0x53E) && hyundai_has_lkas12;
}
static safety_config hyundai_init(uint16_t param) {
static const CanMsg HYUNDAI_CAMERA_SCC_TX_MSGS[] = {
HYUNDAI_COMMON_TX_MSGS(2, false)
@@ -457,6 +509,10 @@ static safety_config hyundai_init(uint16_t param) {
};
hyundai_common_init(param);
hyundai_ray_pedal = (param & (uint16_t)~(32U | 128U | 2048U)) == 0x9405U;
if (hyundai_ray_pedal) {
hyundai_longitudinal = true; // button engagement; no Hyundai SCC TX
}
hyundai_legacy = false;
hyundai_can_canfd_blended_hda2 = hyundai_can_canfd_blended && hyundai_canfd_lka_steering;
hyundai_aol_main_lkas_sync = GET_FLAG(param, 32U);
@@ -467,6 +523,17 @@ static safety_config hyundai_init(uint16_t param) {
}
safety_config ret;
if (hyundai_ray_pedal) {
static RxCheck hyundai_ray_pedal_rx_checks[] = {
HYUNDAI_COMMON_RX_CHECKS(false)
HYUNDAI_NON_SCC_EV_ADDR_CHECK
HYUNDAI_LDA_BUTTON_ADDR_CHECK
HYUNDAI_RAY_PEDAL_ADDR_CHECK
};
SET_RX_CHECKS(hyundai_ray_pedal_rx_checks, ret);
SET_TX_MSGS(HYUNDAI_RAY_PEDAL_TX_MSGS, ret);
return ret;
}
if (hyundai_longitudinal) {
// Use CLU11 (buttons) to manage controls allowed instead of SCC cruise state
static RxCheck hyundai_long_rx_checks[] = {
@@ -696,6 +763,7 @@ static safety_config hyundai_legacy_init(uint16_t param) {
hyundai_common_init(param);
hyundai_legacy = true;
hyundai_ray_pedal = false;
hyundai_can_canfd_blended_hda2 = false;
hyundai_camera_scc = false;
hyundai_can_refresh_msgs = false;
@@ -711,6 +779,7 @@ const safety_hooks hyundai_hooks = {
.get_counter = hyundai_get_counter,
.get_checksum = hyundai_get_checksum,
.compute_checksum = hyundai_compute_checksum,
.fwd = hyundai_fwd_hook,
};
const safety_hooks hyundai_legacy_hooks = {
@@ -721,4 +790,5 @@ const safety_hooks hyundai_legacy_hooks = {
.get_counter = hyundai_get_counter,
.get_checksum = hyundai_get_checksum,
.compute_checksum = hyundai_compute_checksum,
.fwd = hyundai_fwd_hook,
};
@@ -0,0 +1,241 @@
#pragma once
#include "opendbc/safety/declarations.h"
#define TESLA_LEGACY_FLAG_HW1 8U
static bool tesla_external_panda = false;
static bool tesla_hw1 = false;
static bool tesla_hw2 = false;
static bool tesla_hw3 = false;
static bool tesla_legacy_longitudinal = false;
static int chassis_bus = 0U;
static int das_control_msg = 0x2bfU;
static int di_torque1_msg = 0x106U;
static bool tesla_legacy_stock_aeb = false;
static bool tesla_legacy_stock_lkas = false;
static bool tesla_legacy_stock_lkas_prev = false;
static void tesla_legacy_rx_hook(const CANPacket_t *msg) {
// EPAS_sysStatus: steering angle, driver hands, and EAC status.
if (!tesla_external_panda && (msg->bus == 0U) && (msg->addr == 0x370U)) {
const int angle_meas_new = (((msg->data[4] & 0x3FU) << 8) | msg->data[5]) - 8192U;
update_sample(&angle_meas, angle_meas_new);
const int hands_on_level = msg->data[4] >> 6;
const int eac_status = msg->data[6] >> 5;
const int eac_error_code = msg->data[2] >> 4;
steering_disengage = (hands_on_level >= 3) || ((eac_status == 0) && (eac_error_code == 9));
}
// ESP_B: ESP_vehicleSpeed.
if (!tesla_external_panda && (msg->bus == (unsigned)chassis_bus) && (msg->addr == 0x155U)) {
const float speed = ((msg->data[6] | (msg->data[5] << 8)) * 0.01) * KPH_TO_MS;
UPDATE_VEHICLE_SPEED(speed);
}
// DI_torque1: pedal position. HW1 uses the 0x108 message variant.
if ((tesla_external_panda || tesla_hw1) && (msg->bus == 0U) && (msg->addr == di_torque1_msg)) {
gas_pressed = msg->data[6] != 0U;
}
if (((tesla_external_panda) && (msg->bus == 0U) && (msg->addr == 0x1f8U)) ||
((!tesla_external_panda) && (msg->bus == (unsigned)chassis_bus) && (msg->addr == 0x20aU))) {
brake_pressed = (((msg->data[0] & 0x0CU) >> 2) != 1U);
}
// DI_state: cruise state.
if (((tesla_external_panda) && (msg->bus == 0U) && (msg->addr == 0x256U)) ||
((!tesla_external_panda) && (msg->bus == (unsigned)chassis_bus) && (msg->addr == 0x368U))) {
const int cruise_state = (msg->data[1] >> 4) & 0x07U;
const bool cruise_engaged = (cruise_state == 2) || (cruise_state == 3) || (cruise_state == 4) ||
(cruise_state == 6) || (cruise_state == 7);
vehicle_moving = cruise_state != 3;
pcm_cruise_check(cruise_engaged);
}
if (msg->bus == 2U) {
if ((tesla_external_panda || tesla_hw1) && msg->addr == das_control_msg) {
tesla_legacy_stock_aeb = (msg->data[2] & 0x03U) == 1U;
}
if (!tesla_external_panda && msg->addr == 0x488U) {
const int steering_control_type = msg->data[2] >> 6;
const bool stock_lkas_now = steering_control_type == 2;
if (stock_lkas_now && !tesla_legacy_stock_lkas_prev && !controls_allowed) {
tesla_legacy_stock_lkas = true;
}
if (!stock_lkas_now) {
tesla_legacy_stock_lkas = false;
}
tesla_legacy_stock_lkas_prev = stock_lkas_now;
}
}
}
static bool tesla_legacy_tx_hook(const CANPacket_t *msg) {
const AngleSteeringLimits TESLA_STEERING_LIMITS = {
.max_angle = 3600,
.angle_deg_to_can = 10,
.frequency = 50U,
};
const AngleSteeringParams TESLA_LEGACY_STEERING_PARAMS = {
.slip_factor = -0.0005666493436310427,
.steer_ratio = 15.,
.wheelbase = 2.96,
};
const LongitudinalLimits TESLA_LONG_LIMITS = {
.max_accel = 425,
.min_accel = 288,
.inactive_accel = 375,
};
bool violation = false;
// DAS_steeringControl: angle is encoded in 0.1 degree units with a 1638.35 offset.
if (!tesla_external_panda && (msg->addr == 0x488U)) {
const int raw_angle_can = ((msg->data[0] & 0x7FU) << 8) | msg->data[1];
const int desired_angle = raw_angle_can - 16384;
const int steer_control_type = msg->data[2] >> 6;
const bool steer_control_enabled = steer_control_type == 1;
violation |= steer_angle_cmd_checks_vm(desired_angle, steer_control_enabled,
TESLA_STEERING_LIMITS, TESLA_LEGACY_STEERING_PARAMS);
const bool valid_steer_control_type = (steer_control_type == 0) || (steer_control_type == 1);
violation |= !valid_steer_control_type;
violation |= tesla_legacy_stock_lkas;
}
// DAS_control: HW1 longitudinal control is sent to the powertrain bus (bus 0).
if ((tesla_external_panda || tesla_hw1) && (msg->addr == das_control_msg)) {
const int aeb_event = msg->data[2] & 0x03U;
violation |= aeb_event != 0;
violation |= tesla_legacy_stock_aeb;
const int raw_accel_max = ((msg->data[6] & 0x1FU) << 4) | (msg->data[5] >> 4);
const int raw_accel_min = ((msg->data[5] & 0x0FU) << 5) | (msg->data[4] >> 3);
if (tesla_legacy_longitudinal) {
violation |= (raw_accel_max < TESLA_LONG_LIMITS.inactive_accel) &&
(raw_accel_min < TESLA_LONG_LIMITS.inactive_accel);
violation |= longitudinal_accel_checks(raw_accel_max, TESLA_LONG_LIMITS);
violation |= longitudinal_accel_checks(raw_accel_min, TESLA_LONG_LIMITS);
} else {
// Stock ACC may only be cancelled, never spoofed or accelerated.
const int acc_state = msg->data[1] >> 4;
violation |= acc_state != 13;
violation |= (raw_accel_max != TESLA_LONG_LIMITS.inactive_accel) ||
(raw_accel_min != TESLA_LONG_LIMITS.inactive_accel);
}
}
return !violation;
}
static bool tesla_legacy_fwd_hook(int bus_num, int addr) {
bool block_msg = false;
if (bus_num == 2) {
if (!tesla_external_panda && !tesla_hw1 && (addr == 0x27dU)) {
block_msg = true;
}
if (!tesla_external_panda && (addr == 0x488U) && !tesla_legacy_stock_lkas) {
block_msg = true;
}
if ((tesla_external_panda || tesla_hw1) && (addr == das_control_msg) && !tesla_legacy_stock_aeb) {
block_msg = true;
}
}
return block_msg;
}
static safety_config tesla_legacy_init(uint16_t param) {
const int TESLA_FLAG_EXTERNAL_PANDA = 4;
const int TESLA_FLAG_HW2 = 16;
const int TESLA_FLAG_HW3 = 32;
tesla_external_panda = GET_FLAG(param, TESLA_FLAG_EXTERNAL_PANDA);
tesla_hw1 = GET_FLAG(param, TESLA_LEGACY_FLAG_HW1);
tesla_hw2 = GET_FLAG(param, TESLA_FLAG_HW2);
tesla_hw3 = GET_FLAG(param, TESLA_FLAG_HW3);
tesla_legacy_longitudinal = GET_FLAG(param, 1);
tesla_legacy_stock_aeb = false;
tesla_legacy_stock_lkas = false;
tesla_legacy_stock_lkas_prev = false;
chassis_bus = 0U;
di_torque1_msg = 0x106U;
das_control_msg = tesla_external_panda ? 0x2bfU : 0x2b9U;
static const CanMsg TESLA_TX_LEGACY_MSGS[] = {
{0x488, 0, 4, .check_relay = true, .disable_static_blocking = true},
{0x27D, 0, 3, .check_relay = true, .disable_static_blocking = true},
};
static const CanMsg TESLA_LEGACY_PT_MSGS[] = {
{0x2bf, 0, 8, .check_relay = true, .disable_static_blocking = true},
};
static const CanMsg TESLA_TX_LEGACY_HW1_MSGS[] = {
{0x488, 0, 4, .check_relay = true, .disable_static_blocking = true},
{0x2b9, 0, 8, .check_relay = true, .disable_static_blocking = true},
};
static RxCheck tesla_legacy_pt_rx_checks[] = {
{.msg = {{0x106, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x1f8, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x2bf, 2, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x256, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
};
static RxCheck tesla_legacy_hw1_rx_checks[] = {
{.msg = {{0x108, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x2b9, 2, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x370, 0, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x155, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x20a, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x368, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
};
static RxCheck tesla_legacy_hw2_rx_checks[] = {
{.msg = {{0x370, 0, 8, 25U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x155, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x20a, 0, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x368, 0, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
};
static RxCheck tesla_legacy_hw3_rx_checks[] = {
{.msg = {{0x370, 0, 8, 100U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x155, 1, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x20a, 1, 8, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x368, 1, 8, 10U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
{.msg = {{0x488, 2, 4, 50U, .ignore_quality_flag = true, .ignore_checksum = true, .ignore_counter = true}, {0}, {0}}},
};
if (tesla_external_panda && (tesla_hw3 || tesla_hw2)) {
return BUILD_SAFETY_CFG(tesla_legacy_pt_rx_checks, TESLA_LEGACY_PT_MSGS);
}
if (tesla_hw3) {
chassis_bus = 1U;
return BUILD_SAFETY_CFG(tesla_legacy_hw3_rx_checks, TESLA_TX_LEGACY_MSGS);
}
if (tesla_hw1) {
di_torque1_msg = 0x108U;
return BUILD_SAFETY_CFG(tesla_legacy_hw1_rx_checks, TESLA_TX_LEGACY_HW1_MSGS);
}
return BUILD_SAFETY_CFG(tesla_legacy_hw2_rx_checks, TESLA_TX_LEGACY_MSGS);
}
const safety_hooks tesla_legacy_hooks = {
.init = tesla_legacy_init,
.rx = tesla_legacy_rx_hook,
.tx = tesla_legacy_tx_hook,
.fwd = tesla_legacy_fwd_hook,
};
+2 -2
View File
@@ -235,9 +235,9 @@ static bool toyota_tx_hook(const CANPacket_t *msg) {
// the EPS faults when the steering angle rate is above a certain threshold for too long. to prevent this,
// we allow setting STEER_REQUEST bit to 0 while maintaining the requested torque value for a single frame
.min_valid_request_frames = 18,
.min_valid_request_frames = 17,
.max_invalid_request_frames = 1,
.min_valid_request_rt_interval = 171000, // 171ms; a ~10% buffer on cutting every 19 frames
.min_valid_request_rt_interval = 162000, // 162ms; a ~10% buffer on cutting every 18 frames
.has_steer_req_tolerance = true,
};
+3 -1
View File
@@ -12,6 +12,7 @@
#include "opendbc/safety/modes/toyota.h"
#include "opendbc/safety/modes/tesla.h"
#include "opendbc/safety/modes/tesla_preap.h"
#include "opendbc/safety/modes/tesla_legacy.h"
#include "opendbc/safety/modes/gm.h"
#include "opendbc/safety/modes/ford.h"
#include "opendbc/safety/modes/hyundai.h"
@@ -502,7 +503,8 @@ int set_safety_hooks(uint16_t mode, uint16_t param) {
int hook_config_count = sizeof(safety_hook_registry) / sizeof(safety_hook_config);
for (int i = 0; i < hook_config_count; i++) {
if (safety_hook_registry[i].id == mode) {
current_hooks = safety_hook_registry[i].hooks;
current_hooks = ((mode == SAFETY_TESLA) && GET_FLAG(param, TESLA_LEGACY_FLAG_HW1)) ?
&tesla_legacy_hooks : safety_hook_registry[i].hooks;
current_safety_mode = mode;
current_safety_param = param;
set_status = 0; // set
@@ -434,9 +434,11 @@ def test_hyundai_lkas12_tx_requires_stock_camera_message():
lkas12 = libsafety_py.make_CANPacket(0x53E, 0, bytes(6))
assert not safety.safety_tx_hook(lkas12)
assert safety.safety_fwd_hook(2, 0x53E) == 0
safety.safety_rx_hook(libsafety_py.make_CANPacket(0x53E, 2, bytes(6)))
assert safety.safety_tx_hook(lkas12)
assert safety.safety_fwd_hook(2, 0x53E) == -1
class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety):
@@ -0,0 +1,84 @@
import pytest
from opendbc.can import CANPacker
from opendbc.car import create_gas_interceptor_command
from opendbc.car.structs import CarParams
from opendbc.safety.tests.libsafety import libsafety_py
@pytest.mark.parametrize("param", [0x9405, 0x9C05, 0x9401, 0x1005, 0])
def test_ray_pedal_tx_isolation_and_limits(param):
safety = libsafety_py.libsafety
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, param)
safety.init_tests()
safety.set_controls_allowed(True)
packer = CANPacker("hyundai_kia_ray_pedal")
def tx(gas):
addr, dat, bus = create_gas_interceptor_command(packer, gas, 3)
return safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, dat))
has_ray_signature = param in (0x9405, 0x9C05)
assert tx(0) is has_ray_signature
assert tx(0.35) is has_ray_signature
assert not tx(0.36) # above the Ray-only initial command cap
assert not tx(1.0)
if has_ray_signature:
safety.set_controls_allowed(False)
assert tx(0)
assert not tx(0.1)
safety.set_controls_allowed(True)
safety.set_gas_pressed_prev(True)
assert not tx(0.1)
safety.set_gas_pressed_prev(False)
addr, dat, bus = create_gas_interceptor_command(packer, 0.1, 3)
bad_crc = bytearray(dat)
bad_crc[-1] ^= 1
assert not safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, bytes(bad_crc)))
def test_ray_pedal_rx_crc_is_checked_only_for_ray_signature():
safety = libsafety_py.libsafety
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0x9405)
safety.init_tests()
dat = bytes.fromhex("01f403d55de8")
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x201, 0, dat))
bad_crc = bytearray(dat)
bad_crc[-1] ^= 1
assert not safety.safety_rx_hook(libsafety_py.make_CANPacket(0x201, 0, bytes(bad_crc)))
def test_ray_native_commanded_gas_does_not_cancel_driver_override_safety():
safety = libsafety_py.libsafety
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0x9405)
safety.init_tests()
safety.set_controls_allowed(True)
packer = CANPacker("hyundai_kia_ray_pedal")
physical_rest = bytes.fromhex("010801f30cef")
native_gas = bytes.fromhex("004e008000ae0700")
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x201, 0, physical_rest))
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x371, 0, native_gas))
assert not safety.get_gas_pressed_prev()
addr, dat, bus = create_gas_interceptor_command(packer, 0.1, 3)
assert safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, dat))
physical_press = packer.make_can_msg("GAS_SENSOR", 0, {
"INTERCEPTOR_GAS": (310 - 264) * 0.672,
"INTERCEPTOR_GAS2": (593 - 497) * 0.332,
"STATE": 0, "COUNTER_PEDAL": 13,
})
press_addr, press_dat, press_bus = physical_press
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(press_addr, press_bus, press_dat))
assert safety.get_gas_pressed_prev()
assert not safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, dat))
def test_non_ray_hyundai_ev_keeps_native_driver_gas_detection():
safety = libsafety_py.libsafety
safety.set_safety_hooks(CarParams.SafetyModel.hyundai, 0x1001)
safety.init_tests()
native_gas = bytes.fromhex("004e008000ae0700")
assert safety.safety_rx_hook(libsafety_py.make_CANPacket(0x371, 0, native_gas))
assert safety.get_gas_pressed_prev()
@@ -0,0 +1,73 @@
import pytest
from opendbc.can import CANPacker
from opendbc.car import Bus
from opendbc.car.tesla.teslacan_legacy import TeslaCANRaven
from opendbc.car.tesla.values import CANBUS, CAR, DBC, TeslaSafetyFlags
from opendbc.safety.tests.libsafety import libsafety_py
@pytest.fixture
def legacy_safety():
safety = libsafety_py.libsafety
packer = CANPacker(DBC[CAR.TESLA_MODEL_S_HW1][Bus.pt])
return safety, TeslaCANRaven({CANBUS.party: packer})
def tx(safety, msg):
addr, data, bus = msg
return safety.safety_tx_hook(libsafety_py.make_CANPacket(addr, bus, data))
def test_hw1_steering_requires_controls_allowed(legacy_safety):
safety, can = legacy_safety
safety.set_safety_hooks(10, TeslaSafetyFlags.FLAG_HW1.value)
safety.init_tests()
safety.set_angle_meas(0, 0)
safety.set_controls_allowed(False)
assert tx(safety, can.create_steering_control(0, 0, False))
assert not tx(safety, can.create_steering_control(0, 0, True))
safety.set_controls_allowed(True)
assert tx(safety, can.create_steering_control(0, 0, True))
@pytest.mark.parametrize("alpha_long", [False, True])
def test_hw1_accel_only_allowed_with_alpha_long_and_engagement(legacy_safety, alpha_long):
safety, can = legacy_safety
param = TeslaSafetyFlags.FLAG_HW1.value | (TeslaSafetyFlags.LONG_CONTROL.value if alpha_long else 0)
safety.set_safety_hooks(10, param)
safety.init_tests()
safety.set_controls_allowed(True)
assert tx(safety, can.create_longitudinal_command(13, 0, 0, 10, False, False))
assert tx(safety, can.create_longitudinal_command(4, 1, 0, 10, True, False)) == alpha_long
safety.set_controls_allowed(False)
assert not tx(safety, can.create_longitudinal_command(4, 1, 0, 10, True, False))
def test_hw1_stock_ap_steer_and_acc_are_blocked_from_forwarding(legacy_safety):
safety, _ = legacy_safety
safety.set_safety_hooks(10, TeslaSafetyFlags.FLAG_HW1.value)
safety.init_tests()
assert safety.safety_fwd_hook(2, 0x488) == -1
assert safety.safety_fwd_hook(2, 0x2b9) == -1
assert safety.safety_fwd_hook(2, 0x370) == 0
def test_hw1_flag_dispatch_does_not_change_modern_or_preap_hooks(legacy_safety):
safety, can = legacy_safety
steer = can.create_steering_control(0, 0, False)
safety.set_safety_hooks(10, TeslaSafetyFlags.FLAG_HW1.value)
safety.init_tests()
assert tx(safety, steer)
# APS monitor exists only in the modern Tesla TX whitelist; HW1 must not
# accidentally inherit it from the unflagged hook.
monitor = libsafety_py.make_CANPacket(0x27d, 0, bytes(3))
assert not safety.safety_tx_hook(monitor)
safety.set_safety_hooks(10, 0)
safety.init_tests()
assert safety.safety_tx_hook(monitor)
safety.set_safety_hooks(35, 0)
safety.init_tests()
assert safety.safety_fwd_hook(2, 0x370) == -1
@@ -254,7 +254,7 @@ class TestToyotaSafetyTorque(TestToyotaSafetyBase, common.MotorTorqueSteeringSaf
TORQUE_MEAS_TOLERANCE = 1 # toyota safety adds one to be conservative for rounding
# Safety around steering req bit
MIN_VALID_STEERING_FRAMES = 18
MIN_VALID_STEERING_FRAMES = 17
MAX_INVALID_STEERING_FRAMES = 1
def setUp(self):
+1
View File
@@ -130,4 +130,5 @@ flake8-implicit-str-concat.allow-multiline=false
include-package-data = true
[tool.setuptools.package-data]
"opendbc.dbc" = ["hyundai_kia_ray_pedal.dbc"]
"opendbc.safety" = ["*.h", "board/*.h", "board/drivers/*.h", "modes/*.h"]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,2 +1,2 @@
extern const uint8_t gitversion[19];
const uint8_t gitversion[19] = "DEV-bf00f88b-DEBUG";
const uint8_t gitversion[19] = "DEV-14370fe9-DEBUG";
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

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