iPhone FoldGate

This commit is contained in:
firestar5683
2026-09-09 15:32:38 -05:00
parent 50e2c21dbd
commit eedd73e522
54 changed files with 4210 additions and 216 deletions
+56 -5
View File
@@ -37,6 +37,14 @@ CURVATURE_LOOKAHEAD_MAX = 0.40
FORD_CURVATURE_LOOKAHEAD = {
CAR.FORD_EXPLORER_MK6: 0.20,
}
FORD_CONSERVATIVE_PREVIEW_CARS = frozenset({
CAR.FORD_MUSTANG_MACH_E_MK1,
})
FORD_MANUAL_TURN_LATCH_CARS = frozenset({
CAR.FORD_MUSTANG_MACH_E_MK1,
})
MANUAL_TURN_ENTRY_ANGLE_DEG = 12.0
MANUAL_TURN_RECOVERY_SECONDS = 0.25
@dataclass(frozen=True)
@@ -101,6 +109,8 @@ class FordLateralController:
self.curvature_lane_change_factor = 0.85
self.human_turn = HumanTurnDetector()
self.manual_turn_latched = False
self.manual_turn_recovery_timer = 0.0
self.curvature_samples = deque(maxlen=max(2, round(0.3 / STEER_DT)))
self.curvature_last = 0.0
self._frame = 0
@@ -151,8 +161,13 @@ class FordLateralController:
def _current_curvature(CS) -> float:
return -CS.out.yawRate / max(CS.out.vEgoRaw, 0.1)
def _blend_and_scale(self, desired: float, predicted: float, v_ego: float) -> tuple[float, int]:
def _blend_and_scale(self, desired: float, predicted: float, v_ego: float, current: float = 0.0) -> tuple[float, int]:
blend = float(np.interp(abs(desired), [0.0, 0.001], [self.curvature_blend_low, self.curvature_blend_high]))
if self.CP.carFingerprint in FORD_CONSERVATIVE_PREVIEW_CARS:
if desired * predicted <= 0.0:
blend = 0.0
elif current * predicted > 0.0 and abs(current) > abs(desired) and abs(predicted) > abs(desired):
blend *= abs(desired) / abs(predicted)
requested = predicted * blend + desired * (1.0 - blend)
lane_change, direction = self._lane_change()
precision = 1
@@ -166,26 +181,62 @@ class FordLateralController:
def _manual_turn(self, CC, CS) -> bool:
if not CC.latActive:
self.human_turn.reset()
self.manual_turn_latched = False
self.manual_turn_recovery_timer = 0.0
return False
return self.human_turn.update(
detected = self.human_turn.update(
self.human_turn_enabled, CS.out.steeringPressed, CS.out.steeringAngleDeg)
if self.CP.carFingerprint not in FORD_MANUAL_TURN_LATCH_CARS:
return detected
if not self.human_turn_enabled:
self.manual_turn_latched = False
self.manual_turn_recovery_timer = 0.0
return False
blinker_direction = float(CS.out.rightBlinker) - float(CS.out.leftBlinker)
driver_turning_with_signal = (
CS.out.steeringPressed and abs(CS.out.steeringAngleDeg) >= MANUAL_TURN_ENTRY_ANGLE_DEG and
blinker_direction != 0.0 and not self._lane_change()[0] and
CS.out.steeringTorque * blinker_direction < 0.0
)
if detected or driver_turning_with_signal:
self.manual_turn_latched = True
if not self.manual_turn_latched:
self.manual_turn_recovery_timer = 0.0
return False
if CS.out.steeringPressed or blinker_direction != 0.0:
self.manual_turn_recovery_timer = 0.0
else:
self.manual_turn_recovery_timer += STEER_DT
if self.manual_turn_recovery_timer + 1e-9 >= MANUAL_TURN_RECOVERY_SECONDS:
self.manual_turn_latched = False
self.manual_turn_recovery_timer = 0.0
return self.manual_turn_latched
def update(self, CC, CS, actuators) -> FordLateralResult:
current = self._current_curvature(CS)
if not CC.latActive:
self.human_turn.reset()
self.manual_turn_latched = False
self.manual_turn_recovery_timer = 0.0
self.curvature_samples.clear()
self.curvature_last = 0.0
return FordLateralResult()
if self._manual_turn(CC, CS) or CS.out.vEgoRaw < 0.1:
manual_turn = self._manual_turn(CC, CS)
if manual_turn or CS.out.vEgoRaw < 0.1:
self.curvature_samples.clear()
self.curvature_last = 0.0
return FordLateralResult(active=True)
return FordLateralResult(active=not (
manual_turn and self.CP.carFingerprint in FORD_MANUAL_TURN_LATCH_CARS))
v_ego = float(CS.out.vEgoRaw)
predicted = self._predicted_curvature(v_ego, self._curvature_lookahead())
requested, precision = self._blend_and_scale(float(actuators.curvature), predicted, v_ego)
requested, precision = self._blend_and_scale(float(actuators.curvature), predicted, v_ego, current)
if v_ego > 9.0:
requested = float(np.clip(requested, current - CarControllerParams.CURVATURE_ERROR,
+119 -1
View File
@@ -32,12 +32,16 @@ def controller(monkeypatch):
return controller
def car_state(speed=15.0, curvature=0.0, steering_pressed=False, steering_angle=0.0):
def car_state(speed=15.0, curvature=0.0, steering_pressed=False, steering_angle=0.0,
steering_torque=0.0, left_blinker=False, right_blinker=False):
return SimpleNamespace(out=SimpleNamespace(
vEgoRaw=speed,
yawRate=-curvature * speed,
steeringPressed=steering_pressed,
steeringAngleDeg=steering_angle,
steeringTorque=steering_torque,
leftBlinker=left_blinker,
rightBlinker=right_blinker,
))
@@ -114,6 +118,36 @@ def test_curvature_strategy_uses_learned_lookahead(controller, monkeypatch):
assert lookaheads == [pytest.approx(0.38)]
def test_mach_e_preview_does_not_override_opposite_current_path(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
requested, _ = controller._blend_and_scale(-0.0001, 0.002, 20.0)
assert requested == pytest.approx(-0.0001)
def test_mach_e_preview_is_reduced_when_ahead_of_current_path(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
requested, _ = controller._blend_and_scale(0.0005, 0.002, 20.0, current=0.0015)
assert requested == pytest.approx(0.00065)
def test_mach_e_preview_remains_available_on_curve_entry(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
requested, _ = controller._blend_and_scale(0.0005, 0.002, 20.0, current=0.0002)
assert requested == pytest.approx(0.0011)
def test_non_mach_e_preview_blend_is_unchanged(controller):
requested, _ = controller._blend_and_scale(-0.0001, 0.002, 20.0, current=0.0015)
assert requested == pytest.approx(0.00074)
def test_lane_change_accepts_capnp_enum_wrappers(controller):
controller.model = SimpleNamespace(meta=SimpleNamespace(
laneChangeState=SimpleNamespace(raw=2),
@@ -146,3 +180,87 @@ def test_curvature_manual_turn_keeps_session_active_with_neutral_command(control
assert result.active
assert result.curvature == 0.0
def test_mach_e_signaled_manual_turn_yields_until_inputs_settle(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.human_turn_enabled = True
CC = SimpleNamespace(latActive=True)
actuators = SimpleNamespace(curvature=0.006)
result = controller.update(CC, car_state(
steering_pressed=True, steering_angle=-15.0, steering_torque=-2.0,
right_blinker=True), actuators)
assert not result.active
assert result.curvature == 0.0
result = controller.update(CC, car_state(
steering_pressed=True, steering_angle=5.0, steering_torque=2.0,
right_blinker=True), actuators)
assert not result.active
for _ in range(4):
result = controller.update(CC, car_state(), actuators)
assert not result.active
result = controller.update(CC, car_state(), actuators)
assert result.active
assert result.curvature > 0.0
def test_mach_e_opposite_blinker_correction_does_not_start_manual_turn(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
result = controller.update(
SimpleNamespace(latActive=True),
car_state(steering_pressed=True, steering_angle=-15.0, steering_torque=2.0,
right_blinker=True),
SimpleNamespace(curvature=0.001),
)
assert result.active
def test_mach_e_lane_change_nudge_does_not_start_manual_turn(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
controller.model = SimpleNamespace(
orientationRate=SimpleNamespace(z=[0.0] * 33),
meta=SimpleNamespace(
laneChangeState=SimpleNamespace(raw=2),
laneChangeDirection=SimpleNamespace(raw=2),
),
)
result = controller.update(
SimpleNamespace(latActive=True),
car_state(steering_pressed=True, steering_angle=-15.0, steering_torque=-2.0,
right_blinker=True),
SimpleNamespace(curvature=0.001),
)
assert result.active
def test_mach_e_small_blinker_nudge_does_not_start_manual_turn(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
result = controller.update(
SimpleNamespace(latActive=True),
car_state(steering_pressed=True, steering_angle=-5.0, steering_torque=-2.0,
right_blinker=True),
SimpleNamespace(curvature=0.001),
)
assert result.active
def test_mach_e_manual_turn_latch_resets_with_lateral_control(controller):
controller.CP.carFingerprint = CAR.FORD_MUSTANG_MACH_E_MK1
actuators = SimpleNamespace(curvature=0.001)
turning = car_state(
steering_pressed=True, steering_angle=-15.0, steering_torque=-2.0,
right_blinker=True)
assert not controller.update(SimpleNamespace(latActive=True), turning, actuators).active
assert not controller.update(SimpleNamespace(latActive=False), turning, actuators).active
assert controller.update(SimpleNamespace(latActive=True), car_state(), actuators).active
+4 -4
View File
@@ -16,7 +16,7 @@ from cereal import car, custom, log
from opendbc.car import gen_empty_fingerprint
from opendbc.car.car_helpers import interfaces
from opendbc.car.chrysler.values import JEEPS as CHRYSLER_JEEPS
from opendbc.car.gm.values import CAR as GM_CAR, EV_CAR as GM_EV_CAR, GMFlags
from opendbc.car.gm.values import CAR as GM_CAR, EV_CAR as GM_EV_CAR, GM_AUTO_HOLD_CARS, GMFlags
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR, EV_CAR as HYUNDAI_EV_CAR, HyundaiFlags, HyundaiStarPilotSafetyFlags
from opendbc.car.interfaces import TORQUE_SUBSTITUTE_PATH, CarInterfaceBase, GearShifter
from opendbc.car.mock.values import CAR as MOCK
@@ -1406,7 +1406,7 @@ class StarPilotVariables:
screen_management = self.get_value("ScreenManagement")
toggle.screen_brightness = max(self.get_value("ScreenBrightness", cast=float, condition=screen_management), 1)
toggle.screen_brightness_onroad = self.get_value("ScreenBrightnessOnroad", cast=float, condition=(screen_management and not toggle.force_onroad), min=1)
toggle.screen_brightness_onroad = self.get_value("ScreenBrightnessOnroad", cast=float, condition=(screen_management and not toggle.force_onroad), min=0)
toggle.screen_recorder = self.get_value("ScreenRecorder", condition=screen_management) or toggle.debug_mode
toggle.screen_timeout = self.get_value("ScreenTimeout", cast=float, condition=screen_management)
toggle.screen_timeout_onroad = self.get_value("ScreenTimeoutOnroad", cast=float, condition=screen_management)
@@ -1600,9 +1600,9 @@ class StarPilotVariables:
)
toggle.remote_start_boots_comma = self.get_value("RemoteStartBootsComma", condition=toggle.car_make == "gm")
gm_auto_hold_supported = toggle.car_model in LEGACY_VOLT_STOCK_ACC_CARS
gm_auto_hold_supported = toggle.car_model in GM_AUTO_HOLD_CARS
toggle.gm_auto_hold = self.get_value("GMAutoHold", condition=gm_auto_hold_supported)
toggle.volt_one_pedal_mode = self.get_value("VoltOnePedalMode", condition=gm_auto_hold_supported)
toggle.volt_one_pedal_mode = self.get_value("VoltOnePedalMode", condition=toggle.car_model in LEGACY_VOLT_STOCK_ACC_CARS)
toggle.volt_sng = self.get_value("VoltSNG", condition=toggle.car_model in LEGACY_VOLT_STOCK_ACC_CARS)
@@ -34,7 +34,7 @@ const VEHICLE_SETTING_MAKES = {
RemoteStartBootsComma: GM_MAKES,
HKGRemoteStartBootsComma: HKG_MAKES,
VoltSNG: ["Chevrolet", "Holden"],
GMAutoHold: ["Chevrolet", "Holden"],
GMAutoHold: ["Buick", "Chevrolet", "Holden"],
VoltOnePedalMode: ["Chevrolet", "Holden"],
RemapCancelToDistance: ["Chevrolet", "Holden"],
JeepBrakeHold: ["Jeep"],
@@ -490,7 +490,7 @@ function numericBounds(param) {
return { min: 1, max: 101, step: 1 }
}
if (param.key === "ScreenBrightnessOnroad") {
return { min: 1, max: 101, step: 1 }
return { min: 0, max: 101, step: 1 }
}
if (param.key === "LaneCenterOffset") {
@@ -62,7 +62,7 @@ const VEHICLE_SETTING_MAKES = {
RemoteStartBootsComma: ["Buick", "Cadillac", "Chevrolet", "GMC", "Holden"],
HKGRemoteStartBootsComma: ["Genesis", "Hyundai", "Kia"],
VoltSNG: ["Chevrolet", "Holden"],
GMAutoHold: ["Chevrolet", "Holden"],
GMAutoHold: ["Buick", "Chevrolet", "Holden"],
VoltOnePedalMode: ["Chevrolet", "Holden"],
RemapCancelToDistance: ["Chevrolet", "Holden"],
JeepBrakeHold: ["Jeep"],
@@ -140,9 +140,12 @@ export function numericBounds(param, values = {}) {
const n = Number(value)
return Number.isFinite(n) ? n : null
}
if (param.key === "ScreenBrightness" || param.key === "ScreenBrightnessOnroad") {
if (param.key === "ScreenBrightness") {
return { min: 1, max: 101, step: 1 }
}
if (param.key === "ScreenBrightnessOnroad") {
return { min: 0, max: 101, step: 1 }
}
if (param.key === "LaneCenterOffset") {
return { min: -0.3, max: 0.3, step: 0.01 }
}
@@ -26,6 +26,7 @@ export const SystemTools = {
fastStatus: null,
checkedForUpdates: false,
busy: "",
autoUpdateBusy: false,
profiles: [],
profileBusy: "",
}
@@ -77,6 +78,7 @@ export const SystemTools = {
const status = await api.getUpdateFastStatus()
if (!status) throw new Error("Update status unavailable")
this.fastStatus = status
this.isOnroad = !!status.isOnroad
} catch (e) {
this.fastStatus = null
if (throwOnError) throw e
@@ -195,6 +197,21 @@ export const SystemTools = {
this.busy = ""
}
},
async setAutomaticUpdates(enabled) {
if (this.autoUpdateBusy || this.isOnroad || this.fastStatus?.running || !this.fastStatus) return
const previous = !!this.fastStatus.automaticUpdates
this.autoUpdateBusy = true
this.fastStatus = { ...this.fastStatus, automaticUpdates: !!enabled }
try {
const payload = await api.updateParam({ key: "AutomaticUpdates", value: !!enabled, label: "Automatic Updates" })
showSnackbar(payload?.message || `Automatic updates ${enabled ? "enabled" : "disabled"}.`)
} catch (e) {
this.fastStatus = { ...this.fastStatus, automaticUpdates: previous }
showSnackbar(e?.message || "Failed to update Automatic Updates.", "error")
} finally {
this.autoUpdateBusy = false
}
},
async applyFastUpdate() {
if (this.busy || this.isOnroad) return
if (this.fastStatus?.running) { showSnackbar("Fast update is already running."); return }
@@ -301,6 +318,22 @@ export const SystemTools = {
</div>
</div>
<div class="gx-card" style="margin-bottom:12px;">
<div class="gx-row" style="border-top:none;">
<div class="gx-row__info">
<span class="gx-row__label">Automatically Install Updates</span>
<span class="gx-row__desc">Install updates automatically while parked with an active internet connection.</span>
</div>
<label class="gx-switch">
<input type="checkbox" :checked="!!fastStatus?.automaticUpdates"
:disabled="!fastStatus || isOnroad || autoUpdateBusy || !!fastStatus?.running"
@change="setAutomaticUpdates($event.target.checked)" />
<span class="gx-switch__track"></span>
<span class="gx-switch__thumb"></span>
</label>
</div>
</div>
<div class="gx-card" style="margin-bottom:12px;">
<div class="gx-section__header"><i class="bi bi-git-branch"></i><span class="gx-section__title">Switch Branch</span></div>
<div style="padding: var(--sp-3);">
@@ -80,6 +80,14 @@ def test_ui_ports_developer_mode_gating():
assert "isAdvancedHiddenByDeveloperMode" in params
def test_ui_exposes_gm_auto_hold_to_buick():
params = _read("js/params.js")
device_settings = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/tools/device_settings.js").read_text(encoding="utf-8")
assert 'GMAutoHold: ["Buick", "Chevrolet", "Holden"]' in params
assert 'GMAutoHold: ["Buick", "Chevrolet", "Holden"]' in device_settings
def test_ui_restores_hierarchical_sub_toggle_rendering():
# Children must nest under parents via the recursive SettingTree, gated on
# the parent being enabled AND expanded (classic renderSettingTree contract).
@@ -509,6 +517,10 @@ def test_ui_mobile_polish_regressions():
assert "checkedForUpdates && !!this.fastStatus?.updateAvailable" in system
assert "gx-update-progress__fill" in system
assert "linear-gradient(90deg, #5ec8c8 0%, #8b6cc5 100%)" in css
assert "Automatically Install Updates" in system
assert 'key: "AutomaticUpdates"' in system
assert "!!fastStatus?.automaticUpdates" in system
assert "isOnroad || autoUpdateBusy || !!fastStatus?.running" in system
bluetooth = _read("js/components/BluetoothPanel.js")
assert "methods: {\n address," in bluetooth