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:
@@ -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.
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user