From 6ce465664fe48800f1996e362f28563e6f0dfe14 Mon Sep 17 00:00:00 2001 From: Marco Lopes <4539516+Matoran@users.noreply.github.com> Date: Sat, 16 Jan 2021 16:01:50 +0100 Subject: [PATCH 1/8] fix COO_MAX comment COO_MAX value changed but comment does not take modification into account. --- wled00/NpbWrapper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/NpbWrapper.h b/wled00/NpbWrapper.h index 8c1860b25..7e9b8f59c 100644 --- a/wled00/NpbWrapper.h +++ b/wled00/NpbWrapper.h @@ -45,7 +45,7 @@ //This can be useful if you want to chain multiple strings with incompatible color order //#define COLOR_ORDER_OVERRIDE #define COO_MIN 0 -#define COO_MAX 35 //not inclusive, this would set the override for LEDs 0-26 +#define COO_MAX 35 //not inclusive, this would set the override for LEDs 0-34 #define COO_ORDER COL_ORDER_GRB //END CONFIGURATION From c24d574f9049cbea4e42208498149b204a873ff5 Mon Sep 17 00:00:00 2001 From: Christophe Gagnier Date: Thu, 28 Jan 2021 22:16:36 -0500 Subject: [PATCH 2/8] Add Cache-Control to index --- wled00/wled_server.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 4970248fa..1e9e9c36a 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -229,14 +229,32 @@ void serveIndexOrWelcome(AsyncWebServerRequest *request) } } +bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request) +{ + AsyncWebHeader* header = request->getHeader("If-None-Match"); + if (header && header->value() == String(VERSION)) { + request->send(304); + return true; + } + return false; +} + +bool setStaticContentCacheHeaders(AsyncWebServerResponse *response) +{ + response->addHeader(F("Cache-Control"),"max-age=2592000"); + response->addHeader(F("ETag"), String(VERSION)); +} void serveIndex(AsyncWebServerRequest* request) { if (handleFileRead(request, "/index.htm")) return; + if (handleIfNoneMatchCacheHeader(request)) return; + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", PAGE_index, PAGE_index_L); response->addHeader(F("Content-Encoding"),"gzip"); + setStaticContentCacheHeaders(response); request->send(response); } From a9c211d66caed0a34e0b9e6fbb6d3f5216025661 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 7 Feb 2021 14:45:34 +0100 Subject: [PATCH 3/8] Tetris (falling bricks) effect & Colortwinkles low brightness fix. --- wled00/FX.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++- wled00/FX.h | 9 ++++++--- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 27bd730d9..e7723182c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1991,7 +1991,7 @@ uint16_t WS2812FX::mode_colortwinkle() if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed CRGB fastled_col, prev; - fract8 fadeUpAmount = 8 + (SEGMENT.speed/4), fadeDownAmount = 5 + (SEGMENT.speed/7); + fract8 fadeUpAmount = _brightness>28 ? 8 + (SEGMENT.speed>>2) : 68-_brightness, fadeDownAmount = _brightness>28 ? 8 + (SEGMENT.speed>>3) : 68-_brightness; for (uint16_t i = 0; i < SEGLEN; i++) { fastled_col = col_to_crgb(getPixelColor(i)); prev = fastled_col; @@ -3144,6 +3144,59 @@ uint16_t WS2812FX::mode_drip(void) } +/* + * Tetris or Stacking (falling bricks) Effect + * by Blaz Kristan (https://github.com/blazoncek, https://blaz.at/home) + */ +typedef struct Tetris { + float pos; + float speed; + uint32_t col; +} tetris; + +uint16_t WS2812FX::mode_tetris(void) { + + uint16_t dataSize = sizeof(tetris); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Tetris* drop = reinterpret_cast(SEGENV.data); + + // initialize dropping on first call or segment full + if (SEGENV.call == 0 || SEGENV.aux1 >= SEGLEN) { + SEGENV.aux1 = 0; // reset brick stack size + SEGENV.step = 0; + fill(SEGCOLOR(1)); + return 250; // short wait + } + + if (SEGENV.step == 0) { //init + drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>4)+1 : random8(3,20)); // set speed + drop->pos = SEGLEN-1; // start at end of segment + drop->col = color_from_palette(random8(0,15)<<4,false,false,0); // limit color choices so there is enough HUE gap + SEGENV.step = 1; // drop state (0 init, 1 forming, 2 falling) + SEGENV.aux0 = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : random8(1,5)) * (1+(SEGLEN>>6)); // size of brick + } + + if (SEGENV.step == 1) { // forming + if (random8()>>6) { // random drop + SEGENV.step = 2; // fall + } + } + + if (SEGENV.step > 1) { // falling + if (drop->pos > SEGENV.aux1) { // fall until top of stack + drop->pos -= drop->speed; // may add gravity as: speed += gravity + if (int(drop->pos) < SEGENV.aux1) drop->pos = SEGENV.aux1; + for (uint16_t i=int(drop->pos); ipos)+SEGENV.aux0 ? drop->col : SEGCOLOR(1)); + } else { // we hit bottom + SEGENV.step = 0; // go back to init + SEGENV.aux1 += SEGENV.aux0; // increase the stack size + if (SEGENV.aux1 >= SEGLEN) return 1000; // wait for a second + } + } + return FRAMETIME; +} + + /* / Plasma Effect / adapted from https://github.com/atuline/FastLED-Demos/blob/master/plasma/plasma.ino diff --git a/wled00/FX.h b/wled00/FX.h index 27329a0af..0ee30e317 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -118,7 +118,7 @@ #define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE ) #define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED ) -#define MODE_COUNT 118 +#define MODE_COUNT 119 #define FX_MODE_STATIC 0 #define FX_MODE_BLINK 1 @@ -238,6 +238,7 @@ #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 +#define FX_MODE_TETRIS 118 class WS2812FX { @@ -577,6 +578,7 @@ class WS2812FX { _mode[FX_MODE_BLENDS] = &WS2812FX::mode_blends; _mode[FX_MODE_TV_SIMULATOR] = &WS2812FX::mode_tv_simulator; _mode[FX_MODE_DYNAMIC_SMOOTH] = &WS2812FX::mode_dynamic_smooth; + _mode[FX_MODE_TETRIS] = &WS2812FX::mode_tetris; _brightness = DEFAULT_BRIGHTNESS; currentPalette = CRGBPalette16(CRGB::Black); @@ -791,7 +793,8 @@ class WS2812FX { mode_candy_cane(void), mode_blends(void), mode_tv_simulator(void), - mode_dynamic_smooth(void); + mode_dynamic_smooth(void), + mode_tetris(void); private: NeoPixelWrapper *bus; @@ -891,7 +894,7 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", "Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow", "Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", -"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth" +"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth","Tetris" ])====="; From 8e71c3ae177f469fe97e35fc480e226918506199 Mon Sep 17 00:00:00 2001 From: Kevin Dorff Date: Tue, 9 Feb 2021 10:15:43 -0600 Subject: [PATCH 4/8] Rotary Encoder, Four Line Display, and Auto Save Usermods (#1722) * Ability to lookup Usermod by id so Usermods can use other Usermods. * Rotary Encoder UI using two Usermods * Updates. More to come, probably. * Updated rotary usermod to honor USE_FOUR_LINE_DISPLAY if you want to use four line display. It should be truly optional, now. * minor logic improvement to showing the current time in clock mode. * improved 24 hour display foratting and ability to use the FourLineDisplayUsermod without the RotaryEncoderUIUsermod (option disable sleep and clock modes). * Improved ordering of defines in the FourLineDisplayUsermod to put options people might need to change together toward the top. * relocate plugins. add mention of the Wire requirement. * usermod filenames changed, updating comment in const.h * fix usermod locations. * fix usermods_list to include changed folder. * Improved for both usermods: install, config, and docs. Included sample platform_override.ini. * Updated name of SDA and SCL defines for config of display * update docs. * Wrong year. Fixed. * Fix youtube link, improve config of sleep/clock when the rotary usermod isn't installed. * Minor fixes to four line display. Addition of Auto Save v2 usermod. * Allow config for auto-save to set the preset number to use. Load preset at startup (so brightness is set correctly). * Updated docs for Auto Save. * Updated docs for Auto Save. Co-authored-by: Kevin Dorff --- usermods/usermod_v2_auto_save/readme.md | 45 ++ .../usermod_v2_auto_save.h | 192 +++++++ .../usermod_v2_four_line_display/readme.md | 39 ++ .../usermod_v2_four_line_display.h | 530 ++++++++++++++++++ .../platformio_override.ini.sample | 46 ++ .../usermod_v2_rotary_encoder_ui/readme.md | 33 ++ .../usermod_v2_rotary_encoder_ui.h | 420 ++++++++++++++ wled00/const.h | 3 + wled00/fcn_declare.h | 1 + wled00/um_manager.cpp | 12 + wled00/usermods_list.cpp | 20 + 11 files changed, 1341 insertions(+) create mode 100644 usermods/usermod_v2_auto_save/readme.md create mode 100644 usermods/usermod_v2_auto_save/usermod_v2_auto_save.h create mode 100644 usermods/usermod_v2_four_line_display/readme.md create mode 100644 usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h create mode 100644 usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample create mode 100644 usermods/usermod_v2_rotary_encoder_ui/readme.md create mode 100644 usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h diff --git a/usermods/usermod_v2_auto_save/readme.md b/usermods/usermod_v2_auto_save/readme.md new file mode 100644 index 000000000..5c835c60e --- /dev/null +++ b/usermods/usermod_v2_auto_save/readme.md @@ -0,0 +1,45 @@ +# Auto Save + +v2 Usermod to automatically save settings +to preset number AUTOSAVE_PRESET_NUM after a change to any of + +* brightness +* effect speed +* effect intensity +* mode (effect) +* palette + +but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle" +period in case there are other changes (any change will +extend the "settle" window). + +It will additionally load preset AUTOSAVE_PRESET_NUM at startup. +during the first `loop()`. Reasoning below. + +AutoSaveUsermod is standalone, but if FourLineDisplayUsermod is installed, it will notify the user of the saved changes. + +Note: I don't love that WLED doesn't respect the brightness of the preset being auto loaded, so the AutoSaveUsermod will set the AUTOSAVE_PRESET_NUM preset in the first loop, so brightness IS honored. This means WLED will effectively ignore Default brightness and Apply N preset at boot when the AutoSaveUsermod is installed. + +## Installation + +Copy and update the example `platformio_override.ini.sample` +from the Rotary Encoder UI usermode folder to the root directory of your particular build. +This file should be placed in the same directory as `platformio.ini`. + +### Define Your Options + +* `USERMOD_AUTO_SAVE` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp +* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) +* `AUTOSAVE_SETTLE_MS` - Minimum time to wave before auto saving, defaults to 10000 (10s) +* `AUTOSAVE_PRESET_NUM` - Preset number to auto-save to, auto-load at startup from, defaults to 99 + +### PlatformIO requirements + +No special requirements. + +Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. + +## Change Log + +2021-02 +* First public release diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h new file mode 100644 index 000000000..bd7ea6d81 --- /dev/null +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -0,0 +1,192 @@ +#pragma once + +#include "wled.h" + +// +// v2 Usermod to automatically save settings +// to preset number AUTOSAVE_PRESET_NUM after a change to any of +// +// * brightness +// * effect speed +// * effect intensity +// * mode (effect) +// * palette +// +// but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle" +// period in case there are other changes (any change will +// extend the "settle" window). +// +// It will additionally load preset AUTOSAVE_PRESET_NUM at startup. +// during the first `loop()`. Reasoning below. +// +// AutoSaveUsermod is standalone, but if FourLineDisplayUsermod +// is installed, it will notify the user of the saved changes. +// +// Note: I don't love that WLED doesn't respect the brightness +// of the preset being auto loaded, so the AutoSaveUsermod +// will set the AUTOSAVE_PRESET_NUM preset in the first loop, +// so brightness IS honored. This means WLED will effectively +// ignore Default brightness and Apply N preset at boot when +// the AutoSaveUsermod is installed. + +//How long to wait after settings change to auto-save +#ifndef AUTOSAVE_SETTLE_MS +#define AUTOSAVE_SETTLE_MS 10*1000 +#endif + +//Preset number to save to +#ifndef AUTOSAVE_PRESET_NUM +#define AUTOSAVE_PRESET_NUM 99 +#endif + +// "Auto save MM-DD HH:MM:SS" +#define PRESET_NAME_BUFFER_SIZE 25 + +class AutoSaveUsermod : public Usermod { + private: + // If we've detected the need to auto save, this will + // be non zero. + unsigned long autoSaveAfter = 0; + + char presetNameBuffer[PRESET_NAME_BUFFER_SIZE]; + + bool firstLoop = true; + + uint8_t knownBrightness = 0; + uint8_t knownEffectSpeed = 0; + uint8_t knownEffectIntensity = 0; + uint8_t knownMode = 0; + uint8_t knownPalette = 0; + +#ifdef USERMOD_FOUR_LINE_DISLAY + FourLineDisplayUsermod* display; +#endif + + public: + // gets called once at boot. Do all initialization that doesn't depend on + // network here + void setup() { +#ifdef USERMOD_FOUR_LINE_DISLAY + // This Usermod has enhanced funcionality if + // FourLineDisplayUsermod is available. + display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); +#endif + } + + // gets called every time WiFi is (re-)connected. Initialize own network + // interfaces here + void connected() {} + + /** + * Da loop. + */ + void loop() { + unsigned long now = millis(); + uint8_t currentMode = strip.getMode(); + uint8_t currentPalette = strip.getSegment(0).palette; + if (firstLoop) { + firstLoop = false; + applyPreset(AUTOSAVE_PRESET_NUM); + knownBrightness = bri; + knownEffectSpeed = effectSpeed; + knownEffectIntensity = effectIntensity; + knownMode = currentMode; + knownPalette = currentPalette; + return; + } + + unsigned long wouldAutoSaveAfter = now + AUTOSAVE_SETTLE_MS; + 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(); + } + } + + void saveSettings() { + updateLocalTime(); + sprintf(presetNameBuffer, + "Auto save %02d-%02d %02d:%02d:%02d", + month(localTime), day(localTime), + hour(localTime), minute(localTime), second(localTime)); + savePreset(AUTOSAVE_PRESET_NUM, true, presetNameBuffer); + } + + void displayOverlay() { +#ifdef USERMOD_FOUR_LINE_DISLAY + if (display != nullptr) { + display->wakeDisplay(); + display->overlay("Settings", "Auto Saved", 1500); + } +#endif + } + + /* + * 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) { + } + + /* + * 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) { + } + + /* + * 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) { + } + + /* + * 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; + } + +}; \ No newline at end of file diff --git a/usermods/usermod_v2_four_line_display/readme.md b/usermods/usermod_v2_four_line_display/readme.md new file mode 100644 index 000000000..3198b2be5 --- /dev/null +++ b/usermods/usermod_v2_four_line_display/readme.md @@ -0,0 +1,39 @@ +# Rotary Encoder UI Usermod + +First, thanks to the authors of the ssd11306_i2c_oled_u8g2 mod. + +This usermod provides a four line display using either +128x32 or 128x64 OLED displays. +It's can operate independently, but starts to provide +a relatively complete on-device UI when paired with the +Rotary Encoder UI usermod. I strongly encourage you to use +them together. + +[See the pair of usermods in action](https://www.youtube.com/watch?v=tITQY80rIOA) + +## Installation + +Copy and update the example `platformio_override.ini.sample` +from the Rotary Encoder UI usermode folder to the root directory of your particular build. +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 +* `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. + +### PlatformIO requirements + +This usermod requires the `U8g2` and `Wire` libraries. See the +`platformio_override.ini.sample` found in the Rotary Encoder +UI usermod folder for how to include these using `platformio_override.ini`. + +## Change Log + +2021-02 +* First public release 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 new file mode 100644 index 000000000..9f4716218 --- /dev/null +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -0,0 +1,530 @@ +#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. +// +// 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. +#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 +#else +#define SLEEP_MODE_ENABLED false +#define CLOCK_MODE_ENABLED false +#endif + +// When to time out to the clock or blank the screen +// if SLEEP_MODE_ENABLED. +#define SCREEN_TIMEOUT_MS 15*1000 + +#define TIME_INDENT 0 +#define DATE_INDENT 2 + +// 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 + +class FourLineDisplayUsermod : public Usermod { + private: + unsigned long lastTime = 0; + + // 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; + long lastUpdate = 0; + long lastRedraw = 0; + long overlayUntil = 0; + byte lineThreeType = FLD_LINE_3_BRIGHTNESS; + // Set to 2 or 3 to mark lines 2 or 3. Other values ignored. + byte markLineNum = 0; + + char lineBuffer[LINE_BUFFER_SIZE]; + + // 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..."); + } + + // gets called every time WiFi is (re-)connected. Initialize own network + // interfaces here + void connected() {} + + /** + * Da loop. + */ + void loop() { + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + return; + } + lastUpdate = millis(); + + redraw(false); + } + + /** + * Redraw the screen (but only if things have changed + * or if forceRedraw). + */ + void redraw(bool forceRedraw) { + 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(SLEEP_MODE_ENABLED && !displayTurnedOff && + (millis() - lastRedraw > SCREEN_TIMEOUT_MS)) { + // We will still check if there is a change in redraw() + // and turn it back on if it changed. + sleepOrClock(true); + } + else if (displayTurnedOff && CLOCK_MODE_ENABLED) { + showTime(); + } + return; + } + needRedraw = false; + lastRedraw = millis(); + + if (displayTurnedOff) + { + // Turn the display back on + sleepOrClock(false); + } + + // Update last known values. + #if defined(ESP8266) + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #else + knownSsid = WiFi.SSID(); + #endif + 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 + 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, "~"); + } + + // Second row with IP or Psssword + // Print password in AP mode and if led is OFF. + if (apActive && bri == 0) { + u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, apPass); + } + else { + String ipString = knownIp.toString(); + u8x8.DRAW_STRING(1, 1*LINE_HEIGHT, ipString.c_str()); + } + + // Third row with mode name + showCurrentEffectOrPalette(JSON_mode_names, 2, knownMode); + + switch(lineThreeType) { + case FLD_LINE_3_BRIGHTNESS: + sprintf(lineBuffer, "Brightness %d", bri); + u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + break; + case FLD_LINE_3_EFFECT_SPEED: + sprintf(lineBuffer, "FX Speed %d", effectSpeed); + u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + break; + case FLD_LINE_3_EFFECT_INTENSITY: + sprintf(lineBuffer, "FX Intense %d", effectIntensity); + u8x8.DRAW_STRING(1, 3*LINE_HEIGHT, lineBuffer); + break; + case FLD_LINE_3_PALETTE: + showCurrentEffectOrPalette(JSON_palette_names, 3, knownPalette); + 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(const char json[], uint8_t row, uint8_t desiredEntry) { + uint8_t qComma = 0; + bool insideQuotes = false; + // advance past the mark for markLineNum that may exist. + uint8_t printedChars = 1; + char singleJsonSymbol; + + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(json); i++) { + singleJsonSymbol = pgm_read_byte_near(json + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != desiredEntry)) { + break; + } + u8x8.DRAW_GLYPH(printedChars, row * LINE_HEIGHT, singleJsonSymbol); + printedChars++; + } + if ((qComma > desiredEntry) || (printedChars > u8x8.getCols() - 2)) { + break; + } + } + } + + /** + * 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() { + 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 + 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); + } + overlayUntil = millis() + showHowLong; + } + + /** + * Specify what data should be defined on line 3 + * (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; + } + } + + /** + * Line 2 or 3 (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; + } + } + + /* + * 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); + } + displayTurnedOff = true; + } + else { + if (!CLOCK_MODE_ENABLED) { + u8x8.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() { + updateLocalTime(); + byte minuteCurrent = minute(localTime); + byte hourCurrent = hour(localTime); + if (knownMinute == minuteCurrent && knownHour == hourCurrent) { + // Time hasn't changed. + return; + } + 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 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(lineBuffer, "%02d:%02d %s", showHour, minuteCurrent, useAMPM ? (isAM ? "AM" : "PM") : ""); + // For time, we always use LINE_HEIGHT of 2 since + // we are printing it big. + u8x8.DRAW_BIG_STRING(TIME_INDENT + (useAMPM ? 0 : 2), (TIME_LINE + 1) * 2, lineBuffer); + } + + /* + * 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) { + } + + /* + * 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) { + } + + /* + * 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) { + } + + /* + * 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; + } + +}; \ No newline at end of file diff --git a/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample new file mode 100644 index 000000000..485d67f6a --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui/platformio_override.ini.sample @@ -0,0 +1,46 @@ +[platformio] +default_envs = d1_mini +; default_envs = esp32dev + +[env:esp32dev] +board = esp32dev +platform = espressif32@2.0 +build_unflags = ${common.build_unflags} +build_flags = + ${common.build_flags_esp32} + -D USERMOD_FOUR_LINE_DISLAY -D FLD_PIN_SCL=22 -D FLD_PIN_SDA=21 + -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 + -D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1 + -D LEDPIN=16 -D BTNPIN=13 +upload_speed = 460800 +lib_ignore = + ESPAsyncTCP + ESPAsyncUDP + +[env:d1_mini] +board = d1_mini +platform = ${common.platform_wled_default} +platform_packages = ${common.platform_packages} +upload_speed = 460800 +board_build.ldscript = ${common.ldscript_4m1m} +build_unflags = ${common.build_unflags} +build_flags = + ${common.build_flags_esp8266} + -D USERMOD_FOUR_LINE_DISLAY -D FLD_PIN_SCL=5 -D FLD_PIN_SDA=4 + -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=12 -D ENCODER_CLK_PIN=14 -D ENCODER_SW_PIN=13 + -D USERMOD_AUTO_SAVE -D AUTOSAVE_PRESET_NUM=1 + -D LEDPIN=3 -D BTNPIN=0 +monitor_filters = esp8266_exception_decoder + +[env] +lib_deps = + fastled/FastLED @ 3.3.2 + NeoPixelBus @ 2.6.0 + ESPAsyncTCP @ 1.2.0 + ESPAsyncUDP + AsyncTCP @ 1.0.3 + IRremoteESP8266 @ 2.7.3 + https://github.com/lorol/LITTLEFS.git + https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.0 + U8g2@~2.27.2 + Wire diff --git a/usermods/usermod_v2_rotary_encoder_ui/readme.md b/usermods/usermod_v2_rotary_encoder_ui/readme.md new file mode 100644 index 000000000..4477590b5 --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui/readme.md @@ -0,0 +1,33 @@ +# Rotary Encoder UI Usermod + +First, thanks to the authors of other Rotary Encoder usermods. + +This usermod starts to provide a relatively complete on-device +UI when paired with the Four Line Display usermod. I strongly +encourage you to try them together. + +[See the pair of usermods in action](https://www.youtube.com/watch?v=tITQY80rIOA) + +## Installation + +Copy and update the example `platformio_override.ini.sample` to the root directory of your particular build. +This file should be placed in the same directory as `platformio.ini`. + +### Define Your Options + +* `USERMOD_ROTARY_ENCODER_UI` - define this to have this user mod included wled00\usermods_list.cpp +* `USERMOD_FOUR_LINE_DISLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) +* `ENCODER_DT_PIN` - The encoders DT pin, defaults to 12 +* `ENCODER_CLK_PIN` - The encoders CLK pin, defaults to 14 +* `ENCODER_SW_PIN` - The encoders SW pin, defaults to 13 + +### PlatformIO requirements + +No special requirements. + +Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. + +## Change Log + +2021-02 +* First public release diff --git a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h new file mode 100644 index 000000000..59cabfc4d --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h @@ -0,0 +1,420 @@ +#pragma once + +#include "wled.h" + +// +// Inspired by the v1 usermods +// * rotary_encoder_change_brightness +// * rotary_encoder_change_effect +// +// v2 usermod that provides a rotary encoder-based UI. +// +// This Usermod works best coupled with FourLineDisplayUsermod. +// +// This usermod allows you to control: +// +// * Brightness +// * Selected Effect +// * Effect Speed +// * Effect Intensity +// * Palette +// +// Change between modes by pressing a button. +// + +#ifndef ENCODER_DT_PIN +#define ENCODER_DT_PIN 12 +#endif + +#ifndef ENCODER_CLK_PIN +#define ENCODER_CLK_PIN 14 +#endif + +#ifndef ENCODER_SW_PIN +#define ENCODER_SW_PIN 13 +#endif + +#ifndef USERMOD_FOUR_LINE_DISLAY +// These constants won't be defined if we aren't using FourLineDisplay. +#define FLD_LINE_3_BRIGHTNESS 0 +#define FLD_LINE_3_EFFECT_SPEED 0 +#define FLD_LINE_3_EFFECT_INTENSITY 0 +#define FLD_LINE_3_PALETTE 0 +#endif + +// The last UI state +#define LAST_UI_STATE 4 + +/** + * Array of mode indexes in alphabetical order. + * Should be ordered from JSON_mode_names array in FX.h. + * + * NOTE: If JSON_mode_names changes, this will need to be updated. + */ +const byte modes_alpha_order[] = { + 0, 27, 38, 115, 1, 26, 91, 68, 2, 88, 102, 114, 28, 31, 32, + 30, 29, 111, 52, 34, 8, 74, 67, 112, 18, 19, 96, 7, 117, 12, + 69, 66, 45, 42, 90, 89, 110, 87, 46, 53, 82, 100, 58, 64, 75, + 41, 57, 47, 44, 76, 77, 59, 70, 71, 72, 73, 107, 62, 101, 65, + 98, 105, 109, 97, 48, 49, 95, 63, 78, 43, 9, 33, 5, 79, 99, + 15, 37, 16, 10, 11, 40, 60, 108, 92, 93, 94, 103, 83, 84, 20, + 21, 22, 85, 86, 39, 61, 23, 25, 24, 104, 6, 36, 13, 14, 35, + 54, 56, 55, 116, 17, 81, 80, 106, 51, 50, 113, 3, 4 }; + +/** + * Array of palette indexes in alphabetical order. + * Should be ordered from JSON_palette_names array in FX.h. + * + * NOTE: If JSON_palette_names changes, this will need to be updated. + */ +const byte palettes_alpha_order[] = { + 0, 1, 2, 3, 4, 5, 18, 46, 51, 50, 55, 39, 26, 22, 15, + 48, 52, 53, 7, 37, 24, 30, 35, 10, 32, 28, 29, 36, 31, + 25, 8, 38, 40, 41, 9, 44, 47, 6, 20, 11, 12, 16, 33, + 14, 49, 27, 19, 13, 21, 54, 34, 45, 23, 43, 17, 42 }; + +class RotaryEncoderUIUsermod : public Usermod { +private: + int fadeAmount = 10; // Amount to change every step (brightness) + unsigned long currentTime; + unsigned long loopTime; + const int pinA = ENCODER_DT_PIN; // DT from encoder + const int pinB = ENCODER_CLK_PIN; // CLK from encoder + const int pinC = ENCODER_SW_PIN; // SW from encoder + unsigned char select_state = 0; // 0: brightness, 1: effect, 2: effect speed + unsigned char button_state = HIGH; + unsigned char prev_button_state = HIGH; + +#ifdef USERMOD_FOUR_LINE_DISLAY + FourLineDisplayUsermod* display; +#else + void* display = nullptr; +#endif + unsigned char Enc_A; + unsigned char Enc_B; + unsigned char Enc_A_prev = 0; + + bool currentEffectAndPaleeteInitialized = false; + uint8_t effectCurrentIndex = 0; + uint8_t effectPaletteIndex = 0; + +public: + /* + * setup() is called once at boot. WiFi is not yet connected at this point. + * You can use it to initialize variables, sensors or similar. + */ + void setup() + { + pinMode(pinA, INPUT_PULLUP); + pinMode(pinB, INPUT_PULLUP); + pinMode(pinC, INPUT_PULLUP); + currentTime = millis(); + loopTime = currentTime; + +#ifdef USERMOD_FOUR_LINE_DISLAY + // This Usermod uses FourLineDisplayUsermod for the best experience. + // But it's optional. But you want it. + display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); + if (display != nullptr) { + display->setLineThreeType(FLD_LINE_3_BRIGHTNESS); + display->setMarkLine(3); + } +#endif + } + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() + { + //Serial.println("Connected to WiFi!"); + } + + /* + * loop() is called continuously. Here you can check for events, read sensors, etc. + * + * Tips: + * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. + * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. + * + * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. + * Instead, use a timer check as shown here. + */ + void loop() + { + currentTime = millis(); // get the current elapsed time + + // Initialize effectCurrentIndex and effectPaletteIndex to + // current state. We do it here as (at least) effectCurrent + // is not yet initialized when setup is called. + if (!currentEffectAndPaleeteInitialized) { + findCurrentEffectAndPalette(); + } + + if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz + { + button_state = digitalRead(pinC); + if (prev_button_state != button_state) + { + if (button_state == LOW) + { + prev_button_state = button_state; + + char newState = select_state + 1; + if (newState > LAST_UI_STATE) newState = 0; + + bool changedState = true; + if (display != nullptr) { + switch(newState) { + case 0: + changedState = changeState("Brightness", FLD_LINE_3_BRIGHTNESS, 3); + break; + case 1: + changedState = changeState("Select FX", FLD_LINE_3_EFFECT_SPEED, 2); + break; + case 2: + changedState = changeState("FX Speed", FLD_LINE_3_EFFECT_SPEED, 3); + break; + case 3: + changedState = changeState("FX Intensity", FLD_LINE_3_EFFECT_INTENSITY, 3); + break; + case 4: + changedState = changeState("Palette", FLD_LINE_3_PALETTE, 3); + break; + } + } + if (changedState) { + select_state = newState; + } + } + else + { + prev_button_state = button_state; + } + } + int Enc_A = digitalRead(pinA); // Read encoder pins + int Enc_B = digitalRead(pinB); + if ((!Enc_A) && (Enc_A_prev)) + { // A has gone from high to low + if (Enc_B == HIGH) + { // B is high so clockwise + switch(select_state) { + case 0: + changeBrightness(true); + break; + case 1: + changeEffect(true); + break; + case 2: + changeEffectSpeed(true); + break; + case 3: + changeEffectIntensity(true); + break; + case 4: + changePalette(true); + break; + } + } + else if (Enc_B == LOW) + { // B is low so counter-clockwise + switch(select_state) { + case 0: + changeBrightness(false); + break; + case 1: + changeEffect(false); + break; + case 2: + changeEffectSpeed(false); + break; + case 3: + changeEffectIntensity(false); + break; + case 4: + changePalette(false); + break; + } + } + } + Enc_A_prev = Enc_A; // Store value of A for next time + loopTime = currentTime; // Updates loopTime + } + } + + void findCurrentEffectAndPalette() { + currentEffectAndPaleeteInitialized = true; + for (uint8_t i = 0; i < strip.getModeCount(); i++) { + byte value = modes_alpha_order[i]; + if (modes_alpha_order[i] == effectCurrent) { + effectCurrentIndex = i; + break; + } + } + + for (uint8_t i = 0; i < strip.getPaletteCount(); i++) { + byte value = palettes_alpha_order[i]; + if (palettes_alpha_order[i] == strip.getSegment(0).palette) { + effectPaletteIndex = i; + break; + } + } + } + + boolean changeState(const char *stateName, byte lineThreeMode, byte markedLine) { +#ifdef USERMOD_FOUR_LINE_DISLAY + if (display != nullptr) { + if (display->wakeDisplay()) { + // Throw away wake up input + return false; + } + display->overlay("Mode change", stateName, 1500); + display->setLineThreeType(lineThreeMode); + display->setMarkLine(markedLine); + } + #endif + return true; + } + + void lampUdated() { + bool fxChanged = strip.setEffectConfig(effectCurrent, effectSpeed, effectIntensity, effectPalette); + + //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) + // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa + colorUpdated(NOTIFIER_CALL_MODE_DIRECT_CHANGE); + updateInterfaces(NOTIFIER_CALL_MODE_DIRECT_CHANGE); + } + + void changeBrightness(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } +#endif + if (increase) { + bri = (bri + fadeAmount <= 255) ? (bri + fadeAmount) : 255; + } + else { + bri = (bri - fadeAmount >= 0) ? (bri - fadeAmount) : 0; + } + lampUdated(); + } + + void changeEffect(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } +#endif + if (increase) { + effectCurrentIndex = (effectCurrentIndex + 1 >= strip.getModeCount()) ? 0 : (effectCurrentIndex + 1); + } + else { + effectCurrentIndex = (effectCurrentIndex - 1 < 0) ? (strip.getModeCount() - 1) : (effectCurrentIndex - 1); + } + effectCurrent = modes_alpha_order[effectCurrentIndex]; + lampUdated(); + } + + void changeEffectSpeed(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } +#endif + if (increase) { + effectSpeed = (effectSpeed + fadeAmount <= 255) ? (effectSpeed + fadeAmount) : 255; + } + else { + effectSpeed = (effectSpeed - fadeAmount >= 0) ? (effectSpeed - fadeAmount) : 0; + } + lampUdated(); + } + + void changeEffectIntensity(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } +#endif + if (increase) { + effectIntensity = (effectIntensity + fadeAmount <= 255) ? (effectIntensity + fadeAmount) : 255; + } + else { + effectIntensity = (effectIntensity - fadeAmount >= 0) ? (effectIntensity - fadeAmount) : 0; + } + lampUdated(); + } + + void changePalette(bool increase) { +#ifdef USERMOD_FOUR_LINE_DISLAY + if (display && display->wakeDisplay()) { + // Throw away wake up input + return; + } +#endif + if (increase) { + effectPaletteIndex = (effectPaletteIndex + 1 >= strip.getPaletteCount()) ? 0 : (effectPaletteIndex + 1); + } + else { + effectPaletteIndex = (effectPaletteIndex - 1 < 0) ? (strip.getPaletteCount() - 1) : (effectPaletteIndex - 1); + } + effectPalette = palettes_alpha_order[effectPaletteIndex]; + lampUdated(); + } + + /* + * 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 + } + */ + + /* + * 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) + { + //root["user0"] = userVar0; + } + + /* + * 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) + { + userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value + //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); + } + + /* + * 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_ROTARY_ENC_UI; + } + + //More methods can be added in the future, this example will then be extended. + //Your usermod will remain compatible as it does not need to implement all methods from the Usermod base class! +}; diff --git a/wled00/const.h b/wled00/const.h index a7a155b65..d2cf05a3f 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -21,6 +21,9 @@ #define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h" #define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h" #define USERMOD_ID_IMU 6 //Usermod "usermod_mpu6050_imu.h" +#define USERMOD_ID_FOUR_LINE_DISP 7 //Usermod "usermod_v2_four_line_display.h +#define USERMOD_ID_ROTARY_ENC_UI 8 //Usermod "usermod_v2_rotary_encoder_ui.h" +#define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index c789211b4..9d053e27f 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -221,6 +221,7 @@ class UsermodManager { void readFromConfig(JsonObject& obj); bool add(Usermod* um); + Usermod* lookup(uint16_t mod_id); byte getModCount(); }; diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index cec7d6138..51fcd0ec9 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -15,6 +15,18 @@ void UsermodManager::readFromJsonState(JsonObject& obj) { for (byte i = 0; i < n void UsermodManager::addToConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->addToConfig(obj); } void UsermodManager::readFromConfig(JsonObject& obj) { for (byte i = 0; i < numMods; i++) ums[i]->readFromConfig(obj); } +/* + * Enables usermods to lookup another Usermod. + */ +Usermod* UsermodManager::lookup(uint16_t mod_id) { + for (byte i = 0; i < numMods; i++) { + if (ums[i]->getId() == mod_id) { + return ums[i]; + } + } + return nullptr; +} + bool UsermodManager::add(Usermod* um) { if (numMods >= WLED_MAX_USERMODS || um == nullptr) return false; diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index c5f106b0f..bfff2d770 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -21,6 +21,16 @@ #include "usermod_v2_SensorsToMqtt.h" #endif +#ifdef USERMOD_FOUR_LINE_DISLAY +#include "../usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h" +#endif +#ifdef USERMOD_ROTARY_ENCODER_UI +#include "../usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h" +#endif +#ifdef USERMOD_AUTO_SAVE +#include "../usermods/usermod_v2_auto_save/usermod_v2_auto_save.h" +#endif + void registerUsermods() { /* @@ -39,4 +49,14 @@ void registerUsermods() #ifdef USERMOD_SENSORSTOMQTT usermods.add(new UserMod_SensorsToMQTT()); #endif + +#ifdef USERMOD_FOUR_LINE_DISLAY + usermods.add(new FourLineDisplayUsermod()); +#endif +#ifdef USERMOD_ROTARY_ENCODER_UI + usermods.add(new RotaryEncoderUIUsermod()); +#endif +#ifdef USERMOD_AUTO_SAVE + usermods.add(new AutoSaveUsermod()); +#endif } \ No newline at end of file From 7092f337ef8d074e38b6bdeb7e87850395ff4461 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Wed, 10 Feb 2021 00:37:05 +0100 Subject: [PATCH 5/8] Faster Tetrix mode - Replaced a letter in name (copyright) - 2x speed - Replaced Merry christmas mode --- wled00/FX.cpp | 12 ++---------- wled00/FX.h | 17 +++++++---------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e7723182c..786ea6925 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1001,14 +1001,6 @@ uint16_t WS2812FX::mode_running_color(void) { return running(SEGCOLOR(0), SEGCOLOR(1)); } - -/* - * Alternating red/green pixels running. - */ -uint16_t WS2812FX::mode_merry_christmas(void) { - return running(RED, GREEN); -} - /* * Alternating red/white pixels running. */ @@ -3154,7 +3146,7 @@ typedef struct Tetris { uint32_t col; } tetris; -uint16_t WS2812FX::mode_tetris(void) { +uint16_t WS2812FX::mode_tetrix(void) { uint16_t dataSize = sizeof(tetris); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -3169,7 +3161,7 @@ uint16_t WS2812FX::mode_tetris(void) { } if (SEGENV.step == 0) { //init - drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>4)+1 : random8(3,20)); // set speed + drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>3)+1 : random8(6,40)); // set speed drop->pos = SEGLEN-1; // start at end of segment drop->col = color_from_palette(random8(0,15)<<4,false,false,0); // limit color choices so there is enough HUE gap SEGENV.step = 1; // drop state (0 init, 1 forming, 2 falling) diff --git a/wled00/FX.h b/wled00/FX.h index 0ee30e317..44c57d074 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -118,7 +118,7 @@ #define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE ) #define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED ) -#define MODE_COUNT 119 +#define MODE_COUNT 118 #define FX_MODE_STATIC 0 #define FX_MODE_BLINK 1 @@ -164,7 +164,7 @@ #define FX_MODE_COMET 41 #define FX_MODE_FIREWORKS 42 #define FX_MODE_RAIN 43 -#define FX_MODE_MERRY_CHRISTMAS 44 +#define FX_MODE_TETRIX 44 #define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 @@ -238,7 +238,6 @@ #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 -#define FX_MODE_TETRIS 118 class WS2812FX { @@ -502,7 +501,7 @@ class WS2812FX { _mode[FX_MODE_COMET] = &WS2812FX::mode_comet; _mode[FX_MODE_FIREWORKS] = &WS2812FX::mode_fireworks; _mode[FX_MODE_RAIN] = &WS2812FX::mode_rain; - _mode[FX_MODE_MERRY_CHRISTMAS] = &WS2812FX::mode_merry_christmas; + _mode[FX_MODE_TETRIX] = &WS2812FX::mode_tetrix; _mode[FX_MODE_FIRE_FLICKER] = &WS2812FX::mode_fire_flicker; _mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient; _mode[FX_MODE_LOADING] = &WS2812FX::mode_loading; @@ -578,7 +577,6 @@ class WS2812FX { _mode[FX_MODE_BLENDS] = &WS2812FX::mode_blends; _mode[FX_MODE_TV_SIMULATOR] = &WS2812FX::mode_tv_simulator; _mode[FX_MODE_DYNAMIC_SMOOTH] = &WS2812FX::mode_dynamic_smooth; - _mode[FX_MODE_TETRIS] = &WS2812FX::mode_tetris; _brightness = DEFAULT_BRIGHTNESS; currentPalette = CRGBPalette16(CRGB::Black); @@ -719,7 +717,7 @@ class WS2812FX { mode_comet(void), mode_fireworks(void), mode_rain(void), - mode_merry_christmas(void), + mode_tetrix(void), mode_halloween(void), mode_fire_flicker(void), mode_gradient(void), @@ -793,8 +791,7 @@ class WS2812FX { mode_candy_cane(void), mode_blends(void), mode_tv_simulator(void), - mode_dynamic_smooth(void), - mode_tetris(void); + mode_dynamic_smooth(void); private: NeoPixelWrapper *bus; @@ -887,14 +884,14 @@ const char JSON_mode_names[] PROGMEM = R"=====([ "Scan","Scan Dual","Fade","Theater","Theater Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd", "Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random", "Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Running 2","Aurora","Stream", -"Scanner","Lighthouse","Fireworks","Rain","Merry Christmas","Fire Flicker","Gradient","Loading","Police","Police All", +"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Police All", "Two Dots","Two Areas","Circus","Halloween","Tri Chase","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet", "Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise", "Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple", "Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", "Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow", "Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", -"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth","Tetris" +"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth" ])====="; From 2544d2e0680d4fd27ae3741bb773d9e513e42b0d Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 12 Feb 2021 11:54:35 +0100 Subject: [PATCH 6/8] Dynamic LED map creation from JSON file /ledmap.json in format {"map":[4,3,2,1,...]}. Used for rearranging LEDs (matrices, awkward placement, ...) --- wled00/FX_fcn.cpp | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 8c1681a30..41b01016e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -28,26 +28,50 @@ #include "palettes.h" //enable custom per-LED mapping. This can allow for better effects on matrices or special displays -//#define WLED_CUSTOM_LED_MAPPING - -#ifdef WLED_CUSTOM_LED_MAPPING +/* //this is just an example (30 LEDs). It will first set all even, then all uneven LEDs. const uint16_t customMappingTable[] = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29}; //another example. Switches direction every 5 LEDs. -/*const uint16_t customMappingTable[] = { +const uint16_t customMappingTable[] = { 0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, - 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25};*/ + 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25}; const uint16_t customMappingSize = sizeof(customMappingTable)/sizeof(uint16_t); //30 in example -#endif +*/ +uint16_t* customMappingTable = nullptr; +uint16_t customMappingSize = 0; #ifndef PWM_INDEX #define PWM_INDEX 0 #endif +void WS2812FX::deserializeMap(void) { + DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps + + DEBUG_PRINTLN(F("Reading LED map from /ledmap.json...")); + + if (!readObjectFromFile("/ledmap.json", nullptr, &doc)) return; //if file does not exist just exit + + if (customMappingTable != nullptr) { + delete[] customMappingTable; + customMappingTable = nullptr; + customMappingSize = 0; + } + + JsonArray map = doc[F("map")]; + if (!map.isNull() && map.size()) { // not an empty map + customMappingSize = map.size(); + customMappingTable = new uint16_t[customMappingSize]; + for (uint16_t i=0; iBegin((NeoPixelType)ty, _lengthRaw); _segments[0].start = 0; @@ -189,9 +215,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) int16_t indexSet = realIndex + (reversed ? -j : j); int16_t indexSetRev = indexSet; if (reverseMode) indexSetRev = REV(indexSet); - #ifdef WLED_CUSTOM_LED_MAPPING if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; - #endif if (indexSetRev >= SEGMENT.start && indexSetRev < SEGMENT.stop) { bus->SetPixelColor(indexSet + skip, col); if (IS_MIRROR) { //set the corresponding mirrored pixel @@ -205,9 +229,7 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) } } else { //live data, etc. if (reverseMode) i = REV(i); - #ifdef WLED_CUSTOM_LED_MAPPING if (i < customMappingSize) i = customMappingTable[i]; - #endif bus->SetPixelColor(i + skip, col); } if (skip && i == 0) { @@ -482,9 +504,7 @@ uint32_t WS2812FX::getPixelColor(uint16_t i) { i = realPixelIndex(i); - #ifdef WLED_CUSTOM_LED_MAPPING if (i < customMappingSize) i = customMappingTable[i]; - #endif if (_skipFirstMode) i += LED_SKIP_AMOUNT; From 2f7be3475d47d8f8c67aa60e92a0610a6b38303e Mon Sep 17 00:00:00 2001 From: cschwinne Date: Sat, 13 Feb 2021 01:02:14 +0100 Subject: [PATCH 7/8] Updated mapping comment --- wled00/FX.h | 8 ++++- wled00/FX_fcn.cpp | 74 +++++++++++++++++++++++------------------------ 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 44c57d074..589a7005c 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -24,6 +24,8 @@ Modified for WLED */ +#include "wled.h" + #ifndef WS2812FX_h #define WS2812FX_h @@ -847,7 +849,11 @@ class WS2812FX { void blendPixelColor(uint16_t n, uint32_t color, uint8_t blend), - startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot); + startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot), + deserializeMap(void); + + uint16_t* customMappingTable = nullptr; + uint16_t customMappingSize = 0; uint32_t _lastPaletteChange = 0; uint32_t _lastShow = 0; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 41b01016e..aef500ce1 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -27,50 +27,25 @@ #include "FX.h" #include "palettes.h" -//enable custom per-LED mapping. This can allow for better effects on matrices or special displays -/* -//this is just an example (30 LEDs). It will first set all even, then all uneven LEDs. -const uint16_t customMappingTable[] = { - 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, - 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29}; - -//another example. Switches direction every 5 LEDs. -const uint16_t customMappingTable[] = { - 0, 1, 2, 3, 4, 9, 8, 7, 6, 5, 10, 11, 12, 13, 14, - 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25}; - -const uint16_t customMappingSize = sizeof(customMappingTable)/sizeof(uint16_t); //30 in example -*/ -uint16_t* customMappingTable = nullptr; -uint16_t customMappingSize = 0; - #ifndef PWM_INDEX #define PWM_INDEX 0 #endif -void WS2812FX::deserializeMap(void) { - DynamicJsonDocument doc(JSON_BUFFER_SIZE); // full sized buffer for larger maps +/* + Custom per-LED mapping has moved! - DEBUG_PRINTLN(F("Reading LED map from /ledmap.json...")); + Create a file "ledmap.json" using the edit page. - if (!readObjectFromFile("/ledmap.json", nullptr, &doc)) return; //if file does not exist just exit - - if (customMappingTable != nullptr) { - delete[] customMappingTable; - customMappingTable = nullptr; - customMappingSize = 0; - } - - JsonArray map = doc[F("map")]; - if (!map.isNull() && map.size()) { // not an empty map - customMappingSize = map.size(); - customMappingTable = new uint16_t[customMappingSize]; - for (uint16_t i=0; i Date: Fri, 12 Feb 2021 16:21:13 -0800 Subject: [PATCH 8/8] DHT22/DHT11 humidity/temperature sensor usermod (#1719) * DHT22/DHT11 humidity/temperature sensor usermod * cleanup - don't report when usermod is auto-disabled since report isn't persistent * track error count; retry once after error occurs * for esp32, use esp32DHT library * fix unreliable ESP32 readings by switching DHT library to https://github.com/alwynallan/DHT_nonblocking * change default pin to Q2; don't burst readings if error occurs Co-authored-by: Aircoookie --- usermods/DHT/platformio_override.ini | 22 +++ usermods/DHT/readme.md | 41 +++++ usermods/DHT/usermod_dht.h | 216 +++++++++++++++++++++++++++ wled00/const.h | 1 + wled00/usermods_list.cpp | 8 +- 5 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 usermods/DHT/platformio_override.ini create mode 100644 usermods/DHT/readme.md create mode 100644 usermods/DHT/usermod_dht.h diff --git a/usermods/DHT/platformio_override.ini b/usermods/DHT/platformio_override.ini new file mode 100644 index 000000000..1771fd17a --- /dev/null +++ b/usermods/DHT/platformio_override.ini @@ -0,0 +1,22 @@ +; Options +; ------- +; USERMOD_DHT - define this to have this user mod included wled00\usermods_list.cpp +; USERMOD_DHT_DHTTYPE - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22 +; USERMOD_DHT_PIN - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board +; USERMOD_DHT_CELSIUS - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported +; USERMOD_DHT_MEASUREMENT_INTERVAL - the number of milliseconds between measurements, defaults to 60 seconds +; USERMOD_DHT_FIRST_MEASUREMENT_AT - the number of milliseconds after boot to take first measurement, defaults to 90 seconds +; USERMOD_DHT_STATS - For debug, report delay stats + +[env:d1_mini_usermod_dht_C] +extends = env:d1_mini +build_flags = ${env:d1_mini.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS +lib_deps = ${env.lib_deps} + https://github.com/alwynallan/DHT_nonblocking + +[env:custom32_LEDPIN_16_usermod_dht_C] +extends = env:custom32_LEDPIN_16 +build_flags = ${env:custom32_LEDPIN_16.build_flags} -D USERMOD_DHT -D USERMOD_DHT_CELSIUS -D USERMOD_DHT_STATS +lib_deps = ${env.lib_deps} + https://github.com/alwynallan/DHT_nonblocking + diff --git a/usermods/DHT/readme.md b/usermods/DHT/readme.md new file mode 100644 index 000000000..5a123d4bd --- /dev/null +++ b/usermods/DHT/readme.md @@ -0,0 +1,41 @@ +# DHT Temperature/Humidity sensor usermod + +This usermod will read from an attached DHT22 or DHT11 humidity and temperature sensor. +The sensor readings are displayed in the Info section of the web UI. + +If sensor is not detected after a while (10 update intervals), this usermod will be disabled. + +## Installation + +Copy the example `platformio_override.ini` to the root directory. This file should be placed in the same directory as `platformio.ini`. + +### Define Your Options + +* `USERMOD_DHT` - define this to have this user mod included wled00\usermods_list.cpp +* `USERMOD_DHT_DHTTYPE` - DHT model: 11, 21, 22 for DHT11, DHT21, or DHT22, defaults to 22/DHT22 +* `USERMOD_DHT_PIN` - pin to which DTH is connected, defaults to Q2 pin on QuinLed Dig-Uno's board +* `USERMOD_DHT_CELSIUS` - define this to report temperatures in degrees celsious, otherwise fahrenheit will be reported +* `USERMOD_DHT_MEASUREMENT_INTERVAL` - the number of milliseconds between measurements, defaults to 60 seconds +* `USERMOD_DHT_FIRST_MEASUREMENT_AT` - the number of milliseconds after boot to take first measurement, defaults to 90 seconds +* `USERMOD_DHT_STATS` - For debug, report delay stats + +## Project link + +* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link + +### PlatformIO requirements + +If you are using `platformio_override.ini`, you should be able to refresh the task list and see your custom task, for example `env:d1_mini_usermod_dht_C`. If not, you can add the libraries and dependencies into `platformio.ini` as you see fit. + + +## Change Log + +2020-02-04 +* Change default QuinLed pin to Q2 +* Instead of trying to keep updates at constant cadence, space readings out by measurement interval; hope this helps to avoid occasional bursts of readings with errors +* Add some more (optional) stats +2020-02-03 +* Due to poor readouts on ESP32 with previous DHT library, rewrote to use https://github.com/alwynallan/DHT_nonblocking +* The new library serializes/delays up to 5ms for the sensor readout +2020-02-02 +* Created diff --git a/usermods/DHT/usermod_dht.h b/usermods/DHT/usermod_dht.h new file mode 100644 index 000000000..9f46734f8 --- /dev/null +++ b/usermods/DHT/usermod_dht.h @@ -0,0 +1,216 @@ +#pragma once + +#include "wled.h" + +#include + +// USERMOD_DHT_DHTTYPE: +// 11 // DHT 11 +// 21 // DHT 21 +// 22 // DHT 22 (AM2302), AM2321 *** default +#ifndef USERMOD_DHT_DHTTYPE +#define USERMOD_DHT_DHTTYPE 22 +#endif + +#if USERMOD_DHT_DHTTYPE == 11 +#define DHTTYPE DHT_TYPE_11 +#elif USERMOD_DHT_DHTTYPE == 21 +#define DHTTYPE DHT_TYPE_21 +#elif USERMOD_DHT_DHTTYPE == 22 +#define DHTTYPE DHT_TYPE_22 +#endif + +// Connect pin 1 (on the left) of the sensor to +5V +// NOTE: If using a board with 3.3V logic like an Arduino Due connect pin 1 +// to 3.3V instead of 5V! +// Connect pin 2 of the sensor to whatever your DHTPIN is +// NOTE: Pin defaults below are for QuinLed Dig-Uno's Q2 on the board +// Connect pin 4 (on the right) of the sensor to GROUND +// NOTE: If using a bare sensor (AM*), Connect a 10K resistor from pin 2 +// (data) to pin 1 (power) of the sensor. DHT* boards have the pullup already + +#ifdef USERMOD_DHT_PIN +#define DHTPIN USERMOD_DHT_PIN +#else +#ifdef ARDUINO_ARCH_ESP32 +#define DHTPIN 21 +#else //ESP8266 boards +#define DHTPIN 4 +#endif +#endif + +// the frequency to check sensor, 1 minute +#ifndef USERMOD_DHT_MEASUREMENT_INTERVAL +#define USERMOD_DHT_MEASUREMENT_INTERVAL 60000 +#endif + +// how many seconds after boot to take first measurement, 90 seconds +// 90 gives enough time to OTA update firmware if this crashses +#ifndef USERMOD_DHT_FIRST_MEASUREMENT_AT +#define USERMOD_DHT_FIRST_MEASUREMENT_AT 90000 +#endif + +// from COOLDOWN_TIME in dht_nonblocking.cpp +#define DHT_TIMEOUT_TIME 10000 + +DHT_nonblocking dht_sensor(DHTPIN, DHTTYPE); + +class UsermodDHT : public Usermod { + private: + unsigned long nextReadTime = 0; + unsigned long lastReadTime = 0; + float humidity, temperature = 0; + bool initializing = true; + bool disabled = false; + #ifdef USERMOD_DHT_STATS + unsigned long nextResetStatsTime = 0; + uint16_t updates = 0; + uint16_t clean_updates = 0; + uint16_t errors = 0; + unsigned long maxDelay = 0; + unsigned long currentIteration = 0; + unsigned long maxIteration = 0; + #endif + + public: + void setup() { + nextReadTime = millis() + USERMOD_DHT_FIRST_MEASUREMENT_AT; + lastReadTime = millis(); + #ifdef USERMOD_DHT_STATS + nextResetStatsTime = millis() + 60*60*1000; + #endif + } + + void loop() { + if (disabled) { + return; + } + if (millis() < nextReadTime) { + return; + } + + #ifdef USERMOD_DHT_STATS + if (millis() >= nextResetStatsTime) { + nextResetStatsTime += 60*60*1000; + errors = 0; + updates = 0; + clean_updates = 0; + } + unsigned long dcalc = millis(); + if (currentIteration == 0) { + currentIteration = millis(); + } + #endif + + float tempC; + if (dht_sensor.measure(&tempC, &humidity)) { + #ifdef USERMOD_DHT_CELSIUS + temperature = tempC; + #else + temperature = tempC * 9 / 5 + 32; + #endif + + nextReadTime = millis() + USERMOD_DHT_MEASUREMENT_INTERVAL; + lastReadTime = millis(); + initializing = false; + + #ifdef USERMOD_DHT_STATS + unsigned long icalc = millis() - currentIteration; + if (icalc > maxIteration) { + maxIteration = icalc; + } + if (icalc > DHT_TIMEOUT_TIME) { + errors += icalc/DHT_TIMEOUT_TIME; + } else { + clean_updates += 1; + } + updates += 1; + currentIteration = 0; + + #endif + } + + #ifdef USERMOD_DHT_STATS + dcalc = millis() - dcalc; + if (dcalc > maxDelay) { + maxDelay = dcalc; + } + #endif + + if (((millis() - lastReadTime) > 10*USERMOD_DHT_MEASUREMENT_INTERVAL)) { + disabled = true; + } + } + + void addToJsonInfo(JsonObject& root) { + if (disabled) { + return; + } + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray temp = user.createNestedArray("Temperature"); + JsonArray hum = user.createNestedArray("Humidity"); + + #ifdef USERMOD_DHT_STATS + JsonArray next = user.createNestedArray("next"); + if (nextReadTime >= millis()) { + next.add((nextReadTime - millis()) / 1000); + next.add(" sec until read"); + } else { + next.add((millis() - nextReadTime) / 1000); + next.add(" sec active reading"); + } + + JsonArray last = user.createNestedArray("last"); + last.add((millis() - lastReadTime) / 60000); + last.add(" min since read"); + + JsonArray err = user.createNestedArray("errors"); + err.add(errors); + err.add(" Errors"); + + JsonArray upd = user.createNestedArray("updates"); + upd.add(updates); + upd.add(" Updates"); + + JsonArray cupd = user.createNestedArray("cleanUpdates"); + cupd.add(clean_updates); + cupd.add(" Updates"); + + JsonArray iter = user.createNestedArray("maxIter"); + iter.add(maxIteration); + iter.add(" ms"); + + JsonArray delay = user.createNestedArray("maxDelay"); + delay.add(maxDelay); + delay.add(" ms"); + #endif + + if (initializing) { + // if we haven't read the sensor yet, let the user know + // that we are still waiting for the first measurement + temp.add((nextReadTime - millis()) / 1000); + temp.add(" sec until read"); + hum.add((nextReadTime - millis()) / 1000); + hum.add(" sec until read"); + return; + } + + hum.add(humidity); + hum.add("%"); + + temp.add(temperature); + #ifdef USERMOD_DHT_CELSIUS + temp.add("°C"); + #else + temp.add("°F"); + #endif + } + + uint16_t getId() + { + return USERMOD_ID_DHT; + } + +}; diff --git a/wled00/const.h b/wled00/const.h index d2cf05a3f..409a4ce37 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -24,6 +24,7 @@ #define USERMOD_ID_FOUR_LINE_DISP 7 //Usermod "usermod_v2_four_line_display.h #define USERMOD_ID_ROTARY_ENC_UI 8 //Usermod "usermod_v2_rotary_encoder_ui.h" #define USERMOD_ID_AUTO_SAVE 9 //Usermod "usermod_v2_auto_save.h" +#define USERMOD_ID_DHT 10 //Usermod "usermod_dht.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index bfff2d770..cdd6fdfaf 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -31,6 +31,10 @@ #include "../usermods/usermod_v2_auto_save/usermod_v2_auto_save.h" #endif +#ifdef USERMOD_DHT +#include "../usermods/DHT/usermod_dht.h" +#endif + void registerUsermods() { /* @@ -49,7 +53,6 @@ void registerUsermods() #ifdef USERMOD_SENSORSTOMQTT usermods.add(new UserMod_SensorsToMQTT()); #endif - #ifdef USERMOD_FOUR_LINE_DISLAY usermods.add(new FourLineDisplayUsermod()); #endif @@ -59,4 +62,7 @@ void registerUsermods() #ifdef USERMOD_AUTO_SAVE usermods.add(new AutoSaveUsermod()); #endif +#ifdef USERMOD_DHT +usermods.add(new UsermodDHT()); +#endif } \ No newline at end of file