Add metric-aware extra stopped-lead spacing

This commit is contained in:
firestar5683
2026-10-31 12:04:00 -05:00
parent 9068ec74f7
commit 72f04816c3
10 changed files with 264 additions and 15 deletions
@@ -538,7 +538,8 @@ class LongitudinalMpc:
return lead_xv
def update(self, radarstate, personality=log.LongitudinalPersonality.standard,
*, follow_seconds: float | None = None, acceleration_max: float | None = None, stop_line_m: float | None = None, modelV2=None):
*, follow_seconds: float | None = None, acceleration_max: float | None = None, stop_line_m: float | None = None,
modelV2=None, extra_stopped_gap_m: float = 0.0):
t_follow = get_T_FOLLOW(personality)
if follow_seconds is not None and math.isfinite(follow_seconds) and 0.5 <= follow_seconds <= 3.0:
t_follow = follow_seconds
@@ -555,6 +556,13 @@ class LongitudinalMpc:
lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1])
lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1])
if type(extra_stopped_gap_m) in (int, float) and math.isfinite(extra_stopped_gap_m) and 0.0 < extra_stopped_gap_m <= 3.048:
# Shift only lead obstacles; physical trajectories still own collision warnings.
if radarstate.leadOne.present:
lead_0_obstacle -= extra_stopped_gap_m * np.clip(1.0 - lead_xv_0[:, 1] / 5.0, 0.0, 1.0)
if radarstate.leadTwo.present:
lead_1_obstacle -= extra_stopped_gap_m * np.clip(1.0 - lead_xv_1[:, 1] / 5.0, 0.0, 1.0)
x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle])
self.source = MPC_SOURCES[np.argmin(x_obstacles[0])]
if stop_line_m is not None and math.isfinite(stop_line_m) and 0.0 <= stop_line_m <= 500.0:
@@ -271,6 +271,7 @@ class LongitudinalPlanner:
follow_seconds=follow if follow is not None else
(self.last_profile.follow_seconds if self.last_profile is not None else None),
acceleration_max=acceleration_cap,
extra_stopped_gap_m=(selected_profiles.extra_stopped_gap_m if profile_eligible and selected_profiles is not None else 0.0),
stop_line_m=self.force_stop_plan.obstacle_m, modelV2=qualified_model_leads(sm, sample_now_ns, self.model_lead_history))
# MPC has selected the actual follow time for this model cycle. Its solve
@@ -902,7 +902,7 @@ class SettingsGatewayTest(unittest.TestCase):
self.assertTrue(readable)
assert raw is not None
document = json.loads(raw)
self.assertEqual(document["schemaVersion"], 5)
self.assertEqual(document["schemaVersion"], 6)
self.assertEqual(document["selectedDecelerationProfile"], "sport")
self.assertNotIn("globalBrakingResponse", document)
self.assertEqual(document["profiles"]["aggressive"]["braking"], previous)
@@ -7,7 +7,8 @@ import math
import numbers
PERSONALITY_PROFILES_PARAM = "LongitudinalPersonalityProfiles"
PROFILE_SCHEMA_VERSION = 5
PROFILE_SCHEMA_VERSION = 6
MAX_EXTRA_STOPPED_GAP_M = 3.048
GLOBAL_BRAKING_RESPONSES = ("standard", "eco", "sport")
DEFAULT_DECELERATION_PROFILE = "eco"
PERSONALITY_IDS = ("traffic", "aggressive", "standard", "relaxed")
@@ -204,7 +205,8 @@ def default_personality_profiles(ev_tuning: bool, truck_tuning: bool = False) ->
def profile_document(profiles: dict[str, dict], *, enabled: bool, global_braking_response: str = DEFAULT_DECELERATION_PROFILE,
selected_acceleration_profile: str = "dom_default", selected_deceleration_profile: str | None = None) -> dict:
selected_acceleration_profile: str = "dom_default", selected_deceleration_profile: str | None = None,
extra_stopped_gap_m: float = 0.0) -> dict:
if type(enabled) is not bool:
raise ValueError("enabled must be a JSON boolean")
if global_braking_response not in GLOBAL_BRAKING_RESPONSES:
@@ -215,7 +217,10 @@ def profile_document(profiles: dict[str, dict], *, enabled: bool, global_braking
raise ValueError("invalid selected acceleration profile")
if selected_deceleration_profile not in ("dom_default", *GLOBAL_BRAKING_RESPONSES):
raise ValueError("invalid selected deceleration profile")
if type(extra_stopped_gap_m) not in (int, float) or not math.isfinite(extra_stopped_gap_m) or not 0 <= extra_stopped_gap_m <= MAX_EXTRA_STOPPED_GAP_M:
raise ValueError("invalid extra stopped-lead gap")
return {
"extraStoppedGapM": float(extra_stopped_gap_m),
"schemaVersion": PROFILE_SCHEMA_VERSION,
"enabled": enabled,
"selectedAccelerationProfile": selected_acceleration_profile,
@@ -331,6 +336,8 @@ def _strict_document(
keys.add("globalBrakingResponse")
elif schema_version >= 5:
keys.update(("selectedAccelerationProfile", "selectedDecelerationProfile"))
if schema_version >= 6:
keys.add("extraStoppedGapM")
if not isinstance(decoded, dict) or set(decoded) != keys:
return None
if type(decoded["schemaVersion"]) is not int or decoded["schemaVersion"] != schema_version:
@@ -343,6 +350,10 @@ def _strict_document(
if schema_version >= 5 and (decoded["selectedAccelerationProfile"] not in ("dom_default", "standard", "eco", "sport", "sport_plus") or
decoded["selectedDecelerationProfile"] not in ("dom_default", *GLOBAL_BRAKING_RESPONSES)):
return None
if schema_version >= 6 and (type(decoded["extraStoppedGapM"]) not in (int, float) or
not math.isfinite(decoded["extraStoppedGapM"]) or
not 0 <= decoded["extraStoppedGapM"] <= MAX_EXTRA_STOPPED_GAP_M):
return None
raw_profiles = decoded["profiles"]
if not isinstance(raw_profiles, dict) or set(raw_profiles) != set(PERSONALITY_IDS):
return None
@@ -368,6 +379,7 @@ def _strict_document(
profile[category] = validated
profiles[personality] = profile
return {
**({"extraStoppedGapM": float(decoded["extraStoppedGapM"])} if schema_version >= 6 else {}),
"schemaVersion": schema_version,
"enabled": decoded["enabled"],
**({"globalBrakingResponse": decoded["globalBrakingResponse"]} if schema_version == 4 else {}),
@@ -389,7 +401,7 @@ def strict_profile_document(raw_document) -> dict | None:
# meaning; adopting a speed-dependent preset requires an explicit selection.
decoded = _decode_json(raw_document)
version = decoded.get("schemaVersion") if isinstance(decoded, dict) else None
if type(version) is not int or version not in (2, 3, 4, PROFILE_SCHEMA_VERSION):
if type(version) is not int or version not in (2, 3, 4, 5, PROFILE_SCHEMA_VERSION):
return None
document = _strict_document(
decoded,
@@ -408,6 +420,9 @@ def strict_profile_document(raw_document) -> dict | None:
profile[category]["legacyActivation"] = True
document = profile_document(document["profiles"], enabled=document["enabled"],
global_braking_response=document.get("globalBrakingResponse", "standard"))
if document is not None and version == 5:
document["schemaVersion"] = PROFILE_SCHEMA_VERSION
document["extraStoppedGapM"] = 0.0
return document
@@ -482,11 +497,11 @@ def load_personality_profiles(raw_document, ev_tuning: bool, truck_tuning: bool
def serialize_personality_profiles(profiles, ev_tuning: bool, truck_tuning: bool = False, *, enabled: bool,
global_braking_response: str = DEFAULT_DECELERATION_PROFILE,
selected_acceleration_profile: str = "dom_default",
selected_deceleration_profile: str | None = None) -> str:
selected_deceleration_profile: str | None = None, extra_stopped_gap_m: float = 0.0) -> str:
del ev_tuning, truck_tuning
document = profile_document(profiles, enabled=enabled, global_braking_response=global_braking_response,
selected_acceleration_profile=selected_acceleration_profile,
selected_deceleration_profile=selected_deceleration_profile)
selected_deceleration_profile=selected_deceleration_profile, extra_stopped_gap_m=extra_stopped_gap_m)
canonical = strict_profile_document(document)
if canonical is None:
raise ValueError("Longitudinal personality profiles must be complete and valid.")
@@ -267,6 +267,7 @@ class SelectedProfileTuning:
acceleration_max: float | None = None
cruise_brake_magnitude: float | None = None
braking_style: str = 'standard'
extra_stopped_gap_m: float = 0.0
@dataclass(frozen=True)
@@ -300,7 +301,8 @@ def read_global_powertrain_preset(params, CP) -> GlobalPowertrainPreset | None:
def selected_profiles_requested(document: dict | None) -> bool:
if document is None:
return False
if document['selectedAccelerationProfile'] != 'dom_default' or document['selectedDecelerationProfile'] != 'dom_default':
if (document.get('extraStoppedGapM', 0.) > 0 or document['selectedAccelerationProfile'] != 'dom_default' or
document['selectedDecelerationProfile'] != 'dom_default'):
return True
return any(config['preset'] not in ('dom_default', 'selected_profile') and not config.get('legacyActivation', False)
for profile in document['profiles'].values() for category, config in profile.items() if category != 'following')
@@ -353,7 +355,7 @@ def resolve_selected_profiles(document: dict | None, personality, v_ego: float,
else:
braking = min(max(value, 0.35 if traffic_mode else 0.5), abs(ACCEL_MIN))
style = preset if preset in ('eco', 'sport') else 'standard'
return SelectedProfileTuning(acceleration, braking, style)
return SelectedProfileTuning(acceleration, braking, style, document.get("extraStoppedGapM", 0.))
class ProfileHost:
@@ -0,0 +1,192 @@
import copy
from dataclasses import replace
from types import SimpleNamespace as NS
import numpy as np
import pytest
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from openpilot.starpilot.longitudinal.profile_document import (
default_personality_profiles, profile_document, migrate_profile_document, strict_profile_document,
)
from openpilot.starpilot.longitudinal.profile_runtime import ProfileHost, selected_profiles_requested
from openpilot.starpilot.ui.feature_settings_state import row_change
from openpilot.starpilot.ui.tests import test_feature_settings_owner as owner_tests
@pytest.fixture
def owner():
fixture = owner_tests.FeatureSettingsOwnerTests()
fixture.setUp()
try:
yield fixture
finally:
fixture.doCleanups()
def document(gap=0.):
return profile_document(default_personality_profiles(False), enabled=False, selected_acceleration_profile='dom_default',
selected_deceleration_profile='dom_default', extra_stopped_gap_m=gap)
def test_migration_keeps_all_existing_settings_and_default_output():
old = document()
old['schemaVersion'] = 5
del old['extraStoppedGapM']
before = copy.deepcopy(old)
migrated = migrate_profile_document(old)
assert old == before
assert migrated == {**old, 'schemaVersion': 6, 'extraStoppedGapM': 0.}
assert not selected_profiles_requested(migrated)
assert selected_profiles_requested(document(1.))
@pytest.mark.parametrize('bad', [-1, 3.049, float('inf'), float('nan'), True, '1', None])
def test_invalid_gap_cannot_activate(bad):
raw = document()
raw['extraStoppedGapM'] = bad
assert strict_profile_document(raw) is None
def request(row, value):
gesture = row_change(row, 1)
assert gesture is not None
return replace(gesture, value=value)
def test_metric_display_and_edits_preserve_physical_gap_and_other_profiles(owner):
row = owner._row('profiles', 'profile:stopped_gap')
assert row.available and row.value == '0.0' and row.unit == 'ft' and row.maximum == 10.
assert owner.owner.apply(request(row, '5'))
stored = owner.params.get('LongitudinalPersonalityProfiles')
assert stored['extraStoppedGapM'] == pytest.approx(1.524)
owner.params.put_bool('IsMetric', True, block=True)
metric = owner._row('profiles', 'profile:stopped_gap')
assert metric.unit == 'm' and float(metric.value) == pytest.approx(1.524)
assert owner.params.get('LongitudinalPersonalityProfiles') == stored
assert owner.owner.apply(request(metric, '2'))
changed = owner.params.get('LongitudinalPersonalityProfiles')
assert changed == {**stored, 'extraStoppedGapM': 2.}
owner.params.put_bool('IsMetric', False, block=True)
imperial = owner._row('profiles', 'profile:stopped_gap')
assert float(imperial.value) == pytest.approx(2. / .3048, abs=.001)
assert not owner.owner.apply(request(imperial, '-1'))
assert not owner.owner.apply(request(imperial, '10.1'))
assert owner.params.get('LongitudinalPersonalityProfiles') == changed
braking = owner._row('profiles', 'profile:global_braking')
assert owner.owner.apply(request(braking, 'Sport'))
assert owner.params.get('LongitudinalPersonalityProfiles')['extraStoppedGapM'] == 2.
def test_changed_units_or_vehicle_or_permission_rejects_old_press(owner):
row = owner._row('profiles', 'profile:stopped_gap')
press = request(row, '5')
owner.params.put_bool('IsMetric', True, block=True)
assert not owner.owner.apply(press)
row = owner._row('profiles', 'profile:stopped_gap')
owner.fingerprint = 'OTHER'
assert not owner.owner.apply(request(row, '2'))
owner.allowed = False
assert not owner.owner.apply(request(owner._row('profiles', 'profile:stopped_gap'), '2'))
def test_saved_refresh_uses_existing_profile_snapshot_and_expires_bad_data(owner):
from openpilot.cereal import log
from opendbc.car import gen_empty_fingerprint
from opendbc.car.hyundai.interface import CarInterface
from opendbc.car.hyundai.values import CAR
cp = CarInterface.get_params(CAR.KIA_STINGER_2022, gen_empty_fingerprint(), [], True, False, False)
owner.params.put('LongitudinalPersonalityProfiles', document(2.), block=True)
host = ProfileHost(owner.params)
tuning = host.sample_selected(10_000_000_000, log.LongitudinalPersonality.standard, 1., cp)
assert tuning is not None and tuning.extra_stopped_gap_m == 2.
owner.params.put('LongitudinalPersonalityProfiles', {'invalid': True}, block=True)
assert host.sample_selected(11_000_000_000, log.LongitudinalPersonality.standard, 1., cp) is None
def solve(gap=0., *, speed=1., lead_speed=0., present=True, stop_line=None):
mpc = LongitudinalMpc()
mpc.set_cur_state(speed, 0.)
lead = NS(present=present, radar=True, dRel=10., vLead=lead_speed, aLeadK=0., aLeadTau=1.5, modelProb=.99)
radar = NS(leadOne=lead, leadTwo=NS(present=False))
mpc.update(radar, extra_stopped_gap_m=gap, stop_line_m=stop_line)
assert mpc.solution_status == 0
return mpc
def test_actual_native_mpc_adds_only_bounded_low_speed_lead_space():
baseline, extra = solve(), solve(3.048)
np.testing.assert_allclose(extra.params[:, 2], baseline.params[:, 2] - 3.048, rtol=0, atol=1e-12)
assert extra.a_solution[1] < baseline.a_solution[1]
np.testing.assert_array_equal(extra.params[:, [0, 1, 3, 4, 5]], baseline.params[:, [0, 1, 3, 4, 5]])
for kwargs in ({'present': False}, {'speed': 20., 'lead_speed': 20.}, {'stop_line': 0.}):
before, after = solve(**kwargs), solve(3.048, **kwargs)
np.testing.assert_array_equal(before.params, after.params)
np.testing.assert_array_equal(before.a_solution, after.a_solution)
@pytest.mark.parametrize('bad', [-1., 3.049, float('nan'), float('inf'), True, None])
def test_invalid_runtime_input_keeps_original_mpc(bad):
before, after = solve(), solve(bad)
np.testing.assert_array_equal(before.params, after.params)
np.testing.assert_array_equal(before.a_solution, after.a_solution)
def test_unconfigured_document_and_zero_reset_are_editable(owner):
from openpilot.starpilot.ui.feature_settings_state import row_default
owner.params.put('LongitudinalPersonalityProfiles', {}, block=True)
assert owner.owner.apply(request(owner._row('profiles', 'profile:stopped_gap'), '5'))
row = owner._row('profiles', 'profile:stopped_gap')
reset = row_default(row)
assert reset is not None and owner.owner.apply(reset)
assert owner.params.get('LongitudinalPersonalityProfiles')['extraStoppedGapM'] == 0.
def test_actual_stinger_planner_receives_preference_without_changing_limits():
from opendbc.car import gen_empty_fingerprint
from opendbc.car.hyundai.interface import CarInterface
from opendbc.car.hyundai.values import CAR
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
from openpilot.starpilot.longitudinal.profile_runtime import SelectedProfileTuning
from openpilot.starpilot.longitudinal.tests.test_lead_approach_planner import Frame, DRIVE_NS
cp = CarInterface.get_params(CAR.KIA_STINGER_2022, gen_empty_fingerprint(), [], True, False, False)
original_cp = cp.to_dict()
assert cp.openpilotLongitudinalControl
baseline, extra = LongitudinalPlanner(cp, init_v=1.), LongitudinalPlanner(cp, init_v=1.)
for index in range(10):
sm = Frame(index)
sm['carState'].vEgo = 1.
sm['carState'].aEgo = 0.
sm['radarState'].leadTwo.present = False
lead = sm['radarState'].leadOne
lead.radar = True
lead.dRel, lead.vLead, lead.vLeadK, lead.aLeadK = 10., 0., 0., 0.
baseline.update(sm, now_ns=sm.now_ns, drive_id=DRIVE_NS)
extra.update(sm, now_ns=sm.now_ns, drive_id=DRIVE_NS, selected_profiles=SelectedProfileTuning(extra_stopped_gap_m=1.524))
np.testing.assert_allclose(extra.mpc.params[:, 2], baseline.mpc.params[:, 2] - 1.524, rtol=0, atol=1e-12)
np.testing.assert_array_equal(extra.mpc.params[:, [0, 1, 4, 5]], baseline.mpc.params[:, [0, 1, 4, 5]])
assert extra.output_a_target <= baseline.output_a_target and cp.to_dict() == original_cp
def test_galaxy_editor_converts_units_and_rejects_old_unit_press():
from openpilot.starpilot.galaxy.tests import test_settings as gateway_tests
fixture = gateway_tests.SettingsGatewayTest()
fixture.setUp()
try:
page = fixture.page('profiles')
index = next(i for i, row in enumerate(page['rows']) if row['label'] == 'Extra stopped-lead distance')
assert page['rows'][index]['unit'] == 'ft'
intent = fixture.gateway.preview(page['view'], index, 0, fixture.session, fixture.generation, value=5.)
assert fixture.gateway.confirm(intent['intent'], fixture.session, fixture.generation)
saved = fixture.params.get('LongitudinalPersonalityProfiles')
assert saved['extraStoppedGapM'] == 1.524
fixture.params.put_bool('IsMetric', True, block=True)
page = fixture.page('profiles')
row = page['rows'][index]
assert row['unit'] == 'm' and float(row['value']) == 1.524
assert fixture.params.get('LongitudinalPersonalityProfiles') == saved
intent = fixture.gateway.preview(page['view'], index, 0, fixture.session, fixture.generation, value=2.)
fixture.params.put_bool('IsMetric', False, block=True)
assert not fixture.gateway.confirm(intent['intent'], fixture.session, fixture.generation)
assert fixture.params.get('LongitudinalPersonalityProfiles') == saved
finally:
fixture.doCleanups()
@@ -30,6 +30,7 @@ def legacy_document(profiles, *, enabled, global_braking_response='standard'):
profile[category]['preset'] = 'dom_default'
document = current_profile_document(profiles, enabled=enabled, global_braking_response=global_braking_response)
document['schemaVersion'] = 4
document.pop('extraStoppedGapM')
document['globalBrakingResponse'] = document.pop('selectedDecelerationProfile')
document.pop('selectedAccelerationProfile')
return document
@@ -324,7 +325,7 @@ class ProfileRuntimeTests(unittest.TestCase):
self.params.put('LongitudinalPersonalityProfiles', document, block=True)
migrated = read_settings(self.params)
assert migrated is not None and migrated.document is not None
self.assertEqual(migrated.document['schemaVersion'], 5)
self.assertEqual(migrated.document['schemaVersion'], 6)
self.params.put('LongitudinalPersonalityProfiles', document, block=True)
Path(self.params.get_param_path('LongitudinalPersonalityProfiles')).write_text('{broken')
self.assertIsNone(read_settings(self.params))
@@ -108,6 +108,7 @@ class SelectedProfilesTests(unittest.TestCase):
def test_v4_migration_preserves_dormant_custom_and_original_activation(self):
document = self.document('dom_default', 'sport', enabled=True)
document['schemaVersion'] = 4
document.pop('extraStoppedGapM')
document['globalBrakingResponse'] = document.pop('selectedDecelerationProfile')
document.pop('selectedAccelerationProfile')
for profile in document['profiles'].values():
@@ -27,10 +27,10 @@ from openpilot.starpilot.longitudinal.planner_selection import KEY as PLANNER_SE
from openpilot.starpilot.longitudinal.profile_document import (
ACCELERATION_PRESETS, ACCELERATION_SPEEDS_MPH, BRAKING_PRESETS, FOLLOWING_PRESETS, CURVE_BOUNDS,
DEFAULT_DECELERATION_PROFILE, PERSONALITY_PROFILES_PARAM, default_personality_profiles, initial_custom_curve,
DEFAULT_DECELERATION_PROFILE, MAX_EXTRA_STOPPED_GAP_M, PERSONALITY_PROFILES_PARAM, default_personality_profiles, initial_custom_curve,
FOLLOWING_SPEEDS_MPH, is_truck_fingerprint,
personality_reference_curves, serialize_personality_profiles,
update_personality_profile,
update_personality_profile, profile_document,
synchronise_profile_document_enabled,
)
from openpilot.starpilot.ui.feature_settings_state import (
@@ -1202,6 +1202,15 @@ class FeatureSettingsOwner:
reason=(CATEGORY_HELP[category] if category == 'braking' else
'Personalities set to Selected Profile follow this choice; explicit overrides stay unchanged.'),
default_value=preset_label('dom_default' if category == 'acceleration' else DEFAULT_DECELERATION_PROFILE)))
unit_value, unit_raw, unit_valid = self._value("IsMetric")
unit_valid = unit_valid and unit_value in ("0", "1") and self._readable("IsMetric")
scale = 1.0 if unit_value == "1" else 1.0 / 0.3048
gap = document.get("extraStoppedGapM", 0.0) if document is not None else 0.0
rows.append(FeatureRow("profile:stopped_gap", "Extra stopped-lead distance", str(round(gap * scale, 3)), raw,
step=0.1 if unit_value == "1" else 0.5, minimum=0., maximum=round(MAX_EXTRA_STOPPED_GAP_M * scale, 3),
unit="m" if unit_value == "1" else "ft", available=allowed and valid and unit_valid,
dependencies=(("IsMetric", unit_raw),), display_unit="m" if unit_value == "1" else "ft",
default_value="0", reason="Adds space behind slow or stopped leads with the StarPilot planner; zero keeps the normal gap."))
health = read_profile_health(self.params)
master = health.values["CustomPersonalities"]
master_row = self._bool_row("CustomPersonalities", "Custom Following and Jerk",
@@ -1482,6 +1491,11 @@ class FeatureSettingsOwner:
not self.authority(group) or self._raw(source_key) != request.expected or
not self._readable(source_key)):
return False
if key == "profile:stopped_gap":
units, raw_units, units_valid = self._value("IsMetric")
if (not units_valid or units not in ("0", "1") or not self._readable("IsMetric") or
request.dependencies != (("IsMetric", raw_units),) or request.display_unit != ("m" if units == "1" else "ft")):
return False
if group in ("torque", "aol", "aol_wheel"):
if request.capability is None or request.capability != self._capability(group) or \
any(self._raw(name) != raw for name, raw in request.dependencies):
@@ -1752,6 +1766,8 @@ class FeatureSettingsOwner:
self.traffic_profiles._sources() == (request.dependencies, True)) and
(not require_seed_capability or request.capability is not None and
self.long_profiles.curve_capability() == request.capability) and
(request.key != "profile:stopped_gap" or all(self._raw(key) == value and self._readable(key)
for key, value in request.dependencies)) and
self._readable("CustomPersonalities") and self._raw("CustomPersonalities") == master_source and
(dependencies is None or self.long_profiles._fresh(request, dependencies)))
result = commit_exact(self.params, key=PERSONALITY_PROFILES_PARAM, max_bytes=65536, raw=raw, expected=expected,
@@ -1760,6 +1776,17 @@ class FeatureSettingsOwner:
def _edit_document(self, key: str, value: str, *, seed_capability: tuple | None = None) -> str | None:
parts = key.split(":")
if key == "profile:stopped_gap":
document, _, valid = self._document()
if not valid:
return None
if document is None:
document = profile_document(default_personality_profiles(False, is_truck_fingerprint(self.vehicle_fingerprint())), enabled=False)
gap = float(value) * (1.0 if self._value("IsMetric")[0] == "1" else 0.3048)
if not math.isfinite(gap) or not 0. <= gap <= MAX_EXTRA_STOPPED_GAP_M:
return None
document["extraStoppedGapM"] = round(gap, 6)
return json.dumps(document, separators=(",", ":"), sort_keys=True, allow_nan=False)
if key in ('profile:global_acceleration', 'profile:global_braking'):
category = 'acceleration' if key.endswith('acceleration') else 'braking'
selected = preset_value(value) if type(value) is str else None
@@ -1778,7 +1805,8 @@ class FeatureSettingsOwner:
braking = document['selectedDecelerationProfile'] if document is not None else DEFAULT_DECELERATION_PROFILE
return serialize_personality_profiles(profiles, False, truck, enabled=document['enabled'] if document is not None else False,
selected_acceleration_profile=selected if category == 'acceleration' else acceleration,
selected_deceleration_profile=selected if category == 'braking' else braking)
selected_deceleration_profile=selected if category == 'braking' else braking,
extra_stopped_gap_m=document.get("extraStoppedGapM", 0.) if document is not None else 0.)
if len(parts) not in (3, 4) or parts[1] not in DOCUMENT_PROFILE_NAMES or parts[2] not in PRESETS:
return None
fingerprint = self.vehicle_fingerprint()
@@ -1843,4 +1871,5 @@ class FeatureSettingsOwner:
return serialize_personality_profiles(updated, ev, truck, enabled=enabled == "1",
selected_acceleration_profile=document["selectedAccelerationProfile"] if document is not None else "dom_default",
selected_deceleration_profile=(document["selectedDecelerationProfile"] if document is not None
else DEFAULT_DECELERATION_PROFILE))
else DEFAULT_DECELERATION_PROFILE),
extra_stopped_gap_m=document.get("extraStoppedGapM", 0.) if document is not None else 0.)
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "868381b1c309f270f146c1e4d6f8a3a1f02e06c46be1b2f9edaebb4a605dc9cf", "schema_version": 1}
{"protected_inventory_sha256": "50570638434af7b38217a29051f06666683cc974fee8abd0f3070a983bb29ffc", "schema_version": 1}