This commit is contained in:
firestar5683
2026-08-15 23:35:50 -05:00
parent 1fe6a5011a
commit 5ef9fbda99
9 changed files with 88 additions and 15 deletions
@@ -412,6 +412,10 @@ def preserve_stock_canfd_lfa_status(car_fingerprint) -> bool:
return car_fingerprint not in (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN)
def preserve_stock_canfd_lkas_status(car_fingerprint) -> bool:
return car_fingerprint not in (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN)
def suppress_redundant_gv70_brake_cancel(CP, brake_pressed: bool, lat_active: bool) -> bool:
return bool(
CP.carFingerprint == CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and
@@ -806,7 +810,8 @@ class CarController(CarControllerBase):
# payload. Forwarding its stock status bits leaves lane-safety state asserted
# while StarPilot is suppressing the stock LFA path.
preserve_stock_lkas = bool(self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING) and \
not self.long_active_ecu and self.CP.carFingerprint != CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
not self.long_active_ecu and self.CP.carFingerprint != CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN and \
preserve_stock_canfd_lkas_status(self.CP.carFingerprint)
angle_lkas_alt = bool(self.CP.flags & HyundaiFlags.CANFD_ANGLE_STEERING and
self.CP.flags & HyundaiFlags.CANFD_LKA_STEERING_ALT)
ccnc_angle_long = self.CP.carFingerprint in CANFD_ANGLE_LONGITUDINAL_CAR and \
@@ -183,6 +183,8 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_torque,
else:
lkas_values = copy.copy(control_values)
lkas_values["LKA_AVAILABLE"] = 0
if CP.carFingerprint in (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN):
lkas_values["DAMP_FACTOR"] = 100
if lfa_base_values:
# Preserve stock UI/status fields and only override the actuation-relevant signals.
@@ -19,6 +19,7 @@ from opendbc.car.hyundai.carcontroller import CarController, Ioniq6LongitudinalT
should_use_ev6_gt_line_stop_direct_tracking, \
should_track_stop_accel_directly_for_car, \
preserve_stock_canfd_lfa_status, \
preserve_stock_canfd_lkas_status, \
suppress_redundant_gv70_brake_cancel
from opendbc.car.hyundai.carstate import CarState, decode_canfd_camera_lead, decode_ioniq_6_blindspot_radar_state, \
get_canfd_cruise_available
@@ -128,7 +129,9 @@ class TestHyundaiFingerprint:
@pytest.mark.parametrize("candidate", (CAR.KIA_CARNIVAL_4TH_GEN, CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN))
def test_carnival_uses_clean_canfd_lfa_status(self, candidate):
assert not preserve_stock_canfd_lfa_status(candidate)
assert not preserve_stock_canfd_lkas_status(candidate)
assert preserve_stock_canfd_lfa_status(CAR.HYUNDAI_IONIQ_6)
assert preserve_stock_canfd_lkas_status(CAR.HYUNDAI_IONIQ_6)
CP = CarParams.new_message()
CP.carFingerprint = candidate
@@ -154,6 +157,56 @@ class TestHyundaiFingerprint:
active_cluster_msg = hyundaicanfd.create_lfahda_cluster(packer, can_bus, True, None, lfa_icon=2)
assert active_cluster_msg[1] == bytes.fromhex("cdfb0180000001000000000000000000")
@pytest.mark.parametrize("candidate", (CAR.KIA_CARNIVAL_2025, CAR.KIA_CARNIVAL_HEV_4TH_GEN))
def test_carnival_hda2_clears_stock_lkas_warning_status(self, candidate):
CP = CarParams.new_message()
CP.carFingerprint = candidate
CP.flags = int(HyundaiFlags.CANFD | HyundaiFlags.CCNC | HyundaiFlags.CANFD_LKA_STEERING |
HyundaiFlags.CANFD_LKA_STEERING_ALT)
CP.openpilotLongitudinalControl = False
controller = CarController(DBC[CP.carFingerprint], CP)
controller.frame = 1
can_bus = CanBus(CP)
parser = CANParser(DBC[CP.carFingerprint][Bus.pt], [("LKAS_ALT", 0)], can_bus.ACAN)
stock_lkas = {
"CHECKSUM": 1234,
"COUNTER": 42,
"LKA_MODE": 0,
"LKA_AVAILABLE": 0,
"LKA_WARNING": 0,
"LKA_ICON": 0,
"FCA_SYSWARN": 1,
"TORQUE_REQUEST": 0,
"STEER_REQ": 0,
"LFA_BUTTON": 0,
"LKA_ASSIST": 1,
"STEER_MODE": 2,
"NEW_SIGNAL_2": 3,
"HAS_LANE_SAFETY": 0,
"DAMP_FACTOR": 100,
}
cc = SimpleNamespace(enabled=False, latActive=True,
actuators=SimpleNamespace(longControlState=LongCtrlState.off),
leftBlinker=False, rightBlinker=False,
hudControl=SimpleNamespace())
cs = SimpleNamespace(stock_lfa_msg=None, stock_lkas_msg=stock_lkas,
out=SimpleNamespace(standstill=False, steeringAngleDeg=0.0,
gearShifter=structs.CarState.GearShifter.drive))
msgs = controller.create_canfd_msgs(0, True, 123, 0.0, 0.0, 0.0, False,
cc.hudControl, cs, cc, get_test_toggles(), lka_icon=2, lfa_icon=2)
lkas_msgs = [msg for msg in msgs if msg[0] == 0x110]
assert len(lkas_msgs) == 1
parser.update([(1, lkas_msgs)])
assert parser.can_valid
assert parser.vl["LKAS_ALT"]["FCA_SYSWARN"] == 0
assert parser.vl["LKAS_ALT"]["LKA_ASSIST"] == 0
assert parser.vl["LKAS_ALT"]["DAMP_FACTOR"] == 100
assert parser.vl["LKAS_ALT"]["TORQUE_REQUEST"] == 123
assert parser.vl["LKAS_ALT"]["STEER_REQ"] == 1
def test_canfd_torque_bsm_parser_registers_rear_blindspots(self):
CP = CarParams.new_message()
CP.carFingerprint = CAR.GENESIS_GV70_ELECTRIFIED_1ST_GEN
+7 -4
View File
@@ -111,6 +111,7 @@ class Soundd:
self.openpilot_crashed_played = False
self.auto_volume = 0
self.pending_stream_status = None
self.previous_sound_pack = None
self.previous_sound_source_signature = None
@@ -193,7 +194,7 @@ class Soundd:
def callback(self, data_out: np.ndarray, frames: int, time, status) -> None:
if status:
cloudlog.warning(f"soundd stream over/underflow: {status}")
self.pending_stream_status = status
data_out[:frames, 0] = self.get_sound_data(frames)
def update_alert(self, new_alert):
@@ -256,9 +257,6 @@ class Soundd:
@retry(attempts=10, delay=3)
def get_stream(self, sd):
# reload sounddevice to reinitialize portaudio
sd._terminate()
sd._initialize()
return sd.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback, blocksize=SAMPLE_BUFFER)
def start_stream(self, sd):
@@ -297,6 +295,11 @@ class Soundd:
while True:
sm.update(0)
if self.pending_stream_status is not None:
status = self.pending_stream_status
self.pending_stream_status = None
cloudlog.warning(f"soundd stream over/underflow: {status}")
if sm.updated['soundPressure'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert
self.spl_filter_weighted.update(sm["soundPressure"].soundPressureWeightedDb)
self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x))
+7 -4
View File
@@ -30,17 +30,20 @@ def _online_cpu_count() -> int | None:
return None
def _online_cpu_usage(cpu_usage) -> list[float]:
def _online_cpu_usage(cpu_usage, cores=None) -> list[float]:
usage = [float(value) for value in cpu_usage]
online_count = _online_cpu_count()
if online_count is not None and online_count < len(usage):
return usage[:online_count]
usage = usage[:online_count]
if cores is not None:
usage = [usage[core] for core in cores if 0 <= core < len(usage)]
return usage
def device_cpu_throttle_factor(cpu_usage, name="vision"):
def device_cpu_throttle_factor(cpu_usage, name="vision", cores=None):
"""Return a process-local, low-pass-filtered CPU throttle factor."""
usage = _online_cpu_usage(cpu_usage)
usage = _online_cpu_usage(cpu_usage, cores=cores)
if not usage:
return 1.0
@@ -13,6 +13,15 @@ def test_offline_cpu_placeholders_do_not_hide_sustained_load(monkeypatch):
assert cpu_throttle._compute_throttle_factor(average, 0) == pytest.approx(1.75)
def test_idle_reserved_cores_do_not_hide_affinity_core_saturation(monkeypatch):
monkeypatch.setattr(cpu_throttle, "_online_cpu_count", lambda: 8)
usage = cpu_throttle._online_cpu_usage([74, 82, 100, 85, 92, 98, 0, 47], cores=[2])
assert usage == [100.0]
assert cpu_throttle._compute_throttle_factor(sum(usage) / len(usage), 1) == 4.0
@pytest.mark.parametrize(("online_spec", "expected"), (
("0-3", 4),
("0-3,6-7", 6),
+2 -1
View File
@@ -151,7 +151,8 @@ class VASMDaemon:
in_followup = now < self.followup_until
base = FOLLOWUP_INTERVAL if in_followup else BASE_INTERVAL
cpu_usage = list(self.sm["deviceState"].cpuUsagePercent) if self.sm.valid.get("deviceState", False) else []
factor = device_cpu_throttle_factor(cpu_usage, name="VASM")
affinity_cores = V_ASM_AFFINITY_CORES if self._slv_enabled else V_ASM_SOLO_AFFINITY_CORES
factor = device_cpu_throttle_factor(cpu_usage, name="VASM", cores=affinity_cores)
self._throttle_factor = factor
interval = base * factor
self._throttle_reason = f"cpu_{factor:.1f}x" if factor > 1.05 else ("followup" if in_followup else "steady")
+2 -2
View File
@@ -997,7 +997,7 @@ class SpeedLimitVisionDaemon:
if self.coexistence_mode:
cpu_usage = list(self.sm["deviceState"].cpuUsagePercent) if self.sm is not None and self.sm.valid.get("deviceState", False) else []
factor = device_cpu_throttle_factor(cpu_usage, name="SpeedLimit")
factor = device_cpu_throttle_factor(cpu_usage, name="SpeedLimit", cores=SPEED_LIMIT_VISION_COEXISTENCE_AFFINITY_CORES)
if factor > 1.05:
self.last_cpu_busy = True
interval *= factor
@@ -1348,7 +1348,7 @@ class SpeedLimitVisionDaemon:
return max(interval, MEMORY_PRESSURE_CLASSIFICATION_INTERVAL)
if self.coexistence_mode:
cpu_usage = list(self.sm["deviceState"].cpuUsagePercent) if self.sm is not None and self.sm.valid.get("deviceState", False) else []
if device_cpu_throttle_factor(cpu_usage, name="SpeedLimit") > 1.05:
if device_cpu_throttle_factor(cpu_usage, name="SpeedLimit", cores=SPEED_LIMIT_VISION_COEXISTENCE_AFFINITY_CORES) > 1.05:
return max(interval, TRACK_BUSY_CLASSIFICATION_INTERVAL)
elif self._device_cpu_busy():
return max(interval, TRACK_BUSY_CLASSIFICATION_INTERVAL)
-3
View File
@@ -97,9 +97,6 @@ class Mic:
@retry(attempts=10, delay=3)
def get_stream(self, sd, device=None):
# reload sounddevice to reinitialize portaudio
sd._terminate()
sd._initialize()
kwargs = {
"channels": 1,
"samplerate": SAMPLE_RATE,