Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ src/usbhostmanager.cpp
src/config_legacy.cpp
src/config_utils.cpp
src/webconfig.cpp
src/addons/absolute_analog.cpp
src/addons/analog.cpp
src/addons/board_led.cpp
src/addons/bootsel_button.cpp
Expand Down
34 changes: 34 additions & 0 deletions headers/addons/absolute_analog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef _AbsoluteAnalog_H
#define _AbsoluteAnalog_H

#include "gpaddon.h"
#include "gamepad.h"

#ifndef ABSOLUTE_ANALOG_ENABLED
#define ABSOLUTE_ANALOG_ENABLED 0
#endif

#define ABSOLUTE_ANALOG_COUNT 9

// Axis values are stored as -255 to 255
#define ABSOLUTE_ANALOG_AXIS_MAX 255

#define AbsoluteAnalogName "AbsoluteAnalog"

// Also used by the hotkey handler in gamepad.cpp
void applyAbsoluteAnalogValue(GamepadState& state, uint8_t index);

class AbsoluteAnalogAddon : public GPAddon {
public:
virtual bool available();
virtual void setup();
virtual void process();
virtual void preprocess() {}
virtual void postprocess(bool sent) {}
virtual void reinit();
virtual std::string name() { return AbsoluteAnalogName; }
private:
Mask_t pinMasks[ABSOLUTE_ANALOG_COUNT];
};

#endif // _AbsoluteAnalog_H_
16 changes: 16 additions & 0 deletions proto/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,21 @@ message HETriggerOptions
optional int32 smoothingFactor = 13;
}

message AbsoluteAnalogEntry
{
optional bool enabled = 1;
optional AbsoluteAnalogStick stick = 2;
// -255 to 255, scaled to the gamepad axis range at runtime
optional int32 x = 3;
optional int32 y = 4;
}

message AbsoluteAnalogOptions
{
optional bool enabled = 1;
repeated AbsoluteAnalogEntry entries = 2 [(nanopb).max_count = 9];
}

message AddonOptions
{
optional BootselButtonOptions bootselButtonOptions = 1;
Expand Down Expand Up @@ -940,6 +955,7 @@ message AddonOptions
optional GamepadUSBHostOptions gamepadUSBHostOptions = 28;
optional TG16Options tg16Options = 29;
optional HETriggerOptions heTriggerOptions = 30;
optional AbsoluteAnalogOptions absoluteAnalogOptions = 31;
}

message MigrationHistory
Expand Down
27 changes: 27 additions & 0 deletions proto/enums.proto
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,24 @@ enum GpioAction
MODE_WHEEL_PEDAL_GAS = 128;
MODE_WHEEL_PEDAL_BRAKE = 129;
MODE_WHEEL_PEDAL_CLUTCH = 130;

ABSOLUTE_ANALOG_1 = 131;
ABSOLUTE_ANALOG_2 = 132;
ABSOLUTE_ANALOG_3 = 133;
ABSOLUTE_ANALOG_4 = 134;
ABSOLUTE_ANALOG_5 = 135;
ABSOLUTE_ANALOG_6 = 136;
ABSOLUTE_ANALOG_7 = 137;
ABSOLUTE_ANALOG_8 = 138;
ABSOLUTE_ANALOG_9 = 139;
}

enum AbsoluteAnalogStick
{
option (nanopb_enumopt).long_names = false;

ABSOLUTE_ANALOG_STICK_LEFT = 0;
ABSOLUTE_ANALOG_STICK_RIGHT = 1;
}

enum GpioDirection
Expand Down Expand Up @@ -463,6 +481,15 @@ enum GamepadHotkey
HOTKEY_RS_RIGHT = 85;
HOTKEY_REBOOT_WEBCONFIG = 86;
HOTKEY_REBOOT_USB = 87;
HOTKEY_ABSOLUTE_ANALOG_1 = 88;
HOTKEY_ABSOLUTE_ANALOG_2 = 89;
HOTKEY_ABSOLUTE_ANALOG_3 = 90;
HOTKEY_ABSOLUTE_ANALOG_4 = 91;
HOTKEY_ABSOLUTE_ANALOG_5 = 92;
HOTKEY_ABSOLUTE_ANALOG_6 = 93;
HOTKEY_ABSOLUTE_ANALOG_7 = 94;
HOTKEY_ABSOLUTE_ANALOG_8 = 95;
HOTKEY_ABSOLUTE_ANALOG_9 = 96;
}

enum AnimationEffects
Expand Down
76 changes: 76 additions & 0 deletions src/addons/absolute_analog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include "addons/absolute_analog.h"
#include "storagemanager.h"
#include "helper.h"
#include "config.pb.h"

// Scale -255..255 into the gamepad's 16-bit axis range
static uint16_t scaleAxis(int32_t value) {
if (value > ABSOLUTE_ANALOG_AXIS_MAX) value = ABSOLUTE_ANALOG_AXIS_MAX;
if (value < -ABSOLUTE_ANALOG_AXIS_MAX) value = -ABSOLUTE_ANALOG_AXIS_MAX;

int32_t scaled = (int32_t)GAMEPAD_JOYSTICK_MID +
((value * ((int32_t)GAMEPAD_JOYSTICK_MID + 1)) / ABSOLUTE_ANALOG_AXIS_MAX);

if (scaled < (int32_t)GAMEPAD_JOYSTICK_MIN) scaled = (int32_t)GAMEPAD_JOYSTICK_MIN;
if (scaled > (int32_t)GAMEPAD_JOYSTICK_MAX) scaled = (int32_t)GAMEPAD_JOYSTICK_MAX;

return (uint16_t)scaled;
}

void applyAbsoluteAnalogValue(GamepadState& state, uint8_t index) {
const AbsoluteAnalogOptions& options = Storage::getInstance().getAddonOptions().absoluteAnalogOptions;
if (!options.enabled || index >= options.entries_count) return;

const AbsoluteAnalogEntry& entry = options.entries[index];
if (!entry.enabled) return;

if (entry.stick == ABSOLUTE_ANALOG_STICK_RIGHT) {
state.rx = scaleAxis(entry.x);
state.ry = scaleAxis(entry.y);
} else {
state.lx = scaleAxis(entry.x);
state.ly = scaleAxis(entry.y);
}
}

bool AbsoluteAnalogAddon::available() {
const AbsoluteAnalogOptions& options = Storage::getInstance().getAddonOptions().absoluteAnalogOptions;
return options.enabled;
}

void AbsoluteAnalogAddon::setup() {
for (uint8_t index = 0; index < ABSOLUTE_ANALOG_COUNT; index++) {
pinMasks[index] = 0;
}

GpioMappingInfo* pinMappings = Storage::getInstance().getProfilePinMappings();
for (Pin_t pin = 0; pin < (Pin_t)NUM_BANK0_GPIOS; pin++)
{
switch (pinMappings[pin].action) {
case GpioAction::ABSOLUTE_ANALOG_1: pinMasks[0] |= 1 << pin; break;
case GpioAction::ABSOLUTE_ANALOG_2: pinMasks[1] |= 1 << pin; break;
case GpioAction::ABSOLUTE_ANALOG_3: pinMasks[2] |= 1 << pin; break;
case GpioAction::ABSOLUTE_ANALOG_4: pinMasks[3] |= 1 << pin; break;
case GpioAction::ABSOLUTE_ANALOG_5: pinMasks[4] |= 1 << pin; break;
case GpioAction::ABSOLUTE_ANALOG_6: pinMasks[5] |= 1 << pin; break;
case GpioAction::ABSOLUTE_ANALOG_7: pinMasks[6] |= 1 << pin; break;
case GpioAction::ABSOLUTE_ANALOG_8: pinMasks[7] |= 1 << pin; break;
case GpioAction::ABSOLUTE_ANALOG_9: pinMasks[8] |= 1 << pin; break;
default: break;
}
}
}

void AbsoluteAnalogAddon::reinit() {
setup();
}

void AbsoluteAnalogAddon::process() {
Gamepad * gamepad = Storage::getInstance().GetGamepad();

for (uint8_t index = 0; index < ABSOLUTE_ANALOG_COUNT; index++) {
if (pinMasks[index] && (gamepad->debouncedGpio & pinMasks[index])) {
applyAbsoluteAnalogValue(gamepad->state, index);
}
}
}
12 changes: 12 additions & 0 deletions src/config_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "BoardConfig.h"
#include "GamepadConfig.h"
#include "version.h"
#include "addons/absolute_analog.h"
#include "addons/analog.h"
#include "addons/board_led.h"
#include "addons/bootsel_button.h"
Expand Down Expand Up @@ -1094,6 +1095,17 @@ void ConfigUtils::initUnsetPropertiesWithDefaults(Config& config)
// reminder that this must be set or else nanopb won't retain anything
config.addonOptions.heTriggerOptions.triggers_count = HETRIGGER_COUNT;

// addonOptions.absoluteAnalogOptions
INIT_UNSET_PROPERTY(config.addonOptions.absoluteAnalogOptions, enabled, !!ABSOLUTE_ANALOG_ENABLED);
for (uint8_t absoluteAnalogIndex = 0; absoluteAnalogIndex < ABSOLUTE_ANALOG_COUNT; absoluteAnalogIndex++) {
AbsoluteAnalogEntry& absoluteAnalogEntry = config.addonOptions.absoluteAnalogOptions.entries[absoluteAnalogIndex];
INIT_UNSET_PROPERTY(absoluteAnalogEntry, enabled, false);
INIT_UNSET_PROPERTY(absoluteAnalogEntry, stick, ABSOLUTE_ANALOG_STICK_LEFT);
INIT_UNSET_PROPERTY(absoluteAnalogEntry, x, 0);
INIT_UNSET_PROPERTY(absoluteAnalogEntry, y, 0);
}
config.addonOptions.absoluteAnalogOptions.entries_count = ABSOLUTE_ANALOG_COUNT;

// keyboardMapping
INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions, enabled, KEYBOARD_HOST_ENABLED);
INIT_UNSET_PROPERTY(config.addonOptions.keyboardHostOptions, deprecatedPinDplus, KEYBOARD_HOST_PIN_DPLUS);
Expand Down
31 changes: 30 additions & 1 deletion src/gamepad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include "storagemanager.h"
#include "system.h"

#include "addons/absolute_analog.h"

// MUST BE DEFINED for mpgs
uint32_t getMillis() {
return to_ms_since_boot(get_absolute_time());
Expand Down Expand Up @@ -793,7 +795,34 @@ void Gamepad::processHotkeyAction(GamepadHotkey action) {
case HOTKEY_RS_RIGHT:
state.rx = GAMEPAD_JOYSTICK_MAX;
break;

case HOTKEY_ABSOLUTE_ANALOG_1:
applyAbsoluteAnalogValue(state, 0);
break;
case HOTKEY_ABSOLUTE_ANALOG_2:
applyAbsoluteAnalogValue(state, 1);
break;
case HOTKEY_ABSOLUTE_ANALOG_3:
applyAbsoluteAnalogValue(state, 2);
break;
case HOTKEY_ABSOLUTE_ANALOG_4:
applyAbsoluteAnalogValue(state, 3);
break;
case HOTKEY_ABSOLUTE_ANALOG_5:
applyAbsoluteAnalogValue(state, 4);
break;
case HOTKEY_ABSOLUTE_ANALOG_6:
applyAbsoluteAnalogValue(state, 5);
break;
case HOTKEY_ABSOLUTE_ANALOG_7:
applyAbsoluteAnalogValue(state, 6);
break;
case HOTKEY_ABSOLUTE_ANALOG_8:
applyAbsoluteAnalogValue(state, 7);
break;
case HOTKEY_ABSOLUTE_ANALOG_9:
applyAbsoluteAnalogValue(state, 8);
break;

default: // Unknown action
break;
}
Expand Down
2 changes: 2 additions & 0 deletions src/gp2040.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "usbhostmanager.h"

// Inputs for Core0
#include "addons/absolute_analog.h"
#include "addons/analog.h"
#include "addons/bootsel_button.h"
#include "addons/focus_mode.h"
Expand Down Expand Up @@ -118,6 +119,7 @@ void GP2040::setup() {
addons.LoadAddon(new TG16padInput());

// Input override addons
addons.LoadAddon(new AbsoluteAnalogAddon()); // Overrides analog stick input
addons.LoadAddon(new ReverseInput());
addons.LoadAddon(new TurboInput()); // Turbo overrides button states and should be close to the end
addons.LoadAddon(new InputMacro());
Expand Down
38 changes: 38 additions & 0 deletions src/webconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "lwip/apps/httpd.h"
#include "lwip/def.h"
#include "lwip/mem.h"
#include "addons/absolute_analog.h"
#include "addons/input_macro.h"

#define PATH_CGI_ACTION "/cgi/action"
Expand Down Expand Up @@ -108,6 +109,24 @@ static void __attribute__((noinline)) docToValue(T& value, const DynamicJsonDocu
}
}

// ArduinoJson stores const char* keys by pointer, so these must be literals, not a stack buffer.
static const struct {
const char* enabled;
const char* stick;
const char* x;
const char* y;
} ABSOLUTE_ANALOG_KEYS[ABSOLUTE_ANALOG_COUNT] = {
{ "absoluteAnalog1Enabled", "absoluteAnalog1Stick", "absoluteAnalog1X", "absoluteAnalog1Y" },
{ "absoluteAnalog2Enabled", "absoluteAnalog2Stick", "absoluteAnalog2X", "absoluteAnalog2Y" },
{ "absoluteAnalog3Enabled", "absoluteAnalog3Stick", "absoluteAnalog3X", "absoluteAnalog3Y" },
{ "absoluteAnalog4Enabled", "absoluteAnalog4Stick", "absoluteAnalog4X", "absoluteAnalog4Y" },
{ "absoluteAnalog5Enabled", "absoluteAnalog5Stick", "absoluteAnalog5X", "absoluteAnalog5Y" },
{ "absoluteAnalog6Enabled", "absoluteAnalog6Stick", "absoluteAnalog6X", "absoluteAnalog6Y" },
{ "absoluteAnalog7Enabled", "absoluteAnalog7Stick", "absoluteAnalog7X", "absoluteAnalog7Y" },
{ "absoluteAnalog8Enabled", "absoluteAnalog8Stick", "absoluteAnalog8X", "absoluteAnalog8Y" },
{ "absoluteAnalog9Enabled", "absoluteAnalog9Stick", "absoluteAnalog9X", "absoluteAnalog9Y" },
};

// Don't inline this function, we do not want to consume stack space in the calling function
static void __attribute__((noinline)) cleanAddonGpioMappings(Pin_t& addonPin, Pin_t oldAddonPin)
{
Expand Down Expand Up @@ -1939,6 +1958,16 @@ std::string setAddonOptions()
docToValue(heTriggerOptions.emaSmoothing, doc, "heTriggerSmoothing");
docToValue(heTriggerOptions.smoothingFactor, doc, "heTriggerSmoothingFactor");

AbsoluteAnalogOptions& absoluteAnalogOptions = Storage::getInstance().getAddonOptions().absoluteAnalogOptions;
docToValue(absoluteAnalogOptions.enabled, doc, "AbsoluteAnalogAddonEnabled");
for (int index = 0; index < ABSOLUTE_ANALOG_COUNT; index++) {
docToValue(absoluteAnalogOptions.entries[index].enabled, doc, ABSOLUTE_ANALOG_KEYS[index].enabled);
docToValue(absoluteAnalogOptions.entries[index].stick, doc, ABSOLUTE_ANALOG_KEYS[index].stick);
docToValue(absoluteAnalogOptions.entries[index].x, doc, ABSOLUTE_ANALOG_KEYS[index].x);
docToValue(absoluteAnalogOptions.entries[index].y, doc, ABSOLUTE_ANALOG_KEYS[index].y);
}
absoluteAnalogOptions.entries_count = ABSOLUTE_ANALOG_COUNT;

EventManager::getInstance().triggerEvent(new GPStorageSaveEvent(true));

return serialize_json(doc);
Expand Down Expand Up @@ -2397,6 +2426,15 @@ std::string getAddonOptions()
writeDoc(doc, "heTriggerSmoothing", heTriggerOptions.emaSmoothing);
writeDoc(doc, "heTriggerSmoothingFactor", heTriggerOptions.smoothingFactor);

const AbsoluteAnalogOptions& absoluteAnalogOptions = Storage::getInstance().getAddonOptions().absoluteAnalogOptions;
writeDoc(doc, "AbsoluteAnalogAddonEnabled", absoluteAnalogOptions.enabled);
for (int index = 0; index < ABSOLUTE_ANALOG_COUNT; index++) {
writeDoc(doc, ABSOLUTE_ANALOG_KEYS[index].enabled, absoluteAnalogOptions.entries[index].enabled);
writeDoc(doc, ABSOLUTE_ANALOG_KEYS[index].stick, absoluteAnalogOptions.entries[index].stick);
writeDoc(doc, ABSOLUTE_ANALOG_KEYS[index].x, absoluteAnalogOptions.entries[index].x);
writeDoc(doc, ABSOLUTE_ANALOG_KEYS[index].y, absoluteAnalogOptions.entries[index].y);
}

return serialize_json(doc);
}

Expand Down
37 changes: 37 additions & 0 deletions www/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,43 @@ app.get('/api/getAddonsOptions', (req, res) => {
keyboardHostMouseMovement: 0,
AnalogInputEnabled: 1,
BoardLedAddonEnabled: 1,
AbsoluteAnalogAddonEnabled: 1,
absoluteAnalog1Enabled: 0,
absoluteAnalog1Stick: 0,
absoluteAnalog1X: 0,
absoluteAnalog1Y: 0,
absoluteAnalog2Enabled: 0,
absoluteAnalog2Stick: 0,
absoluteAnalog2X: 0,
absoluteAnalog2Y: 0,
absoluteAnalog3Enabled: 0,
absoluteAnalog3Stick: 0,
absoluteAnalog3X: 0,
absoluteAnalog3Y: 0,
absoluteAnalog4Enabled: 0,
absoluteAnalog4Stick: 0,
absoluteAnalog4X: 0,
absoluteAnalog4Y: 0,
absoluteAnalog5Enabled: 0,
absoluteAnalog5Stick: 0,
absoluteAnalog5X: 0,
absoluteAnalog5Y: 0,
absoluteAnalog6Enabled: 0,
absoluteAnalog6Stick: 0,
absoluteAnalog6X: 0,
absoluteAnalog6Y: 0,
absoluteAnalog7Enabled: 0,
absoluteAnalog7Stick: 0,
absoluteAnalog7X: 0,
absoluteAnalog7Y: 0,
absoluteAnalog8Enabled: 0,
absoluteAnalog8Stick: 0,
absoluteAnalog8X: 0,
absoluteAnalog8Y: 0,
absoluteAnalog9Enabled: 0,
absoluteAnalog9Stick: 0,
absoluteAnalog9X: 0,
absoluteAnalog9Y: 0,
FocusModeAddonEnabled: 1,
focusModeMacroLockEnabled: 0,
BuzzerSpeakerAddonEnabled: 1,
Expand Down
Loading