diff --git a/opendbc_repo/opendbc/car/honda/tests/test_stock_aol.py b/opendbc_repo/opendbc/car/honda/tests/test_stock_aol.py index 2e90f8e55d..b868c1c720 100644 --- a/opendbc_repo/opendbc/car/honda/tests/test_stock_aol.py +++ b/opendbc_repo/opendbc/car/honda/tests/test_stock_aol.py @@ -335,8 +335,8 @@ def test_actual_card_active_observation_requires_fresh_selfdrive_state(monkeypat host.observe_ioniq6_long_authority = lambda *args: None host.update_vehicle_state_context = lambda *args: None - def update_cruise_speed(_state, _pending, *, approved=None): - assert approved is None + def update_cruise_speed(_state, _pending, *, wheel_context=None): + assert wheel_context is None host.update_cruise_speed = update_cruise_speed host.v_cruise_helper = SimpleNamespace(slc_consumed_button=None, toyota=None) host.startup_panda_configured = lambda: True diff --git a/opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_handoff.py b/opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_handoff.py index 5b3b17bb0b..e938997241 100644 --- a/opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_handoff.py +++ b/opendbc_repo/opendbc/car/hyundai/tests/test_ioniq6_handoff.py @@ -870,7 +870,7 @@ class TestIoniq6PrepublicationHandoff(unittest.TestCase): card_owner.is_metric = True card_owner.experimental_mode = False card_owner.initial_cruise_speed = 40 - card_owner.v_cruise_helper = SimpleNamespace(update_v_cruise=lambda *_, slc_enabled=False, slc_approved=None: None, + card_owner.v_cruise_helper = SimpleNamespace(update_v_cruise=lambda *_, slc_enabled=False, wheel_context=None: None, initialize_v_cruise=lambda *_, **kwargs: None, slc_consumed_button=None, toyota=None, slc_cruise_change=None, v_cruise_kph=60.0, v_cruise_cluster_kph=60.0) card_owner.RI = SimpleNamespace(update=lambda _: None) diff --git a/openpilot/cereal/custom.capnp b/openpilot/cereal/custom.capnp index 23dcb87ddb..bfd2c41454 100644 --- a/openpilot/cereal/custom.capnp +++ b/openpilot/cereal/custom.capnp @@ -333,7 +333,7 @@ struct SlcState @0xa1680744031fdb2d { hasPendingAdjustedLimit @45 :Bool; presentationAdjustedLimit @46 :Float32; hasPresentationAdjustedLimit @47 :Bool; - overrideBasis @48 :Text; # none/pedal/persistent; qualified contribution only + overrideBasis @48 :Text; # none/pedal/persistent/wheel; qualified contribution only retainedOverride @49 :Bool; commandActionSequenceId @50 :UInt64; # UI sequence that issued this command automaticAdoptionPending @51 :Bool; @@ -347,6 +347,7 @@ struct SlcState @0xa1680744031fdb2d { upcomingSpeedLimit @57 :Float32; upcomingBoundaryId @58 :Text; acceptedIsPreview @59 :Bool; # a temporary ceiling, ineligible for initializing SET + wheelOverrideSince @60 :UInt64; # nonzero while a wheel tap may cross the current adjusted limit struct SourceReading { source @0 :Text; enabled @1 :Bool; @@ -515,6 +516,7 @@ struct SlcCruiseEvent @0xcd96dafb67a082d0 { trafficMode @13 :TrafficModeGesture; wheelAction @14 :SlcAction.ControllerCruiseAction; teslaStockCruise @15 :TeslaStockCruise; + wheelOverrideSince @16 :UInt64; # SLC eligibility captured by a physical + tap; zero for ordinary SET changes struct TeslaStockCruise { version @0 :UInt16; diff --git a/openpilot/cereal/gen/cpp/custom.capnp.c++ b/openpilot/cereal/gen/cpp/custom.capnp.c++ index 960755722b..fb10327229 100644 --- a/openpilot/cereal/gen/cpp/custom.capnp.c++ +++ b/openpilot/cereal/gen/cpp/custom.capnp.c++ @@ -3741,17 +3741,17 @@ const ::capnp::_::RawSchema s_da96579883444c35 = { 0, 5, i_da96579883444c35, nullptr, nullptr, { &s_da96579883444c35, nullptr, nullptr, 0, 0, nullptr }, false }; #endif // !CAPNP_LITE -static const ::capnp::_::AlignedData<1030> b_a1680744031fdb2d = { +static const ::capnp::_::AlignedData<1047> b_a1680744031fdb2d = { { 0, 0, 0, 0, 5, 0, 6, 0, 45, 219, 31, 3, 68, 7, 104, 161, - 13, 0, 0, 0, 1, 0, 18, 0, + 13, 0, 0, 0, 1, 0, 19, 0, 89, 10, 85, 29, 102, 186, 38, 181, 22, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 178, 0, 0, 0, 29, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 117, 0, 0, 0, 39, 13, 0, 0, + 117, 0, 0, 0, 95, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 117, 115, 116, 111, 109, 46, 99, @@ -3781,427 +3781,434 @@ static const ::capnp::_::AlignedData<1030> b_a1680744031fdb2d = { 67, 111, 110, 100, 105, 116, 105, 111, 110, 97, 108, 77, 111, 100, 101, 83, 116, 97, 116, 101, 0, 0, 0, 0, - 240, 0, 0, 0, 3, 0, 4, 0, + 244, 0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 129, 6, 0, 0, 58, 0, 0, 0, + 157, 6, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 124, 6, 0, 0, 3, 0, 1, 0, - 136, 6, 0, 0, 2, 0, 1, 0, + 152, 6, 0, 0, 3, 0, 1, 0, + 164, 6, 0, 0, 2, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 133, 6, 0, 0, 74, 0, 0, 0, + 161, 6, 0, 0, 74, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 132, 6, 0, 0, 3, 0, 1, 0, - 144, 6, 0, 0, 2, 0, 1, 0, + 160, 6, 0, 0, 3, 0, 1, 0, + 172, 6, 0, 0, 2, 0, 1, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 141, 6, 0, 0, 66, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 136, 6, 0, 0, 3, 0, 1, 0, - 148, 6, 0, 0, 2, 0, 1, 0, - 3, 0, 0, 0, 3, 0, 0, 0, - 0, 0, 1, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 145, 6, 0, 0, 106, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 144, 6, 0, 0, 3, 0, 1, 0, - 156, 6, 0, 0, 2, 0, 1, 0, - 4, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 1, 0, 4, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 153, 6, 0, 0, 58, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 148, 6, 0, 0, 3, 0, 1, 0, - 160, 6, 0, 0, 2, 0, 1, 0, - 5, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 5, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 157, 6, 0, 0, 114, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 156, 6, 0, 0, 3, 0, 1, 0, - 168, 6, 0, 0, 2, 0, 1, 0, - 6, 0, 0, 0, 5, 0, 0, 0, - 0, 0, 1, 0, 6, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 165, 6, 0, 0, 82, 0, 0, 0, + 169, 6, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164, 6, 0, 0, 3, 0, 1, 0, 176, 6, 0, 0, 2, 0, 1, 0, - 7, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 1, 0, 7, 0, 0, 0, + 3, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 173, 6, 0, 0, 114, 0, 0, 0, + 173, 6, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 6, 0, 0, 3, 0, 1, 0, 184, 6, 0, 0, 2, 0, 1, 0, - 8, 0, 0, 0, 128, 0, 0, 0, - 0, 0, 1, 0, 8, 0, 0, 0, + 4, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 181, 6, 0, 0, 66, 0, 0, 0, + 181, 6, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 6, 0, 0, 3, 0, 1, 0, 188, 6, 0, 0, 2, 0, 1, 0, - 9, 0, 0, 0, 129, 0, 0, 0, - 0, 0, 1, 0, 9, 0, 0, 0, + 5, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 185, 6, 0, 0, 98, 0, 0, 0, + 185, 6, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 6, 0, 0, 3, 0, 1, 0, 196, 6, 0, 0, 2, 0, 1, 0, - 10, 0, 0, 0, 6, 0, 0, 0, - 0, 0, 1, 0, 10, 0, 0, 0, + 6, 0, 0, 0, 5, 0, 0, 0, + 0, 0, 1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 193, 6, 0, 0, 130, 0, 0, 0, + 193, 6, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 6, 0, 0, 3, 0, 1, 0, 204, 6, 0, 0, 2, 0, 1, 0, - 11, 0, 0, 0, 7, 0, 0, 0, - 0, 0, 1, 0, 11, 0, 0, 0, + 7, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 201, 6, 0, 0, 58, 0, 0, 0, + 201, 6, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 196, 6, 0, 0, 3, 0, 1, 0, - 208, 6, 0, 0, 2, 0, 1, 0, - 12, 0, 0, 0, 5, 0, 0, 0, - 0, 0, 1, 0, 12, 0, 0, 0, + 200, 6, 0, 0, 3, 0, 1, 0, + 212, 6, 0, 0, 2, 0, 1, 0, + 8, 0, 0, 0, 128, 0, 0, 0, + 0, 0, 1, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 205, 6, 0, 0, 90, 0, 0, 0, + 209, 6, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 6, 0, 0, 3, 0, 1, 0, 216, 6, 0, 0, 2, 0, 1, 0, - 13, 0, 0, 0, 6, 0, 0, 0, - 0, 0, 1, 0, 13, 0, 0, 0, + 9, 0, 0, 0, 129, 0, 0, 0, + 0, 0, 1, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 213, 6, 0, 0, 154, 0, 0, 0, + 213, 6, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 216, 6, 0, 0, 3, 0, 1, 0, - 228, 6, 0, 0, 2, 0, 1, 0, - 14, 0, 0, 0, 130, 0, 0, 0, - 0, 0, 1, 0, 14, 0, 0, 0, + 212, 6, 0, 0, 3, 0, 1, 0, + 224, 6, 0, 0, 2, 0, 1, 0, + 10, 0, 0, 0, 6, 0, 0, 0, + 0, 0, 1, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 225, 6, 0, 0, 98, 0, 0, 0, + 221, 6, 0, 0, 130, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 220, 6, 0, 0, 3, 0, 1, 0, + 232, 6, 0, 0, 2, 0, 1, 0, + 11, 0, 0, 0, 7, 0, 0, 0, + 0, 0, 1, 0, 11, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 229, 6, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 6, 0, 0, 3, 0, 1, 0, 236, 6, 0, 0, 2, 0, 1, 0, - 15, 0, 0, 0, 7, 0, 0, 0, - 0, 0, 1, 0, 15, 0, 0, 0, + 12, 0, 0, 0, 5, 0, 0, 0, + 0, 0, 1, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 233, 6, 0, 0, 58, 0, 0, 0, + 233, 6, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 228, 6, 0, 0, 3, 0, 1, 0, - 240, 6, 0, 0, 2, 0, 1, 0, - 16, 0, 0, 0, 8, 0, 0, 0, - 0, 0, 1, 0, 16, 0, 0, 0, + 232, 6, 0, 0, 3, 0, 1, 0, + 244, 6, 0, 0, 2, 0, 1, 0, + 13, 0, 0, 0, 6, 0, 0, 0, + 0, 0, 1, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 237, 6, 0, 0, 106, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 236, 6, 0, 0, 3, 0, 1, 0, - 248, 6, 0, 0, 2, 0, 1, 0, - 17, 0, 0, 0, 131, 0, 0, 0, - 0, 0, 1, 0, 17, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 245, 6, 0, 0, 90, 0, 0, 0, + 241, 6, 0, 0, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 244, 6, 0, 0, 3, 0, 1, 0, 0, 7, 0, 0, 2, 0, 1, 0, - 18, 0, 0, 0, 9, 0, 0, 0, - 0, 0, 1, 0, 18, 0, 0, 0, + 14, 0, 0, 0, 130, 0, 0, 0, + 0, 0, 1, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 253, 6, 0, 0, 146, 0, 0, 0, + 253, 6, 0, 0, 98, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 252, 6, 0, 0, 3, 0, 1, 0, + 8, 7, 0, 0, 2, 0, 1, 0, + 15, 0, 0, 0, 7, 0, 0, 0, + 0, 0, 1, 0, 15, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 7, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 3, 0, 1, 0, 12, 7, 0, 0, 2, 0, 1, 0, - 19, 0, 0, 0, 132, 0, 0, 0, - 0, 0, 1, 0, 19, 0, 0, 0, + 16, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 1, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 9, 7, 0, 0, 90, 0, 0, 0, + 9, 7, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 0, 0, 3, 0, 1, 0, 20, 7, 0, 0, 2, 0, 1, 0, - 20, 0, 0, 0, 5, 0, 0, 0, - 0, 0, 1, 0, 20, 0, 0, 0, + 17, 0, 0, 0, 131, 0, 0, 0, + 0, 0, 1, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 7, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 7, 0, 0, 3, 0, 1, 0, 28, 7, 0, 0, 2, 0, 1, 0, - 21, 0, 0, 0, 6, 0, 0, 0, - 0, 0, 1, 0, 21, 0, 0, 0, + 18, 0, 0, 0, 9, 0, 0, 0, + 0, 0, 1, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 25, 7, 0, 0, 122, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 24, 7, 0, 0, 3, 0, 1, 0, - 36, 7, 0, 0, 2, 0, 1, 0, - 22, 0, 0, 0, 8, 0, 0, 0, - 0, 0, 1, 0, 22, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 33, 7, 0, 0, 58, 0, 0, 0, + 25, 7, 0, 0, 146, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 7, 0, 0, 3, 0, 1, 0, 40, 7, 0, 0, 2, 0, 1, 0, + 19, 0, 0, 0, 132, 0, 0, 0, + 0, 0, 1, 0, 19, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 37, 7, 0, 0, 90, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 36, 7, 0, 0, 3, 0, 1, 0, + 48, 7, 0, 0, 2, 0, 1, 0, + 20, 0, 0, 0, 5, 0, 0, 0, + 0, 0, 1, 0, 20, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 45, 7, 0, 0, 90, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 44, 7, 0, 0, 3, 0, 1, 0, + 56, 7, 0, 0, 2, 0, 1, 0, + 21, 0, 0, 0, 6, 0, 0, 0, + 0, 0, 1, 0, 21, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 53, 7, 0, 0, 122, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 52, 7, 0, 0, 3, 0, 1, 0, + 64, 7, 0, 0, 2, 0, 1, 0, + 22, 0, 0, 0, 8, 0, 0, 0, + 0, 0, 1, 0, 22, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 61, 7, 0, 0, 58, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 56, 7, 0, 0, 3, 0, 1, 0, + 68, 7, 0, 0, 2, 0, 1, 0, 23, 0, 0, 0, 7, 0, 0, 0, 0, 0, 1, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 37, 7, 0, 0, 138, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 40, 7, 0, 0, 3, 0, 1, 0, - 52, 7, 0, 0, 2, 0, 1, 0, - 24, 0, 0, 0, 9, 0, 0, 0, - 0, 0, 1, 0, 24, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 49, 7, 0, 0, 106, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 48, 7, 0, 0, 3, 0, 1, 0, - 60, 7, 0, 0, 2, 0, 1, 0, - 25, 0, 0, 0, 10, 0, 0, 0, - 0, 0, 1, 0, 25, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 57, 7, 0, 0, 194, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 60, 7, 0, 0, 3, 0, 1, 0, - 72, 7, 0, 0, 2, 0, 1, 0, - 26, 0, 0, 0, 16, 0, 0, 0, - 0, 0, 1, 0, 26, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 69, 7, 0, 0, 114, 0, 0, 0, + 65, 7, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 7, 0, 0, 3, 0, 1, 0, 80, 7, 0, 0, 2, 0, 1, 0, - 27, 0, 0, 0, 9, 0, 0, 0, - 0, 0, 1, 0, 27, 0, 0, 0, + 24, 0, 0, 0, 9, 0, 0, 0, + 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 77, 7, 0, 0, 186, 0, 0, 0, + 77, 7, 0, 0, 106, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 80, 7, 0, 0, 3, 0, 1, 0, - 92, 7, 0, 0, 2, 0, 1, 0, - 28, 0, 0, 0, 10, 0, 0, 0, - 0, 0, 1, 0, 28, 0, 0, 0, + 76, 7, 0, 0, 3, 0, 1, 0, + 88, 7, 0, 0, 2, 0, 1, 0, + 25, 0, 0, 0, 10, 0, 0, 0, + 0, 0, 1, 0, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 89, 7, 0, 0, 202, 0, 0, 0, + 85, 7, 0, 0, 194, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 88, 7, 0, 0, 3, 0, 1, 0, + 100, 7, 0, 0, 2, 0, 1, 0, + 26, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 1, 0, 26, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 97, 7, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 7, 0, 0, 3, 0, 1, 0, 108, 7, 0, 0, 2, 0, 1, 0, - 29, 0, 0, 0, 11, 0, 0, 0, - 0, 0, 1, 0, 29, 0, 0, 0, + 27, 0, 0, 0, 9, 0, 0, 0, + 0, 0, 1, 0, 27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 105, 7, 0, 0, 82, 0, 0, 0, + 105, 7, 0, 0, 186, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 104, 7, 0, 0, 3, 0, 1, 0, - 116, 7, 0, 0, 2, 0, 1, 0, - 30, 0, 0, 0, 11, 0, 0, 0, - 0, 0, 1, 0, 30, 0, 0, 0, + 108, 7, 0, 0, 3, 0, 1, 0, + 120, 7, 0, 0, 2, 0, 1, 0, + 28, 0, 0, 0, 10, 0, 0, 0, + 0, 0, 1, 0, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 113, 7, 0, 0, 114, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 112, 7, 0, 0, 3, 0, 1, 0, - 124, 7, 0, 0, 2, 0, 1, 0, - 31, 0, 0, 0, 12, 0, 0, 0, - 0, 0, 1, 0, 31, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 121, 7, 0, 0, 50, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 116, 7, 0, 0, 3, 0, 1, 0, - 128, 7, 0, 0, 2, 0, 1, 0, - 32, 0, 0, 0, 13, 0, 0, 0, - 0, 0, 1, 0, 32, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 125, 7, 0, 0, 130, 0, 0, 0, + 117, 7, 0, 0, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 124, 7, 0, 0, 3, 0, 1, 0, 136, 7, 0, 0, 2, 0, 1, 0, - 33, 0, 0, 0, 14, 0, 0, 0, - 0, 0, 1, 0, 33, 0, 0, 0, + 29, 0, 0, 0, 11, 0, 0, 0, + 0, 0, 1, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 133, 7, 0, 0, 98, 0, 0, 0, + 133, 7, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 132, 7, 0, 0, 3, 0, 1, 0, 144, 7, 0, 0, 2, 0, 1, 0, - 34, 0, 0, 0, 15, 0, 0, 0, - 0, 0, 1, 0, 34, 0, 0, 0, + 30, 0, 0, 0, 11, 0, 0, 0, + 0, 0, 1, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 141, 7, 0, 0, 122, 0, 0, 0, + 141, 7, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 7, 0, 0, 3, 0, 1, 0, 152, 7, 0, 0, 2, 0, 1, 0, - 35, 0, 0, 0, 16, 0, 0, 0, - 0, 0, 1, 0, 35, 0, 0, 0, + 31, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 1, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 149, 7, 0, 0, 122, 0, 0, 0, + 149, 7, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 148, 7, 0, 0, 3, 0, 1, 0, - 160, 7, 0, 0, 2, 0, 1, 0, - 36, 0, 0, 0, 17, 0, 0, 0, - 0, 0, 1, 0, 36, 0, 0, 0, + 144, 7, 0, 0, 3, 0, 1, 0, + 156, 7, 0, 0, 2, 0, 1, 0, + 32, 0, 0, 0, 13, 0, 0, 0, + 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 157, 7, 0, 0, 114, 0, 0, 0, + 153, 7, 0, 0, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 156, 7, 0, 0, 3, 0, 1, 0, - 168, 7, 0, 0, 2, 0, 1, 0, - 37, 0, 0, 0, 17, 0, 0, 0, - 0, 0, 1, 0, 37, 0, 0, 0, + 152, 7, 0, 0, 3, 0, 1, 0, + 164, 7, 0, 0, 2, 0, 1, 0, + 33, 0, 0, 0, 14, 0, 0, 0, + 0, 0, 1, 0, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 165, 7, 0, 0, 186, 0, 0, 0, + 161, 7, 0, 0, 98, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 160, 7, 0, 0, 3, 0, 1, 0, + 172, 7, 0, 0, 2, 0, 1, 0, + 34, 0, 0, 0, 15, 0, 0, 0, + 0, 0, 1, 0, 34, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 169, 7, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 168, 7, 0, 0, 3, 0, 1, 0, 180, 7, 0, 0, 2, 0, 1, 0, - 38, 0, 0, 0, 133, 0, 0, 0, - 0, 0, 1, 0, 38, 0, 0, 0, + 35, 0, 0, 0, 16, 0, 0, 0, + 0, 0, 1, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 177, 7, 0, 0, 210, 0, 0, 0, + 177, 7, 0, 0, 122, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 176, 7, 0, 0, 3, 0, 1, 0, + 188, 7, 0, 0, 2, 0, 1, 0, + 36, 0, 0, 0, 17, 0, 0, 0, + 0, 0, 1, 0, 36, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 185, 7, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 184, 7, 0, 0, 3, 0, 1, 0, 196, 7, 0, 0, 2, 0, 1, 0, - 39, 0, 0, 0, 134, 0, 0, 0, - 0, 0, 1, 0, 39, 0, 0, 0, + 37, 0, 0, 0, 17, 0, 0, 0, + 0, 0, 1, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 193, 7, 0, 0, 138, 0, 0, 0, + 193, 7, 0, 0, 186, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 196, 7, 0, 0, 3, 0, 1, 0, 208, 7, 0, 0, 2, 0, 1, 0, + 38, 0, 0, 0, 133, 0, 0, 0, + 0, 0, 1, 0, 38, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 205, 7, 0, 0, 210, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 212, 7, 0, 0, 3, 0, 1, 0, + 224, 7, 0, 0, 2, 0, 1, 0, + 39, 0, 0, 0, 134, 0, 0, 0, + 0, 0, 1, 0, 39, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 221, 7, 0, 0, 138, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 224, 7, 0, 0, 3, 0, 1, 0, + 236, 7, 0, 0, 2, 0, 1, 0, 40, 0, 0, 0, 135, 0, 0, 0, 0, 0, 1, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 205, 7, 0, 0, 170, 0, 0, 0, + 233, 7, 0, 0, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 208, 7, 0, 0, 3, 0, 1, 0, - 220, 7, 0, 0, 2, 0, 1, 0, + 236, 7, 0, 0, 3, 0, 1, 0, + 248, 7, 0, 0, 2, 0, 1, 0, 41, 0, 0, 0, 18, 0, 0, 0, 0, 0, 1, 0, 41, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 217, 7, 0, 0, 122, 0, 0, 0, + 245, 7, 0, 0, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 216, 7, 0, 0, 3, 0, 1, 0, - 244, 7, 0, 0, 2, 0, 1, 0, + 244, 7, 0, 0, 3, 0, 1, 0, + 16, 8, 0, 0, 2, 0, 1, 0, 42, 0, 0, 0, 24, 0, 0, 0, 0, 0, 1, 0, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 241, 7, 0, 0, 178, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 244, 7, 0, 0, 3, 0, 1, 0, - 0, 8, 0, 0, 2, 0, 1, 0, - 43, 0, 0, 0, 136, 0, 0, 0, - 0, 0, 1, 0, 43, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 253, 7, 0, 0, 202, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 4, 8, 0, 0, 3, 0, 1, 0, - 16, 8, 0, 0, 2, 0, 1, 0, - 44, 0, 0, 0, 25, 0, 0, 0, - 0, 0, 1, 0, 44, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 13, 8, 0, 0, 170, 0, 0, 0, + 13, 8, 0, 0, 178, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 8, 0, 0, 3, 0, 1, 0, 28, 8, 0, 0, 2, 0, 1, 0, - 45, 0, 0, 0, 137, 0, 0, 0, - 0, 0, 1, 0, 45, 0, 0, 0, + 43, 0, 0, 0, 136, 0, 0, 0, + 0, 0, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 25, 8, 0, 0, 194, 0, 0, 0, + 25, 8, 0, 0, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 28, 8, 0, 0, 3, 0, 1, 0, - 40, 8, 0, 0, 2, 0, 1, 0, - 46, 0, 0, 0, 26, 0, 0, 0, - 0, 0, 1, 0, 46, 0, 0, 0, + 32, 8, 0, 0, 3, 0, 1, 0, + 44, 8, 0, 0, 2, 0, 1, 0, + 44, 0, 0, 0, 25, 0, 0, 0, + 0, 0, 1, 0, 44, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 37, 8, 0, 0, 210, 0, 0, 0, + 41, 8, 0, 0, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 8, 0, 0, 3, 0, 1, 0, 56, 8, 0, 0, 2, 0, 1, 0, + 45, 0, 0, 0, 137, 0, 0, 0, + 0, 0, 1, 0, 45, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 53, 8, 0, 0, 194, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 56, 8, 0, 0, 3, 0, 1, 0, + 68, 8, 0, 0, 2, 0, 1, 0, + 46, 0, 0, 0, 26, 0, 0, 0, + 0, 0, 1, 0, 46, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 65, 8, 0, 0, 210, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 72, 8, 0, 0, 3, 0, 1, 0, + 84, 8, 0, 0, 2, 0, 1, 0, 47, 0, 0, 0, 138, 0, 0, 0, 0, 0, 1, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 53, 8, 0, 0, 234, 0, 0, 0, + 81, 8, 0, 0, 234, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 60, 8, 0, 0, 3, 0, 1, 0, - 72, 8, 0, 0, 2, 0, 1, 0, + 88, 8, 0, 0, 3, 0, 1, 0, + 100, 8, 0, 0, 2, 0, 1, 0, 48, 0, 0, 0, 19, 0, 0, 0, 0, 0, 1, 0, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 69, 8, 0, 0, 114, 0, 0, 0, + 97, 8, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 68, 8, 0, 0, 3, 0, 1, 0, - 80, 8, 0, 0, 2, 0, 1, 0, + 96, 8, 0, 0, 3, 0, 1, 0, + 108, 8, 0, 0, 2, 0, 1, 0, 49, 0, 0, 0, 139, 0, 0, 0, 0, 0, 1, 0, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 77, 8, 0, 0, 138, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 80, 8, 0, 0, 3, 0, 1, 0, - 92, 8, 0, 0, 2, 0, 1, 0, - 50, 0, 0, 0, 14, 0, 0, 0, - 0, 0, 1, 0, 50, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 89, 8, 0, 0, 194, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 92, 8, 0, 0, 3, 0, 1, 0, - 104, 8, 0, 0, 2, 0, 1, 0, - 51, 0, 0, 0, 140, 0, 0, 0, - 0, 0, 1, 0, 51, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 101, 8, 0, 0, 202, 0, 0, 0, + 105, 8, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 108, 8, 0, 0, 3, 0, 1, 0, 120, 8, 0, 0, 2, 0, 1, 0, - 52, 0, 0, 0, 15, 0, 0, 0, - 0, 0, 1, 0, 52, 0, 0, 0, + 50, 0, 0, 0, 14, 0, 0, 0, + 0, 0, 1, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 117, 8, 0, 0, 170, 0, 0, 0, + 117, 8, 0, 0, 194, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 8, 0, 0, 3, 0, 1, 0, 132, 8, 0, 0, 2, 0, 1, 0, + 51, 0, 0, 0, 140, 0, 0, 0, + 0, 0, 1, 0, 51, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 129, 8, 0, 0, 202, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 136, 8, 0, 0, 3, 0, 1, 0, + 148, 8, 0, 0, 2, 0, 1, 0, + 52, 0, 0, 0, 15, 0, 0, 0, + 0, 0, 1, 0, 52, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 145, 8, 0, 0, 170, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 148, 8, 0, 0, 3, 0, 1, 0, + 160, 8, 0, 0, 2, 0, 1, 0, 53, 0, 0, 0, 27, 0, 0, 0, 0, 0, 1, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 129, 8, 0, 0, 186, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 132, 8, 0, 0, 3, 0, 1, 0, - 144, 8, 0, 0, 2, 0, 1, 0, - 54, 0, 0, 0, 20, 0, 0, 0, - 0, 0, 1, 0, 54, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 141, 8, 0, 0, 154, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 144, 8, 0, 0, 3, 0, 1, 0, - 156, 8, 0, 0, 2, 0, 1, 0, - 55, 0, 0, 0, 32, 0, 0, 0, - 0, 0, 1, 0, 55, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 153, 8, 0, 0, 210, 0, 0, 0, + 157, 8, 0, 0, 186, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 8, 0, 0, 3, 0, 1, 0, 172, 8, 0, 0, 2, 0, 1, 0, - 56, 0, 0, 0, 17, 0, 0, 0, - 0, 0, 1, 0, 56, 0, 0, 0, + 54, 0, 0, 0, 20, 0, 0, 0, + 0, 0, 1, 0, 54, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 169, 8, 0, 0, 138, 0, 0, 0, + 169, 8, 0, 0, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 172, 8, 0, 0, 3, 0, 1, 0, 184, 8, 0, 0, 2, 0, 1, 0, + 55, 0, 0, 0, 32, 0, 0, 0, + 0, 0, 1, 0, 55, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 181, 8, 0, 0, 210, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 188, 8, 0, 0, 3, 0, 1, 0, + 200, 8, 0, 0, 2, 0, 1, 0, + 56, 0, 0, 0, 17, 0, 0, 0, + 0, 0, 1, 0, 56, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 197, 8, 0, 0, 138, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 200, 8, 0, 0, 3, 0, 1, 0, + 212, 8, 0, 0, 2, 0, 1, 0, 57, 0, 0, 0, 33, 0, 0, 0, 0, 0, 1, 0, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 181, 8, 0, 0, 154, 0, 0, 0, + 209, 8, 0, 0, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 184, 8, 0, 0, 3, 0, 1, 0, - 196, 8, 0, 0, 2, 0, 1, 0, + 212, 8, 0, 0, 3, 0, 1, 0, + 224, 8, 0, 0, 2, 0, 1, 0, 58, 0, 0, 0, 21, 0, 0, 0, 0, 0, 1, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 193, 8, 0, 0, 154, 0, 0, 0, + 221, 8, 0, 0, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 196, 8, 0, 0, 3, 0, 1, 0, - 208, 8, 0, 0, 2, 0, 1, 0, + 224, 8, 0, 0, 3, 0, 1, 0, + 236, 8, 0, 0, 2, 0, 1, 0, 59, 0, 0, 0, 141, 0, 0, 0, 0, 0, 1, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 205, 8, 0, 0, 146, 0, 0, 0, + 233, 8, 0, 0, 146, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 208, 8, 0, 0, 3, 0, 1, 0, - 220, 8, 0, 0, 2, 0, 1, 0, + 236, 8, 0, 0, 3, 0, 1, 0, + 248, 8, 0, 0, 2, 0, 1, 0, + 60, 0, 0, 0, 18, 0, 0, 0, + 0, 0, 1, 0, 60, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 245, 8, 0, 0, 154, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 248, 8, 0, 0, 3, 0, 1, 0, + 4, 9, 0, 0, 2, 0, 1, 0, 115, 108, 111, 116, 73, 100, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -4771,6 +4778,16 @@ static const ::capnp::_::AlignedData<1030> b_a1680744031fdb2d = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 119, 104, 101, 101, 108, 79, 118, 101, + 114, 114, 105, 100, 101, 83, 105, 110, + 99, 101, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }; ::capnp::word const* const bp_a1680744031fdb2d = b_a1680744031fdb2d.words; @@ -4782,11 +4799,11 @@ static const ::capnp::_::RawSchema* const d_a1680744031fdb2d[] = { &s_f58695a93b568caf, &s_fb41e1a738d832cb, }; -static const uint16_t m_a1680744031fdb2d[] = {42, 59, 56, 35, 13, 23, 24, 51, 50, 29, 30, 32, 31, 20, 9, 40, 16, 37, 8, 7, 14, 43, 17, 38, 19, 45, 47, 39, 55, 54, 53, 10, 15, 48, 44, 52, 36, 18, 46, 21, 4, 49, 6, 0, 1, 11, 26, 27, 25, 41, 28, 12, 22, 34, 33, 58, 57, 2, 3, 5}; -static const uint16_t i_a1680744031fdb2d[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59}; +static const uint16_t m_a1680744031fdb2d[] = {42, 59, 56, 35, 13, 23, 24, 51, 50, 29, 30, 32, 31, 20, 9, 40, 16, 37, 8, 7, 14, 43, 17, 38, 19, 45, 47, 39, 55, 54, 53, 10, 15, 48, 44, 52, 36, 18, 46, 21, 4, 49, 6, 0, 1, 11, 26, 27, 25, 41, 28, 12, 22, 34, 33, 58, 57, 2, 3, 5, 60}; +static const uint16_t i_a1680744031fdb2d[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60}; const ::capnp::_::RawSchema s_a1680744031fdb2d = { - 0xa1680744031fdb2d, b_a1680744031fdb2d.words, 1030, d_a1680744031fdb2d, m_a1680744031fdb2d, - 5, 60, i_a1680744031fdb2d, nullptr, nullptr, { &s_a1680744031fdb2d, nullptr, nullptr, 0, 0, nullptr }, false + 0xa1680744031fdb2d, b_a1680744031fdb2d.words, 1047, d_a1680744031fdb2d, m_a1680744031fdb2d, + 5, 61, i_a1680744031fdb2d, nullptr, nullptr, { &s_a1680744031fdb2d, nullptr, nullptr, 0, 0, nullptr }, false }; #endif // !CAPNP_LITE static const ::capnp::_::AlignedData<81> b_a7940385bedd1d4c = { @@ -6891,17 +6908,17 @@ const ::capnp::_::RawSchema s_b8cbd9e666181b95 = { }; #endif // !CAPNP_LITE CAPNP_DEFINE_ENUM(Kind_b8cbd9e666181b95, b8cbd9e666181b95); -static const ::capnp::_::AlignedData<295> b_cd96dafb67a082d0 = { +static const ::capnp::_::AlignedData<312> b_cd96dafb67a082d0 = { { 0, 0, 0, 0, 5, 0, 6, 0, 208, 130, 160, 103, 251, 218, 150, 205, - 13, 0, 0, 0, 1, 0, 7, 0, + 13, 0, 0, 0, 1, 0, 8, 0, 89, 10, 85, 29, 102, 186, 38, 181, 6, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, 0, 0, 226, 0, 0, 0, 33, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 113, 0, 0, 0, 135, 3, 0, 0, + 113, 0, 0, 0, 191, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 117, 115, 116, 111, 109, 46, 99, @@ -6930,119 +6947,126 @@ static const ::capnp::_::AlignedData<295> b_cd96dafb67a082d0 = { 77, 97, 110, 117, 97, 108, 77, 111, 100, 101, 71, 101, 115, 116, 117, 114, 101, 0, 0, 0, 0, 0, 0, 0, - 64, 0, 0, 0, 3, 0, 4, 0, + 68, 0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 177, 1, 0, 0, 66, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 172, 1, 0, 0, 3, 0, 1, 0, - 184, 1, 0, 0, 2, 0, 1, 0, - 1, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 1, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 181, 1, 0, 0, 138, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 184, 1, 0, 0, 3, 0, 1, 0, - 196, 1, 0, 0, 2, 0, 1, 0, - 2, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 1, 0, 2, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 193, 1, 0, 0, 98, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 192, 1, 0, 0, 3, 0, 1, 0, - 204, 1, 0, 0, 2, 0, 1, 0, - 3, 0, 0, 0, 5, 0, 0, 0, - 0, 0, 1, 0, 3, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 201, 1, 0, 0, 98, 0, 0, 0, + 205, 1, 0, 0, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 1, 0, 0, 3, 0, 1, 0, 212, 1, 0, 0, 2, 0, 1, 0, - 4, 0, 0, 0, 12, 0, 0, 0, - 0, 0, 1, 0, 4, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 209, 1, 0, 0, 58, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 204, 1, 0, 0, 3, 0, 1, 0, - 216, 1, 0, 0, 2, 0, 1, 0, - 5, 0, 0, 0, 208, 0, 0, 0, - 0, 0, 1, 0, 5, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 213, 1, 0, 0, 82, 0, 0, 0, + 209, 1, 0, 0, 138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 212, 1, 0, 0, 3, 0, 1, 0, 224, 1, 0, 0, 2, 0, 1, 0, - 6, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 6, 0, 0, 0, + 2, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 221, 1, 0, 0, 146, 0, 0, 0, + 221, 1, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 224, 1, 0, 0, 3, 0, 1, 0, - 236, 1, 0, 0, 2, 0, 1, 0, - 7, 0, 0, 0, 14, 0, 0, 0, - 0, 0, 1, 0, 7, 0, 0, 0, + 220, 1, 0, 0, 3, 0, 1, 0, + 232, 1, 0, 0, 2, 0, 1, 0, + 3, 0, 0, 0, 5, 0, 0, 0, + 0, 0, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 233, 1, 0, 0, 42, 0, 0, 0, + 229, 1, 0, 0, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 228, 1, 0, 0, 3, 0, 1, 0, 240, 1, 0, 0, 2, 0, 1, 0, - 8, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 1, 0, 8, 0, 0, 0, + 4, 0, 0, 0, 12, 0, 0, 0, + 0, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 237, 1, 0, 0, 82, 0, 0, 0, + 237, 1, 0, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 236, 1, 0, 0, 3, 0, 1, 0, - 248, 1, 0, 0, 2, 0, 1, 0, - 9, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 1, 0, 9, 0, 0, 0, + 232, 1, 0, 0, 3, 0, 1, 0, + 244, 1, 0, 0, 2, 0, 1, 0, + 5, 0, 0, 0, 208, 0, 0, 0, + 0, 0, 1, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 245, 1, 0, 0, 90, 0, 0, 0, + 241, 1, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 244, 1, 0, 0, 3, 0, 1, 0, - 0, 2, 0, 0, 2, 0, 1, 0, - 10, 0, 0, 0, 5, 0, 0, 0, - 0, 0, 1, 0, 10, 0, 0, 0, + 240, 1, 0, 0, 3, 0, 1, 0, + 252, 1, 0, 0, 2, 0, 1, 0, + 6, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 253, 1, 0, 0, 122, 0, 0, 0, + 249, 1, 0, 0, 146, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, 1, 0, 0, 3, 0, 1, 0, 8, 2, 0, 0, 2, 0, 1, 0, + 7, 0, 0, 0, 14, 0, 0, 0, + 0, 0, 1, 0, 7, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 2, 0, 0, 42, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 3, 0, 1, 0, + 12, 2, 0, 0, 2, 0, 1, 0, + 8, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 1, 0, 8, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 9, 2, 0, 0, 82, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 8, 2, 0, 0, 3, 0, 1, 0, + 20, 2, 0, 0, 2, 0, 1, 0, + 9, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 1, 0, 9, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 17, 2, 0, 0, 90, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 16, 2, 0, 0, 3, 0, 1, 0, + 28, 2, 0, 0, 2, 0, 1, 0, + 10, 0, 0, 0, 5, 0, 0, 0, + 0, 0, 1, 0, 10, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 25, 2, 0, 0, 122, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 24, 2, 0, 0, 3, 0, 1, 0, + 36, 2, 0, 0, 2, 0, 1, 0, 11, 0, 0, 0, 6, 0, 0, 0, 0, 0, 1, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 5, 2, 0, 0, 82, 0, 0, 0, + 33, 2, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 2, 0, 0, 3, 0, 1, 0, - 16, 2, 0, 0, 2, 0, 1, 0, + 32, 2, 0, 0, 3, 0, 1, 0, + 44, 2, 0, 0, 2, 0, 1, 0, 12, 0, 0, 0, 2, 0, 0, 0, 0, 0, 1, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 13, 2, 0, 0, 90, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 12, 2, 0, 0, 3, 0, 1, 0, - 24, 2, 0, 0, 2, 0, 1, 0, - 13, 0, 0, 0, 3, 0, 0, 0, - 0, 0, 1, 0, 13, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 21, 2, 0, 0, 98, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 20, 2, 0, 0, 3, 0, 1, 0, - 32, 2, 0, 0, 2, 0, 1, 0, - 14, 0, 0, 0, 4, 0, 0, 0, - 0, 0, 1, 0, 14, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 29, 2, 0, 0, 98, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 28, 2, 0, 0, 3, 0, 1, 0, - 40, 2, 0, 0, 2, 0, 1, 0, - 15, 0, 0, 0, 5, 0, 0, 0, - 0, 0, 1, 0, 15, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 37, 2, 0, 0, 138, 0, 0, 0, + 41, 2, 0, 0, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 2, 0, 0, 3, 0, 1, 0, 52, 2, 0, 0, 2, 0, 1, 0, + 13, 0, 0, 0, 3, 0, 0, 0, + 0, 0, 1, 0, 13, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 49, 2, 0, 0, 98, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 48, 2, 0, 0, 3, 0, 1, 0, + 60, 2, 0, 0, 2, 0, 1, 0, + 14, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 1, 0, 14, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 57, 2, 0, 0, 98, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 56, 2, 0, 0, 3, 0, 1, 0, + 68, 2, 0, 0, 2, 0, 1, 0, + 15, 0, 0, 0, 5, 0, 0, 0, + 0, 0, 1, 0, 15, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 65, 2, 0, 0, 138, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 68, 2, 0, 0, 3, 0, 1, 0, + 80, 2, 0, 0, 2, 0, 1, 0, + 16, 0, 0, 0, 7, 0, 0, 0, + 0, 0, 1, 0, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 77, 2, 0, 0, 154, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 80, 2, 0, 0, 3, 0, 1, 0, + 92, 2, 0, 0, 2, 0, 1, 0, 101, 118, 101, 110, 116, 73, 100, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -7185,6 +7209,16 @@ static const ::capnp::_::AlignedData<295> b_cd96dafb67a082d0 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 119, 104, 101, 101, 108, 79, 118, 101, + 114, 114, 105, 100, 101, 83, 105, 110, + 99, 101, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, } }; @@ -7198,11 +7232,11 @@ static const ::capnp::_::RawSchema* const d_cd96dafb67a082d0[] = { &s_efc922094a634c9f, &s_fd07e0b91e3573c2, }; -static const uint16_t m_cd96dafb67a082d0[] = {4, 11, 9, 0, 7, 5, 12, 1, 10, 2, 6, 3, 8, 15, 13, 14}; -static const uint16_t i_cd96dafb67a082d0[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; +static const uint16_t m_cd96dafb67a082d0[] = {4, 11, 9, 0, 7, 5, 12, 1, 10, 2, 6, 3, 8, 15, 13, 14, 16}; +static const uint16_t i_cd96dafb67a082d0[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; const ::capnp::_::RawSchema s_cd96dafb67a082d0 = { - 0xcd96dafb67a082d0, b_cd96dafb67a082d0.words, 295, d_cd96dafb67a082d0, m_cd96dafb67a082d0, - 6, 16, i_cd96dafb67a082d0, nullptr, nullptr, { &s_cd96dafb67a082d0, nullptr, nullptr, 0, 0, nullptr }, false + 0xcd96dafb67a082d0, b_cd96dafb67a082d0.words, 312, d_cd96dafb67a082d0, m_cd96dafb67a082d0, + 6, 17, i_cd96dafb67a082d0, nullptr, nullptr, { &s_cd96dafb67a082d0, nullptr, nullptr, 0, 0, nullptr }, false }; #endif // !CAPNP_LITE static const ::capnp::_::AlignedData<99> b_cc2db5640fb6dc61 = { diff --git a/openpilot/cereal/gen/cpp/custom.capnp.h b/openpilot/cereal/gen/cpp/custom.capnp.h index 20d21945a5..b5ac8a968f 100644 --- a/openpilot/cereal/gen/cpp/custom.capnp.h +++ b/openpilot/cereal/gen/cpp/custom.capnp.h @@ -730,7 +730,7 @@ struct SlcState { struct ConditionalModeState; struct _capnpPrivate { - CAPNP_DECLARE_STRUCT_HEADER(a1680744031fdb2d, 18, 22) + CAPNP_DECLARE_STRUCT_HEADER(a1680744031fdb2d, 19, 22) #if !CAPNP_LITE static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; } #endif // !CAPNP_LITE @@ -878,7 +878,7 @@ struct SlcCruiseEvent { struct ManualModeGesture; struct _capnpPrivate { - CAPNP_DECLARE_STRUCT_HEADER(cd96dafb67a082d0, 7, 6) + CAPNP_DECLARE_STRUCT_HEADER(cd96dafb67a082d0, 8, 6) #if !CAPNP_LITE static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; } #endif // !CAPNP_LITE @@ -4368,6 +4368,8 @@ public: inline bool getAcceptedIsPreview() const; + inline ::uint64_t getWheelOverrideSince() const; + private: ::capnp::_::StructReader _reader; template @@ -4664,6 +4666,9 @@ public: inline bool getAcceptedIsPreview(); inline void setAcceptedIsPreview(bool value); + inline ::uint64_t getWheelOverrideSince(); + inline void setWheelOverrideSince( ::uint64_t value); + private: ::capnp::_::StructBuilder _builder; template @@ -5975,6 +5980,8 @@ public: inline bool hasTeslaStockCruise() const; inline ::cereal::SlcCruiseEvent::TeslaStockCruise::Reader getTeslaStockCruise() const; + inline ::uint64_t getWheelOverrideSince() const; + private: ::capnp::_::StructReader _reader; template @@ -6075,6 +6082,9 @@ public: inline void adoptTeslaStockCruise(::capnp::Orphan< ::cereal::SlcCruiseEvent::TeslaStockCruise>&& value); inline ::capnp::Orphan< ::cereal::SlcCruiseEvent::TeslaStockCruise> disownTeslaStockCruise(); + inline ::uint64_t getWheelOverrideSince(); + inline void setWheelOverrideSince( ::uint64_t value); + private: ::capnp::_::StructBuilder _builder; template @@ -19732,6 +19742,20 @@ inline void SlcState::Builder::setAcceptedIsPreview(bool value) { ::capnp::bounded<141>() * ::capnp::ELEMENTS, value); } +inline ::uint64_t SlcState::Reader::getWheelOverrideSince() const { + return _reader.getDataField< ::uint64_t>( + ::capnp::bounded<18>() * ::capnp::ELEMENTS); +} + +inline ::uint64_t SlcState::Builder::getWheelOverrideSince() { + return _builder.getDataField< ::uint64_t>( + ::capnp::bounded<18>() * ::capnp::ELEMENTS); +} +inline void SlcState::Builder::setWheelOverrideSince( ::uint64_t value) { + _builder.setDataField< ::uint64_t>( + ::capnp::bounded<18>() * ::capnp::ELEMENTS, value); +} + inline bool SlcState::SourceReading::Reader::hasSource() const { return !_reader.getPointerField( ::capnp::bounded<0>() * ::capnp::POINTERS).isNull(); @@ -22107,6 +22131,20 @@ inline ::capnp::Orphan< ::cereal::SlcCruiseEvent::TeslaStockCruise> SlcCruiseEve ::capnp::bounded<5>() * ::capnp::POINTERS)); } +inline ::uint64_t SlcCruiseEvent::Reader::getWheelOverrideSince() const { + return _reader.getDataField< ::uint64_t>( + ::capnp::bounded<7>() * ::capnp::ELEMENTS); +} + +inline ::uint64_t SlcCruiseEvent::Builder::getWheelOverrideSince() { + return _builder.getDataField< ::uint64_t>( + ::capnp::bounded<7>() * ::capnp::ELEMENTS); +} +inline void SlcCruiseEvent::Builder::setWheelOverrideSince( ::uint64_t value) { + _builder.setDataField< ::uint64_t>( + ::capnp::bounded<7>() * ::capnp::ELEMENTS, value); +} + inline ::uint16_t SlcCruiseEvent::TeslaStockCruise::Reader::getVersion() const { return _reader.getDataField< ::uint16_t>( ::capnp::bounded<0>() * ::capnp::ELEMENTS); diff --git a/openpilot/cereal/libcereal.a b/openpilot/cereal/libcereal.a index 1d44f32500..97bb5df352 100644 Binary files a/openpilot/cereal/libcereal.a and b/openpilot/cereal/libcereal.a differ diff --git a/openpilot/common/libcommon.a b/openpilot/common/libcommon.a index cff0ad7eda..047f2dcc4a 100644 Binary files a/openpilot/common/libcommon.a and b/openpilot/common/libcommon.a differ diff --git a/openpilot/common/libparams_c.so b/openpilot/common/libparams_c.so index b9fc0e1203..88066b5368 100755 Binary files a/openpilot/common/libparams_c.so and b/openpilot/common/libparams_c.so differ diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index 9a8f994ec8..c48fa056a0 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -332,6 +332,7 @@ inline static std::unordered_map keys = { {"SLCAdvanceHigherSeconds", {PERSISTENT, INT, "0"}}, {"SLCAdvanceLowerSeconds", {PERSISTENT, INT, "0"}}, {"SLCOverrideMode", {PERSISTENT, INT, "1"}}, + {"SLCWheelOverride", {PERSISTENT, BOOL, "0"}}, {"SLCQualifiedHistory", {PERSISTENT, JSON}}, {"ConditionalModeConfig", {PERSISTENT | DONT_LOG, JSON}}, {"ConditionalManualState", {PERSISTENT | DONT_LOG, JSON}}, diff --git a/openpilot/selfdrive/car/card.py b/openpilot/selfdrive/car/card.py index 4bbb663bd0..91f72f8a2f 100644 --- a/openpilot/selfdrive/car/card.py +++ b/openpilot/selfdrive/car/card.py @@ -31,7 +31,7 @@ from opendbc.car.hyundai.ioniq6_media import Ioniq6MediaButtons from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp -from openpilot.selfdrive.car.cruise import VCruiseHelper +from openpilot.selfdrive.car.cruise import SlcWheelContext, VCruiseHelper from openpilot.starpilot.speed_limits import physical_actions as slc_physical from openpilot.starpilot.aol.intent import AolProcessFaultContext, independent_axis_requested, read_settings from openpilot.starpilot.aol.transport_pause import TransportPauseFeedback, cp_identity @@ -573,7 +573,15 @@ class Car: if self.slc_replay: pending = slc_physical.pending_confirmation( self.sm, now_ns, long_active=slc_long_active, after_ns=floor_ns) - self.update_cruise_speed(CS, pending) + wheel_context = None + slc_settings = getattr(self, 'slc_configuration', (False, None))[1] + cruise_helper = self.v_cruise_helper + if (self.slc_replay and getattr(slc_settings, 'wheel_override', False) and host_long_active and can_rcv_valid and + not self.CP.pcmCruise and pending is None and (cruise_helper.slc_wheel_press is not None or len(CS.buttonEvents) > 0) and + not (CS.brakePressed or CS.gasPressed or CS.standstill or CS.cruiseState.standstill)): + wheel_context = slc_physical.wheel_override_context(self.sm, now_ns, after_ns=floor_ns, + selected_kph=cruise_helper.v_cruise_kph, is_metric=self.is_metric) + self.update_cruise_speed(CS, pending, wheel_context=wheel_context) if self.controller_cruise_sock is not None: for _ in range(8): request = messaging.recv_one_or_none(self.controller_cruise_sock) @@ -788,9 +796,10 @@ class Car: return CS, RD - def update_cruise_speed(self, CS, pending=None): + def update_cruise_speed(self, CS, pending=None, *, wheel_context: SlcWheelContext | None = None): self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric, pending, - slc_enabled=getattr(self, 'slc_control_enabled', getattr(self, 'slc_replay', False))) + slc_enabled=getattr(self, 'slc_control_enabled', getattr(self, 'slc_replay', False)), + wheel_context=wheel_context) if self.sm['carControl'].enabled and not self.CC_prev.enabled: target, initial = None, None now_ns = int(self.can_log_mono_time) if REPLAY and hasattr(self, 'can_log_mono_time') else time.monotonic_ns() @@ -1123,6 +1132,8 @@ class Car: if kind == 'driverChange': assert isinstance(detail, tuple) and len(detail) == 4 record.button, record.longPress = detail[2], detail[3] + if self.v_cruise_helper.slc_wheel_tap is not None: + record.sessionId, record.wheelOverrideSince = self.v_cruise_helper.slc_wheel_tap elif kind in ('confirmationAccept', 'confirmationReject'): record.button = detail.button record.sessionId = detail.pending.session_id diff --git a/openpilot/selfdrive/car/cruise.py b/openpilot/selfdrive/car/cruise.py index 7e0d6563eb..62e2d5dd5c 100644 --- a/openpilot/selfdrive/car/cruise.py +++ b/openpilot/selfdrive/car/cruise.py @@ -43,6 +43,9 @@ class SlcConsumedButton: button: str +SlcWheelContext = tuple[str, int] + + def validated_initial_cruise_speed(value: object) -> int: if type(value) is int and V_CRUISE_INITIAL <= value <= V_CRUISE_MAX: return value @@ -70,18 +73,22 @@ class VCruiseHelper: self.slc_consumed_button: SlcConsumedButton | None = None self.slc_suppressed_buttons: set[ButtonType] = set() self.slc_released_suppressed = False + self.slc_wheel_press: SlcWheelContext | None = None + self.slc_wheel_tap: SlcWheelContext | None = None @property def v_cruise_initialized(self): return self.v_cruise_kph != V_CRUISE_UNSET - def update_v_cruise(self, CS, enabled, is_metric, slc_pending: SlcPendingConfirmation | None = None, *, slc_enabled: bool = False): + def update_v_cruise(self, CS, enabled, is_metric, slc_pending: SlcPendingConfirmation | None = None, *, + slc_enabled: bool = False, wheel_context: SlcWheelContext | None = None): from opendbc.car.subaru.software_cruise import selected as subaru_software_selected subaru_software = subaru_software_selected(self.CP) software_cruise = not self.CP.pcmCruise or subaru_software self.slc_cruise_change = None self.slc_consumed_button = None self.slc_released_suppressed = False + self.slc_wheel_tap = None self.v_cruise_kph_last = self.v_cruise_kph now_ns = monotonic_ns() if not CS.cruiseState.available or now_ns - self.pending_resume_ns > 500_000_000: @@ -97,6 +104,16 @@ class VCruiseHelper: if software_cruise or slc_enabled and self.CP.openpilotLongitudinalControl or self.slc_suppressed_buttons: self._update_slc_button_latches(CS, enabled, slc_pending) + if (not slc_enabled or not enabled or self.CP.pcmCruise or slc_pending is not None or + not CS.cruiseState.available or ButtonType.accelCruise in self.slc_suppressed_buttons): + wheel_context = None + if wheel_context != self.slc_wheel_press: + self.slc_wheel_press = None + if wheel_context is not None and self.button_timers[ButtonType.accelCruise] == 0: + pressed = {b.type.raw for b in CS.buttonEvents if b.pressed} + if ButtonType.accelCruise in pressed and ButtonType.resumeCruise not in pressed: + self.slc_wheel_press = wheel_context + if CS.cruiseState.available: if software_cruise: if subaru_software and (not enabled or not self.v_cruise_initialized): @@ -122,6 +139,8 @@ class VCruiseHelper: self.pcm_slc_button = ButtonType.unknown if software_cruise: self.update_button_timers(CS, enabled) + if self.button_timers[ButtonType.accelCruise] == 0: + self.slc_wheel_press = None def _update_toyota_pcm_slc_change(self, CS, enabled: bool, now_ns: int) -> None: """Observe a physical PCM speed change without writing stock selected speed.""" @@ -211,7 +230,9 @@ class VCruiseHelper: if not self.button_change_states[button_type]["enabled"]: return - self.adjust_v_cruise(button_type, CS, is_metric, long_press=long_press) + changed = self.adjust_v_cruise(button_type, CS, is_metric, long_press=long_press) + if changed and button_type == ButtonType.accelCruise and not long_press and self.button_timers[button_type] > 0: + self.slc_wheel_tap = self.slc_wheel_press def adjust_v_cruise(self, button_type, CS, is_metric: bool, *, long_press: bool = False) -> bool: """Apply the normal software cruise step without synthesizing button events.""" diff --git a/openpilot/selfdrive/controls/plannerd.py b/openpilot/selfdrive/controls/plannerd.py index ce27c3b259..82c251d9cc 100755 --- a/openpilot/selfdrive/controls/plannerd.py +++ b/openpilot/selfdrive/controls/plannerd.py @@ -124,6 +124,12 @@ def confirm_curve_frame(host: CurveHost | None, planner: LongitudinalPlanner, no host.runtime.confirm_applied(now_ns, applied=bool(planner.last_curve_ceiling_applied)) +def wheel_override_blocked(force_stop: ForceStopRuntime | None, curve_host: CurveHost | None, *, curve_backlog: bool = False) -> bool: + # Force Stop can consume RES on the tick it commits, before a forcing plan is published. + return ((force_stop is not None and (force_stop.policy.can_commit_next_frame or force_stop.plan.speed_ceiling_mps is not None)) or + (curve_host is not None and (curve_backlog or curve_host.runtime.was_controlling))) + + def conditional_handoff_for_frame(host: ConditionalPlannerHost | None, sm, CP, now_ns: int, drive_id: int) -> ConditionalHandoff | None: if host is None or not CP.openpilotLongitudinalControl or CP.passive or CP.dashcamOnly: @@ -444,7 +450,10 @@ def starpilot_main(): else: wheel_coast.receive(event, params, CP, sm, now_ns=now_ns) pending_wheel = waiting_wheel - slc_output = slc_lifecycle.step(sm, now_ns, pending_slc_actions, pending_slc_cruise, drive_changed=drive_changed) + # Curve observes the press before SLC sees the release. A queued Curve press can still own the tap. + curve_event = current_cruise_event(pending_curve_events, now_ns) + slc_output = slc_lifecycle.step(sm, now_ns, pending_slc_actions, pending_slc_cruise, drive_changed=drive_changed, + wheel_override_blocked=wheel_override_blocked(force_stop_owner, curve_host, curve_backlog=bool(pending_curve_events))) cruise_ceiling = slc_output.result.ceiling if slc_output is not None else None if slc_output is not None: planner_status = slc_output.message @@ -507,7 +516,6 @@ def starpilot_main(): lead_approach_key = lead_approach_for_frame(approach_preferences, sm, CP, now_ns) if curve_preferences is not None and curve_host is not None: curve_preferences.refresh(curve_host, now_ns) - curve_event = current_cruise_event(pending_curve_events, now_ns) conditional_handoff = (conditional_handoff_for_frame(conditional_host, sm, CP, clock_pair[0], current_start_ns) if clock_pair is not None else None) wheel_coast.conditional_stop = bool(conditional_handoff is not None and conditional_host is not None and @@ -586,6 +594,8 @@ def starpilot_main(): mode_owner.sample('switchback', sm, CP, now_ns=now_ns), session=mode_session, sequence=mode_sequence, now_ns=now_ns, source_car_control_ns=int(sm.logMonoTime['carControl'])) if planner_status is not None: + if planner_status.slcState.wheelOverrideSince and wheel_override_blocked(force_stop_owner, curve_host): + planner_status.slcState.wheelOverrideSince = 0 pm.send('slcState', planner_status) if current_start_ns > 0: drive_start_ns = current_start_ns diff --git a/openpilot/starpilot/galaxy/setting_descriptions.py b/openpilot/starpilot/galaxy/setting_descriptions.py index 70c0061dd3..a975b56421 100644 --- a/openpilot/starpilot/galaxy/setting_descriptions.py +++ b/openpilot/starpilot/galaxy/setting_descriptions.py @@ -63,7 +63,9 @@ DESCRIPTIONS = { 'curve_reset': 'Deletes saved cornering learning so Curve Speed Controller can learn your preferences again.', 'SpeedLimitController': 'Follows accepted limits plus offsets up to SET speed. Missing data uses the previous accepted limit.', 'SLCOnEngage': 'Starts software cruise SET at the SLC limit plus offset, including a saved limit when live data is missing. RES restores the previous SET.', - 'SLCOverrideMode': 'Chooses whether pressing the gas past the adjusted speed limit holds the speed you reach, uses SET, or leaves the limit in control.', + 'SLCOverrideMode': ('Cruise uses this target when you lift off the accelerator. ' + + 'Holding your speed uses the highest speed you reached, up to SET.'), + 'SLCWheelOverride': 'Press + to raise SET past the adjusted limit and use your new SET speed.', 'ShowSpeedLimits': 'Shows recognized speed-limit signs on the driving screen.', 'SLCConfirmation': ('Off adopts new limits automatically. Both, Higher only, and Lower only prompt relative to the last accepted limit. ' + '+ accepts and − declines within 30 seconds.'), diff --git a/openpilot/starpilot/galaxy/tests/description-key-inventory.json b/openpilot/starpilot/galaxy/tests/description-key-inventory.json index b3f039d8e3..29b0d74fb1 100644 --- a/openpilot/starpilot/galaxy/tests/description-key-inventory.json +++ b/openpilot/starpilot/galaxy/tests/description-key-inventory.json @@ -85,6 +85,7 @@ "SLCPriority1", "SLCPriority2", "SLCPriority3", + "SLCWheelOverride", "ScreenBrightness", "ScreenBrightnessOnroad", "ScreenTimeout", diff --git a/openpilot/starpilot/longitudinal/force_stop.py b/openpilot/starpilot/longitudinal/force_stop.py index 6417e1b737..2848423180 100644 --- a/openpilot/starpilot/longitudinal/force_stop.py +++ b/openpilot/starpilot/longitudinal/force_stop.py @@ -7,6 +7,8 @@ from openpilot.common.realtime import DT_MDL STANDSTILL_HOLD_NS = 2_000_000_000 STANDSTILL_CLEAR_NS = 750_000_000 +STOP_COMMIT_TIME_S = .5 +SIGN_CONFIRM_STEP_S = 2 * DT_MDL SIGN_MAX_SPEED_MPS = 17.88 SIGN_MODEL_AGREE_M = 50.0 SIGN_SEED_M = 27.0 @@ -102,6 +104,10 @@ class ForceStop: self.standstill_clear_ns = 0 self.standstill_reason = None + @property + def can_commit_next_frame(self) -> bool: + return self.timer + SIGN_CONFIRM_STEP_S >= STOP_COMMIT_TIME_S + def step(self, frame: StopFrame, tune: StopTune, offset_ft: int = 0) -> StopPlan: now = frame.model_ns if (type(now) is not int or now <= 0 or now <= self.last_model_ns or @@ -177,16 +183,16 @@ class ForceStop: if not available and not self.forcing: self.timer = 0 elif active and not frame.standstill: - self.timer = min(2.0, self.timer + DT_MDL * (2 if frame.sign else 1)) + self.timer = min(2.0, self.timer + (SIGN_CONFIRM_STEP_S if frame.sign else DT_MDL)) elif turn and not self.forcing and not frame.standstill: self.timer = 0 elif held: - self.timer = max(self.timer, .5) + self.timer = max(self.timer, STOP_COMMIT_TIME_S) elif self.forcing and frame.standstill and not frame.sign and not frame.light and not frame.raw_model_stopped: self.timer = 0 else: self.timer = max(0, self.timer - DT_MDL * .25) - force = self.timer >= .5 or self.forcing and not frame.standstill or held + force = self.timer >= STOP_COMMIT_TIME_S or self.forcing and not frame.standstill or held light_cleared = (not held and self.forcing and self.from_light and not frame.standstill and available and not frame.light and not frame.sign and go_scene) low_speed_hold = (light_cleared and tune.low_speed_hold_mps is not None and self.entry_speed is not None and diff --git a/openpilot/starpilot/longitudinal/tests/test_force_stop_runtime.py b/openpilot/starpilot/longitudinal/tests/test_force_stop_runtime.py index c4dc90b5b5..40a72b52a1 100644 --- a/openpilot/starpilot/longitudinal/tests/test_force_stop_runtime.py +++ b/openpilot/starpilot/longitudinal/tests/test_force_stop_runtime.py @@ -7,11 +7,12 @@ from opendbc.car.honda.interface import CarInterface from opendbc.car.honda.values import CAR from openpilot.common.params import Params from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner -from openpilot.selfdrive.controls.plannerd import update_curve_frame +from openpilot.selfdrive.controls.plannerd import update_curve_frame, wheel_override_blocked from openpilot.starpilot.curve_speed.host import CurveHost -from openpilot.starpilot.longitudinal.force_stop import StopPlan +from openpilot.starpilot.longitudinal.force_stop import StopPlan, vehicle_tune from openpilot.starpilot.longitudinal.force_stop_runtime import ForceStopRuntime, dashboard_stop_sign from openpilot.starpilot.longitudinal.tests.test_cruise_ceiling import messages, snapshot +from openpilot.starpilot.longitudinal.tests.test_force_stop import frame as stop_frame from openpilot.starpilot.longitudinal.tests.test_stop_resume import event as button_event BASE = 100_000_000_000 @@ -61,6 +62,31 @@ class ForceStopRuntimeTests(unittest.TestCase): self.assertTrue(output.forcing) return output + def test_wheel_priority_requires_a_stop_cap_or_possible_commit_next_frame(self): + tune = vehicle_tune(str(self.cp.carFingerprint)) + sign = {'light': False, 'sign': True, 'horizon_m': 40.} + self.owner.plan = self.owner.policy.step(stop_frame(0, **sign), tune) + self.assertIsNone(self.owner.plan.speed_ceiling_mps) + self.assertFalse(wheel_override_blocked(self.owner, None)) + self.owner.plan = self.owner.policy.step(stop_frame(1, light=False, resume_requested=True), tune) + self.assertGreater(self.owner.policy.timer, 0.) + self.assertEqual(self.owner.policy.override_until_ns, 0) + self.assertFalse(wheel_override_blocked(self.owner, None)) + + self.owner.reset() + for tick in range(4): + self.owner.plan = self.owner.policy.step(stop_frame(tick, **sign), tune) + self.assertIsNone(self.owner.plan.speed_ceiling_mps) + self.assertTrue(wheel_override_blocked(self.owner, None)) + self.owner.policy.step(stop_frame(4, resume_requested=True, **sign), tune) + self.assertGreater(self.owner.policy.override_until_ns, 0) + + self.owner.reset() + self.owner.plan = self.owner.policy.step(stop_frame(0), tune) + self.assertFalse(self.owner.policy.can_commit_next_frame) + self.assertIsNotNone(self.owner.plan.speed_ceiling_mps) + self.assertTrue(wheel_override_blocked(self.owner, None)) + def test_committed_stop_survives_valid_mode_settings_and_traffic_changes(self): import json from openpilot.starpilot.conditional_mode.policy import ModeChoice diff --git a/openpilot/starpilot/speed_limits/OVERRIDES.md b/openpilot/starpilot/speed_limits/OVERRIDES.md index 288fbf7977..37e01f9817 100644 --- a/openpilot/starpilot/speed_limits/OVERRIDES.md +++ b/openpilot/starpilot/speed_limits/OVERRIDES.md @@ -1,12 +1,8 @@ # Driver inputs -Ordinary cruise +/- changes the driver's SET ceiling. It does not bypass SLC, create -a persistent override, or cross out the sign. The causal ledger still distinguishes -driver actions and consumed confirmations. SLC has no separate override reducer. - -Normal accelerator handling belongs to the vehicle controls. SLC never raises or -latches its target from pedal speed. Confirmation remains available while the -driver is engaged, including when the native controls temporarily yield to the pedal. +Ordinary cruise +/- changes the driver's SET ceiling. With **Override speed limit +with +** enabled, a qualifying physical tap past the adjusted limit also lets cruise +use SET. The accelerator override has its own policy. See [SET speed and accelerator](README.md#set-speed-and-accelerator). Source loss uses the previous accepted limit plus the current offset, up to SET. That remembered limit survives re-engagement and restarts; applying it still needs diff --git a/openpilot/starpilot/speed_limits/README.md b/openpilot/starpilot/speed_limits/README.md index 7ba81c2db2..0bc4cd6a3b 100644 --- a/openpilot/starpilot/speed_limits/README.md +++ b/openpilot/starpilot/speed_limits/README.md @@ -176,9 +176,8 @@ cap while renewing pending actions. Disabling SLC still clears that constraint. ## SET speed and accelerator -Ordinary cruise +/- changes the driver's maximum; it does not bypass SLC or cross -out the sign. Confirmation presses remain consumed through hold and release and -do not also apply a software cruise step. Native PCM button effects remain native; +Cruise + and − change SET. Confirmation presses remain consumed through hold and +release and do not also apply a software cruise step. Native PCM button effects remain native; StarPilot does not pretend it can suppress the vehicle's own SET adjustment. Native standstill RES takes precedence over confirmation. @@ -188,16 +187,23 @@ Subaru software-cruise mode remains independent of SLC. The Volt conventional-cruise controller uses the fresh SLC cap beneath SET for its cruise-button speed boundary, with its existing physical-source and timing checks. -**Gas pedal override** defaults to **Hold Reached Speed**. Pressing the gas -above the accepted limit plus offset lets cruise hold the fastest speed you reach, -up to SET. **Use Set Speed** uses SET as the override target. **Off** returns -to the accepted limit on release. The override clears when cruise disengages, +**After an accelerator override** defaults to **Hold the speed you reached**. Pressing +the gas above the accepted limit plus offset lets cruise hold the fastest speed you +reach, up to SET. **Use your SET speed** targets SET. **Resume the adjusted limit** +returns to the accepted limit plus offset on release. The override clears when cruise disengages, the adjusted limit catches up, or the driver explicitly accepts or adopts a limit. After an explicit choice, the current accelerator press cannot start another override. Releasing and pressing again can start one. SET and the accepted limit are separate from the override. Lead, curve and stopping constraints still apply. +**Override speed limit with +** defaults to Off and is available when StarPilot +owns SET. A tap must move the displayed SET from at or below the adjusted limit +to above it. Cruise then uses SET until it returns to the adjusted limit, a different +limit is accepted, or engagement ends. A display-unit change or a stop while +cruise stays engaged preserves an active override. Confirmation, Curve Speed and +Force Stop take priority over this gesture. + The configured wheel action can explicitly adopt the displayed current limit. It updates SLC acceptance, not SET. The onroad screen adds no new control. diff --git a/openpilot/starpilot/speed_limits/composition.py b/openpilot/starpilot/speed_limits/composition.py index 9a06508191..fe6d346703 100644 --- a/openpilot/starpilot/speed_limits/composition.py +++ b/openpilot/starpilot/speed_limits/composition.py @@ -43,6 +43,9 @@ class Frame: pedal_override: PedalOverride = PedalOverride.OFF preview_reverted: bool = False confirmation_grace_ns: int = 0 + wheel_override_enabled: bool = False + wheel_override_blocked: bool = False + wheel_override_since_ns: int = 0 @dataclass(frozen=True) @@ -53,6 +56,8 @@ class State: reset_required: bool = False pedal_target_mps: float = 0. pedal_blocked_until_release: bool = False + wheel_override_metric: bool | None = None + wheel_override_since_ns: int = 0 @dataclass(frozen=True) @@ -86,7 +91,9 @@ def _state_valid(state: object) -> bool: return False return (type(state.session_id) is str and bool(state.session_id) and state.acceptance.session_id == state.session_id and _number(state.pedal_target_mps) and (state.last_now_ns is None or _integer(state.last_now_ns)) and - type(state.reset_required) is bool and type(state.pedal_blocked_until_release) is bool) + type(state.reset_required) is bool and type(state.pedal_blocked_until_release) is bool and + (state.wheel_override_metric is None or type(state.wheel_override_metric) is bool) and + _integer(state.wheel_override_since_ns)) def _host_valid(host: object) -> bool: @@ -119,9 +126,11 @@ def step(state: State, frame: Frame) -> Result: not _host_valid(frame.host) or not acc.authority_is_valid(frame.authority) or not isinstance(frame.acceptance_policy, acc.Policy) or not isinstance(frame.pedal_override, PedalOverride) or + not _integer(frame.wheel_override_since_ns) or any(type(value) is not bool for value in (frame.acceptance_policy.confirm_lower, frame.acceptance_policy.confirm_higher, - frame.acceptance_policy.display_only)) or + frame.acceptance_policy.display_only, + frame.wheel_override_enabled, frame.wheel_override_blocked)) or not isinstance(frame.selected_kind, sd.DomainStatus) or not isinstance(frame.ego_pair, sd.SpeedPair) or (frame.is_metric is not None and type(frame.is_metric) is not bool)): return Result(replace(state, reset_required=True), status="invalid_frame", errors=("invalid frame or clock",)) @@ -136,7 +145,7 @@ def step(state: State, frame: Frame) -> Result: engaged_path = (frame.authority.can_control and (host_long or frame.authority.accelerator_override) and selected_initialized and not frame.acceptance_policy.display_only) active_path = engaged_path and not host.force_decel - base = replace(state, last_now_ns=frame.now_ns, pedal_target_mps=0.) + base = replace(state, last_now_ns=frame.now_ns, pedal_target_mps=0., wheel_override_metric=None, wheel_override_since_ns=0) if frame.classified_cruise is not None: classified = frame.classified_cruise @@ -185,6 +194,8 @@ def step(state: State, frame: Frame) -> Result: return Result(replace(base, reset_required=True), selected, acceptance, domain, status="invalid_domain", errors=(domain.reason,)) clear_override = frame.preview_reverted or acceptance.basis in ('driver_accept', 'driver_adopt') + clear_wheel = clear_override or state.acceptance.accepted != acceptance.state.accepted + wheel_metric = state.wheel_override_metric if frame.wheel_override_enabled and not clear_wheel else None previous = 0. if clear_override else state.pedal_target_mps pedal_blocked = (state.pedal_blocked_until_release or clear_override) and ( frame.pedal_pressed is not False or ego.status is not sd.DomainStatus.VALID) @@ -193,17 +204,39 @@ def step(state: State, frame: Frame) -> Result: # Longitudinal output can resume one frame after the driver releases the pedal. if (system_mode and host.selfdrive_enabled and not engaged_path and selected_initialized and not (host.force_decel or frame.acceptance_policy.display_only) and - frame.pedal_override is not PedalOverride.OFF and ego.status is sd.DomainStatus.VALID and pair.status is sd.DomainStatus.VALID): - base = replace(base, pedal_target_mps=previous) + ego.status is sd.DomainStatus.VALID and pair.status is sd.DomainStatus.VALID): + base = replace(base, pedal_target_mps=previous if frame.pedal_override is not PedalOverride.OFF else 0., + wheel_override_metric=wheel_metric) return Result(base, selected, acceptance, domain, status='inactive_or_unavailable') context = domain.context assert context is not None + selected_cluster = context.selected_raw_mps + context.selected_delta_mps + if frame.wheel_override_enabled and frame.is_metric is not None: + since = 0 if clear_wheel else state.wheel_override_since_ns + pending = acceptance.state.pending + ready = (host_long and not frame.pedal_pressed and not frame.wheel_override_blocked and + (pending is None or pending.automatic)) + adjusted_limit = context.effective_cluster_mps + change = frame.classified_cruise.change if frame.classified_cruise is not None else None + # A tap belongs to the eligibility interval at button-down, even if SET changes later. + if (wheel_metric is None and ready and since > 0 and frame.wheel_override_since_ns == since and change is not None and + change.disposition is arb.Disposition.DRIVER_INTENT and change.previous_mps is not None and + not sd.set_above_limit(change.previous_mps, adjusted_limit, frame.is_metric)): + wheel_metric = frame.is_metric + # A display-unit edit can round two different speeds to the same number. + if wheel_metric is not None and not sd.set_above_limit(selected_cluster, adjusted_limit, wheel_metric): + wheel_metric = None + base = replace(base, wheel_override_metric=wheel_metric, + wheel_override_since_ns=(since or frame.now_ns) if ready else 0) + if wheel_metric is not None: + coordinate = sd.PlannerCoordinate(None, 'wheel', selected_cluster, context.ego_delta_mps) + return Result(base, selected, acceptance, domain, coordinate, status=acceptance.basis) + pedal_target = 0. if frame.pedal_override is not PedalOverride.OFF: ego_cluster = context.ego_raw_mps + context.ego_delta_mps gas_above_limit = not pedal_blocked and frame.authority.accelerator_override and ego_cluster > context.effective_cluster_mps if gas_above_limit or previous > context.effective_cluster_mps: - selected_cluster = context.selected_raw_mps + context.selected_delta_mps pedal_target = (selected_cluster if frame.pedal_override is PedalOverride.SET_SPEED else min(selected_cluster, max(previous, ego_cluster if gas_above_limit else 0.))) if pedal_target <= context.effective_cluster_mps: diff --git a/openpilot/starpilot/speed_limits/physical_actions.py b/openpilot/starpilot/speed_limits/physical_actions.py index 3d952ab5ed..948d9e226e 100644 --- a/openpilot/starpilot/speed_limits/physical_actions.py +++ b/openpilot/starpilot/speed_limits/physical_actions.py @@ -3,9 +3,10 @@ import math from openpilot.cereal.services import SERVICE_LIST -from openpilot.selfdrive.car.cruise import SlcPendingConfirmation, V_CRUISE_MIN, V_CRUISE_MAX +from openpilot.selfdrive.car.cruise import SlcPendingConfirmation, SlcWheelContext, V_CRUISE_MIN, V_CRUISE_MAX from openpilot.starpilot.speed_limits.acceptance import CONFIRMATION_NS from openpilot.starpilot.speed_limits.selection import Source +from openpilot.starpilot.speed_limits.speed_domain import set_above_limit STATE_MAX_AGE_NS = int(2e9 / SERVICE_LIST['slcState'].frequency) @@ -67,6 +68,17 @@ def active_ceiling(sm, now_ns: int, *, after_ns: int = 0) -> float | None: return cap if finite_speed(cap) and cap <= V_CRUISE_MAX / 3.6 + 1e-5 else None +def wheel_override_context(sm, now_ns: int, *, selected_kph: float, is_metric: bool, + after_ns: int = 0) -> SlcWheelContext | None: + state = _state(sm, now_ns) + if (state is None or int(state.frameMonoTime) <= after_ns or not state.hasAcceptedAdjustedLimit or + not finite_speed(state.acceptedAdjustedLimit) or not V_CRUISE_MIN <= selected_kph <= V_CRUISE_MAX or + set_above_limit(selected_kph / 3.6, float(state.acceptedAdjustedLimit), is_metric)): + return None + since = int(state.wheelOverrideSince) + return (str(state.sessionId), since) if 0 < since <= int(state.frameMonoTime) else None + + def engagement_state(sm, observation, producer_session: str, now_ns: int, *, after_ns: int = 0): state = _current_state(sm, now_ns) if (state is None or int(state.frameMonoTime) <= after_ns or int(state.presentationId) <= 0 or diff --git a/openpilot/starpilot/speed_limits/planner_host.py b/openpilot/starpilot/speed_limits/planner_host.py index db488c4e2f..03cd88159d 100644 --- a/openpilot/starpilot/speed_limits/planner_host.py +++ b/openpilot/starpilot/speed_limits/planner_host.py @@ -69,6 +69,8 @@ class SavedSlcLifecycle: if (settings.advance_higher_seconds != self.runtime.settings.advance_higher_seconds or settings.advance_lower_seconds != self.runtime.settings.advance_lower_seconds): self.runtime.update_advance(settings.advance_higher_seconds, settings.advance_lower_seconds) + if settings.wheel_override != self.runtime.settings.wheel_override: + self.runtime.state = replace(self.runtime.state, wheel_override_metric=None, wheel_override_since_ns=0) self.runtime.settings = settings self.runtime.vision_enabled = vision self.runtime.vision_control_qualified = vision and configuration[3] @@ -90,7 +92,7 @@ class SavedSlcLifecycle: pending_cruise.clear() return self.runtime, True - def step(self, sm, now_ns, pending_actions, pending_cruise, *, drive_changed): + def step(self, sm, now_ns, pending_actions, pending_cruise, *, drive_changed, wheel_override_blocked=False): runtime = self.runtime if runtime is None: return None @@ -105,7 +107,7 @@ class SavedSlcLifecycle: pending_actions[0][0] <= now_ns else None) previous_accepted = runtime.history_limit output = runtime.step(sm, self.cp, now_ns=now_ns, request=request, cruise_event=cruise_event, - confirmation_grace_ns=ACTION_MAX_AGE_NS) + confirmation_grace_ns=ACTION_MAX_AGE_NS, wheel_override_blocked=wheel_override_blocked) accepted = runtime.history_limit if accepted != previous_accepted: self.params.put("SLCQualifiedHistory", json.loads(history.encode(accepted, time.time_ns())) if accepted is not None else {}) @@ -125,7 +127,7 @@ def cruise_event_from_message(event): return CruiseEvent(int(cruise.eventId), int(cruise.observedMonoTime), float(cruise.previousMps), float(cruise.selectedMps), str(cruise.button), str(cruise.producerSessionId), int(event.logMonoTime), str(cruise.kind), str(cruise.sessionId), int(cruise.decisionId), - int(cruise.presentationId), int(cruise.commandId)) + int(cruise.presentationId), int(cruise.commandId), int(cruise.wheelOverrideSince), bool(cruise.longPress)) def action_from_message(event): diff --git a/openpilot/starpilot/speed_limits/runtime.py b/openpilot/starpilot/speed_limits/runtime.py index 9bb67f594a..2a46b5ea23 100644 --- a/openpilot/starpilot/speed_limits/runtime.py +++ b/openpilot/starpilot/speed_limits/runtime.py @@ -6,6 +6,7 @@ from dataclasses import dataclass, replace import openpilot.cereal.messaging as messaging from openpilot.cereal.services import SERVICE_LIST +from openpilot.selfdrive.car.cruise import V_CRUISE_MIN, V_CRUISE_MAX from openpilot.starpilot.aol.runtime import current_axis from openpilot.starpilot.speed_limits import acceptance as acc from openpilot.starpilot.speed_limits import action_arbitration as arb @@ -100,6 +101,8 @@ class CruiseEvent: decision_id: int = 0 presentation_id: int = 0 command_id: int = 0 + wheel_override_since_ns: int = 0 + long_press: bool = False @dataclass(frozen=True) @@ -175,7 +178,7 @@ class Runtime: acceptance = replace(self.state.acceptance, observation=acc.Observation(acc.ObservationKind.UNKNOWN), pending=None, presentation=None, timer_running=False) self.current_presentation = None - self.state = replace(self.state, acceptance=acceptance) + self.state = replace(self.state, acceptance=acceptance, wheel_override_since_ns=0) self._ui_receipt = None def update_advance(self, higher_seconds: int, lower_seconds: int) -> None: @@ -327,7 +330,7 @@ class Runtime: return values def _frame(self, sm, CP, now_ns: int, now_boot_ns: int | None, action: acc.DriverAction | None, adopt: acc.AdoptRequest | None, - classified: arb.Result | None) -> comp.Frame: + classified: arb.Result | None, *, wheel_override_blocked: bool) -> comp.Frame: car_fresh = _fresh(sm, "carState", now_ns) and not sm["carState"].canTimeout controls_fresh = _fresh(sm, "controlsState", now_ns) selfdrive_fresh = _fresh(sm, "selfdriveState", now_ns) @@ -353,11 +356,16 @@ class Runtime: host = comp.HostState(bool(CP.openpilotLongitudinalControl), host_long, bool(selfdrive.enabled and selfdrive_fresh), bool(controls.forceDecel) if controls_fresh else True, selected, selected_cluster) observations = self._observations(sm, CP, now_ns, now_boot_ns) + wheel_enabled = (self.settings.wheel_override and not CP.pcmCruise and car_fresh and car_state.canValid and + V_CRUISE_MIN <= selected <= V_CRUISE_MAX and selected_cluster == selected and + not car_state.brakePressed) + wheel_blocked = wheel_override_blocked or car_state.standstill or car_state.cruiseState.standstill frame = comp.Frame(now_ns, observations, self.settings.selection, authority, self.settings.acceptance, self.settings.offsets, sd.DomainStatus.VALID if car_fresh and selected_cluster is not None else sd.DomainStatus.STALE, ego, bool(car_state.gasPressed) if car_fresh else False, host, action=action, adopt=adopt, - classified_cruise=classified, is_metric=self.is_metric, pedal_override=self.settings.pedal_override) + classified_cruise=classified, is_metric=self.is_metric, pedal_override=self.settings.pedal_override, + wheel_override_enabled=wheel_enabled, wheel_override_blocked=wheel_blocked) maps_selected = self._maps_selected encounter = self.advance.encounter if not maps_selected and encounter is None: @@ -432,7 +440,7 @@ class Runtime: return acc.DriverAction(self.session_id, action_id, request.decision_id, kind, request.observed_ns), None, action_id def step(self, sm, CP, *, now_ns: int, now_boot_ns: int | None = None, request: Action | None = None, - cruise_event: CruiseEvent | None = None, confirmation_grace_ns: int = 0) -> Output: + cruise_event: CruiseEvent | None = None, confirmation_grace_ns: int = 0, wheel_override_blocked: bool = False) -> Output: classified = self._cruise(cruise_event, sm, now_ns) if request is None else None action, adopt, action_id = self._action(request, now_ns) if cruise_event is not None and request is None: @@ -444,8 +452,16 @@ class Runtime: adopt = replace(adopt, candidate=shown.candidate) self.state = replace(self.state, acceptance=replace(self.state.acceptance, presentation=shown, pending=None, timer_running=False, observation=acc.Observation(acc.ObservationKind.VALID, shown.candidate))) - frame = replace(self._frame(sm, CP, now_ns, now_boot_ns, action, adopt, classified), - confirmation_grace_ns=confirmation_grace_ns) + wheel_since = 0 + if (classified is not None and cruise_event is not None and cruise_event.button == 'accel' and + not cruise_event.long_press and cruise_event.selected_mps > cruise_event.previous_mps and + abs(float(sm['carState'].vCruise) / 3.6 - cruise_event.selected_mps) <= 0.0001 and + 0 < cruise_event.wheel_override_since_ns <= cruise_event.observed_ns and cruise_event.session_id == self.session_id and + _fresh(sm, 'slcDashboardObservation', now_ns) and + cruise_event.producer_session == str(sm['slcDashboardObservation'].producerSessionId)): + wheel_since = cruise_event.wheel_override_since_ns + frame = self._frame(sm, CP, now_ns, now_boot_ns, action, adopt, classified, wheel_override_blocked=wheel_override_blocked) + frame = replace(frame, confirmation_grace_ns=confirmation_grace_ns, wheel_override_since_ns=wheel_since) result = comp.step(self.state, frame) self.state = result.state self.advance.observe(result) @@ -541,8 +557,11 @@ class Runtime: state.decisionId = pending.decision_id state.automaticAdoptionPending = pending.automatic state.pendingSinceMonoTime = now_ns - pending.active_elapsed_ns - state.driverOverrideActive = result.ceiling is not None and result.state.pedal_target_mps > 0. - state.overrideBasis = ('pedal' if frame.pedal_pressed else 'persistent') if state.driverOverrideActive else 'none' + wheel_active = result.coordinate is not None and result.coordinate.basis == 'wheel' + state.wheelOverrideSince = result.state.wheel_override_since_ns if not result.errors else 0 + state.driverOverrideActive = wheel_active or result.ceiling is not None and result.state.pedal_target_mps > 0. + state.overrideBasis = 'wheel' if wheel_active else ( + ('pedal' if frame.pedal_pressed else 'persistent') if state.driverOverrideActive else 'none') shown = result.state.acceptance.presentation ego = frame.ego_pair ego_delta = (max(0.0, ego.cluster_mps - ego.raw_mps) diff --git a/openpilot/starpilot/speed_limits/runtime_settings.py b/openpilot/starpilot/speed_limits/runtime_settings.py index ecf27aa213..baeb07bc19 100644 --- a/openpilot/starpilot/speed_limits/runtime_settings.py +++ b/openpilot/starpilot/speed_limits/runtime_settings.py @@ -22,8 +22,9 @@ ADVANCE_SECONDS = range(11) ADVANCE_BYTES = tuple(str(seconds).encode() for seconds in ADVANCE_SECONDS) PEDAL_OVERRIDE_KEY = 'SLCOverrideMode' -PEDAL_OVERRIDE_CHOICES = ('Off', 'Hold Reached Speed', 'Use Set Speed') +PEDAL_OVERRIDE_CHOICES = ('Resume the adjusted limit', 'Hold the speed you reached', 'Use your SET speed') PEDAL_OVERRIDE_BYTES = (b'0', b'1', b'2') +WHEEL_OVERRIDE_KEY = 'SLCWheelOverride' class PedalOverride(IntEnum): @@ -45,6 +46,7 @@ class Settings: advance_higher_seconds: int = 0 advance_lower_seconds: int = 0 pedal_override: PedalOverride = PedalOverride.OFF + wheel_override: bool = False def _bool(values: Mapping[str, object], key: str, default: bool = False) -> bool: @@ -95,7 +97,7 @@ def parse(values: Mapping[str, object]) -> Settings: pedal_override = PedalOverride.OFF.value return Settings(control, display, sel.SelectionPolicy(mode, slots, False), policy, offsets, requested=control, advance_higher_seconds=higher, advance_lower_seconds=lower, - pedal_override=PedalOverride(pedal_override)) + pedal_override=PedalOverride(pedal_override), wheel_override=values.get(WHEEL_OVERRIDE_KEY) is True) except (ValueError, OverflowError) as error: return _disabled(str(error)) @@ -105,7 +107,7 @@ SAVED_KEYS = ("SpeedLimitController", "ShowSpeedLimits", "IsMetric", *SOURCE_PRI def read_params(params) -> Settings: - values = {} + values = {WHEEL_OVERRIDE_KEY: read_saved(params, WHEEL_OVERRIDE_KEY, 1) == (b'1', True)} raw_override, readable_override = read_saved(params, PEDAL_OVERRIDE_KEY, 1) if raw_override is not None or not readable_override: values[PEDAL_OVERRIDE_KEY] = int(raw_override) if readable_override and raw_override in PEDAL_OVERRIDE_BYTES else 0 diff --git a/openpilot/starpilot/speed_limits/speed_domain.py b/openpilot/starpilot/speed_limits/speed_domain.py index e23e893ede..3ec866adf8 100644 --- a/openpilot/starpilot/speed_limits/speed_domain.py +++ b/openpilot/starpilot/speed_limits/speed_domain.py @@ -5,6 +5,7 @@ import struct from dataclasses import dataclass from enum import Enum +from openpilot.common.constants import CV from openpilot.selfdrive.car.cruise import V_CRUISE_MIN, V_CRUISE_MAX, slc_target_kph from openpilot.starpilot.speed_limits import acceptance as acc @@ -165,6 +166,15 @@ def set_speed_matches(target_mps: float, raw_kph: float, cluster_kph: float | No math.isclose(target_kph, cluster_kph, rel_tol=0, abs_tol=0.001)) +def set_above_limit(selected_mps: float, adjusted_limit_mps: float, is_metric: bool) -> bool: + # Use the sign's wire precision and restore software SET's 0.1 km/h after transport. + limit_mps = struct.unpack('f', struct.pack('f', adjusted_limit_mps))[0] + selected_kph = round(selected_mps * CV.MS_TO_KPH, 1) + if is_metric: + return round(selected_kph) > round(limit_mps * CV.MS_TO_KPH) + return round(selected_kph * CV.KPH_TO_MPH) > round(limit_mps * CV.MS_TO_MPH) + + def resolve(decision: acc.Decision, schedule: OffsetSchedule, selected: SpeedPair, ego: SpeedPair) -> DomainResolution: """Bind an explicit offset and contemporaneous speed pairs to an acceptance output.""" if (not isinstance(decision, acc.Decision) or not isinstance(decision.state, acc.State) or decision.errors or diff --git a/openpilot/starpilot/speed_limits/tests/test_core_flow.py b/openpilot/starpilot/speed_limits/tests/test_core_flow.py index 40d94f1c58..b863ffe925 100644 --- a/openpilot/starpilot/speed_limits/tests/test_core_flow.py +++ b/openpilot/starpilot/speed_limits/tests/test_core_flow.py @@ -71,6 +71,147 @@ def test_cruise_adjustments_change_only_the_driver_ceiling(drive, selected): assert sm['carState'].vCruise == selected +def wheel_crossing(drive, *, metric=True, enabled=True, previous_kph=90., selected_kph=91., offset=0.): + sm, cp = drive + cp.pcmCruise = False + car_state = sm['carState'] + car_state.canValid = True + car_state.vCruise = car_state.vCruiseCluster = previous_kph + settings = replace(controller(offset).settings, wheel_override=enabled) + runtime = Runtime(settings, is_metric=metric) + first = tick(runtime, sm, cp, 0).message.slcState + car_state.vCruise = car_state.vCruiseCluster = selected_kph + event = CruiseEvent(1, START + 50_000_000, previous_kph / 3.6, selected_kph / 3.6, 'accel', + 'card-producer', START + 50_000_000, session_id=runtime.session_id, + wheel_override_since_ns=first.wheelOverrideSince) + return runtime, event + + +@pytest.mark.parametrize('metric,previous_kph,selected_kph,limit,offset', [ + (True, 90., 91., 25., 0.), + (True, 54.5, 55.5, 54. / 3.6, 0.), + (False, 89., 90.6, 55. * CV.MPH_TO_MS, 0.), + (False, 86.9, 88.5, 55. * CV.MPH_TO_MS, -.5 * CV.MPH_TO_MS), +]) +def test_wheel_tap_crosses_displayed_limit_and_uses_native_set(drive, metric, previous_kph, selected_kph, limit, offset): + from struct import pack, unpack + sm, cp = drive + sm['slcDashboardObservation'].speedMps = limit + sm['carState'].vEgoCluster += 1.0 + runtime, event = wheel_crossing(drive, metric=metric, previous_kph=previous_kph, selected_kph=selected_kph, offset=offset) + event = replace(event, previous_mps=unpack('f', pack('f', event.previous_mps))[0]) + assert event.wheel_override_since_ns == START + # Card publishes carState before the event. The new SET can be sampled first. + assert tick(runtime, sm, cp, 1).result.ceiling is not None + crossed = tick(runtime, sm, cp, 2, cruise_event=event) + status = crossed.message.slcState + assert crossed.result.ceiling is None and status.driverOverrideActive and status.overrideBasis == 'wheel' + assert status.effectiveClusterTarget == pytest.approx(selected_kph / 3.6) + assert not status.isLimitingMaxSet + planner = LongitudinalPlanner(cp) + planner.update(sm, cruise_ceiling=crossed.result.ceiling) + assert planner.last_cruise_ceiling_status == 'absent' + + sm['carState'].vCruise = sm['carState'].vCruiseCluster = previous_kph + lowered = tick(runtime, sm, cp, 3) + assert lowered.result.ceiling is not None and not lowered.message.slcState.driverOverrideActive + sm['carState'].vCruise = sm['carState'].vCruiseCluster = selected_kph + assert not tick(runtime, sm, cp, 4, cruise_event=event).message.slcState.driverOverrideActive + + +@pytest.mark.parametrize('scenario', ['off', 'already above', 'controller', 'hold', 'foreign producer', 'foreign session', + 'different SET', 'stop or curve', 'confirmation', 'new limit', 'disengaged', 'pcm', + 'standstill', 'units changed']) +def test_wheel_override_requires_current_unconsumed_physical_crossing(drive, scenario): + sm, cp = drive + prior, selected = (100., 101.) if scenario == 'already above' else (90., 91.) + runtime, event = wheel_crossing(drive, enabled=scenario != 'off', previous_kph=prior, selected_kph=selected) + changes = { + 'controller': {'wheel_override_since_ns': 0}, + 'hold': {'long_press': True}, + 'foreign producer': {'producer_session': 'old-card'}, + 'foreign session': {'session_id': 'old-slc'}, + 'different SET': {'selected_mps': event.selected_mps - .05}, + } + event = replace(event, **changes.get(scenario, {})) + if scenario in ('confirmation', 'new limit'): + sm['slcDashboardObservation'].speedMps = 23. + if scenario == 'confirmation': + runtime.settings = replace(runtime.settings, acceptance=acc.Policy(confirm_lower=True)) + if scenario == 'disengaged': + sm['carControl'].enabled = sm['carControl'].longActive = sm['selfdriveState'].enabled = False + if scenario == 'pcm': + cp.pcmCruise = True + if scenario == 'standstill': + sm['carState'].standstill = True + if scenario == 'units changed': + runtime.update_offsets(runtime.settings.offsets, is_metric=False) + output = tick(runtime, sm, cp, 1, cruise_event=event, wheel_override_blocked=scenario == 'stop or curve') + assert not output.message.slcState.driverOverrideActive + if scenario in ('stop or curve', 'standstill'): + assert output.message.slcState.wheelOverrideSince == 0 + sm['carState'].standstill = False + assert not tick(runtime, sm, cp, 2, cruise_event=replace(event, event_id=2)).message.slcState.driverOverrideActive + + +@pytest.mark.parametrize('limit_kph', [51., 90.]) +def test_wheel_override_survives_units_change_until_set_returns_to_limit(drive, limit_kph): + sm, cp = drive + sm['slcDashboardObservation'].speedMps = limit_kph / 3.6 + runtime, event = wheel_crossing(drive, previous_kph=limit_kph, selected_kph=limit_kph + 1.) + assert tick(runtime, sm, cp, 1, cruise_event=event).message.slcState.overrideBasis == 'wheel' + + # At 51 km/h both speeds round to 32 mph, even though SET remains higher. + runtime.update_offsets(runtime.settings.offsets, is_metric=False) + changed = tick(runtime, sm, cp, 2) + assert changed.result.ceiling is None + assert changed.message.slcState.overrideBasis == 'wheel' + assert changed.message.slcState.wheelOverrideSince != event.wheel_override_since_ns + + sm['carState'].vCruise = sm['carState'].vCruiseCluster = limit_kph + returned = tick(runtime, sm, cp, 3) + assert returned.result.ceiling.speed_mps == pytest.approx(limit_kph / 3.6) + assert not returned.message.slcState.driverOverrideActive + + +@pytest.mark.parametrize('cruise_standstill', [False, True]) +def test_wheel_override_survives_engaged_stop(drive, cruise_standstill): + sm, cp = drive + runtime, event = wheel_crossing(drive) + assert tick(runtime, sm, cp, 1, cruise_event=event).message.slcState.overrideBasis == 'wheel' + + stopped = sm['carState'].cruiseState if cruise_standstill else sm['carState'] + stopped.standstill = True + sm['carState'].vEgo = sm['carState'].vEgoCluster = 0. + sm['controlsState'].longControlState = 'stopping' + held = tick(runtime, sm, cp, 2) + assert held.message.slcState.overrideBasis == 'wheel' + assert held.message.slcState.wheelOverrideSince == 0 + + stopped.standstill = False + sm['controlsState'].longControlState = 'pid' + assert tick(runtime, sm, cp, 3).message.slcState.overrideBasis == 'wheel' + + +def test_wheel_override_survives_gas_handoff_without_latching_pedal_speed(drive): + sm, cp = drive + runtime, event = wheel_crossing(drive) + assert tick(runtime, sm, cp, 1, cruise_event=event).message.slcState.overrideBasis == 'wheel' + sm['carState'].vEgo = sm['carState'].vEgoCluster = 91. / 3.6 + sm['carState'].gasPressed = True + sm['carControl'].longActive = False + sm['controlsState'].longControlState = 'off' + assert tick(runtime, sm, cp, 2).result.state.pedal_target_mps == 0. + sm['carState'].gasPressed = False + assert tick(runtime, sm, cp, 3).result.state.wheel_override_metric is True + sm['carControl'].longActive = True + sm['controlsState'].longControlState = 'pid' + assert tick(runtime, sm, cp, 4).message.slcState.overrideBasis == 'wheel' + sm['slcDashboardObservation'].speedMps = 23. + accepted = tick(runtime, sm, cp, 5) + assert accepted.result.ceiling.speed_mps == 23. and not accepted.message.slcState.driverOverrideActive + + @pytest.mark.parametrize('missing', ['absent', 'unknown', 'stale']) def test_source_gap_uses_the_previous_limit_and_current_offset(drive, missing): sm, cp = drive diff --git a/openpilot/starpilot/speed_limits/tests/test_runtime_replay.py b/openpilot/starpilot/speed_limits/tests/test_runtime_replay.py index afdce951f7..ac2c1725d3 100644 --- a/openpilot/starpilot/speed_limits/tests/test_runtime_replay.py +++ b/openpilot/starpilot/speed_limits/tests/test_runtime_replay.py @@ -524,6 +524,68 @@ class RuntimeReplayTests(unittest.TestCase): self.assertEqual(result.message.slcState.actionStatus, outcome) + def test_physical_wheel_override_receipt_through_card_and_planner_queue(self): + from openpilot.cereal import messaging + from openpilot.selfdrive.car import card + from openpilot.selfdrive.controls.plannerd import current_cruise_event, queue_cruise_event, wheel_override_blocked + from openpilot.starpilot.curve_speed.host import CurveHost + + self.cp.pcmCruise = False + self.sm['carState'].canValid = True + self.sm['carState'].vCruise = self.sm['carState'].vCruiseCluster = 90. + settings = parse({'SpeedLimitController': True, 'SLCPriority1': 'Dashboard', 'SLCWheelOverride': True}) + for curve_claims, curve_backlog in ((False, False), (True, False), (False, True)): + with self.subTest(curve_claims=curve_claims, curve_backlog=curve_backlog): + start = 2_000_000_000 + self.sm.advance(start) + self.sm['carState'].vCruise = self.sm['carState'].vCruiseCluster = 90. + runtime = Runtime(settings, is_metric=True) + state = runtime.step(self.sm, self.cp, now_ns=start).message.slcState + fake, current_cs = self._card_with_slc_state(state, None, start) + fake.slc_configuration = fake.slc_requested_configuration = (True, settings) + fake.v_cruise_helper.v_cruise_kph = fake.v_cruise_helper.v_cruise_cluster_kph = 90. + fake.is_metric = True + fake.curve_replay = True + fake.car_params_published = True + fake.sm.frame = 1 + fake.sm.all_checks = lambda services: True + fake.last_actuators_output = car.CarControl.Actuators() + fake.slc_cruise_event_id = 0 + fake.radar_publish = mock.Mock() + fake.rk = SimpleNamespace(remaining=0.0) + fake.aol_replay = False + clock = [start + 10_000_000] + + def can_batch(*args, clock=clock, **kwargs): + return [messaging.new_message('can', size=0, logMonoTime=clock[0]).to_bytes()] + + with (mock.patch.object(card, 'REPLAY', True), + mock.patch.object(card.messaging, 'drain_sock_raw', side_effect=can_batch)): + for pressed in (True, False): + current_cs[0] = car.CarState(canValid=True, cruiseState={'available': True}, + buttonEvents=[car.CarState.ButtonEvent(type='accelCruise', pressed=pressed)]) + cs, radar = fake.state_update() + fake.state_publish(cs, radar) + clock[0] += 10_000_000 + fake.sm.logMonoTime['carControl'] = clock[0] + slc_events, curve_events = deque(), deque() + for call in fake.pm.send.call_args_list: + name, message = call.args + if name == 'slcCruiseEvent': + queue_cruise_event(messaging.log_from_bytes(message.to_bytes()), slc_events, curve_events) + self.assertEqual(len(curve_events), 1) + self.assertEqual(len(slc_events), 1) + self.assertEqual(cs.vCruise, 91.) + self.sm['carState'].vCruise = self.sm['carState'].vCruiseCluster = cs.vCruise + self.sm.advance(clock[0]) + event = current_cruise_event(slc_events, clock[0]) + self.assertEqual(event.wheel_override_since_ns, start) + curve = CurveHost() + curve.runtime.was_controlling = curve_claims + output = runtime.step(self.sm, self.cp, now_ns=clock[0], cruise_event=event, + wheel_override_blocked=wheel_override_blocked(None, curve, curve_backlog=curve_backlog)) + self.assertEqual(output.message.slcState.driverOverrideActive, not (curve_claims or curve_backlog)) + def test_malformed_saved_priorities_disable_runtime_without_crashing(self): for key in ('SLCPriority1', 'SLCPriority2'): for malformed in ([], {}, 1, None): diff --git a/openpilot/starpilot/speed_limits/tests/test_runtime_settings.py b/openpilot/starpilot/speed_limits/tests/test_runtime_settings.py index e14778b91f..33bf7b3d8d 100644 --- a/openpilot/starpilot/speed_limits/tests/test_runtime_settings.py +++ b/openpilot/starpilot/speed_limits/tests/test_runtime_settings.py @@ -151,6 +151,19 @@ class RuntimeSettingsTests(unittest.TestCase): self.assertFalse(settings.enabled) self.assertTrue(settings.errors) + def test_wheel_override_is_opt_in_and_bad_bytes_keep_slc_enabled(self): + with tempfile.TemporaryDirectory() as directory: + params = Params(directory) + params.put_bool('SpeedLimitController', True, block=True) + self.assertFalse(params.get_default_value('SLCWheelOverride')) + self.assertFalse(read_params(params).wheel_override) + params.put_bool('SLCWheelOverride', True, block=True) + self.assertTrue(read_params(params).wheel_override) + Path(params.get_param_path('SLCWheelOverride')).write_bytes(b'bad') + settings = read_params(params) + self.assertTrue(settings.enabled) + self.assertFalse(settings.wheel_override) + def test_retired_offsets_are_unregistered_and_ignored_with_zero_defaults(self): for metric in (False, True): with self.subTest(metric=metric), tempfile.TemporaryDirectory() as directory: diff --git a/openpilot/starpilot/ui/feature_settings_owner.py b/openpilot/starpilot/ui/feature_settings_owner.py index ddbbadb040..30d981ef06 100644 --- a/openpilot/starpilot/ui/feature_settings_owner.py +++ b/openpilot/starpilot/ui/feature_settings_owner.py @@ -39,7 +39,7 @@ from openpilot.starpilot.ui.feature_settings_state import ( from openpilot.starpilot.speed_limits.offset_document import MPH_TO_MPS from openpilot.starpilot.speed_limits.runtime_settings import ( ADVANCE_BYTES, ADVANCE_KEYS, CONFIRMATION_CHOICES, - PEDAL_OVERRIDE_BYTES, PEDAL_OVERRIDE_CHOICES, PEDAL_OVERRIDE_KEY, SOURCE_PRIORITY_KEYS, + PEDAL_OVERRIDE_BYTES, PEDAL_OVERRIDE_CHOICES, PEDAL_OVERRIDE_KEY, SOURCE_PRIORITY_KEYS, WHEEL_OVERRIDE_KEY, PedalOverride, ) from openpilot.starpilot.aol.vehicle import policy_for as aol_policy_for from openpilot.starpilot.feature_capabilities import longitudinal_supported as feature_longitudinal_supported @@ -92,6 +92,7 @@ BOOL_DEFAULTS = { "RemapCancelToDistance": False, "LongPitch": True, "DisableOpenpilotLongitudinal": False, "SpeedLimitController": False, "ShowSpeedLimits": True, "SLCOnEngage": False, + WHEEL_OVERRIDE_KEY: False, "PauseLateralOnSignal": False, "LaneCentering": True, "LaneCenteringPauseOnSignal": True, "CustomPersonalities": False, PLANNER_SELECTION_KEY: True, @@ -1129,6 +1130,9 @@ class FeatureSettingsOwner: rows.append(self._bool_row("ShowSpeedLimits", "Show speed limit signs", self.authority("preferences"), reason="Display signs independently of Speed Limit Controller; does not change cruise speed")) rows.extend(policy_rows(self._value, self._readable, allowed=allowed)) + if cp is not None and not cp.pcmCruise: + rows.append(self._bool_row(WHEEL_OVERRIDE_KEY, "Override speed limit with +", allowed and control == "1", + reason="Press + to raise SET past the adjusted limit and use your new SET speed.")) source_capability = self._source_capability() vision_dev, maps_available = source_capability source_allowed = configurable and self.authority("preferences") @@ -1633,7 +1637,7 @@ class FeatureSettingsOwner: encoded = str(SLC_ADVANCE_CHOICES.index(request.value)) elif key == PEDAL_OVERRIDE_KEY: if request.value not in PEDAL_OVERRIDE_CHOICES or ( - request.value != 'Off' and request.expected not in (None, *PEDAL_OVERRIDE_BYTES)): + request.value != PEDAL_OVERRIDE_CHOICES[PedalOverride.OFF] and request.expected not in (None, *PEDAL_OVERRIDE_BYTES)): return False encoded = PEDAL_OVERRIDE_CHOICES.index(request.value) elif key in BOOL_DEFAULTS: @@ -1642,7 +1646,7 @@ class FeatureSettingsOwner: return False if key == "SpeedLimitController" and request.value == "On" and not self.slc_offsets.ready_to_enable(): return False - if key == "SLCOnEngage": + if key in ("SLCOnEngage", WHEEL_OVERRIDE_KEY): cp = self.vehicle_params() if cp is None or cp.pcmCruise or not self.longitudinal_available() or ( request.value == "On" and self._raw("SpeedLimitController") != b"1"): diff --git a/openpilot/starpilot/ui/slc_policy_feature.py b/openpilot/starpilot/ui/slc_policy_feature.py index 4e02c22ca6..c1dceec889 100644 --- a/openpilot/starpilot/ui/slc_policy_feature.py +++ b/openpilot/starpilot/ui/slc_policy_feature.py @@ -26,12 +26,13 @@ def policy_rows(read_value, readable, *, allowed): reason=f'Offer or adopt {direction} mapped limits this many seconds early at the current speed' if valid_advance else 'Invalid saved advance time; restore Off')) _, raw_override, readable_override = read_value(PEDAL_OVERRIDE_KEY) + resume_limit = PEDAL_OVERRIDE_CHOICES[PedalOverride.OFF] default_override = PEDAL_OVERRIDE_CHOICES[PedalOverride.SPEED_REACHED] valid_override = readable_override and (raw_override is None or raw_override in PEDAL_OVERRIDE_BYTES) - rows.append(FeatureRow(PEDAL_OVERRIDE_KEY, 'Gas pedal override', + rows.append(FeatureRow(PEDAL_OVERRIDE_KEY, 'After an accelerator override', default_override if raw_override is None else PEDAL_OVERRIDE_CHOICES[int(raw_override)] if valid_override else 'Invalid', - raw_override, PEDAL_OVERRIDE_CHOICES if valid_override else ('Off',), + raw_override, PEDAL_OVERRIDE_CHOICES if valid_override else (resume_limit,), available=allowed and readable(PEDAL_OVERRIDE_KEY), default_value=default_override, - repair_value='' if valid_override else 'Off', - reason='Choose what cruise holds after you press the gas past the adjusted speed limit')) + repair_value='' if valid_override else resume_limit, + reason='Cruise uses this target when you lift off the accelerator. Held speed stays at or below SET.')) return rows diff --git a/openpilot/starpilot/ui/tests/test_feature_settings_owner.py b/openpilot/starpilot/ui/tests/test_feature_settings_owner.py index d2ee4a2526..5f8c7b3485 100644 --- a/openpilot/starpilot/ui/tests/test_feature_settings_owner.py +++ b/openpilot/starpilot/ui/tests/test_feature_settings_owner.py @@ -586,15 +586,41 @@ class FeatureSettingsOwnerTests(unittest.TestCase): def test_gas_pedal_override_uses_the_shared_slc_settings_owner(self): row = self._row('slc', 'SLCOverrideMode') - self.assertEqual(row.value, 'Hold Reached Speed') - self.assertEqual(row.choices, ('Off', 'Hold Reached Speed', 'Use Set Speed')) - request = required_change(row) + choices = ('Resume the adjusted limit', 'Hold the speed you reached', 'Use your SET speed') + self.assertEqual((row.label, row.value), ('After an accelerator override', choices[1])) + self.assertEqual(row.choices, choices) self.allowed = False - self.assertFalse(self.owner.apply(request)) + self.assertFalse(self.owner.apply(required_change(row))) self.allowed = True + for mode in (2, 0, 1): + self.assertTrue(self.owner.apply(required_change(row))) + self.assertEqual(self.params.get('SLCOverrideMode'), mode) + row = self._row('slc', 'SLCOverrideMode') + self.assertEqual(row.value, choices[mode]) + + Path(self.params.get_param_path('SLCOverrideMode')).write_bytes(b'9') + repair = required_change(self._row('slc', 'SLCOverrideMode')) + self.assertEqual(repair.value, choices[0]) + self.assertFalse(self.owner.apply(replace(repair, value=choices[2]))) + self.assertTrue(self.owner.apply(repair)) + self.assertEqual(self.params.get('SLCOverrideMode'), 0) + + def test_wheel_override_is_off_and_only_configurable_for_software_set(self): + from opendbc.car.structs import car + cp = car.CarParams(carFingerprint=self.fingerprint, openpilotLongitudinalControl=True, pcmCruise=False) + self.owner.vehicle_params = lambda: cp + self.params.put_bool('SpeedLimitController', True, block=True) + row = self._row('slc', 'SLCWheelOverride') + self.assertEqual((row.label, row.value, row.default_value), ('Override speed limit with +', 'Off', 'Off')) + request = required_change(row) + cp.pcmCruise = True + self.assertFalse(self.owner.apply(request)) + cp.pcmCruise = False + self.params.put_bool('SpeedLimitController', False, block=True) + self.assertFalse(self.owner.apply(request)) + self.params.put_bool('SpeedLimitController', True, block=True) self.assertTrue(self.owner.apply(request)) - self.assertEqual(self.params.get('SLCOverrideMode'), 2) - self.assertEqual(self._row('slc', 'SLCOverrideMode').value, 'Use Set Speed') + self.assertTrue(self.params.get_bool('SLCWheelOverride')) def test_limit_sign_preference_needs_parked_authority_not_long_control_or_vehicle(self): self.fingerprint = None