diff --git a/headers/addons/analog.h b/headers/addons/analog.h index 26dabc5bb..a8fbcd38e 100644 --- a/headers/addons/analog.h +++ b/headers/addons/analog.h @@ -61,6 +61,14 @@ #define AUTO_CALIBRATE_ENABLED 0 #endif +#ifndef ANALOG_SMOOTHING_ENABLED +#define ANALOG_SMOOTHING_ENABLED 0 +#endif + +#ifndef SMOOTHING_FACTOR +#define SMOOTHING_FACTOR 5 +#endif + // Analog Module Name #define AnalogName "Analog" @@ -78,6 +86,7 @@ class AnalogInput : public GPAddon { uint16_t adc_2_y_center = 0; static float readPin(int pin, uint16_t center, bool autoCalibrate); + static float emaCalculation(float ema_value, float smoothing_factor, float ema_previous); static uint16_t map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max); static float magnitudeCalculation(float x, float y, float& x_magnitude, float& y_magnitude); static void radialDeadzone(float& x, float& y, float in_deadzone, float out_deadzone, float x_magnitude, float y_magnitude, float magnitude, bool circularity); diff --git a/headers/gamepad/GamepadState.h b/headers/gamepad/GamepadState.h index 803b13256..470aa3da8 100644 --- a/headers/gamepad/GamepadState.h +++ b/headers/gamepad/GamepadState.h @@ -154,6 +154,10 @@ struct GamepadState uint16_t ry {GAMEPAD_JOYSTICK_MID}; uint8_t lt {0}; uint8_t rt {0}; + float ema_1_x {GAMEPAD_JOYSTICK_MID}; + float ema_1_y {GAMEPAD_JOYSTICK_MID}; + float ema_2_x {GAMEPAD_JOYSTICK_MID}; + float ema_2_y {GAMEPAD_JOYSTICK_MID}; }; // Convert the horizontal GamepadState dpad axis value into an analog value diff --git a/proto/config.proto b/proto/config.proto index 7770dfe7a..b0f96d39d 100644 --- a/proto/config.proto +++ b/proto/config.proto @@ -351,6 +351,8 @@ message AnalogOptions optional InvertMode analogAdc2Invert = 11; optional bool auto_calibrate = 12; optional uint32 outer_deadzone = 13; + optional bool analog_smoothing = 14; + optional float smoothing_factor = 15; } message TurboOptions diff --git a/src/addons/analog.cpp b/src/addons/analog.cpp index e3fa86476..aae3e26bf 100644 --- a/src/addons/analog.cpp +++ b/src/addons/analog.cpp @@ -64,6 +64,9 @@ void AnalogInput::process() float magnitude_1 = 0.0f; float magnitude_2 = 0.0f; + bool ema_option = analogOptions.analog_smoothing; + float ema_smoothing = analogOptions.smoothing_factor / 100.0f; + struct adc_pair { int x_pin; @@ -77,6 +80,8 @@ void AnalogInput::process() float& y_magnitude; InvertMode analog_invert; DpadMode analog_dpad; + float& x_ema; + float& y_ema; }; adc_pair adc_pairs[num_adc_pairs] = @@ -86,14 +91,16 @@ void AnalogInput::process() adc_1_x_center, adc_1_y_center, magnitude_1, x_magnitude_1, y_magnitude_1, analogOptions.analogAdc1Invert, - analogOptions.analogAdc1Mode}, + analogOptions.analogAdc1Mode, + gamepad->state.ema_1_x, gamepad->state.ema_1_y}, {analogOptions.analogAdc2PinX, analogOptions.analogAdc2PinY, adc_2_x, adc_2_y, adc_2_x_center, adc_2_y_center, magnitude_2, x_magnitude_2, y_magnitude_2, analogOptions.analogAdc2Invert, - analogOptions.analogAdc2Mode} + analogOptions.analogAdc2Mode, + gamepad->state.ema_2_x, gamepad->state.ema_2_y} }; for(size_t i = 0; i < num_adc_pairs; i++) { @@ -106,6 +113,11 @@ void AnalogInput::process() adc_pairs[i].x_value = ANALOG_MAX - adc_pairs[i].x_value; } + + if (ema_option) { + adc_pairs[i].x_value = emaCalculation(adc_pairs[i].x_value, ema_smoothing, adc_pairs[i].x_ema); + adc_pairs[i].x_ema = adc_pairs[i].x_value; + } } if (isValidPin(adc_pairs[i].y_pin)) { adc_select_input(adc_pairs[i].y_pin - ADC_PIN_OFFSET); @@ -116,6 +128,11 @@ void AnalogInput::process() adc_pairs[i].y_value = ANALOG_MAX - adc_pairs[i].y_value; } + + if (ema_option) { + adc_pairs[i].y_value = emaCalculation(adc_pairs[i].y_value, ema_smoothing, adc_pairs[i].y_ema); + adc_pairs[i].y_ema = adc_pairs[i].y_value; + } } if (in_deadzone >= 0.0f || analogOptions.forced_circularity == true) { @@ -171,6 +188,10 @@ float AnalogInput::readPin(int pin, uint16_t center, bool autoCalibrate) { return ((float)adc_calibrated) / ADC_MAX; } +float AnalogInput::emaCalculation(float ema_value, float smoothing_factor, float ema_previous) { + return (smoothing_factor * ema_value) + ((1.0f - smoothing_factor) * ema_previous); +} + uint16_t AnalogInput::map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } diff --git a/src/config_utils.cpp b/src/config_utils.cpp index 21844b3ed..fed18dcab 100644 --- a/src/config_utils.cpp +++ b/src/config_utils.cpp @@ -536,6 +536,8 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config) INIT_UNSET_PROPERTY(config.addonOptions.analogOptions, inner_deadzone, DEFAULT_INNER_DEADZONE); INIT_UNSET_PROPERTY(config.addonOptions.analogOptions, outer_deadzone, DEFAULT_OUTER_DEADZONE); INIT_UNSET_PROPERTY(config.addonOptions.analogOptions, auto_calibrate, !!AUTO_CALIBRATE_ENABLED); + INIT_UNSET_PROPERTY(config.addonOptions.analogOptions, analog_smoothing, !!ANALOG_SMOOTHING_ENABLED); + INIT_UNSET_PROPERTY(config.addonOptions.analogOptions, smoothing_factor, !!SMOOTHING_FACTOR); // addonOptions.turboOptions INIT_UNSET_PROPERTY(config.addonOptions.turboOptions, enabled, !!TURBO_ENABLED); diff --git a/src/configs/webconfig.cpp b/src/configs/webconfig.cpp index 1ce49081e..24b766cba 100644 --- a/src/configs/webconfig.cpp +++ b/src/configs/webconfig.cpp @@ -1396,6 +1396,8 @@ std::string setAddonOptions() docToValue(analogOptions.inner_deadzone, doc, "inner_deadzone"); docToValue(analogOptions.outer_deadzone, doc, "outer_deadzone"); docToValue(analogOptions.auto_calibrate, doc, "auto_calibrate"); + docToValue(analogOptions.analog_smoothing, doc, "analog_smoothing"); + docToValue(analogOptions.smoothing_factor, doc, "smoothing_factor"); docToValue(analogOptions.enabled, doc, "AnalogInputEnabled"); BootselButtonOptions& bootselButtonOptions = Storage::getInstance().getAddonOptions().bootselButtonOptions; @@ -1820,6 +1822,8 @@ std::string getAddonOptions() writeDoc(doc, "inner_deadzone", analogOptions.inner_deadzone); writeDoc(doc, "outer_deadzone", analogOptions.outer_deadzone); writeDoc(doc, "auto_calibrate", analogOptions.auto_calibrate); + writeDoc(doc, "analog_smoothing", analogOptions.analog_smoothing); + writeDoc(doc, "smoothing_factor", analogOptions.smoothing_factor); writeDoc(doc, "AnalogInputEnabled", analogOptions.enabled); const BootselButtonOptions& bootselButtonOptions = Storage::getInstance().getAddonOptions().bootselButtonOptions; diff --git a/www/server/app.js b/www/server/app.js index 35cb943cc..ffb33d157 100644 --- a/www/server/app.js +++ b/www/server/app.js @@ -429,6 +429,8 @@ app.get('/api/getAddonsOptions', (req, res) => { inner_deadzone: 5, outer_deadzone: 95, auto_calibrate: 0, + analog_smoothing: 0, + smoothing_factor: 5, bootselButtonMap: 0, buzzerPin: -1, buzzerEnablePin: -1, diff --git a/www/src/Addons/Analog.tsx b/www/src/Addons/Analog.tsx index 69f4ea6b6..c2b49a42e 100644 --- a/www/src/Addons/Analog.tsx +++ b/www/src/Addons/Analog.tsx @@ -73,6 +73,14 @@ export const analogScheme = { .number() .label('Auto Calibration') .validateRangeWhenValue('AnalogInputEnabled', 0, 1), + analog_smoothing: yup + .number() + .label('Analog Smoothing') + .validateRangeWhenValue('AnalogInputEnabled', 0, 1), + smoothing_factor: yup + .number() + .label('Smoothing Factor') + .validateRangeWhenValue('AnalogInputEnabled', 0, 100), }; export const analogState = { @@ -89,6 +97,8 @@ export const analogState = { inner_deadzone: 5, outer_deadzone: 95, auto_calibrate: 0, + analog_smoothing: 0, + smoothing_factor: 5, }; const Analog = ({ values, errors, handleChange, handleCheckbox }) => { @@ -279,6 +289,32 @@ const Analog = ({ values, errors, handleChange, handleCheckbox }) => { handleChange(e); }} /> + { + handleCheckbox('analog_smoothing', values); + handleChange(e); + }} + /> +