From 75c46f7a0e6c6cdbe257aeab24914aa7d9df7de0 Mon Sep 17 00:00:00 2001 From: Kevin Dorff Date: Wed, 17 Feb 2021 17:28:01 -0600 Subject: [PATCH] Create the sorted index of modes and palettes at startup (#1741) * Very incomplete work toward sorting. * Sort modes and palettes upon startup so I don't have to maintain the static index array. * Remove cpp test file I used for development * Added ModeSortUsermod, modified the other two usermods to use it. * Update platformio_override.ini.sample and readme for ModeSortUsermod * restore methods accidentally removed. --- .../usermod_v2_four_line_display.h | 52 ++-- usermods/usermod_v2_mode_sort/readme.md | 33 +++ .../usermod_v2_mode_sort.h | 248 ++++++++++++++++++ .../platformio_override.ini.sample | 2 + .../usermod_v2_rotary_encoder_ui.h | 61 ++--- wled00/const.h | 1 + wled00/usermods_list.cpp | 10 +- 7 files changed, 338 insertions(+), 69 deletions(-) create mode 100644 usermods/usermod_v2_mode_sort/readme.md create mode 100644 usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h diff --git a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h index 9f4716218..0b59af551 100644 --- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -10,7 +10,10 @@ // OLED displays to provide a four line display // for WLED. // -// This Usermod works best, by far, when coupled with RotaryEncoderUIUsermod. +// Dependencies +// * This usermod REQURES the ModeSortUsermod +// * This Usermod works best, by far, when coupled +// with RotaryEncoderUIUsermod. // // Make sure to enable NTP and set your time zone in WLED Config | Time. // @@ -124,6 +127,9 @@ class FourLineDisplayUsermod : public Usermod { char lineBuffer[LINE_BUFFER_SIZE]; + char **modes_qstrings = nullptr; + char **palettes_qstrings = nullptr; + // If display does not work or looks corrupted check the // constructor reference: // https://github.com/olikraus/u8g2/wiki/u8x8setupcpp @@ -140,6 +146,10 @@ class FourLineDisplayUsermod : public Usermod { u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 u8x8.setFont(u8x8_font_chroma48medium8_r); u8x8.DRAW_STRING(0, 0*LINE_HEIGHT, "Loading..."); + + ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT); + modes_qstrings = modeSortUsermod->getModesQStrings(); + palettes_qstrings = modeSortUsermod->getPalettesQStrings(); } // gets called every time WiFi is (re-)connected. Initialize own network @@ -254,7 +264,7 @@ class FourLineDisplayUsermod : public Usermod { } // Third row with mode name - showCurrentEffectOrPalette(JSON_mode_names, 2, knownMode); + showCurrentEffectOrPalette(modes_qstrings[knownMode], 2); switch(lineThreeType) { case FLD_LINE_3_BRIGHTNESS: @@ -270,7 +280,7 @@ class FourLineDisplayUsermod : public Usermod { u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); break; case FLD_LINE_3_PALETTE: - showCurrentEffectOrPalette(JSON_palette_names, 3, knownPalette); + showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3); break; } @@ -289,35 +299,21 @@ class FourLineDisplayUsermod : public Usermod { * TODO: Should we cache the current effect and * TODO: palette name? This seems expensive. */ - void showCurrentEffectOrPalette(const char json[], uint8_t row, uint8_t desiredEntry) { - uint8_t qComma = 0; - bool insideQuotes = false; - // advance past the mark for markLineNum that may exist. + void showCurrentEffectOrPalette(char *qstring, uint8_t row) { uint8_t printedChars = 1; char singleJsonSymbol; - - // Find the mode name in JSON - for (size_t i = 0; i < strlen_P(json); i++) { - singleJsonSymbol = pgm_read_byte_near(json + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != desiredEntry)) { - break; - } - u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol); - printedChars++; - } - if ((qComma > desiredEntry) || (printedChars > u8x8.getCols() - 2)) { + int i = 0; + while (true) { + singleJsonSymbol = pgm_read_byte_near(qstring + i); + if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) { break; } + u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol); + printedChars++; + if ( (printedChars > u8x8.getCols() - 2)) { + break; + } + i++; } } diff --git a/usermods/usermod_v2_mode_sort/readme.md b/usermods/usermod_v2_mode_sort/readme.md new file mode 100644 index 000000000..b4fe90e73 --- /dev/null +++ b/usermods/usermod_v2_mode_sort/readme.md @@ -0,0 +1,33 @@ +# Mode Sort + +v2 usermod that provides data about modes and +palettes to other usermods. Notably it provides: +* A direct method for a mode or palette name +* Ability to retrieve mode and palette names in + alphabetical order + +```char **getModesQStrings()``` + +Provides an array of char* (pointers) to the names of the +palettes within JSON_mode_names, in the same order as +JSON_mode_names. These strings end in double quote (") +(or \0 if there is a problem). + +```byte *getModesAlphaIndexes()``` + +An array of byte designating the indexes of names of the +modes in alphabetical order. "Solid" will always remain +at the front of the list. + +```char **getPalettesQStrings()``` + +Provides an array of char* (pointers) to the names of the +palettes within JSON_palette_names, in the same order as +JSON_palette_names. These strings end in double quote (") +(or \0 if there is a problem). + +```byte *getPalettesAlphaIndexes()``` + +An array of byte designating the indexes of names of the +palettes in alphabetical order. "Default" and those +starting with "(" will always remain at the front of the list. diff --git a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h new file mode 100644 index 000000000..2be7ce84c --- /dev/null +++ b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h @@ -0,0 +1,248 @@ +#pragma once + +#include "wled.h" + +// +// v2 usermod that provides data about modes and +// palettes to other usermods. Notably it provides: +// * A direct method for a mode or palette name +// * Ability to retrieve mode and palette names in +// alphabetical order +// +// char **getModesQStrings() +// Provides an array of char* (pointers) to the names of the +// palettes within JSON_mode_names, in the same order as +// JSON_mode_names. These strings end in double quote (") +// (or \0 if there is a problem). +// +// byte *getModesAlphaIndexes() +// An array of byte designating the indexes of names of the +// modes in alphabetical order. "Solid" will always remain +// at the front of the list. +// +// char **getPalettesQStrings() +// Provides an array of char* (pointers) to the names of the +// palettes within JSON_palette_names, in the same order as +// JSON_palette_names. These strings end in double quote (") +// (or \0 if there is a problem). +// +// byte *getPalettesAlphaIndexes() +// An array of byte designating the indexes of names of the +// palettes in alphabetical order. "Default" and those +// starting with "(" will always remain at the front of the list. +// + +// Number of modes at the start of the list to not sort +#define MODE_SORT_SKIP_COUNT 1 + +// Which list is being sorted +char **listBeingSorted = nullptr; + +/** + * Modes and palettes are stored as strings that + * end in a quote character. Compare two of them. + * We are comparing directly within either + * JSON_mode_names or JSON_palette_names. + */ +int re_qstringCmp(const void *ap, const void *bp) { + char *a = listBeingSorted[*((byte *)ap)]; + char *b = listBeingSorted[*((byte *)bp)]; + int i = 0; + do { + char aVal = pgm_read_byte_near(a + i); + if (aVal >= 97 && aVal <= 122) { + // Lowercase + aVal -= 32; + } + char bVal = pgm_read_byte_near(b + i); + if (bVal >= 97 && bVal <= 122) { + // Lowercase + bVal -= 32; + } + // Relly we shouldn't ever get to '\0' + if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') { + // We're done. one is a substring of the other + // or something happenend and the quote didn't stop us. + if (aVal == bVal) { + // Same value, probably shouldn't happen + // with this dataset + return 0; + } + else if (aVal == '"' || aVal == '\0') { + return -1; + } + else { + return 1; + } + } + if (aVal == bVal) { + // Same characters. Move to the next. + i++; + continue; + } + // We're done + if (aVal < bVal) { + return -1; + } + else { + return 1; + } + } while (true); + // We shouldn't get here. + return 0; +} + +class ModeSortUsermod : public Usermod { +private: + + // Pointers the start of the mode names within JSON_mode_names + char **modes_qstrings = nullptr; + + // Array of mode indexes in alphabetical order. + byte *modes_alpha_indexes = nullptr; + + // Pointers the start of the palette names within JSON_palette_names + char **palettes_qstrings = nullptr; + + // Array of palette indexes in alphabetical order. + byte *palettes_alpha_indexes = nullptr; + +public: + /** + * setup() is called once at boot. WiFi is not yet connected at this point. + * You can use it to initialize variables, sensors or similar. + */ + void setup() { + // Sort the modes and palettes on startup + // as they are guarantted to change. + sortModesAndPalettes(); + } + + char **getModesQStrings() { + return modes_qstrings; + } + + byte *getModesAlphaIndexes() { + return modes_alpha_indexes; + } + + char **getPalettesQStrings() { + return palettes_qstrings; + } + + byte *getPalettesAlphaIndexes() { + return palettes_alpha_indexes; + } + + /** + * This Usermod doesn't have anything for loop. + */ + void loop() {} + + /** + * Sort the modes and palettes to the index arrays + * modes_alpha_indexes and palettes_alpha_indexes. + */ + void sortModesAndPalettes() { + modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); + modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); + re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); + + palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount()); + palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount()); + + int skipPaletteCount = 1; + while (true) { + // How many palette names start with '*' and should not be sorted? + // (Also skipping the first one, 'Default'). + if (pgm_read_byte_near(palettes_qstrings[skipPaletteCount]) == '*') { + skipPaletteCount++; + } + else { + break; + } + } + + re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); + } + + byte *re_initIndexArray(int numModes) { + byte *indexes = (byte *)malloc(sizeof(byte) * numModes); + for (byte i = 0; i < numModes; i++) { + indexes[i] = i; + } + return indexes; + } + + /** + * Return an array of mode or palette names from the JSON string. + * They don't end in '\0', they end in '"'. + */ + char **re_findModeStrings(const char json[], int numModes) { + char **modeStrings = (char **)malloc(sizeof(char *) * numModes); + uint8_t modeIndex = 0; + bool insideQuotes = false; + // advance past the mark for markLineNum that may exist. + char singleJsonSymbol; + + // Find the mode name in JSON + bool complete = false; + for (size_t i = 0; i < strlen_P(json); i++) { + singleJsonSymbol = pgm_read_byte_near(json + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + if (insideQuotes) { + // We have a new mode or palette + modeStrings[modeIndex] = (char *)(json + i + 1); + } + break; + case '[': + break; + case ']': + complete = true; + break; + case ',': + modeIndex++; + default: + if (!insideQuotes) { + break; + } + } + if (complete) { + break; + } + } + return modeStrings; + } + + /** + * Sort either the modes or the palettes using quicksort. + */ + void re_sortModes(char **modeNames, byte *indexes, int count, int numSkip) { + listBeingSorted = modeNames; + qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); + listBeingSorted = nullptr; + } + + /* + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void addToJsonState(JsonObject &root) {} + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void readFromJsonState(JsonObject &root) {} + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() + { + return USERMOD_ID_MODE_SORT; + } +}; diff --git a/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample index 485d67f6a..3bfa851c9 100644 --- a/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample +++ b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample @@ -8,6 +8,7 @@ platform = espressif32@2.0 build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} + -D USERMOD_MODE_SORT -D USERMOD_FOUR_LINE_DISLAY -D FLD_PIN_SCL=22 -D FLD_PIN_SDA=21 -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 -D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1 @@ -26,6 +27,7 @@ board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} + -D USERMOD_MODE_SORT -D USERMOD_FOUR_LINE_DISLAY -D FLD_PIN_SCL=5 -D FLD_PIN_SDA=4 -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=12 -D ENCODER_CLK_PIN=14 -D ENCODER_SW_PIN=13 -D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1 diff --git a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h index 59cabfc4d..6dc2a1be3 100644 --- a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h +++ b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h @@ -9,8 +9,6 @@ // // v2 usermod that provides a rotary encoder-based UI. // -// This Usermod works best coupled with FourLineDisplayUsermod. -// // This usermod allows you to control: // // * Brightness @@ -21,6 +19,11 @@ // // Change between modes by pressing a button. // +// Dependencies +// * This usermod REQURES the ModeSortUsermod +// * This Usermod works best coupled with +// FourLineDisplayUsermod. +// #ifndef ENCODER_DT_PIN #define ENCODER_DT_PIN 12 @@ -42,36 +45,10 @@ #define FLD_LINE_3_PALETTE 0 #endif + // The last UI state #define LAST_UI_STATE 4 -/** - * Array of mode indexes in alphabetical order. - * Should be ordered from JSON_mode_names array in FX.h. - * - * NOTE: If JSON_mode_names changes, this will need to be updated. - */ -const byte modes_alpha_order[] = { - 0, 27, 38, 115, 1, 26, 91, 68, 2, 88, 102, 114, 28, 31, 32, - 30, 29, 111, 52, 34, 8, 74, 67, 112, 18, 19, 96, 7, 117, 12, - 69, 66, 45, 42, 90, 89, 110, 87, 46, 53, 82, 100, 58, 64, 75, - 41, 57, 47, 44, 76, 77, 59, 70, 71, 72, 73, 107, 62, 101, 65, - 98, 105, 109, 97, 48, 49, 95, 63, 78, 43, 9, 33, 5, 79, 99, - 15, 37, 16, 10, 11, 40, 60, 108, 92, 93, 94, 103, 83, 84, 20, - 21, 22, 85, 86, 39, 61, 23, 25, 24, 104, 6, 36, 13, 14, 35, - 54, 56, 55, 116, 17, 81, 80, 106, 51, 50, 113, 3, 4 }; - -/** - * Array of palette indexes in alphabetical order. - * Should be ordered from JSON_palette_names array in FX.h. - * - * NOTE: If JSON_palette_names changes, this will need to be updated. - */ -const byte palettes_alpha_order[] = { - 0, 1, 2, 3, 4, 5, 18, 46, 51, 50, 55, 39, 26, 22, 15, - 48, 52, 53, 7, 37, 24, 30, 35, 10, 32, 28, 29, 36, 31, - 25, 8, 38, 40, 41, 9, 44, 47, 6, 20, 11, 12, 16, 33, - 14, 49, 27, 19, 13, 21, 54, 34, 45, 23, 43, 17, 42 }; class RotaryEncoderUIUsermod : public Usermod { private: @@ -86,10 +63,14 @@ private: unsigned char prev_button_state = HIGH; #ifdef USERMOD_FOUR_LINE_DISLAY - FourLineDisplayUsermod* display; + FourLineDisplayUsermod *display; #else void* display = nullptr; #endif + + byte *modes_alpha_indexes = nullptr; + byte *palettes_alpha_indexes = nullptr; + unsigned char Enc_A; unsigned char Enc_B; unsigned char Enc_A_prev = 0; @@ -111,6 +92,10 @@ public: currentTime = millis(); loopTime = currentTime; + ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT); + modes_alpha_indexes = modeSortUsermod->getModesAlphaIndexes(); + palettes_alpha_indexes = modeSortUsermod->getPalettesAlphaIndexes(); + #ifdef USERMOD_FOUR_LINE_DISLAY // This Usermod uses FourLineDisplayUsermod for the best experience. // But it's optional. But you want it. @@ -246,16 +231,16 @@ public: void findCurrentEffectAndPalette() { currentEffectAndPaleeteInitialized = true; for (uint8_t i = 0; i < strip.getModeCount(); i++) { - byte value = modes_alpha_order[i]; - if (modes_alpha_order[i] == effectCurrent) { + byte value = modes_alpha_indexes[i]; + if (modes_alpha_indexes[i] == effectCurrent) { effectCurrentIndex = i; break; } } for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { - byte value = palettes_alpha_order[i]; - if (palettes_alpha_order[i] == strip.getSegment(0).palette) { + byte value = palettes_alpha_indexes[i]; + if (palettes_alpha_indexes[i] == strip.getSegment(0).palette) { effectPaletteIndex = i; break; } @@ -315,7 +300,7 @@ public: else { effectCurrentIndex = (effectCurrentIndex - 1 < 0) ? (strip.getModeCount() - 1) : (effectCurrentIndex - 1); } - effectCurrent = modes_alpha_order[effectCurrentIndex]; + effectCurrent = modes_alpha_indexes[effectCurrentIndex]; lampUdated(); } @@ -364,7 +349,7 @@ public: else { effectPaletteIndex = (effectPaletteIndex - 1 < 0) ? (strip.getPaletteCount() - 1) : (effectPaletteIndex - 1); } - effectPalette = palettes_alpha_order[effectPaletteIndex]; + effectPalette = palettes_alpha_indexes[effectPaletteIndex]; lampUdated(); } @@ -380,7 +365,6 @@ public: //this code adds "u":{"Light":[20," lux"]} to the info object JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - JsonArray lightArr = user.createNestedArray("Light"); //name lightArr.add(reading); //value lightArr.add(" lux"); //unit @@ -414,7 +398,4 @@ public: { return USERMOD_ID_ROTARY_ENC_UI; } - - //More methods can be added in the future, this example will then be extended. - //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! }; diff --git a/wled00/const.h b/wled00/const.h index 409a4ce37..04b112cf4 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -25,6 +25,7 @@ #define USERMOD_ID_ROTARY_ENC_UI 8 //Usermod "usermod_v2_rotary_encoder_ui.h" #define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h" #define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h" +#define USERMOD_ID_MODE_SORT 11 //Usermod "usermod_v2_mode_sort.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index cdd6fdfaf..2e757a102 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -21,6 +21,9 @@ #include "usermod_v2_SensorsToMqtt.h" #endif +#ifdef USERMOD_MODE_SORT +#include "../usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h" +#endif #ifdef USERMOD_FOUR_LINE_DISLAY #include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h" #endif @@ -53,6 +56,10 @@ void registerUsermods() #ifdef USERMOD_SENSORSTOMQTT usermods.add(new UserMod_SensorsToMQTT()); #endif + +#ifdef USERMOD_MODE_SORT + usermods.add(new ModeSortUsermod()); +#endif #ifdef USERMOD_FOUR_LINE_DISLAY usermods.add(new FourLineDisplayUsermod()); #endif @@ -62,7 +69,8 @@ void registerUsermods() #ifdef USERMOD_AUTO_SAVE usermods.add(new AutoSaveUsermod()); #endif + #ifdef USERMOD_DHT usermods.add(new UsermodDHT()); #endif -} \ No newline at end of file +}