Update dev branch (resolve conflicts)

This commit is contained in:
cschwinne 2021-02-13 01:43:16 +01:00
commit aa0f4c9985
18 changed files with 1756 additions and 37 deletions

View File

@ -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

41
usermods/DHT/readme.md Normal file
View File

@ -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

216
usermods/DHT/usermod_dht.h Normal file
View File

@ -0,0 +1,216 @@
#pragma once
#include "wled.h"
#include <dht_nonblocking.h>
// 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;
}
};

View File

@ -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

View File

@ -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;
}
};

View File

@ -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

View File

@ -0,0 +1,530 @@
#pragma once
#include "wled.h"
#include <U8x8lib.h> // 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;
}
};

View File

@ -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

View File

@ -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

View File

@ -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!
};

View File

@ -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.
*/
@ -1991,7 +1983,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 +3136,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_tetrix(void) {
uint16_t dataSize = sizeof(tetris);
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
Tetris* drop = reinterpret_cast<Tetris*>(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>>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)
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); i<SEGLEN; i++) setPixelColor(i,i<int(drop->pos)+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

View File

@ -160,7 +160,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
@ -497,7 +497,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;
@ -721,7 +721,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),
@ -850,7 +850,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;
@ -881,7 +885,7 @@ 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",

View File

@ -27,27 +27,26 @@
#include "FX.h"
#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[] = {
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
#endif
#ifndef PWM_INDEX
#define PWM_INDEX 0
#endif
/*
Custom per-LED mapping has moved!
Create a file "ledmap.json" using the edit page.
this is just an example (30 LEDs). It will first set all even, then all uneven LEDs.
{"map":[
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.
{"map":[
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]
*/
//do not call this method from system context (network callback)
void WS2812FX::finalizeInit(bool supportWhite, uint16_t countPixels, bool skipFirst)
{
@ -68,6 +67,8 @@ void WS2812FX::finalizeInit(bool supportWhite, uint16_t countPixels, bool skipFi
BusConfig defCfg = BusConfig(TYPE_WS2812_RGB, defPin, 0, _lengthRaw, COL_ORDER_GRB);
busses.add(defCfg);
}
deserializeMap();
_segments[0].start = 0;
_segments[0].stop = _length;
@ -202,9 +203,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) {
busses.setPixelColor(indexSet + skip, col);
if (IS_MIRROR) { //set the corresponding mirrored pixel
@ -218,9 +217,8 @@ 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
uint32_t col = ((w << 24) | (r << 16) | (g << 8) | (b));
busses.setPixelColor(i + skip, col);
}
@ -499,9 +497,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;
@ -986,6 +982,31 @@ bool WS2812FX::segmentsAreIdentical(Segment* a, Segment* b)
}
//load custom mapping table from JSON file
void WS2812FX::deserializeMap(void) {
if (!WLED_FS.exists("/ledmap.json")) return;
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; i<customMappingSize; i++) {
customMappingTable[i] = (uint16_t) map[i];
}
}
}
//gamma 2.8 lookup table used for color correction
byte gammaT[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

View File

@ -27,6 +27,10 @@
#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"
#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

View File

@ -205,6 +205,7 @@ class UsermodManager {
void readFromConfig(JsonObject& obj);
bool add(Usermod* um);
Usermod* lookup(uint16_t mod_id);
byte getModCount();
};

View File

@ -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;

View File

@ -23,10 +23,24 @@
#include "usermod_v2_SensorsToMqtt.h"
#endif
// BME280 v2 usermod. Define "USERMOD_BME280" in my_config.h
#ifdef USERMOD_BME280
#include "../usermods/BME280_v2/usermod_bme280.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
#ifdef USERMOD_DHT
#include "../usermods/DHT/usermod_dht.h"
#endif
void registerUsermods()
{
@ -50,4 +64,20 @@ void registerUsermods()
#ifdef USERMOD_BME280
usermods.add(new UsermodBME280());
#endif
#endif
#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
#ifdef USERMOD_DHT
usermods.add(new UsermodDHT());
#endif
}

View File

@ -235,14 +235,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);
}