Skip to content

Commit 75c46f7

Browse files
authored
Create the sorted index of modes and palettes at startup (wled#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.
1 parent aa242d8 commit 75c46f7

File tree

7 files changed

+336
-67
lines changed

7 files changed

+336
-67
lines changed

usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
// OLED displays to provide a four line display
1111
// for WLED.
1212
//
13-
// This Usermod works best, by far, when coupled with RotaryEncoderUIUsermod.
13+
// Dependencies
14+
// * This usermod REQURES the ModeSortUsermod
15+
// * This Usermod works best, by far, when coupled
16+
// with RotaryEncoderUIUsermod.
1417
//
1518
// Make sure to enable NTP and set your time zone in WLED Config | Time.
1619
//
@@ -124,6 +127,9 @@ class FourLineDisplayUsermod : public Usermod {
124127

125128
char lineBuffer[LINE_BUFFER_SIZE];
126129

130+
char **modes_qstrings = nullptr;
131+
char **palettes_qstrings = nullptr;
132+
127133
// If display does not work or looks corrupted check the
128134
// constructor reference:
129135
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
@@ -140,6 +146,10 @@ class FourLineDisplayUsermod : public Usermod {
140146
u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
141147
u8x8.setFont(u8x8_font_chroma48medium8_r);
142148
u8x8.DRAW_STRING(0, 0*LINE_HEIGHT, "Loading...");
149+
150+
ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
151+
modes_qstrings = modeSortUsermod->getModesQStrings();
152+
palettes_qstrings = modeSortUsermod->getPalettesQStrings();
143153
}
144154

145155
// gets called every time WiFi is (re-)connected. Initialize own network
@@ -254,7 +264,7 @@ class FourLineDisplayUsermod : public Usermod {
254264
}
255265

256266
// Third row with mode name
257-
showCurrentEffectOrPalette(JSON_mode_names, 2, knownMode);
267+
showCurrentEffectOrPalette(modes_qstrings[knownMode], 2);
258268

259269
switch(lineThreeType) {
260270
case FLD_LINE_3_BRIGHTNESS:
@@ -270,7 +280,7 @@ class FourLineDisplayUsermod : public Usermod {
270280
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
271281
break;
272282
case FLD_LINE_3_PALETTE:
273-
showCurrentEffectOrPalette(JSON_palette_names, 3, knownPalette);
283+
showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3);
274284
break;
275285
}
276286

@@ -289,35 +299,21 @@ class FourLineDisplayUsermod : public Usermod {
289299
* TODO: Should we cache the current effect and
290300
* TODO: palette name? This seems expensive.
291301
*/
292-
void showCurrentEffectOrPalette(const char json[], uint8_t row, uint8_t desiredEntry) {
293-
uint8_t qComma = 0;
294-
bool insideQuotes = false;
295-
// advance past the mark for markLineNum that may exist.
302+
void showCurrentEffectOrPalette(char *qstring, uint8_t row) {
296303
uint8_t printedChars = 1;
297304
char singleJsonSymbol;
298-
299-
// Find the mode name in JSON
300-
for (size_t i = 0; i < strlen_P(json); i++) {
301-
singleJsonSymbol = pgm_read_byte_near(json + i);
302-
switch (singleJsonSymbol) {
303-
case '"':
304-
insideQuotes = !insideQuotes;
305-
break;
306-
case '[':
307-
case ']':
305+
int i = 0;
306+
while (true) {
307+
singleJsonSymbol = pgm_read_byte_near(qstring + i);
308+
if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) {
308309
break;
309-
case ',':
310-
qComma++;
311-
default:
312-
if (!insideQuotes || (qComma != desiredEntry)) {
313-
break;
314-
}
315-
u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol);
316-
printedChars++;
317310
}
318-
if ((qComma > desiredEntry) || (printedChars > u8x8.getCols() - 2)) {
311+
u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol);
312+
printedChars++;
313+
if ( (printedChars > u8x8.getCols() - 2)) {
319314
break;
320315
}
316+
i++;
321317
}
322318
}
323319

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Mode Sort
2+
3+
v2 usermod that provides data about modes and
4+
palettes to other usermods. Notably it provides:
5+
* A direct method for a mode or palette name
6+
* Ability to retrieve mode and palette names in
7+
alphabetical order
8+
9+
```char **getModesQStrings()```
10+
11+
Provides an array of char* (pointers) to the names of the
12+
palettes within JSON_mode_names, in the same order as
13+
JSON_mode_names. These strings end in double quote (")
14+
(or \0 if there is a problem).
15+
16+
```byte *getModesAlphaIndexes()```
17+
18+
An array of byte designating the indexes of names of the
19+
modes in alphabetical order. "Solid" will always remain
20+
at the front of the list.
21+
22+
```char **getPalettesQStrings()```
23+
24+
Provides an array of char* (pointers) to the names of the
25+
palettes within JSON_palette_names, in the same order as
26+
JSON_palette_names. These strings end in double quote (")
27+
(or \0 if there is a problem).
28+
29+
```byte *getPalettesAlphaIndexes()```
30+
31+
An array of byte designating the indexes of names of the
32+
palettes in alphabetical order. "Default" and those
33+
starting with "(" will always remain at the front of the list.
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#pragma once
2+
3+
#include "wled.h"
4+
5+
//
6+
// v2 usermod that provides data about modes and
7+
// palettes to other usermods. Notably it provides:
8+
// * A direct method for a mode or palette name
9+
// * Ability to retrieve mode and palette names in
10+
// alphabetical order
11+
//
12+
// char **getModesQStrings()
13+
// Provides an array of char* (pointers) to the names of the
14+
// palettes within JSON_mode_names, in the same order as
15+
// JSON_mode_names. These strings end in double quote (")
16+
// (or \0 if there is a problem).
17+
//
18+
// byte *getModesAlphaIndexes()
19+
// An array of byte designating the indexes of names of the
20+
// modes in alphabetical order. "Solid" will always remain
21+
// at the front of the list.
22+
//
23+
// char **getPalettesQStrings()
24+
// Provides an array of char* (pointers) to the names of the
25+
// palettes within JSON_palette_names, in the same order as
26+
// JSON_palette_names. These strings end in double quote (")
27+
// (or \0 if there is a problem).
28+
//
29+
// byte *getPalettesAlphaIndexes()
30+
// An array of byte designating the indexes of names of the
31+
// palettes in alphabetical order. "Default" and those
32+
// starting with "(" will always remain at the front of the list.
33+
//
34+
35+
// Number of modes at the start of the list to not sort
36+
#define MODE_SORT_SKIP_COUNT 1
37+
38+
// Which list is being sorted
39+
char **listBeingSorted = nullptr;
40+
41+
/**
42+
* Modes and palettes are stored as strings that
43+
* end in a quote character. Compare two of them.
44+
* We are comparing directly within either
45+
* JSON_mode_names or JSON_palette_names.
46+
*/
47+
int re_qstringCmp(const void *ap, const void *bp) {
48+
char *a = listBeingSorted[*((byte *)ap)];
49+
char *b = listBeingSorted[*((byte *)bp)];
50+
int i = 0;
51+
do {
52+
char aVal = pgm_read_byte_near(a + i);
53+
if (aVal >= 97 && aVal <= 122) {
54+
// Lowercase
55+
aVal -= 32;
56+
}
57+
char bVal = pgm_read_byte_near(b + i);
58+
if (bVal >= 97 && bVal <= 122) {
59+
// Lowercase
60+
bVal -= 32;
61+
}
62+
// Relly we shouldn't ever get to '\0'
63+
if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
64+
// We're done. one is a substring of the other
65+
// or something happenend and the quote didn't stop us.
66+
if (aVal == bVal) {
67+
// Same value, probably shouldn't happen
68+
// with this dataset
69+
return 0;
70+
}
71+
else if (aVal == '"' || aVal == '\0') {
72+
return -1;
73+
}
74+
else {
75+
return 1;
76+
}
77+
}
78+
if (aVal == bVal) {
79+
// Same characters. Move to the next.
80+
i++;
81+
continue;
82+
}
83+
// We're done
84+
if (aVal < bVal) {
85+
return -1;
86+
}
87+
else {
88+
return 1;
89+
}
90+
} while (true);
91+
// We shouldn't get here.
92+
return 0;
93+
}
94+
95+
class ModeSortUsermod : public Usermod {
96+
private:
97+
98+
// Pointers the start of the mode names within JSON_mode_names
99+
char **modes_qstrings = nullptr;
100+
101+
// Array of mode indexes in alphabetical order.
102+
byte *modes_alpha_indexes = nullptr;
103+
104+
// Pointers the start of the palette names within JSON_palette_names
105+
char **palettes_qstrings = nullptr;
106+
107+
// Array of palette indexes in alphabetical order.
108+
byte *palettes_alpha_indexes = nullptr;
109+
110+
public:
111+
/**
112+
* setup() is called once at boot. WiFi is not yet connected at this point.
113+
* You can use it to initialize variables, sensors or similar.
114+
*/
115+
void setup() {
116+
// Sort the modes and palettes on startup
117+
// as they are guarantted to change.
118+
sortModesAndPalettes();
119+
}
120+
121+
char **getModesQStrings() {
122+
return modes_qstrings;
123+
}
124+
125+
byte *getModesAlphaIndexes() {
126+
return modes_alpha_indexes;
127+
}
128+
129+
char **getPalettesQStrings() {
130+
return palettes_qstrings;
131+
}
132+
133+
byte *getPalettesAlphaIndexes() {
134+
return palettes_alpha_indexes;
135+
}
136+
137+
/**
138+
* This Usermod doesn't have anything for loop.
139+
*/
140+
void loop() {}
141+
142+
/**
143+
* Sort the modes and palettes to the index arrays
144+
* modes_alpha_indexes and palettes_alpha_indexes.
145+
*/
146+
void sortModesAndPalettes() {
147+
modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
148+
modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
149+
re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT);
150+
151+
palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount());
152+
palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount());
153+
154+
int skipPaletteCount = 1;
155+
while (true) {
156+
// How many palette names start with '*' and should not be sorted?
157+
// (Also skipping the first one, 'Default').
158+
if (pgm_read_byte_near(palettes_qstrings[skipPaletteCount]) == '*') {
159+
skipPaletteCount++;
160+
}
161+
else {
162+
break;
163+
}
164+
}
165+
166+
re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount);
167+
}
168+
169+
byte *re_initIndexArray(int numModes) {
170+
byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
171+
for (byte i = 0; i < numModes; i++) {
172+
indexes[i] = i;
173+
}
174+
return indexes;
175+
}
176+
177+
/**
178+
* Return an array of mode or palette names from the JSON string.
179+
* They don't end in '\0', they end in '"'.
180+
*/
181+
char **re_findModeStrings(const char json[], int numModes) {
182+
char **modeStrings = (char **)malloc(sizeof(char *) * numModes);
183+
uint8_t modeIndex = 0;
184+
bool insideQuotes = false;
185+
// advance past the mark for markLineNum that may exist.
186+
char singleJsonSymbol;
187+
188+
// Find the mode name in JSON
189+
bool complete = false;
190+
for (size_t i = 0; i < strlen_P(json); i++) {
191+
singleJsonSymbol = pgm_read_byte_near(json + i);
192+
switch (singleJsonSymbol) {
193+
case '"':
194+
insideQuotes = !insideQuotes;
195+
if (insideQuotes) {
196+
// We have a new mode or palette
197+
modeStrings[modeIndex] = (char *)(json + i + 1);
198+
}
199+
break;
200+
case '[':
201+
break;
202+
case ']':
203+
complete = true;
204+
break;
205+
case ',':
206+
modeIndex++;
207+
default:
208+
if (!insideQuotes) {
209+
break;
210+
}
211+
}
212+
if (complete) {
213+
break;
214+
}
215+
}
216+
return modeStrings;
217+
}
218+
219+
/**
220+
* Sort either the modes or the palettes using quicksort.
221+
*/
222+
void re_sortModes(char **modeNames, byte *indexes, int count, int numSkip) {
223+
listBeingSorted = modeNames;
224+
qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
225+
listBeingSorted = nullptr;
226+
}
227+
228+
/*
229+
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
230+
* Values in the state object may be modified by connected clients
231+
*/
232+
void addToJsonState(JsonObject &root) {}
233+
234+
/*
235+
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
236+
* Values in the state object may be modified by connected clients
237+
*/
238+
void readFromJsonState(JsonObject &root) {}
239+
240+
/*
241+
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
242+
* This could be used in the future for the system to determine whether your usermod is installed.
243+
*/
244+
uint16_t getId()
245+
{
246+
return USERMOD_ID_MODE_SORT;
247+
}
248+
};

usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ platform = [email protected]
88
build_unflags = ${common.build_unflags}
99
build_flags =
1010
${common.build_flags_esp32}
11+
-D USERMOD_MODE_SORT
1112
-D USERMOD_FOUR_LINE_DISLAY -D FLD_PIN_SCL=22 -D FLD_PIN_SDA=21
1213
-D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19
1314
-D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1
@@ -26,6 +27,7 @@ board_build.ldscript = ${common.ldscript_4m1m}
2627
build_unflags = ${common.build_unflags}
2728
build_flags =
2829
${common.build_flags_esp8266}
30+
-D USERMOD_MODE_SORT
2931
-D USERMOD_FOUR_LINE_DISLAY -D FLD_PIN_SCL=5 -D FLD_PIN_SDA=4
3032
-D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=12 -D ENCODER_CLK_PIN=14 -D ENCODER_SW_PIN=13
3133
-D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1

0 commit comments

Comments
 (0)