Skip to content

Commit f60d137

Browse files
Frontend: allow using outdated param sets
1 parent 2a6209a commit f60d137

1 file changed

Lines changed: 160 additions & 28 deletions

File tree

core/frontend/src/components/vehiclesetup/overview/ParamSets.vue

Lines changed: 160 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,68 @@
4444
These are the recommended parameter sets for your vehicle and firmware version. Curated by Blue Robotics
4545
</p>
4646
</v-card-text>
47-
<v-card-actions>
47+
<v-card-actions class="flex-wrap">
48+
<p v-if="filtered_param_sets.length === 0">
49+
No parameters available for this setup
50+
</p>
51+
4852
<v-btn
49-
v-for="(paramSet, name) in filtered_param_sets"
50-
:key="name"
53+
v-for="item in current_param_sets"
54+
:key="item.name"
5155
color="primary"
52-
@click="loadParams(name, paramSet)"
56+
@click="loadParams(item.name, item.paramset)"
5357
>
54-
{{ name.split('/').pop() }}
58+
{{ displayName(item.name) }}
59+
<span class="version-tag ml-2">v{{ item.version_label }}</span>
5560
</v-btn>
56-
<p v-if="(Object.keys(filtered_param_sets).length === 0)">
57-
No parameters available for this setup
61+
62+
<p
63+
v-if="current_param_sets.length === 0 && outdated_param_sets.length > 0"
64+
class="ma-2 text--secondary full-row"
65+
>
66+
No parameter sets match your firmware version. Older sets are shown below.
5867
</p>
68+
69+
<v-btn
70+
v-if="current_param_sets.length > 0 && outdated_param_sets.length > 0"
71+
text
72+
small
73+
@click="show_older = !show_older"
74+
>
75+
{{ show_older ? 'Hide' : 'Show' }}
76+
{{ outdated_param_sets.length }}
77+
older set{{ outdated_param_sets.length === 1 ? '' : 's' }}
78+
</v-btn>
79+
80+
<template v-if="show_older || current_param_sets.length === 0">
81+
<v-tooltip
82+
v-for="item in outdated_param_sets"
83+
:key="item.name"
84+
bottom
85+
>
86+
<template #activator="{ on }">
87+
<v-btn
88+
color="warning"
89+
outlined
90+
@click="loadParams(item.name, item.paramset)"
91+
v-on="on"
92+
>
93+
<v-icon
94+
left
95+
small
96+
>
97+
mdi-alert
98+
</v-icon>
99+
{{ displayName(item.name) }}
100+
<span class="version-tag ml-2">v{{ item.version_label }}</span>
101+
</v-btn>
102+
</template>
103+
<span>
104+
Outdated: built for firmware {{ item.version_label }}
105+
(current is {{ current_version_label }})
106+
</span>
107+
</v-tooltip>
108+
</template>
59109
</v-card-actions>
60110
</v-card>
61111
<ParameterLoader
@@ -95,6 +145,20 @@ import { frontend_service } from '@/types/frontend_services'
95145
const notifier = new Notifier(frontend_service)
96146
const REPOSITORY_URL = 'https://docs.bluerobotics.com/Blueos-Parameter-Repository/params_v1.json'
97147
148+
interface FilteredParamSet {
149+
name: string
150+
paramset: Dictionary<number>
151+
version: SemVer
152+
version_label: string
153+
outdated: boolean
154+
}
155+
156+
type Candidate = Omit<FilteredParamSet, 'outdated'> & { has_patch: boolean }
157+
158+
function escapeRegex(value: string): string {
159+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
160+
}
161+
98162
export default Vue.extend({
99163
name: 'ParamSets',
100164
components: {
@@ -111,6 +175,7 @@ export default Vue.extend({
111175
erasing: false,
112176
settings,
113177
show_warning: false,
178+
show_older: false,
114179
}),
115180
computed: {
116181
board(): string | undefined {
@@ -122,30 +187,83 @@ export default Vue.extend({
122187
version(): SemVer | undefined {
123188
return autopilot.firmware_info?.version
124189
},
125-
filtered_param_sets(): Dictionary<Dictionary<number>> | undefined {
126-
const fw_patch = `${this.vehicle}/${this.version}/${this.board}`
127-
const fw_minor = `${this.vehicle}/${this.version?.major}.${this.version?.minor}/${this.board}`
128-
const fw_major = `${this.vehicle}/${this.version?.major}/${this.board}`
129-
130-
// returns a new dict where the keys start with the fullname
131-
// e.g. "ArduSub/BlueROV2/4.0.3" -> "ArduSub/BlueROV2/4.0.3/BlueROV2"
132-
133-
let fw_params = {}
134-
// try to find a paramset that matches the firmware version, starting from patch and walking up to major
135-
for (const string of [fw_patch, fw_minor, fw_major]) {
136-
fw_params = Object.fromEntries(
137-
Object.entries(this.all_param_sets).filter(
138-
// We add a trailing slash to avoid matching Navigator and Navigator64, or any board with suffix
139-
([name]) => name.toLocaleLowerCase().includes(`${string.toLowerCase()}/`),
140-
),
141-
)
142-
if (Object.keys(fw_params).length > 0) {
143-
break
190+
filtered_param_sets(): FilteredParamSet[] {
191+
if (!this.vehicle || !this.board || !this.version) {
192+
return []
193+
}
194+
195+
// Match keys shaped like ".../<vehicle>/<X.Y[.Z]>/<board>/..."
196+
// Trailing slash on the board avoids matching Navigator vs Navigator64, etc.
197+
const pattern = new RegExp(
198+
`/${escapeRegex(this.vehicle)}/(\\d+\\.\\d+(?:\\.\\d+)?)/${escapeRegex(this.board)}/`,
199+
'i',
200+
)
201+
202+
const current = this.version
203+
const candidates: Candidate[] = []
204+
205+
for (const [name, paramset] of Object.entries(this.all_param_sets)) {
206+
const match = name.match(pattern)
207+
if (!match) {
208+
continue
209+
}
210+
211+
const version_label = match[1]
212+
const has_patch = version_label.split('.').length === 3
213+
let version: SemVer
214+
try {
215+
// Normalize "4.5" -> "4.5.0" so SemVer can parse it
216+
version = new SemVer(has_patch ? version_label : `${version_label}.0`)
217+
} catch {
218+
continue
219+
}
220+
221+
// Skip paramsets targeting a newer firmware than the one currently installed
222+
if (version.compare(current) > 0) {
223+
continue
144224
}
225+
226+
candidates.push({
227+
name, paramset, version, version_label, has_patch,
228+
})
145229
}
146-
return {
147-
...fw_params,
230+
231+
// Specificity: 2 = exact patch match, 1 = same major.minor, 0 = older
232+
function specificity(c: Candidate): number {
233+
if (c.has_patch && c.version.compare(current) === 0) return 2
234+
if (c.version.major === current.major && c.version.minor === current.minor) return 1
235+
return 0
148236
}
237+
const scored = candidates.map((c) => ({ candidate: c, score: specificity(c) }))
238+
const best = scored.reduce((max, s) => Math.max(max, s.score), 0)
239+
240+
const result: FilteredParamSet[] = scored.map(({ candidate, score }) => ({
241+
name: candidate.name,
242+
paramset: candidate.paramset,
243+
version: candidate.version,
244+
version_label: candidate.version_label,
245+
outdated: best === 0 || score < best,
246+
}))
247+
248+
// Current paramsets first, then outdated ones sorted newest-first
249+
result.sort((a, b) => {
250+
if (a.outdated !== b.outdated) {
251+
return a.outdated ? 1 : -1
252+
}
253+
return b.version.compare(a.version)
254+
})
255+
256+
return result
257+
},
258+
current_param_sets(): FilteredParamSet[] {
259+
return this.filtered_param_sets.filter((p) => !p.outdated)
260+
},
261+
outdated_param_sets(): FilteredParamSet[] {
262+
return this.filtered_param_sets.filter((p) => p.outdated)
263+
},
264+
current_version_label(): string {
265+
if (!this.version) return 'unknown'
266+
return `${this.version.major}.${this.version.minor}.${this.version.patch}`
149267
},
150268
warningMessage(): string {
151269
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({
165283
this.selected_paramset_name = name
166284
this.selected_paramset = paramset
167285
},
286+
displayName(name: string): string {
287+
const basename = name.split('/').pop() ?? name
288+
return basename.replace(/\.params$/i, '')
289+
},
168290
async restartAutopilot(): Promise<void> {
169291
this.rebooting = true
170292
await AutopilotManager.restart()
@@ -236,4 +358,14 @@ button {
236358
.checkbox-label label {
237359
font-weight: 700;
238360
}
361+
362+
.version-tag {
363+
font-size: 0.75em;
364+
font-weight: normal;
365+
opacity: 0.7;
366+
}
367+
368+
.full-row {
369+
flex-basis: 100%;
370+
}
239371
</style>

0 commit comments

Comments
 (0)