From 4078e6fcfad37faf74d54a692114d33661de5473 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:28:05 -0500 Subject: [PATCH] Zone 5 --- cereal/libcereal.a | Bin 645116 -> 645140 bytes cereal/log.capnp | 10 +-- opendbc_repo/opendbc/car/hyundai/carstate.py | 11 +++ .../opendbc/car/hyundai/tests/test_hyundai.py | 16 +++++ opendbc_repo/opendbc/car/nissan/interface.py | 65 +++++++++++------- .../opendbc/car/nissan/tests/test_nissan.py | 34 +++++---- .../controls/lib/latcontrol_vehicle_tunes.py | 2 +- selfdrive/selfdrived/selfdrived.py | 4 +- selfdrive/selfdrived/tests/test_selfdrived.py | 8 +++ selfdrive/ui/layouts/sidebar.py | 4 +- system/hardware/base.py | 3 +- system/hardware/hardwared.py | 38 ++++++---- system/hardware/tici/hardware.py | 11 ++- 13 files changed, 141 insertions(+), 65 deletions(-) diff --git a/cereal/libcereal.a b/cereal/libcereal.a index 45677bf9565a9c560a09a954f3bbaf71d0fd71ac..e24b78e71f8aa393a31f7f1e28bb0d1aa98103ae 100644 GIT binary patch delta 8613 zcmc&&dsI}{)j#)fE`y4J;DbTsAu(cnL81*9AE4EMMgvw8&=?_ViizUj6MO-p224~C zaf8nx(FUR-pvl2k9a7t3zhKceRx}T(nv4nBA;wfc)26>O7r(W}@1JI^bamDm_TBsU z+xzUZ&$;`Y;rl~-7yRD0xb%0n!wES_$;IzA2PqMUf8RWRhjL2{U84M^hxaej7cH7HXTEI= z`3+GWq5blMoKR+r&Q^$ z89sv?6CehkPy+Dk&g;>#UMw8CAHjOBZ`>mK!qjXgG89YBK=GQ8R zIqw$D*iFK7u=shM^1WiT!7yT&j5nONgH9WdDaVc2VT>6g#iZlPXdh$Z2*%Wrw0oKs zMB%<(T`1?c(p!AhpsZJnHKQ1NFNT7elt5AV2PHt`qhc7V5CU=N{tIv&u6H z{}j(w(W^=6!TE()#=6pokCpqp*J#GZiV+u-JkFmT!)$F@`4K=>qVbM0iyPJB8Jqb$ zP0G>(MZ$fh+RLbVj~NVPbUJP(L#E1M+&_TnCwfIPax?r|{J08T*rH^j7bOa@%Ca0!3qgwZ9h+ zn!;GD_{WoK9yi8LW$e%k6cmEwTl=d|iN8dtm4d=jIuP`=smbu+q*Xcpx!xz<&s*ya4u?w@z6kMZF3x{3pEXGY! z$E(IAVDYm}7rF6F?7~axD+;$^j94+^W%aPm$IM}DtGM%u>hH^cIhU~w!e^0sT;-cy zV{C?KT%vw$@K@$B)>oWdsfHOw|8&OE)9LIs)L+(k+fua~4a!q%jfFr$ZSFb=eXY9E zF#6cAK{9BQ9o0!_HM!#K2DMi8>5_r1w$PmFYvkwa6(II)R3~sF7{)CN>9Z+_-?~8$ zrU{GGj$&l7dP(8ifGrojO602KFJ>%ri5a_7Fv!zNz`MC!J**fjmoVnPlvdkd_Wd0D zAZ54SQC!`r?$dbJrFa9xM!WisVjRG8+fw(Yr73T#-}~H#q4(+s3lRBF!(Z6mQ5^xs ztSrXPWYIA06(pvdPzNZ+pI0#!vYM(UVlVAIsUA^y7#vrN8K=}9D$l|5EwQRzea4?_ zxs3fnocy!;HQxC=##+R|3uC`~Vkwzfiwd_%#rQiG5D>s?OKH$ykKgcvHQq@gZ+97A2OnsrOamcX%GM(cHZ5 zAdZ`=zc~J_%2l7?8yUM(XwG&EwK*}{E8nR$)hGinb+fyEV(~roBa4x|nX$(T-PIPe zRc(mQ!{K;a>@u|Jy76~BTS~=0J7^yo##dVyJ6LRPgKK`;OPWzr%-EU|vmp&H!CnXq z*8D_qS8W$J)|X(LZKI$F4TbHYVOFWz80$&BLbPCW*1o}75AjYfZK%RSNcO5vgS2O` zc6c^Q1>VXh72cr~iy@|lY8|=ZU&h$7?KG)X3EFlYYabJ>brA<1*A^?rc0h}^n|s&& z%1(>WI*57U+G&li*v{BhF?4{20$$s}*ecO0LffzKyq#DD(HN;cRTtDiVV|nmHJ=XeGPVL=*#_RG zSnkzb%ud#ZC_ER&vtrjYt)pUGz_V5PAroV?R{17jmYgdQ~zOBzmQ46MT8^ z1B`tt=4WWdy3zX}VZ+I@wWRWWv7?8?%9 z;p_85Eo1q0W>Qa3O+Rmc;k!YLGkD<<#`46BLhY%}##HggY3! z(O|j_Gu=XtX_iO0dYZB8V&EC=xF5d>!ew#uk~ZCkfBrv=ImAS#Hbyf({D`qP8|kyD zcsXBs8NKiP*V-W6*x1O}7iY~gv~>y0qg~p|hT%NRSkfP9B#6{X=zM?uPFt-Q)BecV zZ_d-|7jaVL+|^>ZF`)^4>AY<4!Q%Qqw96Lbx98E1F3_fQcwIB}d7X{03pl5n&5pac ztA3&r@7N44F|@b7-(q}miLp(WWxa>f_yKx1it4X7@hz7b`y=@b&~NiU;jypa19gq_ zzkkBm6yfuv9;W(C`;@V;&&+ZhGnaPC5PhZQ(;vqD?Le&weEYQCQ#B63sDDmXjSu$d zlX{3q8>ahe-1a$RgT$&4`hJ~9T*2hUz|s1mj)s1fu@|nI{pR#U9sOn+ERTy-&+GTS zc=&b362+<)^=EW$`GT?M#mVWqrWn7#^Q+GG8oZ>>^ET3)j1BmT&ThaqX}yCb-SVpb zg24xU#n>m}<~)6e!bjuzpxBtMKc)D5hUd;-n{(Sjv1`4%Z|j1?B%ujc56z}Ga$kU# z#K$&$f?`a>^Tn^*&m04noj2vtyih-_@=pNW76X^)J#>Dj756PMF-yPgWn8|+Slr*t z7L0ZuL{F^K9}z{l`sa%A+~2Tk{^mZj#nwE1io#FB;!8Kz>X&&J7i059<2v16H8Svg z*G0GH!9V3y*(tMhAJHdY$1C+7uiROD%8M%h=SA6B{lZCR2TTlLp9ehAOs5T1htcH}&Te+xq}%_!6JjL>gU zt~)gH0ycNjT4aLTNkb(XS5kz(aV5=s zrjkRrRt{DQX|5D;R3=3losq&u_Y_d-C|QM8N(CykCb|JlW%nph<1Z^Ql%>I%Ld_aD zDdepqw38Bb7^A30Y6v4WgyEpLr~ubMC5g^)7{e*jfH96zrKq4HDe9nPnF*AYas6q5EV4H1B~HRBSjtElg1QE z?g%Y_$~!`f!0!QQE{d1p3KezYGgZq$`lb^fq*|(|w+|mD!z0cZ5kIB>-9sr3OHAQKi%($lJ=F zRV}a5WGgC@LS;}Q#GGzVo!(M08eFggi84F)4jWDhV4DXRyJ;nXaR4)X5l z7STO{1yF8JU^&zxMIFWVf-#&5dO=H|8xX25r}04;*G5G_0PCn(if{@IhG?L(7Xr;m$x>ued5H9nCp8p17z-3C7kVmv7>Y7PP+KVSK%Cz2jG)}!a7m&wQoBq2 z!k|S_h7?XZ8U~}CtdH?!$g1cuXbsf%7%)3U_i>BdJ}_RPmOhAyG#-bRNg0o$ZoWjv z9>@6DCw<*ud|!CnrJ}yz#8I2n5-2(x!bZ8_Fs4z96r~i~59{W8h!*xk!Y(TBCzqj? zzUt?mPW%&aaZwS3Y7unl30U(fx<9PgaQzX%MJ><*bAF@YW`X0}{g<(VHH;~YGs|fj zJph$$pn?JJB+dgk+9xeHN=6qH=r9w-iN3pI(Ys;R8)_N-s}xIHfK_thm7@eB~iHj_M^`5z+1)%%Yv$-;-TIZGe$(;u|0b z(A+nm;S74i{pQ?z!(FnBd|+rp`KSVpRa3i>kFeE$FvH#*=oz+}QVS3$iRugBnncY~ z%cSTHh?hw<5Z0tGOxM!Eo~}t0`=;EiUz%`0geP1<TZ4k>RT#T<%!3nA@PX^PN| zhzwOCd5UCBGt&OM^^u+E7n&DU}z2j3ao9TcmCQIWx>8=lsl* zoJnnxl}3rh?iA-1qvVc=cJkAnWCyhX##JMBt6P+B#q4kwD{+sLRpOp&TM6cqM$y}# zB~i^bB%DMo(5#tgpz;$p%rh*GNx~t;;!U{WAsE+|^TfwlW5&0;KjZ0+OGc@?u34pM z7uW%17#a70GK`Ej6Ph({vgv9W;prMj8QYONTHkg=NFwVFxW-V?4%8xsDt7>Lk@rpn za#8e7U=dWa6IcYD*~w=HT3$87+DCbY#YJiKF1#NpRK5%B6gs*Kkt)c#8>8T**^QLa z@=V`|SWn+HY6HvaSZl)755W#f+=C!ZRJI3MHPM+p$O=b?9pm8EV+R&Pz3r#JS++ZZZ3IlmXoaEjgsESz%p0ZX8JQcIxNU!f`aa`UcI8s|9( zZh_~2g`(Oh@?Au-QT)5us&!r4+3}uij0YGagjRrz@hU)W2x%ve_arw^4Pa~*ze@L> ziLFGboPABQeS#<1N#`mN%Nb$9A$4ObcANPhN z>UV&@EBD<2*@mZ@p=^^pL*b(O%0X=Wh!;%U^$?EEf6!e;?<%)QtU}rmFPUVP;2A7} zTB?v2THhheF^Mt`fqXaBBwG?a$#+Sq1{rs>YE&MU z4f+@Y;ZAuB4bAtE87P0cXCU0?rya+w8~6F+V7ut(arv{PmI6=6-=DQK{)Bsv)Sm!~ zgY5*8avd>a70&RCg>(I+bQV;5QaTIz>q)-Unt$A+m%i*t&!>!EV-7iV?$^i~ujnb% z+*NOq?K3^e_+@d*O>R4dL~_X6!3PIgPPda&JjwVeG1-CF6Yr7(>;^jHfNuiztH(eI zlw6PUT%>dLz%J5_dSG=T{zIPK0rJd8{Q3Q;gEiAQrLwQ+Ql>Eus~VbRJWUsuMSE%)+NgrMWrhM4_FS^_5ijm2W+RZ<;Ezy_{~z3l63{h0V`#! z6~+K%2$jLWtJmp_6-O@jC)WxiTxByUaHTO4tuuKgyc|>}MI1FlXjdM@k64BH0hGK7 z*c2**P%Z1}(kf#lC1uNOR$~DC!(lamkoB}(Mogn-s0eJ$mU-mJJhDMsOl2_er1k%o zM>c}vUjaE_=TI_)n${j8A6G@>k-xsZ3=z4Y7E*F9s9UK_ifU?>!a>$G5OzwIqJhdF zG-tbCvBPGem~SkWZFxq3>ib4<>gH~+`BDsYtW9zrd@9~3%QNux!q+0o^1NqF`4{17 BlwSY< delta 8034 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 tuple[str, str, str, str, str, str]: if CP.flags & HyundaiFlags.EV: @@ -437,6 +442,9 @@ class CarState(CarStateBase): ret.lowSpeedAlert = self.low_speed_alert fp_ret = custom.StarPilotCarState.new_message() + if self.CP.carFingerprint in CLASSIC_MEDIA_BUTTON_CARS: + fp_ret.modePressed = bool(cp.vl["GW_SWRC_PE"]["C_ModeSW"]) + fp_ret.customPressed = bool(cp.vl["GW_SWRC_PE"]["C_MTSSW"]) return ret, fp_ret @@ -686,6 +694,9 @@ class CarState(CarStateBase): ("BCM_PO_11", 0), ("CLU13", 0), ] + if CP.carFingerprint in CLASSIC_MEDIA_BUTTON_CARS: + # Steering-wheel media switches are event-driven on the refresh Elantra. + msgs.append(("GW_SWRC_PE", 0)) if CP.flags & HyundaiFlags.NON_SCC and not (CP.flags & HyundaiFlags.NON_SCC_NO_FCA): msgs.append(("FCA11", 0)) # Non-SCC trims can stop publishing FCA11; don't let it poison canValid diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py index 6eecf52a7..258c731bf 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_hyundai.py @@ -654,6 +654,22 @@ class TestHyundaiFingerprint: assert DBC[CP.carFingerprint][Bus.pt] == "hyundai_can_refresh_generated" assert CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.CAN_REFRESH_MSGS + def test_elantra_refresh_decodes_classic_media_buttons(self): + toggles = get_test_toggles() + CP = CarInterface.get_params(CAR.HYUNDAI_ELANTRA_HEV_2024, gen_empty_fingerprint(), [], True, False, False, toggles) + FPCP = CarInterface.get_starpilot_params(CAR.HYUNDAI_ELANTRA_HEV_2024, gen_empty_fingerprint(), [], CP, toggles) + + car_state = CarState(CP, FPCP) + can_parsers = car_state.get_can_parsers(CP) + packer = CANPacker(DBC[CP.carFingerprint][Bus.pt]) + media_msg = packer.make_can_msg("GW_SWRC_PE", 0, {"C_ModeSW": 1, "C_MTSSW": 1}) + + can_parsers[Bus.pt].update([(1_000_000_000, [media_msg])]) + _, fp_ret = car_state.update(can_parsers, toggles) + + assert fp_ret.modePressed + assert fp_ret.customPressed + def test_hyundai_lkas_button_sets_starpilot_safety_flag(self): fingerprint = gen_empty_fingerprint() fingerprint[0][0x391] = 8 diff --git a/opendbc_repo/opendbc/car/nissan/interface.py b/opendbc_repo/opendbc/car/nissan/interface.py index 1a236f3ec..147b486cd 100644 --- a/opendbc_repo/opendbc/car/nissan/interface.py +++ b/opendbc_repo/opendbc/car/nissan/interface.py @@ -17,15 +17,12 @@ LEAF_ADAS_COMMAND_ADDRS = frozenset((0x1C3, 0x2B0)) LEAF_2025_SV_PLUS_CAMERA_FW = b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80' # This Leaf camera uses KWP2000 rather than UDS for session management. -LEAF_KWP_AFTER_SALES_REQUEST = b"\x10\xC0" -LEAF_KWP_AFTER_SALES_RESPONSE = b"\x50\xC0" +LEAF_KWP_DATA_MONITOR_REQUEST = b"\x10\xF0" +LEAF_KWP_DATA_MONITOR_RESPONSE = b"\x50\xF0" LEAF_KWP_DISABLE_NORMAL_TX = b"\x28\x01" -LEAF_KWP_ENABLE_NORMAL_TX = b"\x29\x01" -LEAF_KWP_ENABLE_NORMAL_TX_RESPONSE = b"\x69" -LEAF_KWP_SESSIONS = ( - (LEAF_KWP_AFTER_SALES_REQUEST, LEAF_KWP_AFTER_SALES_RESPONSE), - (NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_DIAGNOSTIC_RESPONSE_KWP), +LEAF_KWP_TAKEOVER_SESSIONS = ( + (LEAF_KWP_DATA_MONITOR_REQUEST, LEAF_KWP_DATA_MONITOR_RESPONSE), ) @@ -68,25 +65,45 @@ def leaf_adas_commands_silent(can_recv, settle_time=0.05, observe_time=0.15): return saw_adas_bus_traffic +def leaf_adas_commands_present(can_recv, settle_time=0.05, observe_time=0.15): + """Confirm the stock ADAS command sender resumed on bus 1.""" + if can_recv is None: + return False + + try: + time.sleep(settle_time) + can_recv() + + deadline = time.monotonic() + observe_time + while time.monotonic() < deadline: + for packet in can_recv(wait_for_one=True): + if any(msg.src == LEAF_ADAS_COMMAND_BUS and msg.address in LEAF_ADAS_COMMAND_ADDRS for msg in packet): + return True + except Exception as e: + ecu_log(f"Nissan Leaf ADAS TX recovery verification exception: {e}") + return False + + ecu_log("Nissan Leaf ADAS normal TX recovery could not be verified") + return False + + def restore_leaf_adas_tx(can_recv, can_send): - """Re-enable normal KWP message transmission, trying both safe diagnostic sessions.""" + """Return to the confirmed KWP default session and verify normal TX resumes.""" if can_recv is None or can_send is None: return False - for diag_request, diag_response in LEAF_KWP_SESSIONS: - try: - ecu_log(f"Nissan Leaf ADAS TX restore using KWP session {diag_request.hex()}") - query = IsoTpParallelQuery( - can_send, can_recv, LEAF_ADAS_ECU_BUS, [LEAF_ADAS_ECU_ADDR], - [diag_request, LEAF_KWP_ENABLE_NORMAL_TX], - [diag_response, LEAF_KWP_ENABLE_NORMAL_TX_RESPONSE], - response_offset=NISSAN_RX_OFFSET, - ) - if query.get_data(0.2): - ecu_log("Nissan Leaf ADAS normal TX restored") - return True - except Exception as e: - ecu_log(f"Nissan Leaf ADAS TX restore exception: {e}") + try: + ecu_log("Nissan Leaf ADAS TX restore using KWP default session 1081") + query = IsoTpParallelQuery( + can_send, can_recv, LEAF_ADAS_ECU_BUS, [LEAF_ADAS_ECU_ADDR], + [NISSAN_DIAGNOSTIC_REQUEST_KWP], [NISSAN_DIAGNOSTIC_RESPONSE_KWP], + response_offset=NISSAN_RX_OFFSET, + ) + if query.get_data(0.2) and leaf_adas_commands_present(can_recv): + ecu_log("Nissan Leaf ADAS normal TX restored and command traffic confirmed") + return True + except Exception as e: + ecu_log(f"Nissan Leaf ADAS TX restore exception: {e}") ecu_log("Nissan Leaf ADAS normal TX restore was not confirmed") return False @@ -141,10 +158,10 @@ class CarInterface(CarInterfaceBase): from openpilot.common.params import Params params = Params() ecu_disabled = False - for diag_request, diag_response in LEAF_KWP_SESSIONS: + for diag_request, diag_response in LEAF_KWP_TAKEOVER_SESSIONS: ecu_log(f"Nissan Leaf ADAS takeover using KWP session {diag_request.hex()}") ecu_disabled = disable_ecu(can_recv, can_send, bus=LEAF_ADAS_ECU_BUS, addr=LEAF_ADAS_ECU_ADDR, - com_cont_req=LEAF_KWP_DISABLE_NORMAL_TX, require_response=True, retry=3, + com_cont_req=LEAF_KWP_DISABLE_NORMAL_TX, require_response=True, retry=1, diag_request=diag_request, diag_response=diag_response, response_offset=NISSAN_RX_OFFSET) if ecu_disabled: break diff --git a/opendbc_repo/opendbc/car/nissan/tests/test_nissan.py b/opendbc_repo/opendbc/car/nissan/tests/test_nissan.py index c8917d957..d43a23198 100644 --- a/opendbc_repo/opendbc/car/nissan/tests/test_nissan.py +++ b/opendbc_repo/opendbc/car/nissan/tests/test_nissan.py @@ -5,7 +5,8 @@ import pytest from opendbc.car import Bus, ButtonType, gen_empty_fingerprint, structs from opendbc.car.can_definitions import CanData from opendbc.car.nissan.carstate import CarState -from opendbc.car.nissan.interface import CarInterface, LEAF_2025_SV_PLUS_CAMERA_FW, leaf_adas_commands_silent, restore_leaf_adas_tx +from opendbc.car.nissan.interface import CarInterface, LEAF_2025_SV_PLUS_CAMERA_FW, leaf_adas_commands_present, \ + leaf_adas_commands_silent, restore_leaf_adas_tx from opendbc.car.nissan.values import CAR, CarControllerParams, NissanSafetyFlags @@ -143,28 +144,24 @@ def test_leaf_ecu_disable_is_strict_and_falls_back(monkeypatch, ecu_disabled): monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None) CarInterface.init(CP, None, None) - assert len(calls) == (1 if ecu_disabled else 2) + assert len(calls) == 1 assert calls[0]["addr"] == 0x707 assert calls[0]["bus"] == 0 assert calls[0]["response_offset"] == 0x20 assert calls[0]["require_response"] is True - assert calls[0]["diag_request"] == b"\x10\xc0" - assert calls[0]["diag_response"] == b"\x50\xc0" + assert calls[0]["diag_request"] == b"\x10\xf0" + assert calls[0]["diag_response"] == b"\x50\xf0" assert calls[0]["com_cont_req"] == b"\x28\x01" - assert calls[0]["retry"] == 3 - if not ecu_disabled: - assert calls[1]["diag_request"] == b"\x10\x81" - assert calls[1]["diag_response"] == b"\x50\x81" + assert calls[0]["retry"] == 1 assert CP.openpilotLongitudinalControl is ecu_disabled assert CP.pcmCruise is not ecu_disabled assert bool(CP.safetyConfigs[-1].safetyParam & NissanSafetyFlags.LONG_CONTROL) is ecu_disabled -def test_leaf_kwp_session_can_confirm_ecu_disable(monkeypatch): +def test_leaf_kwp_data_monitor_session_can_confirm_ecu_disable(monkeypatch): CP = CarInterface.get_params(CAR.NISSAN_LEAF, gen_empty_fingerprint(), SUPPORTED_LEAF_FW, True, False, False, None) - results = iter((False, True)) - monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: next(results)) + monkeypatch.setattr("opendbc.car.nissan.interface.disable_ecu", lambda *args, **kwargs: True) monkeypatch.setattr("opendbc.car.nissan.interface.leaf_adas_commands_silent", lambda *_: True) monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None) CarInterface.init(CP, None, None) @@ -205,7 +202,17 @@ def test_leaf_adas_command_silence_requires_live_bus_without_stock_commands(monk assert not leaf_adas_commands_silent(lambda wait_for_one=False: [], settle_time=0, observe_time=0.001) -def test_leaf_adas_restore_uses_kwp_enable_normal_transmission(monkeypatch): +def test_leaf_adas_command_recovery_requires_stock_command(monkeypatch): + monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None) + + def stock_command_traffic(wait_for_one=False): + return [] if not wait_for_one else [[CanData(0x1C3, b"\x00" * 8, 1)]] + + assert leaf_adas_commands_present(stock_command_traffic, settle_time=0, observe_time=0.001) + assert not leaf_adas_commands_present(lambda wait_for_one=False: [], settle_time=0, observe_time=0.001) + + +def test_leaf_adas_restore_returns_to_kwp_default_session(monkeypatch): queries = [] class FakeQuery: @@ -216,7 +223,8 @@ def test_leaf_adas_restore_uses_kwp_enable_normal_transmission(monkeypatch): return {(0x707, None): b""} monkeypatch.setattr("opendbc.car.nissan.interface.IsoTpParallelQuery", FakeQuery) + monkeypatch.setattr("opendbc.car.nissan.interface.leaf_adas_commands_present", lambda *_: True) monkeypatch.setattr("opendbc.car.nissan.interface.ecu_log", lambda *_: None) assert restore_leaf_adas_tx(lambda **kwargs: [], lambda msgs: None) - assert queries == [(0, [0x707], [b"\x10\xc0", b"\x29\x01"], [b"\x50\xc0", b"\x69"], 0x20)] + assert queries == [(0, [0x707], [b"\x10\x81"], [b"\x50\x81"], 0x20)] diff --git a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py index 7d40cdbd1..52ede8fb4 100644 --- a/selfdrive/controls/lib/latcontrol_vehicle_tunes.py +++ b/selfdrive/controls/lib/latcontrol_vehicle_tunes.py @@ -709,7 +709,7 @@ IONIQ_5_LOW_SPEED_CENTER_LAT = 0.40 IONIQ_5_LOW_SPEED_CENTER_LAT_WIDTH = 0.10 IONIQ_5_LOW_SPEED_CENTER_JERK = 0.40 IONIQ_5_LOW_SPEED_CENTER_JERK_WIDTH = 0.12 -IONIQ_5_FRICTION_JERK_DEADZONE_MAX = 0.30 +IONIQ_5_FRICTION_JERK_DEADZONE_MAX = 0.36 IONIQ_5_FRICTION_JERK_DEADZONE_LAT = 1.25 IONIQ_5_FRICTION_JERK_DEADZONE_LAT_WIDTH = 0.35 IONIQ_5_FRICTION_JERK_DEADZONE_SPEED = 18.0 diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 2c7848e2f..6f5188e73 100644 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -56,7 +56,7 @@ MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy StarPilotEventName = custom.StarPilotOnroadEvent.EventName IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) -VALID_ONLY_COMM_ISSUE_GRACE_FRAMES = max(1, round(0.25 / DT_CTRL)) +VALID_ONLY_COMM_ISSUE_GRACE_FRAMES = max(1, round(0.5 / DT_CTRL)) def evaluate_comm_issue(all_checks: bool, all_alive: bool, all_freq_ok: bool, @@ -515,7 +515,7 @@ class SelfdriveD: self.events.add(EventName.pedalPressed) # Create events for temperature, disk space, and memory - if self.sm['deviceState'].thermalStatus >= ThermalStatus.red: + if self.sm['deviceState'].thermalStatus >= ThermalStatus.overheated: self.events.add(EventName.overheat) if self.sm['deviceState'].freeSpacePercent < 7 and not SIMULATION: self.events.add(EventName.outOfSpace) diff --git a/selfdrive/selfdrived/tests/test_selfdrived.py b/selfdrive/selfdrived/tests/test_selfdrived.py index dcde96bc4..f0c2ba5e9 100644 --- a/selfdrive/selfdrived/tests/test_selfdrived.py +++ b/selfdrive/selfdrived/tests/test_selfdrived.py @@ -3,6 +3,7 @@ import cereal.messaging as messaging from cereal import car, custom, log from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR from opendbc.car.nissan.values import CAR as NISSAN_CAR +from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.selfdrived.selfdrived import ( VALID_ONLY_COMM_ISSUE_GRACE_FRAMES, @@ -27,6 +28,13 @@ def test_valid_only_comm_issue_is_debounced(): assert frames == 0 +def test_route_length_validity_cascade_stays_silent(): + frames = 0 + for _ in range(round(0.4 / DT_CTRL)): + should_alert, frames = evaluate_comm_issue(False, True, True, frames) + assert not should_alert + + def test_dead_or_slow_comm_issue_is_immediate(): assert evaluate_comm_issue(False, False, True, 0) == (True, 0) assert evaluate_comm_issue(False, True, False, 0) == (True, 0) diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 23ef92981..2a36e191a 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -122,9 +122,9 @@ class Sidebar(Widget): thermal_status = device_state.thermalStatus temperature = f"{int(device_state.maxTempC)}°C" - if thermal_status == ThermalStatus.green: + if thermal_status == ThermalStatus.ok: self._temp_status.update(tr_noop("TEMP"), temperature, Colors.GOOD) - elif thermal_status == ThermalStatus.yellow: + elif thermal_status == ThermalStatus.warmDEPRECATED: self._temp_status.update(tr_noop("TEMP"), temperature, Colors.WARNING) else: self._temp_status.update(tr_noop("TEMP"), temperature, Colors.DANGER) diff --git a/system/hardware/base.py b/system/hardware/base.py index 17d0ec161..d0a4833cc 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -51,7 +51,8 @@ class ThermalConfig: memory: ThermalZone | None = None intake: ThermalZone | None = None exhaust: ThermalZone | None = None - case: ThermalZone | None = None + gnss: ThermalZone | None = None + bottomSoc: ThermalZone | None = None def get_msg(self): ret = {} diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 9ac43e9f1..ec7c52a59 100644 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -133,15 +133,21 @@ HardwareState = namedtuple("HardwareState", ['network_type', 'network_info', 'ne # List of thermal bands. We will stay within this region as long as we are within the bounds. # When exiting the bounds, we'll jump to the lower or higher band. Bands are ordered in the dict. -THERMAL_BANDS = OrderedDict({ - ThermalStatus.green: ThermalBand(None, 80.0), - ThermalStatus.yellow: ThermalBand(75.0, 96.0), - ThermalStatus.red: ThermalBand(88.0, 107.), - ThermalStatus.danger: ThermalBand(94.0, None), -}) +if HARDWARE.get_device_type() == "mici": + THERMAL_BANDS = OrderedDict({ + ThermalStatus.ok: ThermalBand(None, 100.0), + ThermalStatus.overheated: ThermalBand(92.0, 107.), + ThermalStatus.critical: ThermalBand(98.0, None), + }) +else: + THERMAL_BANDS = OrderedDict({ + ThermalStatus.ok: ThermalBand(None, 96.0), + ThermalStatus.overheated: ThermalBand(88.0, 107.), + ThermalStatus.critical: ThermalBand(94.0, None), + }) # Override to highest thermal band when offroad and above this temp -OFFROAD_DANGER_TEMP = 75 +OFFROAD_DANGER_TEMP = 85 if HARDWARE.get_device_type() == "mici" else 75 prev_offroad_states: dict[str, tuple[bool, str | None]] = {} @@ -279,7 +285,7 @@ def hardware_thread(end_event, hw_queue) -> None: started_ts: float | None = None started_seen = False startup_blocked_ts: float | None = None - thermal_status = ThermalStatus.yellow + thermal_status = ThermalStatus.ok last_hw_state = HardwareState( network_type=NetworkType.none, @@ -405,13 +411,13 @@ def hardware_thread(end_event, hw_queue) -> None: # StarPilot variables if starpilot_toggles.increase_thermal_limits: - all_comp_temp -= (THERMAL_BANDS[ThermalStatus.danger].min_temp - THERMAL_BANDS[ThermalStatus.red].min_temp) + all_comp_temp -= (THERMAL_BANDS[ThermalStatus.critical].min_temp - THERMAL_BANDS[ThermalStatus.overheated].min_temp) is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5)) if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: # if device is offroad and already hot without the extra onroad load, # we want to cool down first before increasing load - thermal_status = ThermalStatus.danger + thermal_status = ThermalStatus.critical else: current_band = THERMAL_BANDS[thermal_status] band_idx = list(THERMAL_BANDS.keys()).index(thermal_status) @@ -434,17 +440,20 @@ def hardware_thread(end_event, hw_queue) -> None: startup_conditions["not_taking_snapshot"] = not params.get_bool("IsTakingSnapshot") # must be at an engageable thermal band to go onroad - startup_conditions["device_temp_engageable"] = thermal_status < ThermalStatus.red + startup_conditions["device_temp_engageable"] = thermal_status < ThermalStatus.overheated # ensure device is fully booted startup_conditions["device_booted"] = startup_conditions.get("device_booted", False) or HARDWARE.booted() # if the temperature enters the danger zone, go offroad to cool down - onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger + onroad_conditions["device_temp_good"] = thermal_status < ThermalStatus.critical extra_text = f"{offroad_comp_temp:.1f}C" show_alert = (not onroad_conditions["device_temp_good"] or not startup_conditions["device_temp_engageable"]) and onroad_conditions["ignition"] set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", show_alert, extra_text=extra_text) + if show_alert: + msg.deviceState.fanSpeedPercentDesired = 100 + # *** registration check *** if not PC: # we enforce this for our software, but you are welcome @@ -563,9 +572,10 @@ def hardware_thread(end_event, hw_queue) -> None: statlog.gauge("fan_speed_percent_desired", msg.deviceState.fanSpeedPercentDesired) statlog.gauge("screen_brightness_percent", msg.deviceState.screenBrightnessPercent) - # report to server once every 10 minutes + # report to server once every 10 minutes, or every 1s when thermally blocked rising_edge_started = should_start and not should_start_prev - if rising_edge_started or (count % int(600. / DT_HW)) == 0: + status_packet_interval = 1. if show_alert else 600. + if rising_edge_started or (count % int(status_packet_interval / DT_HW)) == 0: dat = { 'count': count, 'pandaStates': [strip_deprecated_keys(p.to_dict()) for p in pandaStates], diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 8f3810e1f..eacc69b0b 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -321,11 +321,12 @@ class Tici(HardwareBase): os.system("sudo poweroff") def get_thermal_config(self): - intake, exhaust, case = None, None, None + intake, exhaust, gnss, bottomSoc = None, None, None, None if self.get_device_type() == "mici": - case = ThermalZone("case") + gnss = ThermalZone("gnss") intake = ThermalZone("intake") exhaust = ThermalZone("exhaust") + bottomSoc = ThermalZone("bottom_soc") return ThermalConfig(cpu=[ThermalZone(f"cpu{i}-silver-usr") for i in range(4)] + [ThermalZone(f"cpu{i}-gold-usr") for i in range(4)], gpu=[ThermalZone("gpu0-usr"), ThermalZone("gpu1-usr")], @@ -334,7 +335,8 @@ class Tici(HardwareBase): pmic=[ThermalZone("pm8998_tz"), ThermalZone("pm8005_tz")], intake=intake, exhaust=exhaust, - case=case) + gnss=gnss, + bottomSoc=bottomSoc) def set_display_power(self, on): try: @@ -383,6 +385,9 @@ class Tici(HardwareBase): continue gov = 'ondemand' if powersave_enabled else 'performance' sudo_write(gov, f'/sys/devices/system/cpu/cpufreq/policy{n}/scaling_governor') + if not powersave_enabled: + # cap max core freq to 1689 Mhz + sudo_write('1689600', f'/sys/devices/system/cpu/cpufreq/policy{n}/scaling_max_freq') # *** IRQ config ***