Skip to content

Commit

Permalink
Allow for the enabling and disabling of profiles (#1149)
Browse files Browse the repository at this point in the history
* add a enabled flag to GpioMappings to indicate what's active

with this flag we no longer need to copy profile 1 to 2-4 by default, so
that migration goes away, but because of that we need to migrate the old
way of doing things and only enable profiles that are actually different
than the base.

in the end, new fresh boards should have profile 1 only enabled, and
profiles 2-4 blank (no copying, disabled by default), and migrated
configs should have profiles (that exist) that differ from profile 1
enabled and profiles that don't disabled

* refactor set/next/previousProfile to only set enabled profiles

* always provide a list in getProfileOptions

* Add possibility to add, disabled/enable profiles. State can now handle any amount of profiles

* Mock data with enabled flag, handle flag correctly

* Correctly handle disabled translation string

* Ignore base profile enabled label in menu

* add enabled flag to the API; not sure how I missed this

---------

Co-authored-by: ian <[email protected]>
  • Loading branch information
bsstephan and Pelsin authored Sep 27, 2024
1 parent 576c7b8 commit 20c5433
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 135 deletions.
2 changes: 1 addition & 1 deletion headers/storagemanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Storage {
void SetProcessedGamepad(Gamepad *); // MPGS Processed Gamepad Get/Set
Gamepad * GetProcessedGamepad();

void setProfile(const uint32_t); // profile support for multiple mappings
bool setProfile(const uint32_t); // profile support for multiple mappings
void nextProfile();
void previousProfile();
void setFunctionalPinMappings();
Expand Down
2 changes: 2 additions & 0 deletions proto/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ message GpioMappings
{
repeated GpioMappingInfo pins = 1 [(nanopb).max_count = 30];
optional string profileLabel = 2 [(nanopb).max_length = 16];
optional bool enabled = 3 [default = false];
}


Expand Down Expand Up @@ -823,6 +824,7 @@ message MigrationHistory
optional bool hotkeysMigrated = 1 [default = false];
optional bool gpioMappingsMigrated = 2 [default = false];
optional bool buttonProfilesMigrated = 3 [default = false];
optional bool profileEnabledFlagsMigrated = 4 [default = false];
}

message Config
Expand Down
79 changes: 31 additions & 48 deletions src/config_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1247,50 +1247,6 @@ void gpioMappingsMigrationCore(Config& config)
config.migrations.gpioMappingsMigrated = true;
}

// populate the alternative gpio mapping sets, aka profiles, with
// the old values or whatever is in the core mappings
// NOTE: this also handles initializations for a blank config! if/when the deprecated
// pin mappings go away, the remainder of this code should go in there (there was no point
// in duplicating it right now)
void gpioMappingsMigrationProfiles(Config& config)
{
AlternativePinMappings* deprecatedAlts = config.profileOptions.deprecatedAlternativePinMappings;

const auto assignProfilePinIfUsed = [&](uint8_t profileNum, Pin_t profilePin, GpioAction action) -> void {
if (isValidPin(profilePin)) {
config.profileOptions.gpioMappingsSets[profileNum].pins[profilePin].action = action;
}
};

for (uint8_t profileNum = 0; profileNum <= 2; profileNum++) {
for (Pin_t pin = 0; pin < (Pin_t)NUM_BANK0_GPIOS; pin++) {
config.profileOptions.gpioMappingsSets[profileNum].pins[pin].action = config.gpioMappings.pins[pin].action;
}
// only check protobuf if profiles are defined
if (profileNum < config.profileOptions.deprecatedAlternativePinMappings_count) {
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinButtonB1, GpioAction::BUTTON_PRESS_B1);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinButtonB2, GpioAction::BUTTON_PRESS_B2);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinButtonB3, GpioAction::BUTTON_PRESS_B3);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinButtonB4, GpioAction::BUTTON_PRESS_B4);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinButtonL1, GpioAction::BUTTON_PRESS_L1);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinButtonR1, GpioAction::BUTTON_PRESS_R1);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinButtonL2, GpioAction::BUTTON_PRESS_L2);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinButtonR2, GpioAction::BUTTON_PRESS_R2);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinDpadUp, GpioAction::BUTTON_PRESS_UP);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinDpadDown, GpioAction::BUTTON_PRESS_DOWN);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinDpadLeft, GpioAction::BUTTON_PRESS_LEFT);
assignProfilePinIfUsed(profileNum, deprecatedAlts[profileNum].pinDpadRight, GpioAction::BUTTON_PRESS_RIGHT);
}

// reminder that this must be set or else nanopb won't retain anything
config.profileOptions.gpioMappingsSets[profileNum].pins_count = NUM_BANK0_GPIOS;
}
// reminder that this must be set or else nanopb won't retain anything
config.profileOptions.gpioMappingsSets_count = 3;

config.migrations.buttonProfilesMigrated = true;
}

void migrateTurboPinToGpio(Config& config) {
// Features converted here must set their previous deprecated pin/set value as well (pin = -1)
TurboOptions & turboOptions = config.addonOptions.turboOptions;
Expand Down Expand Up @@ -1343,6 +1299,34 @@ void migrateAuthenticationMethods(Config& config) {
}
}

// enable profiles that have real data in them (profile 1 is always enabled)
// note that profiles 2-4 are no longer populated with profile 1's data on a fresh
// config, and this is checking previous configs with non-copy mappings to enable them
void profileEnabledFlagsMigration(Config& config) {
config.gpioMappings.enabled = true;
for (uint8_t profileNum = 0; profileNum < config.profileOptions.gpioMappingsSets_count; profileNum++) {
if (!config.profileOptions.gpioMappingsSets[profileNum].pins_count) {
// uninitialized profile, skip it and leave it disabled
config.profileOptions.gpioMappingsSets[profileNum].enabled = false;
continue;
}
for (uint8_t pinNum = 0; pinNum < config.gpioMappings.pins_count; pinNum++) {
// check each pin: if the alt. mapping pin is different than the base (profile 1)
// mapping, enable the profile and check the next one
if (config.gpioMappings.pins[pinNum].action !=
config.profileOptions.gpioMappingsSets[profileNum].pins[pinNum].action ||
config.gpioMappings.pins[pinNum].customButtonMask !=
config.profileOptions.gpioMappingsSets[profileNum].pins[pinNum].customButtonMask ||
config.gpioMappings.pins[pinNum].customDpadMask !=
config.profileOptions.gpioMappingsSets[profileNum].pins[pinNum].customDpadMask) {
config.profileOptions.gpioMappingsSets[profileNum].enabled = true;
break;
}
}
}
config.migrations.profileEnabledFlagsMigrated = true;
}

void migrateMacroPinsToGpio(Config& config) {
// Convert Macro pin mapping to GPIO mapping configs
MacroOptions & macroOptions = config.addonOptions.macroOptions;
Expand Down Expand Up @@ -1521,9 +1505,9 @@ void ConfigUtils::load(Config& config)
if (!config.migrations.gpioMappingsMigrated)
gpioMappingsMigrationCore(config);

// Run button profile migrations
if (!config.migrations.buttonProfilesMigrated)
gpioMappingsMigrationProfiles(config);
// Run migration to enable or disable pre-existing profiles
if (!config.migrations.profileEnabledFlagsMigrated)
profileEnabledFlagsMigration(config);

// following migrations are simple enough to not need a protobuf boolean to track
// Migrate turbo into GpioMappings
Expand Down Expand Up @@ -2163,7 +2147,6 @@ bool ConfigUtils::fromJSON(Config& config, const char* data, size_t dataLen)

// we need to run migrations here too, in case the json document changed pins or things derived from pins
gpioMappingsMigrationCore(config);
gpioMappingsMigrationProfiles(config);
migrateTurboPinToGpio(config);
migrateAuthenticationMethods(config);
migrateMacroPinsToGpio(config);
Expand Down
10 changes: 10 additions & 0 deletions src/configs/webconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ std::string setProfileOptions()
size_t profileLabelSize = sizeof(profileOptions.gpioMappingsSets[altsIndex].profileLabel);
strncpy(profileOptions.gpioMappingsSets[altsIndex].profileLabel, alt["profileLabel"], profileLabelSize - 1);
profileOptions.gpioMappingsSets[altsIndex].profileLabel[profileLabelSize - 1] = '\0';
profileOptions.gpioMappingsSets[altsIndex].enabled = alt["enabled"];

profileOptions.gpioMappingsSets_count = ++altsIndex;
if (altsIndex > 2) break;
Expand All @@ -584,6 +585,12 @@ std::string getProfileOptions()
};

ProfileOptions& profileOptions = Storage::getInstance().getProfileOptions();

// return an empty list if no profiles are currently set, since we no longer populate by default
if (profileOptions.gpioMappingsSets_count == 0) {
doc.createNestedArray("alternativePinMappings");
}

for (int i = 0; i < profileOptions.gpioMappingsSets_count; i++) {
// this looks duplicative, but something in arduinojson treats the doc
// field string by reference so you can't be "clever" and do an snprintf
Expand Down Expand Up @@ -619,6 +626,7 @@ std::string getProfileOptions()
writePinDoc(i, "pin28", profileOptions.gpioMappingsSets[i].pins[28]);
writePinDoc(i, "pin29", profileOptions.gpioMappingsSets[i].pins[29]);
writeDoc(doc, "alternativePinMappings", i, "profileLabel", profileOptions.gpioMappingsSets[i].profileLabel);
doc["alternativePinMappings"][i]["enabled"] = profileOptions.gpioMappingsSets[i].enabled;
}

return serialize_json(doc);
Expand Down Expand Up @@ -1062,6 +1070,7 @@ std::string setPinMappings()
size_t profileLabelSize = sizeof(gpioMappings.profileLabel);
strncpy(gpioMappings.profileLabel, doc["profileLabel"], profileLabelSize - 1);
gpioMappings.profileLabel[profileLabelSize - 1] = '\0';
gpioMappings.enabled = doc["enabled"];

Storage::getInstance().save();

Expand Down Expand Up @@ -1113,6 +1122,7 @@ std::string getPinMappings()
writePinDoc("pin29", gpioMappings.pins[29]);

writeDoc(doc, "profileLabel", gpioMappings.profileLabel);
doc["enabled"] = gpioMappings.enabled;

return serialize_json(doc);
}
Expand Down
28 changes: 16 additions & 12 deletions src/gamepad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -518,30 +518,34 @@ void Gamepad::processHotkeyAction(GamepadHotkey action) {
break;
case HOTKEY_LOAD_PROFILE_1:
if (action != lastAction) {
Storage::getInstance().setProfile(1);
userRequestedReinit = true;
reqSave = true;
if (Storage::getInstance().setProfile(1)) {
userRequestedReinit = true;
reqSave = true;
}
}
break;
case HOTKEY_LOAD_PROFILE_2:
if (action != lastAction) {
Storage::getInstance().setProfile(2);
userRequestedReinit = true;
reqSave = true;
if (Storage::getInstance().setProfile(2)) {
userRequestedReinit = true;
reqSave = true;
}
}
break;
case HOTKEY_LOAD_PROFILE_3:
if (action != lastAction) {
Storage::getInstance().setProfile(3);
userRequestedReinit = true;
reqSave = true;
if (Storage::getInstance().setProfile(3)) {
userRequestedReinit = true;
reqSave = true;
}
}
break;
case HOTKEY_LOAD_PROFILE_4:
if (action != lastAction) {
Storage::getInstance().setProfile(4);
userRequestedReinit = true;
reqSave = true;
if (Storage::getInstance().setProfile(4)) {
userRequestedReinit = true;
reqSave = true;
}
}
break;
case HOTKEY_NEXT_PROFILE:
Expand Down
32 changes: 25 additions & 7 deletions src/storagemanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,39 @@ void Storage::ResetSettings()
watchdog_reboot(0, SRAM_END, 2000);
}

void Storage::setProfile(const uint32_t profileNum)
bool Storage::setProfile(const uint32_t profileNum)
{
this->config.gamepadOptions.profileNumber = (profileNum < 1 || profileNum > 4) ? 1 : profileNum;
// is this profile defined?
if (profileNum >= 1 && profileNum <= config.profileOptions.gpioMappingsSets_count + 1) {
// is this profile enabled?
// profile 1 (core) is always enabled, others we must check
if (profileNum == 1 || config.profileOptions.gpioMappingsSets[profileNum-2].enabled) {
this->config.gamepadOptions.profileNumber = profileNum;
return true;
}
}
// if we get here, the requested profile doesn't exist or isn't enabled, so don't change it
return false;
}

void Storage::nextProfile()
{
this->config.gamepadOptions.profileNumber = (this->config.gamepadOptions.profileNumber % 4) + 1;
uint32_t profileCeiling = config.profileOptions.gpioMappingsSets_count + 1;
uint32_t requestedProfile = (this->config.gamepadOptions.profileNumber % profileCeiling) + 1;
while (!setProfile(requestedProfile)) {
// if the set failed, try again with the next in the sequence
requestedProfile = (requestedProfile % profileCeiling) + 1;
}
}
void Storage::previousProfile()
{
if (this->config.gamepadOptions.profileNumber == 1)
this->config.gamepadOptions.profileNumber = 4;
else
this->config.gamepadOptions.profileNumber -= 1;
uint32_t profileCeiling = config.profileOptions.gpioMappingsSets_count + 1;
uint32_t requestedProfile = this->config.gamepadOptions.profileNumber > 1 ?
config.gamepadOptions.profileNumber - 1 : profileCeiling;
while (!setProfile(requestedProfile)) {
// if the set failed, try again with the next in the sequence
requestedProfile = requestedProfile > 1 ? requestedProfile - 1 : profileCeiling;
}
}

/**
Expand Down
3 changes: 1 addition & 2 deletions www/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { pico: picoController } = JSON.parse(

// Structure pin mappings to include masks and profile label
const createPinMappings = ({ profileLabel = 'Profile' }) => {
let pinMappings = { profileLabel };
let pinMappings = { profileLabel, enabled: true };

for (const [key, value] of Object.entries(picoController)) {
pinMappings[key] = {
Expand Down Expand Up @@ -390,7 +390,6 @@ app.get('/api/getProfileOptions', (req, res) => {
alternativePinMappings: [
createPinMappings({ profileLabel: 'Profile 2' }),
createPinMappings({ profileLabel: 'Profile 3' }),
createPinMappings({ profileLabel: 'Profile 4' }),
],
});
});
Expand Down
4 changes: 4 additions & 0 deletions www/src/Locales/en/PinMapping.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export default {
'Max 16 characters. Letters, numbers, and spaces allowed.',
'profile-pin-mapping-title': '{{profileLabel}} - Pin Mapping',
'profile-label-default': 'Profile {{profileNumber}}',
'profile-add-button': '+ Add Profile',
'profile-disabled': ' - (Disabled)',
'profile-enabled-tooltip':
'Disabled profiles will not be available when using hotkeys to change profile.',
'profile-pins-warning':
'Try to avoid changing the buttons and/or directions used for the switch profile hotkeys. Otherwise, it will be difficult to understand what profile is being selected!',
'profile-copy-base': 'Copy base profile',
Expand Down
Loading

0 comments on commit 20c5433

Please sign in to comment.