Compare commits

..

8 Commits

Author SHA1 Message Date
firestarsdog 68c0b55a00 last 2026-11-04 12:16:09 -05:00
firestarsdog 446801ca74 Match newer Dashboard packets to avoid stale SET 2026-11-04 11:52:42 -05:00
firestarsdog 7fe8dd5e83 Fix SLC standstill SET and stale wheel writes 2026-11-04 11:42:45 -05:00
whoisdomi 72d886cef9 Adopt SLC Speed Limit button fixes 2026-11-04 10:14:11 -06:00
firestarsdog b161b33ae8 Refresh ARM64 device artifacts and release receipt 2026-11-04 01:25:08 -05:00
firestarsdog b0c03bdbda Fixes 2026-11-04 00:46:58 -05:00
firestarsdog 08cfff03af Build 2026-11-03 22:47:22 -05:00
firestarsdog af4d7e5a0d flop 2026-11-03 22:15:26 -05:00
251 changed files with 8337 additions and 9704 deletions
+3 -7
View File
@@ -343,8 +343,6 @@ jobs:
run: scons
- name: Gate every assigned lifecycle module serially
timeout-minutes: 10
env:
RAYLIB_BACKEND: headless
run: python3 tools/ci/run_vehicle_tests.py --suite aol-lifecycle --shard-index "${{ matrix.shard }}" --shard-count 16 --output "ci-results/aol-lifecycle-${{ matrix.shard }}"
- name: Save every lifecycle outcome and native build
if: always()
@@ -445,7 +443,7 @@ jobs:
strategy:
fail-fast: false
matrix:
shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
shard: [0, 1, 2, 3]
env:
FUZZ_SEED: '0'
steps:
@@ -455,9 +453,7 @@ jobs:
run: scons
- name: Run every assigned RELEASE target serially
timeout-minutes: 20
env:
RAYLIB_BACKEND: headless
run: python3 tools/ci/run_vehicle_tests.py --suite safety-release --shard-index "${{ matrix.shard }}" --shard-count 16 --output "ci-results/safety-release-${{ matrix.shard }}"
run: python3 tools/ci/run_vehicle_tests.py --suite safety-release --shard-index "${{ matrix.shard }}" --shard-count 4 --output "ci-results/safety-release-${{ matrix.shard }}"
- name: Save every RELEASE outcome and native build
if: always()
uses: actions/upload-artifact@v4
@@ -480,7 +476,7 @@ jobs:
pattern: safety-release-*
path: ci-results/safety-release-shards
- name: Require every RELEASE case exactly once and passed
run: python3 tools/ci/lifecycle_shards.py ci-results/safety-release-shards --suite safety-release --count 16 --output ci-results/safety-release-complete.json
run: python3 tools/ci/lifecycle_shards.py ci-results/safety-release-shards --suite safety-release --count 4 --output ci-results/safety-release-complete.json
- name: Save complete RELEASE verdict
if: always()
uses: actions/upload-artifact@v4
+4 -37
View File
@@ -57,37 +57,6 @@ EOF
fi
}
function update_is_ready {
[ -f "$1" ] && [ ! -L "$1" ]
}
# A validated legacy return records the OS it was staged against. Check it on
# every boot before swapping trees, including manual reboots.
# Keep this in sync with TRUSTED_LEGACY_RELEASES in updated.py.
TRUSTED_LEGACY_STARPILOT_COMMIT="4a553da9ac22a14e34513c011aed04c1e93b4619"
function staged_os_is_compatible {
local staged="$1" marker="$1/.git/starpilot-retain-agnos" commit
commit="$(git -C "$staged" rev-parse --verify 'HEAD^{commit}')" || return 1
if [ "$commit" = "$TRUSTED_LEGACY_STARPILOT_COMMIT" ]; then
[ -f "$marker" ] && [ -s "$marker" ] && [ ! -L "$marker" ] && [ -r /VERSION ] && [ "$(cat "$marker")" = "$(< /VERSION)" ]
else
[ ! -e "$marker" ] && [ ! -L "$marker" ]
fi
}
function reset_update_os_environment {
unset AGNOS_VERSION AGNOS_ACCEPTED_VERSIONS
}
function maybe_activate_legacy_agnos {
local repo="$1" verifier="$2" manifest="$3" commit
commit="$(git -C "$repo" rev-parse --verify 'HEAD^{commit}')" || return 1
if [ "$commit" != "$TRUSTED_LEGACY_STARPILOT_COMMIT" ] && [ ! -L "$manifest" ]; then
"$verifier" --verify "$manifest" && sudo reboot
fi
}
function launch {
# Remove orphaned git lock if it exists on boot
[ -f "$DIR/.git/index.lock" ] && rm -f "$DIR/.git/index.lock"
@@ -106,10 +75,8 @@ function launch {
if [ $? -eq 0 ]; then
echo "${DIR} has been modified, skipping overlay update installation"
else
if update_is_ready "${STAGING_ROOT}/finalized/.overlay_consistent"; then
if ! staged_os_is_compatible "${STAGING_ROOT}/finalized"; then
echo "Staged update requires a different or unverified AGNOS version; skipping installation."
elif [ ! -d /data/safe_staging/old_openpilot ]; then
if [ -f "${STAGING_ROOT}/finalized/.overlay_consistent" ]; then
if [ ! -d /data/safe_staging/old_openpilot ]; then
echo "Valid overlay update found, installing"
LAUNCHER_LOCATION="${BASH_SOURCE[0]}"
@@ -117,10 +84,10 @@ function launch {
mv "${STAGING_ROOT}/finalized" "$DIR"
cd "$DIR"
# releases before the openpilot/ move can't swap AGNOS on newer AGNOS, use ours
maybe_activate_legacy_agnos "$DIR" /data/safe_staging/old_openpilot/openpilot/common/hardware/comma/agnos.py system/hardware/tici/agnos.json
[ ! -L system/hardware/tici/agnos.json ] && /data/safe_staging/old_openpilot/openpilot/common/hardware/comma/agnos.py --verify system/hardware/tici/agnos.json && sudo reboot
echo "Restarting launch script ${LAUNCHER_LOCATION}"
reset_update_os_environment
unset AGNOS_VERSION
exec "${LAUNCHER_LOCATION}"
else
echo "openpilot backup found, not updating"
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5 -1
View File
@@ -360,6 +360,7 @@ class CarController(CarControllerBase):
self.volt_cc_cancel_pending = False
self.volt_cc_last_cancel_frame = 0
self.volt_cc_metric = False
self.volt_cc_speed_ceiling_mps = None
self.volt_cc_last_button_frame = 0
self.volt_cc_last_direction_frame = None
self.volt_cc_consumed_source_ns = 0
@@ -1396,7 +1397,10 @@ class CarController(CarControllerBase):
self.volt_cc_consumed_source_ns = source_ns
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, button))
elif self.frame % 4 == 0:
button, rate = button_request(CS.out.vEgo, CS.out.cruiseState.speed, actuators.accel, CS.out.vCruise,
cruise_kph = CS.out.vCruise
if self.volt_cc_speed_ceiling_mps is not None:
cruise_kph = min(cruise_kph, self.volt_cc_speed_ceiling_mps * CV.MS_TO_KPH)
button, rate = button_request(CS.out.vEgo, CS.out.cruiseState.speed, actuators.accel, cruise_kph,
bool(CC.hudControl.leadVisible), self.volt_cc_metric)
source_ns = physical.button_credit_ns
if ((self.frame - self.volt_cc_last_button_frame) * DT_CTRL > rate and
@@ -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
@@ -244,14 +244,14 @@ class TestIoniq6PrepublicationHandoff(unittest.TestCase):
cp = CarInterface.get_params(model, fp, [], False, False, False)
ci = CarInterface(cp)
packer = CANPacker(DBC[model][Bus.pt])
ci.update([(1_000_000_000, [])])
ci.update((1_000_000_000, []))
sources = [(parser, state) for parser in ci.can_parsers.values()
for state in parser.message_states.values() if not state.ignore_alive and state.address != 0x2E0]
for frame in range(120):
packets = [packer.make_can_msg(state.name, parser.bus,
{"COUNTER": frame % 256} if any(sig.name == "COUNTER" for sig in state.signals) else {})
for parser, state in sources]
state = ci.update([(1_000_000_000 + frame * 100_000_000, packets)])
state = ci.update((1_000_000_000 + frame * 100_000_000, packets))
self.assertTrue(state.canValid)
self.assertFalse(cp.openpilotLongitudinalControl)
self.assertTrue(cp.pcmCruise)
@@ -261,7 +261,7 @@ class TestIoniq6PrepublicationHandoff(unittest.TestCase):
packets = [packer.make_can_msg(msg.name, parser.bus,
{"COUNTER": frame % 256} if any(sig.name == "COUNTER" for sig in msg.signals) else {})
for parser, msg in sources if msg.name != "MDPS"]
state = ci.update([(1_000_000_000 + frame * 100_000_000, packets)])
state = ci.update((1_000_000_000 + frame * 100_000_000, packets))
self.assertFalse(state.canValid)
def test_card_tags_aol_candidate_before_cp_publication_without_disabling_ecu(self):
@@ -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)
+1 -6
View File
@@ -77,12 +77,7 @@ class RadarInterface(RadarInterfaceBase):
if not self.rcp.can_valid:
ret.errors.canError = True
status = self.rcp.vl['STATUS_MSG']
pre_fault = status['RADAR_PRE_FAULT']
# Legacy pre-warning can rise on an empty road before the documented dirty-radar threshold.
legacy_pre_warning = (not self.CP.flags & ToyotaFlags.TSS2 and pre_fault == 3 and
status['INVALID_COUNTER'] < 2659)
if status['RADAR_STATUS'] != 1 or pre_fault != 0 and not legacy_pre_warning:
if self.rcp.vl['STATUS_MSG']['RADAR_STATUS'] != 1 or self.rcp.vl['STATUS_MSG']['RADAR_PRE_FAULT'] != 0:
ret.errors.radarUnavailableTemporary = True
for ii in sorted(updated_messages):
@@ -659,9 +659,7 @@ static void hyundai_canfd_rx_hook(const CANPacket_t *msg) {
aol_optional_release_neutral = false;
aol_optional_release_previous = 0U;
aol_optional_release_pending = false;
if ((elapsed == 0U) || (elapsed > 100000U)) {
aol_optional_release_latch = false;
}
aol_optional_release_latch = false;
}
const bool raw_neutral = fresh && !main_button &&
!(hyundai_canfd_alt_buttons ? GET_BIT(msg, 39U) : GET_BIT(msg, 23U)) && (cruise_button == HYUNDAI_BTN_NONE);
@@ -68,9 +68,9 @@ class TestHyundaiOptionalRelease(unittest.TestCase):
self.buttons(button)
self.buttons()
def claimed(self, button=2):
def claimed(self):
self.safety.aol_set_host_request(5)
self.release_gesture(button)
self.release_gesture()
self.assert_rx_profile()
self.safety.aol_set_host_request(7)
self.buttons(4)
@@ -200,35 +200,3 @@ class TestHyundaiOptionalRelease(unittest.TestCase):
self.assertEqual(self.safety.aol_get_request_mask(), 0, self.context)
self.assertEqual(self.safety.aol_get_permission_mask(), 0, self.context)
self.assertFalse(self.tx(1, 1), self.context)
def test_claimed_lateral_survives_timely_button_counter_interruption(self):
for raw in (0x815, 0x895):
for button in (1, 2):
for defect in ("replay", "gap"):
with self.subTest(raw=raw, button=button, defect=defect):
self.mode(raw)
self.claimed(button)
self.now += 20_000
self.safety.set_timer(self.now)
self.healthy()
counter = self.counters["CRUISE_BUTTONS"] % 16
self.buttons(counter=counter if defect == "replay" else (counter + 2) % 16)
self.assert_rx_profile()
self.assertEqual(self.safety.aol_get_permission_mask(), 0 if self.release else 1, self.context)
self.assertEqual(bool(self.tx(1, 1)), not self.release, self.context)
self.buttons(counter=(counter + 2) % 16)
self.safety.aol_set_host_request(5)
self.assertFalse(self.safety.get_controls_allowed(), self.context)
self.assertEqual(self.safety.aol_get_permission_mask(), 0 if self.release else 1, self.context)
self.safety.aol_set_host_request(0)
self.safety.aol_set_host_request(5)
self.assert_denied()
def test_claimed_lateral_still_requires_bounded_button_source_time(self):
for raw in (0x815, 0x895):
for elapsed in (0, 100_001):
with self.subTest(raw=raw, elapsed=elapsed):
self.mode(raw)
self.claimed()
self.buttons(counter=self.counters["CRUISE_BUTTONS"] % 16, advance=elapsed)
self.assert_denied()
@@ -1,6 +1,5 @@
"""Malformed and contradictory physical sources withdraw receiver credits."""
import unittest
from opendbc.car.structs import CarParams
from opendbc.safety.tests import test_subaru_software_cruise as software
from opendbc.safety.tests import test_subaru_sng as sng
from opendbc.safety.tests import test_subaru_stock_aol as stock
@@ -41,36 +40,12 @@ class TestSubaruReceiveGuards(unittest.TestCase):
def test_stock_aol_truncated_optional_gear_withdraws(self):
case = stock.TestSubaruStockAol()
self.addCleanup(case.tearDown)
case.configure()
case.arm()
gear = case.frame('Transmission', {'Gear': 121})
case.safety.safety_rx_hook(libsafety_py.make_CANPacket(gear.addr, gear.bus, bytes(gear.data[0:7])))
self.assertEqual(case.safety.aol_get_permission_mask(), 0)
def test_stock_aol_cleanup_runs_on_success_failure_and_error(self):
"""The manually owned helper must not leave its native AOL profile for the next test."""
safety = libsafety_py.libsafety
for outcome in ('success', 'failure', 'error'):
with self.subTest(outcome=outcome):
class OwnedCase(TestSubaruReceiveGuards):
def runTest(self, expected_outcome=outcome):
self.test_stock_aol_truncated_optional_gear_withdraws()
if expected_outcome == 'failure':
self.fail('deliberate failure after the real receiver assertions')
if expected_outcome == 'error':
raise RuntimeError('deliberate error after the real receiver assertions')
result = unittest.TestResult()
OwnedCase().run(result)
self.assertEqual(result.testsRun, 1)
self.assertEqual(len(result.failures), int(outcome == 'failure'))
self.assertEqual(len(result.errors), int(outcome == 'error'))
self.assertEqual(safety.get_current_safety_mode(), CarParams.SafetyModel.noOutput)
self.assertFalse(safety.get_controls_allowed())
self.assertEqual(safety.aol_get_request_mask(), 0)
self.assertEqual(safety.aol_get_permission_mask(), 0)
def test_sng_conflicting_duplicate_withdraws_source_and_forwarding_lease(self):
case = sng.TestSubaruSng()
case.setup()
+7 -1
View File
@@ -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;
@@ -346,6 +346,10 @@ struct SlcState @0xa1680744031fdb2d {
# Qualified adoption proposal; the current displayed observation stays above.
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
wheelAdoptMonoTime @61 :UInt64; # Card's original wheel event timestamp, only on the approval frame
wheelAdoptTargetMps @62 :Float32; # exact adjusted SET approved for that event
struct SourceReading {
source @0 :Text;
enabled @1 :Bool;
@@ -447,6 +451,7 @@ struct SlcAction @0x9ccdc8676701b412 {
conditionalManual @5 :ConditionalManualAction;
controllerCruise @6 :ControllerCruiseAction;
controllerMode @7 :ControllerCruiseAction;
wheelAdoptMonoTime @8 :UInt64; # original timestamp of a qualified Adopt Speed Limit wheel event
struct ControllerCruiseAction {
version @0 :UInt16;
@@ -514,6 +519,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;
File diff suppressed because it is too large Load Diff
+117 -3
View File
@@ -730,7 +730,7 @@ struct SlcState {
struct ConditionalModeState;
struct _capnpPrivate {
CAPNP_DECLARE_STRUCT_HEADER(a1680744031fdb2d, 18, 22)
CAPNP_DECLARE_STRUCT_HEADER(a1680744031fdb2d, 21, 22)
#if !CAPNP_LITE
static constexpr ::capnp::_::RawBrandedSchema const* brand() { return &schema->defaultBrand; }
#endif // !CAPNP_LITE
@@ -826,7 +826,7 @@ struct SlcAction {
struct _capnpPrivate {
CAPNP_DECLARE_STRUCT_HEADER(9ccdc8676701b412, 4, 4)
CAPNP_DECLARE_STRUCT_HEADER(9ccdc8676701b412, 5, 4)
#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
@@ -4366,6 +4366,14 @@ public:
inline bool hasUpcomingBoundaryId() const;
inline ::capnp::Text::Reader getUpcomingBoundaryId() const;
inline bool getAcceptedIsPreview() const;
inline ::uint64_t getWheelOverrideSince() const;
inline ::uint64_t getWheelAdoptMonoTime() const;
inline float getWheelAdoptTargetMps() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -4659,6 +4667,18 @@ public:
inline void adoptUpcomingBoundaryId(::capnp::Orphan< ::capnp::Text>&& value);
inline ::capnp::Orphan< ::capnp::Text> disownUpcomingBoundaryId();
inline bool getAcceptedIsPreview();
inline void setAcceptedIsPreview(bool value);
inline ::uint64_t getWheelOverrideSince();
inline void setWheelOverrideSince( ::uint64_t value);
inline ::uint64_t getWheelAdoptMonoTime();
inline void setWheelAdoptMonoTime( ::uint64_t value);
inline float getWheelAdoptTargetMps();
inline void setWheelAdoptTargetMps(float value);
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
@@ -5511,6 +5531,8 @@ public:
inline bool hasControllerMode() const;
inline ::cereal::SlcAction::ControllerCruiseAction::Reader getControllerMode() const;
inline ::uint64_t getWheelAdoptMonoTime() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -5579,6 +5601,9 @@ public:
inline void adoptControllerMode(::capnp::Orphan< ::cereal::SlcAction::ControllerCruiseAction>&& value);
inline ::capnp::Orphan< ::cereal::SlcAction::ControllerCruiseAction> disownControllerMode();
inline ::uint64_t getWheelAdoptMonoTime();
inline void setWheelAdoptMonoTime( ::uint64_t value);
private:
::capnp::_::StructBuilder _builder;
template <typename, ::capnp::Kind>
@@ -5970,6 +5995,8 @@ public:
inline bool hasTeslaStockCruise() const;
inline ::cereal::SlcCruiseEvent::TeslaStockCruise::Reader getTeslaStockCruise() const;
inline ::uint64_t getWheelOverrideSince() const;
private:
::capnp::_::StructReader _reader;
template <typename, ::capnp::Kind>
@@ -6070,6 +6097,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 <typename, ::capnp::Kind>
@@ -19713,6 +19743,62 @@ inline ::capnp::Orphan< ::capnp::Text> SlcState::Builder::disownUpcomingBoundary
::capnp::bounded<21>() * ::capnp::POINTERS));
}
inline bool SlcState::Reader::getAcceptedIsPreview() const {
return _reader.getDataField<bool>(
::capnp::bounded<141>() * ::capnp::ELEMENTS);
}
inline bool SlcState::Builder::getAcceptedIsPreview() {
return _builder.getDataField<bool>(
::capnp::bounded<141>() * ::capnp::ELEMENTS);
}
inline void SlcState::Builder::setAcceptedIsPreview(bool value) {
_builder.setDataField<bool>(
::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 ::uint64_t SlcState::Reader::getWheelAdoptMonoTime() const {
return _reader.getDataField< ::uint64_t>(
::capnp::bounded<19>() * ::capnp::ELEMENTS);
}
inline ::uint64_t SlcState::Builder::getWheelAdoptMonoTime() {
return _builder.getDataField< ::uint64_t>(
::capnp::bounded<19>() * ::capnp::ELEMENTS);
}
inline void SlcState::Builder::setWheelAdoptMonoTime( ::uint64_t value) {
_builder.setDataField< ::uint64_t>(
::capnp::bounded<19>() * ::capnp::ELEMENTS, value);
}
inline float SlcState::Reader::getWheelAdoptTargetMps() const {
return _reader.getDataField<float>(
::capnp::bounded<40>() * ::capnp::ELEMENTS);
}
inline float SlcState::Builder::getWheelAdoptTargetMps() {
return _builder.getDataField<float>(
::capnp::bounded<40>() * ::capnp::ELEMENTS);
}
inline void SlcState::Builder::setWheelAdoptTargetMps(float value) {
_builder.setDataField<float>(
::capnp::bounded<40>() * ::capnp::ELEMENTS, value);
}
inline bool SlcState::SourceReading::Reader::hasSource() const {
return !_reader.getPointerField(
::capnp::bounded<0>() * ::capnp::POINTERS).isNull();
@@ -21220,6 +21306,20 @@ inline ::capnp::Orphan< ::cereal::SlcAction::ControllerCruiseAction> SlcAction::
::capnp::bounded<3>() * ::capnp::POINTERS));
}
inline ::uint64_t SlcAction::Reader::getWheelAdoptMonoTime() const {
return _reader.getDataField< ::uint64_t>(
::capnp::bounded<4>() * ::capnp::ELEMENTS);
}
inline ::uint64_t SlcAction::Builder::getWheelAdoptMonoTime() {
return _builder.getDataField< ::uint64_t>(
::capnp::bounded<4>() * ::capnp::ELEMENTS);
}
inline void SlcAction::Builder::setWheelAdoptMonoTime( ::uint64_t value) {
_builder.setDataField< ::uint64_t>(
::capnp::bounded<4>() * ::capnp::ELEMENTS, value);
}
inline ::uint16_t SlcAction::ControllerCruiseAction::Reader::getVersion() const {
return _reader.getDataField< ::uint16_t>(
::capnp::bounded<0>() * ::capnp::ELEMENTS);
@@ -22088,6 +22188,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);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8 -7
View File
@@ -327,12 +327,12 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"HideDMIcon", {PERSISTENT, BOOL, "0"}},
{"HidePersonalityAlerts", {PERSISTENT, BOOL, "0"}},
{"ShowSpeedLimits", {PERSISTENT, BOOL, "1"}},
{"SLCConfirmation", {PERSISTENT, BOOL, "0"}},
{"SLCAdvanceSeconds", {PERSISTENT, INT, "0"}},
{"SLCConfirmationHigher", {PERSISTENT, BOOL, "0"}},
{"SLCConfirmationLower", {PERSISTENT, BOOL, "0"}},
{"SLCFallback", {PERSISTENT, INT}}, // Retired; preserve saved bytes for older versions.
{"SLCOverride", {PERSISTENT, INT, "1"}},
{"SLCOnEngage", {PERSISTENT, BOOL, "0"}},
{"SLCConfirmation", {PERSISTENT, INT, "0"}},
{"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}},
@@ -361,7 +361,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"VASMPreferences", {PERSISTENT | DONT_LOG, JSON}},
{"LaneChangePreferences", {PERSISTENT | DONT_LOG, JSON}},
{"SLCPriority1", {PERSISTENT, STRING, "Vision"}},
{"SLCPriority2", {PERSISTENT, STRING, "Dashboard"}},
{"SLCPriority2", {PERSISTENT, STRING, "Map Data"}},
{"SLCPriority3", {PERSISTENT, STRING, "Off"}},
{"SpeedLimitController", {PERSISTENT, BOOL, "0"}},
{"RouteCount", {PERSISTENT, INT, "0"}},
{"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}},
+70 -92
View File
@@ -4,7 +4,6 @@ import os
import time
import threading
import uuid
from collections import deque
from typing import cast
import openpilot.cereal.messaging as messaging
@@ -32,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, SlcPendingConfirmation
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
@@ -93,7 +92,7 @@ from openpilot.starpilot.vehicle_selection import startup_candidate
from openpilot.starpilot.vehicle_preferences import VehicleStartupPreferences
from openpilot.starpilot.controller_extensions import configure_controller, observe_controller_can
from openpilot.starpilot.vehicle_startup import VehicleStartupOwner
from openpilot.starpilot.controllers.wheel_actions import WheelPublisher, key_for
from openpilot.starpilot.controllers.wheel_actions import LIFETIME_NS as WHEEL_ACTION_MAX_AGE_NS, WheelPublisher, key_for
from openpilot.starpilot.controllers.cruise_action import CruiseActionConsumer, eligible as cruise_action_eligible
from openpilot.starpilot.schema_cache import get_cache, prewarm_cache_contracts, put_cache
@@ -139,15 +138,15 @@ def can_comm_callbacks(logcan: messaging.SubSocket, sendcan: messaging.PubSocket
def curve_press_receipt(CS: car.CarState, helper: VCruiseHelper, sm, *, curve_replay: bool,
slc_replay: bool, host_long_active: bool, observed_ns: int):
"""Reserve SLC presentation presses even when their command is stale or rejected."""
"""A confirmation or native standstill RES must not also override Curve control."""
accelerate = frozenset((car.CarState.ButtonEvent.Type.accelCruise,
car.CarState.ButtonEvent.Type.resumeCruise))
pressed = {b.type.raw for b in CS.buttonEvents if b.pressed and b.type.raw in accelerate}
if (not curve_replay or not host_long_active or not CS.canValid or CS.canTimeout or not CS.cruiseState.available or
not pressed):
CS.standstill or CS.cruiseState.standstill or not pressed):
return None
if slc_replay and (helper.slc_consumed_button is not None or bool(pressed & helper.slc_suppressed_buttons) or
bool(sm['slcState'].hasPending)):
if (helper.slc_consumed_button is not None or bool(pressed & helper.slc_suppressed_buttons) or
slc_replay and bool(sm['slcState'].hasPending)):
return None
return ('curveAccelPress', None, float(helper.v_cruise_kph_last / 3.6),
float(helper.v_cruise_kph / 3.6), observed_ns)
@@ -187,10 +186,6 @@ class Car:
ignore_valid=['autoSteeringLearning'], ignore_avg_freq=['autoSteeringLearning'])
self.slc_cruise_event_id = 0
self.slc_producer_session = uuid.uuid4().hex
self.slc_command_sock = messaging.sub_sock('slcCruiseCommand', conflate=False) if self.slc_replay else None
self.slc_commands = deque(maxlen=8)
self.slc_last_command_id = 0
self.slc_command_session = ''
self.slc_receipts: list[tuple[str, object, float, float, int]] = []
self.manual_receipt = None
self.manual_event_sequence = 0
@@ -427,14 +422,13 @@ class Car:
for ignored in (self.sm.ignore_alive, self.sm.ignore_valid, self.sm.ignore_average_freq):
if 'slcState' not in ignored:
ignored.append('slcState')
if self.slc_command_sock is None:
self.slc_command_sock = messaging.sub_sock('slcCruiseCommand', conflate=False)
if 'slcDashboardObservation' not in self.pm.sock:
self.pm.sock['slcDashboardObservation'] = messaging.pub_sock('slcDashboardObservation')
self.slc_requested_configuration = self.read_slc_configuration()
self.slc_configuration = self.slc_requested_configuration
self.slc_replay = self.slc_configuration[0]
self.slc_control_enabled = self.slc_replay and self.slc_configuration[1].enabled
self.slc_match_on_engage = self.slc_transport_available and self.read_slc_match_on_engage()
self.slc_source_floor_ns = 0
# Write previous route's CarParams
@@ -475,6 +469,9 @@ class Car:
feature_enabled(self.params, self.CP, 'slc', os.environ))
return bool(admitted), settings
def read_slc_match_on_engage(self) -> bool:
return read_saved(self.params, 'SLCOnEngage', 1) == (b'1', True)
def refresh_slc_configuration(self, now_ns: int) -> None:
configuration = getattr(self, 'slc_requested_configuration', None)
if configuration is None or configuration == getattr(self, 'slc_configuration', None):
@@ -482,12 +479,8 @@ class Car:
self.slc_configuration = configuration
self.slc_replay = configuration[0]
self.slc_control_enabled = self.slc_replay and configuration[1].enabled
# Invalidate old SLC choices while keeping consumed buttons latched until release.
self.slc_source_floor_ns = max(getattr(self, 'slc_source_floor_ns', 0), now_ns)
self.slc_commands.clear()
self.slc_command_session = ''
self.slc_last_command_id = 0
# Keep the shared event producer and held suppression until physical release.
# Only SLC's sourced/issued work predating this admission is retired.
def observe_distance_personality(self, CS: car.CarState, now_ns: int) -> None:
tracker = getattr(self, 'distance_personality_tracker', None)
@@ -573,18 +566,22 @@ class Car:
0 < control_log_ns <= now_ns and now_ns - control_log_ns <= slc_physical.STATE_MAX_AGE_NS and
self.sm['carControl'].enabled)
host_control_enabled = bool(self.CP.openpilotLongitudinalControl and not self.CP.pcmCruise and host_enabled)
host_long_active = host_control_enabled and self.sm['carControl'].longActive
host_long_active = bool(self.CP.openpilotLongitudinalControl and host_enabled and self.sm['carControl'].longActive)
slc_long_active = bool(getattr(self, 'slc_control_enabled', self.slc_replay) and self.CP.openpilotLongitudinalControl and
host_enabled and (self.sm['carControl'].longActive or CS.gasPressed))
pending = None
if self.slc_replay:
pending = slc_physical.pending_confirmation(
self.sm, now_ns, long_active=slc_long_active, after_ns=floor_ns)
approved = (slc_physical.state_current(self.sm, observation, self.slc_producer_session, now_ns)
if self.v_cruise_helper.toyota is not None and self.slc_control_enabled else None)
if approved is not None and int(approved.frameMonoTime) <= floor_ns:
approved = None
self.update_cruise_speed(CS, pending, approved=approved)
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)
@@ -593,6 +590,8 @@ class Car:
self.controller_cruise_consumer.apply(request, self.CP, CS, self.sm, self.v_cruise_helper,
now_ns=now_ns, is_metric=self.is_metric,
enabled=bool(host_control_enabled and can_rcv_valid and not self.params.get_bool("SafeMode")))
if self.slc_replay:
self.apply_slc_adopt(now_ns, host_control_enabled=host_control_enabled, after_ns=floor_ns)
consumed = self.v_cruise_helper.slc_consumed_button
if self.aol_card_intent is not None:
if self.aol_process_fault_context is None:
@@ -784,52 +783,6 @@ class Car:
slc_replay=self.slc_replay, host_long_active=host_long_active, observed_ns=now_ns)
if curve_receipt is not None:
self.slc_receipts.append(curve_receipt)
if self.slc_command_sock is not None:
for _ in range(8):
command_message = messaging.recv_one_or_none(self.slc_command_sock)
if command_message is None:
break
if self.slc_replay and command_message.valid:
self.slc_commands.append(command_message)
for _ in range(len(self.slc_commands)):
command_message = self.slc_commands.popleft()
command = command_message.slcCruiseCommand
if int(command.issuedMonoTime) <= floor_ns:
continue
if now_ns < int(command.issuedMonoTime):
if now_ns <= int(command.expiresMonoTime):
self.slc_commands.append(command_message)
continue
state = slc_physical.state_current(self.sm, observation, self.slc_producer_session, now_ns)
if (state is None or int(self.sm.logMonoTime['slcState']) < int(command.issuedMonoTime)) and now_ns < int(command.expiresMonoTime):
self.slc_commands.append(command_message)
continue
if state is not None and str(state.sessionId) != self.slc_command_session:
self.slc_command_session = str(state.sessionId)
self.slc_last_command_id = 0
if not self.slc_command_session:
self.slc_command_session = str(command.sessionId)
if str(command.sessionId) != self.slc_command_session or int(command.commandId) <= self.slc_last_command_id:
continue
self.slc_last_command_id = int(command.commandId)
previous_mps = float(self.v_cruise_helper.v_cruise_kph / 3.6)
origin = SlcPendingConfirmation(str(command.sessionId), int(command.decisionId), int(command.presentationId))
owners = {**self.v_cruise_helper.slc_suppression_owner, **self.v_cruise_helper.slc_released_owner}
allowed_buttons = {button for button, owner in owners.items() if owner == origin} if int(command.decisionId) > 0 else set()
intervening_button = (bool(self.v_cruise_helper.slc_cruise_change) or
any(b.type.raw not in allowed_buttons for b in CS.buttonEvents) or
any(timer > 0 and button not in allowed_buttons
for button, timer in self.v_cruise_helper.button_timers.items()))
applicable = slc_physical.command_applicable(
command, state, observation, self.slc_producer_session, now_ns, previous_mps,
long_active=slc_long_active,
button_event=intervening_button)
if self.v_cruise_helper.toyota is not None:
applicable = applicable and self.v_cruise_helper.toyota.can_apply(float(command.targetMps))
if applicable:
self.v_cruise_helper.apply_slc_target(float(command.targetMps), self.is_metric)
self.slc_receipts.append(('commandApplied' if applicable else 'commandRejected', command,
previous_mps, float(self.v_cruise_helper.v_cruise_kph / 3.6), now_ns))
blocked_keys = set()
if self.manual_receipt is not None:
blocked_keys.add(key_for(self.manual_receipt[0]))
@@ -845,28 +798,49 @@ class Car:
return CS, RD
def update_cruise_speed(self, CS, pending=None, *, approved=None):
def apply_slc_adopt(self, now_ns: int, *, host_control_enabled: bool, after_ns: int = 0) -> None:
pending = getattr(self, 'slc_wheel_adopt', None)
if pending is None:
return
wheel_ns, selected_kph = pending
helper = self.v_cruise_helper
if (not after_ns < wheel_ns <= now_ns <= wheel_ns + WHEEL_ACTION_MAX_AGE_NS or
not host_control_enabled or not self.slc_control_enabled or
not helper.v_cruise_initialized or helper.v_cruise_kph != selected_kph or any(helper.button_timers.values())):
self.slc_wheel_adopt = None
return
target = slc_physical.adopted_target(self.sm, now_ns, wheel_ns=wheel_ns)
if target is not None:
self.slc_wheel_adopt = None
helper.apply_slc_target(target, self.is_metric)
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_approved=approved)
wheel_context=wheel_context)
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
initial = None
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()
if (getattr(self, 'slc_control_enabled', False) and self.CP.openpilotLongitudinalControl and
if (getattr(self, 'slc_match_on_engage', False) and getattr(self, 'slc_control_enabled', False) and
not self.CP.pcmCruise and self.CP.openpilotLongitudinalControl and
CS.canValid and not CS.canTimeout and CS.cruiseState.available and
self.sm.valid['carControl'] and self.sm.alive['carControl'] and
0 < int(self.sm.logMonoTime['carControl']) <= now_ns and
now_ns - int(self.sm.logMonoTime['carControl']) <= slc_physical.STATE_MAX_AGE_NS and
(self.sm['carControl'].longActive or CS.gasPressed)):
evidence = getattr(self.CI.CS, 'dashboard_limit', None)
initial = slc_physical.engagement_state(
self.sm, evidence.observation if evidence is not None else None, self.slc_producer_session,
now_ns, after_ns=getattr(self, 'slc_source_floor_ns', 0))
# Initialization owns this edge; evaluate commands against its final speed.
now_ns - int(self.sm.logMonoTime['carControl']) <= slc_physical.STATE_MAX_AGE_NS):
long_requested = self.sm['carControl'].longActive or CS.gasPressed
if not long_requested and 'aolAxisState' in self.sm.logMonoTime:
# AOL can acknowledge the request before longitudinal actuators become active.
axis = current_axis(self.sm, now_ns=now_ns)
long_requested = axis is not None and axis.nativeAcknowledged and axis.desiredLongitudinal
if long_requested:
evidence = getattr(self.CI.CS, 'dashboard_limit', None)
target, initial = slc_physical.engagement_target(
self.sm, evidence.observation if evidence is not None else None, self.slc_producer_session,
now_ns, after_ns=getattr(self, 'slc_source_floor_ns', 0))
adopted = self.v_cruise_helper.initialize_v_cruise(
self.CS_prev, self.experimental_mode, resume=self.vehicle_startup.consume_cruise_resume(), initial_speed=self.initial_cruise_speed,
slc_target_mps=float(initial.presentationAdjustedLimit) if initial is not None else None, is_metric=self.is_metric)
if adopted:
slc_target_mps=target, is_metric=self.is_metric)
if adopted and initial is not None:
self.slc_receipts.append(('engagementAdopt', initial, 0.0, float(self.v_cruise_helper.v_cruise_kph / 3.6), now_ns))
def aol_cancel_source(self, CS, now_ns: int) -> int:
@@ -1135,11 +1109,6 @@ class Car:
elif self.CP.brand == 'hyundai':
from openpilot.starpilot.car.hyundai.dashboard import compose as hyundai_compose
companion = hyundai_compose(self.CP, self.CI.CS, CS, companion, car_state_stamp=int(cs_send.logMonoTime))
if self.v_cruise_helper.toyota is not None:
if companion is None:
companion = messaging.new_message('starpilotCarState', valid=bool(cs_send.valid), logMonoTime=int(cs_send.logMonoTime))
companion.starpilotCarState.sourceCarStateMonoTime = int(cs_send.logMonoTime)
self.v_cruise_helper.toyota.publish(companion.starpilotCarState.toyotaCruise)
self.car_gps_publisher.update(self.CI.CS, self.pm, companion)
self.pm.send('carState', cs_send)
if getattr(self, 'learning_stamp_observer', None) is not None:
@@ -1147,6 +1116,9 @@ class Car:
wheel = getattr(self, 'wheel_publisher', None)
if wheel is not None:
if (getattr(self, 'slc_control_enabled', False) and self.CC_prev.enabled and self.sm['carControl'].enabled and
any(action == 10 for _, action in self.wheel_commands)):
self.slc_wheel_adopt = (int(cs_send.logMonoTime), self.v_cruise_helper.v_cruise_kph)
wheel.publish(self.wheel_commands, self.CP, self.pm, now_ns=int(cs_send.logMonoTime),
drive_id=int(self.sm['deviceState'].startedMonoTime), source_car_ns=int(cs_send.logMonoTime),
source_control_ns=int(self.sm.logMonoTime['carControl']))
@@ -1186,6 +1158,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
@@ -1193,13 +1167,11 @@ class Car:
record.presentationId = detail.pending.presentation_id
elif kind == 'curveAccelPress':
record.button = 'accel'
else:
elif kind == 'engagementAdopt':
record.sessionId = str(detail.sessionId)
record.commandId = 0 if kind == 'engagementAdopt' else int(detail.commandId)
record.decisionId = int(detail.decisionId)
record.presentationId = int(detail.presentationId)
if kind == 'engagementAdopt':
record.button = 'decel'
record.button = 'decel'
self.pm.send('slcCruiseEvent', event)
if getattr(self, 'conditional_replay', False) and self.manual_receipt is not None:
@@ -1619,6 +1591,10 @@ class Car:
if physical.button_credit_ns - self.volt_cc_boot_offset_ns <= max(self.volt_cc_drive_id, self.volt_cc_source_floor_ns):
self.CI.CC.volt_cc_consumed_source_ns = physical.button_credit_ns
self.CI.CC.volt_cc_metric = self.is_metric
self.CI.CC.volt_cc_speed_ceiling_mps = (slc_physical.active_ceiling(
self.sm, self.volt_cc_now_mono_ns,
after_ns=max(self.volt_cc_drive_id, self.volt_cc_source_floor_ns, getattr(self, 'slc_source_floor_ns', 0)))
if getattr(self, 'slc_control_enabled', False) else None)
if getattr(self.CI.CC, "bolt_cc_profile", False):
self.CI.CC.bolt_cc_metric = self.is_metric
@@ -1871,6 +1847,8 @@ class Car:
now = time.monotonic()
if now >= next_cruise_read:
self.slc_requested_configuration = self.read_slc_configuration()
if getattr(self, 'slc_transport_available', False):
self.slc_match_on_engage = self.read_slc_match_on_engage()
self.v_cruise_helper.intervals = read_cruise_intervals(self.params, self.CP)
pitch_input = getattr(self.CI.CC, 'long_pitch_input', None)
if pitch_input is not None:
+40 -59
View File
@@ -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
@@ -66,35 +69,27 @@ class VCruiseHelper:
self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers}
self.pcm_slc_sample: tuple[float, int] | None = None
self.pcm_slc_button = ButtonType.unknown
# Set by software cruise steps or a checked Toyota PCM physical +/- change.
self.slc_cruise_change: tuple[float, float, str, bool] | None = None
self.slc_consumed_button: SlcConsumedButton | None = None
self.slc_suppressed_buttons: set[ButtonType] = set()
self.slc_suppression_owner: dict[ButtonType, SlcPendingConfirmation] = {}
self.slc_released_owner: dict[ButtonType, SlcPendingConfirmation] = {}
self.slc_released_suppressed = False
self.slc_pcm = False
from openpilot.starpilot.speed_limits.toyota import eligible, ToyotaCruise
self.toyota = ToyotaCruise() if eligible(CP) else None
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,
slc_approved=None):
self.slc_pcm = bool(slc_enabled and self.CP.pcmCruise and self.CP.openpilotLongitudinalControl)
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 self.slc_pcm or subaru_software
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_released_owner = {}
self.slc_wheel_tap = None
self.v_cruise_kph_last = self.v_cruise_kph
if self.toyota is not None:
self.slc_pcm = self.toyota.update(self, CS, enabled, slc_enabled, slc_approved, is_metric)
software_cruise = self.slc_pcm
now_ns = monotonic_ns()
if not CS.cruiseState.available or now_ns - self.pending_resume_ns > 500_000_000:
self.pending_resume_ns = 0
@@ -106,21 +101,26 @@ class VCruiseHelper:
button.type in (ButtonType.accelCruise, ButtonType.resumeCruise) and self.v_cruise_initialized):
self.pending_resume_ns = now_ns
if software_cruise or self.slc_suppressed_buttons:
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 self.toyota is None and (self.slc_pcm or subaru_software) and (not enabled or not self.v_cruise_initialized):
# PCM still owns engagement; SLC owns the software set speed while system long is selected.
if subaru_software and (not enabled or not self.v_cruise_initialized):
stock_kph = CS.cruiseState.speed * CV.MS_TO_KPH
self.v_cruise_kph = stock_kph if 0 < stock_kph < V_CRUISE_UNSET else V_CRUISE_UNSET
# if stock cruise is completely disabled, then we can use our own set speed logic
self._update_v_cruise_non_pcm(CS, enabled, is_metric)
if self.toyota is None:
self.v_cruise_cluster_kph = self.v_cruise_kph
if self.toyota is not None:
self.toyota.constrain(self)
self.v_cruise_cluster_kph = self.v_cruise_kph
self.update_button_timers(CS, enabled)
else:
self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH
@@ -139,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."""
@@ -165,8 +167,9 @@ class VCruiseHelper:
held = ButtonType.unknown
if continuous:
delta = self.v_cruise_kph - previous[0]
if ((button == ButtonType.accelCruise and delta > 0.) or
(button == ButtonType.decelCruise and delta < 0.)):
if (not self.slc_suppressed_buttons and not self.slc_released_suppressed and
((button == ButtonType.accelCruise and delta > 0.) or
(button == ButtonType.decelCruise and delta < 0.))):
self.slc_cruise_change = (previous[0] / 3.6, self.v_cruise_kph / 3.6,
'accel' if delta > 0. else 'decel', False)
self.pcm_slc_button = held
@@ -179,17 +182,13 @@ class VCruiseHelper:
continue
if not b.pressed and button in self.slc_suppressed_buttons:
self.slc_suppressed_buttons.remove(button)
prior_owner = self.slc_suppression_owner.pop(button, None)
if prior_owner is not None:
self.slc_released_owner[button] = prior_owner
self.slc_released_suppressed = True
self.button_timers[button] = 0
elif (b.pressed and enabled and CS.cruiseState.available and slc_pending is not None and
not (self.toyota is not None and button == ButtonType.accelCruise and
not (self.CP.pcmCruise and button == ButtonType.accelCruise and
(CS.standstill or CS.cruiseState.standstill or not CS.cruiseState.enabled)) and
button not in self.slc_suppressed_buttons):
self.slc_suppressed_buttons.add(button)
self.slc_suppression_owner[button] = slc_pending
self.slc_consumed_button = SlcConsumedButton(slc_pending, 'accel' if button == ButtonType.accelCruise else 'decel')
def _update_v_cruise_non_pcm(self, CS, enabled, is_metric):
@@ -231,26 +230,20 @@ 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."""
from opendbc.car.subaru.software_cruise import selected as subaru_software_selected
subaru_software = subaru_software_selected(self.CP)
if ((self.CP.pcmCruise and not self.slc_pcm and not subaru_software) or button_type not in (ButtonType.accelCruise, ButtonType.decelCruise) or
if ((self.CP.pcmCruise and not subaru_software) or button_type not in (ButtonType.accelCruise, ButtonType.decelCruise) or
not 0 < self.v_cruise_kph < V_CRUISE_UNSET or
(button_type == ButtonType.accelCruise and CS.cruiseState.standstill)):
return False
v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT
previous_kph = self.v_cruise_kph
toyota = self.toyota if self.slc_pcm else None
previous_cluster_kph = self.v_cruise_cluster_kph
if toyota is not None:
if toyota.requested is None:
return False
if button_type == ButtonType.decelCruise:
previous_kph = toyota.requested[0]
self.v_cruise_kph = toyota.requested[1]
interval = self.intervals.held if long_press else self.intervals.short
v_cruise_delta = v_cruise_delta * interval
display_speed = round(self.v_cruise_kph if is_metric else self.v_cruise_kph * CV.KPH_TO_MPH)
@@ -266,11 +259,6 @@ class VCruiseHelper:
self.v_cruise_kph = np.clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)
self.v_cruise_cluster_kph = self.v_cruise_kph
if toyota is not None:
toyota.requested = (float(self.v_cruise_kph), float(self.v_cruise_cluster_kph))
toyota.manual_step = (previous_kph, previous_cluster_kph, 'accel' if button_type == ButtonType.accelCruise else 'decel', long_press)
toyota.constrain(self)
return self.v_cruise_kph != previous_kph
if 0 < previous_kph < V_CRUISE_UNSET and self.v_cruise_kph != previous_kph:
button = "accel" if button_type == ButtonType.accelCruise else "decel"
self.slc_cruise_change = (float(previous_kph / 3.6), float(self.v_cruise_kph / 3.6), button, long_press)
@@ -291,23 +279,19 @@ class VCruiseHelper:
self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled}
def apply_slc_target(self, target_mps: float, is_metric: bool) -> None:
"""Apply a qualified planner command to the card-owned software set speed."""
"""Set the software cruise ceiling for engagement or a qualified driver request."""
# Match ordinary button precision so stepping away and back restores this target.
self.v_cruise_kph = slc_target_kph(target_mps, is_metric)
self.v_cruise_cluster_kph = self.v_cruise_kph
if self.toyota is not None:
self.toyota.manual_step = None
self.toyota.constrain(self)
self.toyota.requested = (self.v_cruise_kph, self.v_cruise_cluster_kph)
def initialize_v_cruise(self, CS, experimental_mode: bool, *, resume: bool | None = None,
initial_speed: object = V_CRUISE_INITIAL, slc_target_mps: float | None = None, is_metric: bool = False) -> bool:
"""Return whether engagement adopts the qualified current SLC limit."""
"""Return whether engagement used the supplied SLC target."""
if (not self.CP.openpilotLongitudinalControl or slc_target_mps is None or type(slc_target_mps) not in (int, float) or
not math.isfinite(slc_target_mps) or not V_CRUISE_MIN / 3.6 - 1e-5 <= slc_target_mps <= V_CRUISE_MAX / 3.6 + 1e-5):
slc_target_mps = None
# PCM initializes when SLC does not own SET.
if self.CP.pcmCruise and (not self.slc_pcm or slc_target_mps is None):
# Native cruise owns PCM SET, including when SLC supplies a planner cap.
if self.CP.pcmCruise:
return False
# A car-owned acknowledgment can outlive the physical button event.
if resume is None:
@@ -315,15 +299,12 @@ class VCruiseHelper:
resume = (0 < self.pending_resume_ns and 0 <= age <= 500_000_000) or any(
b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents)
self.pending_resume_ns = 0
if slc_target_mps is None and resume and self.v_cruise_initialized:
if not self.CP.pcmCruise:
self.v_cruise_kph = self.v_cruise_kph_last
self.v_cruise_cluster_kph = self.v_cruise_kph
if resume and self.v_cruise_initialized:
self.v_cruise_kph = self.v_cruise_kph_last
self.v_cruise_cluster_kph = self.v_cruise_kph
return False
elif slc_target_mps is not None:
# Toyota still needs its native capacity/release approval before application.
if self.toyota is None:
self.apply_slc_target(slc_target_mps, is_metric)
self.apply_slc_target(slc_target_mps, is_metric)
return True
else:
self.v_cruise_kph = int(round(np.clip(max(CS.vEgo * CV.MS_TO_KPH, validated_initial_cruise_speed(initial_speed)), V_CRUISE_INITIAL, V_CRUISE_MAX)))
@@ -52,7 +52,7 @@ class TestCruiseIntervals(unittest.TestCase):
Path(self.params.get_param_path("CustomCruiseLong")).write_bytes(b"200")
self.assertEqual(read_cruise_intervals(self.params, self.cp), CruiseIntervals())
def test_slc_uses_saved_intervals_for_every_other_make_and_pcm_mode(self):
def test_slc_preserves_software_intervals_and_native_pcm_set(self):
brands = [brand.__module__.split('.')[-2] for brand in BRANDS if brand.__module__.split('.')[-2] != 'toyota'] + ['future']
self.params.put("CustomCruise", 3.0, block=True)
self.params.put("CustomCruiseLong", 7.0, block=True)
@@ -67,6 +67,11 @@ class TestCruiseIntervals(unittest.TestCase):
helper.v_cruise_kph = helper.v_cruise_cluster_kph = initial
helper.intervals = read_cruise_intervals(self.params, cp)
helper.update_v_cruise(state(button, True, native=140.), True, metric, slc_enabled=True)
if pcm:
helper.update_v_cruise(state(button, False, native=140.), True, metric, slc_enabled=True)
self.assertAlmostEqual(helper.v_cruise_kph, 140., places=4)
self.assertIsNone(helper.slc_cruise_change)
continue
if held:
for _ in range(CRUISE_LONG_PRESS):
helper.update_v_cruise(state(native=140.), True, metric, slc_enabled=True)
@@ -373,12 +373,11 @@ class LongitudinalPlanner:
cruise_profile = replace(cruise_profile, cruise_brake_magnitude=global_magnitude)
if sm['controlsState'].forceDecel:
cruise_profile = None
elif (ceiling.effective_mps is not None or curve.effective_mps is not None) and cruise_profile is not None:
# An eligible lower ceiling must retain at least native approach
# braking even when the saved profile requests a softer cruise response.
elif curve.effective_mps is not None and cruise_profile is not None:
# Curve approach retains native braking when the saved cruise response is softer.
cruise_profile = replace(cruise_profile, cruise_brake_magnitude=max(cruise_profile.cruise_brake_magnitude,
abs(A_CRUISE_MIN)))
if ceiling.effective_mps is not None or curve.effective_mps is not None:
if curve.effective_mps is not None:
global_floor = min(global_floor, A_CRUISE_MIN)
if selected_acceleration is not None and cruise_profile is not None:
# The selected curve owns acceleration; following/jerks retain their owner.
+37 -116
View File
@@ -1,8 +1,6 @@
#!/usr/bin/env python3
import os
import json
from collections import deque
from dataclasses import replace
from opendbc.car.structs import car
from openpilot.common.params import Params
@@ -12,8 +10,9 @@ from openpilot.selfdrive.controls.lib.ldw import LaneDepartureWarning
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner, NATIVE_PLAN_INPUTS
import openpilot.cereal.messaging as messaging
from openpilot.cereal.services import SERVICE_LIST
from openpilot.starpilot.speed_limits import history as slc_history
from openpilot.starpilot.speed_limits.runtime import Action as SlcAction, CruiseEvent as SlcCruiseEvent, Runtime as SlcRuntime
from openpilot.starpilot.speed_limits.planner_host import (
SLC_ACTIONS, SavedSlcLifecycle, action_from_message, cruise_event_from_message, current_cruise_event, receive_slc_events,
)
from openpilot.starpilot.longitudinal.profile_runtime import ProfileHost
from openpilot.starpilot.longitudinal.lane_change_gap import LaneGapPreferences
from openpilot.starpilot.longitudinal.experimental_release import ConditionalHandoff
@@ -40,62 +39,8 @@ from openpilot.starpilot.feature_capabilities import longitudinal_supported
from openpilot.starpilot.conditional_mode.projection import paired_clocks_ns
from openpilot.starpilot.conditional_mode.ui_action import observation as ui_manual_observation
from openpilot.starpilot.longitudinal.cruise_ceiling import CurveCeiling
from openpilot.starpilot.feature_runtime import (enabled as feature_enabled, requested as feature_requested,
slc_runtime_settings, slc_transport_capable, vision_control_enabled)
from openpilot.starpilot.feature_runtime import enabled as feature_enabled, requested as feature_requested, slc_transport_capable
import time
from openpilot.starpilot.speed_limits.runtime_settings import read_params as read_slc_settings
class SavedSlcLifecycle:
"""Refresh saved admission; offsets and display units preserve driver choices."""
def __init__(self, params, cp, environment):
self.params, self.cp, self.environment = params, cp, dict(environment)
self.runtime = None
self.configuration = None
self.last_refresh_ns = None
def refresh(self, now_ns: int, *, pending_actions=None, pending_cruise=None):
if self.last_refresh_ns is not None and 0 <= now_ns - self.last_refresh_ns < 1_000_000_000:
return self.runtime, False
self.last_refresh_ns = now_ns
# One immutable parsed snapshot owns this refresh. Repeated qualification
# used to reopen the same saved keys five times in a due model iteration.
saved_slc = read_slc_settings(self.params)
admitted = feature_enabled(self.params, self.cp, 'slc', self.environment, saved_slc=saved_slc)
settings = slc_runtime_settings(self.params, self.cp, self.environment, saved_slc=saved_slc)
vision = admitted and feature_enabled(self.params, self.cp, 'vision', self.environment, saved_slc=saved_slc)
configuration = (admitted and not settings.errors, settings, vision,
vision_control_enabled(self.params, self.cp, saved_slc=saved_slc),
feature_enabled(self.params, self.cp, 'vision', {}, saved_slc=saved_slc),
self.params.get_bool('IsMetric'))
if configuration == self.configuration:
return self.runtime, False
previous_runtime = self.runtime
if (self.runtime is not None and self.configuration is not None and
configuration[:-1] == (self.configuration[0], replace(self.runtime.settings, offsets=settings.offsets,
advance_seconds=settings.advance_seconds),
*self.configuration[2:-1])):
if settings.offsets != self.runtime.settings.offsets or configuration[5] != self.configuration[5]:
self.runtime.update_offsets(settings.offsets, is_metric=configuration[5])
if settings.advance_seconds != self.runtime.settings.advance_seconds:
self.runtime.update_advance(settings.advance_seconds)
else:
set_speed_after_ns = self.runtime.set_speed_after_ns if self.runtime is not None else 0
if self.runtime is not None:
self.runtime.reset()
self.runtime = (SlcRuntime(settings, vision_enabled=vision,
vision_control_qualified=configuration[3],
vision_display_qualified=configuration[4], is_metric=configuration[5])
if configuration[0] else None)
if self.runtime is not None:
self.runtime.set_speed_after_ns = set_speed_after_ns
self.configuration = configuration
if pending_actions is not None:
pending_actions.clear()
if pending_cruise is not None and (self.runtime is None or self.runtime is not previous_runtime):
pending_cruise.clear()
return self.runtime, True
def profile_for_frame(host: ProfileHost | None, sm, CP, now_ns: int, *, traffic_mode: bool | None = False):
@@ -159,11 +104,13 @@ def traffic_profile_status(mode: bool | None, target, applied, settings) -> tupl
def curve_for_frame(host: CurveHost | None, sm, CP, now_ns: int, *, drive_id: int = 0,
event: CurveDriverEvent | None = None, follow_time_s: float | None = None):
event: CurveDriverEvent | None = None, follow_time_s: float | None = None,
cruise_ceiling_mps: float | None = None):
"""A diagnostic runtime result is not planner authority by itself."""
if host is None:
return None, None
result = host.sample(sm, CP, now_ns, drive_id=drive_id, event=event, follow_time_s=follow_time_s)
result = host.sample(sm, CP, now_ns, drive_id=drive_id, event=event, follow_time_s=follow_time_s,
cruise_ceiling_mps=cruise_ceiling_mps)
if result.ceiling_mps is None:
return None, result
model_ns = sm.logMonoTime.get('modelV2', 0)
@@ -177,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:
@@ -290,7 +243,8 @@ def update_curve_frame(planner: LongitudinalPlanner, sm, CP, now_ns: int, *, hos
results = []
def provider(follow_time_s: float | None):
ceiling, result = curve_for_frame(host, sm, CP, now_ns, drive_id=drive_id,
event=event, follow_time_s=follow_time_s)
event=event, follow_time_s=follow_time_s,
cruise_ceiling_mps=cruise_ceiling.speed_mps if cruise_ceiling is not None else None)
results.append(result)
return ceiling
planner.update(sm, cruise_ceiling=cruise_ceiling, profile_tuning=profile_tuning,
@@ -320,18 +274,7 @@ def queue_cruise_event(event, slc_events, curve_events, conditional_events=None,
curve_events.append(CurveDriverEvent(str(cruise.producerSessionId), int(cruise.eventId),
int(cruise.observedMonoTime), kind, str(cruise.button), int(event.logMonoTime)))
else:
slc_events.append(SlcCruiseEvent(int(cruise.eventId), int(cruise.observedMonoTime),
float(cruise.previousMps), float(cruise.selectedMps), str(cruise.button),
str(cruise.producerSessionId), int(event.logMonoTime), kind,
str(cruise.sessionId), int(cruise.decisionId),
int(cruise.presentationId), int(cruise.commandId)))
def current_cruise_event(events, now_ns: int):
max_event_age_ns = int(2e9 / SERVICE_LIST['modelV2'].frequency)
while events and now_ns - events[0].observed_ns > max_event_age_ns:
events.popleft()
return events.popleft() if events and events[0].observed_ns <= now_ns else None
slc_events.append(cruise_event_from_message(event))
def queue_ui_action(event, slc_actions, conditional_actions, mode_actions=None) -> None:
@@ -343,9 +286,8 @@ def queue_ui_action(event, slc_actions, conditional_actions, mode_actions=None)
mode_actions.append(event)
elif kind == 'conditionalModeCycle':
conditional_actions.append(event)
elif kind in ('accept', 'reject', 'adopt'):
slc_actions.append((int(event.logMonoTime), SlcAction(str(request.sessionId), int(request.sequenceId),
int(request.decisionId), int(request.presentationId), kind)))
elif kind in SLC_ACTIONS:
slc_actions.append(action_from_message(event))
def current_ui_manual_event(events, now_ns: int):
@@ -417,7 +359,7 @@ def starpilot_main():
pending_switchback = deque(maxlen=8)
mode_settings = ConditionalSettingsOwner(params) if mode_owner is not None else None
slc_lifecycle = SavedSlcLifecycle(params, CP, os.environ)
slc_runtime, _ = slc_lifecycle.refresh(0)
slc_lifecycle.refresh(0)
slc_action_sock = (messaging.sub_sock("slcAction", conflate=False)
if slc_available or conditional_host is not None or mode_owner is not None else None)
slc_cruise_sock = messaging.sub_sock("slcCruiseEvent", conflate=False)
@@ -431,18 +373,14 @@ def starpilot_main():
publish_services = ['longitudinalPlan', 'starpilotLongitudinalPlan', 'driverAssistance']
if slc_available or curve_host is not None or conditional_host is not None or traffic_owner is not None or mode_owner is not None:
publish_services.append('slcState')
if slc_available:
publish_services.append('slcCruiseCommand')
pm = messaging.PubMaster(publish_services)
native_plan_checks = list(NATIVE_PLAN_INPUTS)
subscribed_services = list(NATIVE_PLAN_INPUTS)
subscribed_services.append('deviceState')
if conditional_host is not None:
subscribed_services.append('starpilotRadarState')
optional_slc = ['slcDashboardObservation', 'slcVisionObservation', 'mapdOut', 'starpilotCarState'] if slc_available else []
if slc_available and feature_enabled(params, CP, 'aol', os.environ):
optional_slc.append('aolAxisState')
if force_stop_owner is not None and 'starpilotCarState' not in optional_slc:
optional_slc = slc_lifecycle.source_services() if slc_available else []
if force_stop_owner is not None:
optional_slc.append('starpilotCarState')
subscribed_services.extend(optional_slc)
subscribed_services.append('starpilotNavigation')
@@ -459,16 +397,14 @@ def starpilot_main():
# Due saved-setting verification can block on Params. Complete it
# before acquiring the model and fast-source batch, not after that
# batch has already begun aging toward the CEM freshness deadline.
slc_runtime, _ = slc_lifecycle.refresh(time.monotonic_ns(), pending_actions=pending_slc_actions,
pending_cruise=pending_slc_cruise)
slc_lifecycle.refresh(time.monotonic_ns(), pending_actions=pending_slc_actions, pending_cruise=pending_slc_cruise)
sm.update()
if sm.updated['modelV2']:
# Only explicit replay uses the recorded model timeline. In a live host,
# modelV2 may lag newer inputs in the same SubMaster update.
now_ns = int(sm.logMonoTime['modelV2']) if 'REPLAY' in os.environ else time.monotonic_ns()
if 'REPLAY' in os.environ:
slc_runtime, _ = slc_lifecycle.refresh(now_ns, pending_actions=pending_slc_actions,
pending_cruise=pending_slc_cruise)
slc_lifecycle.refresh(now_ns, pending_actions=pending_slc_actions, pending_cruise=pending_slc_cruise)
current_start_ns = (int(sm['deviceState'].startedMonoTime)
if 'deviceState' in subscribed_services and sm.valid['deviceState'] and
sm.alive['deviceState'] and 0 < sm.logMonoTime['deviceState'] <= now_ns else 0)
@@ -479,10 +415,7 @@ def starpilot_main():
planner_status = None
drive_changed = current_start_ns > 0 and drive_start_ns > 0 and current_start_ns != drive_start_ns
if slc_cruise_sock is not None:
for _ in range(8):
event = messaging.recv_one_or_none(slc_cruise_sock)
if event is None:
break
for event in receive_slc_events(slc_cruise_sock):
if str(event.slcCruiseEvent.kind) == 'wheelAction' and event.valid:
pending_wheel.append(event)
continue
@@ -505,10 +438,7 @@ def starpilot_main():
if traffic_owner is not None:
traffic_owner.reset()
if slc_action_sock is not None:
for _ in range(8):
event = messaging.recv_one_or_none(slc_action_sock)
if event is None:
break
for event in receive_slc_events(slc_action_sock):
queue_ui_action(event, pending_slc_actions, pending_conditional_ui, pending_modes)
waiting_wheel = deque(maxlen=32)
while pending_wheel:
@@ -520,25 +450,13 @@ def starpilot_main():
else:
wheel_coast.receive(event, params, CP, sm, now_ns=now_ns)
pending_wheel = waiting_wheel
if slc_runtime is not None:
if drive_changed or slc_runtime.state.reset_required or slc_runtime.ledger.reset_required:
slc_runtime.reset()
pending_slc_actions.clear()
pending_slc_cruise.clear()
max_event_age_ns = int(2e9 / SERVICE_LIST['modelV2'].frequency)
while pending_slc_actions and now_ns - pending_slc_actions[0][0] > max_event_age_ns:
pending_slc_actions.popleft()
cruise_event = current_cruise_event(pending_slc_cruise, now_ns)
request = (pending_slc_actions.popleft()[1] if cruise_event is None and pending_slc_actions and
pending_slc_actions[0][0] <= now_ns else None)
output = slc_runtime.step(sm, CP, now_ns=now_ns, request=request, cruise_event=cruise_event)
planner_status = output.message
if output.command is not None:
pm.send('slcCruiseCommand', output.command)
history_write = output.result.acceptance.history_write if output.result.acceptance is not None else None
# A camera episode has no road-location identity for a later drive.
if history_write is not None and history_write.candidate.source != 'vision':
params.put("SLCQualifiedHistory", json.loads(slc_history.encode(history_write, time.time_ns())))
# 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
needs_clock = conditional_host is not None or traffic_owner is not None or force_stop_owner is not None or mode_owner is not None
clock_pair = paired_clocks_ns() if needs_clock and 'REPLAY' not in os.environ else None
controller_traffic_toggle = bool(wheel_coast.traffic_toggles % 2)
@@ -598,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
@@ -615,6 +532,7 @@ def starpilot_main():
drive_id=current_start_ns, follow_seconds=follow_seconds,
traffic_mode=traffic_mode, takeoff_enabled=faster_lead_takeoff)
curve_result = update_curve_frame(longitudinal_planner, sm, CP, now_ns, host=curve_host,
cruise_ceiling=cruise_ceiling,
profile_tuning=profile_tuning,
traffic_mode=traffic_mode, lane_change_policy=lane_change_policy,
global_braking_response=global_braking_response, selected_profiles=selected_profiles,
@@ -641,6 +559,7 @@ def starpilot_main():
proposal, snapshot = conditional_host.sample(
sm, CP, longitudinal_planner, now_mono_ns=conditional_now_ns, now_boot_ns=conditional_boot_ns,
sample_skew_ns=conditional_skew_ns, drive_id=current_start_ns,
cruise_ceiling_mps=cruise_ceiling.speed_mps if cruise_ceiling is not None else None,
owner_context=ConditionalOwnerContext(
traffic_mode=traffic_observation(traffic_verdict, model_ns=int(sm.logMonoTime['modelV2']),
owner_present=traffic_owner is not None),
@@ -675,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
@@ -9,6 +9,7 @@ import unittest
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.starpilot import feature_runtime
from openpilot.starpilot.speed_limits import planner_host as slc_host
from openpilot.starpilot.speed_limits.runtime_settings import read_params as read_slc_settings
from openpilot.selfdrive.controls import plannerd
from openpilot.starpilot.conditional_mode.tests import test_status_consumer as transport
@@ -98,7 +99,7 @@ class TestCemInputAcquisitionOrder(unittest.TestCase):
def read(params):
reads.append(True)
return read_slc_settings(params)
with patch.object(plannerd, 'read_slc_settings', side_effect=read, create=True), \
with patch.object(slc_host, 'read_slc_settings', side_effect=read), \
patch.object(feature_runtime, 'read_slc_settings', side_effect=read):
runtime, changed = owner.refresh(MONO)
self.assertTrue(changed)
@@ -23,21 +23,21 @@ from openpilot.starpilot.conditional_mode.projection import ConditionalOwnerCont
from openpilot.starpilot.conditional_mode.runtime_settings import DOCUMENT_KEY
from openpilot.starpilot.conditional_mode.status import settings_fingerprint
from openpilot.starpilot.conditional_mode.tests.test_projection import BOOT, MONO, FakeSubMaster, serialized_scene
from openpilot.starpilot.speed_limits.runtime import Action as SlcAction, CruiseEvent as SlcCruiseEvent
class TestConditionalRuntimeLoop(unittest.TestCase):
def test_slc_unit_change_keeps_receipts_for_the_current_command(self):
from openpilot.selfdrive.car.cruise import slc_target_kph
def test_slc_unit_change_preserves_cap_and_queued_driver_event(self):
from openpilot.starpilot.speed_limits import offset_document as od
from openpilot.starpilot.speed_limits.runtime import Action, CruiseEvent
from openpilot.starpilot.speed_limits.tests.test_runtime_replay import RuntimeReplayTests
fixture = RuntimeReplayTests()
from openpilot.starpilot.speed_limits.tests import test_runtime_replay as replay
fixture = replay.RuntimeReplayTests()
fixture.setUp()
sm, cp = fixture.sm, fixture.cp
with tempfile.TemporaryDirectory() as directory:
params = Params(directory)
params.put_bool('SpeedLimitController', True, block=True)
params.put_bool('SLCConfirmation', True, block=True)
params.put('SLCConfirmation', 1, block=True)
params.put('SLCPriority1', 'Dashboard', block=True)
params.put('SLCOffsetSchedule', od.to_value(od.default_document(False)), block=True)
owner = plannerd.SavedSlcLifecycle(params, cp, {})
@@ -46,20 +46,19 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
sm.advance(2_900_000_000)
pending = runtime.step(sm, cp, now_ns=2_900_000_000).message.slcState
sm.advance(2_950_000_000)
issued = runtime.step(sm, cp, now_ns=2_950_000_000,
accepted = runtime.step(sm, cp, now_ns=2_950_000_000,
request=Action(runtime.session_id, 1, pending.decisionId, pending.presentationId, 'accept'))
command = issued.command.slcCruiseCommand
selected = slc_target_kph(command.targetMps, False)
receipts = deque([CruiseEvent(1, 2_990_000_000, 60 / 3.6, selected / 3.6, '', 'card-producer',
2_990_000_000, 'commandApplied', runtime.session_id, command.decisionId, command.presentationId, command.commandId)])
self.assertEqual(accepted.result.ceiling.speed_mps, 25.)
events = deque([CruiseEvent(1, 2_990_000_000, 60/3.6, 65/3.6, 'accel', 'card-producer', 2_990_000_000)])
params.put_bool('IsMetric', True, block=True)
metric, _ = owner.refresh(3_000_000_000, pending_cruise=receipts)
self.assertEqual(len(receipts), 1)
sm['carState'].vCruise = sm['carState'].vCruiseCluster = selected
metric, _ = owner.refresh(3_000_000_000, pending_cruise=events)
self.assertIs(metric, runtime)
self.assertEqual(len(events), 1)
sm['carState'].vCruise = sm['carState'].vCruiseCluster = 65.
sm.advance(3_000_000_000)
applied = metric.step(sm, cp, now_ns=3_000_000_000, cruise_event=receipts.popleft())
self.assertEqual(applied.message.slcState.commandStatus, 'applied')
self.assertIsNone(applied.command)
output = metric.step(sm, cp, now_ns=3_000_000_000, cruise_event=events.popleft())
self.assertEqual(output.result.ceiling.speed_mps, 25.)
self.assertEqual(sm['carState'].vCruise, 65.)
def test_slc_unit_changes_preserve_set_speed_and_driver_choices(self):
from openpilot.common.constants import CV
@@ -68,14 +67,14 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
from openpilot.starpilot.speed_limits.runtime import Action, CruiseEvent
from openpilot.starpilot.speed_limits.tests.test_runtime_replay import RuntimeReplayTests
now = 2_000_000_000
for confirmation, choice in ((False, 'applied'), (True, 'applied'), (False, 'override'), (True, 'decline')):
for confirmation, choice in ((False, 'applied'), (True, 'applied'), (False, 'ceiling'), (True, 'decline')):
with self.subTest(confirmation=confirmation, choice=choice), tempfile.TemporaryDirectory() as directory:
fixture = RuntimeReplayTests()
fixture.setUp()
sm, cp = fixture.sm, fixture.cp
params = Params(directory)
params.put_bool('SpeedLimitController', True, block=True)
params.put_bool('SLCConfirmation', confirmation, block=True)
params.put('SLCConfirmation', int(confirmation), block=True)
params.put('SLCPriority1', 'Dashboard', block=True)
params.put('SLCOffsetSchedule', od.to_value(od.default_document(False)), block=True)
owner = plannerd.SavedSlcLifecycle(params, cp, {})
@@ -84,14 +83,14 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
baseline = slc_target_kph(65 * CV.MPH_TO_MS, False)
sm['carState'].vCruise = sm['carState'].vCruiseCluster = baseline
aligned = original.step(sm, cp, now_ns=now)
self.assertIsNone(aligned.command)
if choice == 'override':
self.assertTrue(aligned.message.slcState.hasAccepted)
if choice == 'ceiling':
selected = slc_target_kph(70 * CV.MPH_TO_MS, False)
sm['carState'].vCruise = sm['carState'].vCruiseCluster = selected
sm.advance(now + 50_000_000)
chosen = original.step(sm, cp, now_ns=now + 50_000_000, cruise_event=CruiseEvent(
1, now + 50_000_000, baseline / 3.6, selected / 3.6, 'accel', 'card-producer', now + 50_000_000))
self.assertTrue(chosen.message.slcState.driverOverrideActive)
self.assertFalse(chosen.message.slcState.driverOverrideActive)
elif choice == 'decline':
sm['slcDashboardObservation'].speedMps = 55 * CV.MPH_TO_MS
sm.advance(now + 50_000_000)
@@ -105,11 +104,10 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
self.assertTrue(changed)
sm.advance(now + 1_000_000_000)
output = metric.step(sm, cp, now_ns=now + 1_000_000_000)
self.assertIsNone(output.command)
self.assertFalse(output.message.slcState.hasPending)
self.assertTrue(output.message.slcState.hasAccepted)
self.assertEqual(sm['carState'].vCruise, selected)
self.assertEqual(output.message.slcState.driverOverrideActive, choice == 'override')
self.assertFalse(output.message.slcState.driverOverrideActive)
if choice == 'decline':
self.assertEqual(output.result.acceptance.basis, 'rejected')
@@ -122,13 +120,11 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
owner = plannerd.SavedSlcLifecycle(params, cp, {})
imperial, _ = owner.refresh(MONO)
self.assertFalse(imperial.is_metric)
imperial.set_speed_after_ns = MONO + 1_050_000_000
actions, cruise = deque([object()]), deque([object()])
params.put_bool('IsMetric', True, block=True)
metric, changed = owner.refresh(MONO + 1_000_000_000, pending_actions=actions, pending_cruise=cruise)
self.assertTrue(changed)
self.assertTrue(metric.is_metric)
self.assertEqual(metric.set_speed_after_ns, MONO + 1_050_000_000)
self.assertIs(metric, imperial)
self.assertFalse(actions)
self.assertEqual(len(cruise), 1)
@@ -168,7 +164,7 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
# Status and transport are reserved independently of saved SLC/CEM
# admission, so a later saved change can attach without restarting.
self.assertEqual(publisher.call_args.args[0], ['longitudinalPlan', 'starpilotLongitudinalPlan',
'driverAssistance', 'slcState', 'slcCruiseCommand'])
'driverAssistance', 'slcState'])
self.assertEqual(conditional_ctor.call_count, int(enabled))
if enabled:
self.assertIs(conditional_ctor.call_args.args[0], params)
@@ -200,8 +196,8 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
self.assertFalse(vision.vision_control_qualified)
self.assertEqual(vision.settings.selection.slots[0], Source.VISION)
session = vision.session_id
pending_actions = deque([(3_000_000_000, plannerd.SlcAction(session, 1, 1, 1, 'accept'))])
pending_cruise = deque([plannerd.SlcCruiseEvent(1, 3_000_000_000, 20., 21., 'set', session_id=session)])
pending_actions = deque([(3_000_000_000, SlcAction(session, 1, 1, 1, 'accept'))])
pending_cruise = deque([SlcCruiseEvent(1, 3_000_000_000, 20., 21., 'set', session_id=session)])
self.assertEqual(owner.refresh(3_050_000_000, pending_actions=pending_actions,
pending_cruise=pending_cruise), (vision, False))
self.assertEqual(len(pending_actions), 1)
@@ -217,8 +213,8 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
self.assertNotEqual(vision.session_id, session) # Retired session was reset.
params.put_bool('ShowSpeedLimits', False, block=True)
params.put_bool('SpeedLimitController', False, block=True)
pending_actions.append((4_050_000_000, plannerd.SlcAction(control.session_id, 1, 1, 1, 'accept')))
pending_cruise.append(plannerd.SlcCruiseEvent(2, 4_050_000_000, 20., 21., 'set', session_id=control.session_id))
pending_actions.append((4_050_000_000, SlcAction(control.session_id, 1, 1, 1, 'accept')))
pending_cruise.append(SlcCruiseEvent(2, 4_050_000_000, 20., 21., 'set', session_id=control.session_id))
self.assertEqual(owner.refresh(5_000_000_000, pending_actions=pending_actions,
pending_cruise=pending_cruise), (None, True))
self.assertFalse(pending_actions)
@@ -251,7 +247,7 @@ class TestConditionalRuntimeLoop(unittest.TestCase):
patch.object(messaging, 'SubMaster', return_value=sm) as subscriber, patch.object(messaging, 'sub_sock'):
with self.assertRaises(EndLoop):
plannerd.starpilot_main()
self.assertIn('slcCruiseCommand', publisher.call_args.args[0])
self.assertNotIn('slcCruiseCommand', publisher.call_args.args[0])
for service in ('slcDashboardObservation', 'slcVisionObservation'):
self.assertIn(service, subscriber.call_args.args[0])
for check in ('ignore_alive', 'ignore_valid', 'ignore_avg_freq'):
@@ -156,7 +156,7 @@ class CurveLoopTests(unittest.TestCase):
stamp = NOW + n * 50_000_000
sm = bus(stamp)
result = update_curve_frame(planner, sm, cp, stamp, host=host, cruise_ceiling=slc)
self.assertIsNotNone(result.ceiling_mps)
self.assertIsNone(result.ceiling_mps)
self.assertFalse(planner.last_curve_ceiling_applied)
self.assertFalse(host.runtime.was_controlling)
self.assertEqual(host.runtime.curve.revision, 0)
@@ -45,326 +45,326 @@ const static double MAHA_THRESH_31 = 3.8414588206941227;
* *
* This file is part of 'ekf' *
******************************************************************************/
void err_fun(double *nom_x, double *delta_x, double *out_7353202191454035649) {
out_7353202191454035649[0] = delta_x[0] + nom_x[0];
out_7353202191454035649[1] = delta_x[1] + nom_x[1];
out_7353202191454035649[2] = delta_x[2] + nom_x[2];
out_7353202191454035649[3] = delta_x[3] + nom_x[3];
out_7353202191454035649[4] = delta_x[4] + nom_x[4];
out_7353202191454035649[5] = delta_x[5] + nom_x[5];
out_7353202191454035649[6] = delta_x[6] + nom_x[6];
out_7353202191454035649[7] = delta_x[7] + nom_x[7];
out_7353202191454035649[8] = delta_x[8] + nom_x[8];
void err_fun(double *nom_x, double *delta_x, double *out_7567495437456270106) {
out_7567495437456270106[0] = delta_x[0] + nom_x[0];
out_7567495437456270106[1] = delta_x[1] + nom_x[1];
out_7567495437456270106[2] = delta_x[2] + nom_x[2];
out_7567495437456270106[3] = delta_x[3] + nom_x[3];
out_7567495437456270106[4] = delta_x[4] + nom_x[4];
out_7567495437456270106[5] = delta_x[5] + nom_x[5];
out_7567495437456270106[6] = delta_x[6] + nom_x[6];
out_7567495437456270106[7] = delta_x[7] + nom_x[7];
out_7567495437456270106[8] = delta_x[8] + nom_x[8];
}
void inv_err_fun(double *nom_x, double *true_x, double *out_5273149315250963980) {
out_5273149315250963980[0] = -nom_x[0] + true_x[0];
out_5273149315250963980[1] = -nom_x[1] + true_x[1];
out_5273149315250963980[2] = -nom_x[2] + true_x[2];
out_5273149315250963980[3] = -nom_x[3] + true_x[3];
out_5273149315250963980[4] = -nom_x[4] + true_x[4];
out_5273149315250963980[5] = -nom_x[5] + true_x[5];
out_5273149315250963980[6] = -nom_x[6] + true_x[6];
out_5273149315250963980[7] = -nom_x[7] + true_x[7];
out_5273149315250963980[8] = -nom_x[8] + true_x[8];
void inv_err_fun(double *nom_x, double *true_x, double *out_6277676899296321355) {
out_6277676899296321355[0] = -nom_x[0] + true_x[0];
out_6277676899296321355[1] = -nom_x[1] + true_x[1];
out_6277676899296321355[2] = -nom_x[2] + true_x[2];
out_6277676899296321355[3] = -nom_x[3] + true_x[3];
out_6277676899296321355[4] = -nom_x[4] + true_x[4];
out_6277676899296321355[5] = -nom_x[5] + true_x[5];
out_6277676899296321355[6] = -nom_x[6] + true_x[6];
out_6277676899296321355[7] = -nom_x[7] + true_x[7];
out_6277676899296321355[8] = -nom_x[8] + true_x[8];
}
void H_mod_fun(double *state, double *out_5463203357507594110) {
out_5463203357507594110[0] = 1.0;
out_5463203357507594110[1] = 0.0;
out_5463203357507594110[2] = 0.0;
out_5463203357507594110[3] = 0.0;
out_5463203357507594110[4] = 0.0;
out_5463203357507594110[5] = 0.0;
out_5463203357507594110[6] = 0.0;
out_5463203357507594110[7] = 0.0;
out_5463203357507594110[8] = 0.0;
out_5463203357507594110[9] = 0.0;
out_5463203357507594110[10] = 1.0;
out_5463203357507594110[11] = 0.0;
out_5463203357507594110[12] = 0.0;
out_5463203357507594110[13] = 0.0;
out_5463203357507594110[14] = 0.0;
out_5463203357507594110[15] = 0.0;
out_5463203357507594110[16] = 0.0;
out_5463203357507594110[17] = 0.0;
out_5463203357507594110[18] = 0.0;
out_5463203357507594110[19] = 0.0;
out_5463203357507594110[20] = 1.0;
out_5463203357507594110[21] = 0.0;
out_5463203357507594110[22] = 0.0;
out_5463203357507594110[23] = 0.0;
out_5463203357507594110[24] = 0.0;
out_5463203357507594110[25] = 0.0;
out_5463203357507594110[26] = 0.0;
out_5463203357507594110[27] = 0.0;
out_5463203357507594110[28] = 0.0;
out_5463203357507594110[29] = 0.0;
out_5463203357507594110[30] = 1.0;
out_5463203357507594110[31] = 0.0;
out_5463203357507594110[32] = 0.0;
out_5463203357507594110[33] = 0.0;
out_5463203357507594110[34] = 0.0;
out_5463203357507594110[35] = 0.0;
out_5463203357507594110[36] = 0.0;
out_5463203357507594110[37] = 0.0;
out_5463203357507594110[38] = 0.0;
out_5463203357507594110[39] = 0.0;
out_5463203357507594110[40] = 1.0;
out_5463203357507594110[41] = 0.0;
out_5463203357507594110[42] = 0.0;
out_5463203357507594110[43] = 0.0;
out_5463203357507594110[44] = 0.0;
out_5463203357507594110[45] = 0.0;
out_5463203357507594110[46] = 0.0;
out_5463203357507594110[47] = 0.0;
out_5463203357507594110[48] = 0.0;
out_5463203357507594110[49] = 0.0;
out_5463203357507594110[50] = 1.0;
out_5463203357507594110[51] = 0.0;
out_5463203357507594110[52] = 0.0;
out_5463203357507594110[53] = 0.0;
out_5463203357507594110[54] = 0.0;
out_5463203357507594110[55] = 0.0;
out_5463203357507594110[56] = 0.0;
out_5463203357507594110[57] = 0.0;
out_5463203357507594110[58] = 0.0;
out_5463203357507594110[59] = 0.0;
out_5463203357507594110[60] = 1.0;
out_5463203357507594110[61] = 0.0;
out_5463203357507594110[62] = 0.0;
out_5463203357507594110[63] = 0.0;
out_5463203357507594110[64] = 0.0;
out_5463203357507594110[65] = 0.0;
out_5463203357507594110[66] = 0.0;
out_5463203357507594110[67] = 0.0;
out_5463203357507594110[68] = 0.0;
out_5463203357507594110[69] = 0.0;
out_5463203357507594110[70] = 1.0;
out_5463203357507594110[71] = 0.0;
out_5463203357507594110[72] = 0.0;
out_5463203357507594110[73] = 0.0;
out_5463203357507594110[74] = 0.0;
out_5463203357507594110[75] = 0.0;
out_5463203357507594110[76] = 0.0;
out_5463203357507594110[77] = 0.0;
out_5463203357507594110[78] = 0.0;
out_5463203357507594110[79] = 0.0;
out_5463203357507594110[80] = 1.0;
void H_mod_fun(double *state, double *out_7420380263460044385) {
out_7420380263460044385[0] = 1.0;
out_7420380263460044385[1] = 0.0;
out_7420380263460044385[2] = 0.0;
out_7420380263460044385[3] = 0.0;
out_7420380263460044385[4] = 0.0;
out_7420380263460044385[5] = 0.0;
out_7420380263460044385[6] = 0.0;
out_7420380263460044385[7] = 0.0;
out_7420380263460044385[8] = 0.0;
out_7420380263460044385[9] = 0.0;
out_7420380263460044385[10] = 1.0;
out_7420380263460044385[11] = 0.0;
out_7420380263460044385[12] = 0.0;
out_7420380263460044385[13] = 0.0;
out_7420380263460044385[14] = 0.0;
out_7420380263460044385[15] = 0.0;
out_7420380263460044385[16] = 0.0;
out_7420380263460044385[17] = 0.0;
out_7420380263460044385[18] = 0.0;
out_7420380263460044385[19] = 0.0;
out_7420380263460044385[20] = 1.0;
out_7420380263460044385[21] = 0.0;
out_7420380263460044385[22] = 0.0;
out_7420380263460044385[23] = 0.0;
out_7420380263460044385[24] = 0.0;
out_7420380263460044385[25] = 0.0;
out_7420380263460044385[26] = 0.0;
out_7420380263460044385[27] = 0.0;
out_7420380263460044385[28] = 0.0;
out_7420380263460044385[29] = 0.0;
out_7420380263460044385[30] = 1.0;
out_7420380263460044385[31] = 0.0;
out_7420380263460044385[32] = 0.0;
out_7420380263460044385[33] = 0.0;
out_7420380263460044385[34] = 0.0;
out_7420380263460044385[35] = 0.0;
out_7420380263460044385[36] = 0.0;
out_7420380263460044385[37] = 0.0;
out_7420380263460044385[38] = 0.0;
out_7420380263460044385[39] = 0.0;
out_7420380263460044385[40] = 1.0;
out_7420380263460044385[41] = 0.0;
out_7420380263460044385[42] = 0.0;
out_7420380263460044385[43] = 0.0;
out_7420380263460044385[44] = 0.0;
out_7420380263460044385[45] = 0.0;
out_7420380263460044385[46] = 0.0;
out_7420380263460044385[47] = 0.0;
out_7420380263460044385[48] = 0.0;
out_7420380263460044385[49] = 0.0;
out_7420380263460044385[50] = 1.0;
out_7420380263460044385[51] = 0.0;
out_7420380263460044385[52] = 0.0;
out_7420380263460044385[53] = 0.0;
out_7420380263460044385[54] = 0.0;
out_7420380263460044385[55] = 0.0;
out_7420380263460044385[56] = 0.0;
out_7420380263460044385[57] = 0.0;
out_7420380263460044385[58] = 0.0;
out_7420380263460044385[59] = 0.0;
out_7420380263460044385[60] = 1.0;
out_7420380263460044385[61] = 0.0;
out_7420380263460044385[62] = 0.0;
out_7420380263460044385[63] = 0.0;
out_7420380263460044385[64] = 0.0;
out_7420380263460044385[65] = 0.0;
out_7420380263460044385[66] = 0.0;
out_7420380263460044385[67] = 0.0;
out_7420380263460044385[68] = 0.0;
out_7420380263460044385[69] = 0.0;
out_7420380263460044385[70] = 1.0;
out_7420380263460044385[71] = 0.0;
out_7420380263460044385[72] = 0.0;
out_7420380263460044385[73] = 0.0;
out_7420380263460044385[74] = 0.0;
out_7420380263460044385[75] = 0.0;
out_7420380263460044385[76] = 0.0;
out_7420380263460044385[77] = 0.0;
out_7420380263460044385[78] = 0.0;
out_7420380263460044385[79] = 0.0;
out_7420380263460044385[80] = 1.0;
}
void f_fun(double *state, double dt, double *out_6125045598488451197) {
out_6125045598488451197[0] = state[0];
out_6125045598488451197[1] = state[1];
out_6125045598488451197[2] = state[2];
out_6125045598488451197[3] = state[3];
out_6125045598488451197[4] = state[4];
out_6125045598488451197[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5];
out_6125045598488451197[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6];
out_6125045598488451197[7] = state[7];
out_6125045598488451197[8] = state[8];
void f_fun(double *state, double dt, double *out_3045596088569131513) {
out_3045596088569131513[0] = state[0];
out_3045596088569131513[1] = state[1];
out_3045596088569131513[2] = state[2];
out_3045596088569131513[3] = state[3];
out_3045596088569131513[4] = state[4];
out_3045596088569131513[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5];
out_3045596088569131513[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6];
out_3045596088569131513[7] = state[7];
out_3045596088569131513[8] = state[8];
}
void F_fun(double *state, double dt, double *out_548031277288491442) {
out_548031277288491442[0] = 1;
out_548031277288491442[1] = 0;
out_548031277288491442[2] = 0;
out_548031277288491442[3] = 0;
out_548031277288491442[4] = 0;
out_548031277288491442[5] = 0;
out_548031277288491442[6] = 0;
out_548031277288491442[7] = 0;
out_548031277288491442[8] = 0;
out_548031277288491442[9] = 0;
out_548031277288491442[10] = 1;
out_548031277288491442[11] = 0;
out_548031277288491442[12] = 0;
out_548031277288491442[13] = 0;
out_548031277288491442[14] = 0;
out_548031277288491442[15] = 0;
out_548031277288491442[16] = 0;
out_548031277288491442[17] = 0;
out_548031277288491442[18] = 0;
out_548031277288491442[19] = 0;
out_548031277288491442[20] = 1;
out_548031277288491442[21] = 0;
out_548031277288491442[22] = 0;
out_548031277288491442[23] = 0;
out_548031277288491442[24] = 0;
out_548031277288491442[25] = 0;
out_548031277288491442[26] = 0;
out_548031277288491442[27] = 0;
out_548031277288491442[28] = 0;
out_548031277288491442[29] = 0;
out_548031277288491442[30] = 1;
out_548031277288491442[31] = 0;
out_548031277288491442[32] = 0;
out_548031277288491442[33] = 0;
out_548031277288491442[34] = 0;
out_548031277288491442[35] = 0;
out_548031277288491442[36] = 0;
out_548031277288491442[37] = 0;
out_548031277288491442[38] = 0;
out_548031277288491442[39] = 0;
out_548031277288491442[40] = 1;
out_548031277288491442[41] = 0;
out_548031277288491442[42] = 0;
out_548031277288491442[43] = 0;
out_548031277288491442[44] = 0;
out_548031277288491442[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4]));
out_548031277288491442[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2));
out_548031277288491442[47] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_548031277288491442[48] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_548031277288491442[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2)));
out_548031277288491442[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1;
out_548031277288491442[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]));
out_548031277288491442[52] = dt*stiffness_front*state[0]/(mass*state[1]);
out_548031277288491442[53] = -9.8100000000000005*dt;
out_548031277288491442[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4]));
out_548031277288491442[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2));
out_548031277288491442[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_548031277288491442[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_548031277288491442[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2)));
out_548031277288491442[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]);
out_548031277288491442[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1;
out_548031277288491442[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_548031277288491442[62] = 0;
out_548031277288491442[63] = 0;
out_548031277288491442[64] = 0;
out_548031277288491442[65] = 0;
out_548031277288491442[66] = 0;
out_548031277288491442[67] = 0;
out_548031277288491442[68] = 0;
out_548031277288491442[69] = 0;
out_548031277288491442[70] = 1;
out_548031277288491442[71] = 0;
out_548031277288491442[72] = 0;
out_548031277288491442[73] = 0;
out_548031277288491442[74] = 0;
out_548031277288491442[75] = 0;
out_548031277288491442[76] = 0;
out_548031277288491442[77] = 0;
out_548031277288491442[78] = 0;
out_548031277288491442[79] = 0;
out_548031277288491442[80] = 1;
void F_fun(double *state, double dt, double *out_8700545601531255316) {
out_8700545601531255316[0] = 1;
out_8700545601531255316[1] = 0;
out_8700545601531255316[2] = 0;
out_8700545601531255316[3] = 0;
out_8700545601531255316[4] = 0;
out_8700545601531255316[5] = 0;
out_8700545601531255316[6] = 0;
out_8700545601531255316[7] = 0;
out_8700545601531255316[8] = 0;
out_8700545601531255316[9] = 0;
out_8700545601531255316[10] = 1;
out_8700545601531255316[11] = 0;
out_8700545601531255316[12] = 0;
out_8700545601531255316[13] = 0;
out_8700545601531255316[14] = 0;
out_8700545601531255316[15] = 0;
out_8700545601531255316[16] = 0;
out_8700545601531255316[17] = 0;
out_8700545601531255316[18] = 0;
out_8700545601531255316[19] = 0;
out_8700545601531255316[20] = 1;
out_8700545601531255316[21] = 0;
out_8700545601531255316[22] = 0;
out_8700545601531255316[23] = 0;
out_8700545601531255316[24] = 0;
out_8700545601531255316[25] = 0;
out_8700545601531255316[26] = 0;
out_8700545601531255316[27] = 0;
out_8700545601531255316[28] = 0;
out_8700545601531255316[29] = 0;
out_8700545601531255316[30] = 1;
out_8700545601531255316[31] = 0;
out_8700545601531255316[32] = 0;
out_8700545601531255316[33] = 0;
out_8700545601531255316[34] = 0;
out_8700545601531255316[35] = 0;
out_8700545601531255316[36] = 0;
out_8700545601531255316[37] = 0;
out_8700545601531255316[38] = 0;
out_8700545601531255316[39] = 0;
out_8700545601531255316[40] = 1;
out_8700545601531255316[41] = 0;
out_8700545601531255316[42] = 0;
out_8700545601531255316[43] = 0;
out_8700545601531255316[44] = 0;
out_8700545601531255316[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4]));
out_8700545601531255316[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2));
out_8700545601531255316[47] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_8700545601531255316[48] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_8700545601531255316[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2)));
out_8700545601531255316[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1;
out_8700545601531255316[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]));
out_8700545601531255316[52] = dt*stiffness_front*state[0]/(mass*state[1]);
out_8700545601531255316[53] = -9.8100000000000005*dt;
out_8700545601531255316[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4]));
out_8700545601531255316[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2));
out_8700545601531255316[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_8700545601531255316[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_8700545601531255316[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2)));
out_8700545601531255316[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]);
out_8700545601531255316[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1;
out_8700545601531255316[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_8700545601531255316[62] = 0;
out_8700545601531255316[63] = 0;
out_8700545601531255316[64] = 0;
out_8700545601531255316[65] = 0;
out_8700545601531255316[66] = 0;
out_8700545601531255316[67] = 0;
out_8700545601531255316[68] = 0;
out_8700545601531255316[69] = 0;
out_8700545601531255316[70] = 1;
out_8700545601531255316[71] = 0;
out_8700545601531255316[72] = 0;
out_8700545601531255316[73] = 0;
out_8700545601531255316[74] = 0;
out_8700545601531255316[75] = 0;
out_8700545601531255316[76] = 0;
out_8700545601531255316[77] = 0;
out_8700545601531255316[78] = 0;
out_8700545601531255316[79] = 0;
out_8700545601531255316[80] = 1;
}
void h_25(double *state, double *unused, double *out_7825793012049214991) {
out_7825793012049214991[0] = state[6];
void h_25(double *state, double *unused, double *out_2824946100795669147) {
out_2824946100795669147[0] = state[6];
}
void H_25(double *state, double *unused, double *out_8986251996152959258) {
out_8986251996152959258[0] = 0;
out_8986251996152959258[1] = 0;
out_8986251996152959258[2] = 0;
out_8986251996152959258[3] = 0;
out_8986251996152959258[4] = 0;
out_8986251996152959258[5] = 0;
out_8986251996152959258[6] = 1;
out_8986251996152959258[7] = 0;
out_8986251996152959258[8] = 0;
void H_25(double *state, double *unused, double *out_2516688438275379545) {
out_2516688438275379545[0] = 0;
out_2516688438275379545[1] = 0;
out_2516688438275379545[2] = 0;
out_2516688438275379545[3] = 0;
out_2516688438275379545[4] = 0;
out_2516688438275379545[5] = 0;
out_2516688438275379545[6] = 1;
out_2516688438275379545[7] = 0;
out_2516688438275379545[8] = 0;
}
void h_24(double *state, double *unused, double *out_3863850671382452828) {
out_3863850671382452828[0] = state[4];
out_3863850671382452828[1] = state[5];
void h_24(double *state, double *unused, double *out_3049085865514909722) {
out_3049085865514909722[0] = state[4];
out_3049085865514909722[1] = state[5];
}
void H_24(double *state, double *unused, double *out_5428326397350636122) {
out_5428326397350636122[0] = 0;
out_5428326397350636122[1] = 0;
out_5428326397350636122[2] = 0;
out_5428326397350636122[3] = 0;
out_5428326397350636122[4] = 1;
out_5428326397350636122[5] = 0;
out_5428326397350636122[6] = 0;
out_5428326397350636122[7] = 0;
out_5428326397350636122[8] = 0;
out_5428326397350636122[9] = 0;
out_5428326397350636122[10] = 0;
out_5428326397350636122[11] = 0;
out_5428326397350636122[12] = 0;
out_5428326397350636122[13] = 0;
out_5428326397350636122[14] = 1;
out_5428326397350636122[15] = 0;
out_5428326397350636122[16] = 0;
out_5428326397350636122[17] = 0;
void H_24(double *state, double *unused, double *out_339474014668229572) {
out_339474014668229572[0] = 0;
out_339474014668229572[1] = 0;
out_339474014668229572[2] = 0;
out_339474014668229572[3] = 0;
out_339474014668229572[4] = 1;
out_339474014668229572[5] = 0;
out_339474014668229572[6] = 0;
out_339474014668229572[7] = 0;
out_339474014668229572[8] = 0;
out_339474014668229572[9] = 0;
out_339474014668229572[10] = 0;
out_339474014668229572[11] = 0;
out_339474014668229572[12] = 0;
out_339474014668229572[13] = 0;
out_339474014668229572[14] = 1;
out_339474014668229572[15] = 0;
out_339474014668229572[16] = 0;
out_339474014668229572[17] = 0;
}
void h_30(double *state, double *unused, double *out_3653635896979304131) {
out_3653635896979304131[0] = state[4];
void h_30(double *state, double *unused, double *out_6312771183888182035) {
out_6312771183888182035[0] = state[4];
}
void H_30(double *state, double *unused, double *out_6467919037645710631) {
out_6467919037645710631[0] = 0;
out_6467919037645710631[1] = 0;
out_6467919037645710631[2] = 0;
out_6467919037645710631[3] = 0;
out_6467919037645710631[4] = 1;
out_6467919037645710631[5] = 0;
out_6467919037645710631[6] = 0;
out_6467919037645710631[7] = 0;
out_6467919037645710631[8] = 0;
void H_30(double *state, double *unused, double *out_1644520231869082) {
out_1644520231869082[0] = 0;
out_1644520231869082[1] = 0;
out_1644520231869082[2] = 0;
out_1644520231869082[3] = 0;
out_1644520231869082[4] = 1;
out_1644520231869082[5] = 0;
out_1644520231869082[6] = 0;
out_1644520231869082[7] = 0;
out_1644520231869082[8] = 0;
}
void h_26(double *state, double *unused, double *out_8655309164640149776) {
out_8655309164640149776[0] = state[7];
void h_26(double *state, double *unused, double *out_6908065673986044634) {
out_6908065673986044634[0] = state[7];
}
void H_26(double *state, double *unused, double *out_5718988758682536134) {
out_5718988758682536134[0] = 0;
out_5718988758682536134[1] = 0;
out_5718988758682536134[2] = 0;
out_5718988758682536134[3] = 0;
out_5718988758682536134[4] = 0;
out_5718988758682536134[5] = 0;
out_5718988758682536134[6] = 0;
out_5718988758682536134[7] = 1;
out_5718988758682536134[8] = 0;
void H_26(double *state, double *unused, double *out_6258191757149435769) {
out_6258191757149435769[0] = 0;
out_6258191757149435769[1] = 0;
out_6258191757149435769[2] = 0;
out_6258191757149435769[3] = 0;
out_6258191757149435769[4] = 0;
out_6258191757149435769[5] = 0;
out_6258191757149435769[6] = 0;
out_6258191757149435769[7] = 1;
out_6258191757149435769[8] = 0;
}
void h_27(double *state, double *unused, double *out_5968325673074535865) {
out_5968325673074535865[0] = state[3];
void h_27(double *state, double *unused, double *out_709679758613972836) {
out_709679758613972836[0] = state[3];
}
void H_27(double *state, double *unused, double *out_4244324966461767414) {
out_4244324966461767414[0] = 0;
out_4244324966461767414[1] = 0;
out_4244324966461767414[2] = 0;
out_4244324966461767414[3] = 1;
out_4244324966461767414[4] = 0;
out_4244324966461767414[5] = 0;
out_4244324966461767414[6] = 0;
out_4244324966461767414[7] = 0;
out_4244324966461767414[8] = 0;
void H_27(double *state, double *unused, double *out_2173118791568555829) {
out_2173118791568555829[0] = 0;
out_2173118791568555829[1] = 0;
out_2173118791568555829[2] = 0;
out_2173118791568555829[3] = 1;
out_2173118791568555829[4] = 0;
out_2173118791568555829[5] = 0;
out_2173118791568555829[6] = 0;
out_2173118791568555829[7] = 0;
out_2173118791568555829[8] = 0;
}
void h_29(double *state, double *unused, double *out_2671551803644006019) {
out_2671551803644006019[0] = state[1];
void h_29(double *state, double *unused, double *out_6982675176999014474) {
out_6982675176999014474[0] = state[1];
}
void H_29(double *state, double *unused, double *out_5957687693331318447) {
out_5957687693331318447[0] = 0;
out_5957687693331318447[1] = 1;
out_5957687693331318447[2] = 0;
out_5957687693331318447[3] = 0;
out_5957687693331318447[4] = 0;
out_5957687693331318447[5] = 0;
out_5957687693331318447[6] = 0;
out_5957687693331318447[7] = 0;
out_5957687693331318447[8] = 0;
void H_29(double *state, double *unused, double *out_511875864546261266) {
out_511875864546261266[0] = 0;
out_511875864546261266[1] = 1;
out_511875864546261266[2] = 0;
out_511875864546261266[3] = 0;
out_511875864546261266[4] = 0;
out_511875864546261266[5] = 0;
out_511875864546261266[6] = 0;
out_511875864546261266[7] = 0;
out_511875864546261266[8] = 0;
}
void h_28(double *state, double *unused, double *out_3845497107703219871) {
out_3845497107703219871[0] = state[0];
void h_28(double *state, double *unused, double *out_2400127593349653796) {
out_2400127593349653796[0] = state[0];
}
void H_28(double *state, double *unused, double *out_7406657363308702595) {
out_7406657363308702595[0] = 1;
out_7406657363308702595[1] = 0;
out_7406657363308702595[2] = 0;
out_7406657363308702595[3] = 0;
out_7406657363308702595[4] = 0;
out_7406657363308702595[5] = 0;
out_7406657363308702595[6] = 0;
out_7406657363308702595[7] = 0;
out_7406657363308702595[8] = 0;
void H_28(double *state, double *unused, double *out_4570523152523269308) {
out_4570523152523269308[0] = 1;
out_4570523152523269308[1] = 0;
out_4570523152523269308[2] = 0;
out_4570523152523269308[3] = 0;
out_4570523152523269308[4] = 0;
out_4570523152523269308[5] = 0;
out_4570523152523269308[6] = 0;
out_4570523152523269308[7] = 0;
out_4570523152523269308[8] = 0;
}
void h_31(double *state, double *unused, double *out_1981073584941794741) {
out_1981073584941794741[0] = state[8];
void h_31(double *state, double *unused, double *out_1304974248711835435) {
out_1304974248711835435[0] = state[8];
}
void H_31(double *state, double *unused, double *out_8955606034275998830) {
out_8955606034275998830[0] = 0;
out_8955606034275998830[1] = 0;
out_8955606034275998830[2] = 0;
out_8955606034275998830[3] = 0;
out_8955606034275998830[4] = 0;
out_8955606034275998830[5] = 0;
out_8955606034275998830[6] = 0;
out_8955606034275998830[7] = 0;
out_8955606034275998830[8] = 1;
void H_31(double *state, double *unused, double *out_6884399859382787245) {
out_6884399859382787245[0] = 0;
out_6884399859382787245[1] = 0;
out_6884399859382787245[2] = 0;
out_6884399859382787245[3] = 0;
out_6884399859382787245[4] = 0;
out_6884399859382787245[5] = 0;
out_6884399859382787245[6] = 0;
out_6884399859382787245[7] = 0;
out_6884399859382787245[8] = 1;
}
#include <eigen3/Eigen/Dense>
#include <iostream>
@@ -518,68 +518,68 @@ void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_31, H_31, NULL, in_z, in_R, in_ea, MAHA_THRESH_31);
}
void car_err_fun(double *nom_x, double *delta_x, double *out_7353202191454035649) {
err_fun(nom_x, delta_x, out_7353202191454035649);
void car_err_fun(double *nom_x, double *delta_x, double *out_7567495437456270106) {
err_fun(nom_x, delta_x, out_7567495437456270106);
}
void car_inv_err_fun(double *nom_x, double *true_x, double *out_5273149315250963980) {
inv_err_fun(nom_x, true_x, out_5273149315250963980);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_6277676899296321355) {
inv_err_fun(nom_x, true_x, out_6277676899296321355);
}
void car_H_mod_fun(double *state, double *out_5463203357507594110) {
H_mod_fun(state, out_5463203357507594110);
void car_H_mod_fun(double *state, double *out_7420380263460044385) {
H_mod_fun(state, out_7420380263460044385);
}
void car_f_fun(double *state, double dt, double *out_6125045598488451197) {
f_fun(state, dt, out_6125045598488451197);
void car_f_fun(double *state, double dt, double *out_3045596088569131513) {
f_fun(state, dt, out_3045596088569131513);
}
void car_F_fun(double *state, double dt, double *out_548031277288491442) {
F_fun(state, dt, out_548031277288491442);
void car_F_fun(double *state, double dt, double *out_8700545601531255316) {
F_fun(state, dt, out_8700545601531255316);
}
void car_h_25(double *state, double *unused, double *out_7825793012049214991) {
h_25(state, unused, out_7825793012049214991);
void car_h_25(double *state, double *unused, double *out_2824946100795669147) {
h_25(state, unused, out_2824946100795669147);
}
void car_H_25(double *state, double *unused, double *out_8986251996152959258) {
H_25(state, unused, out_8986251996152959258);
void car_H_25(double *state, double *unused, double *out_2516688438275379545) {
H_25(state, unused, out_2516688438275379545);
}
void car_h_24(double *state, double *unused, double *out_3863850671382452828) {
h_24(state, unused, out_3863850671382452828);
void car_h_24(double *state, double *unused, double *out_3049085865514909722) {
h_24(state, unused, out_3049085865514909722);
}
void car_H_24(double *state, double *unused, double *out_5428326397350636122) {
H_24(state, unused, out_5428326397350636122);
void car_H_24(double *state, double *unused, double *out_339474014668229572) {
H_24(state, unused, out_339474014668229572);
}
void car_h_30(double *state, double *unused, double *out_3653635896979304131) {
h_30(state, unused, out_3653635896979304131);
void car_h_30(double *state, double *unused, double *out_6312771183888182035) {
h_30(state, unused, out_6312771183888182035);
}
void car_H_30(double *state, double *unused, double *out_6467919037645710631) {
H_30(state, unused, out_6467919037645710631);
void car_H_30(double *state, double *unused, double *out_1644520231869082) {
H_30(state, unused, out_1644520231869082);
}
void car_h_26(double *state, double *unused, double *out_8655309164640149776) {
h_26(state, unused, out_8655309164640149776);
void car_h_26(double *state, double *unused, double *out_6908065673986044634) {
h_26(state, unused, out_6908065673986044634);
}
void car_H_26(double *state, double *unused, double *out_5718988758682536134) {
H_26(state, unused, out_5718988758682536134);
void car_H_26(double *state, double *unused, double *out_6258191757149435769) {
H_26(state, unused, out_6258191757149435769);
}
void car_h_27(double *state, double *unused, double *out_5968325673074535865) {
h_27(state, unused, out_5968325673074535865);
void car_h_27(double *state, double *unused, double *out_709679758613972836) {
h_27(state, unused, out_709679758613972836);
}
void car_H_27(double *state, double *unused, double *out_4244324966461767414) {
H_27(state, unused, out_4244324966461767414);
void car_H_27(double *state, double *unused, double *out_2173118791568555829) {
H_27(state, unused, out_2173118791568555829);
}
void car_h_29(double *state, double *unused, double *out_2671551803644006019) {
h_29(state, unused, out_2671551803644006019);
void car_h_29(double *state, double *unused, double *out_6982675176999014474) {
h_29(state, unused, out_6982675176999014474);
}
void car_H_29(double *state, double *unused, double *out_5957687693331318447) {
H_29(state, unused, out_5957687693331318447);
void car_H_29(double *state, double *unused, double *out_511875864546261266) {
H_29(state, unused, out_511875864546261266);
}
void car_h_28(double *state, double *unused, double *out_3845497107703219871) {
h_28(state, unused, out_3845497107703219871);
void car_h_28(double *state, double *unused, double *out_2400127593349653796) {
h_28(state, unused, out_2400127593349653796);
}
void car_H_28(double *state, double *unused, double *out_7406657363308702595) {
H_28(state, unused, out_7406657363308702595);
void car_H_28(double *state, double *unused, double *out_4570523152523269308) {
H_28(state, unused, out_4570523152523269308);
}
void car_h_31(double *state, double *unused, double *out_1981073584941794741) {
h_31(state, unused, out_1981073584941794741);
void car_h_31(double *state, double *unused, double *out_1304974248711835435) {
h_31(state, unused, out_1304974248711835435);
}
void car_H_31(double *state, double *unused, double *out_8955606034275998830) {
H_31(state, unused, out_8955606034275998830);
void car_H_31(double *state, double *unused, double *out_6884399859382787245) {
H_31(state, unused, out_6884399859382787245);
}
void car_predict(double *in_x, double *in_P, double *in_Q, double dt) {
predict(in_x, in_P, in_Q, dt);
@@ -9,27 +9,27 @@ void car_update_27(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void car_update_29(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_err_fun(double *nom_x, double *delta_x, double *out_7353202191454035649);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_5273149315250963980);
void car_H_mod_fun(double *state, double *out_5463203357507594110);
void car_f_fun(double *state, double dt, double *out_6125045598488451197);
void car_F_fun(double *state, double dt, double *out_548031277288491442);
void car_h_25(double *state, double *unused, double *out_7825793012049214991);
void car_H_25(double *state, double *unused, double *out_8986251996152959258);
void car_h_24(double *state, double *unused, double *out_3863850671382452828);
void car_H_24(double *state, double *unused, double *out_5428326397350636122);
void car_h_30(double *state, double *unused, double *out_3653635896979304131);
void car_H_30(double *state, double *unused, double *out_6467919037645710631);
void car_h_26(double *state, double *unused, double *out_8655309164640149776);
void car_H_26(double *state, double *unused, double *out_5718988758682536134);
void car_h_27(double *state, double *unused, double *out_5968325673074535865);
void car_H_27(double *state, double *unused, double *out_4244324966461767414);
void car_h_29(double *state, double *unused, double *out_2671551803644006019);
void car_H_29(double *state, double *unused, double *out_5957687693331318447);
void car_h_28(double *state, double *unused, double *out_3845497107703219871);
void car_H_28(double *state, double *unused, double *out_7406657363308702595);
void car_h_31(double *state, double *unused, double *out_1981073584941794741);
void car_H_31(double *state, double *unused, double *out_8955606034275998830);
void car_err_fun(double *nom_x, double *delta_x, double *out_7567495437456270106);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_6277676899296321355);
void car_H_mod_fun(double *state, double *out_7420380263460044385);
void car_f_fun(double *state, double dt, double *out_3045596088569131513);
void car_F_fun(double *state, double dt, double *out_8700545601531255316);
void car_h_25(double *state, double *unused, double *out_2824946100795669147);
void car_H_25(double *state, double *unused, double *out_2516688438275379545);
void car_h_24(double *state, double *unused, double *out_3049085865514909722);
void car_H_24(double *state, double *unused, double *out_339474014668229572);
void car_h_30(double *state, double *unused, double *out_6312771183888182035);
void car_H_30(double *state, double *unused, double *out_1644520231869082);
void car_h_26(double *state, double *unused, double *out_6908065673986044634);
void car_H_26(double *state, double *unused, double *out_6258191757149435769);
void car_h_27(double *state, double *unused, double *out_709679758613972836);
void car_H_27(double *state, double *unused, double *out_2173118791568555829);
void car_h_29(double *state, double *unused, double *out_6982675176999014474);
void car_H_29(double *state, double *unused, double *out_511875864546261266);
void car_h_28(double *state, double *unused, double *out_2400127593349653796);
void car_H_28(double *state, double *unused, double *out_4570523152523269308);
void car_h_31(double *state, double *unused, double *out_1304974248711835435);
void car_H_31(double *state, double *unused, double *out_6884399859382787245);
void car_predict(double *in_x, double *in_P, double *in_Q, double dt);
void car_set_mass(double x);
void car_set_rotational_inertia(double x);
File diff suppressed because it is too large Load Diff
@@ -5,18 +5,18 @@ void pose_update_4(double *in_x, double *in_P, double *in_z, double *in_R, doubl
void pose_update_10(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_13(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_14(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_err_fun(double *nom_x, double *delta_x, double *out_2134324718959906218);
void pose_inv_err_fun(double *nom_x, double *true_x, double *out_7874553077816062449);
void pose_H_mod_fun(double *state, double *out_8129875391940694982);
void pose_f_fun(double *state, double dt, double *out_5920058179701921818);
void pose_F_fun(double *state, double dt, double *out_534012189001621552);
void pose_h_4(double *state, double *unused, double *out_6191552396506608484);
void pose_H_4(double *state, double *unused, double *out_4468786875016897913);
void pose_h_10(double *state, double *unused, double *out_8347226084972414573);
void pose_H_10(double *state, double *unused, double *out_4111169058602364742);
void pose_h_13(double *state, double *unused, double *out_2707578914264125574);
void pose_H_13(double *state, double *unused, double *out_7681060700349230714);
void pose_h_14(double *state, double *unused, double *out_4697260034120461682);
void pose_H_14(double *state, double *unused, double *out_8432027731356382442);
void pose_err_fun(double *nom_x, double *delta_x, double *out_4830772321857990181);
void pose_inv_err_fun(double *nom_x, double *true_x, double *out_5200017052910787602);
void pose_H_mod_fun(double *state, double *out_1712341249416054868);
void pose_f_fun(double *state, double dt, double *out_3100560613383761185);
void pose_F_fun(double *state, double dt, double *out_3350602915939425859);
void pose_h_4(double *state, double *unused, double *out_2074712223981570498);
void pose_H_4(double *state, double *unused, double *out_4135740557335903853);
void pose_h_10(double *state, double *unused, double *out_5949398724887603410);
void pose_H_10(double *state, double *unused, double *out_3347508424845693314);
void pose_h_13(double *state, double *unused, double *out_9220727019356339608);
void pose_H_13(double *state, double *unused, double *out_923466732003571052);
void pose_h_14(double *state, double *unused, double *out_8529780658774629922);
void pose_H_14(double *state, double *unused, double *out_172499700996419324);
void pose_predict(double *in_x, double *in_P, double *in_Q, double dt);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1352,27 +1352,6 @@ class SelfdriveD:
self.aol_fault_reason = 'availabilityPause'
self.aol_publication_retained_intent = intent
return
from openpilot.starpilot.car.toyota.aol import qualified as toyota_aol_qualified
companion = getattr(self, 'aol_intent_companion', None)
if (healthy_expiry and toyota_aol_qualified(self.CP, marked_only=True) and
not was_enabled and not self.active and not getattr(self, 'aol_ever_active', False) and
not any((previous.desired_lateral, previous.desired_longitudinal, previous.lateral_active,
previous.longitudinal_active, previous.native_acknowledged)) and
not self.aol_authority_lost and getattr(self, 'aol_fault_reason', 'none') == 'availabilityPause' and
availability_only(self.events.to_msg()) and not any(
event.userDisable and event.name != EventName.pcmDisable for event in self.events.to_msg()) and
intent is not None and intent is self.aol_last_intent and
intent.settingsQualified and intent.allowedLatch and intent.lateralArmed and
intent.carStateLogMonoTime == source_ns and
0 < intent.observedMonoTime <= now_ns <= intent.validUntilMonoTime and
companion is not None and companion.last_drain_ok and companion.session == intent.producerSessionId):
from openpilot.starpilot.aol.transport_pause import _healthy_transport_native
native = _healthy_transport_native(self.sm, self.CP, CS, self.aol_session_id, now_ns)
if native is not None and native.pandaInventory and not any((native.requestedLateral,
native.lateralAllowed, native.requestedLongitudinal, native.longitudinalAllowed)):
# Cold readiness has no granted command to lose; the expired source still withdraws all output.
self.aol_publication_retained_intent = intent
return
pause = getattr(self, 'aol_availability_pause', None)
if (healthy_expiry and pause is not None and intent is not None and
getattr(self, 'aol_fault_reason', 'none') in ('none', 'availabilityPause') and
@@ -98,7 +98,6 @@ class Plant:
# always start at current speed
model.modelV2.velocity.x = [float(self.speed)] + [float(self.speed + 0.5)] * (len(T_IDXS) - 1)
model.modelV2.acceleration.x = [0.0] * len(T_IDXS)
model.modelV2.meta.disengagePredictions.gasPressProbs = [float(prob_throttle)] * 6
control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off
ss.selfdriveState.experimentalMode = self.e2e
File diff suppressed because it is too large Load Diff
+7 -14
View File
@@ -34,18 +34,10 @@ def update_frame(preview=None, controllers=None, star=None, game_mode=None, arab
def main():
device_type = HARDWARE.get_device_type() if COMMA_HARDWARE else ""
camera_core_ui = device_type in ("mici", "tici", "tizi")
background_ui = device_type == "mici"
cores = {0, 1, 2, 7} if background_ui else {6} if camera_core_ui else {5}
display_nice = 5 if background_ui else 0
camera_core_ui = COMMA_HARDWARE and HARDWARE.get_device_type() in ("mici", "tici", "tizi")
cores = {6} if camera_core_ui else {5}
main_thread_configured = False
if background_ui:
drop_realtime()
os.setpriority(os.PRIO_PROCESS, 0, display_nice)
set_core_affinity(list(cores))
else:
config_realtime_process(0, Priority.UI)
config_realtime_process(0, Priority.UI)
selection = select_ui(Profile.LARGE if BIG_UI else Profile.COMPACT, os.environ)
print(f"UI: {'StarPilot' if selection.custom else 'upstream'} selected ({selection.reason})", file=sys.stderr, flush=True)
@@ -84,10 +76,11 @@ def main():
for should_render, frame_time, cpu_time in gui_app.render(before_frame=lambda: update_frame(preview, controllers, star, poll_game_mode, poll_arabesque)):
if should_render:
if camera_core_ui and not main_thread_configured:
# Display work stays below real-time driving and audio playback.
# Share the camera core with normal scheduling, without FIFO display
# work preempting camera work or waiting on the driving core.
drop_realtime()
os.setpriority(os.PRIO_PROCESS, 0, display_nice)
if os.sched_getscheduler(0) != os.SCHED_OTHER or os.getpriority(os.PRIO_PROCESS, 0) != display_nice:
os.setpriority(os.PRIO_PROCESS, 0, 0)
if os.sched_getscheduler(0) != os.SCHED_OTHER or os.getpriority(os.PRIO_PROCESS, 0) != 0:
raise RuntimeError("Could not configure display scheduling")
main_thread_configured = True
# reaffine after power save offlines our core
+6 -5
View File
@@ -16,6 +16,8 @@ GearShifter = car.CarState.GearShifter
AOL_TOGGLE = 9
PAUSE_LATERAL = 3
PAUSE_LONGITUDINAL = 4
# The only wheel action WheelPublisher delivers from cruise MAIN.
ADOPT_SPEED_LIMIT = 10
@dataclass(frozen=True)
class AolSettings:
@@ -28,6 +30,7 @@ class AolSettings:
mode_actions: tuple[int, int, int] = (0, 0, 0)
custom_actions: tuple[int, int, int] = (0, 0, 0)
default_physical_buttons: bool = True
main_wheel_action: bool = False
def _button_value(params, key: str) -> int | None:
@@ -64,6 +67,7 @@ def read_settings(params) -> AolSettings:
enabled=params.get_bool('AlwaysOnLateral') and valid_pause, pause_brake_mps=pause_mps if valid_pause else 0.0,
lkas_action=_axis_action(lkas), main_action=_axis_action(main),
default_physical_buttons=lkas == 0 and main == 0,
main_wheel_action=main == ADOPT_SPEED_LIMIT,
distance_actions=(_button_action(params, 'DistanceButtonControl'),
_button_action(params, 'LongDistanceButtonControl'),
_button_action(params, 'VeryLongDistanceButtonControl')),
@@ -241,10 +245,6 @@ class AolCardIntent:
return
if self._engagement_consumed or not active and not blocked:
return
if (not blocked and getattr(self, 'defer_full_engagement_on_gas', False) and CS.gasPressed and
CS.cruiseState.available and
CS.gearShifter not in (GearShifter.park, GearShifter.neutral, GearShifter.reverse, GearShifter.unknown)):
return
self._engagement_consumed = True
if (blocked or not active or not self.settings.enabled or not CS.cruiseState.available or
CS.gearShifter in (GearShifter.park, GearShifter.neutral, GearShifter.reverse, GearShifter.unknown)):
@@ -319,7 +319,8 @@ class AolCardIntent:
independent_buttons = self.independent_cruise_buttons and (main_managed or lkas_managed)
# The opt-in is bound to existing HKG torque owners. Other physical
# owners keep their current event and native contracts.
main_toggle = not independent_buttons or main_managed
main_toggle = (not independent_buttons or main_managed) and not (
self.independent_cruise_buttons and self.settings.main_wheel_action)
lkas_toggle = not independent_buttons or lkas_managed
previously_held = (self._main_held and main_toggle) or (self._lkas_held and lkas_toggle)
cancel_pressed = False
+9
View File
@@ -21,6 +21,15 @@ def _diagnostic_mask(cp):
int(cp.flags) & int(HondaFlags.BOSCH) and qualified(cp, marked_only=True)) else 0
if cp.brand == 'hyundai':
from opendbc.car.hyundai.values import CAR, HyundaiFlags
if cp.carFingerprint == CAR.HYUNDAI_ELANTRA_2021:
from opendbc.car.hyundai.classic_scc_aol import qualified
# Firmware latches this IRQ diagnostic without revoking native authority.
# Limit compatibility to the recorded stock-SCC profile; keep raw faults;
# native permission, receive health, relay and heartbeat remain required.
recorded_flags = HyundaiFlags.SEND_LFA | HyundaiFlags.USE_FCA | HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HAS_LDA_BUTTON
return (1 << 2) if (str(cp.fingerprintSource) == 'fw' and int(cp.flags) == int(recorded_flags) and
len(cp.safetyConfigs) == 1 and int(cp.safetyConfigs[0].safetyParam) == 0xC00 and
cp.alternativeExperience == 32 and qualified(cp, marked_only=True)) else 0
if cp.carFingerprint != CAR.HYUNDAI_SONATA_HYBRID:
return 0
from opendbc.car.hyundai.classic_scc_aol import qualified
@@ -1,4 +1,4 @@
"""Elantra CAN interrupt faults block steering regardless of otherwise valid authority."""
"""Exact stock-SCC Elantra IRQ diagnostics retain native authority vetoes."""
from contextlib import ExitStack
from dataclasses import replace
from unittest import mock
@@ -32,8 +32,7 @@ def recorded_cp():
@pytest.mark.parametrize('fault_source', ['none', 'panda', 'native', 'both'])
@pytest.mark.parametrize('full', [False, True])
@pytest.mark.parametrize('fault', ['none', 'rx', 'relay', 'denied', 'request', 'expired-wire'])
def test_recorded_irq_through_selfdrived_and_controls_blocks_faulted_steering(full, fault, fault_source):
irq = fault_source != 'none'
def test_recorded_irq_through_selfdrived_and_controls_keeps_native_vetoes(full, fault, fault_source):
cp = recorded_cp()
before = cp.to_dict()
case = fixture.IoniqAvailabilityTests(methodName='runTest')
@@ -70,20 +69,16 @@ def test_recorded_irq_through_selfdrived_and_controls_blocks_faulted_steering(fu
if fault == 'request':
options.update(native_request_lat=False, native_request_long=False)
axis, state, command = drive(tick, **options)
if fault == 'none' and not irq:
if fault == 'none':
assert axis.lateralActive and command.latActive, 'HEALTHY_ELANTRA_AUTHORITY_REJECTED'
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
native = current_native(sd.sm, sd.CP, now_ns=stamp, axis_session_id=sd.aol_session_id)
assert native is not None and native.pandaInventory[0].faults == 0
assert list(sd.sm['pandaStates'][0].faults) == []
assert native is not None and native.pandaInventory[0].faults == (4 if fault_source in ('native', 'both') else 0)
assert list(sd.sm['pandaStates'][0].faults) == ([log.PandaState.FaultType.interruptRateCan1] if fault_source in ('panda', 'both') else [])
assert not axis.longitudinalActive and not command.longActive
assert state.enabled == full
else:
assert not axis.lateralActive and not command.latActive, 'ELANTRA_HARDWARE_FAULT_MUST_BLOCK'
if irq and fault == 'none':
assert log.OnroadEvent.EventName.controlsMismatch in sd.events.names
expected_faults = [log.PandaState.FaultType.interruptRateCan1] if fault_source in ('panda', 'both') else []
assert list(sd.sm['pandaStates'][0].faults) == expected_faults
assert not axis.longitudinalActive and not command.longActive
assert cp.to_dict() == before
@@ -91,7 +86,7 @@ def test_recorded_irq_through_selfdrived_and_controls_blocks_faulted_steering(fu
@pytest.mark.parametrize('name,value', tuple(log.PandaState.FaultType.schema.enumerants.items()))
def test_other_and_mixed_faults_still_block(name, value):
cp = recorded_cp()
expected = True
expected = name != 'interruptRateCan1'
assert blocking_fault_mask(1 << value, cp) == expected
assert blocking_fault_mask((1 << value) | 4, cp) == expected
assert blocking_fault_list([value], cp) == expected
@@ -100,48 +100,3 @@ def test_ev6_card_cancel_join_requires_its_dedicated_physical_owner(restriction)
owner._last_latch_edge_ns = now - 10_000_000
assert retained_shared_long_cancel_intent(cp, cs, sm, feedback, owner,
cancel_source_ns=now, now_ns=now, producer='authority-card') == (restriction == 'healthy')
@pytest.mark.parametrize('ev6', (False, True))
@pytest.mark.parametrize('restriction', ('healthy', 'no_source', 'old_source', 'lat_denied', 'rx',
'heartbeat', 'same_health', 'wrong_session'))
def test_cancel_can_join_new_raw_long_off_health_on_its_first_physical_sample(ev6, restriction):
from openpilot.starpilot.aol.wire import encode_safety
cp, cs, sm, feedback, _, native, now = captured_context()
if ev6:
cp = ev6_params()
cp.safetyConfigs[0].safetyParam |= 0x800
cp.alternativeExperience = 32
feedback.cp = cp_identity(cp)
slot = replace(native.pandaInventory[0], controlsAllowed=True)
native = replace(native, longitudinalAllowed=True, pandaInventory=(slot,),
sourcePandaStatesMonoTime=now - 10_000_000)
source = now
if restriction == 'no_source':
source = 0
elif restriction == 'old_source':
source = now - 30_000_001
elif restriction == 'lat_denied':
native = replace(native, lateralAllowed=False)
elif restriction == 'same_health':
native = replace(native, sourcePandaStatesMonoTime=now)
elif restriction == 'wrong_session':
native = replace(native, axisSessionId='foreign')
from openpilot.cereal import messaging
pandas = messaging.new_message('pandaStates', 1)
pandas.pandaStates[0] = sm['pandaStates'][0]
pandas.pandaStates[0].safetyRxChecksInvalid = restriction == 'rx'
pandas.pandaStates[0].heartbeatLost = restriction == 'heartbeat'
sm['pandaStates'] = pandas.as_reader().pandaStates
sm['aolSafetyWire'] = encode_safety(native)
assert feedback.longitudinal_cancel_ns == 0
cleared = feedback.observe(sm, cp, cs, now, latched=True, cancel_source_ns=source,
intent_producer_session='authority-card')
assert cleared == (restriction != 'healthy')
if restriction == 'healthy':
assert feedback.longitudinal_cancel_ns == now
cs.buttonEvents = []
assert not feedback.observe(sm, cp, cs, now + 10_000_000, latched=True,
intent_producer_session='authority-card')
assert feedback.longitudinal_cancel_ns == now
@@ -194,49 +194,3 @@ def test_auxiliary_off_after_pending_observation_cannot_be_undone_by_activation(
assert not owner.allowed_latch
observe(owner, cs, 3)
assert not owner.allowed_latch
@pytest.mark.parametrize('factory', (ClassicLongCardIntent, ClassicSccCardIntent))
@pytest.mark.parametrize('main', (0, AOL_TOGGLE))
@pytest.mark.parametrize('lkas', (0, AOL_TOGGLE))
def test_classic_hkg_gas_override_defers_arming_until_release(factory, main, lkas):
owner, cs = factory(CP, replace(SETTINGS, main_action=main, lkas_action=lkas)), state()
observe(owner, cs, 1, enabled=False, active=False)
cs.gasPressed = True
observe(owner, cs, 2)
observe(owner, cs, 3)
assert not owner.allowed_latch
cs.gasPressed = False
observe(owner, cs, 4)
assert owner.allowed_latch and owner._last_latch_edge_ns == 4
cs.brakePressed = True
owner.update(cs, now_ns=5, fault_active=False)
observe(owner, cs, 5, enabled=False, active=False)
assert owner.allowed_latch and owner.output(cs)[0]
@pytest.mark.parametrize('factory', (ClassicLongCardIntent, ClassicSccCardIntent))
@pytest.mark.parametrize('veto', ('cancel', 'lkas', 'fault', 'rejection', 'invalid', 'main_off', 'gear', 'disabled', 'calibration'))
def test_classic_hkg_pending_gas_engagement_cannot_replay_after_withdrawal(factory, veto):
owner, cs = factory(CP, SETTINGS), state()
observe(owner, cs, 1, enabled=False, active=False)
cs.gasPressed = True
observe(owner, cs, 2)
if veto in ('cancel', 'lkas'):
cs.buttonEvents = [car.CarState.ButtonEvent(type=veto, pressed=True)]
elif veto == 'invalid':
cs.canValid = False
elif veto == 'main_off':
cs.cruiseState.available = False
elif veto == 'gear':
cs.gearShifter = 'park'
elif veto == 'disabled':
owner.settings = replace(SETTINGS, enabled=False)
elif veto == 'calibration':
owner._calibration_rearm_required = True
observe(owner, cs, 3, fault=veto == 'fault', rejection=3 if veto == 'rejection' else 0)
owner.settings, owner._calibration_rearm_required = SETTINGS, False
cs.gasPressed, cs.canValid = False, True
cs.gearShifter, cs.cruiseState.available, cs.buttonEvents = 'drive', True, []
observe(owner, cs, 4)
assert not owner.allowed_latch
@@ -256,54 +256,3 @@ def test_simultaneous_cancel_keeps_default_timestamp_side_effects(opted_in, mapp
owner.update(cs, now_ns=2, fault_active=False)
assert not owner.allowed_latch
assert owner._last_latch_edge_ns == (0 if opted_in and mapped else 2)
@pytest.mark.parametrize('identity', (CAR.GENESIS_GV60_EV_1ST_GEN, CAR.HYUNDAI_IONIQ_5,
CAR.HYUNDAI_KONA_EV_2ND_GEN))
@pytest.mark.parametrize('fault', ('none', 'no_cancel', 'lat_denied', 'rx', 'heartbeat', 'expired', 'same_health'))
def test_shared_long_cancel_health_can_precede_native_status(identity, fault):
from opendbc.car import gen_empty_fingerprint
from opendbc.car.hyundai.canfd_stock_aol import qualified_long
from openpilot.starpilot.aol.transport_pause import cp_identity
cp, cs, sm, feedback, owner, native, now = captured_context()
if identity == CAR.GENESIS_GV60_EV_1ST_GEN:
fingerprint = gen_empty_fingerprint()
fingerprint[0][0x1cf] = 8
cp = CarInterface.get_params(identity, fingerprint, [], True, False, False)
cp.safetyConfigs[0].safetyParam |= 0x800
cp.alternativeExperience = 32
else:
cp.carFingerprint = identity
assert qualified_long(cp, marked_only=True)
feedback.cp = cp_identity(cp)
config = cp.safetyConfigs[0]
slot = replace(native.pandaInventory[0], safetyModel=int(config.safetyModel.raw), safetyParam=config.safetyParam,
controlsAllowed=True)
native = replace(native, safetyModel=int(config.safetyModel.raw), safetyParam=config.safetyParam,
longitudinalAllowed=True, pandaInventory=(slot,), sourcePandaStatesMonoTime=now)
sm['aolSafetyWire'] = encode_safety(native)
pandas = messaging.new_message('pandaStates', 1, valid=True, logMonoTime=now)
panda = pandas.pandaStates[0]
panda.safetyModel, panda.safetyParam, panda.alternativeExperience = config.safetyModel, config.safetyParam, 32
panda.controlsAllowed = True
sm['pandaStates'] = pandas.as_reader().pandaStates
if fault == 'no_cancel':
cs.buttonEvents = []
assert not feedback.observe(sm, cp, cs, now, latched=True, cancel_source_ns=now,
intent_producer_session='authority-card')
assert bool(feedback.longitudinal_cancel_ns) == (fault != 'no_cancel')
original_receipt = feedback.longitudinal_cancel_ns
cs.buttonEvents = []
pandas.pandaStates[0].controlsAllowed = False
pandas.pandaStates[0].safetyRxChecksInvalid = fault == 'rx'
pandas.pandaStates[0].heartbeatLost = fault == 'heartbeat'
sm['pandaStates'] = pandas.as_reader().pandaStates
sm.logMonoTime['pandaStates'] = now + (0 if fault == 'same_health' else 10_000_000)
if fault == 'lat_denied':
sm['aolSafetyWire'] = encode_safety(replace(native, lateralAllowed=False))
later = now + (201_000_000 if fault == 'expired' else 10_000_000)
retained = feedback._longitudinal_cancel_current(sm, cp, cs, sm['aolAxisState'], later,
latched=True, cancel_source_ns=0)
assert (retained is not None) == (fault == 'none')
assert feedback.longitudinal_cancel_ns == original_receipt
@@ -0,0 +1,73 @@
"""Cruise MAIN assigned to Adopt speed limit is not also the Hyundai physical lateral toggle."""
import pytest
from opendbc.car import structs
from openpilot.starpilot.aol.intent import AolCardIntent, AolSettings, read_settings
from openpilot.starpilot.aol.tests.test_ioniq6_stock_repair import stock
from openpilot.starpilot.car.hyundai.aol import create_intent, qualified_ioniq6
def long_cp():
cp = stock(False)
cp.pcmCruise = False
cp.openpilotLongitudinalControl = True
cp.safetyConfigs[0].safetyParam = 0x8815
assert qualified_ioniq6(cp)
return cp
def press(owner, cs, button, now_ns, *, engaged=False):
cs.buttonEvents = [structs.CarState.ButtonEvent(type=button, pressed=True)]
owner.update(cs, now_ns=now_ns, fault_active=False, standard_enabled=engaged)
cs.buttonEvents = [structs.CarState.ButtonEvent(type=button, pressed=False)]
owner.update(cs, now_ns=now_ns + 1, fault_active=False, standard_enabled=engaged)
def lateral(owner):
return owner.allowed_latch and not owner.pause_lateral
def armed(owner):
cs = structs.CarState(canValid=True, gearShifter='drive')
cs.cruiseState.available = True
owner.update(cs, now_ns=1, fault_active=False)
press(owner, cs, 'lkas', 2)
assert lateral(owner)
return cs
@pytest.mark.parametrize('main_wheel_action', [False, True])
def test_ioniq6_main_assigned_to_adopt_keeps_lkas_armed_lateral(main_wheel_action):
owner = create_intent(long_cp(), AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0), main_wheel_action=main_wheel_action))
assert owner.independent_cruise_buttons
cs = armed(owner)
press(owner, cs, 'mainCruise', 10, engaged=True)
assert lateral(owner) == main_wheel_action
press(owner, cs, 'lkas', 20, engaged=True)
assert lateral(owner) != main_wheel_action
def test_main_toggle_is_unchanged_without_independent_cruise_buttons():
owner = AolCardIntent(AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0), main_wheel_action=True), explicit_latch=True)
cs = armed(owner)
press(owner, cs, 'mainCruise', 10, engaged=True)
assert not lateral(owner)
class SavedParams:
def __init__(self, values):
self.values = values
def get(self, key, return_default=False):
return self.values.get(key)
def get_bool(self, key):
return self.values.get(key) == b'1'
def get_param_path(self, key):
return f'/nonexistent/{key}'
@pytest.mark.parametrize('lkas,main,main_wheel', [(b'0', b'10', True), (b'0', b'9', False), (b'10', b'0', False), (b'0', b'8', False)])
def test_only_main_adopt_is_marked_as_a_main_wheel_action(lkas, main, main_wheel):
settings = read_settings(SavedParams({'AlwaysOnLateral': b'1', 'LKASButtonControl': lkas, 'MainCruiseButtonControl': main}))
assert settings.main_wheel_action == main_wheel
@@ -1,114 +0,0 @@
"""Cold Toyota readiness is not a previously granted steering command."""
from contextlib import ExitStack
from unittest.mock import patch
import pytest
from openpilot.cereal import log, messaging
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.aol.transport_pause import TransportPauseFeedback
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
from openpilot.starpilot.aol.tests.test_toyota_moving_pcm_off_deadline import cp_for, moving
from openpilot.starpilot.aol.tests.test_toyota_parking_brake_deadline import refresh_saved_settings
BASE = 10_000_000_000
@pytest.mark.parametrize('profile', ('prius_long', 'prius_stock', 'camera_long', 'camera_stock'))
@pytest.mark.parametrize('gas', (False, True))
def test_cold_source_expiry_waits_without_inventing_authority(profile, gas):
with ExitStack() as cleanup:
cleanup.enter_context(OpenpilotPrefix())
cleanup.enter_context(patch.dict('os.environ', {'SIMULATION': '0', 'REPLAY': '0'}))
case = fixture.IoniqAvailabilityTests(methodName='runTest')
cleanup.callback(case.doCleanups)
sd, _, drive = case._authority_callers(False, cp=cp_for(profile), genuine_events=True)
refresh_saved_settings(sd)
feedback = TransportPauseFeedback(sd.CP)
feedback_sm = messaging.SubMaster(['aolAxisState', 'onroadEvents', 'aolSafetyWire', 'pandaStates'])
def card_feedback(now):
samples = [sd.pm.messages[name] for name in ('aolAxisState', 'onroadEvents')]
for name in ('aolSafetyWire', 'pandaStates'):
sample = messaging.new_message(name, len(sd.sm[name]), valid=sd.sm.valid[name], logMonoTime=sd.sm.logMonoTime[name])
setattr(sample, name, sd.sm[name])
samples.append(sample.as_reader())
feedback_sm.update_msgs(now / 1e9, samples)
assert not feedback.observe(feedback_sm, sd.CP, moving(), now, latched=True,
intent_producer_session='authority-card')
update_events, publish = sd.update_events, sd.publish_axis_proof
def startup_events(cs):
sd.sm.valid['longitudinalPlan'] = False
return update_events(cs)
def delayed_publication(cs, **kwargs):
with patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=BASE + 31_148_802):
return publish(cs, **kwargs)
with patch.object(sd, 'update_events', side_effect=startup_events), \
patch.object(sd, 'publish_axis_proof', side_effect=delayed_publication):
axis, host, command = drive(0, observed_state=moving(), gas=gas, allowed=False,
native_request_lat=False, native_request_long=False)
assert log.OnroadEvent.EventName.commIssue in sd.events.names
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names, 'TOYOTA_COLD_SOURCE_EXPIRY'
assert axis.sourceCarStateMonoTime == 0 and str(axis.faultReason) == 'availabilityPause'
assert not axis.nativeAcknowledged and not axis.desiredLateral and not axis.desiredLongitudinal
assert not axis.lateralActive and not axis.longitudinalActive and not host.enabled
assert not command.latActive and not command.longActive and not sd.aol_authority_lost
assert sd.aol_publication_retained_intent.validUntilMonoTime == BASE + 200_000_000
card_feedback(BASE + 31_148_802)
# A fresh source is required; requesting LAT alone still cannot produce it.
axis, host, command = drive(4, observed_state=moving(), gas=gas, allowed=False,
native_request_lat=False, native_request_long=False)
assert axis.sourceCarStateMonoTime == BASE + 40_000_000
assert not axis.lateralActive and not command.latActive and not host.enabled
card_feedback(BASE + 40_000_000)
axis, host, command = drive(5, observed_state=moving(), gas=gas, allowed=True,
native_request_lat=True, native_request_long=False)
assert axis.lateralActive and command.latActive and not host.enabled and not command.longActive
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
card_feedback(BASE + 50_000_000)
assert feedback.ever_lateral_active
@pytest.mark.parametrize('restriction', ('previously_active', 'native_requested', 'native_granted', 'native_invalid',
'rx_fault', 'relay_fault', 'expired_source', 'can_invalid', 'critical'))
def test_cold_wait_never_waives_authority_or_health_failures(restriction):
with ExitStack() as cleanup:
cleanup.enter_context(OpenpilotPrefix())
cleanup.enter_context(patch.dict('os.environ', {'SIMULATION': '0', 'REPLAY': '0'}))
case = fixture.IoniqAvailabilityTests(methodName='runTest')
cleanup.callback(case.doCleanups)
sd, _, drive = case._authority_callers(False, cp=cp_for('prius_long'), genuine_events=True)
refresh_saved_settings(sd)
if restriction == 'previously_active':
axis, _, command = drive(0, observed_state=moving(), native_request_long=False)
assert axis.lateralActive and command.latActive
original_events, publish = sd.update_events, sd.publish_axis_proof
def startup_events(cs):
sd.sm.valid['longitudinalPlan'] = False
return original_events(cs)
def delayed_publication(cs, **kwargs):
delay = 201_000_000 if restriction == 'expired_source' else 31_148_802
with patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=BASE + 10_000_000 + delay):
return publish(cs, **kwargs)
state = moving()
if restriction == 'can_invalid':
state.canValid = False
options = {'allowed': restriction == 'native_granted', 'native_request_lat': restriction == 'native_requested',
'native_request_long': False, 'native_valid': restriction != 'native_invalid',
'panda_fault': restriction == 'rx_fault', 'relay_fault': restriction == 'relay_fault'}
if restriction == 'critical':
options['critical_event'] = log.OnroadEvent.EventName.canError
with patch.object(sd, 'update_events', side_effect=startup_events), \
patch.object(sd, 'publish_axis_proof', side_effect=delayed_publication):
axis, host, command = drive(1, observed_state=state, **options)
assert log.OnroadEvent.EventName.controlsMismatch in sd.events.names
assert not axis.lateralActive and not axis.longitudinalActive
assert not host.enabled and not command.latActive and not command.longActive
assert str(axis.faultReason) == 'critical'
+2 -8
View File
@@ -805,7 +805,7 @@ class TransportPauseFeedback:
return native
def _longitudinal_cancel_current(self, sm, cp, cs, axis, now_ns, *, latched, cancel_source_ns, withdrawal_required=True) -> SafetyState | None:
from opendbc.car.hyundai.canfd_stock_aol import qualified_long as qualified
from opendbc.car.hyundai.ev6_aol import qualified
from openpilot.starpilot.car.hyundai.aol import qualified_ccnc_long_aol as ev9_qualified
from opendbc.car.structs import car
@@ -833,7 +833,7 @@ class TransportPauseFeedback:
return None
native = current_native(sm, cp, now_ns=now_ns, axis_session_id=self.session)
if (native is None or not native.pandaInventory or not native.requestedLateral or not native.lateralAllowed or
not native.requestedLongitudinal or
not native.requestedLongitudinal or withdrawal_required and native.longitudinalAllowed or
native.observedMonoTime < self.lateral_native_ns or
not native_inventory_matches_cp(native, cp) or
(native.pandaSerial, tuple(p.hardwareSerial for p in native.pandaInventory)) != self.lateral_inventory):
@@ -849,12 +849,6 @@ class TransportPauseFeedback:
not p.heartbeatLost and not blocking_fault_list(p.faults, cp) and
(str(c.safetyModel) not in ('silent', 'noOutput') or not p.controlsAllowed)
for p, c in zip(pandas, cp.safetyConfigs, strict=True))
raw_withdrawal = bool((self.longitudinal_cancel_ns or fresh_cancel) and
0 < native.sourcePandaStatesMonoTime < stamp and
all(not p.controlsAllowed for p, c in zip(pandas, cp.safetyConfigs, strict=True)
if str(c.safetyModel) not in ('silent', 'noOutput')))
if withdrawal_required and native.longitudinalAllowed and not raw_withdrawal:
return None
return native if current_pandas else None
@@ -3,8 +3,6 @@ from openpilot.starpilot.aol.intent import AolCardIntent
class ClassicLongCardIntent(AolCardIntent):
defer_full_engagement_on_gas = True
def __init__(self, cp, settings):
super().__init__(settings, explicit_latch=True, independent_cruise_buttons=(
independent_buttons_qualified(cp, marked_only=False)))
@@ -4,8 +4,6 @@ from openpilot.starpilot.car.hyundai.forte_intent import ForteCardIntent
class ClassicSccCardIntent(ForteCardIntent):
defer_full_engagement_on_gas = True
observe_stock_engagement = True
def __init__(self, cp, settings):
@@ -1,6 +1,6 @@
{
"reviewed_schema_commit": "63beafc6af294d0759b47dc3bc546f24082a2a5d",
"reviewed_schema_sha256": "ca01ef4f6d2b15c5fac59ce237ec6e49f1167e2408903c9088ee322ca490de31",
"reviewed_schema_commit": "cd0f46b9d65a9c5e47002f57e5ad6625c9757c63",
"reviewed_schema_sha256": "5effa2eb1fbbada3d1bbaae0acd3d31d780d8ec0e6ee1e214fedbc38ae400182",
"incumbent_fixture_sha256": "a36ab54c00b5bbe61d520873d24aadfb8f33babd70c31a07046516d5545838d9",
"types": {
"0xf35cc4560bbf6ec2": {
@@ -18,7 +18,7 @@
},
"new_storage": {
"dataWordCount": 3,
"pointerCount": 7,
"pointerCount": 6,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
"discriminantCount": 0,
@@ -59,30 +59,10 @@
"hadExplicitDefault": false
},
"name": "toyotaCruise",
"codeOrder": 39,
"discriminantValue": 65535,
"ordinal": {
"explicit": 38
}
},
{
"slot": {
"offset": 6,
"type": {
"struct": {
"typeId": 15172299076723568810
}
},
"defaultValue": {
"schemaValueHex": "00000000040000000000000002000100100000000000000000000000000000000000000000000000"
},
"hadExplicitDefault": false
},
"name": "steeringLimitInfo",
"codeOrder": 38,
"discriminantValue": 65535,
"ordinal": {
"explicit": 39
"explicit": 38
}
}
]
@@ -101,7 +81,7 @@
"discriminantOffset": 0
},
"new_storage": {
"dataWordCount": 18,
"dataWordCount": 21,
"pointerCount": 22,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
@@ -252,6 +232,78 @@
"ordinal": {
"explicit": 58
}
},
{
"slot": {
"offset": 141,
"type": {
"bool": null
},
"defaultValue": {
"bool": false
},
"hadExplicitDefault": false
},
"name": "acceptedIsPreview",
"codeOrder": 59,
"discriminantValue": 65535,
"ordinal": {
"explicit": 59
}
},
{
"slot": {
"offset": 18,
"type": {
"uint64": null
},
"defaultValue": {
"uint64": 0
},
"hadExplicitDefault": false
},
"name": "wheelOverrideSince",
"codeOrder": 60,
"discriminantValue": 65535,
"ordinal": {
"explicit": 60
}
},
{
"slot": {
"offset": 19,
"type": {
"uint64": null
},
"defaultValue": {
"uint64": 0
},
"hadExplicitDefault": false
},
"name": "wheelAdoptMonoTime",
"codeOrder": 61,
"discriminantValue": 65535,
"ordinal": {
"explicit": 61
}
},
{
"slot": {
"offset": 40,
"type": {
"float32": null
},
"defaultValue": {
"float32": 0.0
},
"hadExplicitDefault": false
},
"name": "wheelAdoptTargetMps",
"codeOrder": 62,
"discriminantValue": 65535,
"ordinal": {
"explicit": 62
}
}
]
},
@@ -672,12 +724,53 @@
"codeOrder": 12
}
]
},
"0x9ccdc8676701b412": {
"kind": "struct",
"before_name": "SlcAction",
"after_name": "SlcAction",
"old_field_count": 8,
"old_storage": {
"dataWordCount": 4,
"pointerCount": 4,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
"discriminantCount": 0,
"discriminantOffset": 0
},
"new_storage": {
"dataWordCount": 5,
"pointerCount": 4,
"preferredListEncoding": "inlineComposite",
"isGroup": false,
"discriminantCount": 0,
"discriminantOffset": 0
},
"added_fields": [
{
"slot": {
"offset": 4,
"type": {
"uint64": null
},
"defaultValue": {
"uint64": 0
},
"hadExplicitDefault": false
},
"name": "wheelAdoptMonoTime",
"codeOrder": 8,
"discriminantValue": 65535,
"ordinal": {
"explicit": 8
}
}
]
}
},
"published_addition_commits": {
"slc": "ab8368f287b78b42f8590722878c7d5834609e19",
"flm": "b151ca11cb4d88f32d7adfdbcff50baf29f4fbbc",
"tesla_cooperative_steering": "63beafc6af294d0759b47dc3bc546f24082a2a5d"
"flm": "b151ca11cb4d88f32d7adfdbcff50baf29f4fbbc"
},
"review_basis": "Published SLC, FLM, and Tesla cooperative-steering additions. Every incumbent wire field descriptor and enum prefix was independently checked unchanged; only the exact typed deltas listed here are permitted. Tesla adds steeringLimitInfo @39 at pointer slot 6 using the existing SteeringLimitInfo type. ToyotaCruise @38 retains pointer slot 5, type, and default; only its declaration codeOrder changes from 38 to 39."
"review_basis": "Published SLC and FLM append-only changes. Every incumbent compiled field descriptor and enum prefix was independently checked unchanged; only the exact seven typed deltas below are permitted."
}
@@ -115,47 +115,6 @@ def test_additive_feedback_survives_incumbent_reader_relay(historical):
assert recovered.feedbackPublication.to_dict() == expected_feedback
def test_additive_steering_limits_preserve_defaults_and_survive_incumbent_relay(historical):
original = historical.StarPilotCarState.new_message(
pedalMaxRegen=True, pedalLongActive=True, sourceCarStateMonoTime=123456,
gps={'latitude': 41.25, 'longitude': -87.5, 'hasFix': True})
incumbent = original.to_dict()
with custom.StarPilotCarState.from_bytes(original.to_bytes()) as decoded:
assert not decoded.steeringLimitInfo.valid
assert decoded.steeringLimitInfo.monoTime == 0
current = decoded.as_builder()
current.feedbackPublication = {'version': 1, 'publicationSequence': 27, 'reportedTorque': 0.25}
current.toyotaCruise = {'version': 1, 'status': 'active', 'targetKph': 72., 'sessionId': 'incumbent'}
current.steeringLimitInfo = {
'valid': True, 'modelLimitErrorDeg': 1.25, 'resumeLimitErrorDeg': 2.5,
'cooperativeLimitErrorDeg': 3.75, 'cooperativeOffsetDeg': -0.5,
'monoTime': 123456789, 'combinedLimitErrorDeg': 4.0}
expected = current.to_dict()
with historical.StarPilotCarState.from_bytes(current.to_bytes()) as old:
assert old.to_dict() == incumbent
relayed = old.as_builder().to_bytes()
with custom.StarPilotCarState.from_bytes(relayed) as recovered:
assert recovered.to_dict() == expected
@pytest.mark.parametrize(('before', 'after'), [
('dashboardSpeedLimit @3 :Float32;', 'dashboardSpeedLimit @3 :Float64;'),
('toyotaCruise @38 :ToyotaCruise;', 'toyotaCruise @38 :StarPilotCarControl.SteeringLimitInfo;'),
('steeringLimitInfo @39 :StarPilotCarControl.SteeringLimitInfo;', 'steeringLimitInfo @39 :StarPilotCarState.Gps;'),
('steeringLimitInfo @39 :StarPilotCarControl.SteeringLimitInfo;',
'steeringLimitInfo @39 :StarPilotCarControl.SteeringLimitInfo; unreviewed @40 :Bool;'),
])
def test_reviewed_steering_addition_does_not_allow_other_schema_changes(historical, tmp_path, before, after):
source = Path(CEREAL_PATH, 'custom.capnp').read_text()
assert source.count(before) == 1
path = tmp_path / 'unreviewed.capnp'
path.write_text(source.replace(before, after))
parser = capnp.SchemaParser()
changed = parser.load(str(path), imports=[CEREAL_PATH, str(files('opendbc') / 'car')])
with pytest.raises(AssertionError):
compare_node(historical.StarPilotCarState, changed.StarPilotCarState)
@pytest.mark.parametrize(('fields', 'compatible'), [
('value @0 :UInt32; label @1 :Text;', True),
('value @0 :UInt32; label @1 :Text; extra @2 :Bool;', False),
@@ -1,138 +0,0 @@
"""Legacy Toyota pre-warning is distinct from dirty radar and CAN failure."""
from contextlib import ExitStack
from unittest.mock import patch
import pytest
from opendbc.can import CANPacker
from opendbc.car import Bus, gen_empty_fingerprint
from opendbc.car.toyota.interface import CarInterface
from opendbc.car.toyota.radar_interface import RadarInterface
from opendbc.car.toyota.values import CAR, DBC
from openpilot.cereal import messaging
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture
from openpilot.starpilot.aol.tests.test_toyota_moving_pcm_off_deadline import cp_for
LEGACY = (CAR.TOYOTA_PRIUS, CAR.TOYOTA_PRIUS_RETROFIT, CAR.TOYOTA_RAV4H, CAR.LEXUS_RX)
def params(candidate, alpha):
if candidate == CAR.TOYOTA_PRIUS_RETROFIT:
return cp_for('prius_long' if alpha else 'prius_stock')
return CarInterface.get_params(candidate, gen_empty_fingerprint(), [], alpha, False, False)
def batch(radar, packer, tick, *, status=1, pre=3, counter=1358, target=False, omit=None):
frames = []
for address in radar.RADAR_A_MSGS + radar.RADAR_B_MSGS:
if address == omit:
continue
first = address == radar.RADAR_A_MSGS[0]
fields = ({'LONG_DIST': 30. if target and first else 255., 'VALID': int(target and first),
'LAT_DIST': 0., 'REL_SPEED': -1.} if address in radar.RADAR_A_MSGS else {'SCORE': 100})
frames.append(packer.make_can_msg(address, 1, fields))
if omit != 'status':
frames.append(packer.make_can_msg('STATUS_MSG', 1, {
'RADAR_STATUS': status, 'RADAR_PRE_FAULT': pre, 'INVALID_COUNTER': counter}))
return [(1_000_000_000 + tick * 50_000_000, frames)]
def feed(radar, packer, **kwargs):
result = None
for tick in range(30):
result = radar.update(batch(radar, packer, tick, **kwargs))
assert result is not None
return result
@pytest.mark.parametrize('candidate', LEGACY)
@pytest.mark.parametrize('alpha', (False, True))
@pytest.mark.parametrize('counter', (1358, 1872, 2643))
@pytest.mark.parametrize('target', (False, True))
def test_legacy_ready_radar_pre_warning_keeps_live_measurements(candidate, alpha, counter, target):
cp = params(candidate, alpha)
radar = RadarInterface(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.radar])
result = feed(radar, packer, counter=counter, target=target)
assert radar.rcp.can_valid
assert not result.errors.radarUnavailableTemporary, 'LEGACY_RADAR_PRE_WARNING'
assert not result.errors.canError
assert len(result.points) == int(target)
if target:
assert result.points[0].dRel == 30. and result.points[0].vRel == -1.
@pytest.mark.parametrize('profile', ('prius_long', 'camera_long'))
@pytest.mark.parametrize('condition', ('initializing', 'fault', 'unknown_status', 'unknown_pre', 'dirty_threshold',
'saturated_counter', 'missing_status', 'missing_track'))
def test_real_radar_faults_and_receive_checks_remain(profile, condition):
cp = cp_for(profile)
radar = RadarInterface(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.radar])
options = {'target': True}
if condition == 'initializing':
options['status'] = 0
elif condition == 'fault':
options['status'] = 3
elif condition == 'unknown_status':
options['status'] = 2
elif condition == 'unknown_pre':
options['pre'] = 1
elif condition == 'dirty_threshold':
options['counter'] = 2659
elif condition == 'saturated_counter':
options['counter'] = 4095
elif condition == 'missing_status':
options['omit'] = 'status'
else:
options['omit'] = radar.RADAR_A_MSGS[0]
result = feed(radar, packer, **options)
assert result.errors.canError or result.errors.radarUnavailableTemporary
if condition.startswith('missing'):
assert result.errors.canError and not radar.rcp.can_valid
else:
assert result.errors.radarUnavailableTemporary
def test_tss2_pre_fault_behavior_is_unchanged():
cp = cp_for('camera_long')
radar = RadarInterface(cp)
result = feed(radar, CANPacker(DBC[cp.carFingerprint][Bus.radar]))
assert result.errors.radarUnavailableTemporary
@pytest.mark.parametrize('profile', ('prius_long', 'prius_stock'))
def test_actual_radar_to_selfdrived_keeps_pre_warning_distinct_from_fault(profile):
with ExitStack() as cleanup:
cleanup.enter_context(OpenpilotPrefix())
cleanup.enter_context(patch.dict('os.environ', {'SIMULATION': '0', 'AOL_REPLAY_RUNTIME': '1'}))
cp = cp_for(profile)
case = fixture.IoniqAvailabilityTests(methodName='runTest')
cleanup.callback(case.doCleanups)
sd, _, drive = case._authority_callers(True, cp=cp, genuine_events=True)
radar = RadarInterface(cp)
packer = CANPacker(DBC[cp.carFingerprint][Bus.radar])
observed = fixture.car_state()
observed.cruiseState.available = observed.cruiseState.enabled = True
original = sd.update_events
errors = [feed(radar, packer).errors]
def events(cs):
message = messaging.new_message('radarState', valid=True)
message.radarState.radarErrors = errors[0].as_reader()
sd.sm.data['radarState'] = message.radarState.as_reader()
return original(cs)
with patch.object(sd, 'update_events', side_effect=events):
for tick in range(5):
axis, host, command = drive(tick, observed_state=observed)
assert host.enabled and axis.lateralActive and command.latActive
assert not sd.sm['radarState'].radarErrors.radarUnavailableTemporary, 'LEGACY_RADAR_PRE_WARNING'
if cp.openpilotLongitudinalControl:
assert axis.longitudinalActive and command.longActive
errors[0] = feed(radar, packer, status=3, counter=2659).errors
axis, host, command = drive(5, observed_state=observed, native_request_long=False, long_allowed=False)
if cp.openpilotLongitudinalControl:
assert not host.enabled and not axis.longitudinalActive and not command.longActive
assert axis.lateralActive and command.latActive
@@ -6,9 +6,8 @@ CEM selects Experimental mode for configured speed, road, signal, curve, lead, s
`ConditionalPlannerHost` joins the current native MPC result, current model/car/radar observations, saved preferences, and qualified wheel-button events. It produces a short-lived proposal on `slcState.conditionalMode`; it does not own the effective driving mode. `selfdrived` independently checks the current vehicle capability, longitudinal axis, drive, saved configuration, and input freshness before accepting that proposal. Its ordinary `ExperimentalMode` request remains the fallback. The UI displays an active conditional reason only from a fresh selfdrived acknowledgment matching the exact current `selfdriveState` frame and active longitudinal axis; the saved family is displayed separately.
Speed-limit availability is independent of mode selection. The retired `SLCFallback=1`
preference cannot request Experimental or veto Chill, including when Maps or Vision
becomes unknown or stale. Other scene triggers, manual mode choices and committed
Speed-limit availability is independent of mode selection. A missing or stale Maps or Vision limit does
not request Experimental or veto Chill. Other scene triggers, manual mode choices and committed
stop handling retain their existing behavior. Historical speed-limit reason labels
remain readable in recorded routes.
@@ -49,6 +48,6 @@ Traffic assignment does not suppress ordinary profile tuning.
Scene inputs distinguish false from unknown. Model filters advance once per original model tick; the oldest required source bounds freshness separately, so a slower fresh radar cannot stall a model filter or renew stale data. Startup, clock discontinuities, and drive changes require fresh observations. Conditional Chill requires every applicable veto to be known false. Disabled build features can be known absent; an enabled producer without a qualified observation remains unknown.
Current producers cover native MPC follow time and stop/throttle intent, model lane geometry, qualified adjacent radar, SLC attribution, pedal override, and the effective mode reported by selfdrived. The exact Ioniq media path supplies source-stamped Traffic Mode to CEM and a bounded Traffic follow/jerk, acceleration and cruise-braking profile. The shared committed-turn evaluator feeds both the stop detector and slower-lead retuning from original car/model stamps. Traffic retains the existing 0.75-second effective headway floor; saved values below it remain unsupported and intact. The native planner shapes the cruise-only floor for a qualified SLC cap, a validated named Eco/Sport braking personality, or the explicit global Braking response. A saved Traffic braking category reverts to its 0.42 m/s² cruise baseline for a present radar lead or standstill, then recovers through the existing profile smoother; MPC and model braking retain priority. The saved lane-change close-gap preference and current lateral minimum-speed policy are separate from conditional scene selection. Frozen weather and other hazard-context braking inputs remain absent. Stop-sign, forced-stop, and dashboard stop-sign producers remain absent. The historical red-light name is already represented by the current stop-light observation, so it does not need a duplicate owner.
Current producers cover native MPC follow time and stop/throttle intent, model lane geometry, qualified adjacent radar, SLC attribution, pedal override, and the effective mode reported by selfdrived. The exact Ioniq media path supplies source-stamped Traffic Mode to CEM and a bounded Traffic follow/jerk, acceleration and cruise-braking profile. The shared committed-turn evaluator feeds both the stop detector and slower-lead retuning from original car/model stamps. Traffic retains the existing 0.75-second effective headway floor; saved values below it remain unsupported and intact. The native planner retains the selected cruise-braking response for an SLC cap. Conditional speed comparisons and curve control receive the same-cycle SLC-capped cruise target while the driver's SET remains unchanged. A saved Traffic braking category reverts to its 0.42 m/s² cruise baseline for a present radar lead or standstill, then recovers through the existing profile smoother; MPC and model braking retain priority. The saved lane-change close-gap preference and current lateral minimum-speed policy are separate from conditional scene selection. Frozen weather and other hazard-context braking inputs remain absent. Stop-sign, forced-stop, and dashboard stop-sign producers remain absent. The historical red-light name is already represented by the current stop-light observation, so it does not need a duplicate owner.
The source contract includes frozen `starpilot/controls/lib/conditional_experimental_mode.py`, `conditional_chill_mode.py`, `starpilot/common/experimental_state.py`, and the planner's mode selection. Tests compare executable frozen policy/detector traces and exercise current serialized messages, native MPC/radar joins, lifecycle boundaries, manual controls, and settings concurrency. These are integration evidence, not vehicle qualification. Normal availability is limited to the exact tagged Ioniq 6 LONG profile; the separate ECU handoff and native safety acknowledgment must succeed first. Missing scene producers and road behavior still require evaluation.
@@ -59,6 +59,7 @@ class ConditionalModeHost:
selected_t_follow_s: float | None = None,
selected_t_follow_observed_mono_ns: int | None = None,
settings_owner: ConditionalSettingsOwner | None = None,
cruise_ceiling_mps: float | None = None,
) -> HostProposal:
signal_options = None
settings_revision = None
@@ -120,6 +121,7 @@ class ConditionalModeHost:
selected_t_follow_observed_mono_ns=selected_t_follow_observed_mono_ns,
owner_context=owner_context,
signal_options=signal_options,
cruise_ceiling_mps=cruise_ceiling_mps,
)
authority = projected.authority
if authority is not None and authority.fresh and (not authority.system_long_capable or authority.safe_mode or
@@ -180,6 +180,7 @@ class ConditionalPlannerHost:
drive_id: int, manual: ManualState | None = None,
owner_context: ConditionalOwnerContext | None = None,
native_plan_valid: bool = False,
cruise_ceiling_mps: float | None = None,
manual_event=None,
ui_event=None,
) -> tuple[HostProposal, SettingsSnapshot | None]:
@@ -289,6 +290,7 @@ class ConditionalPlannerHost:
manual=self.manual_state if manual is None else manual, owner_context=context,
selected_t_follow_s=selected_follow_time(planner),
selected_t_follow_observed_mono_ns=sm.logMonoTime.get('modelV2'),
cruise_ceiling_mps=cruise_ceiling_mps,
settings_owner=self.settings,
)
snapshot = self.settings.current
@@ -406,6 +406,7 @@ class SceneProjector:
selected_t_follow_observed_mono_ns: int | None = None,
owner_context: ConditionalOwnerContext | None = None,
signal_options: CEMOptions | None = None,
cruise_ceiling_mps: float | None = None,
) -> ProjectedScene:
if (
type(now_mono_ns) is not int
@@ -441,6 +442,9 @@ class SceneProjector:
speed = _ego_speed(car) if car is not None and _field(car, 'canValid') is True and _field(car, 'canTimeout') is False else None
cruise_kph = _number(_field(car, 'vCruise'), high=V_CRUISE_UNSET_KPH) if speed is not None else None
cruise = cruise_kph * KPH_TO_MPS if cruise_kph is not None and 0 < cruise_kph < V_CRUISE_UNSET_KPH else None
if cruise_ceiling_mps is not None:
cap = _number(cruise_ceiling_mps, high=V_CRUISE_UNSET_KPH * KPH_TO_MPS)
cruise = min(cruise, cap) if cruise is not None and cap is not None and cap > 0 else None
curvature = _number(_field(controls, 'curvature'), low=-1.0, high=1.0) if controls is not None else None
raw_curve = abs(speed * speed * curvature) >= FROZEN_CURVE_ACCEL_MPS2 if speed is not None and curvature is not None else None
horizon = None
@@ -114,6 +114,20 @@ def owner_context(stamp: int, *, stop_time=8.0, slower=True, stopped=False):
class TestSceneProjector(unittest.TestCase):
def test_conditional_speed_comparisons_use_the_slc_cap_without_mutating_set(self):
for cap, expected in ((None, 20.), (15., 15.), (25., 20.), (float('nan'), None)):
with self.subTest(cap=cap):
projector = SceneProjector()
sm = FakeSubMaster(serialized_scene(), MONO-5_000_000)
projector.project(sm, CP, now_mono_ns=MONO, now_boot_ns=BOOT, sample_skew_ns=1000)
now = MONO+50_000_000
sm.stamp(now)
sm.payloads.update(serialized_scene(stamp=BOOT+50_000_000))
result = projector.project(sm, CP, now_mono_ns=now, now_boot_ns=BOOT+50_000_000,
sample_skew_ns=1000, safe_mode=False, owner_context=owner_context(now), cruise_ceiling_mps=cap)
self.assertEqual(result.scene.set_speed_mps, expected)
self.assertEqual(sm['carState'].vCruise, 72.)
def test_standstill_filter_undershoot_requires_fresh_physical_zero_speed(self):
cases = ((-1.401298464324817e-45, True, 0., True),
(-0.0008391447481699288, True, 0., True),
+7 -1
View File
@@ -80,8 +80,14 @@ class CurveHost:
self.previous_long_active = False
self.previous_active_headway = None
def sample(self, sm, CP, now_ns: int, *, drive_id: int = 0, event=None, follow_time_s: float | None = None):
def sample(self, sm, CP, now_ns: int, *, drive_id: int = 0, event=None, follow_time_s: float | None = None,
cruise_ceiling_mps: float | None = None):
frame = frame_from_submaster(sm, CP, now_ns, drive_id=drive_id)
if cruise_ceiling_mps is not None:
if not math.isfinite(cruise_ceiling_mps) or cruise_ceiling_mps <= 0:
frame = replace(frame, inputs_fresh=False)
else:
frame = replace(frame, cruise_mps=min(frame.cruise_mps, cruise_ceiling_mps))
if drive_id and self.lead_drive_id and drive_id != self.lead_drive_id:
self._reset_leads()
if drive_id:
@@ -44,6 +44,12 @@ class TestCurveHost(unittest.TestCase):
self.sm.logMonoTime['radarState'] = NOW - 1_000_000_000
self.assertIsNone(self.host.sample(self.sm, self.cp, NOW + 50_000_000).ceiling_mps)
def test_curve_uses_same_cycle_slc_capped_cruise_without_mutating_set(self):
with patch.object(self.host.runtime, 'step', wraps=self.host.runtime.step) as step:
self.host.sample(self.sm, self.cp, NOW, follow_time_s=1.45, cruise_ceiling_mps=20.)
self.assertEqual(step.call_args.args[0].cruise_mps, 20.)
self.assertEqual(self.sm['carState'].vCruise, 108.)
def test_stock_owner_and_malformed_field_fail_closed(self):
self.cp.openpilotLongitudinalControl = False
self.assertIsNone(self.host.sample(self.sm, self.cp, NOW).ceiling_mps)
+1 -1
View File
@@ -21,7 +21,7 @@ CYCLE_PERSONALITY = "__starpilot_controller_action__:cycle_driving_personality"
# Saved preference shortcuts retain the current owner's authority and dependencies.
FEATURE_KEYS = {
"slc": ("SpeedLimitController", "ShowSpeedLimits", "SLCConfirmation", "SLCConfirmationHigher", "SLCConfirmationLower"),
"slc": ("SpeedLimitController", "ShowSpeedLimits", "SLCConfirmation"),
"lane": ("LaneCentering", "LaneCenteringPauseOnSignal"),
"curve": ("CurveSpeedController", "CurveSpeedControllerNoLead", "ShowCSCStatus"),
"aol": ("AlwaysOnLateral", "NostalgiaMode"),
@@ -61,12 +61,21 @@ DESCRIPTIONS = {
'CurveSpeedControllerNoLead': 'Pauses curve-speed adjustments while following a lead vehicle.',
'ShowCSCStatus': 'Shows when Curve Speed Controller is adjusting speed on the driving screen.',
'curve_reset': 'Deletes saved cornering learning so Curve Speed Controller can learn your preferences again.',
'SpeedLimitController': 'Adjusts the set speed to accepted speed limits using your source priority and offsets.',
'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': ('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': 'Auto adopts new limits immediately. With Automatic adoption off, + accepts and − declines within 10 seconds.',
'SLCAdvanceSeconds': 'Offers mapped limits early using road distance and current speed. Existing adoption and confirmation settings still apply.',
'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.'),
'SLCAdvanceHigherSeconds': ('Offers higher mapped limits early using road distance and current speed. ' +
'Existing adoption and confirmation settings still apply.'),
'SLCAdvanceLowerSeconds': ('Offers lower mapped limits early using road distance and current speed. ' +
'Existing adoption and confirmation settings still apply.'),
'SLCPriority1': 'Chooses the first source checked for a fresh speed limit.',
'SLCPriority2': 'Chooses the source used when the primary source has no fresh speed limit.',
'SLCPriority3': 'Chooses an optional third source when neither earlier source has a fresh speed limit.',
'slc_reset': 'Resets saved speed-limit offsets while retaining valid configured speed ranges.',
'reset_calibration': 'Clears saved camera and steering calibration. The device recalibrates on the next drive.',
'reset_driver_monitoring': 'Clear saved wheel-side detection. Automatic detection restarts on the next drive.',
@@ -100,7 +109,7 @@ DESCRIPTIONS = {
'DeveloperSidebar': 'Shows live developer metrics on the right of the Big driving screen. Off gives that space back to the road view.',
'DriverCamera': 'Shows the driver camera while reversing.',
'StockConfidenceBallWidget': 'Uses StarPilot driving widgets in place of the stock confidence ball.',
'HidePersonalityAlerts': 'Hides the alert shown when the driving personality changes on the comma and Android Auto.',
'HidePersonalityAlerts': 'Hides the alert shown when the driving personality changes.',
'ShowBrakeStatus': 'Colors the steering wheel to show braking and acceleration.',
'HideLeadMarker': 'Shows the lead-vehicle indicator on the driving screen.',
'LeadInfo': 'Shows the lead vehicle distance or speed beside its indicator.',
@@ -77,10 +77,15 @@
"RelaxedJerkSpeedDecrease",
"RemoteStartBootsComma",
"ReverseCruise",
"SLCAdvanceSeconds",
"SLCAdvanceHigherSeconds",
"SLCAdvanceLowerSeconds",
"SLCConfirmation",
"SLCOnEngage",
"SLCOverrideMode",
"SLCPriority1",
"SLCPriority2",
"SLCPriority3",
"SLCWheelOverride",
"ScreenBrightness",
"ScreenBrightnessOnroad",
"ScreenTimeout",
@@ -63,40 +63,23 @@ class TestCanonicalRecovery(TestCase):
self.assertTrue(recovery.confirm(pending['intent'], self.session, self.generation)['saved'])
self.assertEqual(recovery.snapshot(self.session, self.generation)['count'], 0)
def test_legacy_confirmation_flags_do_not_create_hidden_customization(self):
self.params.put_bool('SLCConfirmation', False, block=True)
self.params.put_bool('SLCConfirmationLower', True, block=True)
self.params.put_bool('SLCConfirmationHigher', True, block=True)
def test_directional_confirmation_resets_to_off(self):
self.params.put('SLCConfirmation', 4, block=True)
recovery = self.recovery()
view = recovery.snapshot(self.session, self.generation)
children = [row for row in view['rows'] if row['label'] in ('Confirm lower limits', 'Confirm higher limits')]
self.assertEqual(children, [])
self.assertFalse(self.params.get_bool('SLCConfirmation'))
self.assertTrue(self.params.get_bool('SLCConfirmationLower'))
self.assertTrue(self.params.get_bool('SLCConfirmationHigher'))
def test_reset_confirmation_master_preserves_ignored_legacy_bytes(self):
for key in ('SLCConfirmation', 'SLCConfirmationLower', 'SLCConfirmationHigher'):
self.params.put_bool(key, True, block=True)
recovery = self.recovery()
view = recovery.snapshot(self.session, self.generation)
selected = [row['id'] for row in view['rows'] if row['label'] in
('Automatic adoption', 'Confirm lower limits', 'Confirm higher limits')]
self.assertEqual(len(selected), 1)
pending = recovery.preview(view['view'], selected, self.session, self.generation)
row = next(row for row in view['rows'] if row['label'] == 'Confirm limits')
pending = recovery.preview(view['view'], [row['id']], self.session, self.generation)
self.assertTrue(recovery.confirm(pending['intent'], self.session, self.generation)['saved'])
self.assertFalse(self.params.get_bool('SLCConfirmation'))
for key in ('SLCConfirmationLower', 'SLCConfirmationHigher'):
self.assertTrue(self.params.get_bool(key))
self.assertFalse(self.params.get('SLCConfirmation'))
def test_unsupported_rows_remain_visible_but_cannot_block_available_reset_all(self):
from dataclasses import replace
self.params.put_bool('SLCConfirmation', True, block=True)
self.params.put('SLCConfirmation', 1, block=True)
self.params.put_bool('ShowBrakeStatus', True, block=True)
self.context.value = replace(self.context.value, cp=None, cp_raw=None)
recovery = self.recovery()
view = recovery.snapshot(self.session, self.generation)
unsupported = next(row for row in view['rows'] if row['label'] == 'Automatic adoption')
unsupported = next(row for row in view['rows'] if row['label'] == 'Confirm limits')
self.assertFalse(unsupported['resetAvailable'])
with self.assertRaises(SettingsChanged):
recovery.preview(view['view'], [unsupported['id']], self.session, self.generation)
@@ -104,7 +87,7 @@ class TestCanonicalRecovery(TestCase):
self.assertTrue(selected)
pending = recovery.preview(view['view'], selected, self.session, self.generation)
self.assertTrue(recovery.confirm(pending['intent'], self.session, self.generation)['saved'])
self.assertTrue(self.params.get_bool('SLCConfirmation'))
self.assertTrue(self.params.get('SLCConfirmation'))
self.assertFalse(self.params.get_bool('ShowBrakeStatus'))
def test_other_canonical_inactive_children_reset_without_turning_their_masters_on(self):
@@ -44,20 +44,17 @@ class TestRecoveryHttp(TestCase):
rows = self.request('/api/settings/non-defaults', cookie=cookie)[1]['rows']
self.assertNotIn('Wheel brake and acceleration colors', [row['label'] for row in rows])
def test_confirmation_reset_uses_only_master_and_preserves_legacy_flags(self):
for key in ('SLCConfirmation', 'SLCConfirmationLower', 'SLCConfirmationHigher'):
self.params.put_bool(key, True, block=True)
def test_confirmation_reset_restores_off(self):
self.params.put('SLCConfirmation', 1, block=True)
cookie = self.login()
payload = self.selection(cookie, ['Automatic adoption'])
payload = self.selection(cookie, ['Confirm limits'])
status, pending, _ = self.request('/api/settings/recovery/preview', cookie=cookie, payload=payload)
self.assertEqual(status, 200)
result = self.request('/api/settings/recovery/confirm', cookie=cookie,
payload={'intent': pending['intent'], 'confirmed': True})
self.assertEqual(result[0], 200)
self.assertTrue(result[1]['saved'])
self.assertFalse(self.params.get_bool('SLCConfirmation'))
for key in ('SLCConfirmationLower', 'SLCConfirmationHigher'):
self.assertTrue(self.params.get_bool(key))
self.assertFalse(self.params.get('SLCConfirmation'))
def test_wrong_selection_and_unconfirmed_requests_are_rejected(self):
self.params.put_bool('ShowBrakeStatus', True, block=True)
@@ -81,13 +78,13 @@ class TestRecoveryHttp(TestCase):
self.assertTrue(self.params.get_bool('ShowBrakeStatus'))
def test_unsupported_rows_are_reported_and_available_reset_all_does_not_touch_them(self):
self.params.put_bool('SLCConfirmation', True, block=True)
self.params.put('SLCConfirmation', 1, block=True)
self.params.put_bool('ShowBrakeStatus', True, block=True)
self.context.value = replace(self.context.value, cp=None, cp_raw=None)
cookie = self.login()
status, view, _ = self.request('/api/settings/non-defaults', cookie=cookie)
self.assertEqual(status, 200)
unsupported = next(row for row in view['rows'] if row['label'] == 'Automatic adoption')
unsupported = next(row for row in view['rows'] if row['label'] == 'Confirm limits')
self.assertFalse(unsupported['resetAvailable'])
self.assertEqual(self.request('/api/settings/recovery/preview', cookie=cookie,
payload={'view': view['view'], 'selected': [unsupported['id']]})[0], 409)
@@ -99,4 +96,4 @@ class TestRecoveryHttp(TestCase):
self.assertEqual(result[0], 200)
self.assertTrue(result[1]['saved'])
self.assertFalse(self.params.get_bool('ShowBrakeStatus'))
self.assertTrue(self.params.get_bool('SLCConfirmation'))
self.assertTrue(self.params.get('SLCConfirmation'))
@@ -107,7 +107,7 @@ class TestOffroadConfigurationPages(unittest.TestCase):
'conditional': ('Saved driving mode',),
'conditional/cem': ('Speed threshold', 'Speed with lead', 'Stop lights'),
'lane_change': ('Allow lane changes', 'Minimum lane-change speed', 'Automatic Lane Changes', 'Lane Change Speed'),
'slc': ('Speed Limit Controller', 'Automatic adoption'),
'slc': ('Speed Limit Controller', 'Confirm limits'),
'profiles': ('Force Stop', 'Selected Acceleration Profile', 'Selected Deceleration Profile'),
'torque': ('Steering Controller', 'Turn Assist', 'Lat Accel', 'Friction'),
'wheel': ('Short press', 'Hold', 'Distance long press', 'MODE press', 'Star button'),
@@ -138,8 +138,6 @@ OnroadLayoutPage.methods.setProjectionLeadInfo.call(leadVm, "Both")
assert.equal(leadVm.state.draft.leadInfo, "Speed")
assert.equal(leadChanges.length, 2)
assert.equal(validDocument({ ...native.document, signalBorder: true }, native.metadata), false, "the comma screen keeps its C4 settings")
assert.match(OnroadLayoutPage.template, /Personality alerts follow the comma screen setting\./)
assert.doesNotMatch(OnroadLayoutPage.template, /setProjectionDisplay\('hidePersonalityAlerts', value\)/)
assert.match(OnroadLayoutPage.template, /Red Blind-Spot Border/)
assert.match(OnroadLayoutPage.template, /Amber Signal Border/)
const borderChanges = []
@@ -76,7 +76,7 @@ class TestSavedTorquePage(unittest.TestCase):
self.assertTrue(self.gateway._context('torque').editing_saved_vehicle)
self.assertEqual(self.context.sample().cp.carFingerprint, 'HYUNDAI_IONIQ_6')
self.assertEqual(self.gateway._context('slc').cp.carFingerprint, 'HYUNDAI_IONIQ_6')
slc = next(row for row in self.page('slc')['rows'] if row['label'] == 'Automatic adoption')
slc = next(row for row in self.page('slc')['rows'] if row['label'] == 'Confirm limits')
self.assertTrue(slc['available'])
self.offroad.return_value = False
with patch.object(self.context, 'configuration_allowed', return_value=True):
@@ -461,9 +461,9 @@ class SettingsGatewayTest(unittest.TestCase):
self.assertIn("Vision", primary["choices"])
self.assertIn("Vision", secondary["choices"])
self.assertNotIn("require your confirmation", primary["reason"])
confirmation = next(row for row in rows if row["label"] == "Automatic adoption")
self.assertEqual(confirmation["value"], "On")
self.assertIn("immediately", confirmation["reason"])
confirmation = next(row for row in rows if row["label"] == "Confirm limits")
self.assertEqual(confirmation["value"], "Off")
self.assertIn("automatically", confirmation["reason"])
self.assertIn("accepts", confirmation["reason"])
with mock.patch.dict(os.environ, {"SLC_REPLAY_RUNTIME": "0", "SLC_VISION_DEVELOPMENT": "0"}):
@@ -119,34 +119,18 @@ class SenderJobTest(unittest.TestCase):
def test_sends_in_the_background_and_drops_the_snapshot_when_too_big(self):
bundles, sent = [], []
sending, release = threading.Event(), threading.Event()
def bundle(system_snapshot=True):
bundles.append(system_snapshot)
return "logs.zip", b"x" * (DISCORD_FILE_LIMIT + 1 if system_snapshot else 10)
def sender(*args):
sending.set()
if not release.wait(2):
raise TimeoutError("The test did not release the send")
sent.append(args)
job = StarPortLogSender(bundle, sender=sender, summary=lambda: SUMMARY, url=lambda: URL)
try:
self.assertEqual(job.start("hello")["state"], "sending")
self.assertTrue(sending.wait(2))
self.assertEqual(job.status()["state"], "sending")
self.assertEqual(sent, [])
finally:
release.set()
if job._thread is not None:
job._thread.join(2)
self.assertFalse(job._thread.is_alive())
job = StarPortLogSender(bundle, sender=lambda *args: sent.append(args), summary=lambda: SUMMARY, url=lambda: URL)
self.assertEqual(job.start("hello")["state"], "sending")
status = self.wait(job)
self.assertEqual(status["state"], "sent")
self.assertIn("snapshot was left out", status["message"])
self.assertEqual(bundles, [True, False])
self.assertEqual(sent, [(b"x" * 10, "logs.zip", "hello", SUMMARY)])
self.assertEqual(sent[0][2], "hello")
def test_one_send_at_a_time_and_failures_are_reported(self):
release = threading.Event()
@@ -355,7 +355,8 @@ export const ONROAD_LAYOUT_TEMPLATE = `
<div class="gx-layout__subhead"><h4>StarPort screen options</h4></div>
<p class="gx-note">Only the StarPort screen uses these. The comma screen has its own options on the Comma tab. Save to apply.</p>
<h5 class="gx-layout__display-group">Alerts and borders</h5>
<p class="gx-note">Personality alerts follow the comma screen setting.</p>
<GalaxySettingRow feedback="draft" :row="switchRow('Hide Personality Alerts', state.draft.hidePersonalityAlerts === true)" :index="0"
:disabled="!editable || !!state.drag || !!state.layerDrag" :save-value="(index, value) => setProjectionDisplay('hidePersonalityAlerts', value)" />
<GalaxySettingRow feedback="draft" :row="switchRow('Red Blind-Spot Border', state.draft.blindSpotBorder === true)" :index="1"
:disabled="!editable || !!state.drag || !!state.layerDrag" :save-value="(index, value) => setProjectionDisplay('blindSpotBorder', value)" />
<GalaxySettingRow feedback="draft" :row="switchRow('Amber Signal Border', state.draft.signalBorder === true)" :index="2"
@@ -156,8 +156,7 @@ class HKGVehicleTorquePolicy(HKGShapedTorquePolicy):
if self.profile == 'kia_stinger_2022':
return value * shaping.get_kia_stinger_2022_center_taper_scale(setpoint, speed)
if self.profile == 'kia_ev6':
return (value * shaping.get_kia_ev6_low_speed_center_taper_scale(setpoint, speed) *
shaping.get_kia_ev6_center_output_scale(setpoint, speed, self.future_lateral_accel))
return value * shaping.get_kia_ev6_low_speed_center_taper_scale(setpoint, speed) * shaping.get_kia_ev6_center_output_scale(setpoint, speed)
if self.profile == 'kia_carnival':
return (
value
@@ -544,8 +544,6 @@ KIA_EV6_LOW_SPEED_CENTER_TAPER_SPEED_WIDTH = 1.4
KIA_EV6_CENTER_OUTPUT_TAPER_MAX = 0.14
KIA_EV6_HIGHWAY_CENTER_DAMPING = 0.08
KIA_EV6_CENTER_OUTPUT_TAPER_LAT = 0.30
KIA_EV6_CENTER_OUTPUT_TAPER_LAT_WIDTH = 0.08
@@ -1300,17 +1298,12 @@ def get_kia_ev6_low_speed_center_taper_scale(desired_lateral_accel: float, v_ego
reduction = KIA_EV6_LOW_SPEED_CENTER_TAPER_MAX * speed_weight * center_weight
return 1.0 - reduction
def get_kia_ev6_center_output_scale(desired_lateral_accel: float, v_ego: float,
future_lateral_accel: float | None = None) -> float:
def get_kia_ev6_center_output_scale(desired_lateral_accel: float, v_ego: float) -> float:
speed_weight = _kia_ev6_sigmoid((v_ego - KIA_EV6_CENTER_OUTPUT_TAPER_SPEED) /
KIA_EV6_CENTER_OUTPUT_TAPER_SPEED_WIDTH)
center_weight = _kia_ev6_sigmoid((KIA_EV6_CENTER_OUTPUT_TAPER_LAT - abs(desired_lateral_accel)) /
KIA_EV6_CENTER_OUTPUT_TAPER_LAT_WIDTH)
reduction = _flm_vehicle_knob("hyundai_kia_ev6.center_output_taper_max", KIA_EV6_CENTER_OUTPUT_TAPER_MAX) * speed_weight * center_weight
future_accel = abs(desired_lateral_accel) if future_lateral_accel is None else max(abs(desired_lateral_accel), abs(future_lateral_accel))
future_center_weight = _kia_ev6_sigmoid((KIA_EV6_CENTER_OUTPUT_TAPER_LAT - future_accel) /
KIA_EV6_CENTER_OUTPUT_TAPER_LAT_WIDTH)
reduction += KIA_EV6_HIGHWAY_CENTER_DAMPING * speed_weight * future_center_weight
return 1.0 - reduction
+9 -20
View File
@@ -30,7 +30,6 @@ MAX_LANE_WIDTH = 4.8
MAX_OFFSET = 0.3
MIN_CENTER_TO_LINE = 1.1
MAX_CORRECTION = 0.004 * 0.30
MAX_CORRECTION_JERK = 1.0
CENTER_ERROR_DEADBAND = 0.08
SINGLE_LINE_DEADBAND_MIN = 0.04
E2E_MAX_PATH_STD = 0.35
@@ -297,17 +296,6 @@ class LaneCenteringController:
self._correction = 0.0
return self._result(base, reason)
def _tracking_correction(self, base: float, correction: float, measured: float, speed: float, delay: float) -> float:
if self._tracker.path is None:
return correction
x, y = self._tracker.path
source = self._clearance_observation
offset = source.settings.offset_m if source is not None else 0.
if self._tracker.side is None and self._tracker.width is not None:
bound = min(MAX_OFFSET, max(0., self._tracker.width * .5 - MIN_CENTER_TO_LINE))
offset = clip_scalar(offset, -bound, bound)
return limit_tracking((x, y + offset), base, correction, measured, speed, delay)
def guard_applied(self, model, base: float, candidate: float, measured: float, speed: float, delay) -> float:
if self._body_required and (self._body is None or not self._body.valid):
self.reset()
@@ -318,7 +306,14 @@ class LaneCenteringController:
delta = candidate - base
allowed = 0. if checked_delay is None else delta
if checked_delay is not None:
allowed = self._tracking_correction(base, allowed, measured, speed, checked_delay)
if self._tracker.path is not None:
x, y = self._tracker.path
source = self._clearance_observation
offset = source.settings.offset_m if source is not None else 0.
if self._tracker.side is None and self._tracker.width is not None:
bound = min(MAX_OFFSET, max(0., self._tracker.width * .5 - MIN_CENTER_TO_LINE))
offset = clip_scalar(offset, -bound, bound)
allowed = limit_tracking((x, y + offset), base, allowed, measured, speed, checked_delay)
if self._body is not None:
allowed = limit_correction(model, base, allowed, measured, speed, checked_delay, self._body)
if allowed != delta:
@@ -391,12 +386,6 @@ class LaneCenteringController:
target = clip_scalar(raw, -0.004, 0.004) * 0.30
if strength != 1.0:
target = clip_scalar(target * strength, -MAX_CORRECTION, MAX_CORRECTION)
measured = _finite_number(observation.measured_curvature)
delay = _finite_number(observation.lateral_delay_s)
if measured is not None and delay is not None:
target = self._tracking_correction(base, target, measured, speed, delay)
step = (target - self._correction) * (1.0 - math.exp(-dt / ACQUIRE_TAU))
max_step = MAX_CORRECTION_JERK * dt / speed / speed
self._correction += clip_scalar(step, -max_step, max_step)
self._correction += (target - self._correction) * (1.0 - math.exp(-dt / ACQUIRE_TAU))
self._correction = clip_scalar(self._correction, -MAX_CORRECTION, MAX_CORRECTION)
return self._result(base, "qualified")
@@ -136,61 +136,3 @@ def test_kona_2022_friction_does_not_change_sibling_policies(monkeypatch, vehicl
controller = LatControlTorque(cp.as_reader(), interfaces[vehicle](cp), .01, controller_mode=ControllerMode.STARPILOT)
threshold, scale = controller.starpilot_extension.policy.friction_context(.1, 0., 0., 120. / 3.6)
assert threshold > 0. and scale > 0.
@pytest.mark.parametrize('alpha', (False, True))
@pytest.mark.parametrize('direction', (-1., 1.))
def test_ev6_highway_center_damping_preserves_controller_and_curve_response(monkeypatch, alpha, direction):
from types import SimpleNamespace
import math
from openpilot.cereal import log
from opendbc.car.vehicle_model import VehicleModel
from openpilot.starpilot.lateral import hkg_vehicle_shaping as shaping
cp = interfaces[CAR.KIA_EV6].get_non_essential_params(CAR.KIA_EV6)
cp.openpilotLongitudinalControl, cp.pcmCruise = alpha, not alpha
cs = SimpleNamespace(vEgo=26., steeringAngleDeg=0., steeringPressed=False)
params = log.VehicleParameters.new_message(angleOffsetDeg=0., roll=0.)
selected = shaping.KIA_EV6_HIGHWAY_CENTER_DAMPING
assert selected == .08
def run(taper, turn, mode=ControllerMode.STARPILOT):
monkeypatch.setattr(shaping, 'KIA_EV6_HIGHWAY_CENTER_DAMPING', taper)
controller = LatControlTorque(cp.as_reader(), interfaces[cp.carFingerprint](cp), .01, controller_mode=mode)
vm = VehicleModel(cp)
outputs, stages = [], []
for tick in range(300):
acceleration = direction * (1.5 if turn else .12 * math.sin(2 * math.pi * .4 * tick * .01))
value, _, state = controller.update(True, cs, vm, params, False, acceleration / cs.vEgo**2, False, .386)
assert math.isfinite(value) and abs(value) <= controller.steer_max + 1e-6
outputs.append(value)
stages.append((state.p, state.i, state.d, state.f))
off, _, state = controller.update(False, cs, vm, params, False, 0., False, .386)
assert off == 0. and not state.active
return outputs, stages, controller.torque_params.to_dict(), (controller.pid.pos_limit, controller.pid.neg_limit, controller.steer_max)
actual, baseline = run(selected, False), run(0., False)
assert actual[1:] == baseline[1:]
assert sum(x*x for x in actual[0]) < .88 * sum(x*x for x in baseline[0])
assert all(a*b >= -1e-12 for a, b in zip(actual[0], baseline[0], strict=True))
actual_turn, baseline_turn = run(selected, True), run(0., True)
assert actual_turn[1:] == baseline_turn[1:]
assert actual_turn[0] == pytest.approx(baseline_turn[0], rel=1e-6, abs=1e-8)
assert run(selected, False, ControllerMode.STANDARD) == run(0., False, ControllerMode.STANDARD)
def test_ev6_taper_keeps_low_speed_and_substantial_curve_effect_small():
from openpilot.starpilot.lateral import hkg_vehicle_shaping as shaping
for speed in (0., 3., 26., 36.):
for acceleration in (-2., -1., 0., 1., 2.):
actual = shaping.get_kia_ev6_center_output_scale(acceleration, speed)
speed_weight = shaping._kia_ev6_sigmoid((speed - shaping.KIA_EV6_CENTER_OUTPUT_TAPER_SPEED) /
shaping.KIA_EV6_CENTER_OUTPUT_TAPER_SPEED_WIDTH)
center_weight = shaping._kia_ev6_sigmoid((shaping.KIA_EV6_CENTER_OUTPUT_TAPER_LAT - abs(acceleration)) /
shaping.KIA_EV6_CENTER_OUTPUT_TAPER_LAT_WIDTH)
old = 1. - .14 * speed_weight * center_weight
assert .78 <= actual <= old <= 1.
if speed <= 3.:
assert old - actual < .002
if abs(acceleration) >= 1.:
assert old - actual < .00002
@@ -483,70 +483,11 @@ class TestSingleLineTracking(unittest.TestCase):
controller = LaneCenteringController()
source = observation(m, base_curvature=0., measured_curvature=0., lateral_delay_s=.1)
result = controller.update(source)
self.assertEqual(result.correction, 0.)
self.assertGreater(-direction * result.correction, 0.)
assert result.candidate_curvature is not None
self.assertEqual(controller.guard_applied(m, 0., result.candidate_curvature, 0., 20., .1), 0.)
self.assertEqual(controller._correction, 0.)
def test_centered_planned_curves_do_not_build_an_opposing_nudge(self):
from openpilot.starpilot.lateral.lane_clearance import _center_motion
for curvature in (-.006, -.002, .002, .006):
x, y, _ = _center_motion(curvature, curvature, 25., .1)
x, y = np.r_[0., x, 50.], np.r_[0., y, y[-1]]
m = model()
for index, shift in ((1, -1.8), (2, 1.8)):
m.laneLines[index].x, m.laneLines[index].y = x.tolist(), (y + shift).tolist()
m.position.x, m.position.y = x.tolist(), (y - math.copysign(.6, curvature)).tolist()
m.position.yStd = [.1] * len(x)
controller = LaneCenteringController()
source = observation(m, base_curvature=curvature, measured_curvature=curvature,
speed_mps=25., lateral_delay_s=.1)
self.assertAlmostEqual(controller.update(source).correction, 0., delta=1e-8)
def test_curve_spacing_limit_shapes_the_target_before_it_ramps(self):
from openpilot.starpilot.lateral.lane_clearance import _center_motion, limit_tracking
from openpilot.starpilot.lateral.lane_centering import ACQUIRE_TAU
for sign in (-1., 1.):
base, delta, speed = sign * .004, sign * .00004, 25.
x, y, _ = _center_motion(base + delta, base, speed, .1)
x, y = np.r_[0., x, 50.], np.r_[0., y, y[-1]]
m = model()
for index, shift in ((1, -1.8), (2, 1.8)):
m.laneLines[index].x, m.laneLines[index].y = x.tolist(), (y + shift).tolist()
m.position.x, m.position.y = x.tolist(), (y - sign * .7).tolist()
m.position.yStd = [.1] * len(x)
source = observation(m, base_curvature=base, measured_curvature=base, speed_mps=speed, lateral_delay_s=.1)
controller = LaneCenteringController()
first = controller.update(source)
self.assertGreater(sign * first.correction, 0.)
self.assertLess(abs(first.correction), abs(delta) * .04)
raw = raw_correction(m, speed, 0., 0.)
assert raw is not None
path = controller._tracker.path
assert path is not None
target = limit_tracking(path, base, raw * .3 * 1.5, base, speed, .1)
self.assertAlmostEqual(first.correction, target * (1. - math.exp(-.01 / ACQUIRE_TAU)), delta=1e-12)
assert first.candidate_curvature is not None
applied = controller.guard_applied(m, base, first.candidate_curvature, base, speed, .1) - base
self.assertAlmostEqual(applied, first.correction, delta=1e-12)
def test_nudge_build_and_reversal_respect_lateral_acceleration_rate(self):
for speed in (5., 20., 35.):
controller = LaneCenteringController()
previous = 0.
for tick in range(160):
sign = 1. if tick < 80 else -1.
dt = (.01, .02, .05)[tick % 3]
source = observation(model(left=-1.8 + sign * 1.4, right=1.8 + sign * 1.4),
speed_mps=speed, elapsed_seconds=dt, model_sample_id=tick,
settings=LaneCenteringSettings(True, e2e_authority=0., strength=3.))
result = controller.update(source)
self.assertLessEqual(abs(result.correction - previous) * speed * speed / dt, 1. + 1e-12)
previous = result.correction
self.assertLess(previous, 0.)
self.assertEqual(controller.update(replace(source, driver_override=True)).correction, 0.)
self.assertEqual(controller.update(replace(source, lateral_active=False)).correction, 0.)
def test_two_line_tracking_interpolates_shared_coordinates_and_bounds_offset(self):
m = model(left=-1.31, right=1.31)
m.laneLines[2].x = [float(x) + .25 for x in XS]
@@ -162,8 +162,6 @@ class LaneRuntimeTests(unittest.TestCase):
feed(controls, 1_000_000_000 + tick * 10_000_000, tick)
controls.state_control()
self.assertIsNotNone(controls.last_lane_centering_result)
assert controls.last_lane_centering_result is not None
self.assertEqual(controls.last_lane_centering_result.correction, 0.)
self.assertEqual(controls.lane_centering_applied, 0.)
self.assertAlmostEqual(controls.desired_curvature, .0002)
+8 -9
View File
@@ -12,15 +12,14 @@ Policy order matters. Target shaping precedes PID preparation; vehicle output sh
`LongitudinalPlanner.update(sm, *, cruise_ceiling=None, profile_tuning=None)` accepts an optional
`CruiseCeiling(speed_mps, authority)` in addition to the existing driver cruise
message. The live caller supplies no ceiling. The planner still reads
`carState.vCruise` for the driver's selected ceiling and leaves every supplied
Cap'n Proto message unchanged. [SLC](../speed_limits/README.md) adopts accepted
limits into Card's ordinary cruise SET; it supplies no additional planner ceiling.
The optional input remains available to explicit callers and replay tests.
message. The live [SLC](../speed_limits/README.md) caller supplies a qualified cap.
The planner still reads `carState.vCruise` as the driver's selected ceiling and
leaves supplied messages unchanged. Accepted limits affect the planner cap, not SET.
The `CruiseCeiling` dataclass validates no source or acceptance history by
construction. Its caller must first establish a fresh, accepted, normalized
m/s ceiling in the correct speed coordinate. The planner validates its numeric
construction. Its caller must establish an accepted, normalized m/s ceiling in
the correct speed coordinate, including the fixed previous-accepted-limit fallback.
Vehicle coordinates and authority remain fresh. The planner validates its numeric
shape and the shared four-mode `Authority` contract. It applies the lower of
the supplied ceiling and the existing driver ceiling only when native system
longitudinal control is available, the supplied authority grants active system
@@ -33,7 +32,7 @@ publish that diagnostic or alter the longitudinal plan schema.
This optional input changes only the cruise candidate before the planner's
existing jerk limiting. SLC uses the ordinary cruise braking response and selected
profile after its SET adoption. Lead-relevance checks still prevent cruise comfort
profile for its capped target; a cap does not strengthen a softer cruise profile. Lead-relevance checks still prevent cruise comfort
tuning from weakening braking for a close, slow or braking lead; malformed lead
evidence also vetoes that tuning. The native MPC, lead, e2e, stop and acceleration
arbitration remain in place. An inactive planner may still compute a nonzero
@@ -111,7 +110,7 @@ different historical scales:
| Sport (`sport`) | 2.4 m/s² | 2.0 m/s² |
These are positive deceleration magnitudes for ordinary cruise braking,
including an SLC change to cruise SET. Global values preserve the historical
including a changed SLC cap. Global values preserve the historical
1.2 m/s² baseline with half/normal/double responses; explicit personality
presets retain their original physical values. The distinction preserves
existing tuning and saved-document compatibility.
@@ -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
@@ -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
@@ -245,6 +245,48 @@ class TestVoltCc(unittest.TestCase):
self.assertFalse(any(msg[0] in (0x2cb, 0x315, 0x200, 0x3d1) for msg in sent[-1][0]))
def test_slc_cap_reaches_cruise_down_without_changing_driver_set(self):
for metric in (False, True):
with self.subTest(metric=metric):
controls, card, ci, packer, sent, base, offset = fixture()
card.is_metric, card.slc_control_enabled, card.slc_source_floor_ns = metric, True, 0
state = messaging.new_message('slcState').slcState
state.enabled, state.sessionId, state.hasCeiling, state.effectiveCap = True, 'drive', True, 45 * .44704
controls.sm.data['slcState'] = state
for flags in (controls.sm.seen, controls.sm.valid, controls.sm.alive):
flags['slcState'] = True
flags['carControl'] = True
command = structs.CarControl(enabled=True, latActive=True, longActive=True)
command.actuators.accel = -1.
command.actuators.longControlState = structs.CarControl.Actuators.LongControlState.pid
command.hudControl.setSpeed = 70 * .44704
controls.sm.data['carControl'] = command
controls.sm.logMonoTime['carControl'] = base
by_phase = []
for phase in ('active', 'stale'):
directions = []
for tick in range(100):
now = base + (len(by_phase) * 100 + tick) * 10_000_000
out = ci.update([(now + offset - 2_000_000,
physical_frames(packer, speed=55 * .44704, target_speed=70 * .44704, counter=(tick // 3) % 4))])
self.assertTrue(out.canValid)
out.vCruise = out.vCruiseCluster = 70 * .44704 * 3.6
for name in controls.sm.logMonoTime:
controls.sm.logMonoTime[name] = now - 2_000_000
controls.sm.recv_time[name] = (now - 1_000_000) / 1e9
controls.sm.logMonoTime['slcState'] = state.frameMonoTime = now - (2_000_000 if phase == 'active' else 200_000_000)
with patch('openpilot.selfdrive.car.card.clock_pair_ns', return_value=(now, now + offset)), \
patch('openpilot.selfdrive.car.card.time.monotonic_ns', return_value=now), \
patch('openpilot.selfdrive.car.card.REPLAY', False):
card.controls_update(out, command.as_reader())
directions.extend((data[5] >> 4) & 7 for address, data, _ in sent[-1][0] if address == 0x1e1)
self.assertAlmostEqual(out.vCruise, 70 * .44704 * 3.6, places=4)
self.assertAlmostEqual(command.hudControl.setSpeed, 70 * .44704, places=4)
by_phase.append(directions)
self.assertIn(3, by_phase[0])
self.assertEqual(by_phase[1], [])
self.assertIsNone(ci.CC.volt_cc_speed_ceiling_mps)
def test_actual_card_constructor_subscribes_selected_consumer(self):
class Registered(Exception):
pass
@@ -86,8 +86,9 @@ class TestPlannerSelection(unittest.TestCase):
imports = [node.module for node in planner.body if isinstance(node, ast.ImportFrom)]
self.assertIn('openpilot.starpilot.longitudinal.upstream.mpc', imports)
self.assertNotIn('openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc', imports)
self.assertFalse(any(value and value.startswith('openpilot.starpilot.') and value !=
'openpilot.starpilot.longitudinal.upstream.mpc' for value in imports))
self.assertEqual({value for value in imports if value and value.startswith('openpilot.starpilot.')}, {
'openpilot.starpilot.longitudinal.upstream.mpc', 'openpilot.starpilot.longitudinal.cruise_ceiling',
})
if __name__ == '__main__':
@@ -60,7 +60,7 @@ class TrafficAccelerationPlannerTests(unittest.TestCase):
self.assertLessEqual(traffic.output_a_target, 0.44 + 1e-8)
self.assertLess(traffic.a_cruise, baseline.a_cruise)
def test_soft_saved_traffic_keeps_slc_and_force_decel_braking(self):
def test_slc_cap_preserves_soft_traffic_response_and_native_force_decel(self):
target = self.tuning(acceleration=0.6, braking=0.35)
self.assertAlmostEqual(target.acceleration_max, 0.6)
self.assertAlmostEqual(target.cruise_brake_magnitude, 0.35)
@@ -72,14 +72,15 @@ class TrafficAccelerationPlannerTests(unittest.TestCase):
for _ in range(60):
sm, _ = messages(force=force)
sm['carControl'].longActive = True
baseline.update(sm, cruise_ceiling=ceiling)
traffic.update(sm, cruise_ceiling=ceiling, profile_tuning=target, traffic_mode=True)
sm['carState'].vCruise = 15.0 * 3.6
baseline.update(sm, profile_tuning=target, traffic_mode=True)
self.assertEqual(traffic.mpc.solution_status, 0)
self.assertLessEqual(traffic.a_cruise, baseline.a_cruise + 1e-8)
self.assertEqual(traffic.last_cruise_ceiling_status, 'force_decel' if force else 'applied')
self.assertIsNotNone(traffic.last_profile)
self.assertAlmostEqual(traffic.last_profile.cruise_brake_magnitude, 0.35)
self.assertLessEqual(traffic.a_cruise, -0.5 + 1e-8)
self.assertLessEqual(traffic.a_cruise, (-0.5 if force else -0.35) + 1e-8)
def test_soft_saved_traffic_preserves_lead_and_model_stop_candidates(self):
target = self.tuning(acceleration=0.6, braking=0.5)
@@ -215,8 +216,10 @@ class TrafficAccelerationPlannerTests(unittest.TestCase):
cruise_ceiling=cruise_ceiling, curve_ceiling=curve_ceiling)
self.assertEqual(planner.mpc.solution_status, 0)
self.assertAlmostEqual(planner.output_a_target, baseline.output_a_target, places=5)
if case in ('slc', 'curve'):
if case == 'curve':
self.assertLessEqual(planner.a_cruise, -0.5 + 1e-8)
elif case == 'slc':
self.assertLessEqual(planner.a_cruise, -0.42 + 1e-8)
elif case == 'force_decel':
self.assertLessEqual(planner.a_cruise, -0.5 + 1e-8)
elif case == 'model_stop':
@@ -23,7 +23,7 @@ class EndLoop(Exception):
class TestUpstreamConditional(unittest.TestCase):
def run_loop(self, *, safe=False, stale=False, clocks=True, replay=False):
def run_loop(self, *, safe=False, stale=False, clocks=True, replay=False, show_speed_limits=False):
directory = self.enterContext(tempfile.TemporaryDirectory())
params = Params(directory)
cp = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC_BOSCH)
@@ -32,6 +32,9 @@ class TestUpstreamConditional(unittest.TestCase):
params.put_bool('UseStarPilotLongitudinalPlanner', False, block=True)
params.put_bool('SafeMode', safe, block=True)
params.put_bool('ExperimentalMode', False, block=True)
params.put_bool('ShowSpeedLimits', show_speed_limits, block=True)
if show_speed_limits:
params.put('SLCPriority1', 'Dashboard', block=True)
params.put('ConditionalModeConfig', json.loads(encode_preferences(SavedPreferences(
mode=ModeChoice.CEM, cem=CEMOptions(speed_mps=20., stop_lights=False, lead=False, slower_lead=False)))), block=True)
settings = ConditionalSettingsOwner(params)
@@ -82,6 +85,15 @@ class TestUpstreamConditional(unittest.TestCase):
self.payloads[name] = getattr(messaging.log_from_bytes(event.to_bytes()), name)
if stale:
self.logMonoTime['modelV2'] -= 1_000_000_000
if show_speed_limits:
dashboard = messaging.new_message('slcDashboardObservation', valid=True).slcDashboardObservation
dashboard.carStateLogMonoTime = dashboard.observedMonoTime = stamp
dashboard.validUntilMonoTime = stamp + 100_000_000
dashboard.status, dashboard.speedMps, dashboard.episode = 'valid', 15., 1
dashboard.producerSessionId = 'card'
self.payloads['slcDashboardObservation'] = dashboard
self.logMonoTime['slcDashboardObservation'] = stamp
self.valid['slcDashboardObservation'] = self.alive['slcDashboardObservation'] = True
sm = Scene()
@@ -133,10 +145,19 @@ class TestUpstreamConditional(unittest.TestCase):
self.assertFalse(any(result.accepted for _, result in results))
self.assertFalse(any(str(plan.longitudinalPlanSource) == 'e2e' for plan in plans))
def test_cem_and_speed_limit_share_one_status_per_model_frame(self):
results, statuses, plans, subscriptions = self.run_loop(show_speed_limits=True)
self.assertEqual(subscriptions.count('deviceState'), 1)
self.assertEqual(len(statuses), len(plans))
self.assertTrue(any(result.accepted and result.experimental for _, result in results))
self.assertTrue(all(status.slcState.displayOnly and status.slcState.speedLimit == 15. and
status.slcState.conditionalMode.version == 1 for status in statuses))
def test_safe_mode_retains_upstream_without_conditional_transport(self):
results, statuses, plans, subscriptions = self.run_loop(safe=True)
self.assertFalse(results or statuses)
self.assertNotIn('deviceState', subscriptions)
self.assertNotIn('starpilotRadarState', subscriptions)
self.assertNotIn('starpilotNavigation', subscriptions)
self.assertEqual(len(plans), 100)
def test_missing_live_clock_does_not_publish_conditional_proposals(self):
@@ -2,11 +2,15 @@
from pathlib import Path
from types import SimpleNamespace
import unittest
from unittest.mock import patch
from unittest.mock import Mock, patch
import numpy as np
from openpilot.cereal import log
import pytest
from openpilot.cereal import log, messaging
from openpilot.common.params import Params
from openpilot.selfdrive.controls import plannerd as starpilot_plannerd
from openpilot.starpilot.longitudinal.upstream import mpc, planner, plannerd
from openpilot.starpilot.speed_limits.tests import test_runtime_replay as replay
class Solver:
@@ -51,9 +55,9 @@ class TestUpstreamPlanner(unittest.TestCase):
self.assertEqual(vars(cp), before)
self.assertFalse(hasattr(stock, 'profile_tuning'))
def test_upstream_process_constructs_only_native_publishers_and_planner(self):
def test_upstream_process_without_slc_transport_uses_only_native_services(self):
calls = []
cp = SimpleNamespace(brand='honda', openpilotLongitudinalControl=True)
cp = SimpleNamespace(brand='honda', openpilotLongitudinalControl=True, passive=True)
class SM:
def update(self):
raise InterruptedError('bounded end of mocked loop')
@@ -61,7 +65,8 @@ class TestUpstreamPlanner(unittest.TestCase):
calls.append(('subscribe', services, kwargs))
return SM()
fake_params = SimpleNamespace(get=lambda key, **kwargs: b'CP')
with patch.object(plannerd, 'config_realtime_process'), patch.object(plannerd, 'Params', return_value=fake_params), \
with patch.dict('os.environ', {}, clear=True), patch.object(plannerd, 'config_realtime_process'), \
patch.object(plannerd, 'Params', return_value=fake_params), \
patch.object(plannerd.messaging, 'log_from_bytes', return_value=cp), \
patch.object(plannerd, 'LongitudinalPlanner', side_effect=lambda cp: calls.append(('planner', cp))), \
patch.object(plannerd, 'LaneDepartureWarning', return_value=object()), \
@@ -77,5 +82,87 @@ class TestUpstreamPlanner(unittest.TestCase):
self.assertEqual(Path(plannerd.__file__).resolve(), (root / 'plannerd.py').resolve())
@pytest.mark.parametrize('choice', ['wheel', 'screen', 'stock_cruise'])
def test_upstream_planner_applies_confirmed_slc_with_op_long(tmp_path, monkeypatch, choice):
fixture = replay.RuntimeReplayTests()
fixture.setUp()
cp, sm = fixture.cp, fixture.sm
cp.openpilotLongitudinalControl = choice != 'stock_cruise'
sm['slcDashboardObservation'].speedMps = 15.
sm.data['deviceState'] = messaging.new_message('deviceState').deviceState
sm.valid['deviceState'] = sm.alive['deviceState'] = False
params = Params(str(tmp_path))
params.put('CarParams', cp.to_bytes(), block=True)
params.put_bool('UseStarPilotLongitudinalPlanner', False, block=True)
params.put_bool('SpeedLimitController', True, block=True)
params.put('SLCPriority1', 'Dashboard', block=True)
params.put('SLCConfirmation', 1, block=True)
publisher = Mock()
subscriber = Mock(return_value=sm)
outputs = []
events = {}
start = 2_000_000_000
class EndLoop(Exception):
pass
def update():
sm.frame = len(outputs) + 1
if sm.frame == 3:
raise EndLoop
sm.advance(start + (sm.frame - 1) * 50_000_000)
if sm.frame != 2 or choice == 'stock_cruise':
return
shown = outputs[0].slcState
assert shown.hasPending and not shown.hasCeiling
service = 'slcCruiseEvent' if choice == 'wheel' else 'slcAction'
event = messaging.new_message(service, valid=True)
event.logMonoTime = sm.logMonoTime['modelV2']
action = getattr(event, service)
action.sessionId, action.decisionId, action.presentationId = shown.sessionId, shown.decisionId, shown.presentationId
if choice == 'wheel':
action.kind, action.button = 'confirmationAccept', 'accel'
action.eventId, action.producerSessionId, action.observedMonoTime = 1, 'card-producer', event.logMonoTime
else:
action.kind, action.sequenceId = 'accept', 1
events[service] = event
def send(service, message):
if service == 'slcState':
outputs.append(message)
sm.all_checks = lambda services=None: True
monkeypatch.setattr(sm, 'update', update)
monkeypatch.setenv('REPLAY', '1')
monkeypatch.delenv('SLC_REPLAY_RUNTIME', raising=False)
monkeypatch.setattr(starpilot_plannerd, 'Params', lambda: params)
monkeypatch.setattr(plannerd, 'Params', lambda: params)
monkeypatch.setattr(plannerd, 'config_realtime_process', Mock())
monkeypatch.setattr(plannerd, 'LaneDepartureWarning', Mock(return_value=Mock(left=False, right=False)))
monkeypatch.setattr(messaging, 'SubMaster', subscriber)
monkeypatch.setattr(messaging, 'PubMaster', Mock(return_value=publisher))
monkeypatch.setattr(messaging, 'sub_sock', lambda service, **kwargs: service)
monkeypatch.setattr(messaging, 'recv_one_or_none', lambda service: events.pop(service, None))
publisher.send.side_effect = send
cruise = Mock(wraps=planner.get_cruise_accel)
monkeypatch.setattr(planner, 'get_cruise_accel', cruise)
with pytest.raises(EndLoop):
starpilot_plannerd.main()
assert 'slcDashboardObservation' in subscriber.call_args.kwargs['ignore_valid']
assert cruise.call_args_list[0].args[1] == pytest.approx(100. / 3.6)
assert sm['carState'].vCruise == 100.
state = outputs[1].slcState
if choice == 'stock_cruise':
assert state.displayOnly and not state.hasPending and not state.hasCeiling
assert cruise.call_args.args[1] == pytest.approx(100. / 3.6)
else:
assert state.hasCeiling and state.actionStatus == 'accept'
assert cruise.call_args.args[1] == 15.
assert params.get('SLCQualifiedHistory')['speed_mps'] == 15.
if __name__ == '__main__':
unittest.main()
@@ -14,6 +14,7 @@ from openpilot.starpilot.longitudinal.upstream.mpc import T_IDXS as T_IDXS_MPC
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan, should_stop
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET
from openpilot.common.swaglog import cloudlog
from openpilot.starpilot.longitudinal.cruise_ceiling import select_cruise_ceiling
A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6]
A_CRUISE_MAX_BP = [0., 10.0, 25., 40.]
@@ -70,7 +71,7 @@ class LongitudinalPlanner:
self.a_desired_trajectory = np.zeros(CONTROL_N)
self.j_desired_trajectory = np.zeros(CONTROL_N)
def update(self, sm):
def update(self, sm, *, cruise_ceiling=None):
if len(sm['carControl'].orientationNED) == 3:
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
else:
@@ -79,11 +80,17 @@ class LongitudinalPlanner:
v_ego = sm['carState'].vEgo
v_cruise_kph = min(sm['carState'].vCruise, V_CRUISE_MAX)
v_cruise = v_cruise_kph * CV.KPH_TO_MS
long_control_off = sm['controlsState'].longControlState == LongCtrlState.off
ceiling = select_cruise_ceiling(
cruise_ceiling, driver_v_cruise_kph=sm['carState'].vCruise, driver_cruise_mps=v_cruise,
system_long_available=self.CP.openpilotLongitudinalControl, long_control_active=not long_control_off,
selfdrive_enabled=sm['selfdriveState'].enabled, force_decel=sm['controlsState'].forceDecel,
)
if ceiling.effective_mps is not None:
v_cruise = ceiling.effective_mps
if sm['controlsState'].forceDecel:
v_cruise = 0.0
long_control_off = sm['controlsState'].longControlState == LongCtrlState.off
# Reset current state when not engaged, or user is controlling the speed
reset_state = long_control_off if self.CP.openpilotLongitudinalControl else not sm['selfdriveState'].enabled
# PCM cruise speed may be updated a few cycles later, check if initialized

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