mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-12 07:23:45 +08:00
Queue model selections for the next onroad cycle
This commit is contained in:
@@ -142,7 +142,10 @@ assert.equal(modelActionAllowed(managerPayload, "download", missing), true)
|
||||
assert.equal(modelActionAllowed(managerPayload, "delete", big), false)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, activeBigModel: "" }, "delete", big), true)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true }, "favorite", small), true)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true }, "select-small", small), false)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true }, "select-small", small), true)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true }, "select-big", big), true)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true, jetlinkBig: true }, "select-big", big), false)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true }, "download", missing), false)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, isOnroad: true, downloading: true }, "cancel"), true)
|
||||
assert.equal(modelActionAllowed({ ...managerPayload, capabilities: {} }, "favorite", small), false)
|
||||
assert.equal(fitsModelProfile({ ...small, profiles: ["small", "big"] }, "big"), true)
|
||||
@@ -239,7 +242,7 @@ await disabledRandomizer
|
||||
randomManagement.manager.stop()
|
||||
console.log('Randomizer and exclusion interactions passed')
|
||||
|
||||
assert.match(ModelsPage.template, /Park the vehicle before changing or downloading models/)
|
||||
assert.match(ModelsPage.template, /Selections apply next onroad cycle/)
|
||||
assert.doesNotMatch(ModelsPage.template, /Onroad: switching/)
|
||||
assert.equal((ModelsPage.template.match(/class="gx-model-control"/g) || []).length, 2)
|
||||
assert.equal(ModelsPage.data().hardwareFilter, 'both')
|
||||
@@ -315,7 +318,7 @@ const refreshingCatalog = { ...managerPayload, downloading: true, refreshing: tr
|
||||
assert.equal(modelActionAllowed(refreshingCatalog, "select-small", small), true)
|
||||
assert.equal(modelActionAllowed(refreshingCatalog, "select-big", big), true)
|
||||
assert.equal(modelActionAllowed(refreshingCatalog, "select-big", missing), false)
|
||||
assert.equal(modelActionAllowed({ ...refreshingCatalog, isOnroad: true }, "select-big", big), false)
|
||||
assert.equal(modelActionAllowed({ ...refreshingCatalog, isOnroad: true }, "select-big", big), true)
|
||||
assert.equal(modelActionAllowed({ ...refreshingCatalog, randomizer: true }, "select-big", big), false)
|
||||
assert.equal(modelActionAllowed({ ...refreshingCatalog, refreshing: false }, "select-big", big), false)
|
||||
assert.equal(modelActionAllowed(refreshingCatalog, "delete", small), false)
|
||||
|
||||
@@ -162,9 +162,10 @@ export function fitsModelProfile(model, profile) {
|
||||
|
||||
export function modelActionAllowed(data, action, model = null) {
|
||||
if (!data || data.capabilities?.[actionCapabilities[action]] !== true) return false
|
||||
if (data.isOnroad !== false && !["favorite", "unfavorite", "cancel"].includes(action)) return false
|
||||
if (data.isOnroad !== false && !["favorite", "unfavorite", "cancel", "select-small", "select-big"].includes(action)) return false
|
||||
if (action === "jetlink") return data.jetlink !== undefined
|
||||
if (["select-small", "select-big"].includes(action)) {
|
||||
if (action === "select-big" && data.jetlinkBig && data.isOnroad !== false) return false
|
||||
if (data.downloading && data.refreshing !== true || data.randomizer === true) return false
|
||||
if (action === "select-big" && model === null) return true
|
||||
// the manager marks what each Active can take: installed models, or under Jetlink a big one with an ONNX
|
||||
@@ -469,7 +470,7 @@ export const ModelsPage = {
|
||||
if (!this.disposed && result) {
|
||||
this.trackingProgress = ["download", "downloadAll", "refresh"].includes(action)
|
||||
this.message = (this.trackingProgress && this.status.progress) || result.message ||
|
||||
(action.startsWith("select-") ? "Selection saved for the next start." : "Saved.")
|
||||
(action.startsWith("select-") ? "Selection saved for the next onroad cycle." : "Saved.")
|
||||
}
|
||||
} finally { if (!this.disposed) { this.pendingChoice = null; this.busy = "" } }
|
||||
},
|
||||
@@ -485,7 +486,7 @@ export const ModelsPage = {
|
||||
<p>Manage models, downloads, and Jetlink connections.</p></div></header>
|
||||
<GxNotice v-if="message" tone="info">{{ message }}</GxNotice>
|
||||
<GxNotice v-if="error" tone="danger" title="Model Manager">{{ error }}</GxNotice>
|
||||
<GxNotice v-if="status.isOnroad" tone="warn">Park the vehicle before changing or downloading models.</GxNotice>
|
||||
<GxNotice v-if="status.isOnroad" tone="warn">Selections apply next onroad cycle. Park before downloading models or changing Jetlink.</GxNotice>
|
||||
<GxNotice v-if="status.downloading" :title="'Downloading ' + downloadTargetLabel">{{ status.progress || 'Keep the device connected until the download finishes.' }}</GxNotice>
|
||||
<section class="gx-card gx-model-overview">
|
||||
<div class="gx-section__header">
|
||||
@@ -544,14 +545,14 @@ export const ModelsPage = {
|
||||
|
||||
<div class="gx-model-control">
|
||||
<label class="gx-row__label" for="gx-model-small">Active Small · On-device</label>
|
||||
<GalaxySelect id="gx-model-small" class="gx-field" :value="pendingChoice?.action === 'select-small' ? pendingChoice.value : activeSmallModel" :disabled="selectionUncertain || capabilities.select !== true || status.isOnroad || status.randomizer" @change="runAction('select-small', installedSmallModels.find(m => m.value === $event.target.value))">
|
||||
<GalaxySelect id="gx-model-small" class="gx-field" :value="pendingChoice?.action === 'select-small' ? pendingChoice.value : activeSmallModel" :disabled="selectionUncertain || capabilities.select !== true || status.randomizer" @change="runAction('select-small', installedSmallModels.find(m => m.value === $event.target.value))">
|
||||
<option v-for="m in installedSmallModels" :key="m.value" :value="m.value" :disabled="!m.selectable">{{ m.label || m.value }}</option>
|
||||
</GalaxySelect>
|
||||
</div>
|
||||
|
||||
<div class="gx-model-control">
|
||||
<label class="gx-row__label" for="gx-model-big">Active Big · GPU</label>
|
||||
<GalaxySelect id="gx-model-big" class="gx-field" :value="pendingChoice?.action === 'select-big' ? pendingChoice.value : activeBigModel" :disabled="selectionUncertain || capabilities.select !== true || status.isOnroad || status.randomizer" @change="$event.target.value ? runAction('select-big', installedBigModels.find(m => m.value === $event.target.value)) : runAction('select-big')">
|
||||
<GalaxySelect id="gx-model-big" class="gx-field" :value="pendingChoice?.action === 'select-big' ? pendingChoice.value : activeBigModel" :disabled="selectionUncertain || capabilities.select !== true || status.randomizer || status.jetlinkBig && status.isOnroad !== false" @change="$event.target.value ? runAction('select-big', installedBigModels.find(m => m.value === $event.target.value)) : runAction('select-big')">
|
||||
<option value="">{{ bigNoneLabel }}</option>
|
||||
<option v-for="m in installedBigModels" :key="m.value" :value="m.value" :disabled="!m.selectable">{{ m.label || m.value }}</option>
|
||||
</GalaxySelect>
|
||||
|
||||
@@ -557,7 +557,7 @@ class ModelManager:
|
||||
"modelToDownload": job.get("model", ""), "downloadAll": job.get("downloadAll", False),
|
||||
"downloading": job["downloading"], "refreshing": job["refreshing"], "progress": job.get("progress", ""),
|
||||
"isOnroad": not parked, "gpuAvailable": gpu, "jetlink": jetlink,
|
||||
"capabilities": {"select": parked, "download": parked, "downloadAll": parked, "cancel": True,
|
||||
"capabilities": {"select": True, "download": parked, "downloadAll": parked, "cancel": True,
|
||||
"jetlink": parked, "refresh": parked, "favorites": True, "delete": parked, "randomizer": parked, "exclusions": parked}}
|
||||
|
||||
def _require_parked(self) -> None:
|
||||
@@ -609,7 +609,8 @@ class ModelManager:
|
||||
saved.update(payload)
|
||||
atomic_json(self.root / "preferences.json", saved)
|
||||
return {"message": "Model preferences saved"}
|
||||
self._require_parked()
|
||||
if action != "active":
|
||||
self._require_parked()
|
||||
if action in ("download", "download_all", "refresh_manifest"):
|
||||
if self.worker is not None and self.worker.is_alive():
|
||||
raise ModelError("A model download is already running")
|
||||
@@ -682,7 +683,8 @@ class ModelManager:
|
||||
elif not self._installed(mid, entries):
|
||||
raise ModelError("Download and verify this model first")
|
||||
with state_lock(self.root):
|
||||
self._require_parked()
|
||||
if jetlink:
|
||||
self._require_parked()
|
||||
saved = preferences(self.root)
|
||||
if saved["randomizer"]:
|
||||
raise ModelError("Turn Model Randomizer off before choosing a model")
|
||||
|
||||
@@ -286,13 +286,20 @@ class ModelManagerTest(unittest.TestCase):
|
||||
self.assertFalse(self.model_status()['installed'])
|
||||
self.assertTrue(self.wait_for_check()['installed'])
|
||||
|
||||
def test_offroad_authority_rechecked_after_validation(self):
|
||||
def test_onroad_selection_preserves_current_runtime_until_next_start(self):
|
||||
self.download()
|
||||
samples = iter([True, False])
|
||||
self.manager.parked = lambda: next(samples)
|
||||
current = resolve_runtime(False, root=self.root)
|
||||
self.manager.parked = lambda: False
|
||||
self.assertTrue(self.manager.snapshot()['capabilities']['select'])
|
||||
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
|
||||
self.assertEqual(preferences(self.root)['small'], 'gwm8223')
|
||||
self.assertEqual(current.small_id, DEFAULT_SMALL)
|
||||
self.assertEqual(resolve_runtime(False, root=self.root).small_id, 'gwm8223')
|
||||
with self.assertRaises(ModelError):
|
||||
self.manager.action('active', {'profile': 'small', 'model': 'gwm8223'})
|
||||
self.assertEqual(preferences(self.root)['small'], DEFAULT_SMALL)
|
||||
self.manager.action('download', {'model': 'gwm8223'})
|
||||
owner = SimpleNamespace(chooses_big=lambda: True, choose=lambda *args: self.fail('Live Jetlink selection changed'))
|
||||
with patch.object(self.manager, 'jetlink_owner', return_value=owner), self.assertRaises(ModelError):
|
||||
self.manager.action('active', {'profile': 'big', 'model': ''})
|
||||
|
||||
def test_hardware_profiles_and_selected_delete(self):
|
||||
self.download()
|
||||
@@ -811,8 +818,8 @@ class ModelManagerTest(unittest.TestCase):
|
||||
with patch('openpilot.starpilot.models.manager.verified_artifact', side_effect=AssertionError('unchanged local file rehashed')):
|
||||
observer.action('active', {'profile': 'big', 'model': 'cinquev3'})
|
||||
self.parked = False
|
||||
with self.assertRaises(ModelError):
|
||||
observer.action('active', {'profile': 'big', 'model': 'cinquev3'})
|
||||
observer.action('active', {'profile': 'big', 'model': 'cinquev3'})
|
||||
self.assertEqual(preferences(self.root)['big'], 'cinquev3')
|
||||
self.parked = True
|
||||
(self.root / 'cinquev3/cinquev3_driving_tinygrad.pkl').write_bytes(b'corrupt')
|
||||
with self.assertRaisesRegex(ModelError, 'verify'):
|
||||
|
||||
@@ -61,9 +61,11 @@ def model_action_allowed(data: dict, action: str, model: dict | None = None, pro
|
||||
"exclusion": "exclusions"}.get(action, action)
|
||||
if data.get("capabilities", {}).get(capability) is not True:
|
||||
return False
|
||||
if data.get("isOnroad") is not False and action not in ("preferences", "cancel"):
|
||||
if data.get("isOnroad") is not False and action not in ("preferences", "cancel", "active"):
|
||||
return False
|
||||
if action == "active":
|
||||
if profile == "big" and data.get("jetlinkBig") and data.get("isOnroad") is not False:
|
||||
return False
|
||||
return (not data.get("randomizer") and (not data.get("downloading") or data.get("refreshing") is True) and profile in ("small", "big") and
|
||||
((model is None and profile == "big") or
|
||||
bool(model and model.get("selectable") is True and model_fits_profile(model, profile))))
|
||||
@@ -118,11 +120,12 @@ def model_manager_rows(data: dict) -> tuple[FeatureRow, ...]:
|
||||
for profile, field in (("small", "activeSmallModel"), ("big", "activeBigModel")):
|
||||
mid = data.get(field, "")
|
||||
label = next((m.get("label", mid) for m in data.get("models", ()) if m.get("value") == mid), mid or big_none_label(data))
|
||||
available = (data.get("capabilities", {}).get("select") is True and data.get("isOnroad") is False and
|
||||
available = (data.get("capabilities", {}).get("select") is True and
|
||||
not (profile == "big" and data.get("jetlinkBig") and data.get("isOnroad") is not False) and
|
||||
(not data.get("downloading") or data.get("refreshing") is True) and not data.get("randomizer"))
|
||||
rows.append(FeatureRow("", f"Active {profile.title()}", label, source=mid.encode(), available=available,
|
||||
reason="Randomizer selects at the next start" if data.get("randomizer") else
|
||||
"Change for next start" if available else "Turn the vehicle off to change models", page=f"models:{profile}"))
|
||||
"Applies next onroad cycle" if available else "Model selection unavailable", page=f"models:{profile}"))
|
||||
return tuple(rows)
|
||||
|
||||
|
||||
|
||||
@@ -156,7 +156,10 @@ class TestModelManager(unittest.TestCase):
|
||||
self.assertTrue(model_action_allowed({**self.data, "activeBigModel": ""}, "delete", self.big))
|
||||
self.assertFalse(model_action_allowed({**self.data, "capabilities": {}}, "active", self.small, "small"))
|
||||
moving = {**self.data, "isOnroad": True, "downloading": True}
|
||||
self.assertFalse(model_action_allowed(moving, "active", self.small, "small"))
|
||||
self.assertTrue(model_action_allowed(moving, "active", self.small, "small"))
|
||||
self.assertTrue(model_action_allowed(moving, "active", self.big, "big"))
|
||||
self.assertFalse(model_action_allowed(moving | {"jetlinkBig": True}, "active", self.big, "big"))
|
||||
self.assertFalse(model_action_allowed(moving, "download", self.missing))
|
||||
self.assertTrue(model_action_allowed(moving, "preferences", self.small))
|
||||
self.assertTrue(model_action_allowed(moving, "cancel"))
|
||||
|
||||
@@ -248,7 +251,7 @@ class TestModelManager(unittest.TestCase):
|
||||
self.assertTrue(model_action_allowed(data, "active", self.small, "small"))
|
||||
self.assertTrue(model_action_allowed(data, "active", self.big, "big"))
|
||||
self.assertFalse(model_action_allowed(data, "active", self.pending, "small"))
|
||||
self.assertFalse(model_action_allowed(data | {"isOnroad": True}, "active", self.big, "big"))
|
||||
self.assertTrue(model_action_allowed(data | {"isOnroad": True}, "active", self.big, "big"))
|
||||
self.assertFalse(model_action_allowed(data | {"randomizer": True}, "active", self.big, "big"))
|
||||
self.assertFalse(model_action_allowed(data | {"refreshing": False}, "active", self.big, "big"))
|
||||
self.assertFalse(model_action_allowed(data, "delete", self.small))
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{"protected_inventory_sha256": "3f48edce7f387d0e45d93fd3e6dd8cdad10f0e88def45bb818e65882bf2d977e", "schema_version": 1}
|
||||
{"protected_inventory_sha256": "03e24bc36f837e5456c78ef3a7b9a2739826cbe2ebe67a9f592987be876893e6", "schema_version": 1}
|
||||
|
||||
Reference in New Issue
Block a user