User configurable usermods.

- Utilizing addToConfig() & readFromConfig()
- UM configuration Settings page
- MQTT support for usermods
- A few sample usermods including rewritten Temparature and new Multi-Relay
This commit is contained in:
Blaz Kristan
2021-05-07 12:41:39 +02:00
parent fa075f6800
commit 136a00a301
23 changed files with 2583 additions and 1437 deletions

View File

@@ -1,4 +1,4 @@
# Rotary Encoder UI Usermod
# I2C 4 Line Display Usermod
First, thanks to the authors of the ssd11306_i2c_oled_u8g2 mod.
@@ -19,13 +19,11 @@ This file should be placed in the same directory as `platformio.ini`.
### Define Your Options
* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, that the display is available
* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells Rotary Encoder usermod, if installed, that the display is available
* `FLD_PIN_SCL` - The display SCL pin, defaults to 5
* `FLD_PIN_SDA` - The display SDA pin, defaults to 4
* `FLIP_MODE` - Set to 0 or 1
* `LINE_HEIGHT` - Set to 1 or 2
There are other `#define` values in the Usermod that might be of interest.
All of the parameters can be configured using Usermods settings page, inluding GPIO pins.
### PlatformIO requirements
@@ -37,3 +35,5 @@ UI usermod folder for how to include these using `platformio_override.ini`.
2021-02
* First public release
2021-04
* Adaptation for runtime configuration.

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,43 @@ 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_4_BRIGHTNESS = 0,
FLD_LINE_4_EFFECT_SPEED,
FLD_LINE_4_EFFECT_INTENSITY,
FLD_LINE_4_MODE,
FLD_LINE_4_PALETTE
} Line4Type;
typedef enum {
NONE = 0,
SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C
SH1106, // U8X8_SH1106_128X64_WINSTAR_HW_I2C
SSD1306_64 // U8X8_SSD1306_128X64_NONAME_HW_I2C
} DisplayType;
class FourLineDisplayUsermod : public Usermod {
private:
bool initDone = false;
unsigned long lastTime = 0;
// HW interface & configuration
U8X8 *u8x8 = nullptr; // pointer to U8X8 display object
int8_t sclPin=FLD_PIN_SCL, sdaPin=FLD_PIN_SDA; // I2C pins for interfacing, get initialised in readFromConfig()
DisplayType type = SSD1306; // display type
bool flip = false; // flip display 180°
uint8_t contrast = 10; // screen contrast
uint8_t lineHeight = 1; // 1 row or 2 rows
uint32_t refreshRate = USER_LOOP_REFRESH_RATE_MS; // in ms
uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms
bool sleepMode = true; // allow screen sleep?
bool clockMode = false; // display clock
// needRedraw marks if redraw is required to prevent often redrawing.
bool needRedraw = true;
@@ -118,38 +103,72 @@ class FourLineDisplayUsermod : public Usermod {
uint8_t knownHour = 99;
bool displayTurnedOff = false;
long lastUpdate = 0;
long lastRedraw = 0;
long overlayUntil = 0;
byte lineThreeType = FLD_LINE_3_BRIGHTNESS;
unsigned long lastUpdate = 0;
unsigned long lastRedraw = 0;
unsigned long overlayUntil = 0;
Line4Type lineFourType = FLD_LINE_4_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;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _contrast[];
static const char _refreshRate[];
static const char _screenTimeOut[];
static const char _flip[];
static const char _sleepMode[];
static const char _clockMode[];
// If display does not work or looks corrupted check the
// constructor reference:
// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp
// or check the gallery:
// https://github.com/olikraus/u8g2/wiki/gallery
public:
// 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...");
ModeSortUsermod *modeSortUsermod = (ModeSortUsermod*) usermods.lookup(USERMOD_ID_MODE_SORT);
modes_qstrings = modeSortUsermod->getModesQStrings();
palettes_qstrings = modeSortUsermod->getPalettesQStrings();
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:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
break;
case SH1106:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SH1106_128X64_WINSTAR_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
break;
case SSD1306_64:
#ifdef ESP8266
if (!(sclPin==5 && sdaPin==4))
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_SW_I2C(sclPin, sdaPin); // SCL, SDA, reset
else
#endif
u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_HW_I2C(U8X8_PIN_NONE, sclPin, sdaPin); // Pins are Reset, SCL, SDA
break;
default:
u8x8 = nullptr;
type = NONE;
return;
}
(static_cast<U8X8*>(u8x8))->begin();
setFlipMode(flip);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
setPowerSave(0);
drawString(0, 0, "Loading...");
initDone = true;
}
// gets called every time WiFi is (re-)connected. Initialize own network
@@ -160,7 +179,7 @@ class FourLineDisplayUsermod : public Usermod {
* Da loop.
*/
void loop() {
if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) {
if (millis() - lastUpdate < (clockMode?1000:refreshRate)) {
return;
}
lastUpdate = millis();
@@ -168,18 +187,59 @@ class FourLineDisplayUsermod : public Usermod {
redraw(false);
}
/**
* Wrappers for screen drawing
*/
void setFlipMode(uint8_t mode) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setFlipMode(mode);
}
void setContrast(uint8_t contrast) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setContrast(contrast);
}
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) (static_cast<U8X8*>(u8x8))->draw1x2String(col, row, string);
else (static_cast<U8X8*>(u8x8))->drawString(col, row, string);
}
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setFont(u8x8_font_chroma48medium8_r);
(static_cast<U8X8*>(u8x8))->draw2x2String(col, row, string);
}
void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setFont(font);
if (!ignoreLH && lineHeight==2) (static_cast<U8X8*>(u8x8))->draw1x2Glyph(col, row, glyph);
else (static_cast<U8X8*>(u8x8))->drawGlyph(col, row, glyph);
}
uint8_t getCols() {
if (type==NONE) return 0;
return (static_cast<U8X8*>(u8x8))->getCols();
}
void clear() {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->clear();
}
void setPowerSave(uint8_t save) {
if (type==NONE) return;
(static_cast<U8X8*>(u8x8))->setPowerSave(save);
}
/**
* 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 +268,46 @@ 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.
knownHour = 99; // force screen clear
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 (lineFourType) {
case FLD_LINE_4_BRIGHTNESS:
setLineFourType(FLD_LINE_4_EFFECT_SPEED);
break;
case FLD_LINE_4_MODE:
setLineFourType(FLD_LINE_4_BRIGHTNESS);
break;
case FLD_LINE_4_PALETTE:
setLineFourType(clockMode ? FLD_LINE_4_MODE : FLD_LINE_4_BRIGHTNESS);
break;
case FLD_LINE_4_EFFECT_SPEED:
setLineFourType(FLD_LINE_4_EFFECT_INTENSITY);
break;
case FLD_LINE_4_EFFECT_INTENSITY:
setLineFourType(FLD_LINE_4_PALETTE);
break;
default:
break;
}
drawLineFour();
}
return;
} else {
knownHour = 99; // force time display
clear();
}
needRedraw = false;
lastRedraw = millis();
if (displayTurnedOff)
{
if (displayTurnedOff) {
// Turn the display back on
sleepOrClock(false);
}
@@ -242,79 +326,94 @@ 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, "~");
drawGlyph(0, 0, 80, u8x8_font_open_iconic_embedded_1x1); // wifi icon
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
drawGlyph(0, lineHeight, 68, u8x8_font_open_iconic_embedded_1x1); // home icon
// 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);
// Third row with mode name or current time
if (clockMode) showTime(false);
else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 2);
switch(lineThreeType) {
case FLD_LINE_3_BRIGHTNESS:
sprintf(lineBuffer, "Brightness %d", bri);
u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer);
// Fourth row
drawLineFour();
drawGlyph(0, 2*lineHeight, 66 + (bri > 0 ? 3 : 0), u8x8_font_open_iconic_weather_2x2); // sun/moon icon
//if (markLineNum>1) drawGlyph(2, markLineNum*lineHeight, 66, u8x8_font_open_iconic_arrow_1x1); // arrow icon
}
void drawLineFour() {
char lineBuffer[LINE_BUFFER_SIZE];
switch(lineFourType) {
case FLD_LINE_4_BRIGHTNESS:
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);
case FLD_LINE_4_EFFECT_SPEED:
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);
case FLD_LINE_4_EFFECT_INTENSITY:
sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity);
drawString(2, 3*lineHeight, lineBuffer);
break;
case FLD_LINE_3_PALETTE:
showCurrentEffectOrPalette(palettes_qstrings[knownPalette], 3);
case FLD_LINE_4_MODE:
showCurrentEffectOrPalette(knownMode, JSON_mode_names, 3);
break;
case FLD_LINE_4_PALETTE:
default:
showCurrentEffectOrPalette(knownPalette, JSON_palette_names, 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
}
/**
* Display the current effect or palette (desiredEntry)
* on the appropriate line (row).
*
* TODO: Should we cache the current effect and
* TODO: palette name? This seems expensive.
*/
void showCurrentEffectOrPalette(char *qstring, uint8_t row) {
uint8_t printedChars = 1;
void showCurrentEffectOrPalette(int knownMode, const char *qstring, uint8_t row) {
char lineBuffer[LINE_BUFFER_SIZE];
uint8_t qComma = 0;
bool insideQuotes = false;
uint8_t printedChars = 0;
char singleJsonSymbol;
int i = 0;
while (true) {
// Find the mode name in JSON
for (size_t i = 0; i < strlen_P(qstring); i++) {
singleJsonSymbol = pgm_read_byte_near(qstring + i);
if (singleJsonSymbol == '"' || singleJsonSymbol == '\0' ) {
break;
if (singleJsonSymbol == '\0') break;
switch (singleJsonSymbol) {
case '"':
insideQuotes = !insideQuotes;
break;
case '[':
case ']':
break;
case ',':
qComma++;
default:
if (!insideQuotes || (qComma != knownMode)) break;
lineBuffer[printedChars++] = singleJsonSymbol;
}
u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol);
printedChars++;
if ( (printedChars > u8x8.getCols() - 2)) {
break;
}
i++;
if ((qComma > knownMode) || (printedChars > getCols()-2) || printedChars > sizeof(lineBuffer)-2) break;
}
for (;printedChars < getCols()-2 || printedChars > sizeof(lineBuffer)-2; printedChars++) lineBuffer[printedChars]=' ';
lineBuffer[printedChars] = 0;
drawString(2, row*lineHeight, lineBuffer);
}
/**
@@ -324,6 +423,7 @@ class FourLineDisplayUsermod : public Usermod {
* to wake up the screen.
*/
bool wakeDisplay() {
knownHour = 99;
if (displayTurnedOff) {
// Turn the display back on
sleepOrClock(false);
@@ -345,36 +445,31 @@ 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;
}
/**
* Specify what data should be defined on line 3
* Specify what data should be defined on line 4
* (the last line).
*/
void setLineThreeType(byte newLineThreeType) {
if (newLineThreeType == FLD_LINE_3_BRIGHTNESS ||
newLineThreeType == FLD_LINE_3_EFFECT_SPEED ||
newLineThreeType == FLD_LINE_3_EFFECT_INTENSITY ||
newLineThreeType == FLD_LINE_3_PALETTE) {
lineThreeType = newLineThreeType;
}
else {
// Unknown value.
lineThreeType = FLD_LINE_3_BRIGHTNESS;
void setLineFourType(Line4Type newLineFourType) {
if (newLineFourType == FLD_LINE_4_BRIGHTNESS ||
newLineFourType == FLD_LINE_4_EFFECT_SPEED ||
newLineFourType == FLD_LINE_4_EFFECT_INTENSITY ||
newLineFourType == FLD_LINE_4_MODE ||
newLineFourType == FLD_LINE_4_PALETTE) {
lineFourType = newLineFourType;
} else {
// Unknown value
lineFourType = FLD_LINE_4_BRIGHTNESS;
}
}
/**
* Line 2 or 3 (last two lines) can be marked with an
* Line 3 or 4 (last two lines) can be marked with an
* arrow in the first column. Pass 2 or 3 to this to
* specify which line to mark with an arrow.
* Any other values are ignored.
@@ -388,42 +483,17 @@ 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) {
showTime();
}
else {
u8x8.setPowerSave(1);
}
if (clockMode) showTime();
else setPowerSave(1);
displayTurnedOff = true;
}
else {
if (!CLOCK_MODE_ENABLED) {
u8x8.setPowerSave(0);
}
setPowerSave(0);
displayTurnedOff = false;
}
}
@@ -433,23 +503,28 @@ 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);
byte secondCurrent = second(localTime);
if (knownMinute == minuteCurrent && knownHour == hourCurrent) {
// Time hasn't changed.
return;
if (!fullScreen) return;
} else {
if (fullScreen) clear();
}
knownMinute = minuteCurrent;
knownHour = hourCurrent;
u8x8.clear();
u8x8.setFont(u8x8_font_chroma48medium8_r);
int currentMonth = month(localTime);
sprintf(lineBuffer, "%s %d", monthShortStr(currentMonth), day(localTime));
u8x8.DRAW_BIG_STRING(DATE_INDENT, TIME_LINE*LINE_HEIGHT, lineBuffer);
byte currentMonth = month(localTime);
sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(currentMonth), day(localTime));
if (fullScreen)
draw2x2String(DATE_INDENT, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays
else
drawString(2, lineHeight*2, lineBuffer);
byte showHour = hourCurrent;
boolean isAM = false;
@@ -467,25 +542,45 @@ class FourLineDisplayUsermod : public Usermod {
}
}
sprintf(lineBuffer, "%02d:%02d %s", showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : "");
sprintf_P(lineBuffer, (secondCurrent%2 || !fullScreen) ? PSTR("%2d:%02d") : PSTR("%2d %02d"), (useAMPM ? showHour : hourCurrent), minuteCurrent);
// 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+2, lineHeight*2, lineBuffer);
sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent);
if (!useAMPM) drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
} else {
drawString(9+(useAMPM?0:2), lineHeight*2, lineBuffer);
}
if (useAMPM) drawString(12+(fullScreen?0:2), lineHeight*2, (isAM ? "AM" : "PM"), true);
}
/*
* 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."));
//}
/*
* 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) {
}
//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) {
}
//void readFromJsonState(JsonObject& root) {
// if (!initDone) return; // prevent crash on boot applyPreset()
//}
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
@@ -502,6 +597,18 @@ 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(FPSTR(_name));
JsonArray i2c_pin = top.createNestedArray("pin");
i2c_pin.add(sclPin);
i2c_pin.add(sdaPin);
top["type"] = type;
top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast;
top[FPSTR(_refreshRate)] = refreshRate/1000;
top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
top[FPSTR(_sleepMode)] = (bool) sleepMode;
top[FPSTR(_clockMode)] = (bool) clockMode;
DEBUG_PRINTLN(F("4 Line Display config saved."));
}
/*
@@ -513,6 +620,74 @@ 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) {
bool needsRedraw = false;
DisplayType newType = type;
int8_t newScl = sclPin;
int8_t newSda = sdaPin;
JsonObject top = root[FPSTR(_name)];
if (!top.isNull() && top["pin"] != nullptr) {
newScl = top["pin"][0];
newSda = top["pin"][1];
newType = top["type"];
if (top[FPSTR(_flip)].is<bool>()) {
flip = top[FPSTR(_flip)].as<bool>();
} else {
String str = top[FPSTR(_flip)]; // checkbox -> off or on
flip = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true;
}
contrast = top[FPSTR(_contrast)].as<int>();
refreshRate = top[FPSTR(_refreshRate)].as<int>() * 1000;
screenTimeout = top[FPSTR(_screenTimeOut)].as<int>() * 1000;
if (top[FPSTR(_sleepMode)].is<bool>()) {
sleepMode = top[FPSTR(_sleepMode)].as<bool>();
} else {
String str = top[FPSTR(_sleepMode)]; // checkbox -> off or on
sleepMode = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true;
}
if (top[FPSTR(_clockMode)].is<bool>()) {
clockMode = top[FPSTR(_clockMode)].as<bool>();
} else {
String str = top[FPSTR(_clockMode)]; // checkbox -> off or on
clockMode = (bool)(str!="off"); // off is guaranteed to be present
needRedraw |= true;
}
DEBUG_PRINTLN(F("4 Line Display config (re)loaded."));
} else {
DEBUG_PRINTLN(F("No config found. (Using defaults.)"));
}
if (!initDone) {
// first run: reading from cfg.json
sclPin = newScl;
sdaPin = newSda;
type = newType;
lineHeight = type==SSD1306 ? 1 : 2;
} else {
// changing paramters from settings page
if (sclPin!=newScl || sdaPin!=newSda || type!=newType) {
if (type==SSD1306) delete (static_cast<U8X8*>(u8x8));
if (type==SH1106) delete (static_cast<U8X8*>(u8x8));
if (type==SSD1306_64) delete (static_cast<U8X8*>(u8x8));
pinManager.deallocatePin(sclPin);
pinManager.deallocatePin(sdaPin);
sclPin = newScl;
sdaPin = newSda;
if (newScl<0 || newSda<0) {
type = NONE;
return;
} else
type = newType;
lineHeight = type==SSD1306 ? 1 : 2;
setup();
needRedraw |= true;
}
setContrast(contrast);
setFlipMode(flip);
if (needsRedraw && !wakeDisplay()) redraw(true);
}
}
/*
@@ -522,5 +697,13 @@ class FourLineDisplayUsermod : public Usermod {
uint16_t getId() {
return USERMOD_ID_FOUR_LINE_DISP;
}
};
};
// strings to reduce flash memory usage (used more than twice)
const char FourLineDisplayUsermod::_name[] PROGMEM = "4LineDisplay";
const char FourLineDisplayUsermod::_contrast[] PROGMEM = "contrast";
const char FourLineDisplayUsermod::_refreshRate[] PROGMEM = "refreshRateSec";
const char FourLineDisplayUsermod::_screenTimeOut[] PROGMEM = "screenTimeOutSec";
const char FourLineDisplayUsermod::_flip[] PROGMEM = "flip";
const char FourLineDisplayUsermod::_sleepMode[] PROGMEM = "sleepMode";
const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode";