mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-30 11:23:49 +08:00
iPhone FoldGate
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user