From c309d35c2e638309a47b0c2de2d875ba5e34ce78 Mon Sep 17 00:00:00 2001 From: nicolasrmerz <57548231+nicolasrmerz@users.noreply.github.com> Date: Sat, 20 Jul 2024 15:53:01 -0400 Subject: [PATCH] DRV8833-Based Rumble (for XInput) (#1090) * Captured rumble data -> featureData * Add endpoint_out_checked and initial write to xinput_out_buffer * Copy to featureData during endpoint_out claim * Revert to simpler logic (endpoint_out always checked in process) * Add GamepadRumbleState, update rumble state in XInput driver * Create hard-coded DRV8833RumbleAddon, test 0 and 255 values * Fix rumble state comparison * Add rumble duty rescaling * Properly handle case where one motor is zero and other is non-zero * Working in WebConfig * Remove leftover debug code --- CMakeLists.txt | 1 + headers/addons/drv8833_rumble.h | 74 +++++++++++++ headers/gamepad.h | 2 + headers/gamepad/GamepadState.h | 10 ++ proto/config.proto | 67 +++++++----- src/addons/drv8833_rumble.cpp | 108 +++++++++++++++++++ src/config_utils.cpp | 134 +++++++++++++----------- src/configs/webconfig.cpp | 24 ++++- src/drivers/xinput/XInputDriver.cpp | 22 +++- src/gamepad.cpp | 13 ++- src/gp2040aux.cpp | 6 +- www/server/app.js | 7 ++ www/src/Addons/DRV8833.tsx | 156 ++++++++++++++++++++++++++++ www/src/Locales/en/AddonsConfig.jsx | 7 ++ www/src/Pages/AddonsConfigPage.jsx | 4 + 15 files changed, 536 insertions(+), 99 deletions(-) create mode 100644 headers/addons/drv8833_rumble.h create mode 100644 src/addons/drv8833_rumble.cpp create mode 100644 www/src/Addons/DRV8833.tsx diff --git a/CMakeLists.txt b/CMakeLists.txt index 0455165e1..53a301af8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -217,6 +217,7 @@ src/addons/playernum.cpp src/addons/playerleds.cpp src/addons/rotaryencoder.cpp src/addons/reverse.cpp +src/addons/drv8833_rumble.cpp src/addons/turbo.cpp src/addons/slider_socd.cpp src/addons/wiiext.cpp diff --git a/headers/addons/drv8833_rumble.h b/headers/addons/drv8833_rumble.h new file mode 100644 index 000000000..3000328f7 --- /dev/null +++ b/headers/addons/drv8833_rumble.h @@ -0,0 +1,74 @@ +#ifndef RUMBLE_H_ +#define RUMBLE_H_ + +#include +#include +#include "gpaddon.h" +#include "gamepad/GamepadState.h" + +#ifndef DRV8833_RUMBLE_ENABLED +#define DRV8833_RUMBLE_ENABLED 0 +#endif + +#ifndef DRV8833_RUMBLE_LEFT_MOTOR_PIN +#define DRV8833_RUMBLE_LEFT_MOTOR_PIN -1 //26 +#endif + +#ifndef DRV8833_RUMBLE_RIGHT_MOTOR_PIN +#define DRV8833_RUMBLE_RIGHT_MOTOR_PIN -1 //27 +#endif + +#ifndef DRV8833_RUMBLE_MOTOR_SLEEP_PIN +#define DRV8833_RUMBLE_MOTOR_SLEEP_PIN -1 //22 +#endif + +#ifndef DRV8833_RUMBLE_PWM_FREQUENCY +#define DRV8833_RUMBLE_PWM_FREQUENCY 10000 // 10 kHz +#endif + +#ifndef DRV8833_RUMBLE_DUTY_MIN +#define DRV8833_RUMBLE_DUTY_MIN 0.0f +#endif + +#ifndef DRV8833_RUMBLE_DUTY_MAX +#define DRV8833_RUMBLE_DUTY_MAX 100.0f +#endif + +// DRV8833 Rumble Module +#define DRV8833RumbleName "DRV8833Rumble" + +// Scale uint8 to 0 -> 100 range +#define motorToDuty(m) (100.0f * (m/255.0f)) +// Rescale from 0 -> 100 range to min -> max range +#define scaleDuty(in, min, max) ((in/100.0f) * (max-min) + min) + +// Buzzer Speaker +class DRV8833RumbleAddon : public GPAddon +{ +public: + virtual bool available(); + virtual void setup(); + virtual void preprocess() {} + virtual void process(); + virtual std::string name() { return DRV8833RumbleName; } +private: + uint32_t pwmSetFreqDuty(uint slice, uint channel, uint32_t frequency, float duty); + bool compareRumbleState(Gamepad * gamepad); + void setRumbleState(Gamepad * gamepad); + void disableMotors(); + void enableMotors(Gamepad * gamepad); + uint8_t leftMotorPin; + uint8_t rightMotorPin; + uint8_t motorSleepPin; + uint8_t leftMotorPinSlice; + uint8_t leftMotorPinChannel; + uint8_t rightMotorPinSlice; + uint8_t rightMotorPinChannel; + uint32_t pwmFrequency; + float dutyMin; + float dutyMax; + uint32_t sysClock; + GamepadRumbleState currentRumbleState; +}; + +#endif diff --git a/headers/gamepad.h b/headers/gamepad.h index 4a37346c1..5552f7ba7 100644 --- a/headers/gamepad.h +++ b/headers/gamepad.h @@ -39,6 +39,7 @@ class Gamepad { void hotkey(); void clearState(); + void clearRumbleState(); /** * @brief Flag to indicate analog trigger support. @@ -135,6 +136,7 @@ class Gamepad { GamepadState rawState; GamepadState state; GamepadState turboState; + GamepadRumbleState rumbleState; GamepadButtonMapping *mapDpadUp; GamepadButtonMapping *mapDpadDown; GamepadButtonMapping *mapDpadLeft; diff --git a/headers/gamepad/GamepadState.h b/headers/gamepad/GamepadState.h index b3249edfa..803b13256 100644 --- a/headers/gamepad/GamepadState.h +++ b/headers/gamepad/GamepadState.h @@ -133,6 +133,16 @@ const uint32_t buttonMasks[] = GAMEPAD_MASK_E12, }; +struct GamepadRumbleState +{ + // XInput General Motors + uint8_t leftMotor {0}; + uint8_t rightMotor {0}; + // GameInput Trigger Motors (XBOne) + uint8_t leftTrigger {0}; + uint8_t rightTrigger {0}; +}; + struct GamepadState { uint8_t dpad {0}; diff --git a/proto/config.proto b/proto/config.proto index a32b31923..a4280328f 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -205,26 +205,26 @@ message ProfileOptions message DisplayOptions { optional bool enabled = 1; - + optional int32 i2cBlock = 2; optional int32 deprecatedI2cSDAPin = 3 [deprecated = true]; optional int32 deprecatedI2cSCLPin = 4 [deprecated = true]; optional int32 i2cAddress = 5; optional int32 deprecatedI2cSpeed = 6 [deprecated = true]; - + optional ButtonLayout buttonLayout = 7; optional ButtonLayoutRight buttonLayoutRight = 8; optional ButtonLayoutCustomOptions buttonLayoutCustomOptions = 9; - + optional SplashMode splashMode = 10; optional SplashChoice splashChoice = 11; optional int32 splashDuration = 12; optional bytes splashImage = 13 [(nanopb).max_size = 1024]; - + optional int32 size = 14; optional int32 flip = 15; optional bool invert = 16; - + optional int32 displaySaverTimeout = 17; optional bool turnOffWhenSuspended = 18; } @@ -237,7 +237,7 @@ message LEDOptions optional uint32 ledsPerButton = 4; optional uint32 brightnessMaximum = 5; optional uint32 brightnessSteps = 6; - + optional int32 indexUp = 7; optional int32 indexDown = 8; optional int32 indexLeft = 9; @@ -256,7 +256,7 @@ message LEDOptions optional int32 indexR3 = 22; optional int32 indexA1 = 23; optional int32 indexA2 = 24; - + optional PLEDType pledType = 25; optional int32 pledPin1 = 26; optional int32 pledPin2 = 27; @@ -282,7 +282,7 @@ message AnimationOptions_Proto optional int32 chaseCycleTime = 5; optional int32 rainbowCycleTime = 6; optional uint32 themeIndex = 7; - + optional bool hasCustomTheme = 8; optional uint32 customThemeUp = 9; optional uint32 customThemeDown = 10; @@ -320,7 +320,7 @@ message AnimationOptions_Proto optional uint32 customThemeR3Pressed = 42; optional uint32 customThemeA1Pressed = 43; optional uint32 customThemeA2Pressed = 44; - optional uint32 buttonPressColorCooldownTimeInMs = 45; + optional uint32 buttonPressColorCooldownTimeInMs = 45; } message BootselButtonOptions @@ -338,7 +338,7 @@ message OnBoardLedOptions message AnalogOptions { optional bool enabled = 1; - + optional int32 analogAdc1PinX = 2; optional int32 analogAdc1PinY = 3; optional bool forced_circularity = 4; @@ -356,12 +356,12 @@ message AnalogOptions message TurboOptions { optional bool enabled = 1; - + optional int32 deprecatedButtonPin = 2 [deprecated = true]; optional int32 ledPin = 3; optional uint32 shotCount = 4; optional int32 shmupDialPin = 5; - + optional bool shmupModeEnabled = 6; optional uint32 shmupAlwaysOn1 = 7; optional uint32 shmupAlwaysOn2 = 8; @@ -381,7 +381,7 @@ message TurboOptions message SliderOptions { optional bool enabled = 1; - + optional int32 deprecatedPinSliderOne = 2 [deprecated = true]; optional int32 deprecatedPinSliderTwo = 3 [deprecated = true]; optional DpadMode deprecatedModeOne = 4 [deprecated = true]; @@ -392,10 +392,10 @@ message SliderOptions message SOCDSliderOptions { optional bool enabled = 1; - + optional int32 deprecatedPinOne = 2 [deprecated = true]; optional int32 deprecatedPinTwo = 3 [deprecated = true]; - + optional SOCDMode modeDefault = 4; optional SOCDMode deprecatedModeOne = 5 [deprecated = true]; optional SOCDMode deprecatedModeTwo = 6 [deprecated = true]; @@ -404,10 +404,10 @@ message SOCDSliderOptions message ReverseOptions { optional bool enabled = 1; - + optional int32 buttonPin = 2; optional int32 ledPin = 3; - + optional uint32 actionUp = 4; optional uint32 actionDown = 5; optional uint32 actionLeft = 6; @@ -417,7 +417,7 @@ message ReverseOptions message AnalogADS1219Options { optional bool enabled = 1; - + optional int32 i2cBlock = 2; optional int32 deprecatedI2cSDAPin = 3 [deprecated = true]; optional int32 deprecatedI2cSCLPin = 4 [deprecated = true]; @@ -439,12 +439,12 @@ message AnalogADS1256Options message DualDirectionalOptions { optional bool enabled = 1; - + optional int32 deprecatedUpPin = 2 [deprecated = true]; optional int32 deprecatedDownPin = 3 [deprecated = true]; optional int32 deprecatedLeftPin = 4 [deprecated = true]; optional int32 deprecatedRightPin = 5 [deprecated = true]; - + optional DpadMode dpadMode = 6; optional DualDirectionalCombinationMode combineMode = 7; optional bool fourWayMode = 8; @@ -467,7 +467,7 @@ message TiltOptions optional int32 tiltRightAnalogRightPin = 12; optional SOCDMode tiltSOCDMode = 13; - + optional int32 factorTilt1LeftX = 14; optional int32 factorTilt1LeftY = 15; optional int32 factorTilt1RightX = 16; @@ -481,7 +481,7 @@ message TiltOptions message BuzzerOptions { optional bool enabled = 1; - + optional int32 pin = 2; optional uint32 volume = 3; optional int32 enablePin = 4; @@ -490,7 +490,7 @@ message BuzzerOptions message ExtraButtonOptions { optional bool enabled = 1; - + optional int32 pin = 2; optional uint32 buttonMap = 3; } @@ -543,7 +543,7 @@ message WiiOptions optional AnalogAxis x = 1; optional AnalogAxis y = 2; } - + message NunchukOptions { optional int32 buttonC = 1; @@ -574,7 +574,7 @@ message WiiOptions optional AnalogAxis rightTrigger = 19; } - message TaikoOptions + message TaikoOptions { optional int32 buttonKatLeft = 1; optional int32 buttonKatRight = 2; @@ -738,6 +738,18 @@ message PCF8575Options repeated GpioMappingInfo pins = 3 [(nanopb).max_count = 16]; } +message DRV8833RumbleOptions +{ + optional bool enabled = 1; + + optional int32 leftMotorPin = 2; + optional int32 rightMotorPin = 3; + optional int32 motorSleepPin = 4; + optional uint32 pwmFrequency = 5; + optional float dutyMin = 6; + optional float dutyMax = 7; +} + message AddonOptions { optional BootselButtonOptions bootselButtonOptions = 1; @@ -765,6 +777,7 @@ message AddonOptions optional AnalogADS1256Options analogADS1256Options = 23; optional RotaryOptions rotaryOptions = 24; optional PCF8575Options pcf8575Options = 25; + optional DRV8833RumbleOptions drv8833RumbleOptions = 26; } message MigrationHistory @@ -777,7 +790,7 @@ message MigrationHistory message Config { optional string boardVersion = 1 [(nanopb).max_length = 31]; - + optional GamepadOptions gamepadOptions = 2; optional HotkeyOptions hotkeyOptions = 3; optional PinMappings deprecatedPinMappings = 4 [deprecated = true]; @@ -788,7 +801,7 @@ message Config optional AddonOptions addonOptions = 9; optional ForcedSetupOptions forcedSetupOptions = 10; optional ProfileOptions profileOptions = 11; - + optional string boardConfig = 12 [(nanopb).max_length = 63]; optional GpioMappings gpioMappings = 13; optional MigrationHistory migrations = 14; diff --git a/src/addons/drv8833_rumble.cpp b/src/addons/drv8833_rumble.cpp new file mode 100644 index 000000000..05e9bd663 --- /dev/null +++ b/src/addons/drv8833_rumble.cpp @@ -0,0 +1,108 @@ +#include "hardware/pwm.h" +#include "addons/drv8833_rumble.h" +#include "storagemanager.h" +#include "peripheralmanager.h" +#include "usbdriver.h" +#include "math.h" +#include "helper.h" +#include "config.pb.h" + +#include +#include "pico/stdlib.h" + +bool DRV8833RumbleAddon::available() { + const DRV8833RumbleOptions& options = Storage::getInstance().getAddonOptions().drv8833RumbleOptions; + return options.enabled && (isValidPin(options.leftMotorPin) && isValidPin(options.rightMotorPin)); +} + +void DRV8833RumbleAddon::setup() { + const DRV8833RumbleOptions& options = Storage::getInstance().getAddonOptions().drv8833RumbleOptions; + leftMotorPin = options.leftMotorPin; + rightMotorPin = options.rightMotorPin; + motorSleepPin = options.motorSleepPin; + pwmFrequency = options.pwmFrequency; + dutyMin = options.dutyMin; + dutyMax = options.dutyMax; + + // TODO: More robust clock check. Currently just assumes 120 MHz if USB Enabled, 125 MHz otherwise. + if ( PeripheralManager::getInstance().isUSBEnabled(0) ) + sysClock = 120000000; + else + sysClock = 125000000; + + gpio_set_function(leftMotorPin, GPIO_FUNC_PWM); + gpio_set_function(rightMotorPin, GPIO_FUNC_PWM); + leftMotorPinSlice = pwm_gpio_to_slice_num (leftMotorPin); + leftMotorPinChannel = pwm_gpio_to_channel (leftMotorPin); + rightMotorPinSlice = pwm_gpio_to_slice_num (rightMotorPin); + rightMotorPinChannel = pwm_gpio_to_channel (rightMotorPin); + pwmSetFreqDuty(leftMotorPinSlice, leftMotorPinChannel, pwmFrequency, 0); + pwmSetFreqDuty(rightMotorPinSlice, rightMotorPinChannel, pwmFrequency, 0); + pwm_set_enabled(leftMotorPinSlice, true); + pwm_set_enabled(rightMotorPinSlice, true); + + if(isValidPin(motorSleepPin)) { + gpio_init(motorSleepPin); + gpio_set_dir(motorSleepPin, GPIO_OUT); + // turn on sleep mode + gpio_put(motorSleepPin, false); + } +} + +bool DRV8833RumbleAddon::compareRumbleState(Gamepad * gamepad) { + if (currentRumbleState.leftMotor == gamepad->rumbleState.leftMotor && currentRumbleState.rightMotor == gamepad->rumbleState.rightMotor) + return true; + + return false; + +} + +void DRV8833RumbleAddon::setRumbleState(Gamepad * gamepad) { + currentRumbleState.leftMotor = gamepad->rumbleState.leftMotor; + currentRumbleState.rightMotor = gamepad->rumbleState.rightMotor; +} + +void DRV8833RumbleAddon::disableMotors() { + // if motorSleepPin set and all motors are off, enable motor driver sleep mode + if (isValidPin(motorSleepPin)) + gpio_put(motorSleepPin, false); + + pwmSetFreqDuty(leftMotorPinSlice, leftMotorPinChannel, pwmFrequency, 0); + pwmSetFreqDuty(rightMotorPinSlice, rightMotorPinChannel, pwmFrequency, 0); +} + +void DRV8833RumbleAddon::enableMotors(Gamepad * gamepad) { + pwmSetFreqDuty(leftMotorPinSlice, leftMotorPinChannel, pwmFrequency, (gamepad->rumbleState.leftMotor == 0) ? 0 : scaleDuty(motorToDuty(gamepad->rumbleState.leftMotor), dutyMin, dutyMax)); + pwmSetFreqDuty(rightMotorPinSlice, rightMotorPinChannel, pwmFrequency, (gamepad->rumbleState.rightMotor == 0) ? 0 : scaleDuty(motorToDuty(gamepad->rumbleState.rightMotor), dutyMin, dutyMax)); + + // if motorSleepPin set and any motors are on, disable motor driver sleep mode + if (isValidPin(motorSleepPin)) + gpio_put(motorSleepPin, true); +} + +void DRV8833RumbleAddon::process() { + Gamepad * gamepad = Storage::getInstance().GetGamepad(); + + if (!compareRumbleState(gamepad)) { + setRumbleState(gamepad); + if (!(gamepad->rumbleState.leftMotor || gamepad->rumbleState.rightMotor)) { + disableMotors(); + return; + } + enableMotors(gamepad); + } +} + +uint32_t DRV8833RumbleAddon::pwmSetFreqDuty(uint slice, uint channel, uint32_t frequency, float duty) { + uint32_t divider16 = sysClock / frequency / 4096 + + (sysClock % (frequency * 4096) != 0); + if (divider16 / 16 == 0) + divider16 = 16; + uint32_t wrap = sysClock * 16 / divider16 / frequency - 1; + pwm_set_clkdiv_int_frac(slice, divider16/16, + divider16 & 0xF); + pwm_set_wrap(slice, wrap); + pwm_set_chan_level(slice, channel, wrap * duty / 100); + return wrap; +} + diff --git a/src/config_utils.cpp b/src/config_utils.cpp index bca144e41..d5985e82d 100644 --- a/src/config_utils.cpp +++ b/src/config_utils.cpp @@ -33,6 +33,7 @@ #include "addons/input_macro.h" #include "addons/rotaryencoder.h" #include "addons/i2c_gpio_pcf8575.h" +#include "addons/drv8833_rumble.h" #include "CRC32.h" #include "FlashPROM.h" @@ -456,7 +457,7 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) INIT_UNSET_PROPERTY(config.ledOptions, pledPin2, PLED2_PIN); INIT_UNSET_PROPERTY(config.ledOptions, pledPin3, PLED3_PIN); INIT_UNSET_PROPERTY(config.ledOptions, pledPin4, PLED4_PIN); - INIT_UNSET_PROPERTY(config.ledOptions, pledColor, static_cast(PLED_COLOR.r) << 16 | static_cast(PLED_COLOR.g) << 8 | static_cast(PLED_COLOR.b)); + INIT_UNSET_PROPERTY(config.ledOptions, pledColor, static_cast(PLED_COLOR.r) << 16 | static_cast(PLED_COLOR.g) << 8 | static_cast(PLED_COLOR.b)); // hacky, but previous versions used PLED1_PIN for either PWM GPIO pins or RGB indexes // so we're just going to copy the defined values into both locations and have the migration // to pin mappings sort it out @@ -695,6 +696,15 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) // reminder that this must be set or else nanopb won't retain anything config.addonOptions.pcf8575Options.pins_count = PCF8575_PIN_COUNT; + // addonOptions.drv8833RumbleOptions + INIT_UNSET_PROPERTY(config.addonOptions.drv8833RumbleOptions, enabled, !!DRV8833_RUMBLE_ENABLED); + INIT_UNSET_PROPERTY(config.addonOptions.drv8833RumbleOptions, leftMotorPin, DRV8833_RUMBLE_LEFT_MOTOR_PIN); + INIT_UNSET_PROPERTY(config.addonOptions.drv8833RumbleOptions, rightMotorPin, DRV8833_RUMBLE_RIGHT_MOTOR_PIN); + INIT_UNSET_PROPERTY(config.addonOptions.drv8833RumbleOptions, motorSleepPin, DRV8833_RUMBLE_MOTOR_SLEEP_PIN); + INIT_UNSET_PROPERTY(config.addonOptions.drv8833RumbleOptions, pwmFrequency, DRV8833_RUMBLE_PWM_FREQUENCY); + INIT_UNSET_PROPERTY(config.addonOptions.drv8833RumbleOptions, dutyMin, DRV8833_RUMBLE_DUTY_MIN); + INIT_UNSET_PROPERTY(config.addonOptions.drv8833RumbleOptions, dutyMax, DRV8833_RUMBLE_DUTY_MAX); + // addonOptions.rotaryOptions INIT_UNSET_PROPERTY(config.addonOptions.rotaryOptions, enabled, !!ROTARY_ENCODER_ENABLED); INIT_UNSET_PROPERTY(config.addonOptions.rotaryOptions.encoderOne, enabled, !!ENCODER_ONE_ENABLED); @@ -1004,7 +1014,7 @@ void gpioMappingsMigrationCore(Config& config) GPIO_PIN_21, GPIO_PIN_22, GPIO_PIN_23, GPIO_PIN_24, GPIO_PIN_25, GPIO_PIN_26, GPIO_PIN_27, GPIO_PIN_28, GPIO_PIN_29}; - + // If we didn't import from protobuf, import from boardconfig for(unsigned int i = 0; i < NUM_BANK0_GPIOS; i++) { fromBoardConfig(i, boardConfig[i]); @@ -1012,28 +1022,28 @@ void gpioMappingsMigrationCore(Config& config) // migrate I2C addons to use peripheral manager if (!peripheralOptions.blockI2C0.enabled && ( - (config.displayOptions.enabled && (config.displayOptions.i2cBlock == 0)) || - (config.addonOptions.analogADS1219Options.enabled && (config.addonOptions.analogADS1219Options.i2cBlock == 0)) || + (config.displayOptions.enabled && (config.displayOptions.i2cBlock == 0)) || + (config.addonOptions.analogADS1219Options.enabled && (config.addonOptions.analogADS1219Options.i2cBlock == 0)) || (config.addonOptions.wiiOptions.enabled && (config.addonOptions.wiiOptions.i2cBlock == 0)) ) ) { peripheralOptions.blockI2C0.enabled = ( - (config.displayOptions.enabled && (config.displayOptions.i2cBlock == 0)) | - (config.addonOptions.analogADS1219Options.enabled && (config.addonOptions.analogADS1219Options.i2cBlock == 0)) | - (config.addonOptions.wiiOptions.enabled && (config.addonOptions.wiiOptions.i2cBlock == 0)) | + (config.displayOptions.enabled && (config.displayOptions.i2cBlock == 0)) | + (config.addonOptions.analogADS1219Options.enabled && (config.addonOptions.analogADS1219Options.i2cBlock == 0)) | + (config.addonOptions.wiiOptions.enabled && (config.addonOptions.wiiOptions.i2cBlock == 0)) | (!!I2C0_ENABLED) ); - + // pin configuration peripheralOptions.blockI2C0.sda = ( - isValidPin(config.displayOptions.deprecatedI2cSDAPin) && (config.displayOptions.i2cBlock == 0) ? - config.displayOptions.deprecatedI2cSDAPin : + isValidPin(config.displayOptions.deprecatedI2cSDAPin) && (config.displayOptions.i2cBlock == 0) ? + config.displayOptions.deprecatedI2cSDAPin : ( - isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSDAPin) && (config.addonOptions.analogADS1219Options.i2cBlock == 0) ? - config.addonOptions.analogADS1219Options.deprecatedI2cSDAPin : + isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSDAPin) && (config.addonOptions.analogADS1219Options.i2cBlock == 0) ? + config.addonOptions.analogADS1219Options.deprecatedI2cSDAPin : ( - isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSDAPin) && (config.addonOptions.wiiOptions.i2cBlock == 0) ? - config.addonOptions.wiiOptions.deprecatedI2cSDAPin : + isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSDAPin) && (config.addonOptions.wiiOptions.i2cBlock == 0) ? + config.addonOptions.wiiOptions.deprecatedI2cSDAPin : I2C0_PIN_SDA ) ) @@ -1041,14 +1051,14 @@ void gpioMappingsMigrationCore(Config& config) markAddonPinIfUsed(peripheralOptions.blockI2C0.sda); peripheralOptions.blockI2C0.scl = ( - isValidPin(config.displayOptions.deprecatedI2cSCLPin) && (config.displayOptions.i2cBlock == 0) ? - config.displayOptions.deprecatedI2cSCLPin : + isValidPin(config.displayOptions.deprecatedI2cSCLPin) && (config.displayOptions.i2cBlock == 0) ? + config.displayOptions.deprecatedI2cSCLPin : ( - isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSCLPin) && (config.addonOptions.analogADS1219Options.i2cBlock == 0) ? - config.addonOptions.analogADS1219Options.deprecatedI2cSCLPin : + isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSCLPin) && (config.addonOptions.analogADS1219Options.i2cBlock == 0) ? + config.addonOptions.analogADS1219Options.deprecatedI2cSCLPin : ( - isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSCLPin) && (config.addonOptions.wiiOptions.i2cBlock == 0) ? - config.addonOptions.wiiOptions.deprecatedI2cSCLPin : + isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSCLPin) && (config.addonOptions.wiiOptions.i2cBlock == 0) ? + config.addonOptions.wiiOptions.deprecatedI2cSCLPin : I2C0_PIN_SCL ) ) @@ -1057,14 +1067,14 @@ void gpioMappingsMigrationCore(Config& config) // option configuration peripheralOptions.blockI2C0.speed = ( - isValidPin(config.displayOptions.deprecatedI2cSpeed) && (config.displayOptions.i2cBlock == 0) ? - config.displayOptions.deprecatedI2cSpeed : + isValidPin(config.displayOptions.deprecatedI2cSpeed) && (config.displayOptions.i2cBlock == 0) ? + config.displayOptions.deprecatedI2cSpeed : ( - isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSpeed) && (config.addonOptions.analogADS1219Options.i2cBlock == 0) ? - config.addonOptions.analogADS1219Options.deprecatedI2cSpeed : + isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSpeed) && (config.addonOptions.analogADS1219Options.i2cBlock == 0) ? + config.addonOptions.analogADS1219Options.deprecatedI2cSpeed : ( - isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSpeed) && (config.addonOptions.wiiOptions.i2cBlock == 0) ? - config.addonOptions.wiiOptions.deprecatedI2cSpeed : + isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSpeed) && (config.addonOptions.wiiOptions.i2cBlock == 0) ? + config.addonOptions.wiiOptions.deprecatedI2cSpeed : I2C0_SPEED ) ) @@ -1072,28 +1082,28 @@ void gpioMappingsMigrationCore(Config& config) } if (!peripheralOptions.blockI2C1.enabled && ( - (config.displayOptions.enabled && (config.displayOptions.i2cBlock == 1)) || - (config.addonOptions.analogADS1219Options.enabled && (config.addonOptions.analogADS1219Options.i2cBlock == 1)) || + (config.displayOptions.enabled && (config.displayOptions.i2cBlock == 1)) || + (config.addonOptions.analogADS1219Options.enabled && (config.addonOptions.analogADS1219Options.i2cBlock == 1)) || (config.addonOptions.wiiOptions.enabled && (config.addonOptions.wiiOptions.i2cBlock == 1)) ) ) { peripheralOptions.blockI2C1.enabled = ( - (config.displayOptions.enabled && (config.displayOptions.i2cBlock == 1)) | - (config.addonOptions.analogADS1219Options.enabled && (config.addonOptions.analogADS1219Options.i2cBlock == 1)) | - (config.addonOptions.wiiOptions.enabled && (config.addonOptions.wiiOptions.i2cBlock == 1)) | + (config.displayOptions.enabled && (config.displayOptions.i2cBlock == 1)) | + (config.addonOptions.analogADS1219Options.enabled && (config.addonOptions.analogADS1219Options.i2cBlock == 1)) | + (config.addonOptions.wiiOptions.enabled && (config.addonOptions.wiiOptions.i2cBlock == 1)) | (!!I2C1_ENABLED) ); - + // pin configuration peripheralOptions.blockI2C1.sda = ( - isValidPin(config.displayOptions.deprecatedI2cSDAPin) && (config.displayOptions.i2cBlock == 1) ? - config.displayOptions.deprecatedI2cSDAPin : + isValidPin(config.displayOptions.deprecatedI2cSDAPin) && (config.displayOptions.i2cBlock == 1) ? + config.displayOptions.deprecatedI2cSDAPin : ( - isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSDAPin) && (config.addonOptions.analogADS1219Options.i2cBlock == 1) ? - config.addonOptions.analogADS1219Options.deprecatedI2cSDAPin : + isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSDAPin) && (config.addonOptions.analogADS1219Options.i2cBlock == 1) ? + config.addonOptions.analogADS1219Options.deprecatedI2cSDAPin : ( - isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSDAPin) && (config.addonOptions.wiiOptions.i2cBlock == 1) ? - config.addonOptions.wiiOptions.deprecatedI2cSDAPin : + isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSDAPin) && (config.addonOptions.wiiOptions.i2cBlock == 1) ? + config.addonOptions.wiiOptions.deprecatedI2cSDAPin : I2C1_PIN_SDA ) ) @@ -1101,14 +1111,14 @@ void gpioMappingsMigrationCore(Config& config) markAddonPinIfUsed(peripheralOptions.blockI2C1.sda); peripheralOptions.blockI2C1.scl = ( - isValidPin(config.displayOptions.deprecatedI2cSCLPin) && (config.displayOptions.i2cBlock == 1) ? - config.displayOptions.deprecatedI2cSCLPin : + isValidPin(config.displayOptions.deprecatedI2cSCLPin) && (config.displayOptions.i2cBlock == 1) ? + config.displayOptions.deprecatedI2cSCLPin : ( - isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSCLPin) && (config.addonOptions.analogADS1219Options.i2cBlock == 1) ? - config.addonOptions.analogADS1219Options.deprecatedI2cSCLPin : + isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSCLPin) && (config.addonOptions.analogADS1219Options.i2cBlock == 1) ? + config.addonOptions.analogADS1219Options.deprecatedI2cSCLPin : ( - isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSCLPin) && (config.addonOptions.wiiOptions.i2cBlock == 1) ? - config.addonOptions.wiiOptions.deprecatedI2cSCLPin : + isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSCLPin) && (config.addonOptions.wiiOptions.i2cBlock == 1) ? + config.addonOptions.wiiOptions.deprecatedI2cSCLPin : I2C1_PIN_SCL ) ) @@ -1117,14 +1127,14 @@ void gpioMappingsMigrationCore(Config& config) // option configuration peripheralOptions.blockI2C1.speed = ( - isValidPin(config.displayOptions.deprecatedI2cSpeed) && (config.displayOptions.i2cBlock == 1) ? - config.displayOptions.deprecatedI2cSpeed : + isValidPin(config.displayOptions.deprecatedI2cSpeed) && (config.displayOptions.i2cBlock == 1) ? + config.displayOptions.deprecatedI2cSpeed : ( - isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSpeed) && (config.addonOptions.analogADS1219Options.i2cBlock == 1) ? - config.addonOptions.analogADS1219Options.deprecatedI2cSpeed : + isValidPin(config.addonOptions.analogADS1219Options.deprecatedI2cSpeed) && (config.addonOptions.analogADS1219Options.i2cBlock == 1) ? + config.addonOptions.analogADS1219Options.deprecatedI2cSpeed : ( - isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSpeed) && (config.addonOptions.wiiOptions.i2cBlock == 1) ? - config.addonOptions.wiiOptions.deprecatedI2cSpeed : + isValidPin(config.addonOptions.wiiOptions.deprecatedI2cSpeed) && (config.addonOptions.wiiOptions.i2cBlock == 1) ? + config.addonOptions.wiiOptions.deprecatedI2cSpeed : I2C1_SPEED ) ) @@ -1137,22 +1147,22 @@ void gpioMappingsMigrationCore(Config& config) if (peripheralOptions.blockUSB0.enabled) { peripheralOptions.blockUSB0.enable5v = ( - isValidPin(keyboardHostOptions.deprecatedPin5V) ? - keyboardHostOptions.deprecatedPin5V : + isValidPin(keyboardHostOptions.deprecatedPin5V) ? + keyboardHostOptions.deprecatedPin5V : ( - isValidPin(psPassthroughOptions.deprecatedPin5V) ? - psPassthroughOptions.deprecatedPin5V : + isValidPin(psPassthroughOptions.deprecatedPin5V) ? + psPassthroughOptions.deprecatedPin5V : -1 ) ); markAddonPinIfUsed(peripheralOptions.blockUSB0.enable5v); peripheralOptions.blockUSB0.dp = ( - isValidPin(keyboardHostOptions.deprecatedPinDplus) ? - keyboardHostOptions.deprecatedPinDplus : + isValidPin(keyboardHostOptions.deprecatedPinDplus) ? + keyboardHostOptions.deprecatedPinDplus : ( - isValidPin(psPassthroughOptions.deprecatedPinDplus) ? - psPassthroughOptions.deprecatedPinDplus : + isValidPin(psPassthroughOptions.deprecatedPinDplus) ? + psPassthroughOptions.deprecatedPinDplus : -1 ) ); @@ -1288,7 +1298,7 @@ void migrateAuthenticationMethods(Config& config) { gamepadOptions.ps4AuthType = InputModeAuthType::INPUT_MODE_AUTH_TYPE_KEYS; ps4Options.enabled = false; // disable PS4-Mode add-on permanently } - + if ( psPassthroughOptions.enabled == true ) { // PS5 add-on "on", USB pass through, update ps4->ps5 boot gamepadOptions.ps5AuthType = InputModeAuthType::INPUT_MODE_AUTH_TYPE_USB; // If current mode is PS4, update to PS5 @@ -1325,7 +1335,7 @@ void migrateMacroPinsToGpio(Config& config) { macroOptions.deprecatedPin = -1; // set our turbo options to -1 for subsequent calls macroOptions.has_deprecatedPin = false; } - + if ( macroOptions.macroList_count == MAX_MACRO_LIMIT ) { const static GpioAction actionList[6] = { GpioAction::BUTTON_PRESS_MACRO_1, GpioAction::BUTTON_PRESS_MACRO_2, GpioAction::BUTTON_PRESS_MACRO_3, GpioAction::BUTTON_PRESS_MACRO_4, @@ -1519,7 +1529,7 @@ static void setHasFlags(const pb_msgdesc_t* fields, void* s) { return; } - + do { // Not implemented for extension fields @@ -1742,7 +1752,7 @@ std::string ConfigUtils::toJSON(const Config& config) // From JSON // ----------------------------------------------------- -#define TEST_VALUE(name, value) if (v == value) return true; +#define TEST_VALUE(name, value) if (v == value) return true; #define GEN_IS_VALID_ENUM_VALUE_FUNCTION(enumtype) \ static bool isValid ## enumtype(int v) \ diff --git a/src/configs/webconfig.cpp b/src/configs/webconfig.cpp index 8dc0bc135..1e0d551dd 100644 --- a/src/configs/webconfig.cpp +++ b/src/configs/webconfig.cpp @@ -842,7 +842,7 @@ std::string getButtonLayoutDefs() for (layoutCtr = _ButtonLayout_MIN; layoutCtr < _ButtonLayout_ARRAYSIZE; layoutCtr++) { writeDoc(doc, "buttonLayout", LayoutManager::getInstance().getButtonLayoutName((ButtonLayout)layoutCtr), layoutCtr); } - + for (layoutCtr = _ButtonLayoutRight_MIN; layoutCtr < _ButtonLayoutRight_ARRAYSIZE; layoutCtr++) { writeDoc(doc, "buttonLayoutRight", LayoutManager::getInstance().getButtonLayoutRightName((ButtonLayoutRight)layoutCtr), layoutCtr); } @@ -856,7 +856,7 @@ std::string getButtonLayouts() const LEDOptions& ledOptions = Storage::getInstance().getLedOptions(); const DisplayOptions& displayOptions = Storage::getInstance().getDisplayOptions(); uint16_t elementCtr = 0; - + LayoutManager::LayoutList layoutA = LayoutManager::getInstance().getLayoutA(); LayoutManager::LayoutList layoutB = LayoutManager::getInstance().getLayoutB(); @@ -917,7 +917,7 @@ std::string getButtonLayouts() writeDoc(ele, "parameters", "closed", layoutB[elementCtr].parameters.closed); writeDoc(doc, "displayLayouts", "buttonLayoutRight", std::to_string(elementCtr), ele); } - + return serialize_json(doc); } @@ -1499,6 +1499,15 @@ std::string setAddonOptions() docToValue(pcf8575Options.i2cBlock, doc, "pcf8575Block"); docToValue(pcf8575Options.enabled, doc, "PCF8575AddonEnabled"); + DRV8833RumbleOptions& drv8833RumbleOptions = Storage::getInstance().getAddonOptions().drv8833RumbleOptions; + docToValue(drv8833RumbleOptions.enabled, doc, "DRV8833RumbleAddonEnabled"); + docToPin(drv8833RumbleOptions.leftMotorPin, doc, "drv8833RumbleLeftMotorPin"); + docToPin(drv8833RumbleOptions.rightMotorPin, doc, "drv8833RumbleRightMotorPin"); + docToPin(drv8833RumbleOptions.motorSleepPin, doc, "drv8833RumbleMotorSleepPin"); + docToValue(drv8833RumbleOptions.pwmFrequency, doc, "drv8833RumblePWMFrequency"); + docToValue(drv8833RumbleOptions.dutyMin, doc, "drv8833RumbleDutyMin"); + docToValue(drv8833RumbleOptions.dutyMax, doc, "drv8833RumbleDutyMax"); + Storage::getInstance().save(); return serialize_json(doc); @@ -1923,6 +1932,15 @@ std::string getAddonOptions() writeDoc(doc, "pcf8575Block", pcf8575Options.i2cBlock); writeDoc(doc, "PCF8575AddonEnabled", pcf8575Options.enabled); + const DRV8833RumbleOptions& drv8833RumbleOptions = Storage::getInstance().getAddonOptions().drv8833RumbleOptions; + writeDoc(doc, "DRV8833RumbleAddonEnabled", drv8833RumbleOptions.enabled); + writeDoc(doc, "drv8833RumbleLeftMotorPin", cleanPin(drv8833RumbleOptions.leftMotorPin)); + writeDoc(doc, "drv8833RumbleRightMotorPin", cleanPin(drv8833RumbleOptions.rightMotorPin)); + writeDoc(doc, "drv8833RumbleMotorSleepPin", cleanPin(drv8833RumbleOptions.motorSleepPin)); + writeDoc(doc, "drv8833RumblePWMFrequency", drv8833RumbleOptions.pwmFrequency); + writeDoc(doc, "drv8833RumbleDutyMin", drv8833RumbleOptions.dutyMin); + writeDoc(doc, "drv8833RumbleDutyMax", drv8833RumbleOptions.dutyMax); + return serialize_json(doc); } diff --git a/src/drivers/xinput/XInputDriver.cpp b/src/drivers/xinput/XInputDriver.cpp index f26af3be6..3fc91c042 100644 --- a/src/drivers/xinput/XInputDriver.cpp +++ b/src/drivers/xinput/XInputDriver.cpp @@ -28,6 +28,9 @@ uint8_t endpoint_in = 0; uint8_t endpoint_out = 0; uint8_t xinput_out_buffer[XINPUT_OUT_SIZE] = {}; +#include +#include "pico/stdlib.h" + static bool authDriverPresent = false; static void xinput_init(void) { @@ -119,6 +122,15 @@ static bool xinput_xfer_callback(uint8_t rhport, uint8_t ep_addr, xfer_result_t return true; } +static void check_and_set_rumble(Gamepad * gamepad, uint8_t * buffer) { + // Check for rumble bytes - starts with 0x00 0x08 + if (!(buffer[0] == 0x00 && buffer[1] == 0x08)) + return; + + gamepad->rumbleState.leftMotor = buffer[3]; + gamepad->rumbleState.rightMotor = buffer[4]; +} + void XInputDriver::initialize() { xinputReport = { .report_id = 0, @@ -221,14 +233,20 @@ void XInputDriver::process(Gamepad * gamepad, uint8_t * outBuffer) { } } - // check for player LEDs + // clear potential initial uncaught data in endpoint_out from before registration of xfer_cb if (tud_ready() && (endpoint_out != 0) && (!usbd_edpt_busy(0, endpoint_out))) { usbd_edpt_claim(0, endpoint_out); // Take control of OUT endpoint - usbd_edpt_xfer(0, endpoint_out, outBuffer, XINPUT_OUT_SIZE); // Retrieve report buffer + usbd_edpt_xfer(0, endpoint_out, xinput_out_buffer, XINPUT_OUT_SIZE); // Retrieve report buffer usbd_edpt_release(0, endpoint_out); // Release control of OUT endpoint } + + if (memcmp(xinput_out_buffer, outBuffer, XINPUT_OUT_SIZE) != 0) { // check if new write to xinput_out_buffer from xinput_xfer_callback + memcpy(outBuffer, xinput_out_buffer, XINPUT_OUT_SIZE); + // Check if this new write to endpoint_out is a rumble packet + check_and_set_rumble(gamepad, outBuffer); + } } void XInputDriver::processAux() { diff --git a/src/gamepad.cpp b/src/gamepad.cpp index b57a36630..38ab68d08 100644 --- a/src/gamepad.cpp +++ b/src/gamepad.cpp @@ -252,7 +252,7 @@ void Gamepad::process() void Gamepad::read() { Mask_t values = Storage::getInstance().GetGamepad()->debouncedGpio; - + // Get the midpoint value for the current mode uint16_t joystickMid = GAMEPAD_JOYSTICK_MID; if ( DriverManager::getInstance().getDriver() != nullptr ) { @@ -317,7 +317,7 @@ void Gamepad::hotkey() { if (options.lockHotkeys) return; - + GamepadHotkey action = HOTKEY_NONE; if (pressedHotkey(hotkeyOptions.hotkey01)) action = selectHotkey(hotkeyOptions.hotkey01); else if (pressedHotkey(hotkeyOptions.hotkey02)) action = selectHotkey(hotkeyOptions.hotkey02); @@ -354,6 +354,13 @@ void Gamepad::clearState() { state.rt = 0; } +void Gamepad::clearRumbleState() { + rumbleState.leftMotor = 0; + rumbleState.rightMotor = 0; + rumbleState.leftTrigger = 0; + rumbleState.rightTrigger = 0; +} + /** * @brief Take a hotkey action if it hasn't already been taken, modifying state/options appropriately. */ @@ -467,7 +474,7 @@ void Gamepad::processHotkeyAction(GamepadHotkey action) { break; case HOTKEY_TOUCHPAD_BUTTON: state.buttons |= GAMEPAD_MASK_A2; - break; + break; case HOTKEY_INVERT_X_AXIS: if (action != lastAction) { options.invertXAxis = !options.invertXAxis; diff --git a/src/gp2040aux.cpp b/src/gp2040aux.cpp index 6b352c3c0..531c31604 100644 --- a/src/gp2040aux.cpp +++ b/src/gp2040aux.cpp @@ -11,6 +11,7 @@ #include "addons/display.h" #include "addons/pleds.h" #include "addons/neopicoleds.h" +#include "addons/drv8833_rumble.h" #include @@ -33,12 +34,13 @@ void GP2040Aux::setup() { addons.LoadAddon(new PlayerLEDAddon(), CORE1_LOOP); addons.LoadAddon(new BoardLedAddon(), CORE1_LOOP); addons.LoadAddon(new BuzzerSpeakerAddon(), CORE1_LOOP); + addons.LoadAddon(new DRV8833RumbleAddon(), CORE1_LOOP); // Initialize our input driver's auxilliary functions inputDriver = DriverManager::getInstance().getDriver(); if ( inputDriver != nullptr ) { inputDriver->initializeAux(); - + // Check if we have a USB listener USBListener * listener = inputDriver->get_usb_auth_listener(); if (listener != nullptr) { @@ -53,7 +55,7 @@ void GP2040Aux::setup() { void GP2040Aux::run() { while (1) { addons.ProcessAddons(CORE1_LOOP); - + // Run auxiliary functions for input driver on Core1 if ( inputDriver != nullptr ) { inputDriver->processAux(); diff --git a/www/server/app.js b/www/server/app.js index 3d734e02c..d27c82403 100644 --- a/www/server/app.js +++ b/www/server/app.js @@ -437,6 +437,12 @@ app.get('/api/getAddonsOptions', (req, res) => { buzzerPin: -1, buzzerEnablePin: -1, buzzerVolume: 100, + drv8833RumbleLeftMotorPin: -1, + drv8833RumbleRightMotorPin: -1, + drv8833RumbleMotorSleepPin: -1, + drv8833RumblePWMFrequency: 10000, + drv8833RumbleDutyMin: 0, + drv8833RumbleDutyMax: 100, focusModePin: -1, focusModeButtonLockMask: 0, focusModeButtonLockEnabled: 0, @@ -508,6 +514,7 @@ app.get('/api/getAddonsOptions', (req, res) => { RotaryAddonEnabled: 1, pcf8575Block: 0, PCF8575AddonEnabled: 1, + DRV8833RumbleAddonEnabled: 1, usedPins: Object.values(picoController), }); }); diff --git a/www/src/Addons/DRV8833.tsx b/www/src/Addons/DRV8833.tsx new file mode 100644 index 000000000..1d47fc4a6 --- /dev/null +++ b/www/src/Addons/DRV8833.tsx @@ -0,0 +1,156 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { FormCheck, Row } from 'react-bootstrap'; +import * as yup from 'yup'; + +import Section from '../Components/Section'; + +import FormControl from '../Components/FormControl'; + +export const drv8833RumbleScheme = { + DRV8833RumbleAddonEnabled: yup + .number() + .required() + .label('DRV8833 Rumble Add-On Enabled'), + drv8833RumbleLeftMotorPin: yup + .number() + .label('Left Motor Pin') + .validatePinWhenValue('DRV8833RumbleAddonEnabled'), + drv8833RumbleRightMotorPin: yup + .number() + .label('Right Motor Pin') + .validatePinWhenValue('DRV8833RumbleAddonEnabled'), + drv8833RumbleMotorSleepPin: yup + .number() + .label('Motor Sleep Pin') + .validatePinWhenValue('DRV8833RumbleAddonEnabled'), + drv8833RumblePWMFrequency: yup + .number() + .label('PWM Frequency') + .validateRangeWhenValue('DRV8833RumbleAddonEnabled', 0, 50000), + drv8833RumbleDutyMin: yup + .number() + .label('Minimum PWM Duty') + .validateRangeWhenValue('DRV8833RumbleAddonEnabled', 0, 100), + drv8833RumbleDutyMax: yup + .number() + .label('Maximum PWM Duty') + .validateRangeWhenValue('DRV8833RumbleAddonEnabled', yup.ref('drv8833RumbleDutyMin'), 100), +}; + +export const drv8833RumbleState = { + DRV8833RumbleAddonEnabled: 0, + drv8833RumbleLeftMotorPin: -1, + drv8833RumbleRightMotorPin: -1, + drv8833RumbleMotorSleepPin: -1, + drv8833RumblePWMFrequency: 10000, + drv8833RumbleDutyMin: 0, + drv8833RumbleDutyMax: 100, +}; + +const DRV8833Rumble = ({ values, errors, handleChange, handleCheckbox }) => { + const { t } = useTranslation(); + return ( +
+ + { + handleCheckbox('DRV8833RumbleAddonEnabled', values); + handleChange(e); + }} + /> +
+ ); +}; + +export default DRV8833Rumble; diff --git a/www/src/Locales/en/AddonsConfig.jsx b/www/src/Locales/en/AddonsConfig.jsx index 64b572cf5..f10e5315b 100644 --- a/www/src/Locales/en/AddonsConfig.jsx +++ b/www/src/Locales/en/AddonsConfig.jsx @@ -154,4 +154,11 @@ export default { 'tilt-socd-mode-0': 'Up Priority', 'tilt-socd-mode-1': 'Neutral', 'tilt-socd-mode-2': 'Last Win', + 'drv8833-rumble-header-text': 'DRV8833 Rumble Configuration', + 'drv8833-rumble-left-motor-pin-label': 'Left Motor Pin', + 'drv8833-rumble-right-motor-pin-label': 'Right Motor Pin', + 'drv8833-rumble-motor-sleep-pin-label': 'Motor Sleep Pin', + 'drv8833-rumble-pwm-frequency-label': 'PWM Frequency', + 'drv8833-rumble-duty-min-label': 'Minimum Duty Cycle', + 'drv8833-rumble-duty-max-label': 'Maximum Duty Cycle', }; diff --git a/www/src/Pages/AddonsConfigPage.jsx b/www/src/Pages/AddonsConfigPage.jsx index d73235738..f2ae576f6 100644 --- a/www/src/Pages/AddonsConfigPage.jsx +++ b/www/src/Pages/AddonsConfigPage.jsx @@ -51,6 +51,7 @@ import InputHistory, { } from '../Addons/InputHistory'; import Rotary, { rotaryScheme, rotaryState } from '../Addons/Rotary'; import PCF8575, { pcf8575Scheme, pcf8575State } from '../Addons/PCF8575'; +import DRV8833Rumble, { drv8833RumbleScheme, drv8833RumbleState } from '../Addons/DRV8833'; const schema = yup.object().shape({ ...analogScheme, @@ -72,6 +73,7 @@ const schema = yup.object().shape({ ...inputHistoryScheme, ...rotaryScheme, ...pcf8575Scheme, + ...drv8833RumbleScheme, }); const defaultValues = { @@ -95,6 +97,7 @@ const defaultValues = { ...inputHistoryState, ...rotaryState, ...pcf8575State, + ...drv8833RumbleState, }; const ADDONS = [ @@ -118,6 +121,7 @@ const ADDONS = [ InputHistory, Rotary, PCF8575, + DRV8833Rumble, ]; const FormContext = ({ setStoredData }) => {