diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 5961f1b7d..a40236814 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -418,14 +418,26 @@ class StarPilotVariables: except (TypeError, ValueError): return + # Angle-control placeholder torque params can be NaN; never persist them. + if not math.isfinite(live_value): + if not math.isfinite(self.params.get_float(stock_key)): + self.params.remove(stock_key) + if not math.isfinite(self.params.get_float(key)): + self.params.remove(key) + return + if math.isclose(live_value, 0.0, abs_tol=1e-6): return current_stock = self.params.get_float(stock_key) + if not math.isfinite(current_stock): + current_stock = 0.0 if math.isclose(current_stock, live_value, abs_tol=1e-6): return current_value = self.params.get_float(key) + if not math.isfinite(current_value): + current_value = 0.0 if math.isclose(current_value, current_stock, abs_tol=1e-6) or math.isclose(current_stock, 0.0, abs_tol=1e-6): self.params.put_float(key, live_value) @@ -462,6 +474,8 @@ class StarPilotVariables: toggle.car_model = CP.carFingerprint toggle.disable_openpilot_long = self.get_value("DisableOpenpilotLongitudinal", condition=not alpha_longitudinal) friction = CP.lateralTuning.torque.friction + if not math.isfinite(friction): + friction = 0.0 has_bsm = CP.enableBsm toggle.has_cc_long = toggle.car_make == "gm" and bool(CP.flags & GMFlags.CC_LONG.value) toggle.has_sascm = toggle.car_make == "gm" and bool(CP.flags & GMFlags.SASCM.value) @@ -473,6 +487,8 @@ class StarPilotVariables: toggle.has_zss = toggle.car_make == "toyota" and bool(FPCP.flags & ToyotaStarPilotFlags.ZSS.value) is_angle_car = CP.steerControlType == car.CarParams.SteerControlType.angle latAccelFactor = CP.lateralTuning.torque.latAccelFactor + if not math.isfinite(latAccelFactor): + latAccelFactor = 0.0 toggle.lkas_allowed_for_aol = toggle.car_make == "hyundai" and bool(CP.flags & HyundaiFlags.CANFD or CP.flags & HyundaiFlags.HAS_LDA_BUTTON) longitudinalActuatorDelay = CP.longitudinalActuatorDelay toggle.openpilot_longitudinal = CP.openpilotLongitudinalControl and not toggle.disable_openpilot_long diff --git a/starpilot/system/the_pond/the_pond.py b/starpilot/system/the_pond/the_pond.py index c01085f92..a946029c7 100644 --- a/starpilot/system/the_pond/the_pond.py +++ b/starpilot/system/the_pond/the_pond.py @@ -3,6 +3,8 @@ from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime, timedelta, timezone import importlib +import math +import numbers import os import sys import tarfile @@ -379,6 +381,32 @@ def _normalize_default_value(value): return value return value +def _sanitize_json_value(value): + if value is None or isinstance(value, bool): + return value + + if isinstance(value, dict): + return {key: _sanitize_json_value(inner_value) for key, inner_value in value.items()} + + if isinstance(value, (list, tuple)): + return [_sanitize_json_value(item) for item in value] + + if isinstance(value, bytes): + try: + return value.decode("utf-8") + except Exception: + return value.decode("utf-8", errors="replace") + + if isinstance(value, numbers.Integral): + return int(value) + + # Flask emits invalid JSON for NaN/inf, so normalize them before jsonify. + if isinstance(value, numbers.Real): + numeric_value = float(value) + return numeric_value if math.isfinite(numeric_value) else None + + return value + def _build_default_params(): defaults = [] for raw_key in _params_raw.all_keys(): @@ -1791,13 +1819,15 @@ def _has_runtime_default_value(key, raw_value): if _is_blank_param_raw(raw_value): return False - if key in _RUNTIME_DEFAULT_ZERO_OK_KEYS: - return True - try: if isinstance(raw_value, bytes): raw_value = raw_value.decode("utf-8", errors="replace") - return float(str(raw_value).strip()) != 0.0 + numeric_value = float(str(raw_value).strip()) + if not math.isfinite(numeric_value): + return False + if key in _RUNTIME_DEFAULT_ZERO_OK_KEYS: + return True + return numeric_value != 0.0 except Exception: return True @@ -1831,7 +1861,10 @@ def _get_runtime_default_param_overrides(): for key, value in car_param_defaults.items(): if key in overrides or value is None: continue - if key not in _RUNTIME_DEFAULT_ZERO_OK_KEYS and float(value) == 0.0: + numeric_value = float(value) + if not math.isfinite(numeric_value): + continue + if key not in _RUNTIME_DEFAULT_ZERO_OK_KEYS and numeric_value == 0.0: continue overrides[key] = value except Exception: @@ -2354,9 +2387,9 @@ def _build_troubleshoot_section_payload(section_definition, value_types, default items.append({ "key": key, "label": label, - "value": current_value, - "defaultValue": default_value, - "learnedValue": learned_values.get(key), + "value": _sanitize_json_value(current_value), + "defaultValue": _sanitize_json_value(default_value), + "learnedValue": _sanitize_json_value(learned_values.get(key)), }) return { @@ -2407,12 +2440,12 @@ def _build_troubleshoot_payload(): for section_definition in _TROUBLESHOOT_SECTION_DEFINITIONS ] - return { + return _sanitize_json_value({ "vehicleStatus": _build_vehicle_fault_status(), "snapshot": snapshot_items, "sections": sections, "isOnroad": params.get_bool("IsOnroad"), - } + }) def _reset_troubleshoot_section(section_id): section_definition = _TROUBLESHOOT_SECTION_BY_ID.get(str(section_id or "").strip()) @@ -3332,7 +3365,7 @@ def setup(app): except Exception: result[key] = None - return jsonify(result), 200 + return jsonify(_sanitize_json_value(result)), 200 @app.route("/api/params/defaults", methods=["GET"]) def get_default_params(): @@ -3363,7 +3396,7 @@ def setup(app): except Exception: result[key] = None - return jsonify(result), 200 + return jsonify(_sanitize_json_value(result)), 200 @app.route("/api/troubleshoot", methods=["GET"]) def get_troubleshoot_data():