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'
95145const notifier = new Notifier (frontend_service )
96146const 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+
98162export 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