mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-05 21:42:05 +08:00
Troubleshoot
This commit is contained in:
@@ -174,7 +174,7 @@ frogpilot_default_params: list[tuple[str, str | bytes, int, str]] = [
|
||||
("CECurves", "0", 1, "0"),
|
||||
("CECurvesLead", "0", 1, "0"),
|
||||
("CELead", "0", 1, "0"),
|
||||
("CEModelStopTime", str(PLANNER_TIME - 2), 2, "0"),
|
||||
("CEModelStopTime", "7", 2, "0"),
|
||||
("CENavigation", "0", 2, "0"),
|
||||
("CENavigationIntersections", "1", 2, "0"),
|
||||
("CENavigationLead", "1", 2, "0"),
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ModelManager } from "/assets/components/tools/model_manager.js?v=202603
|
||||
import { LivePlots } from "/assets/components/tools/plots.js"
|
||||
import { ThemeMaker } from "/assets/components/tools/theme_maker.js"
|
||||
import { TestingGround } from "/assets/components/tools/testing_ground.js"
|
||||
import { Troubleshoot } from "/assets/components/tools/troubleshoot.js"
|
||||
import { TmuxLog } from "/assets/components/tools/tmux.js"
|
||||
import { ToggleControl } from "/assets/components/tools/toggles.js"
|
||||
import { UpdateManager } from "/assets/components/tools/update_manager.js"
|
||||
@@ -50,6 +51,7 @@ function Root() {
|
||||
createRoute("plots", "/plots", LivePlots),
|
||||
createRoute("thememaker", "/theme_maker", ThemeMaker),
|
||||
createRoute("testing_ground", "/testing_ground", TestingGround),
|
||||
createRoute("troubleshoot", "/troubleshoot", Troubleshoot),
|
||||
createRoute("tmux", "/manage_tmux", TmuxLog),
|
||||
createRoute("toggles", "/manage_toggles", ToggleControl),
|
||||
createRoute("updates", "/manage_updates", UpdateManager),
|
||||
|
||||
@@ -23,6 +23,7 @@ const MenuItems = {
|
||||
{ name: "Model Manager", link: "/manage_models", icon: "bi-cpu" },
|
||||
{ name: "Plots", link: "/plots", icon: "bi-graph-up-arrow" },
|
||||
{ name: "Testing Ground", link: "/testing_ground", icon: "bi-bezier2" },
|
||||
{ name: "Troubleshoot", link: "/troubleshoot", icon: "bi-tools" },
|
||||
{ name: "Theme Maker", link: "/theme_maker", icon: "bi-palette-fill" },
|
||||
{ name: "Tmux Log", link: "/manage_tmux", icon: "bi-terminal" },
|
||||
{ name: "Backup and Restore", link: "/manage_toggles", icon: "bi-arrow-repeat" },
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
.troubleshootPage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.troubleshootCard {
|
||||
background: var(--sidebar-bg);
|
||||
border: 1px solid var(--sidebar-border-color);
|
||||
border-radius: var(--border-radius-lg);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.troubleshootIntro {
|
||||
margin: 0 0 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.troubleshootActionRow {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.troubleshootButton {
|
||||
background: linear-gradient(135deg, #7a62b8, #8b6cc5);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: 10px 14px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.troubleshootButton:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.troubleshootDanger {
|
||||
background: linear-gradient(135deg, #b14a6b, #d95a7b);
|
||||
}
|
||||
|
||||
.troubleshootError {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.troubleshootStatusLine {
|
||||
margin: 6px 0 0;
|
||||
}
|
||||
|
||||
.troubleshootSectionHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.troubleshootSectionHeader h3,
|
||||
.troubleshootCard h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.troubleshootHeaderRow,
|
||||
.troubleshootItemRow,
|
||||
.troubleshootSnapshotRow {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--sidebar-border-color);
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.troubleshootHeaderRow {
|
||||
font-weight: 700;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.troubleshootHeaderRow {
|
||||
grid-template-columns: 2fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.troubleshootHeaderRowSnapshot {
|
||||
grid-template-columns: 2fr 2fr;
|
||||
}
|
||||
|
||||
.troubleshootSnapshotRow {
|
||||
grid-template-columns: 2fr 2fr;
|
||||
}
|
||||
|
||||
.troubleshootItemRow {
|
||||
grid-template-columns: 2fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.troubleshootItemLabel {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.troubleshootItemValue,
|
||||
.troubleshootItemDefault {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.troubleshootHeaderRow,
|
||||
.troubleshootItemRow {
|
||||
grid-template-columns: 1.3fr 1fr 1fr;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.troubleshootSnapshotRow {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.troubleshootSectionHeader {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import { html, reactive } from "https://esm.sh/@arrow-js/core"
|
||||
|
||||
const state = reactive({
|
||||
loading: true,
|
||||
error: "",
|
||||
snapshot: [],
|
||||
sections: [],
|
||||
isOnroad: false,
|
||||
refreshing: false,
|
||||
busySection: "",
|
||||
})
|
||||
|
||||
let initialized = false
|
||||
|
||||
function formatValue(value) {
|
||||
if (typeof value === "boolean") return value ? "On" : "Off"
|
||||
if (typeof value === "number") {
|
||||
if (Number.isInteger(value)) return String(value)
|
||||
return String(Number(value.toFixed(4)))
|
||||
}
|
||||
if (value === null || value === undefined) return "n/a"
|
||||
const text = String(value).trim()
|
||||
return text || "(empty)"
|
||||
}
|
||||
|
||||
function buildReportText() {
|
||||
const lines = []
|
||||
lines.push("StarPilot Troubleshoot Report")
|
||||
lines.push(`Generated: ${new Date().toISOString()}`)
|
||||
lines.push(`Onroad: ${state.isOnroad ? "Yes" : "No"}`)
|
||||
lines.push("")
|
||||
lines.push("Snapshot")
|
||||
for (const item of state.snapshot) {
|
||||
lines.push(`- ${item.label}: ${formatValue(item.value)}`)
|
||||
}
|
||||
|
||||
for (const section of state.sections) {
|
||||
lines.push("")
|
||||
lines.push(section.title)
|
||||
for (const item of section.items || []) {
|
||||
lines.push(`- ${item.label}: ${formatValue(item.value)} (default: ${formatValue(item.defaultValue)})`)
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
async function copyToClipboard() {
|
||||
const text = buildReportText()
|
||||
try {
|
||||
if (navigator?.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(text)
|
||||
} else {
|
||||
const textArea = document.createElement("textarea")
|
||||
textArea.value = text
|
||||
textArea.style.position = "fixed"
|
||||
textArea.style.left = "-9999px"
|
||||
document.body.appendChild(textArea)
|
||||
textArea.focus()
|
||||
textArea.select()
|
||||
document.execCommand("copy")
|
||||
document.body.removeChild(textArea)
|
||||
}
|
||||
showSnackbar("Troubleshoot report copied to clipboard.", "", 2500, { key: "troubleshoot-copy" })
|
||||
} catch (error) {
|
||||
showSnackbar(error?.message || "Failed to copy report.", "error", 3000, { key: "troubleshoot-copy" })
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTroubleshoot(showToast = false) {
|
||||
state.refreshing = true
|
||||
try {
|
||||
const response = await fetch("/api/troubleshoot")
|
||||
const payload = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(payload.error || response.statusText || "Failed to load troubleshoot data")
|
||||
}
|
||||
|
||||
state.snapshot = Array.isArray(payload.snapshot) ? payload.snapshot : []
|
||||
state.sections = Array.isArray(payload.sections) ? payload.sections : []
|
||||
state.isOnroad = !!payload.isOnroad
|
||||
state.error = ""
|
||||
|
||||
if (showToast) {
|
||||
showSnackbar("Troubleshoot data refreshed.", "", 1800, { key: "troubleshoot-refresh" })
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error?.message || "Failed to load troubleshoot data"
|
||||
state.error = message
|
||||
if (showToast) {
|
||||
showSnackbar(message, "error", 3000, { key: "troubleshoot-refresh" })
|
||||
}
|
||||
} finally {
|
||||
state.loading = false
|
||||
state.refreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
async function resetSection(section) {
|
||||
const sectionId = String(section?.id || "")
|
||||
if (!sectionId || state.busySection) return
|
||||
|
||||
const sectionTitle = String(section?.title || "this section")
|
||||
if (!confirm(`Reset ${sectionTitle} to defaults?`)) return
|
||||
|
||||
state.busySection = sectionId
|
||||
try {
|
||||
const response = await fetch("/api/troubleshoot/reset", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ sectionId }),
|
||||
})
|
||||
const payload = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(payload.error || response.statusText || "Failed to reset section")
|
||||
}
|
||||
|
||||
showSnackbar(payload.message || `${sectionTitle} reset.`, "", 2500, { key: "troubleshoot-reset" })
|
||||
await fetchTroubleshoot(false)
|
||||
} catch (error) {
|
||||
showSnackbar(error?.message || "Failed to reset section.", "error", 3000, { key: "troubleshoot-reset" })
|
||||
} finally {
|
||||
state.busySection = ""
|
||||
}
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
if (initialized) return
|
||||
initialized = true
|
||||
fetchTroubleshoot(false)
|
||||
}
|
||||
|
||||
function itemRows(section) {
|
||||
const items = Array.isArray(section?.items) ? section.items : []
|
||||
return items.map((item) => html`
|
||||
<div class="troubleshootItemRow">
|
||||
<div class="troubleshootItemLabel">${item.label}</div>
|
||||
<div class="troubleshootItemValue">${formatValue(item.value)}</div>
|
||||
<div class="troubleshootItemDefault">${formatValue(item.defaultValue)}</div>
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
|
||||
export function Troubleshoot() {
|
||||
initialize()
|
||||
|
||||
return html`
|
||||
<div class="troubleshootPage">
|
||||
<h2>Troubleshoot</h2>
|
||||
|
||||
${() => state.loading ? html`<div class="troubleshootCard">Loading troubleshoot data...</div>` : ""}
|
||||
|
||||
${() => !state.loading ? html`
|
||||
<div class="troubleshootCard">
|
||||
<p class="troubleshootIntro">
|
||||
Quick diagnostics snapshot for weird behavior reports and copy-ready debug logs.
|
||||
</p>
|
||||
<div class="troubleshootActionRow">
|
||||
<button class="troubleshootButton" ?disabled="${state.refreshing}" @click="${() => fetchTroubleshoot(true)}">
|
||||
${state.refreshing ? "Refreshing..." : "Refresh"}
|
||||
</button>
|
||||
<button class="troubleshootButton" @click="${copyToClipboard}">
|
||||
Copy to Clipboard
|
||||
</button>
|
||||
</div>
|
||||
${() => state.error ? html`<p class="troubleshootError"><strong>Error:</strong> ${state.error}</p>` : ""}
|
||||
<p class="troubleshootStatusLine"><strong>Onroad:</strong> ${state.isOnroad ? "Yes" : "No"}</p>
|
||||
</div>
|
||||
|
||||
<div class="troubleshootCard">
|
||||
<h3>Snapshot</h3>
|
||||
<div class="troubleshootHeaderRow troubleshootHeaderRowSnapshot">
|
||||
<span>Field</span>
|
||||
<span>Value</span>
|
||||
</div>
|
||||
${() => state.snapshot.map((item) => html`
|
||||
<div class="troubleshootSnapshotRow">
|
||||
<div class="troubleshootItemLabel">${item.label}</div>
|
||||
<div class="troubleshootItemValue">${formatValue(item.value)}</div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
|
||||
${() => state.sections.map((section) => html`
|
||||
<div class="troubleshootCard">
|
||||
<div class="troubleshootSectionHeader">
|
||||
<h3>${section.title}</h3>
|
||||
${section.resettable ? html`
|
||||
<button
|
||||
class="troubleshootButton troubleshootDanger"
|
||||
?disabled="${state.busySection === section.id}"
|
||||
@click="${() => resetSection(section)}">
|
||||
${state.busySection === section.id ? "Resetting..." : "Reset to Default"}
|
||||
</button>
|
||||
` : ""}
|
||||
</div>
|
||||
<div class="troubleshootHeaderRow">
|
||||
<span>Setting</span>
|
||||
<span>Current</span>
|
||||
<span>Default</span>
|
||||
</div>
|
||||
${itemRows(section)}
|
||||
</div>
|
||||
`)}
|
||||
` : ""}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
<link rel="stylesheet" href="/assets/components/tools/speed_limits.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/theme_maker.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/testing_ground.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/troubleshoot.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/tmux.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/toggles.css">
|
||||
<link rel="stylesheet" href="/assets/components/tools/update_manager.css">
|
||||
|
||||
@@ -213,6 +213,121 @@ _plots_state = {
|
||||
"lastError": "",
|
||||
}
|
||||
|
||||
_TROUBLESHOOT_PERSONALITY_KEYS = [
|
||||
"CustomPersonalities",
|
||||
"TrafficPersonalityProfile",
|
||||
"TrafficFollow",
|
||||
"TrafficJerkAcceleration",
|
||||
"TrafficJerkDeceleration",
|
||||
"TrafficJerkDanger",
|
||||
"TrafficJerkSpeedDecrease",
|
||||
"TrafficJerkSpeed",
|
||||
"AggressivePersonalityProfile",
|
||||
"AggressiveFollow",
|
||||
"AggressiveFollowHigh",
|
||||
"AggressiveJerkAcceleration",
|
||||
"AggressiveJerkDeceleration",
|
||||
"AggressiveJerkDanger",
|
||||
"AggressiveJerkSpeedDecrease",
|
||||
"AggressiveJerkSpeed",
|
||||
"StandardPersonalityProfile",
|
||||
"StandardFollow",
|
||||
"StandardFollowHigh",
|
||||
"StandardJerkAcceleration",
|
||||
"StandardJerkDeceleration",
|
||||
"StandardJerkDanger",
|
||||
"StandardJerkSpeedDecrease",
|
||||
"StandardJerkSpeed",
|
||||
"RelaxedPersonalityProfile",
|
||||
"RelaxedFollow",
|
||||
"RelaxedFollowHigh",
|
||||
"RelaxedJerkAcceleration",
|
||||
"RelaxedJerkDeceleration",
|
||||
"RelaxedJerkDanger",
|
||||
"RelaxedJerkSpeedDecrease",
|
||||
"RelaxedJerkSpeed",
|
||||
]
|
||||
|
||||
_TROUBLESHOOT_CEM_KEYS = [
|
||||
"ConditionalExperimental",
|
||||
"CESpeed",
|
||||
"CESpeedLead",
|
||||
"CECurves",
|
||||
"CELead",
|
||||
"CESlowerLead",
|
||||
"CEStoppedLead",
|
||||
"CENavigation",
|
||||
"CEModelStopTime",
|
||||
"CESignalSpeed",
|
||||
"ShowCEMStatus",
|
||||
]
|
||||
|
||||
_TROUBLESHOOT_ADVANCED_LATERAL_KEYS = [
|
||||
"AdvancedLateralTune",
|
||||
"SteerDelay",
|
||||
"SteerFriction",
|
||||
"SteerOffset",
|
||||
"SteerKP",
|
||||
"SteerLatAccel",
|
||||
"SteerRatio",
|
||||
"ForceAutoTune",
|
||||
"ForceAutoTuneOff",
|
||||
"ForceTorqueController",
|
||||
]
|
||||
|
||||
_TROUBLESHOOT_ADVANCED_LONGITUDINAL_KEYS = [
|
||||
"AdvancedLongitudinalTune",
|
||||
"EVTuning",
|
||||
"TruckTuning",
|
||||
"LongitudinalActuatorDelay",
|
||||
"StartAccel",
|
||||
"VEgoStarting",
|
||||
"StopAccel",
|
||||
"StoppingDecelRate",
|
||||
"VEgoStopping",
|
||||
]
|
||||
|
||||
_TROUBLESHOOT_SECTION_DEFINITIONS = [
|
||||
{
|
||||
"id": "personality_settings",
|
||||
"title": "Personality Profile Settings",
|
||||
"keys": _TROUBLESHOOT_PERSONALITY_KEYS,
|
||||
},
|
||||
{
|
||||
"id": "model_stop_distance",
|
||||
"title": "Model Stop Distance",
|
||||
"keys": ["StopDistance"],
|
||||
},
|
||||
{
|
||||
"id": "cem_settings",
|
||||
"title": "CEM Settings",
|
||||
"keys": _TROUBLESHOOT_CEM_KEYS,
|
||||
},
|
||||
{
|
||||
"id": "advanced_lateral_tuning",
|
||||
"title": "Advanced Lateral Tuning",
|
||||
"keys": _TROUBLESHOOT_ADVANCED_LATERAL_KEYS,
|
||||
},
|
||||
{
|
||||
"id": "advanced_longitudinal_tuning",
|
||||
"title": "Advanced Longitudinal Tuning",
|
||||
"keys": _TROUBLESHOOT_ADVANCED_LONGITUDINAL_KEYS,
|
||||
},
|
||||
]
|
||||
|
||||
_TROUBLESHOOT_SECTION_BY_ID = {
|
||||
section["id"]: section
|
||||
for section in _TROUBLESHOOT_SECTION_DEFINITIONS
|
||||
}
|
||||
|
||||
_TROUBLESHOOT_NON_RESETTABLE_SECTION_KEYS = {
|
||||
"CustomPersonalities",
|
||||
"TrafficPersonalityProfile",
|
||||
"AggressivePersonalityProfile",
|
||||
"StandardPersonalityProfile",
|
||||
"RelaxedPersonalityProfile",
|
||||
}
|
||||
|
||||
def _normalize_fingerprint_make_key(make_value):
|
||||
return str(make_value or "").strip().lower()
|
||||
|
||||
@@ -1035,26 +1150,39 @@ def write_legacy_param_file(key, value):
|
||||
os.replace(tmp_path, value_path)
|
||||
|
||||
_layout_type_overrides = None
|
||||
_layout_param_metadata = None
|
||||
|
||||
def _get_layout_type_overrides():
|
||||
global _layout_type_overrides
|
||||
if _layout_type_overrides is None:
|
||||
def _get_layout_param_metadata():
|
||||
global _layout_param_metadata
|
||||
if _layout_param_metadata is None:
|
||||
try:
|
||||
layout_path = os.path.join(os.path.dirname(__file__), "assets", "components", "tools", "device_settings_layout.json")
|
||||
with open(layout_path) as f:
|
||||
layout_data = json.load(f)
|
||||
_layout_type_overrides = {
|
||||
p["key"]: p["data_type"]
|
||||
_layout_param_metadata = {
|
||||
p["key"]: p
|
||||
for section in layout_data
|
||||
for p in section.get("params", [])
|
||||
if "key" in p and "data_type" in p
|
||||
if "key" in p
|
||||
}
|
||||
except Exception:
|
||||
_layout_type_overrides = {}
|
||||
_layout_param_metadata = {}
|
||||
return _layout_param_metadata
|
||||
|
||||
def _get_layout_type_overrides():
|
||||
global _layout_type_overrides
|
||||
if _layout_type_overrides is None:
|
||||
layout_param_metadata = _get_layout_param_metadata()
|
||||
_layout_type_overrides = {
|
||||
key: param_data.get("data_type")
|
||||
for key, param_data in layout_param_metadata.items()
|
||||
if param_data.get("data_type")
|
||||
}
|
||||
return _layout_type_overrides
|
||||
|
||||
_cached_allowed_keys = None
|
||||
_cached_param_types = None
|
||||
_cached_default_values = None
|
||||
|
||||
def _get_param_type_info():
|
||||
global _cached_allowed_keys, _cached_param_types
|
||||
@@ -1080,6 +1208,307 @@ def _get_param_type_info():
|
||||
_cached_param_types = types
|
||||
return _cached_allowed_keys, _cached_param_types
|
||||
|
||||
def _get_default_param_values():
|
||||
global _cached_default_values
|
||||
if _cached_default_values is None:
|
||||
_cached_default_values = {
|
||||
key: default_val
|
||||
for key, default_val, _, _ in frogpilot_default_params
|
||||
if key not in EXCLUDED_KEYS
|
||||
}
|
||||
return _cached_default_values
|
||||
|
||||
def _coerce_param_value(raw_value, value_type):
|
||||
safe_type = value_type or str
|
||||
|
||||
if safe_type == bool:
|
||||
if isinstance(raw_value, bool):
|
||||
return raw_value
|
||||
if isinstance(raw_value, bytes):
|
||||
raw_value = raw_value.decode("utf-8", errors="replace")
|
||||
return str(raw_value or "").strip() in ("1", "true", "True")
|
||||
|
||||
if safe_type == float:
|
||||
if raw_value in (None, "", b""):
|
||||
return 0.0
|
||||
try:
|
||||
if isinstance(raw_value, bytes):
|
||||
raw_value = raw_value.decode("utf-8", errors="replace")
|
||||
return float(str(raw_value).strip())
|
||||
except Exception:
|
||||
return 0.0
|
||||
|
||||
if safe_type == int:
|
||||
if raw_value in (None, "", b""):
|
||||
return 0
|
||||
try:
|
||||
if isinstance(raw_value, bytes):
|
||||
raw_value = raw_value.decode("utf-8", errors="replace")
|
||||
return int(float(str(raw_value).strip()))
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
if isinstance(raw_value, bytes):
|
||||
return raw_value.decode("utf-8", errors="replace")
|
||||
return str(raw_value or "")
|
||||
|
||||
def _safe_params_get(key, encoding=None, default=None):
|
||||
try:
|
||||
if encoding is not None:
|
||||
return params.get(key, encoding=encoding)
|
||||
return params.get(key)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def _safe_params_get_bool(key, default=False):
|
||||
try:
|
||||
return params.get_bool(key)
|
||||
except Exception:
|
||||
return bool(default)
|
||||
|
||||
def _is_blank_param_raw(raw_value):
|
||||
if raw_value is None:
|
||||
return True
|
||||
if isinstance(raw_value, bytes):
|
||||
return len(raw_value.strip()) == 0
|
||||
if isinstance(raw_value, str):
|
||||
return len(raw_value.strip()) == 0
|
||||
return False
|
||||
|
||||
def _get_current_param_value(key, value_type):
|
||||
safe_type = value_type or str
|
||||
if safe_type == bool:
|
||||
return params.get_bool(key)
|
||||
return _coerce_param_value(params.get(key), safe_type)
|
||||
|
||||
def _serialize_param_write_value(raw_value):
|
||||
if isinstance(raw_value, bool):
|
||||
return "1" if raw_value else "0"
|
||||
if isinstance(raw_value, bytes):
|
||||
return raw_value.decode("utf-8", errors="replace")
|
||||
return str(raw_value or "")
|
||||
|
||||
def _format_longitudinal_personality(value):
|
||||
mapping = {
|
||||
"0": "Aggressive",
|
||||
"1": "Standard",
|
||||
"2": "Relaxed",
|
||||
}
|
||||
text = str(value or "").strip()
|
||||
if text in mapping:
|
||||
return mapping[text]
|
||||
return f"Unknown ({text})" if text else "Unknown"
|
||||
|
||||
def _resolve_troubleshoot_current_value(key, value_type, default_values):
|
||||
safe_type = value_type or str
|
||||
|
||||
if safe_type == bool:
|
||||
return _safe_params_get_bool(key)
|
||||
|
||||
raw_value = _safe_params_get(key)
|
||||
if not _is_blank_param_raw(raw_value):
|
||||
return _coerce_param_value(raw_value, safe_type)
|
||||
|
||||
stock_key = f"{key}Stock"
|
||||
if stock_key in default_values:
|
||||
stock_raw_value = _safe_params_get(stock_key)
|
||||
if not _is_blank_param_raw(stock_raw_value):
|
||||
return _coerce_param_value(stock_raw_value, safe_type)
|
||||
|
||||
default_raw_value = default_values.get(key)
|
||||
if not _is_blank_param_raw(default_raw_value):
|
||||
return _coerce_param_value(default_raw_value, safe_type)
|
||||
|
||||
return _coerce_param_value(raw_value, safe_type)
|
||||
|
||||
def _resolve_troubleshoot_default_value(key, value_type, default_values):
|
||||
safe_type = value_type or str
|
||||
default_raw_value = default_values.get(key)
|
||||
if not _is_blank_param_raw(default_raw_value):
|
||||
return _coerce_param_value(default_raw_value, safe_type)
|
||||
|
||||
stock_key = f"{key}Stock"
|
||||
if stock_key in default_values:
|
||||
stock_current_raw = _safe_params_get(stock_key)
|
||||
if not _is_blank_param_raw(stock_current_raw):
|
||||
return _coerce_param_value(stock_current_raw, safe_type)
|
||||
|
||||
stock_default_raw = default_values.get(stock_key)
|
||||
if not _is_blank_param_raw(stock_default_raw):
|
||||
return _coerce_param_value(stock_default_raw, safe_type)
|
||||
|
||||
return _coerce_param_value(default_raw_value, safe_type)
|
||||
|
||||
def _get_safety_snapshot_text():
|
||||
cp_bytes = params.get("CarParamsPersistent")
|
||||
if not cp_bytes:
|
||||
return "Unavailable"
|
||||
|
||||
try:
|
||||
with car.CarParams.from_bytes(cp_bytes) as cp:
|
||||
safety_configs = list(getattr(cp, "safetyConfigs", []))
|
||||
if not safety_configs:
|
||||
return "Unavailable"
|
||||
|
||||
entries = []
|
||||
for config in safety_configs:
|
||||
model = str(getattr(config, "safetyModel", "unknown"))
|
||||
safety_param = int(getattr(config, "safetyParam", 0))
|
||||
entries.append(f"{model} ({safety_param} / 0x{safety_param:X})")
|
||||
|
||||
return ", ".join(entries) if entries else "Unavailable"
|
||||
except Exception:
|
||||
return "Unavailable"
|
||||
|
||||
def _get_fingerprint_snapshot_text():
|
||||
cp_bytes = params.get("CarParamsPersistent")
|
||||
cp_fingerprint = ""
|
||||
try:
|
||||
if cp_bytes:
|
||||
with car.CarParams.from_bytes(cp_bytes) as cp:
|
||||
cp_fingerprint = str(getattr(cp, "carFingerprint", "") or "").strip()
|
||||
except Exception:
|
||||
cp_fingerprint = ""
|
||||
|
||||
model_name = str(params.get("CarModelName", encoding="utf-8") or "").strip()
|
||||
model_value = str(params.get("CarModel", encoding="utf-8") or "").strip()
|
||||
|
||||
if model_name and model_value:
|
||||
return f"{model_name} ({model_value})"
|
||||
if model_name:
|
||||
return model_name
|
||||
if model_value:
|
||||
return model_value
|
||||
if cp_fingerprint:
|
||||
return cp_fingerprint
|
||||
return "Unknown"
|
||||
|
||||
def _build_troubleshoot_section_payload(section_definition, value_types, default_values, layout_metadata):
|
||||
section_keys = [str(key).strip() for key in section_definition.get("keys", []) if str(key).strip()]
|
||||
items = []
|
||||
|
||||
for key in section_keys:
|
||||
param_metadata = layout_metadata.get(key, {}) if isinstance(layout_metadata.get(key, {}), dict) else {}
|
||||
value_type = value_types.get(key, str)
|
||||
data_type = str(param_metadata.get("data_type") or "").strip().lower()
|
||||
if data_type == "float":
|
||||
value_type = float
|
||||
elif data_type == "int":
|
||||
value_type = int
|
||||
elif data_type == "bool":
|
||||
value_type = bool
|
||||
|
||||
label = str(param_metadata.get("label") or key)
|
||||
try:
|
||||
current_value = _resolve_troubleshoot_current_value(key, value_type, default_values)
|
||||
default_value = _resolve_troubleshoot_default_value(key, value_type, default_values)
|
||||
except Exception:
|
||||
current_value = "Unavailable"
|
||||
default_value = "n/a"
|
||||
|
||||
items.append({
|
||||
"key": key,
|
||||
"label": label,
|
||||
"value": current_value,
|
||||
"defaultValue": default_value,
|
||||
})
|
||||
|
||||
return {
|
||||
"id": section_definition["id"],
|
||||
"title": section_definition["title"],
|
||||
"resettable": True,
|
||||
"items": items,
|
||||
}
|
||||
|
||||
def _build_troubleshoot_payload():
|
||||
_, value_types = _get_param_type_info()
|
||||
default_values = _get_default_param_values()
|
||||
layout_metadata = _get_layout_param_metadata()
|
||||
|
||||
longitudinal_personality_raw = _safe_params_get("LongitudinalPersonality", encoding="utf-8", default="") or ""
|
||||
snapshot_items = [
|
||||
{
|
||||
"id": "safety_param",
|
||||
"label": "Safety Param",
|
||||
"value": _get_safety_snapshot_text(),
|
||||
"resettable": False,
|
||||
},
|
||||
{
|
||||
"id": "fingerprint",
|
||||
"label": "Fingerprint",
|
||||
"value": _get_fingerprint_snapshot_text(),
|
||||
"resettable": False,
|
||||
},
|
||||
{
|
||||
"id": "driving_model",
|
||||
"label": "Current Driving Model",
|
||||
"value": str(_safe_params_get("Model", encoding="utf-8", default="") or "Unknown"),
|
||||
"resettable": False,
|
||||
},
|
||||
{
|
||||
"id": "selected_personality_profile",
|
||||
"label": "Selected Personality Profile",
|
||||
"value": _format_longitudinal_personality(longitudinal_personality_raw),
|
||||
"resettable": False,
|
||||
},
|
||||
]
|
||||
|
||||
sections = [
|
||||
_build_troubleshoot_section_payload(section_definition, value_types, default_values, layout_metadata)
|
||||
for section_definition in _TROUBLESHOOT_SECTION_DEFINITIONS
|
||||
]
|
||||
|
||||
return {
|
||||
"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())
|
||||
if section_definition is None:
|
||||
raise ValueError("Unknown troubleshoot section.")
|
||||
|
||||
allowed_keys, _ = _get_param_type_info()
|
||||
default_values = _get_default_param_values()
|
||||
is_onroad = params.get_bool("IsOnroad")
|
||||
blocked_onroad_keys = {"Model", "AlwaysOnLateral", "ForceTorqueController", "NNFF", "NNFFLite"}
|
||||
|
||||
updated_keys = []
|
||||
skipped_keys = []
|
||||
|
||||
for key in section_definition.get("keys", []):
|
||||
if key in _TROUBLESHOOT_NON_RESETTABLE_SECTION_KEYS:
|
||||
skipped_keys.append({"key": key, "reason": "preserved by design"})
|
||||
continue
|
||||
|
||||
if key not in allowed_keys:
|
||||
skipped_keys.append({"key": key, "reason": "not editable"})
|
||||
continue
|
||||
|
||||
if is_onroad and key in blocked_onroad_keys:
|
||||
skipped_keys.append({"key": key, "reason": "blocked while onroad"})
|
||||
continue
|
||||
|
||||
if key not in default_values:
|
||||
skipped_keys.append({"key": key, "reason": "default unavailable"})
|
||||
continue
|
||||
|
||||
params.put(key, _serialize_param_write_value(default_values[key]))
|
||||
updated_keys.append(key)
|
||||
|
||||
if updated_keys:
|
||||
update_frogpilot_toggles()
|
||||
|
||||
return {
|
||||
"sectionId": section_definition["id"],
|
||||
"sectionTitle": section_definition["title"],
|
||||
"updatedKeys": updated_keys,
|
||||
"skippedKeys": skipped_keys,
|
||||
"updatedCount": len(updated_keys),
|
||||
"skippedCount": len(skipped_keys),
|
||||
}
|
||||
|
||||
def _extract_testing_ground_variant_labels(slot_data, include_default=True):
|
||||
labels = {}
|
||||
if not isinstance(slot_data, dict):
|
||||
@@ -1895,6 +2324,34 @@ def setup(app):
|
||||
|
||||
return jsonify(result), 200
|
||||
|
||||
@app.route("/api/troubleshoot", methods=["GET"])
|
||||
def get_troubleshoot_data():
|
||||
try:
|
||||
return jsonify(_build_troubleshoot_payload()), 200
|
||||
except Exception as exception:
|
||||
return jsonify({"error": str(exception)}), 500
|
||||
|
||||
@app.route("/api/troubleshoot/reset", methods=["POST"])
|
||||
def reset_troubleshoot_section():
|
||||
request_data = request.get_json() or {}
|
||||
section_id = str(request_data.get("sectionId") or "").strip()
|
||||
if not section_id:
|
||||
return jsonify({"error": "Missing 'sectionId' in request body."}), 400
|
||||
|
||||
try:
|
||||
result = _reset_troubleshoot_section(section_id)
|
||||
message = f"{result['sectionTitle']} reset to defaults."
|
||||
if result["skippedCount"] > 0:
|
||||
message += f" Updated {result['updatedCount']} setting(s), skipped {result['skippedCount']}."
|
||||
return jsonify({
|
||||
"message": message,
|
||||
**result,
|
||||
}), 200
|
||||
except ValueError as exception:
|
||||
return jsonify({"error": str(exception)}), 400
|
||||
except Exception as exception:
|
||||
return jsonify({"error": str(exception)}), 500
|
||||
|
||||
@app.route("/api/models/installed", methods=["GET"])
|
||||
def get_installed_models():
|
||||
catalog = get_model_catalog()
|
||||
|
||||
Reference in New Issue
Block a user