From a868e63a0e43cca4ba2aeb40d8e9a8435b90c6c4 Mon Sep 17 00:00:00 2001 From: Ori Barbut Date: Fri, 6 Dec 2024 23:18:00 -0500 Subject: [PATCH] Preset Navigation usermod Allows grouping of related effects presets in 'banks' as the user likes, and provides navigation functions to traverse between or within banks. --- .../PresetNavigationDiagram.png | Bin 0 -> 15647 bytes .../preset_navigation/ScreenshotBankUp.png | Bin 0 -> 30976 bytes .../preset_navigation/preset_navigation.h | 198 ++++++++++++++++++ usermods/preset_navigation/readme.md | 46 ++++ wled00/usermods_list.cpp | 8 + 5 files changed, 252 insertions(+) create mode 100644 usermods/preset_navigation/PresetNavigationDiagram.png create mode 100644 usermods/preset_navigation/ScreenshotBankUp.png create mode 100644 usermods/preset_navigation/preset_navigation.h create mode 100644 usermods/preset_navigation/readme.md diff --git a/usermods/preset_navigation/PresetNavigationDiagram.png b/usermods/preset_navigation/PresetNavigationDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..018be823add6138111405c210cd6b646d8160d81 GIT binary patch literal 15647 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 literal 0 HcmV?d00001 diff --git a/usermods/preset_navigation/ScreenshotBankUp.png b/usermods/preset_navigation/ScreenshotBankUp.png new file mode 100644 index 0000000000000000000000000000000000000000..2a043d4dea39a6cce68045c1ec8f08000d8b5020 GIT binary patch literal 30976 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

UYN_ue8&s{pmBm3uaraOFq3!Tnpl(lRds&Bg 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 literal 0 HcmV?d00001 diff --git a/usermods/preset_navigation/preset_navigation.h b/usermods/preset_navigation/preset_navigation.h new file mode 100644 index 0000000000..991a6a45a2 --- /dev/null +++ b/usermods/preset_navigation/preset_navigation.h @@ -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(); + } +} diff --git a/usermods/preset_navigation/readme.md b/usermods/preset_navigation/readme.md new file mode 100644 index 0000000000..66e03440e4 --- /dev/null +++ b/usermods/preset_navigation/readme.md @@ -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. diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 36bd122a51..fa1e9ab89b 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -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() { /* @@ -470,4 +474,8 @@ void registerUsermods() #ifdef USERMOD_POV_DISPLAY UsermodManager::add(new PovDisplayUsermod()); #endif + + #ifdef USERMOD_PRESET_NAVIGATION + UsermodManager::add(new UsermodPresetNavigation()); + #endif }