Skip to content

Commit ffafde3

Browse files
joystick: restore MANUAL_CONTROL axes saved by a newer version
1.19 persisted those axes as data-lake ids. Resolve them when building MANUAL_CONTROL. Do not rewrite the mapping on read; the duplicate-axis guard treats those aliases as the same action. The settings page labels a leftover binding as MAVLink Axis X in the dropdown, display-only.
1 parent b587d83 commit ffafde3

5 files changed

Lines changed: 91 additions & 11 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { type ProtocolAction, JoystickProtocol } from '@/types/joystick'
2+
3+
// Inverse of 1.19's migrateMavlinkManualControlAxes: that version persisted these data-lake ids
4+
// and 1.18 still drives MANUAL_CONTROL from the MAVLink axis actions.
5+
const dataLakeManualControlAxisById: Record<string, string> = {
6+
'inputs/mavlink/axis-x': 'axis_x',
7+
'inputs/mavlink/axis-y': 'axis_y',
8+
'inputs/mavlink/axis-z': 'axis_z',
9+
'inputs/mavlink/axis-r': 'axis_r',
10+
'inputs/mavlink/axis-s': 'axis_s',
11+
'inputs/mavlink/axis-t': 'axis_t',
12+
}
13+
14+
/**
15+
* MANUAL_CONTROL axis id this action should drive, including 1.19 data-lake bindings.
16+
* @param {ProtocolAction} action - Axis action from the stored mapping
17+
* @returns {string | undefined} `axis_x`…`axis_t`, or undefined if this is not a MANUAL_CONTROL axis
18+
*/
19+
export const manualControlAxisId = (action: ProtocolAction): string | undefined => {
20+
if (action.protocol === JoystickProtocol.MAVLinkManualControl) return action.id
21+
if (action.protocol === JoystickProtocol.DataLakeVariable) return dataLakeManualControlAxisById[action.id]
22+
}
23+
24+
/**
25+
* Label for a 1.19 data-lake MANUAL_CONTROL binding. Display-only — do not write this back.
26+
* @param {ProtocolAction} action - Axis action from the stored mapping
27+
* @returns {string | undefined} `MAVLink Axis X`…`T` when this is a 1.19 alias, otherwise undefined
28+
*/
29+
export const manualControlAxisDisplayName = (action: ProtocolAction): string | undefined => {
30+
if (action.protocol !== JoystickProtocol.DataLakeVariable) return
31+
const id = dataLakeManualControlAxisById[action.id]
32+
if (!id) return
33+
return `MAVLink Axis ${id.slice(-1).toUpperCase()}`
34+
}

src/libs/joystick/protocols/mavlink-manual-control.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { capitalize } from 'vue'
66

77
import { useInteractionDialog } from '@/composables/interactionDialog'
88
import { sendManualControl } from '@/libs/communication/mavlink'
9+
import { manualControlAxisId } from '@/libs/joystick/protocols/manual-control-axis-id'
910
import { modifierKeyActions, otherAvailableActions } from '@/libs/joystick/protocols/other'
1011
import { round, scale } from '@/libs/utils'
1112
import type { ArduPilot } from '@/libs/vehicle/ardupilot/ardupilot'
@@ -501,12 +502,12 @@ export class MavlinkManualControlManager {
501502
}
502503

503504
// Calculate axes values
504-
const xCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_x.id)
505-
const yCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_y.id)
506-
const zCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_z.id)
507-
const rCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_r.id)
508-
const sCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_s.id)
509-
const tCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_t.id)
505+
const xCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => manualControlAxisId(entry[1].action) === mavlinkManualControlAxes.axis_x.id)
506+
const yCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => manualControlAxisId(entry[1].action) === mavlinkManualControlAxes.axis_y.id)
507+
const zCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => manualControlAxisId(entry[1].action) === mavlinkManualControlAxes.axis_z.id)
508+
const rCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => manualControlAxisId(entry[1].action) === mavlinkManualControlAxes.axis_r.id)
509+
const sCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => manualControlAxisId(entry[1].action) === mavlinkManualControlAxes.axis_s.id)
510+
const tCorrespondency = Object.entries(this.currentActionsMapping.axesCorrespondencies).find((entry) => manualControlAxisId(entry[1].action) === mavlinkManualControlAxes.axis_t.id)
510511

511512
// Populate MAVLink Manual Control state of axes and buttons
512513
this.manualControlState.x = xCorrespondency === undefined ? 0 : round(scale(this.joystickState.axes[xCorrespondency[0] as unknown as JoystickAxis] ?? 0, -1, 1, xCorrespondency[1].min, xCorrespondency[1].max), 0)

src/stores/controller.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from '@/libs/joystick/manager'
1818
import { allAvailableAxes, allAvailableButtons, performJoystickMappingMigrations } from '@/libs/joystick/protocols'
1919
import { CockpitActionsFunction, executeActionCallback } from '@/libs/joystick/protocols/cockpit-actions'
20+
import { manualControlAxisId } from '@/libs/joystick/protocols/manual-control-axis-id'
2021
import { modifierKeyActions, otherAvailableActions } from '@/libs/joystick/protocols/other'
2122
import { settingsManager } from '@/libs/settings-management'
2223
import { isElectron } from '@/libs/utils'
@@ -333,13 +334,15 @@ export const useControllerStore = defineStore('controller', () => {
333334
// Check if there's any duplicated axis actions. If so, unmap (set to no_function) the axes that use to have the same action
334335
const oldMapping = structuredClone(toRaw(lastValidProtocolMapping))
335336
const newMapping = protocolMapping.value
336-
const mappedAxisActions = Object.values(newMapping.axesCorrespondencies).map((v) => v.action.id)
337+
const mappedAxisActions = Object.values(newMapping.axesCorrespondencies).map(
338+
(v) => manualControlAxisId(v.action) ?? v.action.id
339+
)
337340
const duplicateAxisActions = mappedAxisActions
338341
.filter((item, index) => mappedAxisActions.indexOf(item) !== index)
339342
.filter((v) => v !== otherAvailableActions.no_function.id)
340343
if (!duplicateAxisActions.isEmpty()) {
341344
Object.entries(newMapping.axesCorrespondencies).forEach(([axis, mapping]) => {
342-
const isDuplicated = duplicateAxisActions.includes(mapping.action.id)
345+
const isDuplicated = duplicateAxisActions.includes(manualControlAxisId(mapping.action) ?? mapping.action.id)
343346
const oldMappingId = oldMapping.axesCorrespondencies[axis as unknown as JoystickAxis]?.action?.id
344347
const wasMapped = oldMappingId === mapping.action.id
345348
if (isDuplicated && wasMapped) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { expect, test } from 'vitest'
2+
3+
import { manualControlAxisDisplayName, manualControlAxisId } from '@/libs/joystick/protocols/manual-control-axis-id'
4+
import { JoystickProtocol } from '@/types/joystick'
5+
6+
test('turns 1.19 data-lake manual-control axes back into MAVLink axis ids', () => {
7+
const dataLakeX = {
8+
protocol: JoystickProtocol.DataLakeVariable,
9+
id: 'inputs/mavlink/axis-x',
10+
name: 'Axis X',
11+
}
12+
const mavlinkX = { protocol: JoystickProtocol.MAVLinkManualControl, id: 'axis_x', name: 'Axis X' }
13+
expect(manualControlAxisId(dataLakeX)).toBe('axis_x')
14+
expect(manualControlAxisId(dataLakeX)).toBe(manualControlAxisId(mavlinkX))
15+
expect(manualControlAxisDisplayName(dataLakeX)).toBe('MAVLink Axis X')
16+
expect(manualControlAxisDisplayName(mavlinkX)).toBeUndefined()
17+
expect(
18+
manualControlAxisId({ protocol: JoystickProtocol.DataLakeVariable, id: 'inputs/mavlink/axis-y', name: 'Axis Y' })
19+
).toBe('axis_y')
20+
})
21+
22+
test('leaves already-MAVLink axes and unrelated data-lake axes alone', () => {
23+
expect(manualControlAxisId({ protocol: JoystickProtocol.MAVLinkManualControl, id: 'axis_x', name: 'Axis X' })).toBe(
24+
'axis_x'
25+
)
26+
expect(
27+
manualControlAxisId({ protocol: JoystickProtocol.DataLakeVariable, id: 'camera-zoom', name: 'Camera Zoom' })
28+
).toBeUndefined()
29+
expect(
30+
manualControlAxisDisplayName({
31+
protocol: JoystickProtocol.DataLakeVariable,
32+
id: 'camera-zoom',
33+
name: 'Camera Zoom',
34+
})
35+
).toBeUndefined()
36+
})

src/views/ConfigurationJoystickView.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@
357357
v-if="item.type === 'axis'"
358358
v-model="selectedProfileAxesCorrespondencies[item.id as JoystickAxis].action"
359359
:items="filteredAndSortedAxisActions"
360-
item-title="name"
360+
:item-title="axisActionTitle"
361361
hide-details
362362
class="mb-2"
363363
density="compact"
@@ -622,7 +622,7 @@
622622
<v-select
623623
v-model="selectedProfileAxesCorrespondencies[input.id].action"
624624
:items="filteredAndSortedAxisActions"
625-
item-title="name"
625+
:item-title="axisActionTitle"
626626
hide-details
627627
density="compact"
628628
variant="outlined"
@@ -666,6 +666,7 @@ import { getDataLakeVariableInfo } from '@/libs/actions/data-lake'
666666
import { getAllTransformingFunctions } from '@/libs/actions/data-lake-transformations'
667667
import { getArdupilotVersion, getMavlink2RestVersion } from '@/libs/blueos'
668668
import { JoystickModel } from '@/libs/joystick/manager'
669+
import { manualControlAxisDisplayName } from '@/libs/joystick/protocols/manual-control-axis-id'
669670
import { MAVLinkButtonFunction } from '@/libs/joystick/protocols/mavlink-manual-control'
670671
import { modifierKeyActions } from '@/libs/joystick/protocols/other'
671672
import { mavlinkCameraFocusActionId, mavlinkCameraZoomActionId } from '@/libs/joystick/protocols/predefined-resources'
@@ -780,9 +781,14 @@ const getAxesNotInSvg = (joystick: Joystick): number[] => {
780781
return joystick.state.axes.map((_, index) => index).filter((axisId) => !svgAxes.has(axisId as JoystickAxis))
781782
}
782783
784+
const axisActionTitle = (action: JoystickAction): string => {
785+
return manualControlAxisDisplayName(action as ProtocolAction) ?? action.name
786+
}
787+
783788
const getAxisActionName = (axisId: number): string => {
784789
const action = selectedProfileAxesCorrespondencies.value[axisId as JoystickAxis]?.action
785-
return action?.name ?? 'unassigned'
790+
if (!action) return 'unassigned'
791+
return axisActionTitle(action)
786792
}
787793
788794
const shiftFunction = {

0 commit comments

Comments
 (0)