From 9e44d916fb989c41ced9a2cace591d8dadcca63f Mon Sep 17 00:00:00 2001
From: Rafael Araujo Lehmkuhl
Date: Tue, 7 Nov 2023 16:50:06 -0300
Subject: [PATCH] WIP
---
src/App.vue | 11 +-
src/assets/joystick-profiles.ts | 71 ++---
src/components/joysticks/JoystickPS.vue | 25 +-
src/libs/joystick/protocols.ts | 186 +------------
.../joystick/protocols/cockpit-actions.ts | 84 ++++++
.../protocols/mavlink-manual-control.ts | 129 +++++++++
src/libs/vehicle/ardupilot/ardupilot.ts | 2 +-
src/stores/controller.ts | 17 +-
src/stores/mainVehicle.ts | 31 ++-
src/stores/widgetManager.ts | 16 +-
src/types/joystick.ts | 94 +++++--
src/views/ConfigurationJoystickView.vue | 262 ++++++++----------
12 files changed, 526 insertions(+), 402 deletions(-)
create mode 100644 src/libs/joystick/protocols/cockpit-actions.ts
create mode 100644 src/libs/joystick/protocols/mavlink-manual-control.ts
diff --git a/src/App.vue b/src/App.vue
index 6a05a2f80..648d111cd 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -134,7 +134,11 @@ import {
import { useRoute } from 'vue-router'
import ConfigurationMenu from '@/components/ConfigurationMenu.vue'
-import { CockpitAction, registerActionCallback, unregisterActionCallback } from '@/libs/joystick/protocols'
+import {
+ availableCockpitActions,
+ registerActionCallback,
+ unregisterActionCallback,
+} from '@/libs/joystick/protocols/cockpit-actions'
import { useMissionStore } from '@/stores/mission'
import Dialog from './components/Dialog.vue'
@@ -160,7 +164,10 @@ const routerSection = ref()
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen()
const debouncedToggleFullScreen = useDebounceFn(() => toggleFullscreen(), 10)
-const fullScreenCallbackId = registerActionCallback(CockpitAction.TOGGLE_FULL_SCREEN, debouncedToggleFullScreen)
+const fullScreenCallbackId = registerActionCallback(
+ availableCockpitActions.toggle_full_screen,
+ debouncedToggleFullScreen
+)
onBeforeUnmount(() => unregisterActionCallback(fullScreenCallbackId))
const fullScreenToggleIcon = computed(() => (isFullscreen.value ? 'mdi-fullscreen-exit' : 'mdi-overscan'))
diff --git a/src/assets/joystick-profiles.ts b/src/assets/joystick-profiles.ts
index 8747fb1db..6c6b6bc40 100644
--- a/src/assets/joystick-profiles.ts
+++ b/src/assets/joystick-profiles.ts
@@ -1,38 +1,45 @@
import { JoystickModel } from '@/libs/joystick/manager'
-import { CockpitAction, MAVLinkAxis } from '@/libs/joystick/protocols'
-import { type GamepadToCockpitStdMapping, type ProtocolControllerMapping, JoystickProtocol } from '@/types/joystick'
+import { availableCockpitActions } from '@/libs/joystick/protocols/cockpit-actions'
+import {
+ mavlinkManualControlAxes,
+ mavlinkManualControlButtonFunctions,
+} from '@/libs/joystick/protocols/mavlink-manual-control'
+import {
+ type GamepadToCockpitStdMapping,
+ type JoystickProtocolActionsMapping,
+ JoystickAxis,
+ JoystickButton,
+} from '@/types/joystick'
// TODO: Adjust mapping for PS5 controller
-export const cockpitStandardToProtocols: ProtocolControllerMapping = {
- name: 'Cockpit Standard Gamepad to Protocols',
- axesCorrespondencies: [
- { protocol: JoystickProtocol.MAVLinkManualControl, value: MAVLinkAxis.Y },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: MAVLinkAxis.X },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: MAVLinkAxis.R },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: MAVLinkAxis.Z },
- ],
- axesMins: [-1000, 1000, -1000, 1000],
- axesMaxs: [1000, -1000, 1000, 0],
- buttonsCorrespondencies: [
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 0 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 1 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 2 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 3 },
- { protocol: JoystickProtocol.CockpitAction, value: CockpitAction.GO_TO_PREVIOUS_VIEW },
- { protocol: JoystickProtocol.CockpitAction, value: CockpitAction.GO_TO_NEXT_VIEW },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 9 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 10 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 4 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 6 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 7 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 8 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 11 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 12 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 13 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 14 },
- { protocol: JoystickProtocol.MAVLinkManualControl, value: 5 },
- { protocol: JoystickProtocol.CockpitAction, value: CockpitAction.TOGGLE_FULL_SCREEN },
- ],
+export const cockpitStandardToProtocols: JoystickProtocolActionsMapping = {
+ name: 'Standard for ArduSub',
+ axesCorrespondencies: {
+ [JoystickAxis.A0]: { action: mavlinkManualControlAxes.axis_y, min: -1000, max: +1000 },
+ [JoystickAxis.A1]: { action: mavlinkManualControlAxes.axis_x, min: +1000, max: -1000 },
+ [JoystickAxis.A2]: { action: mavlinkManualControlAxes.axis_r, min: -1000, max: +1000 },
+ [JoystickAxis.A3]: { action: mavlinkManualControlAxes.axis_z, min: +1000, max: 0 },
+ },
+ buttonsCorrespondencies: {
+ [JoystickButton.B0]: { action: mavlinkManualControlButtonFunctions.arm },
+ [JoystickButton.B1]: { action: mavlinkManualControlButtonFunctions.disarm },
+ [JoystickButton.B2]: { action: mavlinkManualControlButtonFunctions.camera_tilt_up },
+ [JoystickButton.B3]: { action: mavlinkManualControlButtonFunctions.camera_tilt_down },
+ [JoystickButton.B4]: { action: availableCockpitActions.go_to_previous_view },
+ [JoystickButton.B5]: { action: availableCockpitActions.go_to_next_view },
+ [JoystickButton.B6]: { action: mavlinkManualControlButtonFunctions.pilot_gain_up },
+ [JoystickButton.B7]: { action: mavlinkManualControlButtonFunctions.pilot_gain_down },
+ [JoystickButton.B8]: { action: mavlinkManualControlButtonFunctions.light_1_up },
+ [JoystickButton.B9]: { action: mavlinkManualControlButtonFunctions.light_1_down },
+ [JoystickButton.B10]: { action: availableCockpitActions.toggle_full_screen },
+ [JoystickButton.B11]: undefined,
+ [JoystickButton.B12]: undefined,
+ [JoystickButton.B13]: undefined,
+ [JoystickButton.B14]: undefined,
+ [JoystickButton.B15]: undefined,
+ [JoystickButton.B16]: undefined,
+ [JoystickButton.B17]: undefined,
+ },
}
/**
diff --git a/src/components/joysticks/JoystickPS.vue b/src/components/joysticks/JoystickPS.vue
index 6f7b80a03..2e71c576b 100644
--- a/src/components/joysticks/JoystickPS.vue
+++ b/src/components/joysticks/JoystickPS.vue
@@ -6,9 +6,13 @@
import { v4 as uuid4 } from 'uuid'
import { computed, onBeforeUnmount, ref, toRefs, watch } from 'vue'
-import type { InputWithPrettyName } from '@/libs/joystick/protocols'
import { scale } from '@/libs/utils'
-import { type JoystickInput, type ProtocolControllerMapping, JoystickAxis, JoystickButton } from '@/types/joystick'
+import {
+ type JoystickButtonActionCorrespondency,
+ type JoystickInput,
+ JoystickAxis,
+ JoystickButton,
+} from '@/types/joystick'
import { InputType } from '@/types/joystick'
const textColor = '#747474'
@@ -74,8 +78,7 @@ const props = defineProps<{
leftAxisVert?: number // State of the vertical left axis as a floating point number, between -1 and +1
rightAxisHoriz?: number // State of the horizontal right axis as a floating point number, between -1 and +1
rightAxisVert?: number // State of the vertical right axis as a floating point number, between -1 and +1
- protocolMapping: ProtocolControllerMapping // Mapping from the Cockpit standard to the protocol functions
- buttonLabelCorrespondency: InputWithPrettyName[] // Mapping from the protocol functions to human readable names
+ buttonsActionsCorrespondency: JoystickButtonActionCorrespondency // Mapping from the Cockpit standard to the protocol functions
}>()
const emit = defineEmits<{
@@ -86,10 +89,10 @@ const emit = defineEmits<{
const findInputFromPath = (path: string): JoystickInput[] => {
const inputs: JoystickInput[] = []
Object.entries(buttonPath).filter(([, v]) => v === path).forEach((button) => {
- inputs.push({ type: InputType.Button, value: button[0] as unknown as JoystickButton })
+ inputs.push({ type: InputType.Button, id: button[0] as unknown as JoystickButton })
})
Object.entries(axisPath).filter(([, v]) => v === path).forEach((axis) => {
- inputs.push({ type: InputType.Axis, value: axis[0] as unknown as JoystickAxis })
+ inputs.push({ type: InputType.Axis, id: axis[0] as unknown as JoystickAxis })
})
return inputs
}
@@ -171,16 +174,14 @@ watch(
() => updateButtonsState()
)
-const buttonLabelCorrespondency = toRefs(props).buttonLabelCorrespondency
-const protocolMapping = toRefs(props).protocolMapping
-watch([protocolMapping, buttonLabelCorrespondency], () => updateLabelsState())
+const buttonsActionsCorrespondency = toRefs(props).buttonsActionsCorrespondency
+watch(buttonsActionsCorrespondency, () => updateLabelsState())
const updateLabelsState = (): void => {
Object.values(JoystickButton).forEach((button) => {
if (isNaN(Number(button))) return
- const protocolButton = props.protocolMapping.buttonsCorrespondencies[button as JoystickButton] || undefined
- const param = props.buttonLabelCorrespondency.find((btn) => btn.input.protocol === protocolButton.protocol && btn.input.value === protocolButton.value)
- const functionName = param === undefined ? `${protocolButton.value} (${protocolButton.protocol})` : param.prettyName
+ const buttonActionCorrespondency = props.buttonsActionsCorrespondency[button as JoystickButton] || undefined
+ const functionName = buttonActionCorrespondency === undefined ? 'unassigned' : buttonActionCorrespondency.action.name
if (!svg) return
// @ts-ignore: we already check if button is a number and so if button is a valid index
const labelId = buttonPath[button].replace('path', 'text')
diff --git a/src/libs/joystick/protocols.ts b/src/libs/joystick/protocols.ts
index 624e82e20..0a09e0e15 100644
--- a/src/libs/joystick/protocols.ts
+++ b/src/libs/joystick/protocols.ts
@@ -1,84 +1,11 @@
-import { v4 as uuid4 } from 'uuid'
+import { type ProtocolAction, JoystickProtocol } from '@/types/joystick'
-import { round, scale } from '@/libs/utils'
-import { sequentialArray } from '@/libs/utils'
-import {
- type JoystickState,
- type ProtocolControllerMapping,
- type ProtocolInput,
- JoystickProtocol,
- ProtocolControllerState,
-} from '@/types/joystick'
-
-/**
- * Correspondency between protocol input and it's pretty name (usually the actual function it triggers)
- */
-export interface InputWithPrettyName {
- /**
- * Input which triggers the function
- */
- input: ProtocolInput
- /**
- * Name of the parameter option
- */
- prettyName: string
-}
+import { availableCockpitActions } from './protocols/cockpit-actions'
+import { mavlinkManualControlAxes, mavlinkManualControlButtonFunctions } from './protocols/mavlink-manual-control'
/**
* Current state of the controller in the MavLink protocol
*/
-export class MavlinkControllerState extends ProtocolControllerState {
- x: number
- y: number
- z: number
- r: number
- buttons: number
- target: number
- public static readonly BUTTONS_PER_BITFIELD = 16
-
- /**
- *
- * @param { JoystickState } joystickState - Cockpit standard mapped values for the joystick
- * @param { ProtocolControllerMapping } mapping - Gamepad API to Protocols joystick mapping, where assignments and limits are got from.
- * @param { number } target - Specify targeted vehicle ID.
- */
- constructor(joystickState: JoystickState, mapping: ProtocolControllerMapping, target = 1) {
- super()
-
- const isMavlinkInput = (input: ProtocolInput): boolean => input.protocol === JoystickProtocol.MAVLinkManualControl
-
- let buttons_int = 0
- for (let i = 0; i < MavlinkControllerState.BUTTONS_PER_BITFIELD; i++) {
- let buttonState = 0
- mapping.buttonsCorrespondencies.forEach((b, idx) => {
- if (isMavlinkInput(b) && b.value === i && joystickState.buttons[idx]) {
- buttonState = 1
- }
- })
- buttons_int += buttonState * 2 ** i
- }
-
- const xIndex = mapping.axesCorrespondencies.findIndex((v) => isMavlinkInput(v) && v.value === MAVLinkAxis.X)
- const yIndex = mapping.axesCorrespondencies.findIndex((v) => isMavlinkInput(v) && v.value === MAVLinkAxis.Y)
- const zIndex = mapping.axesCorrespondencies.findIndex((v) => isMavlinkInput(v) && v.value === MAVLinkAxis.Z)
- const rIndex = mapping.axesCorrespondencies.findIndex((v) => isMavlinkInput(v) && v.value === MAVLinkAxis.R)
-
- const absLimits = mavlinkAxesLimits
-
- const xLimits = [mapping.axesMins[xIndex] ?? absLimits[0], mapping.axesMaxs[xIndex] ?? absLimits[1]]
- const yLimits = [mapping.axesMins[yIndex] ?? absLimits[0], mapping.axesMaxs[yIndex] ?? absLimits[1]]
- const zLimits = [mapping.axesMins[zIndex] ?? absLimits[0], mapping.axesMaxs[zIndex] ?? absLimits[1]]
- const rLimits = [mapping.axesMins[rIndex] ?? absLimits[0], mapping.axesMaxs[rIndex] ?? absLimits[1]]
-
- this.x = xIndex === undefined ? 0 : round(scale(joystickState.axes[xIndex] ?? 0, -1, 1, xLimits[0], xLimits[1]), 0)
- this.y = yIndex === undefined ? 0 : round(scale(joystickState.axes[yIndex] ?? 0, -1, 1, yLimits[0], yLimits[1]), 0)
- this.z = zIndex === undefined ? 0 : round(scale(joystickState.axes[zIndex] ?? 0, -1, 1, zLimits[0], zLimits[1]), 0)
- this.r = rIndex === undefined ? 0 : round(scale(joystickState.axes[rIndex] ?? 0, -1, 1, rLimits[0], rLimits[1]), 0)
-
- this.buttons = buttons_int
- this.target = round(target, 0)
- }
-}
/**
* Possible other protocol functions
@@ -87,102 +14,13 @@ export enum OtherProtocol {
NO_FUNCTION = 'No function',
}
-/**
- * Possible Cockpit Actions
- */
-export enum CockpitAction {
- GO_TO_NEXT_VIEW = 'Go to next view',
- GO_TO_PREVIOUS_VIEW = 'Go to previous view',
- TOGGLE_FULL_SCREEN = 'Toggle full-screen',
- MAVLINK_ARM = 'Mavlink Command - Arm',
- MAVLINK_DISARM = 'Mavlink Command - Disarm',
-}
-
-export type CockpitActionCallback = () => void
-
-/**
- * Callback entry
- */
-interface CallbackEntry {
- /**
- * Unique ID for that callback register
- */
- action: CockpitAction
- /**
- * Callback to be called
- */
- callback: CockpitActionCallback
-}
-
-// @ts-ignore: Typescript does not get that we are initializing the object dinamically
-const actionsCallbacks: { [id in string]: CallbackEntry } = {}
-
-export const registerActionCallback = (action: CockpitAction, callback: CockpitActionCallback): string => {
- const id = uuid4()
- actionsCallbacks[id] = { action, callback }
- return id
-}
-export const unregisterActionCallback = (id: string): void => {
- delete actionsCallbacks[id]
-}
-
-export const sendCockpitActions = (joystickState: JoystickState, mapping: ProtocolControllerMapping): void => {
- const actionsToCallback: CockpitAction[] = []
- joystickState.buttons.forEach((state, idx) => {
- const mappedButton = mapping.buttonsCorrespondencies[idx]
- if (state && mappedButton.protocol === JoystickProtocol.CockpitAction) {
- actionsToCallback.push(mappedButton.value as CockpitAction)
- }
- })
- Object.values(actionsCallbacks).forEach((entry) => {
- if (actionsToCallback.includes(entry.action)) {
- entry.callback()
- }
- })
-}
-
-/**
- * Possible axes in the MAVLink protocol
- */
-export enum MAVLinkAxis {
- X = 'x',
- Y = 'y',
- Z = 'z',
- R = 'r',
-}
-const mavlinkAvailableAxes = Object.values(MAVLinkAxis)
-export const mavlinkAvailableButtons = sequentialArray(16)
-
-const mavlinkAxesLimits = [-1000, 1000]
-export const protocolAxesLimits = (protocol: JoystickProtocol): number[] => {
- switch (protocol) {
- case JoystickProtocol.MAVLinkManualControl:
- return mavlinkAxesLimits
- default:
- // Mavlink is the current main protocol and will be used by default
- return mavlinkAxesLimits
- }
-}
-
-export const allAvailableAxes: InputWithPrettyName[] = []
-mavlinkAvailableAxes.forEach((axis) =>
- allAvailableAxes.push({ input: { protocol: JoystickProtocol.MAVLinkManualControl, value: axis }, prettyName: axis })
-)
-
-Object.values(OtherProtocol).forEach((fn) =>
- allAvailableAxes.push({ input: { protocol: JoystickProtocol.Other, value: fn }, prettyName: fn })
-)
+export const allAvailableAxes: ProtocolAction[] = [
+ ...Object.values(mavlinkManualControlAxes),
+ ...Object.values(OtherProtocol).map((fn) => ({ protocol: JoystickProtocol.Other, id: fn, name: fn })),
+]
-export const allAvailableButtons: InputWithPrettyName[] = []
-mavlinkAvailableButtons.forEach((btn) =>
- allAvailableButtons.push({
- input: { protocol: JoystickProtocol.MAVLinkManualControl, value: btn },
- prettyName: btn.toString(),
- })
-)
-Object.values(CockpitAction).forEach((action) =>
- allAvailableButtons.push({ input: { protocol: JoystickProtocol.CockpitAction, value: action }, prettyName: action })
-)
-Object.values(OtherProtocol).forEach((fn) =>
- allAvailableButtons.push({ input: { protocol: JoystickProtocol.Other, value: fn }, prettyName: fn })
-)
+export const allAvailableButtons: ProtocolAction[] = [
+ ...Object.values(availableCockpitActions),
+ ...Object.values(mavlinkManualControlButtonFunctions),
+ ...Object.values(OtherProtocol).map((fn) => ({ protocol: JoystickProtocol.Other, id: fn, name: fn })),
+]
diff --git a/src/libs/joystick/protocols/cockpit-actions.ts b/src/libs/joystick/protocols/cockpit-actions.ts
new file mode 100644
index 000000000..2920ee212
--- /dev/null
+++ b/src/libs/joystick/protocols/cockpit-actions.ts
@@ -0,0 +1,84 @@
+/* eslint-disable vue/max-len */
+/* eslint-disable prettier/prettier */
+/* eslint-disable max-len */
+import { v4 as uuid4 } from 'uuid'
+
+import { type JoystickProtocolActionsMapping,type JoystickState, type ProtocolAction, JoystickButton,JoystickProtocol } from '@/types/joystick'
+
+/**
+ * Possible functions in the MAVLink `MANUAL_CONTROL` message protocol
+ */
+export enum CockpitActionsFunction {
+ go_to_next_view = 'go_to_next_view',
+ go_to_previous_view = 'go_to_previous_view',
+ toggle_full_screen = 'toggle_full_screen',
+ mavlink_arm = 'mavlink_arm',
+ mavlink_disarm = 'mavlink_disarm',
+}
+
+/**
+ * An action to be performed by Cockpit itself
+ */
+export class CockpitAction implements ProtocolAction {
+ id: CockpitActionsFunction
+ name: string
+ readonly protocol = JoystickProtocol.CockpitAction
+
+ // eslint-disable-next-line jsdoc/require-jsdoc
+ constructor(id: CockpitActionsFunction, name: string) {
+ this.id = id
+ this.name = name
+ }
+}
+
+// Available actions
+export const availableCockpitActions: { [key in CockpitActionsFunction]: CockpitAction } = {
+ [CockpitActionsFunction.go_to_next_view]: new CockpitAction(CockpitActionsFunction.go_to_next_view, 'Go to next view'),
+ [CockpitActionsFunction.go_to_previous_view]: new CockpitAction(CockpitActionsFunction.go_to_previous_view, 'Go to previous view'),
+ [CockpitActionsFunction.toggle_full_screen]: new CockpitAction(CockpitActionsFunction.toggle_full_screen, 'Toggle full screen'),
+ [CockpitActionsFunction.mavlink_arm]: new CockpitAction(CockpitActionsFunction.mavlink_arm, 'Mavlink arm'),
+ [CockpitActionsFunction.mavlink_disarm]: new CockpitAction(CockpitActionsFunction.mavlink_disarm, 'Mavlink disarm'),
+}
+
+export type CockpitActionCallback = () => void
+
+/**
+ * Callback entry
+ */
+interface CallbackEntry {
+ /**
+ * Unique ID for that callback register
+ */
+ action: CockpitAction
+ /**
+ * Callback to be called
+ */
+ callback: CockpitActionCallback
+}
+
+// @ts-ignore: Typescript does not get that we are initializing the object dinamically
+const actionsCallbacks: { [id in string]: CallbackEntry } = {}
+
+export const registerActionCallback = (action: CockpitAction, callback: CockpitActionCallback): string => {
+ const id = uuid4()
+ actionsCallbacks[id] = { action, callback }
+ return id
+}
+export const unregisterActionCallback = (id: string): void => {
+ delete actionsCallbacks[id]
+}
+
+export const sendCockpitActions = (joystickState: JoystickState, mapping: JoystickProtocolActionsMapping): void => {
+ const actionsToCallback: CockpitAction[] = []
+ joystickState.buttons.forEach((state) => {
+ const mappedButton = mapping.buttonsCorrespondencies[state as JoystickButton]
+ if (state && mappedButton?.action.protocol === JoystickProtocol.CockpitAction) {
+ actionsToCallback.push(mappedButton.action as CockpitAction)
+ }
+ })
+ Object.values(actionsCallbacks).forEach((entry) => {
+ if (actionsToCallback.includes(entry.action)) {
+ entry.callback()
+ }
+ })
+}
\ No newline at end of file
diff --git a/src/libs/joystick/protocols/mavlink-manual-control.ts b/src/libs/joystick/protocols/mavlink-manual-control.ts
new file mode 100644
index 000000000..c9e50ce43
--- /dev/null
+++ b/src/libs/joystick/protocols/mavlink-manual-control.ts
@@ -0,0 +1,129 @@
+/* eslint-disable prettier/prettier */
+/* eslint-disable vue/max-len */
+/* eslint-disable max-len */
+/* eslint-disable jsdoc/require-jsdoc */
+import { round, scale } from '@/libs/utils'
+import { type JoystickProtocolActionsMapping, type JoystickState, type ProtocolAction, InputType, JoystickAxis, JoystickProtocol, ProtocolControllerState } from '@/types/joystick'
+
+/**
+ * Possible axes in the MAVLink `MANUAL_CONTROL` message protocol
+ */
+export enum MAVLinkAxisFunction {
+ X = 'axis_x',
+ Y = 'axis_y',
+ Z = 'axis_z',
+ R = 'axis_r',
+}
+
+/**
+ * Possible functions in the MAVLink `MANUAL_CONTROL` message protocol
+ */
+export enum MAVLinkButtonFunction {
+ arm = 'arm',
+ disarm = 'disarm',
+ camera_tilt_up = 'camera_tilt_up',
+ camera_tilt_down = 'camera_tilt_down',
+ pilot_gain_up = 'pilot_gain_up',
+ pilot_gain_down = 'pilot_gain_down',
+ light_1_up = 'light_1_up',
+ light_1_down = 'light_1_down',
+}
+
+/**
+ * An axis action meant to be used with MAVLink's `MANUAL_CONTROL` message
+ */
+export class MAVLinkManualControlAxisAction implements ProtocolAction {
+ readonly protocol = JoystickProtocol.MAVLinkManualControl
+ /**
+ * Create an axis input
+ * @param {MAVLinkAxisFunction} id Axis identification
+ * @param {string} name Axis human-readable name
+ */
+ constructor(public id: MAVLinkAxisFunction, public name: string) {}
+}
+
+/**
+ * A button action meant to be used with MAVLink's `MANUAL_CONTROL` message
+ */
+export class MAVLinkManualControlButtonAction implements ProtocolAction {
+ readonly protocol = JoystickProtocol.MAVLinkManualControl
+ /**
+ * Create a button input
+ * @param {MAVLinkButtonFunction} id Button identification
+ * @param {string} name Button human-readable name
+ */
+ constructor(public id: MAVLinkButtonFunction, public name: string) {}
+}
+
+// Available axis actions
+export const mavlinkManualControlAxes: { [key in MAVLinkAxisFunction]: MAVLinkManualControlAxisAction } = {
+ [MAVLinkAxisFunction.X]: new MAVLinkManualControlAxisAction(MAVLinkAxisFunction.X, 'Axis X'),
+ [MAVLinkAxisFunction.Y]: new MAVLinkManualControlAxisAction(MAVLinkAxisFunction.Y, 'Axis Y'),
+ [MAVLinkAxisFunction.Z]: new MAVLinkManualControlAxisAction(MAVLinkAxisFunction.Z, 'Axis Z'),
+ [MAVLinkAxisFunction.R]: new MAVLinkManualControlAxisAction(MAVLinkAxisFunction.R, 'Axis R'),
+}
+
+// Available button actions
+export const mavlinkManualControlButtonFunctions: { [key in MAVLinkButtonFunction]: MAVLinkManualControlButtonAction } = {
+ [MAVLinkButtonFunction.arm]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.arm, 'Arm'),
+ [MAVLinkButtonFunction.disarm]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.disarm, 'Disarm'),
+ [MAVLinkButtonFunction.camera_tilt_up]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.camera_tilt_up, 'Tilt camera up'),
+ [MAVLinkButtonFunction.camera_tilt_down]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.camera_tilt_down, 'Tilt camera down'),
+ [MAVLinkButtonFunction.pilot_gain_up]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.pilot_gain_up, 'Pilot gain up'),
+ [MAVLinkButtonFunction.pilot_gain_down]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.pilot_gain_down, 'Pilot gain down'),
+ [MAVLinkButtonFunction.light_1_up]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.light_1_up, 'Light 1 up'),
+ [MAVLinkButtonFunction.light_1_down]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.light_1_down, 'Light 1 down'),
+}
+
+// Available actions (axes and buttons)
+export const availableMAVLinkManualControlActions = {
+ ...mavlinkManualControlAxes,
+ ...mavlinkManualControlButtonFunctions,
+}
+
+export class MavlinkControllerState extends ProtocolControllerState {
+ x: number
+ y: number
+ z: number
+ r: number
+ buttons: number
+ target: number
+ public static readonly BUTTONS_PER_BITFIELD = 16
+
+ /**
+ *
+ * @param { JoystickState } joystickState - Cockpit standard mapped values for the joystick
+ * @param { JoystickProtocolActionsMapping } mapping - Gamepad API to Protocols joystick mapping, where assignments and limits are got from.
+ * @param { number } target - Specify targeted vehicle ID.
+ */
+ constructor(joystickState: JoystickState, mapping: JoystickProtocolActionsMapping, target = 1) {
+ super()
+
+ const isMavlinkAction = (action: ProtocolAction): boolean => action.protocol === JoystickProtocol.MAVLinkManualControl
+
+ const buttons_int = 0
+ // for (let i = 0; i < MavlinkControllerState.BUTTONS_PER_BITFIELD; i++) {
+ // let buttonState = 0
+ // mapping.buttonsCorrespondencies.forEach((b, idx) => {
+ // if (isMavlinkAction(b.action) && Number(b.input.id.split('_')[1] || -1) === i && joystickState.buttons[idx]) {
+ // buttonState = 1
+ // }
+ // })
+ // buttons_int += buttonState * 2 ** i
+ // }
+
+ // const xCorrespondency = Object.entries(mapping.axesCorrespondencies).find(([, v]) => v.action === mavlinkManualControlAxes.axis_x)
+ // const yCorrespondency = Object.entries(mapping.axesCorrespondencies).find(([, v]) => v.action === mavlinkManualControlAxes.axis_y)
+ // const zCorrespondency = Object.entries(mapping.axesCorrespondencies).find(([, v]) => v.action === mavlinkManualControlAxes.axis_z)
+ // const rCorrespondency = Object.entries(mapping.axesCorrespondencies).find(([, v]) => v.action === mavlinkManualControlAxes.axis_r)
+
+ // this.x = xCorrespondency === undefined ? 0 : round(scale(joystickState.axes[xCorrespondency[0] as JoystickAxis] ?? 0, -1, 1, xCorrespondency.min, xCorrespondency.max), 0)
+ // this.y = yCorrespondency === undefined ? 0 : round(scale(joystickState.axes[yCorrespondency[0] as JoystickAxis] ?? 0, -1, 1, yCorrespondency.min, yCorrespondency.max), 0)
+ // this.z = zCorrespondency === undefined ? 0 : round(scale(joystickState.axes[zCorrespondency[0] as JoystickAxis] ?? 0, -1, 1, zCorrespondency.min, zCorrespondency.max), 0)
+ // this.r = rCorrespondency === undefined ? 0 : round(scale(joystickState.axes[rCorrespondency[0] as JoystickAxis] ?? 0, -1, 1, rCorrespondency.min, rCorrespondency.max), 0)
+
+ this.buttons = buttons_int
+ this.target = round(target, 0)
+ }
+}
+
diff --git a/src/libs/vehicle/ardupilot/ardupilot.ts b/src/libs/vehicle/ardupilot/ardupilot.ts
index b7624c5ff..0efdedbad 100644
--- a/src/libs/vehicle/ardupilot/ardupilot.ts
+++ b/src/libs/vehicle/ardupilot/ardupilot.ts
@@ -20,7 +20,7 @@ import {
} from '@/libs/connection/m2r/messages/mavlink2rest-enum'
import { MavFrame } from '@/libs/connection/m2r/messages/mavlink2rest-enum'
import { type Message } from '@/libs/connection/m2r/messages/mavlink2rest-message'
-import { MavlinkControllerState } from '@/libs/joystick/protocols'
+import { MavlinkControllerState } from '@/libs/joystick/protocols/mavlink-manual-control'
import { SignalTyped } from '@/libs/signal'
import { round } from '@/libs/utils'
import {
diff --git a/src/stores/controller.ts b/src/stores/controller.ts
index 9ac92c0bf..c2176798b 100644
--- a/src/stores/controller.ts
+++ b/src/stores/controller.ts
@@ -7,17 +7,20 @@ import { ref } from 'vue'
import { availableGamepadToCockpitMaps, cockpitStandardToProtocols } from '@/assets/joystick-profiles'
import { type JoystickEvent, EventType, joystickManager, JoystickModel } from '@/libs/joystick/manager'
import { allAvailableAxes, allAvailableButtons } from '@/libs/joystick/protocols'
-import { type JoystickState, type ProtocolControllerMapping, Joystick } from '@/types/joystick'
+import { type JoystickProtocolActionsMapping, type JoystickState, Joystick } from '@/types/joystick'
-export type controllerUpdateCallback = (state: JoystickState, protocolMapping: ProtocolControllerMapping) => void
+export type controllerUpdateCallback = (
+ state: JoystickState,
+ protocolActionsMapping: JoystickProtocolActionsMapping
+) => void
export const useControllerStore = defineStore('controller', () => {
const joysticks = ref
Could not stablish communication with the vehicle.
@@ -75,8 +71,7 @@
:b15="joystick.state.buttons[15]"
:b16="joystick.state.buttons[16]"
:b17="joystick.state.buttons[17]"
- :protocol-mapping="controllerStore.protocolMapping"
- :button-label-correspondency="controllerStore.availableProtocolButtonFunctions"
+ :buttons-actions-correspondency="controllerStore.protocolMapping.buttonsCorrespondencies"
@click="(e) => setCurrentInputs(joystick, e)"
/>
@@ -130,21 +125,18 @@
:b15="currentJoystick.state.buttons[15]"
:b16="currentJoystick.state.buttons[16]"
:b17="currentJoystick.state.buttons[17]"
- :protocol-mapping="controllerStore.protocolMapping"
- :button-label-correspondency="controllerStore.availableProtocolButtonFunctions"
+ :buttons-actions-correspondency="controllerStore.protocolMapping.buttonsCorrespondencies"
/>
-
-
-
{{
- [JoystickAxis.A0, JoystickAxis.A2].includes(Number(input.value))
- ? 'mdi-pan-horizontal'
- : 'mdi-pan-vertical'
+
+
+
+ {{
+ [JoystickAxis.A0, JoystickAxis.A2].includes(input.id) ? 'mdi-pan-horizontal' : 'mdi-pan-vertical'
}}
updateMapping(input.value, newValue, input.type)"
/>
-
+
Calibrate
@@ -198,27 +193,16 @@
Assign
-
+
{{ protocol }}
@@ -235,25 +219,24 @@