|
44 | 44 | These are the recommended parameter sets for your vehicle and firmware version. Curated by Blue Robotics |
45 | 45 | </p> |
46 | 46 | </v-card-text> |
47 | | - <v-card-actions> |
48 | | - <v-btn |
49 | | - v-for="(paramSet, name) in filtered_param_sets" |
50 | | - :key="name" |
51 | | - color="primary" |
52 | | - @click="loadParams(name, paramSet)" |
| 47 | + <v-card-actions class="flex-wrap"> |
| 48 | + <v-tooltip |
| 49 | + v-for="item in filtered_param_sets" |
| 50 | + :key="item.name" |
| 51 | + bottom |
| 52 | + :disabled="!item.outdated" |
53 | 53 | > |
54 | | - {{ name.split('/').pop() }} |
55 | | - </v-btn> |
56 | | - <p v-if="(Object.keys(filtered_param_sets).length === 0)"> |
| 54 | + <template #activator="{ on }"> |
| 55 | + <v-btn |
| 56 | + :color="item.outdated ? 'warning' : 'primary'" |
| 57 | + :outlined="item.outdated" |
| 58 | + @click="loadParams(item.name, item.paramset)" |
| 59 | + v-on="on" |
| 60 | + > |
| 61 | + <v-icon |
| 62 | + v-if="item.outdated" |
| 63 | + left |
| 64 | + small |
| 65 | + > |
| 66 | + mdi-alert |
| 67 | + </v-icon> |
| 68 | + {{ item.name.split('/').pop()?.replace(/\.params$/i, '') }} |
| 69 | + <span class="version-tag ml-2">v{{ item.version_label }}</span> |
| 70 | + </v-btn> |
| 71 | + </template> |
| 72 | + <span> |
| 73 | + Outdated: built for firmware {{ item.version_label }} (current is |
| 74 | + {{ version ? `${version.major}.${version.minor}` : 'unknown' }}) |
| 75 | + </span> |
| 76 | + </v-tooltip> |
| 77 | + <p v-if="filtered_param_sets.length === 0"> |
57 | 78 | No parameters available for this setup |
58 | 79 | </p> |
59 | 80 | </v-card-actions> |
@@ -95,6 +116,18 @@ import { frontend_service } from '@/types/frontend_services' |
95 | 116 | const notifier = new Notifier(frontend_service) |
96 | 117 | const REPOSITORY_URL = 'https://docs.bluerobotics.com/Blueos-Parameter-Repository/params_v1.json' |
97 | 118 |
|
| 119 | +interface FilteredParamSet { |
| 120 | + name: string |
| 121 | + paramset: Dictionary<number> |
| 122 | + version: SemVer |
| 123 | + version_label: string |
| 124 | + outdated: boolean |
| 125 | +} |
| 126 | +
|
| 127 | +function escapeRegex(value: string): string { |
| 128 | + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') |
| 129 | +} |
| 130 | +
|
98 | 131 | export default Vue.extend({ |
99 | 132 | name: 'ParamSets', |
100 | 133 | components: { |
@@ -122,30 +155,73 @@ export default Vue.extend({ |
122 | 155 | version(): SemVer | undefined { |
123 | 156 | return autopilot.firmware_info?.version |
124 | 157 | }, |
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 |
| 158 | + filtered_param_sets(): FilteredParamSet[] { |
| 159 | + if (!this.vehicle || !this.board || !this.version) { |
| 160 | + return [] |
| 161 | + } |
| 162 | +
|
| 163 | + // Match keys shaped like ".../<vehicle>/<X.Y[.Z]>/<board>/..." |
| 164 | + // Trailing slash on the board avoids matching Navigator vs Navigator64, etc. |
| 165 | + const pattern = new RegExp( |
| 166 | + `/${escapeRegex(this.vehicle)}/(\\d+\\.\\d+(?:\\.\\d+)?)/${escapeRegex(this.board)}/`, |
| 167 | + 'i', |
| 168 | + ) |
| 169 | +
|
| 170 | + const current = this.version |
| 171 | + const candidates: { name: string, paramset: Dictionary<number>, version: SemVer, |
| 172 | + version_label: string, has_patch: boolean }[] = [] |
| 173 | +
|
| 174 | + for (const [name, paramset] of Object.entries(this.all_param_sets)) { |
| 175 | + const match = name.match(pattern) |
| 176 | + if (!match) { |
| 177 | + continue |
| 178 | + } |
| 179 | +
|
| 180 | + const version_label = match[1] |
| 181 | + const has_patch = version_label.split('.').length === 3 |
| 182 | + let version: SemVer |
| 183 | + try { |
| 184 | + // Normalize "4.5" -> "4.5.0" so SemVer can parse it |
| 185 | + version = new SemVer(has_patch ? version_label : `${version_label}.0`) |
| 186 | + } catch { |
| 187 | + continue |
144 | 188 | } |
| 189 | +
|
| 190 | + // Skip paramsets targeting a newer firmware than the one currently installed |
| 191 | + if (version.compare(current) > 0) { |
| 192 | + continue |
| 193 | + } |
| 194 | +
|
| 195 | + candidates.push({ |
| 196 | + name, paramset, version, version_label, has_patch, |
| 197 | + }) |
145 | 198 | } |
146 | | - return { |
147 | | - ...fw_params, |
| 199 | +
|
| 200 | + // Specificity: 2 = exact patch match, 1 = same major.minor, 0 = older |
| 201 | + function specificity(c: typeof candidates[number]): number { |
| 202 | + if (c.has_patch && c.version.compare(current) === 0) return 2 |
| 203 | + if (c.version.major === current.major && c.version.minor === current.minor) return 1 |
| 204 | + return 0 |
148 | 205 | } |
| 206 | + const best = candidates.reduce((max, c) => Math.max(max, specificity(c)), 0) |
| 207 | +
|
| 208 | + const result: FilteredParamSet[] = candidates.map((c) => ({ |
| 209 | + name: c.name, |
| 210 | + paramset: c.paramset, |
| 211 | + version: c.version, |
| 212 | + version_label: c.version_label, |
| 213 | + outdated: best === 0 || specificity(c) < best, |
| 214 | + })) |
| 215 | +
|
| 216 | + // Current paramsets first, then outdated ones sorted newest-first |
| 217 | + result.sort((a, b) => { |
| 218 | + if (a.outdated !== b.outdated) { |
| 219 | + return a.outdated ? 1 : -1 |
| 220 | + } |
| 221 | + return b.version.compare(a.version) |
| 222 | + }) |
| 223 | +
|
| 224 | + return result |
149 | 225 | }, |
150 | 226 | warningMessage(): string { |
151 | 227 | return 'You will lose ALL your parameters, vehicle setup, and calibrations. Are you sure you want to reset?' |
|
0 commit comments