diff --git a/src/App.vue b/src/App.vue index 0011e48f0..33631e134 100644 --- a/src/App.vue +++ b/src/App.vue @@ -135,7 +135,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' @@ -161,7 +165,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')) @@ -194,7 +201,10 @@ watch([() => widgetStore.currentView, () => widgetStore.currentView.showBottomBa showBottomBarNow.value = widgetStore.currentView.showBottomBarOnBoot }) const debouncedToggleBottomBar = useDebounceFn(() => (showBottomBarNow.value = !showBottomBarNow.value), 25) -const bottomBarToggleCallbackId = registerActionCallback(CockpitAction.TOGGLE_BOTTOM_BAR, debouncedToggleBottomBar) +const bottomBarToggleCallbackId = registerActionCallback( + availableCockpitActions.toggle_bottom_bar, + debouncedToggleBottomBar +) onBeforeUnmount(() => unregisterActionCallback(bottomBarToggleCallbackId)) diff --git a/src/assets/joystick-profiles.ts b/src/assets/joystick-profiles.ts index 8747fb1db..70a7af1cd 100644 --- a/src/assets/joystick-profiles.ts +++ b/src/assets/joystick-profiles.ts @@ -1,38 +1,46 @@ import { JoystickModel } from '@/libs/joystick/manager' -import { CockpitAction, MAVLinkAxis } from '@/libs/joystick/protocols' -import { type GamepadToCockpitStdMapping, type ProtocolControllerMapping, JoystickProtocol } from '@/types/joystick' +import { otherAvailableActions } from '@/libs/joystick/protocols' +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['Mount tilt up'] }, + [JoystickButton.B3]: { action: mavlinkManualControlButtonFunctions['Mount tilt down'] }, + [JoystickButton.B4]: { action: availableCockpitActions.go_to_previous_view }, + [JoystickButton.B5]: { action: availableCockpitActions.go_to_next_view }, + [JoystickButton.B6]: { action: mavlinkManualControlButtonFunctions['Gain inc'] }, + [JoystickButton.B7]: { action: mavlinkManualControlButtonFunctions['Gain dec'] }, + [JoystickButton.B8]: { action: mavlinkManualControlButtonFunctions['Lights1 brighter'] }, + [JoystickButton.B9]: { action: mavlinkManualControlButtonFunctions['Lights1 dimmer'] }, + [JoystickButton.B10]: { action: availableCockpitActions.toggle_full_screen }, + [JoystickButton.B11]: { action: otherAvailableActions.no_function }, + [JoystickButton.B12]: { action: otherAvailableActions.no_function }, + [JoystickButton.B13]: { action: otherAvailableActions.no_function }, + [JoystickButton.B14]: { action: otherAvailableActions.no_function }, + [JoystickButton.B15]: { action: otherAvailableActions.no_function }, + [JoystickButton.B16]: { action: otherAvailableActions.no_function }, + [JoystickButton.B17]: { action: otherAvailableActions.no_function }, + }, } /** diff --git a/src/components/joysticks/JoystickPS.vue b/src/components/joysticks/JoystickPS.vue index 6f7b80a03..2f51bd0d3 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 = buttonsActionsCorrespondency.value[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 896f2e2ba..074f31500 100644 --- a/src/libs/joystick/protocols.ts +++ b/src/libs/joystick/protocols.ts @@ -1,189 +1,34 @@ -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 */ export enum OtherProtocol { - NO_FUNCTION = 'No function', + 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', - TOGGLE_BOTTOM_BAR = 'Toggle bottom bar', +export const otherAvailableActions: { [key in OtherProtocol]: ProtocolAction } = { + [OtherProtocol.no_function]: { + protocol: JoystickProtocol.Other, + id: OtherProtocol.no_function, + name: 'No function', + }, } -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..25c355ed2 --- /dev/null +++ b/src/libs/joystick/protocols/cockpit-actions.ts @@ -0,0 +1,86 @@ +/* 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', + toggle_bottom_bar = 'toggle_bottom_bar', +} + +/** + * 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'), + [CockpitActionsFunction.toggle_bottom_bar]: new CockpitAction(CockpitActionsFunction.toggle_bottom_bar, 'Toggle bottom bar'), +} + +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..781318eee --- /dev/null +++ b/src/libs/joystick/protocols/mavlink-manual-control.ts @@ -0,0 +1,395 @@ +/* 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, JoystickButton } 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 { + disabled = 'Disabled', // 0 + shift = 'Shift', // 1 + arm_toggle = 'Arm toggle', // 2 + arm = 'Arm', // 3 + disarm = 'Disarm', // 4 + mode_manual = 'Mode manual', // 5 + mode_stabilize = 'Mode stabilize', // 6 + mode_depth_hold = 'Mode depth hold', // 7 + mode_poshold = 'Mode poshold', // 8 + mode_auto = 'Mode auto', // 9 + mode_circle = 'Mode circle', // 10 + mode_guided = 'Mode guided', // 11 + mode_acro = 'Mode acro', // 12 + mount_center = 'Mount center', // 21 + mount_tilt_up = 'Mount tilt up', // 22 + mount_tilt_down = 'Mount tilt down', // 23 + camera_trigger = 'Camera trigger', // 24 + camera_source_toggle = 'Camera source toggle', // 25 + mount_pan_right = 'Mount pan right', // 26 + mount_pan_left = 'Mount pan left', // 27 + lights1_cycle = 'Lights1 cycle', // 31 + lights1_brighter = 'Lights1 brighter', // 32 + lights1_dimmer = 'Lights1 dimmer', // 33 + lights2_cycle = 'Lights2 cycle', // 34 + lights2_brighter = 'Lights2 brighter', // 35 + lights2_dimmer = 'Lights2 dimmer', // 36 + gain_toggle = 'Gain toggle', // 41 + gain_inc = 'Gain inc', // 42 + gain_dec = 'Gain dec', // 43 + trim_roll_inc = 'Trim roll inc', // 44 + trim_roll_dec = 'Trim roll dec', // 45 + trim_pitch_inc = 'Trim pitch inc', // 46 + trim_pitch_dec = 'Trim pitch dec', // 47 + input_hold_set = 'Input hold set', // 48 + roll_pitch_toggle = 'Roll pitch toggle', // 49 + relay1_on = 'Relay 1 on', // 51 + relay1_off = 'Relay 1 off', // 52 + relay1_toggle = 'Relay 1 toggle', // 53 + relay2_on = 'Relay 2 on', // 54 + relay2_off = 'Relay 2 off', // 55 + relay2_toggle = 'Relay 2 toggle', // 56 + relay3_on = 'Relay 3 on', // 57 + relay3_off = 'Relay 3 off', // 58 + relay3_toggle = 'Relay 3 toggle', // 59 + servo1_inc = 'Servo 1 inc', // 61 + servo1_dec = 'Servo 1 dec', // 62 + servo1_min = 'Servo 1 min', // 63 + servo1_max = 'Servo 1 max', // 64 + servo1_center = 'Servo 1 center', // 65 + servo2_inc = 'Servo 2 inc', // 66 + servo2_dec = 'Servo 2 dec', // 67 + servo2_min = 'Servo 2 min', // 68 + servo2_max = 'Servo 2 max', // 69 + servo2_center = 'Servo 2 center', // 70 + servo3_inc = 'Servo 3 inc', // 71 + servo3_dec = 'Servo 3 dec', // 72 + servo3_min = 'Servo 3 min', // 73 + servo3_max = 'Servo 3 max', // 74 + servo3_center = 'Servo 3 center', // 75 + servo1_min_momentary = 'Servo 1 min momentary', // 76 + servo1_max_momentary = 'Servo 1 max momentary', // 77 + servo1_min_toggle = 'Servo 1 min toggle', // 78 + servo1_max_toggle = 'Servo 1 max toggle', // 79 + servo2_min_momentary = 'Servo 2 min momentary', // 80 + servo2_max_momentary = 'Servo 2 max momentary', // 81 + servo2_min_toggle = 'Servo 2 min toggle', // 82 + servo2_max_toggle = 'Servo 2 max toggle', // 83 + servo3_min_momentary = 'Servo 3 min momentary', // 84 + servo3_max_momentary = 'Servo 3 max momentary', // 85 + servo3_min_toggle = 'Servo 3 min toggle', // 86 + servo3_max_toggle = 'Servo 3 max toggle', // 87 + custom1 = 'Custom 1', // 91 + custom2 = 'Custom 2', // 92 + custom3 = 'Custom 3', // 93 + custom4 = 'Custom 4', // 94 + custom5 = 'Custom 5', // 95 + custom6 = 'Custom 6', // 96 + relay4_on = 'Relay 4 on', // 101 + relay4_off = 'Relay 4 off', // 102 + relay4_toggle = 'Relay 4 toggle', // 103 + relay1_momentary = 'Relay 1 momentary', // 104 + relay2_momentary = 'Relay 2 momentary', // 105 + relay3_momentary = 'Relay 3 momentary', // 106 + relay4_momentary = 'Relay 4 momentary', // 107 +} + +export enum MAVLinkManualControlButton { + R0 = 'BTN0_FUNCTION', + S0 = 'BTN0_SFUNCTION', + R1 = 'BTN1_FUNCTION', + S1 = 'BTN1_SFUNCTION', + R2 = 'BTN2_FUNCTION', + S2 = 'BTN2_SFUNCTION', + R3 = 'BTN3_FUNCTION', + S3 = 'BTN3_SFUNCTION', + R4 = 'BTN4_FUNCTION', + S4 = 'BTN4_SFUNCTION', + R5 = 'BTN5_FUNCTION', + S5 = 'BTN5_SFUNCTION', + R6 = 'BTN6_FUNCTION', + S6 = 'BTN6_SFUNCTION', + R7 = 'BTN7_FUNCTION', + S7 = 'BTN7_SFUNCTION', + R8 = 'BTN8_FUNCTION', + S8 = 'BTN8_SFUNCTION', + R9 = 'BTN9_FUNCTION', + S9 = 'BTN9_SFUNCTION', + R10 = 'BTN10_FUNCTION', + S10 = 'BTN10_SFUNCTION', + R11 = 'BTN11_FUNCTION', + S11 = 'BTN11_SFUNCTION', + R12 = 'BTN12_FUNCTION', + S12 = 'BTN12_SFUNCTION', + R13 = 'BTN13_FUNCTION', + S13 = 'BTN13_SFUNCTION', + R14 = 'BTN14_FUNCTION', + S14 = 'BTN14_SFUNCTION', + R15 = 'BTN15_FUNCTION', + S15 = 'BTN15_SFUNCTION', +} + +const shiftActivatedButtons = (): MAVLinkManualControlButton[] => { + return Object.entries(MAVLinkManualControlButton).filter((btn) => btn[1].includes('SFUNCTION')).map((btn) => btn[0] as MAVLinkManualControlButton) +} + +const manualControlButtonFromParameterName = (name: string): MAVLinkManualControlButton | undefined => { + const button = Object.entries(MAVLinkManualControlButton).find((entry) => entry[1] === name)?.[0] + return button === undefined ? button : button as MAVLinkManualControlButton +} + +/** + * 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.disabled]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.disabled, 'Disabled'), + [MAVLinkButtonFunction.shift]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.shift, 'Shift'), + [MAVLinkButtonFunction.arm_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.arm_toggle, 'Arm toggle'), + [MAVLinkButtonFunction.arm]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.arm, 'Arm'), + [MAVLinkButtonFunction.disarm]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.disarm, 'Disarm'), + [MAVLinkButtonFunction.mode_manual]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mode_manual, 'Mode manual'), + [MAVLinkButtonFunction.mode_stabilize]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mode_stabilize, 'Mode stabilize'), + [MAVLinkButtonFunction.mode_depth_hold]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mode_depth_hold, 'Mode depth hold'), + [MAVLinkButtonFunction.mode_poshold]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mode_poshold, 'Mode poshold'), + [MAVLinkButtonFunction.mode_auto]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mode_auto, 'Mode auto'), + [MAVLinkButtonFunction.mode_circle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mode_circle, 'Mode circle'), + [MAVLinkButtonFunction.mode_guided]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mode_guided, 'Mode guided'), + [MAVLinkButtonFunction.mode_acro]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mode_acro, 'Mode acro'), + [MAVLinkButtonFunction.mount_center]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mount_center, 'Mount center'), + [MAVLinkButtonFunction.mount_tilt_up]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mount_tilt_up, 'Mount tilt up'), + [MAVLinkButtonFunction.mount_tilt_down]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mount_tilt_down, 'Mount tilt down'), + [MAVLinkButtonFunction.camera_trigger]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.camera_trigger, 'Camera trigger'), + [MAVLinkButtonFunction.camera_source_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.camera_source_toggle, 'Camera source toggle'), + [MAVLinkButtonFunction.mount_pan_right]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mount_pan_right, 'Mount pan right'), + [MAVLinkButtonFunction.mount_pan_left]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.mount_pan_left, 'Mount pan left'), + [MAVLinkButtonFunction.lights1_cycle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.lights1_cycle, 'Lights1 cycle'), + [MAVLinkButtonFunction.lights1_brighter]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.lights1_brighter, 'Lights1 brighter'), + [MAVLinkButtonFunction.lights1_dimmer]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.lights1_dimmer, 'Lights1 dimmer'), + [MAVLinkButtonFunction.lights2_cycle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.lights2_cycle, 'Lights2 cycle'), + [MAVLinkButtonFunction.lights2_brighter]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.lights2_brighter, 'Lights2 brighter'), + [MAVLinkButtonFunction.lights2_dimmer]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.lights2_dimmer, 'Lights2 dimmer'), + [MAVLinkButtonFunction.gain_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.gain_toggle, 'Gain toggle'), + [MAVLinkButtonFunction.gain_inc]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.gain_inc, 'Gain inc'), + [MAVLinkButtonFunction.gain_dec]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.gain_dec, 'Gain dec'), + [MAVLinkButtonFunction.trim_roll_inc]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.trim_roll_inc, 'Trim roll inc'), + [MAVLinkButtonFunction.trim_roll_dec]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.trim_roll_dec, 'Trim roll dec'), + [MAVLinkButtonFunction.trim_pitch_inc]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.trim_pitch_inc, 'Trim pitch inc'), + [MAVLinkButtonFunction.trim_pitch_dec]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.trim_pitch_dec, 'Trim pitch dec'), + [MAVLinkButtonFunction.input_hold_set]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.input_hold_set, 'Input hold set'), + [MAVLinkButtonFunction.roll_pitch_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.roll_pitch_toggle, 'Roll pitch toggle'), + [MAVLinkButtonFunction.relay1_on]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay1_on, 'Relay 1 on'), + [MAVLinkButtonFunction.relay1_off]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay1_off, 'Relay 1 off'), + [MAVLinkButtonFunction.relay1_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay1_toggle, 'Relay 1 toggle'), + [MAVLinkButtonFunction.relay2_on]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay2_on, 'Relay 2 on'), + [MAVLinkButtonFunction.relay2_off]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay2_off, 'Relay 2 off'), + [MAVLinkButtonFunction.relay2_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay2_toggle, 'Relay 2 toggle'), + [MAVLinkButtonFunction.relay3_on]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay3_on, 'Relay 3 on'), + [MAVLinkButtonFunction.relay3_off]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay3_off, 'Relay 3 off'), + [MAVLinkButtonFunction.relay3_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay3_toggle, 'Relay 3 toggle'), + [MAVLinkButtonFunction.servo1_inc]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_inc, 'Servo 1 inc'), + [MAVLinkButtonFunction.servo1_dec]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_dec, 'Servo 1 dec'), + [MAVLinkButtonFunction.servo1_min]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_min, 'Servo 1 min'), + [MAVLinkButtonFunction.servo1_max]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_max, 'Servo 1 max'), + [MAVLinkButtonFunction.servo1_center]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_center, 'Servo 1 center'), + [MAVLinkButtonFunction.servo2_inc]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_inc, 'Servo 2 inc'), + [MAVLinkButtonFunction.servo2_dec]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_dec, 'Servo 2 dec'), + [MAVLinkButtonFunction.servo2_min]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_min, 'Servo 2 min'), + [MAVLinkButtonFunction.servo2_max]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_max, 'Servo 2 max'), + [MAVLinkButtonFunction.servo2_center]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_center, 'Servo 2 center'), + [MAVLinkButtonFunction.servo3_inc]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_inc, 'Servo 3 inc'), + [MAVLinkButtonFunction.servo3_dec]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_dec, 'Servo 3 dec'), + [MAVLinkButtonFunction.servo3_min]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_min, 'Servo 3 min'), + [MAVLinkButtonFunction.servo3_max]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_max, 'Servo 3 max'), + [MAVLinkButtonFunction.servo3_center]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_center, 'Servo 3 center'), + [MAVLinkButtonFunction.servo1_min_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_min_momentary, 'Servo 1 min momentary'), + [MAVLinkButtonFunction.servo1_max_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_max_momentary, 'Servo 1 max momentary'), + [MAVLinkButtonFunction.servo1_min_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_min_toggle, 'Servo 1 min toggle'), + [MAVLinkButtonFunction.servo1_max_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo1_max_toggle, 'Servo 1 max toggle'), + [MAVLinkButtonFunction.servo2_min_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_min_momentary, 'Servo 2 min momentary'), + [MAVLinkButtonFunction.servo2_max_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_max_momentary, 'Servo 2 max momentary'), + [MAVLinkButtonFunction.servo2_min_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_min_toggle, 'Servo 2 min toggle'), + [MAVLinkButtonFunction.servo2_max_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo2_max_toggle, 'Servo 2 max toggle'), + [MAVLinkButtonFunction.servo3_min_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_min_momentary, 'Servo 3 min momentary'), + [MAVLinkButtonFunction.servo3_max_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_max_momentary, 'Servo 3 max momentary'), + [MAVLinkButtonFunction.servo3_min_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_min_toggle, 'Servo 3 min toggle'), + [MAVLinkButtonFunction.servo3_max_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.servo3_max_toggle, 'Servo 3 max toggle'), + [MAVLinkButtonFunction.custom1]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.custom1, 'Custom 1'), + [MAVLinkButtonFunction.custom2]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.custom2, 'Custom 2'), + [MAVLinkButtonFunction.custom3]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.custom3, 'Custom 3'), + [MAVLinkButtonFunction.custom4]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.custom4, 'Custom 4'), + [MAVLinkButtonFunction.custom5]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.custom5, 'Custom 5'), + [MAVLinkButtonFunction.custom6]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.custom6, 'Custom 6'), + [MAVLinkButtonFunction.relay4_on]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay4_on, 'Relay 4 on'), + [MAVLinkButtonFunction.relay4_off]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay4_off, 'Relay 4 off'), + [MAVLinkButtonFunction.relay4_toggle]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay4_toggle, 'Relay 4 toggle'), + [MAVLinkButtonFunction.relay1_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay1_momentary, 'Relay 1 momentary'), + [MAVLinkButtonFunction.relay2_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay2_momentary, 'Relay 2 momentary'), + [MAVLinkButtonFunction.relay3_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay3_momentary, 'Relay 3 momentary'), + [MAVLinkButtonFunction.relay4_momentary]: new MAVLinkManualControlButtonAction(MAVLinkButtonFunction.relay4_momentary, 'Relay 4 momentary'), +} + +// 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 { { title: string, value: number } } buttonParameterTable - Gamepad API to Protocols joystick mapping, where assignments and limits are got from. + * @param { { [key in string]: number } } currentParameters - 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, + buttonParameterTable: { title: string, value: number }[], + currentParameters: { [key in string]: number }, + target = 1 + ) { + super() + + const isMavlinkAction = (action: ProtocolAction): boolean => action.protocol === JoystickProtocol.MAVLinkManualControl + + let buttons_int = 0 + + if (buttonParameterTable.length !== 0 && Object.entries(currentParameters).length !== 0) { + console.log('lets create a mavlink manual control package') + + // Calculate buttons + + // console.log(joystickState) + // console.log(mapping) + // console.log(buttonParameterTable) + // console.log(currentParameters) + + const buttonParametersNamedObject: { [key in number]: string } = {} + buttonParameterTable.forEach((entry) => buttonParametersNamedObject[entry.value] = entry.title) + + // console.log(buttonParametersNamedObject) + + const currentButtonParameters = Object.entries(currentParameters).filter(([k,]) => k.includes('BTN')) + + // console.log(currentButtonParameters) + + const buttonActionIdTable = currentButtonParameters.map((btn) => ({ button: btn[0], actionId: buttonParametersNamedObject[btn[1]]})) + + // console.log(buttonActionIdTable) + + const maybeShiftButton = buttonActionIdTable.find((entry) => entry.actionId as MAVLinkButtonFunction === mavlinkManualControlButtonFunctions['Shift'].id) + let shiftButton = undefined + if (maybeShiftButton !== undefined) { + shiftButton = manualControlButtonFromParameterName(maybeShiftButton.button) + } + + if (shiftButton === undefined) { + // map shift to some button + } else { + // we are fine + } + + const actionsBeingActivated = joystickState.buttons + .map((btnState, idx) => ({ id: idx, value: btnState})) + .filter((btn) => btn.value ?? 0 > 0.5) + .map((btn) => mapping.buttonsCorrespondencies[btn.id as JoystickButton].action) + console.log('shift button: ', shiftButton) + console.log('actions activated', actionsBeingActivated.map((action) => action.name)) + + const vehicleButtonsToActivate = buttonActionIdTable.filter((entry) => actionsBeingActivated.map((action) => action.id).includes(entry.actionId as MAVLinkButtonFunction)).map((entry) => manualControlButtonFromParameterName(entry.button)) + console.log('buttons activated', vehicleButtonsToActivate) + const useShift = shiftActivatedButtons().filter((btn) => vehicleButtonsToActivate.includes(btn)).length > 0 + console.log('use shift?', useShift) + + for (let i = 0; i < MavlinkControllerState.BUTTONS_PER_BITFIELD; i++) { + let buttonState = 0 + if (shiftButton !== undefined && Number(shiftButton.replace('R', '').replace('S', '')) === i && useShift) { + buttonState = 1 + } + vehicleButtonsToActivate.forEach((btn) => { + if (btn !== undefined && Number(btn.replace('R', '').replace('S', '')) === i) { + buttonState = 1 + } + }) + buttons_int += buttonState * 2 ** i + } + + // Calculate axes + + const xCorrespondency = Object.entries(mapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_x.id) + const yCorrespondency = Object.entries(mapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_y.id) + const zCorrespondency = Object.entries(mapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_z.id) + const rCorrespondency = Object.entries(mapping.axesCorrespondencies).find((entry) => entry[1].action.protocol === JoystickProtocol.MAVLinkManualControl && entry[1].action.id === mavlinkManualControlAxes.axis_r.id) + + this.x = xCorrespondency === undefined ? 0 : round(scale(joystickState.axes[xCorrespondency[0] as unknown as JoystickAxis] ?? 0, -1, 1, xCorrespondency[1].min, xCorrespondency[1].max), 0) + this.y = yCorrespondency === undefined ? 0 : round(scale(joystickState.axes[yCorrespondency[0] as unknown as JoystickAxis] ?? 0, -1, 1, yCorrespondency[1].min, yCorrespondency[1].max), 0) + this.z = zCorrespondency === undefined ? 0 : round(scale(joystickState.axes[zCorrespondency[0] as unknown as JoystickAxis] ?? 0, -1, 1, zCorrespondency[1].min, zCorrespondency[1].max), 0) + this.r = rCorrespondency === undefined ? 0 : round(scale(joystickState.axes[rCorrespondency[0] as unknown as JoystickAxis] ?? 0, -1, 1, rCorrespondency[1].min, rCorrespondency[1].max), 0) + } else { + console.log('not enough information to create a mavlink manual control package') + } + + console.log('buttons', buttons_int) + console.log('x', this.x) + console.log('y', this.y) + console.log('z', this.z) + console.log('r', this.r) + + 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>(new Map()) const updateCallbacks = ref([]) - const protocolMapping = useStorage('cockpit-protocol-mapping-v3', cockpitStandardToProtocols) + const protocolMapping = useStorage('cockpit-protocol-mapping-v4', cockpitStandardToProtocols) const cockpitStdMappings = useStorage('cockpit-standard-mappings', availableGamepadToCockpitMaps) - const availableProtocolAxesFunctions = allAvailableAxes - const availableProtocolButtonFunctions = allAvailableButtons + const availableAxesActions = allAvailableAxes + const availableButtonActions = allAvailableButtons const enableForwarding = ref(true) const registerControllerUpdateCallback = (callback: controllerUpdateCallback): void => { @@ -91,8 +94,8 @@ export const useControllerStore = defineStore('controller', () => { joysticks, protocolMapping, cockpitStdMappings, - availableProtocolAxesFunctions, - availableProtocolButtonFunctions, + availableAxesActions, + availableButtonActions, downloadJoystickProfile, loadJoystickProfile, } diff --git a/src/stores/mainVehicle.ts b/src/stores/mainVehicle.ts index 325dacce6..2c3e8ef01 100644 --- a/src/stores/mainVehicle.ts +++ b/src/stores/mainVehicle.ts @@ -9,13 +9,12 @@ import type { Package } from '@/libs/connection/m2r/messages/mavlink2rest' import { MavAutopilot, MAVLinkType, MavType } from '@/libs/connection/m2r/messages/mavlink2rest-enum' import type { Message } from '@/libs/connection/m2r/messages/mavlink2rest-message' import { - type InputWithPrettyName, - CockpitAction, - MavlinkControllerState, + availableCockpitActions, registerActionCallback, sendCockpitActions, unregisterActionCallback, -} from '@/libs/joystick/protocols' +} from '@/libs/joystick/protocols/cockpit-actions' +import { MavlinkControllerState } from '@/libs/joystick/protocols/mavlink-manual-control' import type { ArduPilot } from '@/libs/vehicle/ardupilot/ardupilot' import * as arducopter_metadata from '@/libs/vehicle/ardupilot/ParameterRepository/Copter-4.3/apm.pdef.json' import * as arduplane_metadata from '@/libs/vehicle/ardupilot/ParameterRepository/Plane-4.3/apm.pdef.json' @@ -39,8 +38,9 @@ import * as Vehicle from '@/libs/vehicle/vehicle' import { VehicleFactory } from '@/libs/vehicle/vehicle-factory' import { type MetadataFile } from '@/types/ardupilot-metadata' import { + type JoystickProtocolActionsMapping, type JoystickState, - type ProtocolControllerMapping, + type ProtocolAction, JoystickProtocol, ProtocolControllerState, } from '@/types/joystick' @@ -329,8 +329,8 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => { }, setFlightMode: setFlightMode, } - const mavlinkArmId = registerActionCallback(CockpitAction.MAVLINK_ARM, arm) - const mavlinkDisarmId = registerActionCallback(CockpitAction.MAVLINK_DISARM, disarm) + const mavlinkArmId = registerActionCallback(availableCockpitActions.mavlink_arm, arm) + const mavlinkDisarmId = registerActionCallback(availableCockpitActions.mavlink_disarm, disarm) onBeforeUnmount(() => { unregisterActionCallback(mavlinkArmId) unregisterActionCallback(mavlinkDisarmId) @@ -376,8 +376,8 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => { const controllerStore = useControllerStore() const currentControllerState = ref() - const currentProtocolMapping = ref() - const updateCurrentControllerState = (newState: JoystickState, newMapping: ProtocolControllerMapping): void => { + const currentProtocolMapping = ref() + const updateCurrentControllerState = (newState: JoystickState, newMapping: JoystickProtocolActionsMapping): void => { currentControllerState.value = newState currentProtocolMapping.value = newMapping } @@ -386,7 +386,12 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => { // Loop to send MAVLink Manual Control messages setInterval(() => { if (!currentControllerState.value || !currentProtocolMapping.value || controllerStore.joysticks.size === 0) return - const newControllerState = new MavlinkControllerState(currentControllerState.value, currentProtocolMapping.value) + const newControllerState = new MavlinkControllerState( + currentControllerState.value, + currentProtocolMapping.value, + buttonParameterTable, + currentParameters + ) if (controllerStore.enableForwarding) { sendManualControl(newControllerState) } @@ -421,7 +426,6 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => { const updateMavlinkButtonsPrettyNames = (): void => { if (!currentParameters || !parametersTable) return - const newMavlinkButtonsNames: InputWithPrettyName[] = [] buttonParameterTable.splice(0) // @ts-ignore: This type is huge. Needs refactoring typing here. if (parametersTable['BTN0_FUNCTION'] && parametersTable['BTN0_FUNCTION']['Values']) { @@ -431,23 +435,7 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => { const formatedText = capitalize(rawText).replace(new RegExp('_', 'g'), ' ') buttonParameterTable.push({ title: formatedText as string, value: Number(param[0]) }) }) - Object.entries(currentParameters).forEach((param) => { - if (!param[0].startsWith('BTN') || !param[0].endsWith('_FUNCTION')) return - const buttonId = Number(param[0].replace('BTN', '').replace('_FUNCTION', '')) - const functionName = buttonParameterTable.find((p) => p.value === param[1])?.title - if (functionName === undefined) return - newMavlinkButtonsNames.push({ - input: { protocol: JoystickProtocol.MAVLinkManualControl, value: buttonId }, - prettyName: functionName, - }) - }) } - if (newMavlinkButtonsNames.isEmpty()) return - let newProtocolButtonsFunctions = controllerStore.availableProtocolButtonFunctions.filter((btn) => { - return btn.input.protocol !== JoystickProtocol.MAVLinkManualControl - }) - newProtocolButtonsFunctions = newProtocolButtonsFunctions.concat(newMavlinkButtonsNames) - controllerStore.availableProtocolButtonFunctions = newProtocolButtonsFunctions } setInterval(() => updateMavlinkButtonsPrettyNames(), 1000) diff --git a/src/stores/widgetManager.ts b/src/stores/widgetManager.ts index cdf1a483a..c16013bd1 100644 --- a/src/stores/widgetManager.ts +++ b/src/stores/widgetManager.ts @@ -10,7 +10,11 @@ import { computed, onBeforeMount, onBeforeUnmount, ref, watch } from 'vue' import { widgetProfiles } from '@/assets/defaults' import { miniWidgetsProfile } from '@/assets/defaults' import * as Words from '@/libs/funny-name/words' -import { CockpitAction, registerActionCallback, unregisterActionCallback } from '@/libs/joystick/protocols' +import { + availableCockpitActions, + registerActionCallback, + unregisterActionCallback, +} from '@/libs/joystick/protocols/cockpit-actions' import { isEqual } from '@/libs/utils' import type { Point2D, SizeRect2D } from '@/types/general' import type { MiniWidget, MiniWidgetContainer } from '@/types/miniWidgets' @@ -429,7 +433,10 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => { selectView(currentProfile.value.views[newIndex]) } const debouncedSelectNextView = useDebounceFn(() => selectNextView(), 10) - const selectNextViewCallbackId = registerActionCallback(CockpitAction.GO_TO_NEXT_VIEW, debouncedSelectNextView) + const selectNextViewCallbackId = registerActionCallback( + availableCockpitActions.go_to_next_view, + debouncedSelectNextView + ) onBeforeUnmount(() => unregisterActionCallback(selectNextViewCallbackId)) const selectPreviousView = (): void => { @@ -437,7 +444,10 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => { selectView(currentProfile.value.views[newIndex]) } const debouncedSelectPreviousView = useDebounceFn(() => selectPreviousView(), 10) - const selectPrevViewCBId = registerActionCallback(CockpitAction.GO_TO_PREVIOUS_VIEW, debouncedSelectPreviousView) + const selectPrevViewCBId = registerActionCallback( + availableCockpitActions.go_to_previous_view, + debouncedSelectPreviousView + ) onBeforeUnmount(() => unregisterActionCallback(selectPrevViewCBId)) // Profile migrations diff --git a/src/types/joystick.ts b/src/types/joystick.ts index 20ce0af5d..1f8b6357c 100644 --- a/src/types/joystick.ts +++ b/src/types/joystick.ts @@ -68,43 +68,77 @@ export class Joystick { /** * */ -export interface ProtocolInput { +export interface ProtocolAction { /** - * Protocol which this input is used to + * Protocol that holds the action */ protocol: JoystickProtocol /** - * Value for that input + * Action identification */ - value: number | string + id: string | number + /** + * Human-readable name for the action + */ + name: string } /** - * Interface that represents the necessary information for mapping a Gamepad API controller to a specific protocol. + * Correspondency between the hardware axis input and the protocol action that should be triggered by it */ -export interface ProtocolControllerMapping { +export type JoystickAxisActionCorrespondency = { /** - * Name to help identification of a mapping profile + * The ID of the axis that holds the correspondent action */ - name: string + [key in JoystickAxis]: { + /** + * The protocol action that should be triggered + */ + action: ProtocolAction + /** + * The + */ + min: number + /** + * Maximum axis value + */ + max: number + } +} + +/** + * Correspondency between the hardware button input and the protocol action that should be triggered by it + */ +export type JoystickButtonActionCorrespondency = { /** - * Values to which each Gamepad API axis state of -1 should be mapped to + * The ID of the button that holds the correspondent action */ - axesMins: number[] + [key in JoystickButton]: { + /** + * The protocol action that should be triggered + */ + action: ProtocolAction + } +} + +/** + * Interface that represents the necessary information for mapping a Gamepad API controller to a specific protocol. + */ +export interface JoystickProtocolActionsMapping { /** - * Values to which each Gamepad API axis state of 1 should be mapped to + * Name to help identification of a mapping profile */ - axesMaxs: number[] + name: string /** * Correspondency from Gamepad API to protocol axis. * Corresponds to which Axis in the protocol should the Nth axis be mapped to. */ - axesCorrespondencies: ProtocolInput[] + axesCorrespondencies: JoystickAxisActionCorrespondency /** * Correspondency from Gamepad API to protocol button. * Corresponds to which button in the protocol should the Nth button be mapped to. */ - buttonsCorrespondencies: ProtocolInput[] + buttonsCorrespondencies: JoystickButtonActionCorrespondency } export type CockpitButton = null | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 // eslint-disable-line @@ -202,7 +236,31 @@ export interface JoystickInput { */ type: InputType.Axis | InputType.Button /** - * Input value + * Input identification + */ + id: JoystickAxis | JoystickButton +} + +/** + * Joystick button input + */ +export class JoystickButtonInput implements JoystickInput { + readonly type = InputType.Button + /** + * Create an axis input + * @param {JoystickAxis} id Axis identification + */ + constructor(public id: JoystickButton) {} +} + +/** + * Joystick axis input + */ +export class JoystickAxisInput implements JoystickInput { + readonly type = InputType.Axis + /** + * Create an axis input + * @param {JoystickAxis} id Axis identification */ - value: JoystickAxis | JoystickButton + constructor(public id: JoystickAxis) {} } diff --git a/src/views/ConfigurationJoystickView.vue b/src/views/ConfigurationJoystickView.vue index 5bcf9e7cb..8b6f5d52d 100644 --- a/src/views/ConfigurationJoystickView.vue +++ b/src/views/ConfigurationJoystickView.vue @@ -32,11 +32,7 @@

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' }}
-
+
Calibrate

@@ -198,27 +193,24 @@

Assign
-
+
{{ protocol }}
@@ -234,25 +226,26 @@