Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preset Navigation usermod #4354

Open
wants to merge 1 commit into
base: 0_15
Choose a base branch
from
Open
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added usermods/preset_navigation/ScreenshotBankUp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
198 changes: 198 additions & 0 deletions usermods/preset_navigation/preset_navigation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#pragma once

#include "wled.h"

#ifndef USERMOD_PRESET_NAVIGATION_MAX_PRESETS
#define USERMOD_PRESET_NAVIGATION_MAX_PRESETS 64
#endif

#ifndef USERMOD_PRESET_NAVIGATION_MAX_BANKS
#define USERMOD_PRESET_NAVIGATION_MAX_BANKS 23
#endif

class UsermodPresetNavigation : public Usermod {
private:
bool enabled = false;
uint8_t presets[USERMOD_PRESET_NAVIGATION_MAX_PRESETS];
uint8_t bankOffset[USERMOD_PRESET_NAVIGATION_MAX_BANKS+1];
String presetArray = "1";
String bootupPresetNavigation;
static const char *sName, *sEnabled, *sArray;

int x = 0; // x is current bank
int y = 0; // y is current sub-bank preset
int xMax = 0;

// One day might return false on a parsing error
bool readArray();
void printArray();
inline void applyPresetNavigation(){
applyPreset(presets[bankOffset[x]+y]);
}

public:
void incBank(bool wrap=true);
void decBank(bool wrap=true);
void incPreset(bool wrap=true);
void decPreset(bool wrap=true);

inline void enable(bool enable) { enabled = enable; }
inline bool isEnabled() { return enabled; }

void setup() override {
x = y = 0;
return;
}

void loop() override {
return;
}

void readFromJsonState(JsonObject& root) override {
String cmd;
bool cmdExists = getJsonValue(root["pn"], cmd);
if (cmdExists && enabled) {
if (cmd.equals("b~")) incBank();
else if (cmd.equals("p~")) incPreset();
else if (cmd.equals("b~-")) decBank();
else if (cmd.equals("p~-")) decPreset();
// no wrap versions
else if (cmd.equals("b+")) incBank(false);
else if (cmd.equals("p+")) incPreset(false);
else if (cmd.equals("b-")) decBank(false);
else if (cmd.equals("p-")) decPreset(false);
}
}

void addToConfig(JsonObject& root) override {
JsonObject top = root.createNestedObject(FPSTR(sName));
top[FPSTR(sEnabled)] = enabled;
top[FPSTR(sArray)] = presetArray;
}

bool readFromConfig(JsonObject& root) override
{
JsonObject top = root[FPSTR(sName)];
bool configComplete = !top.isNull();

configComplete &= getJsonValue(top[FPSTR(sEnabled)], enabled, false);
configComplete &= getJsonValue(top[FPSTR(sArray)], presetArray, "1");

// Remove any carriage returns (\r) from presetArray, leave only newlines (\n)
int i=0;
int lastI;
while(true) {
lastI = i;
i = presetArray.indexOf('\r',lastI);
if (i < 0) break;
presetArray.remove(i,1);
}

readArray();
return configComplete;
}

};

const char* UsermodPresetNavigation::sName = "Preset Navigation";
const char* UsermodPresetNavigation::sEnabled = "Enable";
const char* UsermodPresetNavigation::sArray = "Preset banks>"; // Trailing > makes this a textarea in settings

bool UsermodPresetNavigation::readArray() {
int readX = 0;
int i = 0;
int cursor = 0;
int currentNewline = presetArray.indexOf("\n");
int currentComma;
bool finished = false;
bool bankFinished;

while (!finished) {
// Iterate over each bank (line of text) in presetArray (string representation of array)
if (currentNewline == -1){
finished = true;
currentNewline = presetArray.length();
}
if (readX >= USERMOD_PRESET_NAVIGATION_MAX_BANKS) {
DEBUG_PRINTLN("Preset navigation: exceeded max banks");
break;
}
currentComma = presetArray.indexOf(",", cursor);
bankFinished = false;
bankOffset[readX]=i;
while (!bankFinished) {
// Iterate through each PRESET (comma separated value) in this bank
if (i >= USERMOD_PRESET_NAVIGATION_MAX_PRESETS) {
DEBUG_PRINTLN("Preset navigation: exceeded max presets");
break;
}
if (currentComma == -1 || currentComma > currentNewline) {
bankFinished = true;
currentComma = currentNewline;
}
String preset = presetArray.substring(cursor,currentComma);
presets[i] = preset.toInt();
i++;
cursor = currentComma + 1;
currentComma = presetArray.indexOf(",", cursor);
} // end PRESET loop
readX++;
// Find our next newline
cursor = currentNewline + 1;
currentNewline = presetArray.indexOf("\n", cursor);
} // end bank loop

xMax=readX-1;
bankOffset[readX]=i;
return true;
}

void UsermodPresetNavigation::printArray() {

}

void UsermodPresetNavigation::incBank(bool wrap){
if (x == xMax && wrap) {
x = 0; y = 0;
applyPresetNavigation();
} else if (x < xMax) {
x++; y = 0;
applyPresetNavigation();
}
}

void UsermodPresetNavigation::decBank(bool wrap){
if (x == 0 && wrap) {
x = xMax; y = 0;
applyPresetNavigation();
} else if (x > 0) {
x--; y = 0;
applyPresetNavigation();
}
}

void UsermodPresetNavigation::incPreset(bool wrap){
int yMax = bankOffset[x+1] - bankOffset[x] - 1;
if (yMax == 0) {
// We don't have anywhere to cycle
} else if (y == yMax && wrap) {
y=0;
applyPresetNavigation();
} else if (y < yMax) {
y++;
applyPresetNavigation();
}
}

void UsermodPresetNavigation::decPreset(bool wrap){
int yMax = bankOffset[x+1] - bankOffset[x] - 1;
if (yMax == 0) {
// We don't have anywhere to cycle
} else if (y == 0 && wrap) {
y = yMax;
applyPresetNavigation();
} else if (y > 0) {
y--;
applyPresetNavigation();
}
}
46 changes: 46 additions & 0 deletions usermods/preset_navigation/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Preset Navigation usermod
This usermod lets you navigate a large number of effect presets more easily. You can group effects together however you see fit into *banks* that you can navigate either between (eg bank 1 to bank 2) or within (switch between presets inside a bank). It's particularly handy for non-touchscreen interfaces, like pushbuttons or encoders.

![A graphic showing three boxes, containing LED strips that each represent an effect preset. The first box contains 3 sets of LED strips of different hues but the same effect, labeled Bank 1. The next box shows only 2 LED strips with rainbow themes, and is labeled Bank 2. The final box shows 4 LED strips and a representation of different LED chase effects, labeled Bank 3.](PresetNavigationDiagram.png)

Commands are provided to cycle up or down, with and without wrapping.

## Using this usermod
Add the flag `-D USERMOD_PRESET_NAVIGATION` to your build flags and [compile WLED](https://kno.wled.ge/advanced/compiling-wled/), if you can't find firmware with this usermod already included. Flash your new firmware and now there will be a Preset Navigation section under Settings>Usermods. Be sure to check the "Enable" checkbox and enter your preset banks as described below.

## Preset notation
The graphical example above could be represented like this in the usermod settings, "Preset Banks" text area:

```
10,11,12
20,21
30,31,32,33
```

This is assuming you've defined presets for given effects with the ID numbers 10, 11, 12, 21... etc. The preset IDs do not have to be sequential or follow any pattern like this, but it's up to you to carefully input this information: the usermod currently doesn't do any checks here for undefined preset IDs. **Do not include trailing commas after banks, just a newline**. You can include a space after commas, but that just makes the stored string take up a little more space in memory.

## How it functions
After starting up WLED, the state will be Preset 10---you can set this preset to be applied at boot when creating it. One option is to cycle through banks. Cycling up will apply Preset 20, and if cycling up again, Preset 30. If a wrapping command is used, then cycling up a third time will cycle back to Preset 10. From Preset 10, the other option is to cycle within a bank. Cycling up will apply Preset 11 next. Cycling down with a preset wrapping command from 10 will apply Preset 12.


## API commands
The commands are sent at the top level of the [JSON state API](https://kno.wled.ge/interfaces/json-api/), under the key `"pn"` for Preset Navigation. Here are some of the possible commands, the same pattern applies for both banks and presets.

| Command | Result |
|---------|-----------------------------------|
| `b~` | Increment bank with wrap around |
| `b+` | Increment bank without wrapping |
| `p~-` | Decrement preset with wrap around |
| `p-` | Decrement preset without wrapping |

Personally, for the navigation commands I'm interested in, I've defined some presets and then assigned their IDs to [buttons](https://kno.wled.ge/features/macros/#buttons). I name them with an underscore at the start so they are sorted separately. One such preset looks like this in the interface:

![A screenshot showing a preset with name "\_bank_up" and API command {"pn" : "b~"}](ScreenshotBankUp.png)

## Maximum number of presets, banks
By default, you can store up to 64 presets in up to 23 banks. These numbers can be increased if you have the memory for it, by redefining `USERMOD_PRESET_NAVIGATION_MAX_PRESETS` and `USERMOD_PRESET_NAVIGATION_MAX_BANKS` respectively. This can be done with a build flag the same way that you added this usermod to the build, eg `-D USERMOD_PRESET_NAVIGATION_MAX_PRESETS=96`

## Other considerations
This usermod naively keeps track of the current position in the navigation array. If you use the navigation commands to get to Preset 20 in our example above, and then use the web interface to apply Preset 10, sending an Cycle Bank Up command will still go to Preset 30 as if the previous state was still 20.

I like to save presets with "include brightness" unchecked, so that changing the brightness is independent to the changing of effects. In fact, on my own WLED builds I change the default of that checkbox to unchecked, by editing the file `wled00/data/index.js` and rebuilding the web interface with npm before compiling.
8 changes: 8 additions & 0 deletions wled00/usermods_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@
#include "../usermods/LD2410_v2/usermod_ld2410.h"
#endif

#ifdef USERMOD_PRESET_NAVIGATION
#include "../usermods/preset_navigation/preset_navigation.h"
#endif

void registerUsermods()
{
/*
Expand Down Expand Up @@ -470,4 +474,8 @@ void registerUsermods()
#ifdef USERMOD_POV_DISPLAY
UsermodManager::add(new PovDisplayUsermod());
#endif

#ifdef USERMOD_PRESET_NAVIGATION
UsermodManager::add(new UsermodPresetNavigation());
#endif
}