4LineDisplay rewrite for dynamic configuration.

Added handling for multiple pins in usermod.
Fixed minor bugs.
This commit is contained in:
Blaz Kristan 2021-04-11 00:38:13 +02:00
parent 13b3b2fd23
commit a6feb77e52
7 changed files with 376 additions and 189 deletions

View File

@ -24,50 +24,25 @@
//
//The SCL and SDA pins are defined here.
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 5
#endif
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA 4
#endif
// U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(
// U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA);
U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8(
U8X8_PIN_NONE, FLD_PIN_SCL, FLD_PIN_SDA);
// Screen upside down? Change to 0 or 1
#ifndef FLIP_MODE
#define FLIP_MODE 0
#endif
// LINE_HEIGHT 1 is single height, for 128x32 displays.
// LINE_HEIGHT 2 makes the 128x64 screen display at double height.
#ifndef LINE_HEIGHT
#define LINE_HEIGHT 2
#endif
// If you aren't also including RotaryEncoderUIUsermod
// you probably want to set both
// SLEEP_MODE_ENABLED false
// CLOCK_MODE_ENABLED false
// as you will never be able wake the display / disable the clock.
#ifdef USERMOD_ROTARY_ENCODER_UI
#ifndef SLEEP_MODE_ENABLED
#define SLEEP_MODE_ENABLED true
#endif
#ifndef CLOCK_MODE_ENABLED
#define CLOCK_MODE_ENABLED true
#endif
#ifdef ARDUINO_ARCH_ESP32
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 22
#endif
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA 21
#endif
#else
#define SLEEP_MODE_ENABLED false
#define CLOCK_MODE_ENABLED false
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL 5
#endif
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA 4
#endif
#endif
// When to time out to the clock or blank the screen
// if SLEEP_MODE_ENABLED.
#define SCREEN_TIMEOUT_MS 15*1000
#define SCREEN_TIMEOUT_MS 60*1000 // 1 min
#define TIME_INDENT 0
#define DATE_INDENT 2
@ -75,33 +50,39 @@ U8X8_SH1106_128X64_WINSTAR_HW_I2C u8x8(
// Minimum time between redrawing screen in ms
#define USER_LOOP_REFRESH_RATE_MS 1000
#if LINE_HEIGHT == 2
#define DRAW_STRING draw1x2String
#define DRAW_GLYPH draw1x2Glyph
#define DRAW_BIG_STRING draw2x2String
#else
#define DRAW_STRING drawString
#define DRAW_GLYPH drawGlyph
#define DRAW_BIG_STRING draw2x2String
#endif
// Extra char (+1) for null
#define LINE_BUFFER_SIZE 16+1
#define FLD_LINE_3_BRIGHTNESS 0
#define FLD_LINE_3_EFFECT_SPEED 1
#define FLD_LINE_3_EFFECT_INTENSITY 2
#define FLD_LINE_3_PALETTE 3
#if LINE_HEIGHT == 2
#define TIME_LINE 1
#else
#define TIME_LINE 0
#endif
typedef enum {
FLD_LINE_3_BRIGHTNESS = 0,
FLD_LINE_3_EFFECT_SPEED,
FLD_LINE_3_EFFECT_INTENSITY,
FLD_LINE_3_PALETTE,
FLD_LINE_3_TIME
} Line3Type;
typedef enum {
NONE = 0,
SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C
SH1106 // U8X8_SH1106_128X64_WINSTAR_HW_I2C
} DisplayType;
class FourLineDisplayUsermod : public Usermod {
private:
unsigned long lastTime = 0;
// HW interface & configuration
void *u8x8; // pointer to U8X8 display object
int8_t sclPin, sdaPin; // I2C pins for interfacing, get initialised in readFromConfig()
DisplayType type = NONE; // display type
bool flip; // flip display 180°
uint8_t contrast;
uint8_t lineHeight; // 1 row or 2 rows
uint32_t refreshRate; // in ms
uint32_t screenTimeout; // in ms
bool sleepMode; // allow screen sleep?
bool clockMode; // display clock instead of info?
// needRedraw marks if redraw is required to prevent often redrawing.
bool needRedraw = true;
@ -121,12 +102,10 @@ class FourLineDisplayUsermod : public Usermod {
long lastUpdate = 0;
long lastRedraw = 0;
long overlayUntil = 0;
byte lineThreeType = FLD_LINE_3_BRIGHTNESS;
Line3Type lineThreeType = FLD_LINE_3_BRIGHTNESS;
// Set to 2 or 3 to mark lines 2 or 3. Other values ignored.
byte markLineNum = 0;
char lineBuffer[LINE_BUFFER_SIZE];
char **modes_qstrings = nullptr;
char **palettes_qstrings = nullptr;
@ -140,12 +119,30 @@ class FourLineDisplayUsermod : public Usermod {
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void setup() {
u8x8.begin();
u8x8.setFlipMode(FLIP_MODE);
u8x8.setPowerSave(0);
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...");
if (type==NONE) return;
if (!pinManager.allocatePin(sclPin)) { sclPin = -1; type = NONE; return;}
if (!pinManager.allocatePin(sdaPin)) { pinManager.deallocatePin(sclPin); sclPin = sdaPin = -1; type = NONE; return; }
switch (type) {
case SSD1306:
u8x8 = (void *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->begin();
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setFlipMode(flip);
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
break;
case SH1106:
u8x8 = (void *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->begin();
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setFlipMode(flip);
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
break;
default:
u8x8 = nullptr;
type = NONE;
return;
}
setPowerSave(0);
String loading = String(F("Loading..."));
drawString(0, 0, loading.c_str());
ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
modes_qstrings = modeSortUsermod->getModesQStrings();
@ -160,7 +157,7 @@ class FourLineDisplayUsermod : public Usermod {
* Da loop.
*/
void loop() {
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) {
if (millis() - lastUpdate < refreshRate) {
return;
}
lastUpdate = millis();
@ -168,18 +165,97 @@ class FourLineDisplayUsermod : public Usermod {
redraw(false);
}
/**
* Wrappers for screen drawing
*/
void drawString(uint8_t col, uint8_t row, const char *string) {
if (type==NONE) return;
switch (type) {
case SSD1306:
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
if (lineHeight==2) (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->draw1x2String(col, row, string);
else (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->drawString(col, row, string);
break;
case SH1106:
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
if (lineHeight==2) (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->draw1x2String(col, row, string);
else (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->drawString(col, row, string);
break;
}
}
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
if (type==NONE) return;
switch (type) {
case SSD1306:
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->draw2x2String(col, row, string);
break;
case SH1106:
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->draw2x2String(col, row, string);
break;
}
}
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) {
if (type==NONE) return;
switch (type) {
case SSD1306:
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setFont(font);
if (lineHeight==2) (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->draw1x2Glyph(col, row, glyph);
else (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->drawGlyph(col, row, glyph);
break;
case SH1106:
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setFont(font);
if (lineHeight==2) (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->draw1x2Glyph(col, row, glyph);
else (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->drawGlyph(col, row, glyph);
break;
}
}
uint8_t getCols() {
if (type==NONE) return 255;
switch (type) {
case SSD1306:
return (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->getCols();
case SH1106:
return (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->getCols();
}
}
void clear() {
if (type==NONE) return;
switch (type) {
case SSD1306:
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->clear();
break;
case SH1106:
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->clear();
break;
}
}
void setPowerSave(uint8_t save) {
if (type==NONE) return;
switch (type) {
case SSD1306:
(static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8))->setPowerSave(save);
break;
case SH1106:
(static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8))->setPowerSave(save);
break;
}
}
/**
* Redraw the screen (but only if things have changed
* or if forceRedraw).
*/
void redraw(bool forceRedraw) {
if (type==NONE) return;
if (overlayUntil > 0) {
if (millis() >= overlayUntil) {
// Time to display the overlay has elapsed.
overlayUntil = 0;
forceRedraw = true;
}
else {
} else {
// We are still displaying the overlay
// Don't redraw.
return;
@ -208,22 +284,41 @@ class FourLineDisplayUsermod : public Usermod {
if (!needRedraw) {
// Nothing to change.
// Turn off display after 3 minutes with no change.
if(SLEEP_MODE_ENABLED && !displayTurnedOff &&
(millis() - lastRedraw > SCREEN_TIMEOUT_MS)) {
if(sleepMode && !displayTurnedOff && (millis() - lastRedraw > screenTimeout)) {
// We will still check if there is a change in redraw()
// and turn it back on if it changed.
sleepOrClock(true);
}
else if (displayTurnedOff && CLOCK_MODE_ENABLED) {
} else if (displayTurnedOff && clockMode) {
showTime();
} else if ((millis() - lastRedraw)/1000%3 == 0) {
// change 4th line every 3s
switch (lineThreeType) {
case FLD_LINE_3_BRIGHTNESS:
setLineThreeType(FLD_LINE_3_PALETTE);
break;
case FLD_LINE_3_PALETTE:
setLineThreeType(FLD_LINE_3_EFFECT_SPEED);
break;
case FLD_LINE_3_EFFECT_SPEED:
setLineThreeType(FLD_LINE_3_EFFECT_INTENSITY);
break;
case FLD_LINE_3_EFFECT_INTENSITY:
setLineThreeType(FLD_LINE_3_BRIGHTNESS);
break;
default:
setLineThreeType(FLD_LINE_3_BRIGHTNESS);
break;
}
drawLineThree();
}
return;
}
} else
clear();
needRedraw = false;
lastRedraw = millis();
if (displayTurnedOff)
{
if (displayTurnedOff) {
// Turn the display back on
sleepOrClock(false);
}
@ -242,54 +337,55 @@ class FourLineDisplayUsermod : public Usermod {
knownEffectIntensity = effectIntensity;
// Do the actual drawing
u8x8.clear();
u8x8.setFont(u8x8_font_chroma48medium8_r);
// First row with Wifi name
String ssidString = knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0);
u8x8.DRAW_STRING(1, 0*LINE_HEIGHT, ssidString.c_str());
// Print `~` char to indicate that SSID is longer, than owr dicplay
if (knownSsid.length() > u8x8.getCols()) {
u8x8.DRAW_STRING(u8x8.getCols() - 1, 0*LINE_HEIGHT, "~");
String ssidString = knownSsid.substring(0, getCols() > 1 ? getCols() - 2 : 0);
drawString(1, 0, ssidString.c_str());
// Print `~` char to indicate that SSID is longer, than our display
if (knownSsid.length() > getCols()) {
drawString(getCols() - 1, 0, "~");
}
// Second row with IP or Psssword
// Print password in AP mode and if led is OFF.
if (apActive && bri == 0) {
u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, apPass);
}
else {
String ipString = knownIp.toString();
u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, ipString.c_str());
drawString(1, lineHeight, apPass);
} else {
drawString(1, lineHeight, (knownIp.toString()).c_str());
}
// Third row with mode name
showCurrentEffectOrPalette(modes_qstrings[knownMode], 2);
// Fourth row
drawLineThree();
drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0), u8x8_font_open_iconic_weather_2x2); // sun/moon icon
drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // wifi icon
drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // home icon
//if (markLineNum>1) drawGlyph(2, markLineNum*lineHeight, 66, u8x8_font_open_iconic_arrow_1x1); // arrow icon
}
void drawLineThree() {
char lineBuffer[LINE_BUFFER_SIZE];
switch(lineThreeType) {
case FLD_LINE_3_BRIGHTNESS:
sprintf(lineBuffer, "Brightness %d", bri);
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
sprintf_P(lineBuffer, PSTR("Brightness %3d"), bri);
drawString(2, 3*lineHeight, lineBuffer);
break;
case FLD_LINE_3_EFFECT_SPEED:
sprintf(lineBuffer, "FX Speed %d", effectSpeed);
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed);
drawString(2, 3*lineHeight, lineBuffer);
break;
case FLD_LINE_3_EFFECT_INTENSITY:
sprintf(lineBuffer, "FX Intense %d", effectIntensity);
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity);
drawString(2, 3*lineHeight, lineBuffer);
break;
case FLD_LINE_3_PALETTE:
default:
showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3);
break;
}
u8x8.setFont(u8x8_font_open_iconic_arrow_1x1);
u8x8.DRAW_GLYPH(0, markLineNum*LINE_HEIGHT, 66); // arrow icon
u8x8.setFont(u8x8_font_open_iconic_embedded_1x1);
u8x8.DRAW_GLYPH(0, 0*LINE_HEIGHT, 80); // wifi icon
u8x8.DRAW_GLYPH(0, 1*LINE_HEIGHT, 68); // home icon
}
/**
@ -300,21 +396,20 @@ class FourLineDisplayUsermod : public Usermod {
* TODO: palette name? This seems expensive.
*/
void showCurrentEffectOrPalette(char *qstring, uint8_t row) {
uint8_t printedChars = 1;
char lineBuffer[LINE_BUFFER_SIZE];
uint8_t printedChars = 0;
char singleJsonSymbol;
int i = 0;
while (true) {
singleJsonSymbol = pgm_read_byte_near(qstring + i);
while (printedChars < getCols() - 2) {
singleJsonSymbol = pgm_read_byte_near(qstring + printedChars);
if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) {
break;
}
u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol);
printedChars++;
if ( (printedChars > u8x8.getCols() - 2)) {
break;
}
i++;
lineBuffer[printedChars++] = singleJsonSymbol;
}
for (;printedChars<getCols()-2; printedChars++) lineBuffer[printedChars]=' ';
lineBuffer[printedChars] = 0;
drawString(2, row*lineHeight, lineBuffer);
}
/**
@ -345,14 +440,9 @@ class FourLineDisplayUsermod : public Usermod {
}
// Print the overlay
u8x8.clear();
u8x8.setFont(u8x8_font_chroma48medium8_r);
if (line1) {
u8x8.DRAW_STRING(0, 1*LINE_HEIGHT, line1);
}
if (line2) {
u8x8.DRAW_STRING(0, 2*LINE_HEIGHT, line2);
}
clear();
if (line1) drawString(0, 1*lineHeight, line1);
if (line2) drawString(0, 2*lineHeight, line2);
overlayUntil = millis() + showHowLong;
}
@ -360,16 +450,16 @@ class FourLineDisplayUsermod : public Usermod {
* Specify what data should be defined on line 3
* (the last line).
*/
void setLineThreeType(byte newLineThreeType) {
void setLineThreeType(Line3Type newLineThreeType) {
if (newLineThreeType == FLD_LINE_3_BRIGHTNESS ||
newLineThreeType == FLD_LINE_3_EFFECT_SPEED ||
newLineThreeType == FLD_LINE_3_EFFECT_INTENSITY ||
newLineThreeType == FLD_LINE_3_PALETTE) {
newLineThreeType == FLD_LINE_3_PALETTE ||
newLineThreeType == FLD_LINE_3_TIME) {
lineThreeType = newLineThreeType;
}
else {
// Unknown value.
lineThreeType = FLD_LINE_3_BRIGHTNESS;
} else {
// Unknown value
lineThreeType = FLD_LINE_3_BRIGHTNESS;
}
}
@ -388,41 +478,22 @@ class FourLineDisplayUsermod : public Usermod {
}
}
/*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
*/
/*
void addToJsonInfo(JsonObject& root)
{
int reading = 20;
//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
}
*/
/**
* Enable sleep (turn the display off) or clock mode.
*/
void sleepOrClock(bool enabled) {
if (enabled) {
if (CLOCK_MODE_ENABLED) {
if (clockMode) {
showTime();
}
else {
u8x8.setPowerSave(1);
setPowerSave(1);
}
displayTurnedOff = true;
}
else {
if (!CLOCK_MODE_ENABLED) {
u8x8.setPowerSave(0);
if (!clockMode) {
setPowerSave(0);
}
displayTurnedOff = false;
}
@ -433,7 +504,9 @@ class FourLineDisplayUsermod : public Usermod {
* on the middle rows. Based 24 or 12 hour depending on
* the useAMPM configuration.
*/
void showTime() {
void showTime(bool fullScreen = true) {
char lineBuffer[LINE_BUFFER_SIZE];
updateLocalTime();
byte minuteCurrent = minute(localTime);
byte hourCurrent = hour(localTime);
@ -444,12 +517,14 @@ class FourLineDisplayUsermod : public Usermod {
knownMinute = minuteCurrent;
knownHour = hourCurrent;
u8x8.clear();
u8x8.setFont(u8x8_font_chroma48medium8_r);
if (fullScreen) clear();
int currentMonth = month(localTime);
sprintf(lineBuffer, "%s %d", monthShortStr(currentMonth), day(localTime));
u8x8.DRAW_BIG_STRING(DATE_INDENT, TIME_LINE*LINE_HEIGHT, lineBuffer);
sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(currentMonth), day(localTime));
if (fullScreen)
draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer);
else
drawString(0, lineHeight, lineBuffer);
byte showHour = hourCurrent;
boolean isAM = false;
@ -467,10 +542,25 @@ class FourLineDisplayUsermod : public Usermod {
}
}
sprintf(lineBuffer, "%02d:%02d %s", showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : "");
sprintf_P(lineBuffer, PSTR("%02d:%02d %s"), showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : "");
// For time, we always use LINE_HEIGHT of 2 since
// we are printing it big.
u8x8.DRAW_BIG_STRING(TIME_INDENT + (useAMPM ? 0 : 2), (TIME_LINE + 1) * 2, lineBuffer);
if (fullScreen)
draw2x2String(TIME_INDENT + (useAMPM ? 0 : 2), lineHeight*2, lineBuffer);
else
drawString(8, lineHeight, lineBuffer);
}
/*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
*/
void addToJsonInfo(JsonObject& root) {
//JsonObject user = root["u"];
//if (user.isNull()) user = root.createNestedObject("u");
//JsonArray data = user.createNestedArray(F("4LineDisplay"));
//data.add(F("Loaded."));
}
/*
@ -485,6 +575,46 @@ class FourLineDisplayUsermod : public Usermod {
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject& root) {
DisplayType newType = type;
uint8_t newScl = sclPin;
uint8_t newSda = sdaPin;
uint8_t newContrast = contrast;
bool newFlip = flip;
if (root[F("4LineDisplay_type")] != nullptr) newType = (DisplayType)root[F("4LineDisplay_type")];
if (root[F("4LineDisplay_pin")] != nullptr) {
newScl = (int)root[F("4LineDisplay_pin")][0];
newSda = (int)root[F("4LineDisplay_pin")][1];
}
if (root[F("4LineDisplay_contrast")] != nullptr) newContrast = (int)root[F("4LineDisplay_contrast")];
if (root[F("4LineDisplay_refreshRate")] != nullptr) refreshRate = (int)root[F("4LineDisplay_refreshRate")]*1000;
if (root[F("4LineDisplay_screenTimeOut")] != nullptr) screenTimeout = (int)root[F("4LineDisplay_screenTimeOut")]*1000;
if (root[F("4LineDisplay_flip")] != nullptr) {
String str = root[F("4LineDisplay_flip")]; // checkbox -> off or on
newFlip = (bool)(str!="off"); // off is guaranteed to be present
}
if (root[F("4LineDisplay_sleepMode")] != nullptr) {
String str = root[F("4LineDisplay_sleepMode")]; // checkbox -> off or on
sleepMode = (bool)(str!="off"); // off is guaranteed to be present
}
if (root[F("4LineDisplay_clockMode")] != nullptr) {
String str = root[F("4LineDisplay_clockMode")]; // checkbox -> off or on
clockMode = (bool)(str!="off"); // off is guaranteed to be present
}
if (flip!=newFlip || contrast!=newContrast || sclPin!=newScl || sdaPin!=newSda || type!=newType) {
if (type==SSD1306) delete (static_cast<U8X8_SSD1306_128X32_UNIVISION_HW_I2C*>(u8x8));
if (type==SH1106) delete (static_cast<U8X8_SH1106_128X64_WINSTAR_HW_I2C*>(u8x8));
pinManager.deallocatePin(sclPin);
pinManager.deallocatePin(sdaPin);
sclPin = newScl;
sdaPin = newSda;
type = newType;
contrast = newContrast;
flip = newFlip;
lineHeight = type==SH1106 ? 2 : 1;
setup();
}
}
/*
@ -502,6 +632,17 @@ class FourLineDisplayUsermod : public Usermod {
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/
void addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(F("4LineDisplay"));
JsonArray i2c_pin = top.createNestedArray("pin");
i2c_pin.add(sclPin);
i2c_pin.add(sdaPin);
top["type"] = type;
top[F("flip")] = (bool) flip;
top[F("contrast")] = contrast;
top[F("refreshRate")] = refreshRate/1000;
top[F("screenTimeOut")] = screenTimeout/1000;
top[F("sleepMode")] = (bool) sleepMode;
top[F("clockMode")] = (bool) clockMode;
}
/*
@ -513,6 +654,17 @@ class FourLineDisplayUsermod : public Usermod {
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
*/
void readFromConfig(JsonObject& root) {
JsonObject top = root[F("4LineDisplay")];
sclPin = top["pin"][0] | FLD_PIN_SCL;
sdaPin = top["pin"][1] | FLD_PIN_SDA;
type = top["type"] | SSD1306;
lineHeight = type==SH1106 ? 2 : 1;
flip = top[F("flip")] | false ;
contrast = top[F("contrast")] | 10;
refreshRate = int(top[F("refreshRate")])*1000 | USER_LOOP_REFRESH_RATE_MS;
screenTimeout = int(top[F("screenTimeOut")])*1000 | SCREEN_TIMEOUT_MS;
sleepMode = top[F("sleepMode")] | true;
clockMode = top[F("clockMode")] | false;
}
/*

View File

@ -10,6 +10,7 @@
var pins = [6,7,8,9,10,11];
var pinO = ["reserved","reserved","reserved","reserved","reserved","reserved"], owner;
var loc = false, locip;
var urows;
function gId(s) { return d.getElementById(s); }
function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); }
function H() { window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings"); }
@ -26,7 +27,8 @@
ldS();
}
function check(o,k) {
if (o.type=="number" && o.name.substr(-4)=="_pin") {
var n = o.name.substr(-6);
if (o.type=="number" && n.substr(0,4)=="_pin") {
for (var i=0; i<pins.length; i++) {
if (k==pinO[i]) continue;
if (o.value==pins[i]) { o.style.color="red"; break; } else o.style.color="#fff";
@ -53,6 +55,32 @@
}
}
}
function addField(k,f,o,a=false) {
if (isO(o)) {
for (const [s,v] of Object.entries(o)) {
addField(k,s,v);
}
} else if (Array.isArray(o)) {
for (var j=0; j<o.length; j++) {
addField(k,f,o[j],true);
}
} else {
var t,c;
switch (typeof o) {
case "boolean":
t = "checkbox"; c = o ? `checked value="on"` : ""; break;
case "number":
t = "number"; c = `value="${parseInt(o,10)}"`; break;
case "string":
t = "text"; c = `value="${o}"`; break;
default:
t = "text"; c = `value="${o}"`; break;
}
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
if (t=="checkbox") urows += `<input type="hidden" name="${k}_${f}${a?"[]":""}" value="off">`;
urows += `${f}: <input type="${t}" name="${k}_${f}${a?"[]":""}" ${c} oninput="check(this,'${k}')"><br>`;
}
}
function ldS() {
var url = (loc?`http://${locip}`:'') + '/cfg.json';
fetch(url, {
@ -65,28 +93,11 @@
.then(json => {
umCfg = json.um;
getPins(json);
var urows="";
urows="";
if (isO(umCfg)) {
for (const [k,o] of Object.entries(umCfg)) {
urows += `<hr><h3>${k}</h3>`;
if (isO(o)) {
for (const [s,v] of Object.entries(o)) {
var t,c;
switch (typeof v) {
case "boolean":
t = "checkbox"; c = v ? `checked value="on"` : ""; break;
case "number":
t = "number"; c = `value="${parseInt(v,10)}"`; break;
case "string":
t = "text"; c = `value="${v}"`; break;
default:
t = "text"; c = `value="${v}"`; break;
}
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
if (t=="checkbox") urows += `<input type="hidden" name="${k}_${s}" value="off">`;
urows += `${s}: <input type="${t}" name="${k}_${s}" ${c} oninput="check(this,'${k}')"><br>`;
}
}
addField(k,'unknown',o);
}
gId("um").innerHTML = urows;
}

View File

@ -394,7 +394,7 @@ type="submit">Save & Reboot</button></form></body></html>)=====";
// Autogenerated from wled00/data/settings_um.htm, do not edit!!
const char PAGE_settings_um[] PROGMEM = R"=====(<!DOCTYPE html><html><head lang="en"><meta charset="utf-8"><meta
name="viewport" content="width=500"><title>UI Settings</title><script>
var owner,locip,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["reserved","reserved","reserved","reserved","reserved","reserved"],loc=!1;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),ldS()}function check(e,n){if("number"==e.type&&"_pin"==e.name.substr(-4))for(var t=0;t<pins.length;t++)if(n!=pinO[t]){if(e.value==pins[t]){e.style.color="red";break}e.style.color="#fff"}}function getPins(e){if(isO(e))for(const[t,i]of Object.entries(e))if(isO(i))owner=t,getPins(i);else if("pin"==t)if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n])}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{umCfg=e.um,getPins(e);var n="";if(isO(umCfg)){for(const[e,o]of Object.entries(umCfg))if(n+=`<hr><h3>${e}</h3>`,isO(o))for(const[r,s]of Object.entries(o)){var t,i;switch(typeof s){case"boolean":t="checkbox",i=s?'checked value="on"':"";break;case"number":t="number",i=`value="${parseInt(s,10)}"`;break;case"string":default:t="text",i=`value="${s}"`}"checkbox"==t&&(n+=`<input type="hidden" name="${e}_${r}" value="off">`),n+=`${r}: <input type="${t}" name="${e}_${r}" ${i} oninput="check(this,'${e}')"><br>`}gId("um").innerHTML=n}}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV(){}
var owner,locip,urows,d=document,umCfg={},pins=[6,7,8,9,10,11],pinO=["reserved","reserved","reserved","reserved","reserved","reserved"],loc=!1;function gId(e){return d.getElementById(e)}function isO(e){return e&&"object"==typeof e&&!Array.isArray(e)}function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings")}function B(){window.open("/settings","_self")}function S(){"file:"==window.location.protocol&&(loc=!0,(locip=localStorage.getItem("locIp"))||(locip=prompt("File Mode. Please enter WLED IP!"),localStorage.setItem("locIp",locip))),ldS()}function check(e,n){var o=e.name.substr(-6);if("number"==e.type&&"_pin"==o.substr(0,4))for(var i=0;i<pins.length;i++)if(n!=pinO[i]){if(e.value==pins[i]){e.style.color="red";break}e.style.color="#fff"}}function getPins(e){if(isO(e))for(const[o,i]of Object.entries(e))if(isO(i))owner=o,getPins(i);else if("pin"==o)if(Array.isArray(i))for(var n=0;n<i.length;n++)i[n]>=0&&(pins.push(i[n]),pinO.push(owner));else i>=0&&(pins.push(i),pinO.push(owner));else if(Array.isArray(i))for(n=0;n<i.length;n++)getPins(i[n])}function addField(e,n,o,i=!1){if(isO(o))for(const[n,i]of Object.entries(o))addField(e,n,i);else if(Array.isArray(o))for(var t=0;t<o.length;t++)addField(e,n,o[t],!0);else{var r,s;switch(typeof o){case"boolean":r="checkbox",s=o?'checked value="on"':"";break;case"number":r="number",s=`value="${parseInt(o,10)}"`;break;case"string":default:r="text",s=`value="${o}"`}"checkbox"==r&&(urows+=`<input type="hidden" name="${e}_${n}${i?"[]":""}" value="off">`),urows+=`${n}: <input type="${r}" name="${e}_${n}${i?"[]":""}" ${s} oninput="check(this,'${e}')"><br>`}}function ldS(){fetch((loc?"http://"+locip:"")+"/cfg.json",{method:"get"}).then(e=>(e.ok||(gId("lserr").style.display="inline"),e.json())).then(e=>{if(umCfg=e.um,getPins(e),urows="",isO(umCfg)){for(const[e,n]of Object.entries(umCfg))urows+=`<hr><h3>${e}</h3>`,addField(e,"unknown",n);gId("um").innerHTML=urows}}).catch((function(e){gId("lserr").style.display="inline",console.log(e)}))}function svS(e){e.preventDefault(),console.log(d.Sf),d.Sf.checkValidity()&&d.Sf.submit()}function GetV(){}
</script>%CSS%%SCSS%</head><body onload="S()"><form
id="form_s" name="Sf" method="post" onsubmit="svS(event)"><div class="toprow">
<div class="helpB"><button type="button" onclick="H()">?</button></div><button

View File

@ -415,15 +415,33 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
JsonObject um = doc.createNestedObject(F("um"));
size_t args = request->args();
uint j=0;
for (size_t i=0; i<args; i++) {
String name = request->argName(i);
String value = request->arg(i);
um.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on")
um[name] = value;
DEBUG_PRINT(name);
DEBUG_PRINT(" = ");
DEBUG_PRINTLN(value);
if (name.endsWith("[]")) {
name.replace("[]","");
if (!um[name].is<JsonArray>()) {
JsonArray ar = um.createNestedArray(name);
ar.add(value);
j=0;
} else {
um[name].add(value);
j++;
}
DEBUG_PRINT(name);
DEBUG_PRINT("[");
DEBUG_PRINT(j);
DEBUG_PRINT("] = ");
DEBUG_PRINTLN(value);
} else {
um.remove(name); // checkboxes get two fields (first is always "off", existence of second depends on checkmark and may be "on")
um[name] = value;
DEBUG_PRINT(name);
DEBUG_PRINT(" = ");
DEBUG_PRINTLN(value);
}
}
usermods.readFromJsonState(um);
}

View File

@ -8,7 +8,7 @@
*/
// version code in format yymmddb (b = daily build)
#define VERSION 2104081
#define VERSION 2104111
//uncomment this if you have a "my_config.h" file you'd like to use
//#define WLED_USE_MY_CONFIG

View File

@ -322,6 +322,7 @@ String settingsProcessor(const String& var)
{
if (var == "CSS") {
char buf[2048];
buf[0] = 0;
getSettingsJS(optionType, buf);
return String(buf);
}

View File

@ -268,15 +268,20 @@ void getSettingsJS(byte subPage, char* dest)
if (!mods.isNull()) {
uint8_t i=0;
for (JsonPair kv : mods) {
if (strncmp_P(kv.key().c_str(),PSTR("pin_"),4) == 0) {
if (i++) oappend(SET_F(","));
oappendi((int)kv.value());
} else if (!kv.value().isNull()) {
if (!kv.value().isNull()) {
// element is an JsonObject
JsonObject obj = kv.value();
if (obj["pin"] != nullptr) {
if (i++) oappend(SET_F(","));
oappendi((int)obj["pin"]);
if (obj["pin"].is<JsonArray>()) {
JsonArray pins = obj["pin"].as<JsonArray>();
for (JsonVariant pv : pins) {
if (i++) oappend(SET_F(","));
oappendi(pv.as<int>());
}
} else {
if (i++) oappend(SET_F(","));
oappendi((int)obj["pin"]);
}
}
}
}