Outsourcing the work

This commit is contained in:
firestar5683
2026-08-03 21:36:18 -05:00
parent 290efba471
commit 934c484896
9 changed files with 175 additions and 3 deletions
Binary file not shown.
+1
View File
@@ -331,6 +331,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
{"FLMActiveOverrides", {PERSISTENT, JSON, "{}", "{}", 2}},
{"FLMActiveProfileId", {PERSISTENT, STRING, "", "", 2}},
{"FLMSubmittedTune", {CLEAR_ON_MANAGER_START, JSON, "{}", "{}"}},
{"FLMTrialBaseline", {PERSISTENT | DONT_LOG, JSON, "{}", "{}"}},
{"FLMTrialApplied", {PERSISTENT, BOOL, "0", "0", 2}},
{"FPSCounter", {PERSISTENT, BOOL, "0", "0", 3}},
Binary file not shown.
+6 -1
View File
@@ -13,7 +13,7 @@ from openpilot.common.gps import get_gps_location_service
from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL, Priority, Ratekeeper, config_realtime_process
from openpilot.common.time_helpers import system_time_valid
from openpilot.system.sentry import capture_report
from openpilot.system.sentry import capture_flm_tune_submission, capture_report
from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID
from openpilot.system.hardware.hw import Paths
@@ -116,6 +116,11 @@ def check_assets(now, model_manager, theme_manager, thread_manager, params, para
capture_report(report_data["DiscordUser"], report_data["Issue"], vars(starpilot_toggles))
params_memory.remove("IssueReported")
flm_submission = params_memory.get("FLMSubmittedTune")
if flm_submission:
capture_flm_tune_submission(flm_submission)
params_memory.remove("FLMSubmittedTune")
if params_memory.get_bool("DownloadMaps"):
thread_manager.run_with_lock(update_maps, (now, params, params_memory, True))
@@ -512,6 +512,35 @@ async function deleteSavedTune(tune) {
}
}
async function submitSavedTune(tune) {
if (!tune?.tuneId || state.runningAction) return
const approved = window.confirm(
"Think this FLM tune is genuinely good and worth sharing? Send it to Firestar for review and possible inclusion in future tuning. Only the tune values, car identity, and your Discord username are sent; routes and driving logs are not included."
)
if (!approved) return
const discordUsername = window.prompt("Enter your Discord username so Firestar can credit you.", "")
if (discordUsername === null || !discordUsername.trim()) return
state.runningAction = true
try {
const response = await fetch(`/api/flm/saved-tunes/${encodeURIComponent(tune.tuneId)}/submit`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ discordUsername: discordUsername.trim() }),
})
const payload = await response.json()
if (!response.ok) throw new Error(payload.error || "Failed to submit saved tune.")
state.error = ""
showSnackbar(payload.message || "Tune submitted to Firestar for review.")
} catch (error) {
state.error = error?.message || "Failed to submit saved tune."
showSnackbar(state.error, "error")
} finally {
state.runningAction = false
}
}
async function selectPath(pathKey) {
if (!state.report?.reportId || !pathKey || state.runningAction) return
if (pathKey === (state.report.selectedPathKey || state.report.primaryPathKey)) return
@@ -1263,6 +1292,9 @@ export function Tuning() {
<p class="longManeuverMuted">
Save a working FLM trial, switch between vehicle or trailer setups, then use Revert Trial to return to the exact manual settings from before FLM.
</p>
<p class="longManeuverMuted">
Think a tune is genuinely excellent? Send it to Firestar for review and possible community sharing. Submission includes only tune values, car identity, and your Discord username, not routes or driving logs.
</p>
<div class="flmWorkspaceList">
${() => (state.workspace?.savedTunes || []).length
? state.workspace.savedTunes.map((tune) => html`
@@ -1294,6 +1326,12 @@ export function Tuning() {
@click="${() => deleteSavedTune(tune)}">
Delete
</button>
<button
class="longManeuverButton"
disabled="${() => state.runningAction}"
@click="${() => submitSavedTune(tune)}">
Send to Firestar
</button>
</div>
</div>
`)
+59 -2
View File
@@ -2731,15 +2731,16 @@ def _active_trial_display_state(paths: dict[str, Path], snapshot: Any) -> dict[s
def _current_car_identity(params: Params) -> dict[str, str]:
cp_bytes = params.get("CarParamsPersistent")
if not cp_bytes:
return {"carFingerprint": "", "brand": ""}
return {"carFingerprint": "", "brand": "", "carName": ""}
try:
with car.CarParams.from_bytes(cp_bytes) as car_params:
return {
"carFingerprint": str(getattr(car_params, "carFingerprint", "") or "").strip(),
"brand": str(getattr(car_params, "brand", "") or "").strip(),
"carName": str(getattr(car_params, "carName", "") or "").strip(),
}
except Exception:
return {"carFingerprint": "", "brand": ""}
return {"carFingerprint": "", "brand": "", "carName": ""}
def _normalize_saved_tune_name(name: str) -> str:
@@ -2751,6 +2752,22 @@ def _normalize_saved_tune_name(name: str) -> str:
return normalized
def _normalize_discord_username(username: str) -> str:
normalized = " ".join(str(username or "").split())
if not normalized:
raise ValueError("A Discord username is required to submit a tune.")
if len(normalized) > 64:
raise ValueError("Discord usernames must be 64 characters or fewer.")
if any(ord(character) < 32 for character in normalized):
raise ValueError("Discord username contains an invalid control character.")
return normalized
def _saved_tune_car_name(tune: dict[str, Any]) -> str:
raw_name = str(tune.get("carFingerprint", "") or tune.get("carName", "") or tune.get("brand", "") or "Unknown car")
return " ".join(raw_name.replace("_", " ").split()).title()
def _load_saved_tune(tune_id: str, paths: dict[str, Path] | None = None) -> dict[str, Any]:
paths = paths or ensure_flm_workspace()
tune = _read_json(paths["savedTunes"] / f"{tune_id}.json", {})
@@ -3153,6 +3170,7 @@ def save_active_trial_as_tune(name: str) -> dict[str, Any]:
current_car = _current_car_identity(params)
car_fingerprint = current_car["carFingerprint"] or str(report_car.get("carFingerprint", "") or "")
brand = current_car["brand"] or str(report_car.get("brand", "") or "")
car_name = current_car.get("carName", "") or str(report_car.get("carName", "") or "")
now = time.time()
tune_id = f"tune-{time.time_ns()}"
tune = {
@@ -3163,6 +3181,7 @@ def save_active_trial_as_tune(name: str) -> dict[str, Any]:
"updatedAt": now,
"carFingerprint": car_fingerprint,
"brand": brand,
"carName": car_name,
"sourceReportId": report_id,
"sourceProfileId": str(display_state.get("profileId", "") or ""),
"pathKey": str(display_state.get("pathKey", "") or ""),
@@ -3188,6 +3207,44 @@ def save_active_trial_as_tune(name: str) -> dict[str, Any]:
}
def submit_saved_tune(tune_id: str, discord_username: str) -> dict[str, Any]:
_require_flm_offroad()
paths = ensure_flm_workspace()
tune = _load_saved_tune(tune_id, paths)
discord_username = _normalize_discord_username(discord_username)
car_name = _saved_tune_car_name(tune)
# Keep this payload deliberately separate from reports: tune review needs the
# applied values, not route names, log files, camera footage, or device state.
submitted_tune = {
"schemaVersion": tune.get("schemaVersion", 1),
"tuneId": str(tune.get("tuneId", tune_id) or tune_id),
"name": str(tune.get("name", "Saved Tune") or "Saved Tune"),
"carName": car_name,
"carFingerprint": str(tune.get("carFingerprint", "") or ""),
"brand": str(tune.get("brand", "") or ""),
"baselineParams": {
key: value for key, value in (tune.get("baselineParams", {}) or {}).items()
if key in TRIAL_PARAM_SPECS
},
"genericParams": {
key: value for key, value in (tune.get("genericParams", {}) or {}).items()
if key in FLM_ADVANCED_LATERAL_PARAM_KEYS
},
"flmOverrides": normalize_flm_overrides(tune.get("flmOverrides", {})),
}
Params(memory=True).put("FLMSubmittedTune", {
"discordUsername": discord_username,
"carName": car_name,
"tune": submitted_tune,
})
return {
"message": f"Submitted {tune.get('name', 'Saved Tune')} to Firestar for review.",
"tuneId": tune_id,
"carName": car_name,
}
def apply_saved_tune(tune_id: str) -> dict[str, Any]:
paths = ensure_flm_workspace()
tune = _load_saved_tune(tune_id, paths)
@@ -1230,6 +1230,48 @@ def test_saved_tune_rename_delete_and_vehicle_guard(tmp_path, monkeypatch):
assert not tune_path.exists()
def test_submit_saved_tune_queues_credit_and_tune_only(tmp_path):
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
workspace = module.ensure_flm_workspace()
tune_id = "tune-submit"
(workspace["savedTunes"] / f"{tune_id}.json").write_text(json.dumps({
"schemaVersion": 1,
"tuneId": tune_id,
"name": "Good Curve Tune",
"createdAt": 1.0,
"updatedAt": 1.0,
"carFingerprint": "HYUNDAI_IONIQ_6",
"brand": "hyundai",
"sourceReportId": "report-private",
"pathLabel": "Cleanup Pass",
"baselineParams": {"SteerLatAccel": 2.1},
"genericParams": {"SteerLatAccel": 2.3},
"flmOverrides": {"vehicleKnobs": {"turn_in_boost": 0.1}},
"routeNames": ["must-not-be-submitted"],
}), encoding="utf-8")
fake_params_cls._store = {"IsOnroad": False}
fake_params_cls._memory_store = {}
result = module.submit_saved_tune(tune_id, "@tuner")
submission = fake_params_cls._memory_store["FLMSubmittedTune"]
assert result["carName"] == "Hyundai Ioniq 6"
assert submission["discordUsername"] == "@tuner"
assert submission["carName"] == "Hyundai Ioniq 6"
assert submission["tune"]["genericParams"] == {"SteerLatAccel": 2.3}
assert "routeNames" not in submission["tune"]
assert "routes" not in submission["tune"]
assert "sourceReportId" not in submission["tune"]
assert "pathLabel" not in submission["tune"]
with pytest.raises(ValueError, match="Discord username"):
module.submit_saved_tune(tune_id, "")
fake_params_cls._store["IsOnroad"] = True
with pytest.raises(module.FLMAnalysisCancelled, match="went onroad"):
module.submit_saved_tune(tune_id, "@tuner")
def test_saved_tune_car_switch_uses_the_destination_car_baseline(tmp_path, monkeypatch):
module, fake_params_cls = _load_flm_workspace_module(tmp_path)
workspace = module.ensure_flm_workspace()
+13
View File
@@ -6059,6 +6059,19 @@ def setup(app):
except RuntimeError as error:
return jsonify({"error": str(error)}), 409
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/saved-tunes/<tune_id>/submit", methods=["POST"])
@app.route("/api/flm/saved-tunes/<tune_id>/submit", methods=["POST"])
def submit_flm_saved_tune(tune_id):
data = request.get_json(silent=True) or {}
try:
return jsonify(flm_workspace.submit_saved_tune(tune_id, str(data.get("discordUsername") or ""))), 200
except FileNotFoundError:
return jsonify({"error": "Saved FLM tune not found."}), 404
except ValueError as error:
return jsonify({"error": str(error)}), 400
except RuntimeError as error:
return jsonify({"error": str(error)}), 409
@app.route(f"{LEGACY_LATERAL_METHOD_API_PREFIX}/saved-tunes/<tune_id>", methods=["PATCH"])
@app.route("/api/flm/saved-tunes/<tune_id>", methods=["PATCH"])
def rename_flm_saved_tune(tune_id):
+16
View File
@@ -107,6 +107,22 @@ def capture_report(discord_user, report, starpilot_toggles):
sentry_sdk.flush()
def capture_flm_tune_submission(submission: dict) -> None:
"""Send an explicitly submitted FLM tune without attaching drive artifacts."""
discord_user = str(submission.get("discordUsername", "Unknown") or "Unknown")
car_name = str(submission.get("carName", "Unknown car") or "Unknown car")
tune = submission.get("tune", {})
if not isinstance(tune, dict):
tune = {}
capture_message(
f"{car_name} Tune submitted by {discord_user}",
level="info",
tags={"report_type": "flm_tune_submission", "car_name": car_name},
extras={"flm_tune": tune},
)
def set_tag(key: str, value: str) -> None:
sentry_sdk.set_tag(key, value)