From a6feb77e526a6e94004438f5dbd844182de6a0ce Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 11 Apr 2021 00:38:13 +0200 Subject: [PATCH] 4LineDisplay rewrite for dynamic configuration. Added handling for multiple pins in usermod. Fixed minor bugs. --- .../usermod_v2_four_line_display.h | 464 ++++++++++++------ wled00/data/settings_um.htm | 51 +- wled00/html_settings.h | 2 +- wled00/set.cpp | 28 +- wled00/wled.h | 2 +- wled00/wled_server.cpp | 1 + wled00/xml.cpp | 17 +- 7 files changed, 376 insertions(+), 189 deletions(-) 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 0b59af55..ac1584f7 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 @@ -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))->begin(); + (static_cast(u8x8))->setFlipMode(flip); + (static_cast(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))->begin(); + (static_cast(u8x8))->setFlipMode(flip); + (static_cast(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))->setFont(u8x8_font_chroma48medium8_r); + if (lineHeight==2) (static_cast(u8x8))->draw1x2String(col, row, string); + else (static_cast(u8x8))->drawString(col, row, string); + break; + case SH1106: + (static_cast(u8x8))->setFont(u8x8_font_chroma48medium8_r); + if (lineHeight==2) (static_cast(u8x8))->draw1x2String(col, row, string); + else (static_cast(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))->setFont(u8x8_font_chroma48medium8_r); + (static_cast(u8x8))->draw2x2String(col, row, string); + break; + case SH1106: + (static_cast(u8x8))->setFont(u8x8_font_chroma48medium8_r); + (static_cast(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))->setFont(font); + if (lineHeight==2) (static_cast(u8x8))->draw1x2Glyph(col, row, glyph); + else (static_cast(u8x8))->drawGlyph(col, row, glyph); + break; + case SH1106: + (static_cast(u8x8))->setFont(font); + if (lineHeight==2) (static_cast(u8x8))->draw1x2Glyph(col, row, glyph); + else (static_cast(u8x8))->drawGlyph(col, row, glyph); + break; + } + } + uint8_t getCols() { + if (type==NONE) return 255; + switch (type) { + case SSD1306: + return (static_cast(u8x8))->getCols(); + case SH1106: + return (static_cast(u8x8))->getCols(); + } + } + void clear() { + if (type==NONE) return; + switch (type) { + case SSD1306: + (static_cast(u8x8))->clear(); + break; + case SH1106: + (static_cast(u8x8))->clear(); + break; + } + } + void setPowerSave(uint8_t save) { + if (type==NONE) return; + switch (type) { + case SSD1306: + (static_cast(u8x8))->setPowerSave(save); + break; + case SH1106: + (static_cast(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 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)); + if (type==SH1106) delete (static_cast(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; } /* diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index b9f3f923..c131187b 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -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`; + urows += `${f}:
`; + } + } 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 += `

${k}

`; - 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 += ``; - urows += `${s}:
`; - } - } + addField(k,'unknown',o); } gId("um").innerHTML = urows; } diff --git a/wled00/html_settings.h b/wled00/html_settings.h index d54a74ca..7aa39a9a 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -394,7 +394,7 @@ type="submit">Save & Reboot)====="; // Autogenerated from wled00/data/settings_um.htm, do not edit!! const char PAGE_settings_um[] PROGMEM = R"=====(UI Settings%CSS%%SCSS%