diff --git a/core/frontend/src/components/vehiclesetup/overview/ParamSets.vue b/core/frontend/src/components/vehiclesetup/overview/ParamSets.vue
index e91c23d1ec..204e824d5c 100644
--- a/core/frontend/src/components/vehiclesetup/overview/ParamSets.vue
+++ b/core/frontend/src/components/vehiclesetup/overview/ParamSets.vue
@@ -44,18 +44,68 @@
These are the recommended parameter sets for your vehicle and firmware version. Curated by Blue Robotics
-
+
+
+ No parameters available for this setup
+
+
- {{ name.split('/').pop() }}
+ {{ displayName(item.name) }}
+ v{{ item.version_label }}
-
- No parameters available for this setup
+
+
+ No parameter sets match your firmware version. Older sets are shown below.
+
+
+ {{ show_older ? 'Hide' : 'Show' }}
+ {{ outdated_param_sets.length }}
+ older set{{ outdated_param_sets.length === 1 ? '' : 's' }}
+
+
+
+
+
+
+
+ mdi-alert
+
+ {{ displayName(item.name) }}
+ v{{ item.version_label }}
+
+
+
+ Outdated: built for firmware {{ item.version_label }}
+ (current is {{ current_version_label }})
+
+
+
+ version: SemVer
+ version_label: string
+ outdated: boolean
+}
+
+type Candidate = Omit & { has_patch: boolean }
+
+function escapeRegex(value: string): string {
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+}
+
export default Vue.extend({
name: 'ParamSets',
components: {
@@ -111,6 +175,7 @@ export default Vue.extend({
erasing: false,
settings,
show_warning: false,
+ show_older: false,
}),
computed: {
board(): string | undefined {
@@ -122,30 +187,83 @@ export default Vue.extend({
version(): SemVer | undefined {
return autopilot.firmware_info?.version
},
- filtered_param_sets(): Dictionary> | undefined {
- const fw_patch = `${this.vehicle}/${this.version}/${this.board}`
- const fw_minor = `${this.vehicle}/${this.version?.major}.${this.version?.minor}/${this.board}`
- const fw_major = `${this.vehicle}/${this.version?.major}/${this.board}`
-
- // returns a new dict where the keys start with the fullname
- // e.g. "ArduSub/BlueROV2/4.0.3" -> "ArduSub/BlueROV2/4.0.3/BlueROV2"
-
- let fw_params = {}
- // try to find a paramset that matches the firmware version, starting from patch and walking up to major
- for (const string of [fw_patch, fw_minor, fw_major]) {
- fw_params = Object.fromEntries(
- Object.entries(this.all_param_sets).filter(
- // We add a trailing slash to avoid matching Navigator and Navigator64, or any board with suffix
- ([name]) => name.toLocaleLowerCase().includes(`${string.toLowerCase()}/`),
- ),
- )
- if (Object.keys(fw_params).length > 0) {
- break
+ filtered_param_sets(): FilteredParamSet[] {
+ if (!this.vehicle || !this.board || !this.version) {
+ return []
+ }
+
+ // Match keys shaped like "...////..."
+ // Trailing slash on the board avoids matching Navigator vs Navigator64, etc.
+ const pattern = new RegExp(
+ `/${escapeRegex(this.vehicle)}/(\\d+\\.\\d+(?:\\.\\d+)?)/${escapeRegex(this.board)}/`,
+ 'i',
+ )
+
+ const current = this.version
+ const candidates: Candidate[] = []
+
+ for (const [name, paramset] of Object.entries(this.all_param_sets)) {
+ const match = name.match(pattern)
+ if (!match) {
+ continue
+ }
+
+ const version_label = match[1]
+ const has_patch = version_label.split('.').length === 3
+ let version: SemVer
+ try {
+ // Normalize "4.5" -> "4.5.0" so SemVer can parse it
+ version = new SemVer(has_patch ? version_label : `${version_label}.0`)
+ } catch {
+ continue
+ }
+
+ // Skip paramsets targeting a newer firmware than the one currently installed
+ if (version.compare(current) > 0) {
+ continue
}
+
+ candidates.push({
+ name, paramset, version, version_label, has_patch,
+ })
}
- return {
- ...fw_params,
+
+ // Specificity: 2 = exact patch match, 1 = same major.minor, 0 = older
+ function specificity(c: Candidate): number {
+ if (c.has_patch && c.version.compare(current) === 0) return 2
+ if (c.version.major === current.major && c.version.minor === current.minor) return 1
+ return 0
}
+ const scored = candidates.map((c) => ({ candidate: c, score: specificity(c) }))
+ const best = scored.reduce((max, s) => Math.max(max, s.score), 0)
+
+ const result: FilteredParamSet[] = scored.map(({ candidate, score }) => ({
+ name: candidate.name,
+ paramset: candidate.paramset,
+ version: candidate.version,
+ version_label: candidate.version_label,
+ outdated: best === 0 || score < best,
+ }))
+
+ // Current paramsets first, then outdated ones sorted newest-first
+ result.sort((a, b) => {
+ if (a.outdated !== b.outdated) {
+ return a.outdated ? 1 : -1
+ }
+ return b.version.compare(a.version)
+ })
+
+ return result
+ },
+ current_param_sets(): FilteredParamSet[] {
+ return this.filtered_param_sets.filter((p) => !p.outdated)
+ },
+ outdated_param_sets(): FilteredParamSet[] {
+ return this.filtered_param_sets.filter((p) => p.outdated)
+ },
+ current_version_label(): string {
+ if (!this.version) return 'unknown'
+ return `${this.version.major}.${this.version.minor}.${this.version.patch}`
},
warningMessage(): string {
return 'You will lose ALL your parameters, vehicle setup, and calibrations. Are you sure you want to reset?'
@@ -165,6 +283,10 @@ export default Vue.extend({
this.selected_paramset_name = name
this.selected_paramset = paramset
},
+ displayName(name: string): string {
+ const basename = name.split('/').pop() ?? name
+ return basename.replace(/\.params$/i, '')
+ },
async restartAutopilot(): Promise {
this.rebooting = true
await AutopilotManager.restart()
@@ -236,4 +358,14 @@ button {
.checkbox-label label {
font-weight: 700;
}
+
+.version-tag {
+ font-size: 0.75em;
+ font-weight: normal;
+ opacity: 0.7;
+}
+
+.full-row {
+ flex-basis: 100%;
+}