This commit is contained in:
firestar5683
2026-08-01 22:57:01 -05:00
parent 165c960e52
commit 7b9b7461bc
9 changed files with 138 additions and 19 deletions
@@ -26,7 +26,10 @@ class TestCanFingerprint:
fingerprint_iter = iter([can])
car_fingerprint, finger = can_fingerprint(lambda **kwargs: [next(fingerprint_iter, [])]) # noqa: B023
assert car_fingerprint == car_model
if car_fingerprint is None and str(car_model).startswith(("BUICK_", "CADILLAC_", "CHEVROLET_", "GMC_", "HOLDEN_")):
assert _get_gm_stored_candidate_fallback(finger, str(car_model), None) is not None
else:
assert car_fingerprint == car_model
assert finger[0] == fingerprint
assert finger[1] == fingerprint
assert finger[2] == {}
@@ -39,7 +42,7 @@ class TestCanFingerprint:
def test_timing(self, subtests):
# just pick any CAN fingerprinting car
car_model = "CHEVROLET_BOLT_ACC_2022_2023"
car_model = "COMMA_BODY"
fingerprint = FINGERPRINTS[car_model][0]
cases = []
@@ -1017,6 +1017,15 @@ class SafetyTest(SafetyTestBase):
# common Hyundai lateral/button messages are intentionally shared across multiple safety variants
tx = list(filter(lambda m: m[0] not in [0x340, 0x4F1, 0x485], tx))
if attr.startswith('TestGm') and current_test.startswith('TestGm'):
tx = list(filter(lambda m: m[0] not in [0x184, 0x1F5, 0x3D1], tx))
if attr.startswith('TestHyundaiCanfdLKASteering') and current_test.startswith('TestToyota'):
tx = list(filter(lambda m: m[0] not in [0x160], tx))
if attr.startswith('TestHyundaiCanfdCCNC') and current_test.startswith('TestSubaruPreglobal'):
tx = list(filter(lambda m: m[0] not in [0x161], tx))
if attr.startswith('TestHyundaiLongitudinal') or attr in ('TestHyundaiSafetyFCEVLong',
'TestHyundaiLongitudinalAolLkasOnEngageSafety',
'TestHyundaiCanCanfdBlendedLongitudinalSafety',
@@ -2513,7 +2513,7 @@ def test_modeld_action_uses_direct_action_head_for_v14(monkeypatch):
starpilot_toggles=toggles,
)
assert action.desiredCurvature == pytest.approx(0.12)
assert action.desiredCurvature == pytest.approx(modeld.smooth_value(0.12, prev_action.desiredCurvature, modeld.LAT_SMOOTH_SECONDS))
assert action.desiredAcceleration < -0.2
assert not action.shouldStop
@@ -2545,7 +2545,7 @@ def test_modeld_action_uses_current_action_head_scaling_for_v15(monkeypatch):
starpilot_toggles=toggles,
)
assert action.desiredCurvature == pytest.approx(0.48)
assert action.desiredCurvature == pytest.approx(modeld.smooth_value(0.48, prev_action.desiredCurvature, modeld.LAT_SMOOTH_SECONDS))
assert action.desiredAcceleration < -0.2
assert not action.shouldStop
@@ -1,3 +1,5 @@
from types import SimpleNamespace
import numpy as np
from cereal import car, messaging
from opendbc.car import ACCELERATION_DUE_TO_GRAVITY
@@ -33,6 +35,7 @@ def generate_inputs(torque_tune, la_err_std, input_noise_std=None):
def get_warmed_up_estimator(steer_torques, lat_accels):
est = TorqueEstimator(car.CarParams())
est.starpilot_toggles = SimpleNamespace(use_custom_latAccelFactor=False, use_custom_friction=False)
for steer_torque, lat_accel in zip(steer_torques, lat_accels, strict=True):
est.filtered_points.add_point(steer_torque, lat_accel)
return est
@@ -227,6 +227,7 @@ class Plant:
desiredFollowDistance=float(d_rel),
dangerFactor=1.0,
tFollow=1.45,
forcingStop=False,
forcingStopLength=2,
)
+25 -13
View File
@@ -15,6 +15,7 @@ import numpy as np
from openpilot.common.constants import CV
from openpilot.common.realtime import set_core_affinity
from openpilot.common.swaglog import cloudlog
from openpilot.starpilot.common.cpu_throttle import device_cpu_throttle_factor
from openpilot.system.hardware import PC
@@ -374,6 +375,7 @@ class SpeedLimitVisionDaemon:
self.debug_log_path = None
self.debug_bookmark_count = 0
self.debug_session_started_at = 0.0
self.debug_session_unavailable = False
self.last_logged_status = ""
self.last_logged_candidate = None
self.last_runtime_telemetry_at = 0.0
@@ -404,22 +406,31 @@ class SpeedLimitVisionDaemon:
self.speed_value_templates = self._build_speed_value_templates()
self._load_model()
def _start_debug_session(self):
if not self.use_runtime or self.params_memory is None or self.debug_session_id:
return
def _start_debug_session(self) -> bool:
if not self.use_runtime or self.params_memory is None or self.debug_session_id or self.debug_session_unavailable:
return False
timestamp = datetime.now(UTC)
session_id = timestamp.strftime("%Y%m%d_%H%M%S")
debug_dir = DEBUG_BASE_DIR / session_id
suffix = 1
while debug_dir.exists():
suffix += 1
session_id = f"{timestamp.strftime('%Y%m%d_%H%M%S')}_{suffix}"
debug_dir = DEBUG_BASE_DIR / session_id
try:
suffix = 1
while debug_dir.exists():
suffix += 1
session_id = f"{timestamp.strftime('%Y%m%d_%H%M%S')}_{suffix}"
debug_dir = DEBUG_BASE_DIR / session_id
debug_dir.mkdir(parents=True, exist_ok=True)
capture_dir = debug_dir / DEBUG_CAPTURE_DIRNAME
capture_dir.mkdir(parents=True, exist_ok=True)
debug_dir.mkdir(parents=True, exist_ok=True)
capture_dir = debug_dir / DEBUG_CAPTURE_DIRNAME
capture_dir.mkdir(parents=True, exist_ok=True)
except OSError as exc:
self.debug_session_unavailable = True
cloudlog.warning(f"Vision speed-limit debug storage unavailable: {exc}")
try:
self.params_memory.put("VisionSpeedLimitLastEvent", f"debug storage unavailable: {type(exc).__name__}"[:160])
except Exception:
pass
return False
self.debug_session_id = session_id
self.debug_dir = debug_dir
@@ -434,6 +445,7 @@ class SpeedLimitVisionDaemon:
self.params_memory.put_int("VisionSpeedLimitBookmarkCount", self.debug_bookmark_count)
self.params_memory.put("VisionSpeedLimitLastEvent", "")
self._write_debug_event("session_start", reason="onroad")
return True
def _close_debug_session(self):
self.debug_session_id = ""
@@ -442,6 +454,7 @@ class SpeedLimitVisionDaemon:
self.debug_log_path = None
self.debug_bookmark_count = 0
self.debug_session_started_at = 0.0
self.debug_session_unavailable = False
self.last_logged_status = ""
self.last_logged_candidate = None
self.last_debug_heartbeat_at = 0.0
@@ -2589,8 +2602,7 @@ class SpeedLimitVisionDaemon:
self.started_prev = True
self._start_debug_session()
self._publish_runtime_telemetry(now, "onroad_start", force=True)
elif not self.debug_session_id:
self._start_debug_session()
elif not self.debug_session_id and self._start_debug_session():
self._write_debug_event("session_recovered", reason="missing_debug_session_while_onroad")
self._publish_runtime_telemetry(now, "session_recovered", force=True)
@@ -19,6 +19,9 @@ class MemoryParams:
def put_int(self, key, value):
self.values[key] = value
def put(self, key, value):
self.values[key] = value
def remove(self, key):
self.values.pop(key, None)
@@ -67,6 +70,32 @@ def publishing_daemon(is_metric):
return daemon
def test_debug_storage_failure_does_not_crash_detection(monkeypatch):
class ReadOnlyPath:
def __truediv__(self, _part):
return self
def exists(self):
return False
def mkdir(self, **_kwargs):
raise OSError(30, "Read-only file system")
daemon = SpeedLimitVisionDaemon.__new__(SpeedLimitVisionDaemon)
daemon.use_runtime = True
daemon.params_memory = MemoryParams()
daemon.debug_session_id = ""
daemon.debug_session_unavailable = False
monkeypatch.setattr(slv, "DEBUG_BASE_DIR", ReadOnlyPath())
assert not daemon._start_debug_session()
assert daemon.debug_session_unavailable
assert daemon.debug_session_id == ""
assert daemon.params_memory.values["VisionSpeedLimitLastEvent"] == "debug storage unavailable: OSError"
assert not daemon._start_debug_session()
def test_disconnect_camera_releases_client_state():
daemon = SpeedLimitVisionDaemon.__new__(SpeedLimitVisionDaemon)
daemon.client = object()
@@ -16,6 +16,9 @@ def _make_wm(mocker: MockerFixture, connections=None):
mocker.patch.object(WifiManager, '_initialize')
wm = WifiManager.__new__(WifiManager)
wm._exit = True # prevent stop() from doing anything in __del__
wm._backend_unavailable = False
wm._fake_networking = False
wm._nmcli_networking = False
wm._conn_monitor = mocker.MagicMock()
wm._connections = dict(connections or {})
wm._wifi_state = WifiState()
@@ -877,7 +880,6 @@ class TestWorkerErrorRecovery:
mock_init.assert_called_once()
assert wm._wifi_state.ssid == "A"
assert wm._wifi_state.status == ConnectStatus.CONNECTED
def test_connect_to_network_dbus_error_resyncs(self, mocker):
"""AddAndActivateConnection2 returns DBus error while A is connected."""
wm = _make_wm(mocker, connections={"A": "/path/A"})
@@ -904,3 +906,32 @@ class TestWorkerErrorRecovery:
mock_init.assert_called_once()
assert wm._wifi_state.ssid == "A"
assert wm._wifi_state.status == ConnectStatus.CONNECTED
class TestMonitorSocketRecovery:
def test_state_monitor_recovers_dead_main_router(self, mocker):
wm = _make_wm(mocker)
wm._handle_state_change = mocker.MagicMock(side_effect=OSError(88, "Socket operation on non-socket"))
wm._recover_main_dbus_connection = mocker.MagicMock(return_value=True)
wm._handle_state_change_safely(NMDeviceState.ACTIVATED, NMDeviceState.IP_CHECK, NMDeviceStateReason.NONE)
wm._recover_main_dbus_connection.assert_called_once_with()
def test_reconnect_replaces_router_and_resyncs_state(self, mocker):
wm = _make_wm(mocker)
old_router = mocker.MagicMock()
new_router = mocker.MagicMock()
wm._router_main = old_router
wm._init_connections = mocker.MagicMock()
wm._init_wifi_state = mocker.MagicMock()
mocker.patch('openpilot.system.ui.lib.wifi_manager.open_dbus_connection_threading', return_value=mocker.MagicMock())
mocker.patch('openpilot.system.ui.lib.wifi_manager.DBusRouter', return_value=new_router)
assert wm._recover_main_dbus_connection()
assert wm._router_main is new_router
old_router.close.assert_called_once_with()
old_router.conn.close.assert_called_once_with()
wm._init_connections.assert_called_once_with()
wm._init_wifi_state.assert_called_once_with()
+32 -1
View File
@@ -465,7 +465,38 @@ class WifiManager:
while len(state_q):
new_state, previous_state, change_reason = state_q.popleft().body
self._handle_state_change(new_state, previous_state, change_reason)
self._handle_state_change_safely(new_state, previous_state, change_reason)
def _handle_state_change_safely(self, new_state: int, previous_state: int, change_reason: int) -> None:
try:
self._handle_state_change(new_state, previous_state, change_reason)
except OSError as exc:
cloudlog.warning(f"Wi-Fi D-Bus socket failed; reconnecting: {exc}")
self._recover_main_dbus_connection()
def _recover_main_dbus_connection(self) -> bool:
try:
router = DBusRouter(open_dbus_connection_threading(bus="SYSTEM"))
_wrap_router(router)
except Exception:
cloudlog.exception("Failed to reconnect Wi-Fi D-Bus router")
return False
old_router = self._router_main
self._router_main = router
if old_router is not None:
try:
old_router.close()
old_router.conn.close()
except Exception:
pass
try:
self._init_connections()
self._init_wifi_state()
except Exception:
cloudlog.exception("Failed to restore Wi-Fi state after D-Bus reconnect")
return True
def _handle_state_change(self, new_state: int, prev_state: int, change_reason: int):
# Thread safety: _wifi_state is read/written by both the monitor thread (this handler)