diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 9b7ccff2..fca43708 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -222,9 +222,8 @@ public: void loop() { // only check sensors 10x/s - unsigned long now = millis(); - if (now - lastLoop < 100) return; - lastLoop = now; + if (millis() - lastLoop < 100 || strip.isUpdating()) return; + lastLoop = millis(); if (!updatePIRsensorState()) { handleOffTimer(); diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index f24ed86e..f85d0c32 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -27,6 +27,9 @@ class UsermodTemperature : public Usermod { int8_t temperaturePin = TEMPERATURE_PIN; // measurement unit (true==°C, false==°F) bool degC = true; + // using parasite power on the sensor + bool parasite = false; + // how often do we read from sensor? unsigned long readingInterval = USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL; // set last reading as "40 sec before boot", so first reading is taken after 20 sec unsigned long lastMeasurement = UINT32_MAX - USERMOD_DALLASTEMPERATURE_MEASUREMENT_INTERVAL; @@ -45,25 +48,24 @@ class UsermodTemperature : public Usermod { static const char _name[]; static const char _enabled[]; static const char _readInterval[]; + static const char _parasite[]; //Dallas sensor quick (& dirty) reading. Credit to - Author: Peter Scargill, August 17th, 2013 - int16_t readDallas() { + float readDallas() { byte i; byte data[2]; - int16_t result; // raw data from sensor - oneWire->reset(); - oneWire->write(0xCC); // skip ROM - oneWire->write(0xBE); // read (temperature) from EEPROM + int16_t result; // raw data from sensor + if (!oneWire->reset()) return -127.0f; // send reset command and fail fast + oneWire->skip(); // skip ROM + oneWire->write(0xBE); // read (temperature) from EEPROM for (i=0; i < 2; i++) data[i] = oneWire->read(); // first 2 bytes contain temperature for (i=2; i < 8; i++) oneWire->read(); // read unused bytes - result = (data[1]<<8) | data[0]; - result >>= 4; // 9-bit precision accurate to 1°C (/16) - if (data[1]&0x80) result |= 0x8000; // fix negative value - //if (data[0]&0x08) ++result; + result = (data[1]<<4) | (data[0]>>4); // we only need whole part, we will add fraction when returning + if (data[1]&0x80) result |= 0xFF00; // fix negative value oneWire->reset(); - oneWire->write(0xCC); // skip ROM - oneWire->write(0x44,0); // request new temperature reading (without parasite power) - return result; + oneWire->skip(); // skip ROM + oneWire->write(0x44,parasite); // request new temperature reading (without parasite power) + return (float)result + ((data[0]&0x0008) ? 0.5f : 0.0f); } void requestTemperatures() { @@ -225,6 +227,7 @@ class UsermodTemperature : public Usermod { top["pin"] = temperaturePin; // usermodparam top["degC"] = degC; // usermodparam top[FPSTR(_readInterval)] = readingInterval / 1000; + top[FPSTR(_parasite)] = parasite; DEBUG_PRINTLN(F("Temperature config saved.")); } @@ -253,7 +256,14 @@ class UsermodTemperature : public Usermod { degC = (bool)(str!="off"); // off is guaranteed to be present } readingInterval = min(120,max(10,top[FPSTR(_readInterval)].as())) * 1000; // convert to ms - DEBUG_PRINTLN(F("Temperature config (re)loaded.")); + if (top[FPSTR(_parasite)].is()) { + // reading from cfg.json + parasite = top[FPSTR(_parasite)].as(); + } else { + // new configuration from set.cpp + String str = top[FPSTR(_parasite)]; // checkbox -> off or on + parasite = (bool)(str!="off"); // off is guaranteed to be present + } } else { DEBUG_PRINTLN(F("No config found. (Using defaults.)")); } @@ -261,7 +271,9 @@ class UsermodTemperature : public Usermod { if (!initDone) { // first run: reading from cfg.json temperaturePin = newTemperaturePin; + DEBUG_PRINTLN(F("Temperature config loaded.")); } else { + DEBUG_PRINTLN(F("Temperature config re-loaded.")); // changing paramters from settings page if (newTemperaturePin != temperaturePin) { // deallocate pin and release memory @@ -283,4 +295,5 @@ class UsermodTemperature : public Usermod { // strings to reduce flash memory usage (used more than twice) const char UsermodTemperature::_name[] PROGMEM = "Temperature"; const char UsermodTemperature::_enabled[] PROGMEM = "enabled"; -const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s"; \ No newline at end of file +const char UsermodTemperature::_readInterval[] PROGMEM = "read-interval-s"; +const char UsermodTemperature::_parasite[] PROGMEM = "parasite-pwr"; diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index caf542e5..43eb1fc3 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -1,244 +1,244 @@ -#pragma once - -#include "wled.h" - -// v2 Usermod to automatically save settings -// to configurable preset after a change to any of -// -// * brightness -// * effect speed -// * effect intensity -// * mode (effect) -// * palette -// -// but it will wait for configurable number of seconds, a "settle" -// period in case there are other changes (any change will -// extend the "settle" window). -// -// It can be configured to load auto saved preset at startup, -// during the first `loop()`. -// -// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod -// is installed, it will notify the user of the saved changes. - -// format: "~ MM-DD HH:MM:SS ~" -#define PRESET_NAME_BUFFER_SIZE 25 - -class AutoSaveUsermod : public Usermod { - - private: - - bool firstLoop = true; - bool initDone = false; - bool enabled = true; - - // configurable parameters - unsigned long autoSaveAfterSec = 15; // 15s by default - uint8_t autoSavePreset = 250; // last possible preset - bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? - - // If we've detected the need to auto save, this will be non zero. - unsigned long autoSaveAfter = 0; - - uint8_t knownBrightness = 0; - uint8_t knownEffectSpeed = 0; - uint8_t knownEffectIntensity = 0; - uint8_t knownMode = 0; - uint8_t knownPalette = 0; - - #ifdef USERMOD_FOUR_LINE_DISPLAY - FourLineDisplayUsermod* display; - #endif - - // strings to reduce flash memory usage (used more than twice) - static const char _name[]; - static const char _autoSaveEnabled[]; - static const char _autoSaveAfterSec[]; - static const char _autoSavePreset[]; - static const char _autoSaveApplyOnBoot[]; - - void inline saveSettings() { - char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; - updateLocalTime(); - sprintf_P(presetNameBuffer, - PSTR("~ %02d-%02d %02d:%02d:%02d ~"), - month(localTime), day(localTime), - hour(localTime), minute(localTime), second(localTime)); - savePreset(autoSavePreset, true, presetNameBuffer); - } - - void inline displayOverlay() { - #ifdef USERMOD_FOUR_LINE_DISPLAY - if (display != nullptr) { - display->wakeDisplay(); - display->overlay("Settings", "Auto Saved", 1500); - } - #endif - } - - public: - - // gets called once at boot. Do all initialization that doesn't depend on - // network here - void setup() { - #ifdef USERMOD_FOUR_LINE_DISPLAY - // This Usermod has enhanced funcionality if - // FourLineDisplayUsermod is available. - display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); - #endif - initDone = true; - } - - // gets called every time WiFi is (re-)connected. Initialize own network - // interfaces here - void connected() {} - - /* - * Da loop. - */ - void loop() { - if (!autoSaveAfterSec || !enabled) return; // setting 0 as autosave seconds disables autosave - - unsigned long now = millis(); - uint8_t currentMode = strip.getMode(); - uint8_t currentPalette = strip.getSegment(0).palette; - if (firstLoop) { - firstLoop = false; - if (applyAutoSaveOnBoot) applyPreset(autoSavePreset); - knownBrightness = bri; - knownEffectSpeed = effectSpeed; - knownEffectIntensity = effectIntensity; - knownMode = currentMode; - knownPalette = currentPalette; - return; - } - - unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; - if (knownBrightness != bri) { - knownBrightness = bri; - autoSaveAfter = wouldAutoSaveAfter; - } else if (knownEffectSpeed != effectSpeed) { - knownEffectSpeed = effectSpeed; - autoSaveAfter = wouldAutoSaveAfter; - } else if (knownEffectIntensity != effectIntensity) { - knownEffectIntensity = effectIntensity; - autoSaveAfter = wouldAutoSaveAfter; - } else if (knownMode != currentMode) { - knownMode = currentMode; - autoSaveAfter = wouldAutoSaveAfter; - } else if (knownPalette != currentPalette) { - knownPalette = currentPalette; - autoSaveAfter = wouldAutoSaveAfter; - } - - if (autoSaveAfter && now > autoSaveAfter) { - autoSaveAfter = 0; - // Time to auto save. You may have some flickry? - saveSettings(); - displayOverlay(); - } - } - - /* - * 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("Autosave")); - //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) { - //} - - /* - * 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) { - // 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. - * It will be called by WLED when settings are actually saved (for example, LED settings are saved) - * If you want to force saving the current state, use serializeConfig() in your loop(). - * - * CAUTION: serializeConfig() will initiate a filesystem write operation. - * It might cause the LEDs to stutter and will cause flash wear if called too often. - * Use it sparingly and always in the loop, never in network callbacks! - * - * addToConfig() will also not yet add your setting to one of the settings pages automatically. - * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. - * - * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! - */ - void addToConfig(JsonObject& root) { - // we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} - JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname - top[FPSTR(_autoSaveEnabled)] = enabled; - top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam - top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam - top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; - DEBUG_PRINTLN(F("Autosave config saved.")); - } - - /* - * readFromConfig() can be used to read back the custom settings you added with addToConfig(). - * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) - * - * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), - * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. - * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) - */ - void readFromConfig(JsonObject& root) { - // we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} - JsonObject top = root[FPSTR(_name)]; - if (top.isNull()) { - DEBUG_PRINTLN(F("No config found. (Using defaults.)")); - return; - } - - if (top[FPSTR(_autoSaveEnabled)].is()) { - // reading from cfg.json - enabled = top[FPSTR(_autoSaveEnabled)].as(); - } else { - // reading from POST message - String str = top[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on - enabled = (bool)(str!="off"); // off is guaranteed to be present - } - autoSaveAfterSec = min(3600,max(10,top[FPSTR(_autoSaveAfterSec)].as())); - autoSavePreset = min(250,max(100,top[FPSTR(_autoSavePreset)].as())); - if (top[FPSTR(_autoSaveApplyOnBoot)].is()) { - // reading from cfg.json - applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as(); - } else { - // reading from POST message - String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on - applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present - } - DEBUG_PRINTLN(F("Autosave config (re)loaded.")); - } - - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - uint16_t getId() { - return USERMOD_ID_AUTO_SAVE; - } -}; - -// strings to reduce flash memory usage (used more than twice) -const char AutoSaveUsermod::_name[] PROGMEM = "Autosave"; -const char AutoSaveUsermod::_autoSaveEnabled[] PROGMEM = "enabled"; -const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; -const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset"; -const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; +#pragma once + +#include "wled.h" + +// v2 Usermod to automatically save settings +// to configurable preset after a change to any of +// +// * brightness +// * effect speed +// * effect intensity +// * mode (effect) +// * palette +// +// but it will wait for configurable number of seconds, a "settle" +// period in case there are other changes (any change will +// extend the "settle" window). +// +// It can be configured to load auto saved preset at startup, +// during the first `loop()`. +// +// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod +// is installed, it will notify the user of the saved changes. + +// format: "~ MM-DD HH:MM:SS ~" +#define PRESET_NAME_BUFFER_SIZE 25 + +class AutoSaveUsermod : public Usermod { + + private: + + bool firstLoop = true; + bool initDone = false; + bool enabled = true; + + // configurable parameters + unsigned long autoSaveAfterSec = 15; // 15s by default + uint8_t autoSavePreset = 250; // last possible preset + bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? + + // If we've detected the need to auto save, this will be non zero. + unsigned long autoSaveAfter = 0; + + uint8_t knownBrightness = 0; + uint8_t knownEffectSpeed = 0; + uint8_t knownEffectIntensity = 0; + uint8_t knownMode = 0; + uint8_t knownPalette = 0; + + #ifdef USERMOD_FOUR_LINE_DISPLAY + FourLineDisplayUsermod* display; + #endif + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _autoSaveEnabled[]; + static const char _autoSaveAfterSec[]; + static const char _autoSavePreset[]; + static const char _autoSaveApplyOnBoot[]; + + void inline saveSettings() { + char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; + updateLocalTime(); + sprintf_P(presetNameBuffer, + PSTR("~ %02d-%02d %02d:%02d:%02d ~"), + month(localTime), day(localTime), + hour(localTime), minute(localTime), second(localTime)); + savePreset(autoSavePreset, true, presetNameBuffer); + } + + void inline displayOverlay() { + #ifdef USERMOD_FOUR_LINE_DISPLAY + if (display != nullptr) { + display->wakeDisplay(); + display->overlay("Settings", "Auto Saved", 1500); + } + #endif + } + + public: + + // gets called once at boot. Do all initialization that doesn't depend on + // network here + void setup() { + #ifdef USERMOD_FOUR_LINE_DISPLAY + // This Usermod has enhanced funcionality if + // FourLineDisplayUsermod is available. + display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + #endif + initDone = true; + } + + // gets called every time WiFi is (re-)connected. Initialize own network + // interfaces here + void connected() {} + + /* + * Da loop. + */ + void loop() { + if (!autoSaveAfterSec || !enabled || strip.isUpdating()) return; // setting 0 as autosave seconds disables autosave + + unsigned long now = millis(); + uint8_t currentMode = strip.getMode(); + uint8_t currentPalette = strip.getSegment(0).palette; + if (firstLoop) { + firstLoop = false; + if (applyAutoSaveOnBoot) applyPreset(autoSavePreset); + knownBrightness = bri; + knownEffectSpeed = effectSpeed; + knownEffectIntensity = effectIntensity; + knownMode = currentMode; + knownPalette = currentPalette; + return; + } + + unsigned long wouldAutoSaveAfter = now + autoSaveAfterSec*1000; + if (knownBrightness != bri) { + knownBrightness = bri; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownEffectSpeed != effectSpeed) { + knownEffectSpeed = effectSpeed; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownEffectIntensity != effectIntensity) { + knownEffectIntensity = effectIntensity; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownMode != currentMode) { + knownMode = currentMode; + autoSaveAfter = wouldAutoSaveAfter; + } else if (knownPalette != currentPalette) { + knownPalette = currentPalette; + autoSaveAfter = wouldAutoSaveAfter; + } + + if (autoSaveAfter && now > autoSaveAfter) { + autoSaveAfter = 0; + // Time to auto save. You may have some flickry? + saveSettings(); + displayOverlay(); + } + } + + /* + * 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("Autosave")); + //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) { + //} + + /* + * 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) { + // 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. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will also not yet add your setting to one of the settings pages automatically. + * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ + void addToConfig(JsonObject& root) { + // we add JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_autoSaveEnabled)] = enabled; + top[FPSTR(_autoSaveAfterSec)] = autoSaveAfterSec; // usermodparam + top[FPSTR(_autoSavePreset)] = autoSavePreset; // usermodparam + top[FPSTR(_autoSaveApplyOnBoot)] = applyAutoSaveOnBoot; + DEBUG_PRINTLN(F("Autosave config saved.")); + } + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + */ + void readFromConfig(JsonObject& root) { + // we look for JSON object: {"Autosave": {"autoSaveAfterSec": 10, "autoSavePreset": 99}} + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINTLN(F("No config found. (Using defaults.)")); + return; + } + + if (top[FPSTR(_autoSaveEnabled)].is()) { + // reading from cfg.json + enabled = top[FPSTR(_autoSaveEnabled)].as(); + } else { + // reading from POST message + String str = top[FPSTR(_autoSaveEnabled)]; // checkbox -> off or on + enabled = (bool)(str!="off"); // off is guaranteed to be present + } + autoSaveAfterSec = min(3600,max(10,top[FPSTR(_autoSaveAfterSec)].as())); + autoSavePreset = min(250,max(100,top[FPSTR(_autoSavePreset)].as())); + if (top[FPSTR(_autoSaveApplyOnBoot)].is()) { + // reading from cfg.json + applyAutoSaveOnBoot = top[FPSTR(_autoSaveApplyOnBoot)].as(); + } else { + // reading from POST message + String str = top[FPSTR(_autoSaveApplyOnBoot)]; // checkbox -> off or on + applyAutoSaveOnBoot = (bool)(str!="off"); // off is guaranteed to be present + } + DEBUG_PRINTLN(F("Autosave config (re)loaded.")); + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() { + return USERMOD_ID_AUTO_SAVE; + } +}; + +// strings to reduce flash memory usage (used more than twice) +const char AutoSaveUsermod::_name[] PROGMEM = "Autosave"; +const char AutoSaveUsermod::_autoSaveEnabled[] PROGMEM = "enabled"; +const char AutoSaveUsermod::_autoSaveAfterSec[] PROGMEM = "autoSaveAfterSec"; +const char AutoSaveUsermod::_autoSavePreset[] PROGMEM = "autoSavePreset"; +const char AutoSaveUsermod::_autoSaveApplyOnBoot[] PROGMEM = "autoSaveApplyOnBoot"; 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 999054e5..c9d9a851 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 @@ -1,705 +1,703 @@ -#pragma once - -#include "wled.h" -#include // from https://github.com/olikraus/u8g2/ - -// -// Insired by the v1 usermod: ssd1306_i2c_oled_u8g2 -// -// v2 usermod for using 128x32 or 128x64 i2c -// OLED displays to provide a four line display -// for WLED. -// -// Dependencies -// * This usermod REQURES the ModeSortUsermod -// * This Usermod works best, by far, when coupled -// with RotaryEncoderUIUsermod. -// -// Make sure to enable NTP and set your time zone in WLED Config | Time. -// -// REQUIREMENT: You must add the following requirements to -// REQUIREMENT: "lib_deps" within platformio.ini / platformio_override.ini -// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine) -// REQUIREMENT: * Wire -// - -//The SCL and SDA pins are defined here. -#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 - #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 60*1000 // 1 min - -#define TIME_INDENT 0 -#define DATE_INDENT 2 - -// Minimum time between redrawing screen in ms -#define USER_LOOP_REFRESH_RATE_MS 1000 - -// Extra char (+1) for null -#define LINE_BUFFER_SIZE 16+1 - -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; - - // Next variables hold the previous known values to determine if redraw is - // required. - String knownSsid = ""; - IPAddress knownIp; - uint8_t knownBrightness = 0; - uint8_t knownEffectSpeed = 0; - uint8_t knownEffectIntensity = 0; - uint8_t knownMode = 0; - uint8_t knownPalette = 0; - uint8_t knownMinute = 99; - uint8_t knownHour = 99; - - bool displayTurnedOff = false; - 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; - - // 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() { - 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))->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 - // interfaces here - void connected() {} - - /** - * Da loop. - */ - void loop() { - if (millis() - lastUpdate < (clockMode?1000:refreshRate)) { - return; - } - lastUpdate = millis(); - - redraw(false); - } - - /** - * Wrappers for screen drawing - */ - void setFlipMode(uint8_t mode) { - if (type==NONE) return; - (static_cast(u8x8))->setFlipMode(mode); - } - void setContrast(uint8_t contrast) { - if (type==NONE) return; - (static_cast(u8x8))->setContrast(contrast); - } - void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { - if (type==NONE) return; - (static_cast(u8x8))->setFont(u8x8_font_chroma48medium8_r); - if (!ignoreLH && lineHeight==2) (static_cast(u8x8))->draw1x2String(col, row, string); - else (static_cast(u8x8))->drawString(col, row, string); - } - void draw2x2String(uint8_t col, uint8_t row, const char *string) { - if (type==NONE) return; - (static_cast(u8x8))->setFont(u8x8_font_chroma48medium8_r); - (static_cast(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))->setFont(font); - if (!ignoreLH && lineHeight==2) (static_cast(u8x8))->draw1x2Glyph(col, row, glyph); - else (static_cast(u8x8))->drawGlyph(col, row, glyph); - } - uint8_t getCols() { - if (type==NONE) return 0; - return (static_cast(u8x8))->getCols(); - } - void clear() { - if (type==NONE) return; - (static_cast(u8x8))->clear(); - } - void setPowerSave(uint8_t save) { - if (type==NONE) return; - (static_cast(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 { - // We are still displaying the overlay - // Don't redraw. - return; - } - } - - // Check if values which are shown on display changed from the last time. - if (forceRedraw) { - needRedraw = true; - } else if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { - needRedraw = true; - } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { - needRedraw = true; - } else if (knownBrightness != bri) { - needRedraw = true; - } else if (knownEffectSpeed != effectSpeed) { - needRedraw = true; - } else if (knownEffectIntensity != effectIntensity) { - needRedraw = true; - } else if (knownMode != strip.getMode()) { - needRedraw = true; - } else if (knownPalette != strip.getSegment(0).palette) { - needRedraw = true; - } - - if (!needRedraw) { - // Nothing to change. - // Turn off display after 3 minutes with no change. - 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 && 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) { - // Turn the display back on - sleepOrClock(false); - } - - // Update last known values. - knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); - knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); - knownBrightness = bri; - knownMode = strip.getMode(); - knownPalette = strip.getSegment(0).palette; - knownEffectSpeed = effectSpeed; - knownEffectIntensity = effectIntensity; - - // Do the actual drawing - - // First row with Wifi name - 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) { - drawString(1, lineHeight, apPass); - } else { - drawString(1, lineHeight, (knownIp.toString()).c_str()); - } - - // Third row with mode name or current time - if (clockMode) showTime(false); - else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 2); - - // 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_4_EFFECT_SPEED: - sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed); - drawString(2, 3*lineHeight, lineBuffer); - break; - case FLD_LINE_4_EFFECT_INTENSITY: - sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity); - drawString(2, 3*lineHeight, lineBuffer); - break; - 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; - } - } - - /** - * Display the current effect or palette (desiredEntry) - * on the appropriate line (row). - */ - 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; - - // 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 == '\0') break; - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownMode)) break; - lineBuffer[printedChars++] = singleJsonSymbol; - } - 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); - } - - /** - * If there screen is off or in clock is displayed, - * this will return true. This allows us to throw away - * the first input from the rotary encoder but - * to wake up the screen. - */ - bool wakeDisplay() { - knownHour = 99; - if (displayTurnedOff) { - // Turn the display back on - sleepOrClock(false); - redraw(true); - return true; - } - return false; - } - - /** - * Allows you to show up to two lines as overlay for a - * period of time. - * Clears the screen and prints on the middle two lines. - */ - void overlay(const char* line1, const char *line2, long showHowLong) { - if (displayTurnedOff) { - // Turn the display back on - sleepOrClock(false); - } - - // Print the overlay - 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 4 - * (the last line). - */ - 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 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. - */ - void setMarkLine(byte newMarkLineNum) { - if (newMarkLineNum == 2 || newMarkLineNum == 3) { - markLineNum = newMarkLineNum; - } - else { - markLineNum = 0; - } - } - - /** - * Enable sleep (turn the display off) or clock mode. - */ - void sleepOrClock(bool enabled) { - if (enabled) { - if (clockMode) showTime(); - else setPowerSave(1); - displayTurnedOff = true; - } - else { - setPowerSave(0); - displayTurnedOff = false; - } - } - - /** - * Display the current date and time in large characters - * on the middle rows. Based 24 or 12 hour depending on - * the useAMPM configuration. - */ - 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. - if (!fullScreen) return; - } else { - if (fullScreen) clear(); - } - knownMinute = minuteCurrent; - knownHour = hourCurrent; - - 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; - if (useAMPM) { - if (showHour == 0) { - showHour = 12; - isAM = true; - } - else if (showHour > 12) { - showHour -= 12; - isAM = false; - } - else { - isAM = true; - } - } - - 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. - 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) { - //} - - /* - * 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) { - // 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. - * It will be called by WLED when settings are actually saved (for example, LED settings are saved) - * If you want to force saving the current state, use serializeConfig() in your loop(). - * - * CAUTION: serializeConfig() will initiate a filesystem write operation. - * It might cause the LEDs to stutter and will cause flash wear if called too often. - * Use it sparingly and always in the loop, never in network callbacks! - * - * addToConfig() will also not yet add your setting to one of the settings pages automatically. - * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. - * - * 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.")); - } - - /* - * readFromConfig() can be used to read back the custom settings you added with addToConfig(). - * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) - * - * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), - * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. - * 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()) { - flip = top[FPSTR(_flip)].as(); - } 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(); - refreshRate = top[FPSTR(_refreshRate)].as() * 1000; - screenTimeout = top[FPSTR(_screenTimeOut)].as() * 1000; - if (top[FPSTR(_sleepMode)].is()) { - sleepMode = top[FPSTR(_sleepMode)].as(); - } 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()) { - clockMode = top[FPSTR(_clockMode)].as(); - } 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)); - if (type==SH1106) delete (static_cast(u8x8)); - if (type==SSD1306_64) delete (static_cast(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); - } - } - - /* - * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). - * This could be used in the future for the system to determine whether your usermod is installed. - */ - 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"; +#pragma once + +#include "wled.h" +#include // from https://github.com/olikraus/u8g2/ + +// +// Insired by the v1 usermod: ssd1306_i2c_oled_u8g2 +// +// v2 usermod for using 128x32 or 128x64 i2c +// OLED displays to provide a four line display +// for WLED. +// +// Dependencies +// * This usermod REQURES the ModeSortUsermod +// * This Usermod works best, by far, when coupled +// with RotaryEncoderUIUsermod. +// +// Make sure to enable NTP and set your time zone in WLED Config | Time. +// +// REQUIREMENT: You must add the following requirements to +// REQUIREMENT: "lib_deps" within platformio.ini / platformio_override.ini +// REQUIREMENT: * U8g2 (the version already in platformio.ini is fine) +// REQUIREMENT: * Wire +// + +//The SCL and SDA pins are defined here. +#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 + #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 60*1000 // 1 min + +#define TIME_INDENT 0 +#define DATE_INDENT 2 + +// Minimum time between redrawing screen in ms +#define USER_LOOP_REFRESH_RATE_MS 1000 + +// Extra char (+1) for null +#define LINE_BUFFER_SIZE 16+1 + +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; + + // Next variables hold the previous known values to determine if redraw is + // required. + String knownSsid = ""; + IPAddress knownIp; + uint8_t knownBrightness = 0; + uint8_t knownEffectSpeed = 0; + uint8_t knownEffectIntensity = 0; + uint8_t knownMode = 0; + uint8_t knownPalette = 0; + uint8_t knownMinute = 99; + uint8_t knownHour = 99; + + bool displayTurnedOff = false; + 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; + + // 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() { + 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))->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 + // interfaces here + void connected() {} + + /** + * Da loop. + */ + void loop() { + if (millis() - lastUpdate < (clockMode?1000:refreshRate) || strip.isUpdating()) return; + lastUpdate = millis(); + + redraw(false); + } + + /** + * Wrappers for screen drawing + */ + void setFlipMode(uint8_t mode) { + if (type==NONE) return; + (static_cast(u8x8))->setFlipMode(mode); + } + void setContrast(uint8_t contrast) { + if (type==NONE) return; + (static_cast(u8x8))->setContrast(contrast); + } + void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { + if (type==NONE) return; + (static_cast(u8x8))->setFont(u8x8_font_chroma48medium8_r); + if (!ignoreLH && lineHeight==2) (static_cast(u8x8))->draw1x2String(col, row, string); + else (static_cast(u8x8))->drawString(col, row, string); + } + void draw2x2String(uint8_t col, uint8_t row, const char *string) { + if (type==NONE) return; + (static_cast(u8x8))->setFont(u8x8_font_chroma48medium8_r); + (static_cast(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))->setFont(font); + if (!ignoreLH && lineHeight==2) (static_cast(u8x8))->draw1x2Glyph(col, row, glyph); + else (static_cast(u8x8))->drawGlyph(col, row, glyph); + } + uint8_t getCols() { + if (type==NONE) return 0; + return (static_cast(u8x8))->getCols(); + } + void clear() { + if (type==NONE) return; + (static_cast(u8x8))->clear(); + } + void setPowerSave(uint8_t save) { + if (type==NONE) return; + (static_cast(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 { + // We are still displaying the overlay + // Don't redraw. + return; + } + } + + // Check if values which are shown on display changed from the last time. + if (forceRedraw) { + needRedraw = true; + } else if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { + needRedraw = true; + } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { + needRedraw = true; + } else if (knownBrightness != bri) { + needRedraw = true; + } else if (knownEffectSpeed != effectSpeed) { + needRedraw = true; + } else if (knownEffectIntensity != effectIntensity) { + needRedraw = true; + } else if (knownMode != strip.getMode()) { + needRedraw = true; + } else if (knownPalette != strip.getSegment(0).palette) { + needRedraw = true; + } + + if (!needRedraw) { + // Nothing to change. + // Turn off display after 3 minutes with no change. + 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 && 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) { + // Turn the display back on + sleepOrClock(false); + } + + // Update last known values. + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMode(); + knownPalette = strip.getSegment(0).palette; + knownEffectSpeed = effectSpeed; + knownEffectIntensity = effectIntensity; + + // Do the actual drawing + + // First row with Wifi name + 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) { + drawString(1, lineHeight, apPass); + } else { + drawString(1, lineHeight, (knownIp.toString()).c_str()); + } + + // Third row with mode name or current time + if (clockMode) showTime(false); + else showCurrentEffectOrPalette(knownMode, JSON_mode_names, 2); + + // 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_4_EFFECT_SPEED: + sprintf_P(lineBuffer, PSTR("FX Speed %3d"), effectSpeed); + drawString(2, 3*lineHeight, lineBuffer); + break; + case FLD_LINE_4_EFFECT_INTENSITY: + sprintf_P(lineBuffer, PSTR("FX Intens. %3d"), effectIntensity); + drawString(2, 3*lineHeight, lineBuffer); + break; + 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; + } + } + + /** + * Display the current effect or palette (desiredEntry) + * on the appropriate line (row). + */ + 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; + + // 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 == '\0') break; + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownMode)) break; + lineBuffer[printedChars++] = singleJsonSymbol; + } + 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); + } + + /** + * If there screen is off or in clock is displayed, + * this will return true. This allows us to throw away + * the first input from the rotary encoder but + * to wake up the screen. + */ + bool wakeDisplay() { + knownHour = 99; + if (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + redraw(true); + return true; + } + return false; + } + + /** + * Allows you to show up to two lines as overlay for a + * period of time. + * Clears the screen and prints on the middle two lines. + */ + void overlay(const char* line1, const char *line2, long showHowLong) { + if (displayTurnedOff) { + // Turn the display back on + sleepOrClock(false); + } + + // Print the overlay + 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 4 + * (the last line). + */ + 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 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. + */ + void setMarkLine(byte newMarkLineNum) { + if (newMarkLineNum == 2 || newMarkLineNum == 3) { + markLineNum = newMarkLineNum; + } + else { + markLineNum = 0; + } + } + + /** + * Enable sleep (turn the display off) or clock mode. + */ + void sleepOrClock(bool enabled) { + if (enabled) { + if (clockMode) showTime(); + else setPowerSave(1); + displayTurnedOff = true; + } + else { + setPowerSave(0); + displayTurnedOff = false; + } + } + + /** + * Display the current date and time in large characters + * on the middle rows. Based 24 or 12 hour depending on + * the useAMPM configuration. + */ + 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. + if (!fullScreen) return; + } else { + if (fullScreen) clear(); + } + knownMinute = minuteCurrent; + knownHour = hourCurrent; + + 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; + if (useAMPM) { + if (showHour == 0) { + showHour = 12; + isAM = true; + } + else if (showHour > 12) { + showHour -= 12; + isAM = false; + } + else { + isAM = true; + } + } + + 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. + 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) { + //} + + /* + * 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) { + // 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. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will also not yet add your setting to one of the settings pages automatically. + * To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually. + * + * 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.")); + } + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens once immediately after boot) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * 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()) { + flip = top[FPSTR(_flip)].as(); + } 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(); + refreshRate = top[FPSTR(_refreshRate)].as() * 1000; + screenTimeout = top[FPSTR(_screenTimeOut)].as() * 1000; + if (top[FPSTR(_sleepMode)].is()) { + sleepMode = top[FPSTR(_sleepMode)].as(); + } 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()) { + clockMode = top[FPSTR(_clockMode)].as(); + } 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)); + if (type==SH1106) delete (static_cast(u8x8)); + if (type==SSD1306_64) delete (static_cast(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); + } + } + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + 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"; diff --git a/wled00/wled.h b/wled00/wled.h index cc1fef6e..227835a4 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2105251 +#define VERSION 2105261 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG