Usermod Settings polishing/documentation (#2061)
* Testing new wrapper functions to read Usermod config * Usermod Settings polishing - remove getBoolFromJsonKey() (no longer needed), fix getValueFromJsonKey(element, destination, defaultvalue) - Update Usermod Settings html "number" field to use step="any", and make wider to make maximum values fully visible - step="any" allows viewing/submitting full 7/8-digit float values, and the arrow buttons step by 1 now, instead of .00001 (which wasn't good for integers or floats) - html wasn't generated/compressed yet * Update usermod_v2_example.h with more complete example and documentation for Usermod Settings - readFromConfig() has three options for how to load values from the config JSON, we need to pick one * Update/rename usermode_rotary_brightness_color, to be used as an example of more robust parsing Usermod Settings values * Update Usermod example, rename getValueFromJsonKey() to getJsonValue() - chose single readFromConfig() pattern - demonstrating 3-argument getJsonValue() - remove leftover printf in getJsonValue() Co-authored-by: Louis Beaudoin <louis@embedded-creations.com>
This commit is contained in:
parent
ec05215a5e
commit
8c44147a45
@ -23,12 +23,20 @@
|
|||||||
//class name. Use something descriptive and leave the ": public Usermod" part :)
|
//class name. Use something descriptive and leave the ": public Usermod" part :)
|
||||||
class MyExampleUsermod : public Usermod {
|
class MyExampleUsermod : public Usermod {
|
||||||
private:
|
private:
|
||||||
// sample usermod default value for variable (you can also use constructor)
|
|
||||||
int userVar0 = 42;
|
|
||||||
|
|
||||||
//Private class members. You can declare variables and functions only accessible to your usermod here
|
//Private class members. You can declare variables and functions only accessible to your usermod here
|
||||||
unsigned long lastTime = 0;
|
unsigned long lastTime = 0;
|
||||||
|
|
||||||
|
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||||
|
bool testBool = false;
|
||||||
|
unsigned long testULong = 42424242;
|
||||||
|
float testFloat = 42.42;
|
||||||
|
String testString = "Forty-Two";
|
||||||
|
|
||||||
|
// These config variables have defaults set inside readFromConfig()
|
||||||
|
int testInt;
|
||||||
|
long testLong;
|
||||||
|
int8_t testPins[2];
|
||||||
|
|
||||||
public:
|
public:
|
||||||
//Functions called by WLED
|
//Functions called by WLED
|
||||||
|
|
||||||
@ -118,40 +126,85 @@ class MyExampleUsermod : public Usermod {
|
|||||||
* It might cause the LEDs to stutter and will cause flash wear if called too often.
|
* 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!
|
* 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.
|
* addToConfig() will make your settings editable through the Usermod Settings page automatically.
|
||||||
* To make that work you still have to add the setting to the HTML, xml.cpp and set.cpp manually.
|
*
|
||||||
|
* Usermod Settings Overview:
|
||||||
|
* - Numeric values are treated as floats in the browser.
|
||||||
|
* - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float
|
||||||
|
* before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and
|
||||||
|
* doubles are not supported, numbers will be rounded to the nearest float value when being parsed.
|
||||||
|
* The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38.
|
||||||
|
* - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a
|
||||||
|
* C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod.
|
||||||
|
* Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type
|
||||||
|
* used in the Usermod when reading the value from ArduinoJson.
|
||||||
|
* - Pin values can be treated differently from an integer value by using the key name "pin"
|
||||||
|
* - "pin" can contain a single or array of integer values
|
||||||
|
* - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins
|
||||||
|
* - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin)
|
||||||
|
* - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used
|
||||||
|
*
|
||||||
|
* See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings
|
||||||
|
*
|
||||||
|
* If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work.
|
||||||
|
* You will have to add the setting to the HTML, xml.cpp and set.cpp manually.
|
||||||
|
* See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED
|
||||||
*
|
*
|
||||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||||
*/
|
*/
|
||||||
void addToConfig(JsonObject& root)
|
void addToConfig(JsonObject& root)
|
||||||
{
|
{
|
||||||
JsonObject top = root.createNestedObject("exampleUsermod");
|
JsonObject top = root.createNestedObject("exampleUsermod");
|
||||||
top["great"] = userVar0; //save this var persistently whenever settings are saved
|
top["great"] = userVar0; //save these vars persistently whenever settings are saved
|
||||||
|
top["testBool"] = testBool;
|
||||||
|
top["testInt"] = testInt;
|
||||||
|
top["testLong"] = testLong;
|
||||||
|
top["testULong"] = testULong;
|
||||||
|
top["testFloat"] = testFloat;
|
||||||
|
top["testString"] = testString;
|
||||||
|
JsonArray pinArray = top.createNestedArray("pin");
|
||||||
|
pinArray.add(testPins[0]);
|
||||||
|
pinArray.add(testPins[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* readFromConfig() can be used to read back the custom settings you added with addToConfig().
|
* 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)
|
* This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page)
|
||||||
*
|
*
|
||||||
* readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes),
|
* 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.
|
* 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 :)
|
* If you don't know what that is, don't fret. It most likely doesn't affect your use case :)
|
||||||
*
|
*
|
||||||
* Return true in case your config was complete, or false if you'd like WLED to save your defaults to disk
|
* Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings)
|
||||||
|
*
|
||||||
|
* getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present
|
||||||
|
* The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them
|
||||||
*
|
*
|
||||||
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
|
* This function is guaranteed to be called on boot, but could also be called every time settings are updated
|
||||||
*/
|
*/
|
||||||
bool readFromConfig(JsonObject& root)
|
bool readFromConfig(JsonObject& root)
|
||||||
{
|
{
|
||||||
//set defaults for variables when declaring the variable (class definition or constructor)
|
// default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor
|
||||||
|
// setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||||
|
|
||||||
JsonObject top = root["exampleUsermod"];
|
JsonObject top = root["exampleUsermod"];
|
||||||
if (!top.isNull()) return false;
|
|
||||||
|
|
||||||
userVar0 = top["great"] | userVar0;
|
bool configComplete = !top.isNull();
|
||||||
|
|
||||||
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
|
configComplete &= getJsonValue(top["great"], userVar0);
|
||||||
return true;
|
configComplete &= getJsonValue(top["testBool"], testBool);
|
||||||
|
configComplete &= getJsonValue(top["testULong"], testULong);
|
||||||
|
configComplete &= getJsonValue(top["testFloat"], testFloat);
|
||||||
|
configComplete &= getJsonValue(top["testString"], testString);
|
||||||
|
|
||||||
|
// A 3-argument getJsonValue() assigns the 3rd argument as a default value if the Json value is missing
|
||||||
|
configComplete &= getJsonValue(top["testInt"], testInt, 42);
|
||||||
|
configComplete &= getJsonValue(top["testLong"], testLong, -42424242);
|
||||||
|
configComplete &= getJsonValue(top["pin"][0], testPins[0], -1);
|
||||||
|
configComplete &= getJsonValue(top["pin"][1], testPins[1], -1);
|
||||||
|
|
||||||
|
return configComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
#include "wled.h"
|
|
||||||
/*
|
|
||||||
* This file allows you to add own functionality to WLED more easily
|
|
||||||
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
|
|
||||||
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h)
|
|
||||||
* bytes 2400+ are currently ununsed, but might be used for future wled features
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Rotary Encoder Example
|
|
||||||
** Use the Sparkfun Rotary Encoder to vary brightness of LED
|
|
||||||
**
|
|
||||||
** Sample the encoder at 500Hz using the millis() function
|
|
||||||
*/
|
|
||||||
|
|
||||||
int fadeAmount = 5; // how many points to fade the Neopixel with each step
|
|
||||||
unsigned long currentTime;
|
|
||||||
unsigned long loopTime;
|
|
||||||
const int pinA = D6; // DT from encoder
|
|
||||||
const int pinB = D7; // CLK from encoder
|
|
||||||
|
|
||||||
unsigned char Enc_A;
|
|
||||||
unsigned char Enc_B;
|
|
||||||
unsigned char Enc_A_prev = 0;
|
|
||||||
|
|
||||||
//gets called once at boot. Do all initialization that doesn't depend on network here
|
|
||||||
void userSetup() {
|
|
||||||
pinMode(pinA, INPUT_PULLUP);
|
|
||||||
pinMode(pinB, INPUT_PULLUP);
|
|
||||||
currentTime = millis();
|
|
||||||
loopTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
|
|
||||||
void userConnected() {
|
|
||||||
}
|
|
||||||
|
|
||||||
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
|
|
||||||
void userLoop() {
|
|
||||||
currentTime = millis(); // get the current elapsed time
|
|
||||||
if(currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
|
|
||||||
{
|
|
||||||
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
|
|
||||||
if(bri + fadeAmount <= 255) bri += fadeAmount; // increase the brightness, dont go over 255
|
|
||||||
|
|
||||||
} else if (Enc_B == LOW) { // B is low so counter-clockwise
|
|
||||||
if(bri - fadeAmount >= 0) bri -= fadeAmount; // decrease the brightness, dont go below 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Enc_A_prev = Enc_A; // Store value of A for next time
|
|
||||||
loopTime = currentTime; // Updates loopTime
|
|
||||||
|
|
||||||
//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(6);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,211 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "wled.h"
|
|
||||||
|
|
||||||
//v2 usermod that allows to change brightness and color using a rotary encoder,
|
|
||||||
//change between modes by pressing a button (many encoder have one included)
|
|
||||||
class RotaryEncoderSet : public Usermod
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
//Private class members. You can declare variables and functions only accessible to your usermod here
|
|
||||||
unsigned long lastTime = 0;
|
|
||||||
/*
|
|
||||||
** Rotary Encoder Example
|
|
||||||
** Use the Sparkfun Rotary Encoder to vary brightness of LED
|
|
||||||
**
|
|
||||||
** Sample the encoder at 500Hz using the millis() function
|
|
||||||
*/
|
|
||||||
|
|
||||||
int fadeAmount = 5; // how many points to fade the Neopixel with each step
|
|
||||||
unsigned long currentTime;
|
|
||||||
unsigned long loopTime;
|
|
||||||
const int pinA = 5; // DT from encoder
|
|
||||||
const int pinB = 18; // CLK from encoder
|
|
||||||
const int pinC = 23; // SW from encoder
|
|
||||||
unsigned char select_state = 0; // 0 = brightness 1 = color
|
|
||||||
unsigned char button_state = HIGH;
|
|
||||||
unsigned char prev_button_state = HIGH;
|
|
||||||
CRGB fastled_col;
|
|
||||||
CHSV prim_hsv;
|
|
||||||
int16_t new_val;
|
|
||||||
|
|
||||||
unsigned char Enc_A;
|
|
||||||
unsigned char Enc_B;
|
|
||||||
unsigned char Enc_A_prev = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
//Functions called by WLED
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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()
|
|
||||||
{
|
|
||||||
//Serial.println("Hello from my usermod!");
|
|
||||||
pinMode(pinA, INPUT_PULLUP);
|
|
||||||
pinMode(pinB, INPUT_PULLUP);
|
|
||||||
pinMode(pinC, INPUT_PULLUP);
|
|
||||||
currentTime = millis();
|
|
||||||
loopTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if (select_state == 1)
|
|
||||||
{
|
|
||||||
select_state = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
select_state = 1;
|
|
||||||
}
|
|
||||||
prev_button_state = button_state;
|
|
||||||
}
|
|
||||||
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
|
|
||||||
if (select_state == 0)
|
|
||||||
{
|
|
||||||
if (bri + fadeAmount <= 255)
|
|
||||||
bri += fadeAmount; // increase the brightness, dont go over 255
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fastled_col.red = col[0];
|
|
||||||
fastled_col.green = col[1];
|
|
||||||
fastled_col.blue = col[2];
|
|
||||||
prim_hsv = rgb2hsv_approximate(fastled_col);
|
|
||||||
new_val = (int16_t)prim_hsv.h + fadeAmount;
|
|
||||||
if (new_val > 255)
|
|
||||||
new_val -= 255; // roll-over if bigger than 255
|
|
||||||
if (new_val < 0)
|
|
||||||
new_val += 255; // roll-over if smaller than 0
|
|
||||||
prim_hsv.h = (byte)new_val;
|
|
||||||
hsv2rgb_rainbow(prim_hsv, fastled_col);
|
|
||||||
col[0] = fastled_col.red;
|
|
||||||
col[1] = fastled_col.green;
|
|
||||||
col[2] = fastled_col.blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Enc_B == LOW)
|
|
||||||
{ // B is low so counter-clockwise
|
|
||||||
if (select_state == 0)
|
|
||||||
{
|
|
||||||
if (bri - fadeAmount >= 0)
|
|
||||||
bri -= fadeAmount; // decrease the brightness, dont go below 0
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fastled_col.red = col[0];
|
|
||||||
fastled_col.green = col[1];
|
|
||||||
fastled_col.blue = col[2];
|
|
||||||
prim_hsv = rgb2hsv_approximate(fastled_col);
|
|
||||||
new_val = (int16_t)prim_hsv.h - fadeAmount;
|
|
||||||
if (new_val > 255)
|
|
||||||
new_val -= 255; // roll-over if bigger than 255
|
|
||||||
if (new_val < 0)
|
|
||||||
new_val += 255; // roll-over if smaller than 0
|
|
||||||
prim_hsv.h = (byte)new_val;
|
|
||||||
hsv2rgb_rainbow(prim_hsv, fastled_col);
|
|
||||||
col[0] = fastled_col.red;
|
|
||||||
col[1] = fastled_col.green;
|
|
||||||
col[2] = fastled_col.blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//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_BUTTON);
|
|
||||||
updateInterfaces()
|
|
||||||
}
|
|
||||||
Enc_A_prev = Enc_A; // Store value of A for next time
|
|
||||||
loopTime = currentTime; // Updates loopTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 0xABCD;
|
|
||||||
}
|
|
||||||
|
|
||||||
//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!
|
|
||||||
};
|
|
42
usermods/usermod_rotary_brightness_color/README.md
Normal file
42
usermods/usermod_rotary_brightness_color/README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# Rotary Encoder (Brightness and Color)
|
||||||
|
|
||||||
|
V2 usermod that allows changing brightness and color using a rotary encoder,
|
||||||
|
change between modes by pressing a button (many encoders have one included)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
define `USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR` e.g.
|
||||||
|
|
||||||
|
`#define USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR` in my_config.h
|
||||||
|
|
||||||
|
or add `-D USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR` to `build_flags` in platformio_override.ini
|
||||||
|
|
||||||
|
### Define Your Options
|
||||||
|
|
||||||
|
Open Usermod Settings in WLED to change settings:
|
||||||
|
|
||||||
|
`fadeAmount` - how many points to fade the Neopixel with each step of the rotary encoder (default 5)
|
||||||
|
`pin[3]` - pins to connect to the rotary encoder:
|
||||||
|
- `pin[0]` is pin A on your rotary encoder
|
||||||
|
- `pin[1]` is pin B on your rotary encoder
|
||||||
|
- `pin[2]` is the button on your rotary encoder (optional, set to -1 to disable the button and the rotary encoder will control brightness only)
|
||||||
|
|
||||||
|
### PlatformIO requirements
|
||||||
|
|
||||||
|
No special requirements.
|
||||||
|
|
||||||
|
## Change Log
|
||||||
|
|
||||||
|
2021-07
|
||||||
|
* Upgraded to work with the latest WLED code, and make settings configurable in Usermod Settings
|
@ -0,0 +1,189 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wled.h"
|
||||||
|
|
||||||
|
//v2 usermod that allows to change brightness and color using a rotary encoder,
|
||||||
|
//change between modes by pressing a button (many encoders have one included)
|
||||||
|
class RotaryEncoderBrightnessColor : public Usermod
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
//Private class members. You can declare variables and functions only accessible to your usermod here
|
||||||
|
unsigned long lastTime = 0;
|
||||||
|
unsigned long currentTime;
|
||||||
|
unsigned long loopTime;
|
||||||
|
|
||||||
|
unsigned char select_state = 0; // 0 = brightness 1 = color
|
||||||
|
unsigned char button_state = HIGH;
|
||||||
|
unsigned char prev_button_state = HIGH;
|
||||||
|
CRGB fastled_col;
|
||||||
|
CHSV prim_hsv;
|
||||||
|
int16_t new_val;
|
||||||
|
|
||||||
|
unsigned char Enc_A;
|
||||||
|
unsigned char Enc_B;
|
||||||
|
unsigned char Enc_A_prev = 0;
|
||||||
|
|
||||||
|
// private class memebers configurable by Usermod Settings (defaults set inside readFromConfig())
|
||||||
|
int8_t pins[3]; // pins[0] = DT from encoder, pins[1] = CLK from encoder, pins[2] = CLK from encoder (optional)
|
||||||
|
int fadeAmount; // how many points to fade the Neopixel with each step
|
||||||
|
|
||||||
|
public:
|
||||||
|
//Functions called by WLED
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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()
|
||||||
|
{
|
||||||
|
//Serial.println("Hello from my usermod!");
|
||||||
|
pinMode(pins[0], INPUT_PULLUP);
|
||||||
|
pinMode(pins[1], INPUT_PULLUP);
|
||||||
|
if(pins[2] >= 0) pinMode(pins[2], INPUT_PULLUP);
|
||||||
|
currentTime = millis();
|
||||||
|
loopTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
|
||||||
|
{
|
||||||
|
if(pins[2] >= 0) {
|
||||||
|
button_state = digitalRead(pins[2]);
|
||||||
|
if (prev_button_state != button_state)
|
||||||
|
{
|
||||||
|
if (button_state == LOW)
|
||||||
|
{
|
||||||
|
if (select_state == 1)
|
||||||
|
{
|
||||||
|
select_state = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
select_state = 1;
|
||||||
|
}
|
||||||
|
prev_button_state = button_state;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prev_button_state = button_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int Enc_A = digitalRead(pins[0]); // Read encoder pins
|
||||||
|
int Enc_B = digitalRead(pins[1]);
|
||||||
|
if ((!Enc_A) && (Enc_A_prev))
|
||||||
|
{ // A has gone from high to low
|
||||||
|
if (Enc_B == HIGH)
|
||||||
|
{ // B is high so clockwise
|
||||||
|
if (select_state == 0)
|
||||||
|
{
|
||||||
|
if (bri + fadeAmount <= 255)
|
||||||
|
bri += fadeAmount; // increase the brightness, dont go over 255
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fastled_col.red = col[0];
|
||||||
|
fastled_col.green = col[1];
|
||||||
|
fastled_col.blue = col[2];
|
||||||
|
prim_hsv = rgb2hsv_approximate(fastled_col);
|
||||||
|
new_val = (int16_t)prim_hsv.h + fadeAmount;
|
||||||
|
if (new_val > 255)
|
||||||
|
new_val -= 255; // roll-over if bigger than 255
|
||||||
|
if (new_val < 0)
|
||||||
|
new_val += 255; // roll-over if smaller than 0
|
||||||
|
prim_hsv.h = (byte)new_val;
|
||||||
|
hsv2rgb_rainbow(prim_hsv, fastled_col);
|
||||||
|
col[0] = fastled_col.red;
|
||||||
|
col[1] = fastled_col.green;
|
||||||
|
col[2] = fastled_col.blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Enc_B == LOW)
|
||||||
|
{ // B is low so counter-clockwise
|
||||||
|
if (select_state == 0)
|
||||||
|
{
|
||||||
|
if (bri - fadeAmount >= 0)
|
||||||
|
bri -= fadeAmount; // decrease the brightness, dont go below 0
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fastled_col.red = col[0];
|
||||||
|
fastled_col.green = col[1];
|
||||||
|
fastled_col.blue = col[2];
|
||||||
|
prim_hsv = rgb2hsv_approximate(fastled_col);
|
||||||
|
new_val = (int16_t)prim_hsv.h - fadeAmount;
|
||||||
|
if (new_val > 255)
|
||||||
|
new_val -= 255; // roll-over if bigger than 255
|
||||||
|
if (new_val < 0)
|
||||||
|
new_val += 255; // roll-over if smaller than 0
|
||||||
|
prim_hsv.h = (byte)new_val;
|
||||||
|
hsv2rgb_rainbow(prim_hsv, fastled_col);
|
||||||
|
col[0] = fastled_col.red;
|
||||||
|
col[1] = fastled_col.green;
|
||||||
|
col[2] = fastled_col.blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//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_BUTTON);
|
||||||
|
updateInterfaces(NOTIFIER_CALL_MODE_BUTTON);
|
||||||
|
}
|
||||||
|
Enc_A_prev = Enc_A; // Store value of A for next time
|
||||||
|
loopTime = currentTime; // Updates loopTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToConfig(JsonObject& root)
|
||||||
|
{
|
||||||
|
JsonObject top = root.createNestedObject("rotEncBrightness");
|
||||||
|
top["fadeAmount"] = fadeAmount;
|
||||||
|
JsonArray pinArray = top.createNestedArray("pin");
|
||||||
|
pinArray.add(pins[0]);
|
||||||
|
pinArray.add(pins[1]);
|
||||||
|
pinArray.add(pins[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This example uses a more robust method of checking for missing values in the config, and setting back to defaults:
|
||||||
|
* - The getJsonValue() function copies the value to the variable only if the key requested is present, returning false with no copy if the value isn't present
|
||||||
|
* - configComplete is used to return false if any value is missing, not just if the main object is missing
|
||||||
|
* - The defaults are loaded every time readFromConfig() is run, not just once after boot
|
||||||
|
*
|
||||||
|
* This ensures that missing values are added to the config, with their default values, in the rare but plauible cases of:
|
||||||
|
* - a single value being missing at boot, e.g. if the Usermod was upgraded and a new setting was added
|
||||||
|
* - a single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed)
|
||||||
|
*
|
||||||
|
* If configComplete is false, the default values are already set, and by returning false, WLED now knows it needs to save the defaults by calling addToConfig()
|
||||||
|
*/
|
||||||
|
bool readFromConfig(JsonObject& root)
|
||||||
|
{
|
||||||
|
// set defaults here, they will be set before setup() is called, and if any values parsed from ArduinoJson below are missing, the default will be used instead
|
||||||
|
fadeAmount = 5;
|
||||||
|
pins[0] = -1;
|
||||||
|
pins[1] = -1;
|
||||||
|
pins[2] = -1;
|
||||||
|
|
||||||
|
JsonObject top = root["rotEncBrightness"];
|
||||||
|
|
||||||
|
bool configComplete = !top.isNull();
|
||||||
|
configComplete &= getJsonValue(top["fadeAmount"], fadeAmount);
|
||||||
|
configComplete &= getJsonValue(top["pin"][0], pins[0]);
|
||||||
|
configComplete &= getJsonValue(top["pin"][1], pins[1]);
|
||||||
|
configComplete &= getJsonValue(top["pin"][2], pins[2]);
|
||||||
|
|
||||||
|
return configComplete;
|
||||||
|
}
|
||||||
|
};
|
@ -84,7 +84,7 @@
|
|||||||
c += ' max="39" min="-1" style="width:40px;"';
|
c += ' max="39" min="-1" style="width:40px;"';
|
||||||
t = "int";
|
t = "int";
|
||||||
} else {
|
} else {
|
||||||
c += ' step="0.00001" style="width:80px;"';
|
c += ' step="any" style="width:100px;"';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -32,6 +32,27 @@ bool deserializeConfigSec();
|
|||||||
void serializeConfig();
|
void serializeConfig();
|
||||||
void serializeConfigSec();
|
void serializeConfigSec();
|
||||||
|
|
||||||
|
template<typename DestType>
|
||||||
|
bool getJsonValue(const JsonVariant& element, DestType& destination) {
|
||||||
|
if (element.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination = element.as<DestType>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename DestType, typename DefaultType>
|
||||||
|
bool getJsonValue(const JsonVariant& element, DestType& destination, const DefaultType defaultValue) {
|
||||||
|
if(!getJsonValue(element, destination)) {
|
||||||
|
destination = defaultValue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//colors.cpp
|
//colors.cpp
|
||||||
void colorFromUint32(uint32_t in, bool secondary = false);
|
void colorFromUint32(uint32_t in, bool secondary = false);
|
||||||
void colorFromUint24(uint32_t in, bool secondary = false);
|
void colorFromUint24(uint32_t in, bool secondary = false);
|
||||||
@ -190,7 +211,7 @@ class Usermod {
|
|||||||
virtual void addToJsonInfo(JsonObject& obj) {}
|
virtual void addToJsonInfo(JsonObject& obj) {}
|
||||||
virtual void readFromJsonState(JsonObject& obj) {}
|
virtual void readFromJsonState(JsonObject& obj) {}
|
||||||
virtual void addToConfig(JsonObject& obj) {}
|
virtual void addToConfig(JsonObject& obj) {}
|
||||||
virtual bool readFromConfig(JsonObject& obj) { return true; } //Heads up! readFromConfig() now needs to return a bool
|
virtual bool readFromConfig(JsonObject& obj) { return true; } // Note as of 2021-06 readFromConfig() now needs to return a bool, see usermod_v2_example.h
|
||||||
virtual void onMqttConnect(bool sessionPresent) {}
|
virtual void onMqttConnect(bool sessionPresent) {}
|
||||||
virtual bool onMqttMessage(char* topic, char* payload) { return false; }
|
virtual bool onMqttMessage(char* topic, char* payload) { return false; }
|
||||||
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
|
virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;}
|
||||||
|
@ -74,6 +74,10 @@
|
|||||||
#include "../usermods/EleksTube_IPS/usermod_elekstube_ips.h"
|
#include "../usermods/EleksTube_IPS/usermod_elekstube_ips.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR
|
||||||
|
#include "../usermods/usermod_rotary_brightness_color/usermod_rotary_brightness_color.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
void registerUsermods()
|
void registerUsermods()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -143,4 +147,8 @@ void registerUsermods()
|
|||||||
#ifdef USERMOD_ELEKSTUBE_IPS
|
#ifdef USERMOD_ELEKSTUBE_IPS
|
||||||
usermods.add(new ElekstubeIPSUsermod());
|
usermods.add(new ElekstubeIPSUsermod());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USERMOD_ROTARY_ENCODER_BRIGHTNESS_COLOR
|
||||||
|
usermods.add(new RotaryEncoderBrightnessColor());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user