diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 9cb458e64..89a9c7d9f 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - GP2040_BOARDCONFIG: [Pico, AlpacaOwO, BentoBox, DURAL, FightboardV3, FightboardV3Mirrored, FlatboxRev4, FlatboxRev5, FlatboxRev5RGB, KB2040, MavercadeKeebfighter, PicoAnn, PicoFightingBoard, ReflexEncoder, RP2040AdvancedBreakoutBoard, RP2040MiniBreakoutBoard, SparkFunProMicro, WaveshareZero, Stress ] + GP2040_BOARDCONFIG: [Pico, AlpacaOwO, BentoBox, DURAL, FightboardV3, FightboardV3Mirrored, FlatboxRev4, FlatboxRev5, FlatboxRev5RGB, KB2040, MavercadeKeebfighter, PicoAnn, PicoFightingBoard, ReflexEncoder, RP2040AdvancedBreakoutBoard, RP2040MiniBreakoutBoard, SparkFunProMicro, WaveshareZero, Stress, SGFDevices ] steps: #Global Setup diff --git a/configs/FlatboxRev5RGB/assets/Flatbox-rev5-rgb.jpg b/configs/FlatboxRev5RGB/assets/Flatbox-rev5-rgb.jpg index 7752f29b2..5325ab617 100644 Binary files a/configs/FlatboxRev5RGB/assets/Flatbox-rev5-rgb.jpg and b/configs/FlatboxRev5RGB/assets/Flatbox-rev5-rgb.jpg differ diff --git a/configs/Pico/BoardConfig.h b/configs/Pico/BoardConfig.h index 47d025d50..7f46f77c8 100644 --- a/configs/Pico/BoardConfig.h +++ b/configs/Pico/BoardConfig.h @@ -294,6 +294,7 @@ // Keyboard Host Addon defaults #define KEYBOARD_HOST_ENABLED 0 #define KEYBOARD_HOST_PIN_DPLUS -1 +#define KEYBOARD_HOST_PIN_5V -1 // For details on this, see: https://gp2040-ce.info/#/development?id=i2c-display-splash #define DEFAULT_SPLASH \ diff --git a/configs/SGFDevices/BoardConfig.h b/configs/SGFDevices/BoardConfig.h new file mode 100644 index 000000000..4f5373bd2 --- /dev/null +++ b/configs/SGFDevices/BoardConfig.h @@ -0,0 +1,264 @@ +/* + * SPDX-License-Identifier: MIT + * SPDX-FileCopyrightText: Copyright (c) 2021 Jason Skuby (mytechtoybox.com) + */ + +#ifndef SGF_DEVICES_CONFIG_H_ +#define SGF_DEVICES_CONFIG_H_ + +#include "enums.pb.h" + +// Mapping between Flatbox Rev4 switch number (as silkscreened) and GPIO pin + +#define FLATBOX_SW1_PIN 6 +#define FLATBOX_SW2_PIN 5 +#define FLATBOX_SW3_PIN 4 +#define FLATBOX_SW4_PIN 3 +#define FLATBOX_SW5_PIN 2 +#define FLATBOX_SW6_PIN 1 +#define FLATBOX_SW7_PIN 9 // LEFT +#define FLATBOX_SW8_PIN 10 // DOWN +#define FLATBOX_SW9_PIN 11 // RIGHT +#define FLATBOX_SW10_PIN 18 // P1 / SQUARE +#define FLATBOX_SW11_PIN 25 // P2 / TRIANGLE +#define FLATBOX_SW12_PIN 27 // P3 +#define FLATBOX_SW13_PIN 29 // P4 +#define FLATBOX_SW14_PIN 19 // K1 / CROSS +#define FLATBOX_SW15_PIN 24 // K2 / CIRCLE +#define FLATBOX_SW16_PIN 26 // K3 +#define FLATBOX_SW17_PIN 28 // K4 +#define FLATBOX_SW18_PIN 16 // UP / THUMB + + +// This is the main pin definition section. +// This will let you specify which GPIO pin each button is assigned too. +// You can set any of the main pins as `-1` to disable it. +// The Turbo pin and LS + RS slider pins can also be set to `-1` to disable that functionality. +// Please note that only when `PIN_BUTTON_TURBO` is set to `-1` will the `T##` be removed from a connected display. +// Please note that only when `PIN_SLIDER_LS` and `PIN_SLIDER_RS` are set to `-1` will the button combo shortcut for DP/LS/RS work. +// The buttons are listed in GP2040 configuration, beside each the listed order is *GP2040 / Xinput / Switch / PS3 / Directinput / Arcade* + +#define PIN_DPAD_UP FLATBOX_SW18_PIN // DOWN +#define PIN_DPAD_DOWN FLATBOX_SW8_PIN // UP +#define PIN_DPAD_RIGHT FLATBOX_SW9_PIN // RIGHT +#define PIN_DPAD_LEFT FLATBOX_SW7_PIN // LEFT +#define PIN_BUTTON_B1 FLATBOX_SW14_PIN // B1 / A / B / Cross / 2 / K1 +#define PIN_BUTTON_B2 FLATBOX_SW15_PIN // B2 / B / A / Circle / 3 / K2 +#define PIN_BUTTON_R2 FLATBOX_SW16_PIN // R2 / RT / ZR / R2 / 8 / K3 +#define PIN_BUTTON_L2 FLATBOX_SW17_PIN // L2 / LT / ZL / L2 / 7 / K4 +#define PIN_BUTTON_B3 FLATBOX_SW10_PIN // B3 / X / Y / Square / 1 / P1 +#define PIN_BUTTON_B4 FLATBOX_SW11_PIN // B4 / Y / X / Triangle / 4 / P2 +#define PIN_BUTTON_R1 FLATBOX_SW12_PIN // R1 / RB / R / R1 / 6 / P3 +#define PIN_BUTTON_L1 FLATBOX_SW13_PIN // L1 / LB / L / L1 / 5 / P4 +#define PIN_BUTTON_S1 FLATBOX_SW2_PIN // S1 / Back / Minus / Select / 9 / Coin +#define PIN_BUTTON_S2 FLATBOX_SW1_PIN // S2 / Start / Plus / Start / 10 / Start +#define PIN_BUTTON_L3 FLATBOX_SW5_PIN // L3 / LS / LS / L3 / 11 / LS +#define PIN_BUTTON_R3 FLATBOX_SW6_PIN // R3 / RS / RS / R3 / 12 / RS +#define PIN_BUTTON_A1 FLATBOX_SW3_PIN // A1 / Guide / Home / PS / 13 / ~ +#define PIN_BUTTON_A2 FLATBOX_SW4_PIN // A2 / ~ / Capture / ~ / 14 / ~ +#define PIN_BUTTON_FN -1 // Hotkey Function +#define PIN_BUTTON_REVERSE -1 // UDLR Reverse +#define PIN_BUTTON_TURBO -1 // Turbo +#define PIN_SLIDER_LS -1 // Left Stick Slider +#define PIN_SLIDER_RS -1 // Right Stick Slider +#define PIN_SLIDER_SOCD_ONE -1 // SOCD Slider Pin One +#define PIN_SLIDER_SOCD_TWO -1 // SOCD Slider Pin Two + + +// This is the SOCD section. +// SOCD stands for `simultaneous opposing cardinal directions`. +// There are three options for `DEFAULT_SOCD_MODE` currently: +// 1 - `SOCD_MODE_NEUTRAL` - This is a neutral SOCD. EG. when you press `up` + `down` no input will be registered. +// 2 - `SOCD_MODE_UP_PRIORITY` - This is up priority SOCD. EG. when you press `up` + `down` `up` will be registered. +// 3 - `SOCD_MODE_SECOND_INPUT_PRIORITY` - This is last priority SOCD. EG. when you press and hold `up` then press `down` `down` will be registered. + +#define DEFAULT_SOCD_MODE SOCD_MODE_NEUTRAL +// SOCD Slider Slot Defaults +#define SLIDER_SOCD_SLOT_ONE SOCD_MODE_UP_PRIORITY +#define SLIDER_SOCD_SLOT_TWO SOCD_MODE_SECOND_INPUT_PRIORITY +#define SLIDER_SOCD_SLOT_DEFAULT SOCD_MODE_NEUTRAL + +#define DEFAULT_FORCED_SETUP_MODE FORCED_SETUP_MODE_OFF // FORCED_SETUP_MODE_OFF, FORCED_SETUP_MODE_LOCK_MODE_SWITCH, FORCED_SETUP_MODE_LOCK_WEB_CONFIG, FORCED_SETUP_MODE_LOCK_BOTH +#define DEFAULT_LOCK_HOTKEYS false // or true + + +// This is the LEDs section. +// The default `TURBO_LED_PIN` pin is set to `15` ( it is recommended to run through 3V3(OUT) with a resistor) +// The Turbo LED will flash at a speed consistant with the set speed of the Turbo when a Turbo button is active. +// It is recommended to disable the `TURBO_LED_PIN` by setting it to `-1` if you are sensitive of flashing lights. +// The default `BOARD_LEDS_PIN` pin is set to `28`. +// The board LED pin will allow you to connect addressible RGB LEDs on the Pico. +// Addressible RGB LEDs should be connected to the `VBUS` pin (#40), an avalible ground pin and the defined `BOARD_LEDS_PIN`. +// Special note - You should only ever use addressible RGB LEDs that are rated for 5v operation on the Pico. +// The defualt `LED_BRIGHTNESS_MAXIMUM` value is `50`. +// This will change how bright the LEDs are with `0` being off and `100` being full brightness. +// The minimum `LED_BRIGHTNESS_MAXIMUM` value is `0`. +// The maximum `LED_BRIGHTNESS_MAXIMUM` value is `100`. +// The defualt `LED_BRIGHTNESS_STEPS` value is `5`. +// This will change how many brightness steps there are when increasing or decreasing the brightness of the LEDs via the onboard shortcut. +// It is recommend to keep this value at `5` or `10`. +// The defualt `LED_FORMAT` is `LED_FORMAT_GRB`. +// You can also choose the `LED_FORMAT` of `LED_FORMAT_RGB` if your addressible RGB LEDs are not working as intended. +// The defualt `LEDS_PER_PIXEL` is set to `1`. +// This will change how many addressible RGB LEDs there are per button. +// The default LEDS_[BUTTON] is an order and has nothing to do with what GPIO pin something is connected to. +// Unless you are planning on running custom animations I would recommmend you leave this as is. + +#define TURBO_LED_PIN -1 +#define BOARD_LEDS_PIN 7 +#define REVERSE_LED_PIN -1 +#define LED_BRIGHTNESS_MAXIMUM 255 +#define LED_BRIGHTNESS_STEPS 10 +#define LED_FORMAT LED_FORMAT_GRB +#define LEDS_PER_PIXEL 2 + +#define LEDS_DPAD_LEFT 0 +#define LEDS_DPAD_DOWN 1 +#define LEDS_DPAD_RIGHT 2 +#define LEDS_DPAD_UP 11 +#define LEDS_BUTTON_B3 3 +#define LEDS_BUTTON_B4 4 +#define LEDS_BUTTON_R1 5 +#define LEDS_BUTTON_L1 6 +#define LEDS_BUTTON_B1 10 +#define LEDS_BUTTON_B2 9 +#define LEDS_BUTTON_R2 8 +#define LEDS_BUTTON_L2 7 + + +// This is the Player LED section. +// In this section you can specify if Player LEDs will be active, and, if active, which pins will be used for them. +// The defualt is `PLED_TYPE_NONE` which will turn the Player LEDs off. +// The default pin for each Player LED is `-1` which disables it. +// To enable a `PLED#_PIN`, replace the `-1` with the GPIO pin number that is desired. +// There are three options for `PLED_TYPE` currently: +// 1 - `PLED_TYPE_NONE` - This will disable the Player LEDs +// 2 - `PLED_TYPE_PWM` - This will enable the Player LEDs ( it is recommended to run through 3V3(OUT) with a resistor) +// 3 - `PLED_TYPE_RGB` - This will enable the Player LEDs as addressible RGB LEDs (please not that this has not been implemented yet) + +#define PLED_TYPE PLED_TYPE_NONE +#define PLED1_PIN -1 +#define PLED2_PIN -1 +#define PLED3_PIN -1 +#define PLED4_PIN -1 + + +// This is the Analog section. +// In this section you can specify if Analog is enabled, and, if endabled, which pins will be used for it. +// The default for `ANALOG_ADC_VRX` and `ANALOG_ADC_VRY` is `-1` which disables them. +// To enable a `ANALOG_ADC_VRX` and `ANALOG_ADC_VRY`, replace the `-1` with the GPIO pin numbers that are desired. + +#define ANALOG_ADC_VRX -1 +#define ANALOG_ADC_VRY -1 + + +// This is the I2C Display section (commonly known as the OLED display section). +// In this section you can specify if a display as been enabled, which pins are assined to it, the block address and speed. +// The default for `HAS_I2C_DISPLAY` is `1` which enables it. +// To disable the display you can change `HAS_I2C_DISPLAY` to `0`. +// The default `I2C_SDA_PIN` is `0`. +// The defualt `I2C_SCL_PIN` is `1`. +// The defualt `I2C_BLOCK` is `12c0`. +// If you change the `I2C_SDA_PIN` and `I2C_SCL_PIN` pin mapping, you may need to change the `I2C_BLOCK` as well. +// The defualt `I2C_SPEED` is `400000`. +// This should be more than fast enough for most displays. +// Some smaller displays (like 0.96" and 1.31") can go up to `800000` or even `1000000`. +// The default `DISPLAY_FLIP` is `0`. +// This can be changed to `1` to have the dispaly output flipped. +// The default `DISPLAY_INVERY` is `0`. +// This can be changed to `1` to have the color on the display inverted. + +#define HAS_I2C_DISPLAY 0 + + +// The default `SPLASH_MODE` is `NOSPLASH`. +// There are four options for `SPLASH_MODE` currently: +// 1 - `STATICSPLASH` - This will display the static splash image +// 2 - `CLOSEIN` - This will display the static splash image as a top and bottom coming together animation +// 3 - `CLOSEINCUSTOM` - This will display the custom splash image as a top and bottom coming together animation +// 4 - `NOSPLASH` - This will not display a splash screen on boot +// Special note - All of the splash screen images can be changed via `include/bitmaps.h` + +#define SPLASH_MODE SPLASH_MODE_NONE +#define SPLASH_CHOICE SPLASH_CHOICE_MAIN +#define SPLASH_DURATION 7500 // Duration in milliseconds + + +// The default `BUTTON_LAYOUT` is `BUTTON_LAYOUT_STICK` which will show an arcade stick on the left hand side of the display. +// There are seven options for `BUTTON_LAYOUT` currently: +// 1 - BUTTON_LAYOUT_STICK - This is a basic joystick layout +// 2 - BUTTON_LAYOUT_STICKLESS - This is a basic stickless (all button) layout +// 3 - BUTTON_LAYOUT_BUTTONS_ANGLED - This a WASD button layout that is angled +// 4 - BUTTON_LAYOUT_BUTTONS_BASIC - This a WASD button layout that is straight +// 5 - BUTTON_LAYOUT_KEYBOARD_ANGLED - This is a WASD keyboard layout that is angled +// 6 - BUTTON_LAYOUT_KEYBOARDA - This is a WASD keyboard layout that is straight +// 7 - BUTTON_LAYOUT_DANCEPADA - This is a dance pad layout (must be used with `BUTTON_LAYOUT_DANCEPADB` in `BUTTON_LAYOUT_RIGHT`) +// The default `BUTTON_LAYOUT_RIGHT` is `BUTTON_LAYOUT_NOIR8` which will show eight buttons on the right hand side of the display. +// There are eleven options for `BUTTON_LAYOUT_RIGHT` currently: +// 1 - BUTTON_LAYOUT_ARCADE - This is a standard 8 button arcade layout +// 2 - BUTTON_LAYOUT_STICKLESSB - This is a basic stickless (all button) layout +// 3 - BUTTON_LAYOUT_BUTTONS_ANGLEDB - This is a standard 8 button arcade layout that is angled +// 4 - BUTTON_LAYOUT_VEWLIX - This is the standard 8 button Vewlix layout +// 5 - BUTTON_LAYOUT_VEWLIX7 - This is the standard 7 button Vewlix layout +// 6 - BUTTON_LAYOUT_CAPCOM - This is the standard 8 button Capcom layout +// 7 - BUTTON_LAYOUT_CAPCOM6 - This is the stndard 6 button Capcom layout +// 8 - BUTTON_LAYOUT_SEGA2P - This is the standard 8 button Sega2P layout +// 9 - BUTTON_LAYOUT_NOIR8 - This is the standard 8 button Noir layout +// 10 - BUTTON_LAYOUT_KEYBOARDB - This is a WASD keyboard layout that is straight +// 11 - BUTTON_LAYOUT_DANCEPADB - This is a dance pad layout (must be used with `BUTTON_LAYOUT_DANCEPADA` in `BUTTON_LAYOUT`) + +#define BUTTON_LAYOUT BUTTON_LAYOUT_STICKLESS +#define BUTTON_LAYOUT_RIGHT BUTTON_LAYOUT_STICKLESSB + +#define REVERSE_UP_DEFAULT 1 +#define REVERSE_DOWN_DEFAULT 1 +#define REVERSE_LEFT_DEFAULT 1 +#define REVERSE_RIGHT_DEFAULT 1 + +// Board LED Add-on Setting +// BOARD_LED_OFF - Turns the on-board LED off +// MODE_INDICATOR - On-board LED blinks on various frequencies depending +// on the current mode (config, normal, or no USB data) +// INPUT_TEST - Blinks whenever any input is made + +#define BOARD_LED_TYPE ON_BOARD_LED_MODE_OFF + +// Dual Directional Add-on Options + +#define PIN_DUAL_DIRECTIONAL_UP -1 +#define PIN_DUAL_DIRECTIONAL_DOWN -1 +#define PIN_DUAL_DIRECTIONAL_LEFT -1 +#define PIN_DUAL_DIRECTIONAL_RIGHT -1 +#define DUAL_DIRECTIONAL_STICK_MODE DPAD_MODE_DIGITAL +#define DUAL_DIRECTIONAL_COMBINE_MODE DUAL_COMBINE_MODE_MIXED + +// BOOTSEL Button Add-on setting +#define BOOTSEL_BUTTON_MASK 0 // 0 means none, get other mask from GamepadState.h + +// Extra Button Add-on setting +#define EXTRA_BUTTON_MASK 0 // 0 means none, get other mask from GamepadState.h +#define EXTRA_BUTTON_PIN -1 + +// Keyboard Mapping Configuration +// List of HID keycodes can be located here: https://github.com/hathach/tinyusb/blob/3623ba1884ddff23e9b64766cb6dd032f1425846/src/class/hid/hid.h#L356 +// Even for the modifier keys, HID_KEY entries should be used as the implementation expects those and will convert as necessary. +#define KEY_DPAD_UP HID_KEY_ARROW_UP // UP +#define KEY_DPAD_DOWN HID_KEY_ARROW_DOWN // DOWN +#define KEY_DPAD_RIGHT HID_KEY_ARROW_RIGHT // RIGHT +#define KEY_DPAD_LEFT HID_KEY_ARROW_LEFT // LEFT +#define KEY_BUTTON_B1 HID_KEY_SHIFT_LEFT // B1 / A / B / Cross / 2 / K1 +#define KEY_BUTTON_B2 HID_KEY_Z // B2 / B / A / Circle / 3 / K2 +#define KEY_BUTTON_R2 HID_KEY_X // R2 / RT / ZR / R2 / 8 / K3 +#define KEY_BUTTON_L2 HID_KEY_V // L2 / LT / ZL / L2 / 7 / K4 +#define KEY_BUTTON_B3 HID_KEY_CONTROL_LEFT // B3 / X / Y / Square / 1 / P1 +#define KEY_BUTTON_B4 HID_KEY_ALT_LEFT // B4 / Y / X / Triangle / 4 / P2 +#define KEY_BUTTON_R1 HID_KEY_SPACE // R1 / RB / R / R1 / 6 / P3 +#define KEY_BUTTON_L1 HID_KEY_C // L1 / LB / L / L1 / 5 / P4 +#define KEY_BUTTON_S1 HID_KEY_5 // S1 / Back / Minus / Select / 9 / Coin +#define KEY_BUTTON_S2 HID_KEY_1 // S2 / Start / Plus / Start / 10 / Start +#define KEY_BUTTON_L3 HID_KEY_EQUAL // L3 / LS / LS / L3 / 11 / LS +#define KEY_BUTTON_R3 HID_KEY_MINUS // R3 / RS / RS / R3 / 12 / RS +#define KEY_BUTTON_A1 HID_KEY_9 // A1 / Guide / Home / PS / 13 / ~ +#define KEY_BUTTON_A2 HID_KEY_F2 // A2 / ~ / Capture / ~ / 14 / ~ + +#endif diff --git a/configs/SGFDevices/Readme.md b/configs/SGFDevices/Readme.md new file mode 100644 index 000000000..8d8e6d4d2 --- /dev/null +++ b/configs/SGFDevices/Readme.md @@ -0,0 +1,6 @@ +# GP2040 Configuration for SGF Flatboxes + + +Configuration for the SGF Flatbox-based controllers, based on the excellent revisions of the [Flatbox](https://github.com/jfedor2/flatbox) design by [jfedor2](https://github.com/jfedor2) powered by an RP2040 MCU. Available for purchase at [SGF](https://sgfdevices.com). The repo for the PCB itself is [here](https://github.com/sgfdevices/SGFlatbox). + +![Layout](assets/bridgetlayout.jpg) \ No newline at end of file diff --git a/configs/SGFDevices/assets/bridgetlayout.jpg b/configs/SGFDevices/assets/bridgetlayout.jpg new file mode 100755 index 000000000..74e04c3f3 Binary files /dev/null and b/configs/SGFDevices/assets/bridgetlayout.jpg differ diff --git a/configs/SGFDevices/env.ini b/configs/SGFDevices/env.ini new file mode 100644 index 000000000..ea1e7af85 --- /dev/null +++ b/configs/SGFDevices/env.ini @@ -0,0 +1,6 @@ +[env:sgfdevices] +upload_port = .pio/build/sgfdevices/ +build_flags = + ${env.build_flags} + -D PICO_XOSC_STARTUP_DELAY_MULTIPLIER=64 + -I configs/SGFDevices/ diff --git a/configs/SGFDevices/sgfdevices.h b/configs/SGFDevices/sgfdevices.h new file mode 100644 index 000000000..877dae201 --- /dev/null +++ b/configs/SGFDevices/sgfdevices.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// ----------------------------------------------------- +// NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO +// SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES +// ----------------------------------------------------- + + +#ifndef _BOARDS_SGF_DEVICES_H +#define _BOARDS_SGF_DEVICES_H + +// For board detection +#define SGF_DEVICES + +//For crappy crystal +#ifndef PICO_XOSC_STARTUP_DELAY_MULTIPLIER +#define PICO_XOSC_STARTUP_DELAY_MULTIPLIER 64 +#endif + +// --- UART --- +#ifndef PICO_DEFAULT_UART +#define PICO_DEFAULT_UART 0 +#endif +#ifndef PICO_DEFAULT_UART_TX_PIN +#define PICO_DEFAULT_UART_TX_PIN 0 +#endif +#ifndef PICO_DEFAULT_UART_RX_PIN +#define PICO_DEFAULT_UART_RX_PIN 1 +#endif + +// --- LED --- +#ifndef PICO_DEFAULT_LED_PIN +#define PICO_DEFAULT_LED_PIN 25 +#endif +// no PICO_DEFAULT_WS2812_PIN + +// --- I2C --- +#ifndef PICO_DEFAULT_I2C +#define PICO_DEFAULT_I2C 0 +#endif +#ifndef PICO_DEFAULT_I2C_SDA_PIN +#define PICO_DEFAULT_I2C_SDA_PIN 4 +#endif +#ifndef PICO_DEFAULT_I2C_SCL_PIN +#define PICO_DEFAULT_I2C_SCL_PIN 5 +#endif + +// --- SPI --- +#ifndef PICO_DEFAULT_SPI +#define PICO_DEFAULT_SPI 0 +#endif +#ifndef PICO_DEFAULT_SPI_SCK_PIN +#define PICO_DEFAULT_SPI_SCK_PIN 18 +#endif +#ifndef PICO_DEFAULT_SPI_TX_PIN +#define PICO_DEFAULT_SPI_TX_PIN 19 +#endif +#ifndef PICO_DEFAULT_SPI_RX_PIN +#define PICO_DEFAULT_SPI_RX_PIN 16 +#endif +#ifndef PICO_DEFAULT_SPI_CSN_PIN +#define PICO_DEFAULT_SPI_CSN_PIN 17 +#endif + +// --- FLASH --- + +#define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 + +#ifndef PICO_FLASH_SPI_CLKDIV +#define PICO_FLASH_SPI_CLKDIV 2 +#endif + +#ifndef PICO_FLASH_SIZE_BYTES +#define PICO_FLASH_SIZE_BYTES (2 * 1024 * 1024) +#endif + +// Drive high to force power supply into PWM mode (lower ripple on 3V3 at light loads) +#define PICO_SMPS_MODE_PIN 23 + +#ifndef PICO_RP2040_B0_SUPPORTED +#define PICO_RP2040_B0_SUPPORTED 1 +#endif + +#endif \ No newline at end of file diff --git a/docs/assets/images/gpc-add-ons-analog.png b/docs/assets/images/gpc-add-ons-analog.png index c87a7742a..72b0d2733 100644 Binary files a/docs/assets/images/gpc-add-ons-analog.png and b/docs/assets/images/gpc-add-ons-analog.png differ diff --git a/docs/assets/images/gpc-add-ons-focus-mode.png b/docs/assets/images/gpc-add-ons-focus-mode.png index e0a7c8a9c..eabd2a740 100644 Binary files a/docs/assets/images/gpc-add-ons-focus-mode.png and b/docs/assets/images/gpc-add-ons-focus-mode.png differ diff --git a/docs/assets/images/gpc-add-ons-socd-slider.png b/docs/assets/images/gpc-add-ons-socd-slider.png index 5027c9155..977fda9fd 100644 Binary files a/docs/assets/images/gpc-add-ons-socd-slider.png and b/docs/assets/images/gpc-add-ons-socd-slider.png differ diff --git a/docs/assets/images/gpc-settings.png b/docs/assets/images/gpc-settings.png index a9f37d27d..ad7ba6e5f 100644 Binary files a/docs/assets/images/gpc-settings.png and b/docs/assets/images/gpc-settings.png differ diff --git a/headers/addons/keyboard_host.h b/headers/addons/keyboard_host.h index e8eb02774..8ff7761ab 100644 --- a/headers/addons/keyboard_host.h +++ b/headers/addons/keyboard_host.h @@ -12,6 +12,10 @@ #define KEYBOARD_HOST_PIN_DPLUS -1 #endif +#ifndef KEYBOARD_HOST_PIN_5V +#define KEYBOARD_HOST_PIN_5V -1 +#endif + // KeyboardHost Module Name #define KeyboardHostName "KeyboardHost" diff --git a/headers/gamepad.h b/headers/gamepad.h index 721a51d40..89c6b018e 100644 --- a/headers/gamepad.h +++ b/headers/gamepad.h @@ -62,6 +62,7 @@ class Gamepad { Gamepad(int debounceMS = 5); void setup(); + void teardown_and_reinit(const uint32_t profileNum); void process(); void read(); void save(); diff --git a/headers/storagemanager.h b/headers/storagemanager.h index cd00eae17..32365579f 100644 --- a/headers/storagemanager.h +++ b/headers/storagemanager.h @@ -44,9 +44,12 @@ class Storage { LEDOptions& getLedOptions() { return config.ledOptions; } AddonOptions& getAddonOptions() { return config.addonOptions; } AnimationOptions_Proto& getAnimationOptions() { return config.animationOptions; } + ProfileOptions& getProfileOptions() { return config.profileOptions; } bool save(); + PinMappings& getProfilePinMappings(); + // Perform saves that were enqueued from core1 void performEnqueuedSaves(); @@ -65,6 +68,9 @@ class Storage { void ClearFeatureData(); uint8_t * GetFeatureData(); + void setProfile(const uint32_t); // profile support for multiple mappings + void setFunctionalPinMappings(const uint32_t); + void ResetSettings(); // EEPROM Reset Feature private: @@ -79,6 +85,7 @@ class Storage { critical_section_t animationOptionsCs; uint32_t animationOptionsCrc = 0; AnimationOptions animationOptionsToSave = {}; + PinMappings* functionalPinMappings = nullptr; }; #endif diff --git a/lib/nanopb/docs/generator_flow.svg b/lib/nanopb/docs/generator_flow.svg index e30277a75..197778c4f 100644 --- a/lib/nanopb/docs/generator_flow.svg +++ b/lib/nanopb/docs/generator_flow.svg @@ -1,2869 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MyMessage.proto - - - pb_encode( ); - pb_decode( ); - - - nanopb_generator.py - - - - - - - - - - - - - - - - - - MyMessage.pb.c - MyMessage.pb.h - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - c - - Nanopb library - - - - Protocol Buffersmessages - - - - - - - - - - - - - - - - - - - - - Data structures - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - User application - - - +MyMessage.protopb_encode( );pb_decode( );nanopb_generator.pyMyMessage.pb.cMyMessage.pb.hcNanopb libraryProtocol BuffersmessagesData structuresUser application \ No newline at end of file diff --git a/lib/nanopb/docs/logo/logo.svg b/lib/nanopb/docs/logo/logo.svg index 91ab28b67..3caf17211 100644 --- a/lib/nanopb/docs/logo/logo.svg +++ b/lib/nanopb/docs/logo/logo.svg @@ -1,1470 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - Pb - Pb - - - - - - - - - - - - - - - - - - nano - nano - - - - - +PbPbnanonano \ No newline at end of file diff --git a/proto/config.proto b/proto/config.proto index 3c5df52f0..70b84a7fa 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -13,6 +13,7 @@ message GamepadOptions optional bool switchTpShareForDs4 = 6; optional bool lockHotkeys = 7; optional bool fourWayMode = 8; + optional uint32 profileNumber = 9; } message KeyboardMapping @@ -115,6 +116,29 @@ message PinMappings optional int32 pinButtonFn = 19; } + +message AlternativePinMappings +{ + optional int32 pinButtonB1 = 1; + optional int32 pinButtonB2 = 2; + optional int32 pinButtonB3 = 3; + optional int32 pinButtonB4 = 4; + optional int32 pinButtonL1 = 5; + optional int32 pinButtonR1 = 6; + optional int32 pinButtonL2 = 7; + optional int32 pinButtonR2 = 8; + optional int32 pinDpadUp = 9; + optional int32 pinDpadDown = 10; + optional int32 pinDpadLeft = 11; + optional int32 pinDpadRight = 12; +} + + +message ProfileOptions +{ + repeated AlternativePinMappings alternativePinMappings = 1 [(nanopb).max_count = 3]; +} + message DisplayOptions { optional bool enabled = 1; @@ -418,6 +442,7 @@ message KeyboardHostOptions optional bool enabled = 1; optional int32 pinDplus = 2; optional KeyboardMapping mapping = 3; + optional int32 pin5V = 4; } message FocusModeOptions @@ -465,4 +490,5 @@ message Config optional AnimationOptions_Proto animationOptions = 8; optional AddonOptions addonOptions = 9; optional ForcedSetupOptions forcedSetupOptions = 10; + optional ProfileOptions profileOptions = 11; } diff --git a/proto/enums.proto b/proto/enums.proto index 4d6e109cc..bdfd3ce3f 100644 --- a/proto/enums.proto +++ b/proto/enums.proto @@ -136,9 +136,13 @@ enum GamepadHotkey HOTKEY_SOCD_BYPASS = 12; HOTKEY_TOGGLE_4_WAY_MODE = 13; HOTKEY_TOGGLE_DDI_4_WAY_MODE = 14; - HOTKEY_L3_BUTTON = 15; - HOTKEY_R3_BUTTON = 16; - HOTKEY_TOUCHPAD_BUTTON = 17; + HOTKEY_LOAD_PROFILE_1 = 15; + HOTKEY_LOAD_PROFILE_2 = 16; + HOTKEY_LOAD_PROFILE_3 = 17; + HOTKEY_LOAD_PROFILE_4 = 18; + HOTKEY_L3_BUTTON = 19; + HOTKEY_R3_BUTTON = 20; + HOTKEY_TOUCHPAD_BUTTON = 21; } // This has to be kept in sync with LEDFormat in NeoPico.hpp diff --git a/src/addons/keyboard_host.cpp b/src/addons/keyboard_host.cpp index ed37c1002..6fc2c87f7 100644 --- a/src/addons/keyboard_host.cpp +++ b/src/addons/keyboard_host.cpp @@ -12,12 +12,12 @@ static KeyboardButtonMapping _keyboard_host_mapDpadLeft = KeyboardButtonMapping static KeyboardButtonMapping _keyboard_host_mapDpadRight = KeyboardButtonMapping(GAMEPAD_MASK_RIGHT); static KeyboardButtonMapping _keyboard_host_mapButtonB1 = KeyboardButtonMapping(GAMEPAD_MASK_B1); static KeyboardButtonMapping _keyboard_host_mapButtonB2 = KeyboardButtonMapping(GAMEPAD_MASK_B2); -static KeyboardButtonMapping _keyboard_host_mapButtonB3 = KeyboardButtonMapping(GAMEPAD_MASK_R2); -static KeyboardButtonMapping _keyboard_host_mapButtonB4 = KeyboardButtonMapping(GAMEPAD_MASK_L2); -static KeyboardButtonMapping _keyboard_host_mapButtonL1 = KeyboardButtonMapping(GAMEPAD_MASK_B3); -static KeyboardButtonMapping _keyboard_host_mapButtonR1 = KeyboardButtonMapping(GAMEPAD_MASK_B4); -static KeyboardButtonMapping _keyboard_host_mapButtonL2 = KeyboardButtonMapping(GAMEPAD_MASK_R1); -static KeyboardButtonMapping _keyboard_host_mapButtonR2 = KeyboardButtonMapping(GAMEPAD_MASK_L1); +static KeyboardButtonMapping _keyboard_host_mapButtonB3 = KeyboardButtonMapping(GAMEPAD_MASK_B3); +static KeyboardButtonMapping _keyboard_host_mapButtonB4 = KeyboardButtonMapping(GAMEPAD_MASK_B4); +static KeyboardButtonMapping _keyboard_host_mapButtonL1 = KeyboardButtonMapping(GAMEPAD_MASK_L1); +static KeyboardButtonMapping _keyboard_host_mapButtonR1 = KeyboardButtonMapping(GAMEPAD_MASK_R1); +static KeyboardButtonMapping _keyboard_host_mapButtonL2 = KeyboardButtonMapping(GAMEPAD_MASK_L2); +static KeyboardButtonMapping _keyboard_host_mapButtonR2 = KeyboardButtonMapping(GAMEPAD_MASK_R2); static KeyboardButtonMapping _keyboard_host_mapButtonS1 = KeyboardButtonMapping(GAMEPAD_MASK_S1); static KeyboardButtonMapping _keyboard_host_mapButtonS2 = KeyboardButtonMapping(GAMEPAD_MASK_S2); static KeyboardButtonMapping _keyboard_host_mapButtonL3 = KeyboardButtonMapping(GAMEPAD_MASK_L3); @@ -27,7 +27,9 @@ static KeyboardButtonMapping _keyboard_host_mapButtonA2 = KeyboardButtonMapping bool KeyboardHostAddon::available() { const KeyboardHostOptions& keyboardHostOptions = Storage::getInstance().getAddonOptions().keyboardHostOptions; - return keyboardHostOptions.enabled && isValidPin(keyboardHostOptions.pinDplus); + return keyboardHostOptions.enabled && + isValidPin(keyboardHostOptions.pinDplus) && + (keyboardHostOptions.pin5V == -1 || isValidPin(keyboardHostOptions.pin5V)); } void KeyboardHostAddon::setup() { @@ -37,6 +39,14 @@ void KeyboardHostAddon::setup() { // board_init(); // board_init() should be doing what the two lines below are doing but doesn't work // needs tinyusb_board library linked + + if (keyboardHostOptions.pin5V != -1) { + const int32_t pin5V = keyboardHostOptions.pin5V; + gpio_init(pin5V); + gpio_set_dir(pin5V, GPIO_OUT); + gpio_pull_up(pin5V); + } + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; pio_cfg.pin_dp = keyboardHostOptions.pinDplus; tuh_configure(1, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &pio_cfg); diff --git a/src/config_utils.cpp b/src/config_utils.cpp index c78e7f084..260a09b13 100644 --- a/src/config_utils.cpp +++ b/src/config_utils.cpp @@ -97,6 +97,7 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) INIT_UNSET_PROPERTY(config.gamepadOptions, switchTpShareForDs4, false); INIT_UNSET_PROPERTY(config.gamepadOptions, lockHotkeys, DEFAULT_LOCK_HOTKEYS); INIT_UNSET_PROPERTY(config.gamepadOptions, fourWayMode, false); + INIT_UNSET_PROPERTY(config.gamepadOptions, profileNumber, 1); // hotkeyOptions HotkeyOptions& hotkeyOptions = config.hotkeyOptions; @@ -227,6 +228,45 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) INIT_UNSET_PROPERTY(config.displayOptions, invert, !!DISPLAY_INVERT); INIT_UNSET_PROPERTY(config.displayOptions, displaySaverTimeout, DISPLAY_SAVER_TIMEOUT); + // alternate pin mappings + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinButtonB1, PIN_BUTTON_B1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinButtonB2, PIN_BUTTON_B2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinButtonB3, PIN_BUTTON_B3); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinButtonB4, PIN_BUTTON_B4); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinButtonL1, PIN_BUTTON_L1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinButtonR1, PIN_BUTTON_R1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinButtonL2, PIN_BUTTON_L2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinButtonR2, PIN_BUTTON_R2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinDpadUp, PIN_DPAD_UP); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinDpadDown, PIN_DPAD_DOWN); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinDpadLeft, PIN_DPAD_LEFT); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[0], pinDpadRight, PIN_DPAD_RIGHT); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinButtonB1, PIN_BUTTON_B1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinButtonB2, PIN_BUTTON_B2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinButtonB3, PIN_BUTTON_B3); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinButtonB4, PIN_BUTTON_B4); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinButtonL1, PIN_BUTTON_L1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinButtonR1, PIN_BUTTON_R1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinButtonL2, PIN_BUTTON_L2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinButtonR2, PIN_BUTTON_R2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinDpadUp, PIN_DPAD_UP); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinDpadDown, PIN_DPAD_DOWN); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinDpadLeft, PIN_DPAD_LEFT); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[1], pinDpadRight, PIN_DPAD_RIGHT); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinButtonB1, PIN_BUTTON_B1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinButtonB2, PIN_BUTTON_B2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinButtonB3, PIN_BUTTON_B3); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinButtonB4, PIN_BUTTON_B4); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinButtonL1, PIN_BUTTON_L1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinButtonR1, PIN_BUTTON_R1); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinButtonL2, PIN_BUTTON_L2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinButtonR2, PIN_BUTTON_R2); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinDpadUp, PIN_DPAD_UP); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinDpadDown, PIN_DPAD_DOWN); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinDpadLeft, PIN_DPAD_LEFT); + INIT_UNSET_PROPERTY(config.profileOptions.alternativePinMappings[2], pinDpadRight, PIN_DPAD_RIGHT); + config.profileOptions.alternativePinMappings_count = 3; + // ledOptions INIT_UNSET_PROPERTY(config.ledOptions, dataPin, BOARD_LEDS_PIN); INIT_UNSET_PROPERTY(config.ledOptions, ledFormat, static_cast(LED_FORMAT)); @@ -447,6 +487,7 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) // keyboardMapping INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions, enabled, KEYBOARD_HOST_ENABLED); INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions, pinDplus, KEYBOARD_HOST_PIN_DPLUS); + INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions, pin5V, KEYBOARD_HOST_PIN_5V); INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions.mapping, keyDpadUp, KEY_DPAD_UP); INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions.mapping, keyDpadDown, KEY_DPAD_DOWN); INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions.mapping, keyDpadRight, KEY_DPAD_RIGHT); diff --git a/src/configs/webconfig.cpp b/src/configs/webconfig.cpp index b7e89250e..2fab38fc3 100644 --- a/src/configs/webconfig.cpp +++ b/src/configs/webconfig.cpp @@ -1,4 +1,5 @@ #include "configs/webconfig.h" +#include "config.pb.h" #include "configs/base64.h" #include "storagemanager.h" @@ -503,6 +504,60 @@ std::string setSplashImage() return serialize_json(doc); } +std::string setProfileOptions() +{ + DynamicJsonDocument doc = get_post_data(); + + ProfileOptions& profileOptions = Storage::getInstance().getProfileOptions(); + JsonObject options = doc.as(); + JsonArray alts = options["alternativePinMappings"]; + int altsIndex = 0; + for (JsonObject alt : alts) { + profileOptions.alternativePinMappings[altsIndex].pinButtonB1 = alt["B1"].as(); + profileOptions.alternativePinMappings[altsIndex].pinButtonB2 = alt["B2"].as(); + profileOptions.alternativePinMappings[altsIndex].pinButtonB3 = alt["B3"].as(); + profileOptions.alternativePinMappings[altsIndex].pinButtonB4 = alt["B4"].as(); + profileOptions.alternativePinMappings[altsIndex].pinButtonL1 = alt["L1"].as(); + profileOptions.alternativePinMappings[altsIndex].pinButtonR1 = alt["R1"].as(); + profileOptions.alternativePinMappings[altsIndex].pinButtonL2 = alt["L2"].as(); + profileOptions.alternativePinMappings[altsIndex].pinButtonR2 = alt["R2"].as(); + profileOptions.alternativePinMappings[altsIndex].pinDpadUp = alt["Up"].as(); + profileOptions.alternativePinMappings[altsIndex].pinDpadDown = alt["Down"].as(); + profileOptions.alternativePinMappings[altsIndex].pinDpadLeft = alt["Left"].as(); + profileOptions.alternativePinMappings[altsIndex].pinDpadRight = alt["Right"].as(); + profileOptions.alternativePinMappings_count = ++altsIndex; + if (altsIndex > 2) break; + } + + Storage::getInstance().save(); + return serialize_json(doc); +} + +std::string getProfileOptions() +{ + DynamicJsonDocument doc(LWIP_HTTPD_POST_MAX_PAYLOAD_LEN); + + ProfileOptions& profileOptions = Storage::getInstance().getProfileOptions(); + JsonArray alts = doc.createNestedArray("alternativePinMappings"); + for (int i = 0; i < profileOptions.alternativePinMappings_count; i++) { + JsonObject altMappings = alts.createNestedObject(); + altMappings["B1"] = profileOptions.alternativePinMappings[i].pinButtonB1; + altMappings["B2"] = profileOptions.alternativePinMappings[i].pinButtonB2; + altMappings["B3"] = profileOptions.alternativePinMappings[i].pinButtonB3; + altMappings["B4"] = profileOptions.alternativePinMappings[i].pinButtonB4; + altMappings["L1"] = profileOptions.alternativePinMappings[i].pinButtonL1; + altMappings["R1"] = profileOptions.alternativePinMappings[i].pinButtonR1; + altMappings["L2"] = profileOptions.alternativePinMappings[i].pinButtonL2; + altMappings["R2"] = profileOptions.alternativePinMappings[i].pinButtonR2; + altMappings["Up"] = profileOptions.alternativePinMappings[i].pinDpadUp; + altMappings["Down"] = profileOptions.alternativePinMappings[i].pinDpadDown; + altMappings["Left"] = profileOptions.alternativePinMappings[i].pinDpadLeft; + altMappings["Right"] = profileOptions.alternativePinMappings[i].pinDpadRight; + } + + return serialize_json(doc); +} + std::string setGamepadOptions() { DynamicJsonDocument doc = get_post_data(); @@ -514,6 +569,7 @@ std::string setGamepadOptions() readDoc(gamepadOptions.switchTpShareForDs4, doc, "switchTpShareForDs4"); readDoc(gamepadOptions.lockHotkeys, doc, "lockHotkeys"); readDoc(gamepadOptions.fourWayMode, doc, "fourWayMode"); + readDoc(gamepadOptions.profileNumber, doc, "profileNumber"); HotkeyOptions& hotkeyOptions = Storage::getInstance().getHotkeyOptions(); save_hotkey(&hotkeyOptions.hotkey01, doc, "hotkey01"); @@ -548,6 +604,7 @@ std::string getGamepadOptions() writeDoc(doc, "switchTpShareForDs4", gamepadOptions.switchTpShareForDs4 ? 1 : 0); writeDoc(doc, "lockHotkeys", gamepadOptions.lockHotkeys ? 1 : 0); writeDoc(doc, "fourWayMode", gamepadOptions.fourWayMode ? 1 : 0); + writeDoc(doc, "profileNumber", gamepadOptions.profileNumber); const PinMappings& pinMappings = Storage::getInstance().getPinMappings(); writeDoc(doc, "fnButtonPin", pinMappings.pinButtonFn); @@ -1041,6 +1098,7 @@ std::string setAddonOptions() KeyboardHostOptions& keyboardHostOptions = Storage::getInstance().getAddonOptions().keyboardHostOptions; docToValue(keyboardHostOptions.enabled, doc, "KeyboardHostAddonEnabled"); docToPin(keyboardHostOptions.pinDplus, doc, "keyboardHostPinDplus"); + docToPin(keyboardHostOptions.pin5V, doc, "keyboardHostPin5V"); docToValue(keyboardHostOptions.mapping.keyDpadUp, doc, "keyboardHostMap", "Up"); docToValue(keyboardHostOptions.mapping.keyDpadDown, doc, "keyboardHostMap", "Down"); docToValue(keyboardHostOptions.mapping.keyDpadLeft, doc, "keyboardHostMap", "Left"); @@ -1273,6 +1331,7 @@ std::string getAddonOptions() const KeyboardHostOptions& keyboardHostOptions = Storage::getInstance().getAddonOptions().keyboardHostOptions; writeDoc(doc, "KeyboardHostAddonEnabled", keyboardHostOptions.enabled); writeDoc(doc, "keyboardHostPinDplus", keyboardHostOptions.pinDplus); + writeDoc(doc, "keyboardHostPin5V", keyboardHostOptions.pin5V); writeDoc(doc, "keyboardHostMap", "Up", keyboardHostOptions.mapping.keyDpadUp); writeDoc(doc, "keyboardHostMap", "Down", keyboardHostOptions.mapping.keyDpadDown); writeDoc(doc, "keyboardHostMap", "Left", keyboardHostOptions.mapping.keyDpadLeft); @@ -1402,6 +1461,7 @@ static const std::pair handlerFuncs[] = { "/api/setCustomTheme", setCustomTheme }, { "/api/getCustomTheme", getCustomTheme }, { "/api/setPinMappings", setPinMappings }, + { "/api/setProfileOptions", setProfileOptions }, { "/api/setKeyMappings", setKeyMappings }, { "/api/setAddonsOptions", setAddonOptions }, { "/api/setPS4Options", setPS4Options }, @@ -1411,6 +1471,7 @@ static const std::pair handlerFuncs[] = { "/api/getGamepadOptions", getGamepadOptions }, { "/api/getLedOptions", getLedOptions }, { "/api/getPinMappings", getPinMappings }, + { "/api/getProfileOptions", getProfileOptions }, { "/api/getKeyMappings", getKeyMappings }, { "/api/getAddonsOptions", getAddonOptions }, { "/api/resetSettings", resetSettings }, diff --git a/src/gamepad.cpp b/src/gamepad.cpp index 57fb42507..9d9dd7adc 100644 --- a/src/gamepad.cpp +++ b/src/gamepad.cpp @@ -94,7 +94,7 @@ Gamepad::Gamepad(int debounceMS) : void Gamepad::setup() { // Configure pin mapping - const PinMappings& pinMappings = Storage::getInstance().getPinMappings(); + const PinMappings& pinMappings = Storage::getInstance().getProfilePinMappings(); const auto convertPin = [](int32_t pin) -> uint8_t { return isValidPin(pin) ? pin : 0xff; }; mapDpadUp = new GamepadButtonMapping(convertPin(pinMappings.pinDpadUp), GAMEPAD_MASK_UP); @@ -143,6 +143,31 @@ void Gamepad::setup() } } +/** + * @brief Undo setup(). + */ +void Gamepad::teardown_and_reinit(const uint32_t profileNum) +{ + const PinMappings& pinMappings = Storage::getInstance().getProfilePinMappings(); + // deinitialize the GPIO pins so we don't have orphans + for (int i = 0; i < GAMEPAD_DIGITAL_INPUT_COUNT; i++) + { + if (gamepadMappings[i]->isAssigned()) + { + gpio_deinit(gamepadMappings[i]->pin); + } + } + if (isValidPin(pinMappings.pinButtonFn)) { + gpio_deinit(pinMappings.pinButtonFn); + } + + // set to new profile + Storage::getInstance().setProfile(profileNum); + + // reinitialize pin mappings + this->setup(); +} + void Gamepad::process() { memcpy(&rawState, &state, sizeof(GamepadState)); @@ -212,8 +237,7 @@ void Gamepad::process() void Gamepad::read() { - const PinMappings& pinMappings = Storage::getInstance().getPinMappings(); - + const PinMappings& pinMappings = Storage::getInstance().getProfilePinMappings(); // Need to invert since we're using pullups uint32_t values = ~gpio_get_all(); @@ -341,6 +365,30 @@ void Gamepad::processHotkeyIfNewAction(GamepadHotkey action) reqSave = true; } break; + case HOTKEY_LOAD_PROFILE_1: + if (action != lastAction) { + this->teardown_and_reinit(1); + reqSave = true; + } + break; + case HOTKEY_LOAD_PROFILE_2: + if (action != lastAction) { + this->teardown_and_reinit(2); + reqSave = true; + } + break; + case HOTKEY_LOAD_PROFILE_3: + if (action != lastAction) { + this->teardown_and_reinit(3); + reqSave = true; + } + break; + case HOTKEY_LOAD_PROFILE_4: + if (action != lastAction) { + this->teardown_and_reinit(4); + reqSave = true; + } + break; } // only save if we did something different (except NONE because NONE doesn't get here) diff --git a/src/gp2040.cpp b/src/gp2040.cpp index 2cb6d2452..c5b8d7da6 100644 --- a/src/gp2040.cpp +++ b/src/gp2040.cpp @@ -103,7 +103,7 @@ void GP2040::setup() { adc_init(); // Setup Add-ons - //addons.LoadAddon(new KeyboardHostAddon(), CORE0_INPUT); + addons.LoadAddon(new KeyboardHostAddon(), CORE0_INPUT); addons.LoadAddon(new AnalogInput(), CORE0_INPUT); addons.LoadAddon(new BootselButtonAddon(), CORE0_INPUT); addons.LoadAddon(new DualDirectionalInput(), CORE0_INPUT); diff --git a/src/storagemanager.cpp b/src/storagemanager.cpp index f0fca8bda..75fbd73db 100644 --- a/src/storagemanager.cpp +++ b/src/storagemanager.cpp @@ -9,6 +9,7 @@ #include "AnimationStorage.hpp" #include "Effects/StaticColor.hpp" #include "FlashPROM.h" +#include "config.pb.h" #include "hardware/watchdog.h" #include "Animation.hpp" #include "CRC32.h" @@ -132,6 +133,39 @@ void Storage::ResetSettings() watchdog_reboot(0, SRAM_END, 2000); } +PinMappings& Storage::getProfilePinMappings() { + if (functionalPinMappings == nullptr) { + functionalPinMappings = (PinMappings*)malloc(sizeof(PinMappings)); + setFunctionalPinMappings(config.gamepadOptions.profileNumber); + } + return *functionalPinMappings; +} + +void Storage::setProfile(const uint32_t profileNum) +{ + setFunctionalPinMappings(profileNum); +} + +void Storage::setFunctionalPinMappings(const uint32_t profileNum) +{ + memcpy(functionalPinMappings, &config.pinMappings, sizeof(PinMappings)); + if (profileNum < 2 || profileNum > 4) return; + + AlternativePinMappings alts = this->config.profileOptions.alternativePinMappings[profileNum-2]; + if (isValidPin(alts.pinButtonB1)) functionalPinMappings->pinButtonB1 = alts.pinButtonB1; + if (isValidPin(alts.pinButtonB2)) functionalPinMappings->pinButtonB2 = alts.pinButtonB2; + if (isValidPin(alts.pinButtonB3)) functionalPinMappings->pinButtonB3 = alts.pinButtonB3; + if (isValidPin(alts.pinButtonB4)) functionalPinMappings->pinButtonB4 = alts.pinButtonB4; + if (isValidPin(alts.pinButtonL1)) functionalPinMappings->pinButtonL1 = alts.pinButtonL1; + if (isValidPin(alts.pinButtonR1)) functionalPinMappings->pinButtonR1 = alts.pinButtonR1; + if (isValidPin(alts.pinButtonL2)) functionalPinMappings->pinButtonL2 = alts.pinButtonL2; + if (isValidPin(alts.pinButtonR2)) functionalPinMappings->pinButtonR2 = alts.pinButtonR2; + if (isValidPin(alts.pinDpadUp)) functionalPinMappings->pinDpadUp = alts.pinDpadUp; + if (isValidPin(alts.pinDpadDown)) functionalPinMappings->pinDpadDown = alts.pinDpadDown; + if (isValidPin(alts.pinDpadLeft)) functionalPinMappings->pinDpadLeft = alts.pinDpadLeft; + if (isValidPin(alts.pinDpadRight)) functionalPinMappings->pinDpadRight = alts.pinDpadRight; +} + void Storage::SetConfigMode(bool mode) { // hack for config mode CONFIG_MODE = mode; previewDisplayOptions = config.displayOptions; diff --git a/www/server/app.js b/www/server/app.js index 8659379cc..d713acd8f 100644 --- a/www/server/app.js +++ b/www/server/app.js @@ -92,6 +92,7 @@ app.get("/api/getGamepadOptions", (req, res) => { lockHotkeys: 0, fourWayMode: 0, fnButtonPin: -1, + profileNumber: 1, hotkey01: { auxMask: 32768, buttonsMask: 66304, @@ -226,6 +227,51 @@ app.get("/api/getKeyMappings", (req, res) => res.send(mapValues(DEFAULT_KEYBOARD_MAPPING)) ); +app.get("/api/getProfileOptions", (req, res) => { + return res.send({ + alternativePinMappings: [{ + B1: 10, + B2: 6, + B3: 11, + B4: 12, + L1: 13, + R1: 9, + L2: 7, + R2: 8, + Up: 2, + Down: 3, + Left: 5, + Right: 4 + },{ + B1: 10, + B2: 11, + B3: 12, + B4: 13, + L1: 6, + R1: 8, + L2: 7, + R2: 9, + Up: 3, + Down: 2, + Left: 4, + Right: 5 + },{ + B1: 6, + B2: 7, + B3: 8, + B4: 9, + L1: 10, + R1: 12, + L2: 11, + R2: 13, + Up: 3, + Down: 5, + Left: 4, + Right: 2 + }] + }); +}); + app.get("/api/getAddonsOptions", (req, res) => { return res.send({ turboPin: -1, @@ -313,6 +359,7 @@ app.get("/api/getAddonsOptions", (req, res) => { snesPadLatchPin: -1, snesPadDataPin: -1, keyboardHostPinDplus: 0, + keyboardHostPin5V: -1, keyboardHostMap: DEFAULT_KEYBOARD_MAPPING, AnalogInputEnabled: 1, BoardLedAddonEnabled: 1, diff --git a/www/src/App.jsx b/www/src/App.jsx index d040982df..b6f646ab9 100644 --- a/www/src/App.jsx +++ b/www/src/App.jsx @@ -7,6 +7,7 @@ import Navigation from './Components/Navigation' import HomePage from './Pages/HomePage' import PinMappingPage from "./Pages/PinMapping"; +import ProfileSettingsPage from "./Pages/ProfileSettings"; import KeyboardMappingPage from "./Pages/KeyboardMapping"; import ResetSettingsPage from './Pages/ResetSettingsPage'; import SettingsPage from './Pages/SettingsPage'; @@ -31,6 +32,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/www/src/Components/KeyboardMapper.jsx b/www/src/Components/KeyboardMapper.jsx index aa971e231..b9019a3f6 100644 --- a/www/src/Components/KeyboardMapper.jsx +++ b/www/src/Components/KeyboardMapper.jsx @@ -21,10 +21,10 @@ const KeyboardMapper = ({ buttonLabels, handleKeyChange, validated, getKeyMappin {Object.keys(BUTTONS[buttonLabelType])?.filter(p => p !== 'label' && p !== 'value').map((button, i) => { let label = BUTTONS[buttonLabelType][button]; - if (button === "S1" && swapTpShareLabels) { + if (button === "S1" && swapTpShareLabels && buttonLabelType === "ps4") { label = BUTTONS[buttonLabelType]["A2"]; } - if (button === "A2" && swapTpShareLabels) { + if (button === "A2" && swapTpShareLabels && buttonLabelType === "ps4") { label = BUTTONS[buttonLabelType]["S1"]; } const keyMapping = getKeyMappingForButton(button); diff --git a/www/src/Components/Navigation.jsx b/www/src/Components/Navigation.jsx index f0eaa75d5..b8fb89678 100644 --- a/www/src/Components/Navigation.jsx +++ b/www/src/Components/Navigation.jsx @@ -51,6 +51,7 @@ const Navigation = (props) => { {t('Navigation:pin-mapping-label')} {t('Navigation:keyboard-mapping-label')} + {t('Navigation:profile-settings-label')} {t('Navigation:led-config-label')} {t('Navigation:custom-theme-label')} {t('Navigation:display-config-label')} diff --git a/www/src/Contexts/AppContext.jsx b/www/src/Contexts/AppContext.jsx index 2e661309d..f0c74ef67 100644 --- a/www/src/Contexts/AppContext.jsx +++ b/www/src/Contexts/AppContext.jsx @@ -59,17 +59,19 @@ yup.addMethod(yup.NumberSchema, 'checkUsedPins', function() { return this.test('', '${originalValue} is unavailable/already assigned!', (value) => checkPins(value)); }); +const parseBoolean = (bool) => String(bool).toLowerCase() === "true"; + export const AppContextProvider = ({ children, ...props }) => { const localValue = localStorage.getItem('buttonLabelType') || 'gp2040'; - const localValue2 = localStorage.getItem('swapTpShareLabels') || false; + const localValue2 = parseBoolean(localStorage.getItem('swapTpShareLabels')) || false; const [buttonLabels, _setButtonLabels] = useState({ swapTpShareLabels: localValue2, buttonLabelType: localValue }); const setButtonLabels = ({ buttonLabelType : newType, swapTpShareLabels: newSwap }) => { console.log('buttonLabelType is', newType) newType && localStorage.setItem('buttonLabelType', newType); - newSwap !== undefined && localStorage.setItem('swapTpShareLabels', newSwap); + newSwap !== undefined && localStorage.setItem('swapTpShareLabels', parseBoolean(newSwap)); _setButtonLabels(({ buttonLabelType, swapTpShareLabels }) => ({ buttonLabelType: newType || buttonLabelType, - swapTpShareLabels: (newSwap !== undefined) ? newSwap : swapTpShareLabels })); + swapTpShareLabels: parseBoolean((newSwap !== undefined) ? newSwap : swapTpShareLabels) })); }; const [savedColors, _setSavedColors] = useState(localStorage.getItem('savedColors') ? localStorage.getItem('savedColors').split(',') : []); @@ -81,16 +83,16 @@ export const AppContextProvider = ({ children, ...props }) => { const updateButtonLabels = (e) => { const { key, newValue } = e; if (key === "swapTpShareLabels") { - _setButtonLabels(({ buttonLabelType }) => ({ buttonLabelType, swapTpShareLabels: newValue === "true" })); + _setButtonLabels(({ buttonLabelType }) => ({ buttonLabelType, swapTpShareLabels: parseBoolean(newValue) })); } if (key === "buttonLabelType") { - _setButtonLabels(({ swapTpShareLabels }) => ({ buttonLabelType: newValue, swapTpShareLabels })); + _setButtonLabels(({ swapTpShareLabels }) => ({ buttonLabelType: newValue, swapTpShareLabels: parseBoolean(swapTpShareLabels) })); } }; useEffect(() => { _setButtonLabels({ buttonLabelType: buttonLabels.buttonLabelType, - swapTpShareLabels: buttonLabels.swapTpShareLabels }); + swapTpShareLabels: parseBoolean(buttonLabels.swapTpShareLabels) }); window.addEventListener("storage", updateButtonLabels); return () => { window.removeEventListener("storage", updateButtonLabels); diff --git a/www/src/Locales/en/AddonsConfig.jsx b/www/src/Locales/en/AddonsConfig.jsx index 51385a3ed..7a7110b69 100644 --- a/www/src/Locales/en/AddonsConfig.jsx +++ b/www/src/Locales/en/AddonsConfig.jsx @@ -114,7 +114,8 @@ export default { 'focus-mode-header-text': 'Focus Mode Configuration', 'focus-mode-pin-label': 'Focus Mode Pin', 'keyboard-host-header-text': 'Keyboard Host Configuration', - 'keyboard-host-sub-header-text': 'Following set the data + and - pins. Only the + pin can be configured.', + 'keyboard-host-sub-header-text': 'Following set the data +, - and 5V (optional) pins. Only the + and 5V pin can be configured.', 'keyboard-host-d-plus-label': 'D+', 'keyboard-host-d-minus-label': 'D-', + 'keyboard-host-five-v-label': '5V Power (optional)', }; diff --git a/www/src/Locales/en/Index.jsx b/www/src/Locales/en/Index.jsx index 67b1611de..80a039408 100644 --- a/www/src/Locales/en/Index.jsx +++ b/www/src/Locales/en/Index.jsx @@ -5,6 +5,7 @@ import SettingsPage from './SettingsPage'; import ResetSettings from './ResetSettings'; import Components from './Components'; import PinMapping from './PinMapping'; +import ProfileSettings from './ProfileSettings'; import KeyboardMapping from './KeyboardMapping'; import LedConfig from './LedConfig'; import CustomTheme from './CustomTheme'; @@ -20,6 +21,7 @@ export default { ResetSettings, Components, PinMapping, + ProfileSettings, KeyboardMapping, LedConfig, CustomTheme, diff --git a/www/src/Locales/en/Navigation.jsx b/www/src/Locales/en/Navigation.jsx index 3fb21711e..07d03fdb8 100644 --- a/www/src/Locales/en/Navigation.jsx +++ b/www/src/Locales/en/Navigation.jsx @@ -12,6 +12,7 @@ export default { 'led-config-label': 'LED Configuration', 'links-label': 'Links', 'pin-mapping-label': 'Pin Mapping', + "profile-settings-label": "Profile Settings", 'reboot-label': 'Reboot', 'reboot-modal-body': 'Select a mode to reboot to', 'reboot-modal-button-bootsel-label': 'USB (BOOTSEL)', diff --git a/www/src/Locales/en/ProfileSettings.jsx b/www/src/Locales/en/ProfileSettings.jsx new file mode 100644 index 000000000..a47f3fb33 --- /dev/null +++ b/www/src/Locales/en/ProfileSettings.jsx @@ -0,0 +1,9 @@ +export default { + "header-text": "Profiles", + "profile-pins-desc": "This page allows three additional button mappings to be configured as profiles 2 through 4, which can be loaded via hotkey. (The first profile is the core configuration from the Pin Mapping page.) A physical layout of the pins:", + "profile-1": "Profile 1", + "profile-2": "Profile 2", + "profile-3": "Profile 3", + "profile-4": "Profile 4", + "profile-pins-warning": "Try to avoid changing the buttons/directions used for your switch profile hotkeys, or else it will get hard to understand what profile you are selecting!", +} diff --git a/www/src/Locales/en/SettingsPage.jsx b/www/src/Locales/en/SettingsPage.jsx index 798e3a144..cdb80ef98 100644 --- a/www/src/Locales/en/SettingsPage.jsx +++ b/www/src/Locales/en/SettingsPage.jsx @@ -24,6 +24,7 @@ export default { 'first-win': 'First Win', 'off': 'Off' }, + 'profile-number-label': 'Profile Number', 'hotkey-settings-label': 'Hotkey Settings', 'hotkey-settings-sub-header': "The <1>Fn slider provides a mappable Function button in the <3 exact='true' to='/pin-mapping'>Pin Mapping page. By selecting the <1>Fn slider option, the Function button must be held along with the selected hotkey settings.<5 />Additionally, select <1>None from the dropdown to unassign any button.", 'hotkey-settings-warning': 'Function button is not mapped. The Fn slider will be disabled.', @@ -46,6 +47,10 @@ export default { 'l3-button': 'L3 Button', 'r3-button': 'R3 Button', 'touchpad-button': 'Touchpad Button', + 'load-profile-1': 'Load Profile #1', + 'load-profile-2': 'Load Profile #2', + 'load-profile-3': 'Load Profile #3', + 'load-profile-4': 'Load Profile #4', }, 'forced-setup-mode-label': 'Forced Setup Mode', 'forced-setup-mode-options': { diff --git a/www/src/Pages/AddonsConfigPage.jsx b/www/src/Pages/AddonsConfigPage.jsx index 75ba24c30..0620a195d 100644 --- a/www/src/Pages/AddonsConfigPage.jsx +++ b/www/src/Pages/AddonsConfigPage.jsx @@ -301,6 +301,7 @@ const schema = yup.object().shape({ KeyboardHostAddonEnabled: yup.number().required().label('Keyboard Host Add-On Enabled'), keyboardHostPinDplus: yup.number().label('Keyboard Host D+ Pin').validatePinWhenValue('KeyboardHostAddonEnabled'), + keyboardHostPin5V: yup.number().label('Keyboard Host 5V Power Pin').validatePinWhenValue('KeyboardHostAddonEnabled'), PlayerNumAddonEnabled: yup.number().required().label('Player Number Add-On Enabled'), playerNumber: yup.number().label('Player Number').validateRangeWhenValue('PlayerNumAddonEnabled', 1, 4), @@ -421,6 +422,7 @@ const defaultValues = { snesPadLatchPin: -1, snesPadDataPin: -1, keyboardHostPinDplus: -1, + keyboardHostPin5V: -1, keyboardHostMap: baseButtonMappings, AnalogInputEnabled: 0, BoardLedAddonEnabled: 0, @@ -1936,7 +1938,6 @@ export default function AddonsConfigPage() { onChange={(e) => { handleCheckbox("FocusModeAddonEnabled", values); handleChange(e);}} /> - {/*
+

{t('KeyboardMapping:sub-header-text')}

@@ -1981,7 +1994,6 @@ export default function AddonsConfigPage() { onChange={(e) => { handleCheckbox("KeyboardHostAddonEnabled", values); handleChange(e);}} />
- */}
{saveMessage ? {saveMessage} : null} diff --git a/www/src/Pages/ProfileSettings.jsx b/www/src/Pages/ProfileSettings.jsx new file mode 100644 index 000000000..fd2f1fe22 --- /dev/null +++ b/www/src/Pages/ProfileSettings.jsx @@ -0,0 +1,199 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { NavLink } from "react-router-dom"; +import { Button, Form } from 'react-bootstrap'; +import { AppContext } from '../Contexts/AppContext'; +import Section from '../Components/Section'; +import WebApi, { baseProfileOptions, baseButtonMappings } from '../Services/WebApi'; +import boards from '../Data/Boards.json'; +import { BUTTONS } from '../Data/Buttons'; +import './PinMappings.scss'; +import { Trans, useTranslation } from 'react-i18next'; + +const requiredButtons = ['S2']; +const errorType = { + required: 'errors.required', + conflict: 'errors.conflict', + invalid: 'errors.invalid', + used: 'errors.used', +}; + +export default function ProfileOptionsPage() { + const { buttonLabels, setButtonLabels, usedPins, updateUsedPins } = useContext(AppContext); + const [validated, setValidated] = useState(false); + const [saveMessage, setSaveMessage] = useState(''); + const [buttonMappings, setButtonMappings] = useState(baseButtonMappings); + const [profileOptions, setProfileOptions] = useState(baseProfileOptions); + const [selectedBoard] = useState(import.meta.env.VITE_GP2040_BOARD); + const { buttonLabelType } = buttonLabels; + const { setLoading } = useContext(AppContext); + + const { t } = useTranslation(''); + + const translatedErrorType = Object.keys(errorType).reduce((a, k) => ({ ...a, [k]: t(`ProfileOptions:${errorType[k]}`) }), {}); + + useEffect(() => { + async function fetchData() { + setButtonMappings(await WebApi.getPinMappings(setLoading)); + setProfileOptions(await WebApi.getProfileOptions(setLoading)); + setButtonLabels({}); + } + + fetchData(); + }, [setButtonMappings, setProfileOptions]); + + const handlePinChange = (e, index, key) => { + const newProfileOptions = { ...profileOptions }; + if (e.target.value) + newProfileOptions['alternativePinMappings'][index][key].pin = parseInt(e.target.value); + else + newProfileOptions['alternativePinMappings'][index][key].pin = ''; + + validateMappings(newProfileOptions); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + e.stopPropagation(); + + let mappings = { ...profileOptions }; + validateMappings(mappings); + + if (Object.keys(mappings).filter(p => mappings[p].error).length > 0) { + setSaveMessage(t('Common:errors.validation-error')); + return; + } + + const success = await WebApi.setProfileOptions(mappings); + if (success) + updateUsedPins(); + setSaveMessage(success ? t('Common:saved-success-message') : t('Common:saved-error-message')); + }; + + const validateMappings = (mappings) => { + profileOptions['alternativePinMappings'].forEach((altMappings) => { + const buttons = Object.keys(altMappings); + + // Create some mapped pin groups for easier error checking + const mappedPins = buttons + .filter(p => altMappings[p].pin > -1) + .reduce((a, p) => { + a.push(altMappings[p].pin); + return a; + }, []); + const mappedPinCounts = mappedPins.reduce((a, p) => ({ ...a, [p]: (a[p] || 0) + 1 }), {}); + const uniquePins = mappedPins.filter((p, i, a) => a.indexOf(p) === i); + const conflictedPins = Object.keys(mappedPinCounts).filter(p => mappedPinCounts[p] > 1).map(parseInt); + const invalidPins = uniquePins.filter(p => boards[selectedBoard].invalidPins.indexOf(p) > -1); + const otherPins = usedPins.filter(p => uniquePins.indexOf(p) === -1); + + for (let button of buttons) { + altMappings[button].error = ''; + + // Validate required button + if ((altMappings[button].pin < boards[selectedBoard].minPin || altMappings[button].pin > boards[selectedBoard].maxPin) && requiredButtons.filter(b => b === button).length) + altMappings[button].error = translatedErrorType.required; + + // Identify conflicted pins + else if (conflictedPins.indexOf(altMappings[button].pin) > -1) + altMappings[button].error = translatedErrorType.conflict; + + // Identify invalid pin assignments + else if (invalidPins.indexOf(altMappings[button].pin) > -1) + altMappings[button].error = translatedErrorType.invalid; + + // Identify used pins + else if (otherPins.indexOf(altMappings[button].pin) > -1) + altMappings[button].error = translatedErrorType.used; + } + }); + + setProfileOptions(mappings); + setValidated(true); + }; + + const pinCell = (profile, button) => { + return + handlePinChange(e, profile, button)} + > + + {renderError(profile, button)} + + + } + + const renderError = (index, button) => { + if (profileOptions['alternativePinMappings'][index][button].error === translatedErrorType.required) { + return {t('PinMapping:errors.required', { + button: BUTTONS[buttonLabelType][button] + })}; + } + else if (profileOptions['alternativePinMappings'][index][button].error === translatedErrorType.conflict) { + const conflictedMappings = Object.keys(profileOptions['alternativePinMappings'][index]) + .filter(b => b !== button) + .filter(b => profileOptions['alternativePinMappings'][index][b].pin === profileOptions['alternativePinMappings'][index][button].pin) + .map(b => BUTTONS[buttonLabelType][b]); + + return {t('PinMapping:errors.conflict', { + pin: profileOptions['alternativePinMappings'][index][button].pin, + conflictedMappings: conflictedMappings.join(', '), + })}; + } + else if (profileOptions['alternativePinMappings'][index][button].error === translatedErrorType.invalid) { + console.log(profileOptions['alternativePinMappings'][index][button].pin); + return {t('PinMapping:errors.invalid', { + pin: profileOptions['alternativePinMappings'][index][button].pin + })}; + } + else if (profileOptions['alternativePinMappings'][index][button].error === translatedErrorType.used) { + return {t('PinMapping:errors.used', { + pin: profileOptions['alternativePinMappings'][index][button].pin + })}; + } + + return <>; + }; + + return ( +
+

{t('ProfileSettings:profile-pins-desc')}

+
  {String(buttonMappings['Up'].pin).padStart(2)}
+ {String(buttonMappings['Left'].pin).padStart(2)}  {String(buttonMappings['Right'].pin).padStart(2)}      {String(buttonMappings['B3'].pin).padStart(2)} {String(buttonMappings['B4'].pin).padStart(2)} {String(buttonMappings['R1'].pin).padStart(2)} {String(buttonMappings['L1'].pin).padStart(2)}
+   {String(buttonMappings['Down'].pin).padStart(2)}        {String(buttonMappings['B1'].pin).padStart(2)} {String(buttonMappings['B2'].pin).padStart(2)} {String(buttonMappings['R2'].pin).padStart(2)} {String(buttonMappings['L2'].pin).padStart(2)}
+

{t('ProfileSettings:profile-pins-warning')}

+
+ + + + + + + + + + + + {console.log(Object.keys(profileOptions['alternativePinMappings'][0]))} + {Object.keys(profileOptions['alternativePinMappings'][0]).map((key) => ( + + + + {pinCell(0, key)} + {pinCell(1, key)} + {pinCell(2, key)} + + ))} + +
{BUTTONS[buttonLabelType].label}{t('ProfileSettings:profile-1')}{t('ProfileSettings:profile-2')}{t('ProfileSettings:profile-3')}{t('ProfileSettings:profile-4')}
{BUTTONS[buttonLabelType][key]}{buttonMappings[key].pin}
+ + {saveMessage ? {saveMessage} : null} +
+
+ ); +} diff --git a/www/src/Pages/SettingsPage.jsx b/www/src/Pages/SettingsPage.jsx index 2ef67c27a..bd960c31e 100644 --- a/www/src/Pages/SettingsPage.jsx +++ b/www/src/Pages/SettingsPage.jsx @@ -49,9 +49,13 @@ const HOTKEY_ACTIONS = [ { labelKey: 'hotkey-actions.invert-y', value: 10 }, { labelKey: 'hotkey-actions.toggle-4way-joystick-mode', value: 13 }, { labelKey: 'hotkey-actions.toggle-ddi-4way-joystick-mode', value: 14 }, - { labelKey: 'hotkey-actions.l3-button', value: 15 }, - { labelKey: 'hotkey-actions.r3-button', value: 16 }, - { labelKey: 'hotkey-actions.touchpad-button', value: 17 }, + { labelKey: 'hotkey-actions.load-profile-1', value: 15 }, + { labelKey: 'hotkey-actions.load-profile-2', value: 16 }, + { labelKey: 'hotkey-actions.load-profile-3', value: 17 }, + { labelKey: 'hotkey-actions.load-profile-4', value: 18 }, + { labelKey: 'hotkey-actions.l3-button', value: 19 }, + { labelKey: 'hotkey-actions.r3-button', value: 20 }, + { labelKey: 'hotkey-actions.touchpad-button', value: 21 }, ]; const FORCED_SETUP_MODES = [ @@ -83,6 +87,7 @@ const schema = yup.object().shape({ forcedSetupMode : yup.number().required().oneOf(FORCED_SETUP_MODES.map(o => o.value)).label('SOCD Cleaning Mode'), lockHotkeys: yup.number().required().label('Lock Hotkeys'), fourWayMode: yup.number().required().label('4-Way Joystick Mode'), + profileNumber: yup.number().required().label('Profile Number'), }); const FormContext = ({ setButtonLabels }) => { @@ -113,6 +118,8 @@ const FormContext = ({ setButtonLabels }) => { values.lockHotkeys = parseInt(values.lockHotkeys); if (!!values.fourWayMode) values.fourWayMode = parseInt(values.fourWayMode); + if (!!values.profileNumber) + values.profileNumber = parseInt(values.profileNumber); setButtonLabels({ swapTpShareLabels: (values.switchTpShareForDs4 === 1) && (values.inputMode === 4) }); @@ -245,6 +252,20 @@ export default function SettingsPage() { checked={Boolean(values.fourWayMode)} onChange={(e) => { setFieldValue("fourWayMode", e.target.checked ? 1 : 0); }} /> + + {t('SettingsPage:profile-number-label')} +
+ { setFieldValue("profileNumber", parseInt(e.target.value));}} + > +
+
diff --git a/www/src/Services/WebApi.js b/www/src/Services/WebApi.js index a6187f41d..d3c7cb5ad 100644 --- a/www/src/Services/WebApi.js +++ b/www/src/Services/WebApi.js @@ -25,6 +25,49 @@ export const baseButtonMappings = { Fn: { pin: -1, key: 0, error: null }, }; +export const baseProfileOptions = { + alternativePinMappings: [{ + Up: { pin: -1, key: 0, error: null }, + Down: { pin: -1, key: 0, error: null }, + Left: { pin: -1, key: 0, error: null }, + Right: { pin: -1, key: 0, error: null }, + B1: { pin: -1, key: 0, error: null }, + B2: { pin: -1, key: 0, error: null }, + B3: { pin: -1, key: 0, error: null }, + B4: { pin: -1, key: 0, error: null }, + L1: { pin: -1, key: 0, error: null }, + R1: { pin: -1, key: 0, error: null }, + L2: { pin: -1, key: 0, error: null }, + R2: { pin: -1, key: 0, error: null }, + },{ + Up: { pin: -1, key: 0, error: null }, + Down: { pin: -1, key: 0, error: null }, + Left: { pin: -1, key: 0, error: null }, + Right: { pin: -1, key: 0, error: null }, + B1: { pin: -1, key: 0, error: null }, + B2: { pin: -1, key: 0, error: null }, + B3: { pin: -1, key: 0, error: null }, + B4: { pin: -1, key: 0, error: null }, + L1: { pin: -1, key: 0, error: null }, + R1: { pin: -1, key: 0, error: null }, + L2: { pin: -1, key: 0, error: null }, + R2: { pin: -1, key: 0, error: null }, + },{ + Up: { pin: -1, key: 0, error: null }, + Down: { pin: -1, key: 0, error: null }, + Left: { pin: -1, key: 0, error: null }, + Right: { pin: -1, key: 0, error: null }, + B1: { pin: -1, key: 0, error: null }, + B2: { pin: -1, key: 0, error: null }, + B3: { pin: -1, key: 0, error: null }, + B4: { pin: -1, key: 0, error: null }, + L1: { pin: -1, key: 0, error: null }, + R1: { pin: -1, key: 0, error: null }, + L2: { pin: -1, key: 0, error: null }, + R2: { pin: -1, key: 0, error: null }, + }] +}; + async function resetSettings() { return axios.get(`${baseUrl}/api/resetSettings`) .then((response) => response.data) @@ -235,6 +278,46 @@ async function setPinMappings(mappings) { }); } +async function getProfileOptions(setLoading) { + setLoading(true); + + try { + const response = await axios.get(`${baseUrl}/api/getProfileOptions`); + let profileOptions = { ...baseProfileOptions }; + response.data['alternativePinMappings'].forEach((altButtons, index) => { + for (let prop of Object.keys(altButtons)) + profileOptions['alternativePinMappings'][index][prop].pin = parseInt( + response.data['alternativePinMappings'][index][prop] + ); + }); + setLoading(false); + return profileOptions; + } catch (error) { + console.error(error); + return false; + } +} + +async function setProfileOptions(options) { + let data = {}; + data['alternativePinMappings'] = []; + options['alternativePinMappings'].forEach((altButtons, index) => { + let altMapping = {}; + Object.keys(options['alternativePinMappings'][index]).map((button, i) => altMapping[button] = altButtons[button].pin); + data['alternativePinMappings'].push(altMapping); + }); + + return axios.post(`${baseUrl}/api/setProfileOptions`, sanitizeRequest(data)) + .then((response) => { + console.log(response.data); + return true; + }) + .catch((err) => { + console.error(err); + return false; + }); +} + async function getKeyMappings(setLoading) { setLoading(true); @@ -385,6 +468,8 @@ const WebApi = { setCustomTheme, getPinMappings, setPinMappings, + getProfileOptions, + setProfileOptions, getKeyMappings, setKeyMappings, getAddonsOptions,