diff --git a/src/assets/joystick-profiles.ts b/src/assets/joystick-profiles.ts index 1c289de24..fbfcd1c9c 100644 --- a/src/assets/joystick-profiles.ts +++ b/src/assets/joystick-profiles.ts @@ -1,38 +1,69 @@ 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 { + availableMavlinkManualControlButtonFunctions, + mavlinkManualControlAxes, +} from '@/libs/joystick/protocols/mavlink-manual-control' +import { modifierKeyActions, otherAvailableActions } from '@/libs/joystick/protocols/other' +import { + type GamepadToCockpitStdMapping, + type JoystickProtocolActionsMapping, + CockpitModifierKeyOption, + JoystickAxis, + JoystickButton, +} from '@/types/joystick' // TODO: Adjust mapping for PS5 controller -export const cockpitStandardToProtocols: ProtocolControllerMapping = { +export const cockpitStandardToProtocols: JoystickProtocolActionsMapping = { 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 }, - ], + 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: { + [CockpitModifierKeyOption.regular]: { + [JoystickButton.B0]: { action: availableMavlinkManualControlButtonFunctions['Arm'] }, + [JoystickButton.B1]: { action: availableMavlinkManualControlButtonFunctions['Disarm'] }, + [JoystickButton.B2]: { action: availableMavlinkManualControlButtonFunctions['Mount tilt up'] }, + [JoystickButton.B3]: { action: availableMavlinkManualControlButtonFunctions['Mount tilt down'] }, + [JoystickButton.B4]: { action: modifierKeyActions.shift }, + [JoystickButton.B5]: { action: otherAvailableActions.no_function }, + [JoystickButton.B6]: { action: availableMavlinkManualControlButtonFunctions['Gain inc'] }, + [JoystickButton.B7]: { action: availableMavlinkManualControlButtonFunctions['Gain dec'] }, + [JoystickButton.B8]: { action: availableMavlinkManualControlButtonFunctions['Lights1 brighter'] }, + [JoystickButton.B9]: { action: availableMavlinkManualControlButtonFunctions['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 }, + }, + [CockpitModifierKeyOption.shift]: { + [JoystickButton.B0]: { action: otherAvailableActions.no_function }, + [JoystickButton.B1]: { action: otherAvailableActions.no_function }, + [JoystickButton.B2]: { action: otherAvailableActions.no_function }, + [JoystickButton.B3]: { action: otherAvailableActions.no_function }, + [JoystickButton.B4]: { action: otherAvailableActions.no_function }, + [JoystickButton.B5]: { action: otherAvailableActions.no_function }, + [JoystickButton.B6]: { action: otherAvailableActions.no_function }, + [JoystickButton.B7]: { action: otherAvailableActions.no_function }, + [JoystickButton.B8]: { action: otherAvailableActions.no_function }, + [JoystickButton.B9]: { action: otherAvailableActions.no_function }, + [JoystickButton.B10]: { action: otherAvailableActions.no_function }, + [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/libs/joystick/protocols.ts b/src/libs/joystick/protocols.ts index 896f2e2ba..e24ae286f 100644 --- a/src/libs/joystick/protocols.ts +++ b/src/libs/joystick/protocols.ts @@ -1,189 +1,20 @@ -import { v4 as uuid4 } from 'uuid' +import { type ProtocolAction } from '@/types/joystick' -import { round, scale } from '@/libs/utils' -import { sequentialArray } from '@/libs/utils' +import { availableCockpitActions } from './protocols/cockpit-actions' 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 -} - -/** - * 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', -} - -/** - * 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 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 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 }) -) + availableMavlinkManualControlButtonFunctions, + mavlinkManualControlAxes, +} from './protocols/mavlink-manual-control' +import { modifierKeyActions, otherAvailableActions } from './protocols/other' + +export const allAvailableAxes: ProtocolAction[] = [ + ...Object.values(mavlinkManualControlAxes), + ...Object.values(otherAvailableActions), +] + +export const allAvailableButtons: ProtocolAction[] = [ + ...Object.values(availableCockpitActions), + ...Object.values(availableMavlinkManualControlButtonFunctions), + ...Object.values(otherAvailableActions), + ...Object.values(modifierKeyActions), +] diff --git a/src/stores/controller.ts b/src/stores/controller.ts index 9ac92c0bf..3c51dcc39 100644 --- a/src/stores/controller.ts +++ b/src/stores/controller.ts @@ -14,7 +14,7 @@ export type controllerUpdateCallback = (state: JoystickState, protocolMapping: P 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 diff --git a/src/types/joystick.ts b/src/types/joystick.ts index 3f24b8a32..df24ad02c 100644 --- a/src/types/joystick.ts +++ b/src/types/joystick.ts @@ -33,11 +33,6 @@ export interface JoystickState { axes: (number | undefined)[] } -/** - * Current state of the controller in the protocol POV - */ -export class ProtocolControllerState {} - /** * Joystick abstraction for widget */ @@ -77,43 +72,82 @@ 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 + */ + id: string + /** + * Human-readable name for the action */ - value: number | string + 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: { + /** + * Defines the buttons correspondencies for each modifier key + */ + [key in CockpitModifierKeyOption]: 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 @@ -211,7 +245,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) {} }