WLED/usermods/usermod_v2_rotary_encoder_u.../usermod_v2_rotary_encoder_u...

1178 wiersze
39 KiB
C++

#pragma once
#include "wled.h"
//
// Inspired by the original v2 usermods
// * usermod_v2_rotary_encoder_ui
//
// v2 usermod that provides a rotary encoder-based UI.
//
// This usermod allows you to control:
//
// * Brightness
// * Selected Effect
// * Effect Speed
// * Effect Intensity
// * Palette
//
// Change between modes by pressing a button.
//
// Dependencies
// * This Usermod works best coupled with
// FourLineDisplayUsermod.
//
// If FourLineDisplayUsermod is used the folowing options are also enabled
//
// * main color
// * saturation of main color
// * display network (long press buttion)
//
#ifdef USERMOD_MODE_SORT
#error "Usermod Mode Sort is no longer required. Remove -D USERMOD_MODE_SORT from platformio.ini"
#endif
#ifndef ENCODER_DT_PIN
#define ENCODER_DT_PIN 18
#endif
#ifndef ENCODER_CLK_PIN
#define ENCODER_CLK_PIN 5
#endif
#ifndef ENCODER_SW_PIN
#define ENCODER_SW_PIN 19
#endif
#ifndef ENCODER_MAX_DELAY_MS // max delay between polling encoder pins
#define ENCODER_MAX_DELAY_MS 8 // 8 milliseconds => max 120 change impulses in 1 second, for full turn of a 30/30 encoder (4 changes per segment, 30 segments for one turn)
#endif
#ifndef USERMOD_USE_PCF8574
#undef USE_PCF8574
#define USE_PCF8574 false
#else
#undef USE_PCF8574
#define USE_PCF8574 true
#endif
#ifndef PCF8574_ADDRESS
#define PCF8574_ADDRESS 0x20 // some may start at 0x38
#endif
#ifndef PCF8574_INT_PIN
#define PCF8574_INT_PIN -1 // GPIO connected to INT pin on PCF8574
#endif
// The last UI state, remove color and saturation option if display not active (too many options)
#ifdef USERMOD_FOUR_LINE_DISPLAY
#define LAST_UI_STATE 11
#else
#define LAST_UI_STATE 4
#endif
// Number of modes at the start of the list to not sort
#define MODE_SORT_SKIP_COUNT 1
// Which list is being sorted
static const char **listBeingSorted;
/**
* Modes and palettes are stored as strings that
* end in a quote character. Compare two of them.
* We are comparing directly within either
* JSON_mode_names or JSON_palette_names.
*/
static int re_qstringCmp(const void *ap, const void *bp) {
const char *a = listBeingSorted[*((byte *)ap)];
const char *b = listBeingSorted[*((byte *)bp)];
int i = 0;
do {
char aVal = pgm_read_byte_near(a + i);
if (aVal >= 97 && aVal <= 122) {
// Lowercase
aVal -= 32;
}
char bVal = pgm_read_byte_near(b + i);
if (bVal >= 97 && bVal <= 122) {
// Lowercase
bVal -= 32;
}
// Really we shouldn't ever get to '\0'
if (aVal == '"' || bVal == '"' || aVal == '\0' || bVal == '\0') {
// We're done. one is a substring of the other
// or something happenend and the quote didn't stop us.
if (aVal == bVal) {
// Same value, probably shouldn't happen
// with this dataset
return 0;
}
else if (aVal == '"' || aVal == '\0') {
return -1;
}
else {
return 1;
}
}
if (aVal == bVal) {
// Same characters. Move to the next.
i++;
continue;
}
// We're done
if (aVal < bVal) {
return -1;
}
else {
return 1;
}
} while (true);
// We shouldn't get here.
return 0;
}
static volatile uint8_t pcfPortData = 0; // port expander port state
static volatile uint8_t addrPcf8574 = PCF8574_ADDRESS; // has to be accessible in ISR
// Interrupt routine to read I2C rotary state
// if we are to use PCF8574 port expander we will need to rely on interrupts as polling I2C every 2ms
// is a waste of resources and causes 4LD to fail.
// in such case rely on ISR to read pin values and store them into static variable
static void IRAM_ATTR i2cReadingISR() {
Wire.requestFrom(addrPcf8574, 1U);
if (Wire.available()) {
pcfPortData = Wire.read();
}
}
class RotaryEncoderUIUsermod : public Usermod {
private:
const int8_t fadeAmount; // Amount to change every step (brightness)
unsigned long loopTime;
unsigned long buttonPressedTime;
unsigned long buttonWaitTime;
bool buttonPressedBefore;
bool buttonLongPressed;
int8_t pinA; // DT from encoder
int8_t pinB; // CLK from encoder
int8_t pinC; // SW from encoder
unsigned char select_state; // 0: brightness, 1: effect, 2: effect speed, ...
uint16_t currentHue1; // default boot color
byte currentSat1;
uint8_t currentCCT;
#ifdef USERMOD_FOUR_LINE_DISPLAY
FourLineDisplayUsermod *display;
#else
void* display;
#endif
// Pointers the start of the mode names within JSON_mode_names
const char **modes_qstrings;
// Array of mode indexes in alphabetical order.
byte *modes_alpha_indexes;
// Pointers the start of the palette names within JSON_palette_names
const char **palettes_qstrings;
// Array of palette indexes in alphabetical order.
byte *palettes_alpha_indexes;
struct { // reduce memory footprint
bool Enc_A : 1;
bool Enc_B : 1;
bool Enc_A_prev : 1;
};
bool currentEffectAndPaletteInitialized;
uint8_t effectCurrentIndex;
uint8_t effectPaletteIndex;
uint8_t knownMode;
uint8_t knownPalette;
byte presetHigh;
byte presetLow;
bool applyToAll;
bool initDone;
bool enabled;
bool usePcf8574;
int8_t pinIRQ;
// strings to reduce flash memory usage (used more than twice)
static const char _name[];
static const char _enabled[];
static const char _DT_pin[];
static const char _CLK_pin[];
static const char _SW_pin[];
static const char _presetHigh[];
static const char _presetLow[];
static const char _applyToAll[];
static const char _pcf8574[];
static const char _pcfAddress[];
static const char _pcfINTpin[];
/**
* readPin() - read rotary encoder pin value
*/
byte readPin(uint8_t pin);
/**
* Sort the modes and palettes to the index arrays
* modes_alpha_indexes and palettes_alpha_indexes.
*/
void sortModesAndPalettes();
byte *re_initIndexArray(int numModes);
/**
* Return an array of mode or palette names from the JSON string.
* They don't end in '\0', they end in '"'.
*/
const char **re_findModeStrings(const char json[], int numModes);
/**
* Sort either the modes or the palettes using quicksort.
*/
void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip);
public:
RotaryEncoderUIUsermod()
: fadeAmount(5)
, buttonPressedTime(0)
, buttonWaitTime(0)
, buttonPressedBefore(false)
, buttonLongPressed(false)
, pinA(ENCODER_DT_PIN)
, pinB(ENCODER_CLK_PIN)
, pinC(ENCODER_SW_PIN)
, select_state(0)
, currentHue1(16)
, currentSat1(255)
, currentCCT(128)
, display(nullptr)
, modes_qstrings(nullptr)
, modes_alpha_indexes(nullptr)
, palettes_qstrings(nullptr)
, palettes_alpha_indexes(nullptr)
, currentEffectAndPaletteInitialized(false)
, effectCurrentIndex(0)
, effectPaletteIndex(0)
, knownMode(0)
, knownPalette(0)
, presetHigh(0)
, presetLow(0)
, applyToAll(true)
, initDone(false)
, enabled(true)
, usePcf8574(USE_PCF8574)
, pinIRQ(PCF8574_INT_PIN)
{}
/*
* 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() override { return USERMOD_ID_ROTARY_ENC_UI; }
/**
* Enable/Disable the usermod
*/
inline void enable(bool enable) { if (!(pinA<0 || pinB<0 || pinC<0)) enabled = enable; }
/**
* Get usermod enabled/disabled state
*/
inline bool isEnabled() { return enabled; }
/**
* 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() override;
/**
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
//void connected();
/**
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop() override;
#ifndef WLED_DISABLE_MQTT
//bool onMqttMessage(char* topic, char* payload) override;
//void onMqttConnect(bool sessionPresent) override;
#endif
/**
* handleButton() can be used to override default button behaviour. Returning true
* will prevent button working in a default way.
* Replicating button.cpp
*/
//bool handleButton(uint8_t b) override;
/**
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
*/
//void addToJsonInfo(JsonObject &root) override;
/**
* 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) override;
/**
* 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) override;
/**
* provide the changeable values
*/
void addToConfig(JsonObject &root) override;
void appendConfigData() override;
/**
* restore the changeable values
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool readFromConfig(JsonObject &root) override;
// custom methods
void displayNetworkInfo();
void findCurrentEffectAndPalette();
bool changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph);
void lampUdated();
void changeBrightness(bool increase);
void changeEffect(bool increase);
void changeEffectSpeed(bool increase);
void changeEffectIntensity(bool increase);
void changeCustom(uint8_t par, bool increase);
void changePalette(bool increase);
void changeHue(bool increase);
void changeSat(bool increase);
void changePreset(bool increase);
void changeCCT(bool increase);
};
/**
* readPin() - read rotary encoder pin value
*/
byte RotaryEncoderUIUsermod::readPin(uint8_t pin) {
if (usePcf8574) {
if (pin >= 100) pin -= 100; // PCF I/O ports
return (pcfPortData>>pin) & 1;
} else {
return digitalRead(pin);
}
}
/**
* Sort the modes and palettes to the index arrays
* modes_alpha_indexes and palettes_alpha_indexes.
*/
void RotaryEncoderUIUsermod::sortModesAndPalettes() {
DEBUG_PRINT(F("Sorting modes: ")); DEBUG_PRINTLN(strip.getModeCount());
//modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount());
modes_qstrings = strip.getModeDataSrc();
modes_alpha_indexes = re_initIndexArray(strip.getModeCount());
re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT);
DEBUG_PRINT(F("Sorting palettes: ")); DEBUG_PRINT(strip.getPaletteCount()); DEBUG_PRINT('/'); DEBUG_PRINTLN(strip.customPalettes.size());
palettes_qstrings = re_findModeStrings(JSON_palette_names, strip.getPaletteCount());
palettes_alpha_indexes = re_initIndexArray(strip.getPaletteCount());
if (strip.customPalettes.size()) {
for (int i=0; i<strip.customPalettes.size(); i++) {
palettes_alpha_indexes[strip.getPaletteCount()-strip.customPalettes.size()+i] = 255-i;
palettes_qstrings[strip.getPaletteCount()-strip.customPalettes.size()+i] = PSTR("~Custom~");
}
}
// How many palette names start with '*' and should not be sorted?
// (Also skipping the first one, 'Default').
int skipPaletteCount = 1;
while (pgm_read_byte_near(palettes_qstrings[skipPaletteCount]) == '*') skipPaletteCount++;
re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount()-strip.customPalettes.size(), skipPaletteCount);
}
byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
for (byte i = 0; i < numModes; i++) {
indexes[i] = i;
}
return indexes;
}
/**
* Return an array of mode or palette names from the JSON string.
* They don't end in '\0', they end in '"'.
*/
const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int numModes) {
const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes);
uint8_t modeIndex = 0;
bool insideQuotes = false;
// advance past the mark for markLineNum that may exist.
char singleJsonSymbol;
// Find the mode name in JSON
bool complete = false;
for (size_t i = 0; i < strlen_P(json); i++) {
singleJsonSymbol = pgm_read_byte_near(json + i);
if (singleJsonSymbol == '\0') break;
switch (singleJsonSymbol) {
case '"':
insideQuotes = !insideQuotes;
if (insideQuotes) {
// We have a new mode or palette
modeStrings[modeIndex] = (char *)(json + i + 1);
}
break;
case '[':
break;
case ']':
if (!insideQuotes) complete = true;
break;
case ',':
if (!insideQuotes) modeIndex++;
default:
if (!insideQuotes) break;
}
if (complete) break;
}
return modeStrings;
}
/**
* Sort either the modes or the palettes using quicksort.
*/
void RotaryEncoderUIUsermod::re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) {
if (!modeNames) return;
listBeingSorted = modeNames;
qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp);
listBeingSorted = nullptr;
}
// public methods
/*
* 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 RotaryEncoderUIUsermod::setup()
{
DEBUG_PRINTLN(F("Usermod Rotary Encoder init."));
if (usePcf8574) {
if (i2c_sda < 0 || i2c_scl < 0 || pinA < 0 || pinB < 0 || pinC < 0) {
DEBUG_PRINTLN(F("I2C and/or PCF8574 pins unused, disabling."));
enabled = false;
return;
} else {
if (pinIRQ >= 0 && pinManager.allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) {
pinMode(pinIRQ, INPUT_PULLUP);
attachInterrupt(pinIRQ, i2cReadingISR, FALLING); // RISING, FALLING, CHANGE, ONLOW, ONHIGH
DEBUG_PRINTLN(F("Interrupt attached."));
} else {
DEBUG_PRINTLN(F("Unable to allocate interrupt pin, disabling."));
pinIRQ = -1;
enabled = false;
return;
}
}
} else {
PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } };
if (pinA<0 || pinB<0 || !pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) {
pinA = pinB = pinC = -1;
enabled = false;
return;
}
#ifndef USERMOD_ROTARY_ENCODER_GPIO
#define USERMOD_ROTARY_ENCODER_GPIO INPUT_PULLUP
#endif
pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO);
pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO);
if (pinC>=0) pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO);
}
loopTime = millis();
currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5;
if (!initDone) sortModesAndPalettes();
#ifdef USERMOD_FOUR_LINE_DISPLAY
// 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->setMarkLine(1, 0);
}
#endif
initDone = true;
Enc_A = readPin(pinA); // Read encoder pins
Enc_B = readPin(pinB);
Enc_A_prev = Enc_A;
}
/*
* 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 RotaryEncoderUIUsermod::loop()
{
if (!enabled) return;
unsigned long currentTime = millis(); // get the current elapsed time
if (strip.isUpdating() && ((currentTime - loopTime) < ENCODER_MAX_DELAY_MS)) return; // be nice, but not too nice
// Initialize effectCurrentIndex and effectPaletteIndex to
// current state. We do it here as (at least) effectCurrent
// is not yet initialized when setup is called.
if (!currentEffectAndPaletteInitialized) {
findCurrentEffectAndPalette();
}
if (modes_alpha_indexes[effectCurrentIndex] != effectCurrent || palettes_alpha_indexes[effectPaletteIndex] != effectPalette) {
DEBUG_PRINTLN(F("Current mode or palette changed."));
currentEffectAndPaletteInitialized = false;
}
if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz
{
bool buttonPressed = !readPin(pinC); //0=pressed, 1=released
if (buttonPressed) {
if (!buttonPressedBefore) buttonPressedTime = currentTime;
buttonPressedBefore = true;
if (currentTime-buttonPressedTime > 3000) {
if (!buttonLongPressed) displayNetworkInfo(); //long press for network info
buttonLongPressed = true;
}
} else if (!buttonPressed && buttonPressedBefore) {
bool doublePress = buttonWaitTime;
buttonWaitTime = 0;
if (!buttonLongPressed) {
if (doublePress) {
toggleOnOff();
lampUdated();
} else {
buttonWaitTime = currentTime;
}
}
buttonLongPressed = false;
buttonPressedBefore = false;
}
if (buttonWaitTime && currentTime-buttonWaitTime>350 && !buttonPressedBefore) { //same speed as in button.cpp
buttonWaitTime = 0;
char newState = select_state + 1;
bool changedState = false;
char lineBuffer[64];
do {
// find new state
switch (newState) {
case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break;
case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed
case 2: if (!extractModeSlider(effectCurrent, 1, lineBuffer, 63)) newState++; else changedState = true; break; // intensity
case 3: strcpy_P(lineBuffer, PSTR("Color Palette")); changedState = true; break;
case 4: strcpy_P(lineBuffer, PSTR("Effect")); changedState = true; break;
case 5: strcpy_P(lineBuffer, PSTR("Main Color")); changedState = true; break;
case 6: strcpy_P(lineBuffer, PSTR("Saturation")); changedState = true; break;
case 7:
if (!(strip.getSegment(applyToAll ? strip.getFirstSelectedSegId() : strip.getMainSegmentId()).getLightCapabilities() & 0x04)) newState++;
else { strcpy_P(lineBuffer, PSTR("CCT")); changedState = true; }
break;
case 8: if (presetHigh==0 || presetLow == 0) newState++; else { strcpy_P(lineBuffer, PSTR("Preset")); changedState = true; } break;
case 9:
case 10:
case 11: if (!extractModeSlider(effectCurrent, newState-7, lineBuffer, 63)) newState++; else changedState = true; break; // custom
}
if (newState > LAST_UI_STATE) newState = 0;
} while (!changedState);
if (display != nullptr) {
switch (newState) {
case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun
case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward
case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire
case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette
case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece
case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush
case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast
case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart
case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star
}
}
if (changedState) select_state = newState;
}
Enc_A = readPin(pinA); // Read encoder pins
Enc_B = readPin(pinB);
if ((Enc_A) && (!Enc_A_prev))
{ // A has gone from high to low
if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse
{ // B is high so clockwise
switch(select_state) {
case 0: changeBrightness(true); break;
case 1: changeEffectSpeed(true); break;
case 2: changeEffectIntensity(true); break;
case 3: changePalette(true); break;
case 4: changeEffect(true); break;
case 5: changeHue(true); break;
case 6: changeSat(true); break;
case 7: changeCCT(true); break;
case 8: changePreset(true); break;
case 9: changeCustom(1,true); break;
case 10: changeCustom(2,true); break;
case 11: changeCustom(3,true); break;
}
}
else if (Enc_B == HIGH)
{ // B is low so counter-clockwise
switch(select_state) {
case 0: changeBrightness(false); break;
case 1: changeEffectSpeed(false); break;
case 2: changeEffectIntensity(false); break;
case 3: changePalette(false); break;
case 4: changeEffect(false); break;
case 5: changeHue(false); break;
case 6: changeSat(false); break;
case 7: changeCCT(false); break;
case 8: changePreset(false); break;
case 9: changeCustom(1,false); break;
case 10: changeCustom(2,false); break;
case 11: changeCustom(3,false); break;
}
}
}
Enc_A_prev = Enc_A; // Store value of A for next time
loopTime = currentTime; // Updates loopTime
}
}
void RotaryEncoderUIUsermod::displayNetworkInfo() {
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->networkOverlay(PSTR("NETWORK INFO"), 10000);
#endif
}
void RotaryEncoderUIUsermod::findCurrentEffectAndPalette() {
DEBUG_PRINTLN(F("Finding current mode and palette."));
currentEffectAndPaletteInitialized = true;
effectCurrentIndex = 0;
for (int i = 0; i < strip.getModeCount(); i++) {
if (modes_alpha_indexes[i] == effectCurrent) {
effectCurrentIndex = i;
DEBUG_PRINTLN(F("Found current mode."));
break;
}
}
effectPaletteIndex = 0;
DEBUG_PRINTLN(effectPalette);
for (uint8_t i = 0; i < strip.getPaletteCount()+strip.customPalettes.size(); i++) {
if (palettes_alpha_indexes[i] == effectPalette) {
effectPaletteIndex = i;
DEBUG_PRINTLN(F("Found palette."));
break;
}
}
}
bool RotaryEncoderUIUsermod::changeState(const char *stateName, byte markedLine, byte markedCol, byte glyph) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display != nullptr) {
if (display->wakeDisplay()) {
// Throw away wake up input
display->redraw(true);
return false;
}
display->overlay(stateName, 750, glyph);
display->setMarkLine(markedLine, markedCol);
}
#endif
return true;
}
void RotaryEncoderUIUsermod::lampUdated() {
//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
//setValuesFromFirstSelectedSeg(); //to make transition work on main segment (should no longer be required)
stateUpdated(CALL_MODE_BUTTON);
updateInterfaces(CALL_MODE_BUTTON);
}
void RotaryEncoderUIUsermod::changeBrightness(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
//bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0);
if (bri < 40) bri = max(min((increase ? bri+fadeAmount/2 : bri-fadeAmount/2), 255), 0); // slower steps when brightness < 16%
else bri = max(min((increase ? bri+fadeAmount : bri-fadeAmount), 255), 0);
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateBrightness();
#endif
}
void RotaryEncoderUIUsermod::changeEffect(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
effectCurrentIndex = max(min((increase ? effectCurrentIndex+1 : effectCurrentIndex-1), strip.getModeCount()-1), 0);
effectCurrent = modes_alpha_indexes[effectCurrentIndex];
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.setMode(effectCurrent);
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.setMode(effectCurrent);
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->showCurrentEffectOrPalette(effectCurrent, JSON_mode_names, 3);
#endif
}
void RotaryEncoderUIUsermod::changeEffectSpeed(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
effectSpeed = max(min((increase ? effectSpeed+fadeAmount : effectSpeed-fadeAmount), 255), 0);
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.speed = effectSpeed;
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.speed = effectSpeed;
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateSpeed();
#endif
}
void RotaryEncoderUIUsermod::changeEffectIntensity(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
effectIntensity = max(min((increase ? effectIntensity+fadeAmount : effectIntensity-fadeAmount), 255), 0);
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.intensity = effectIntensity;
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.intensity = effectIntensity;
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->updateIntensity();
#endif
}
void RotaryEncoderUIUsermod::changeCustom(uint8_t par, bool increase) {
uint8_t val = 0;
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
stateChanged = true;
if (applyToAll) {
uint8_t id = strip.getFirstSelectedSegId();
Segment& sid = strip.getSegment(id);
switch (par) {
case 3: val = sid.custom3 = max(min((increase ? sid.custom3+fadeAmount : sid.custom3-fadeAmount), 255), 0); break;
case 2: val = sid.custom2 = max(min((increase ? sid.custom2+fadeAmount : sid.custom2-fadeAmount), 255), 0); break;
default: val = sid.custom1 = max(min((increase ? sid.custom1+fadeAmount : sid.custom1-fadeAmount), 255), 0); break;
}
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive() || i == id) continue;
switch (par) {
case 3: seg.custom3 = sid.custom3; break;
case 2: seg.custom2 = sid.custom2; break;
default: seg.custom1 = sid.custom1; break;
}
}
} else {
Segment& seg = strip.getMainSegment();
switch (par) {
case 3: val = seg.custom3 = max(min((increase ? seg.custom3+fadeAmount : seg.custom3-fadeAmount), 255), 0); break;
case 2: val = seg.custom2 = max(min((increase ? seg.custom2+fadeAmount : seg.custom2-fadeAmount), 255), 0); break;
default: val = seg.custom1 = max(min((increase ? seg.custom1+fadeAmount : seg.custom1-fadeAmount), 255), 0); break;
}
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
char lineBuffer[64];
sprintf(lineBuffer, "%d", val);
display->overlay(lineBuffer, 500, 10); // use star
#endif
}
void RotaryEncoderUIUsermod::changePalette(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
effectPaletteIndex = max(min((unsigned)(increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()+strip.customPalettes.size()-1), 0U);
effectPalette = palettes_alpha_indexes[effectPaletteIndex];
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.setPalette(effectPalette);
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.setPalette(effectPalette);
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
display->showCurrentEffectOrPalette(effectPalette, JSON_palette_names, 2);
#endif
}
void RotaryEncoderUIUsermod::changeHue(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
currentHue1 = max(min((increase ? currentHue1+fadeAmount : currentHue1-fadeAmount), 255), 0);
colorHStoRGB(currentHue1*256, currentSat1, col);
stateChanged = true;
if (applyToAll) {
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
char lineBuffer[64];
sprintf(lineBuffer, "%d", currentHue1);
display->overlay(lineBuffer, 500, 7); // use brush
#endif
}
void RotaryEncoderUIUsermod::changeSat(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0);
colorHStoRGB(currentHue1*256, currentSat1, col);
if (applyToAll) {
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
}
} else {
Segment& seg = strip.getSegment(strip.getMainSegmentId());
seg.colors[0] = RGBW32(col[0], col[1], col[2], col[3]);
}
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
char lineBuffer[64];
sprintf(lineBuffer, "%d", currentSat1);
display->overlay(lineBuffer, 500, 8); // use contrast
#endif
}
void RotaryEncoderUIUsermod::changePreset(bool increase) {
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
if (presetHigh && presetLow && presetHigh > presetLow) {
StaticJsonDocument<64> root;
char str[64];
sprintf_P(str, PSTR("%d~%d~%s"), presetLow, presetHigh, increase?"":"-");
root["ps"] = str;
deserializeState(root.as<JsonObject>(), CALL_MODE_BUTTON_PRESET);
/*
String apireq = F("win&PL=~");
if (!increase) apireq += '-';
apireq += F("&P1=");
apireq += presetLow;
apireq += F("&P2=");
apireq += presetHigh;
handleSet(nullptr, apireq, false);
*/
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
sprintf(str, "%d", currentPreset);
display->overlay(str, 500, 11); // use heart
#endif
}
}
void RotaryEncoderUIUsermod::changeCCT(bool increase){
#ifdef USERMOD_FOUR_LINE_DISPLAY
if (display && display->wakeDisplay()) {
display->redraw(true);
// Throw away wake up input
return;
}
display->updateRedrawTime();
#endif
currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0);
// if (applyToAll) {
for (byte i=0; i<strip.getSegmentsNum(); i++) {
Segment& seg = strip.getSegment(i);
if (!seg.isActive()) continue;
seg.setCCT(currentCCT);
}
// } else {
// Segment& seg = strip.getSegment(strip.getMainSegmentId());
// seg.setCCT(currentCCT, strip.getMainSegmentId());
// }
lampUdated();
#ifdef USERMOD_FOUR_LINE_DISPLAY
char lineBuffer[64];
sprintf(lineBuffer, "%d", currentCCT);
display->overlay(lineBuffer, 500, 10); // use star
#endif
}
/*
* 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 RotaryEncoderUIUsermod::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 RotaryEncoderUIUsermod::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 RotaryEncoderUIUsermod::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!"));
}
*/
/**
* addToConfig() (called from set.cpp) stores persistent properties to cfg.json
*/
void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) {
// we add JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname
top[FPSTR(_enabled)] = enabled;
top[FPSTR(_DT_pin)] = pinA;
top[FPSTR(_CLK_pin)] = pinB;
top[FPSTR(_SW_pin)] = pinC;
top[FPSTR(_presetLow)] = presetLow;
top[FPSTR(_presetHigh)] = presetHigh;
top[FPSTR(_applyToAll)] = applyToAll;
top[FPSTR(_pcf8574)] = usePcf8574;
top[FPSTR(_pcfAddress)] = addrPcf8574;
top[FPSTR(_pcfINTpin)] = pinIRQ;
DEBUG_PRINTLN(F("Rotary Encoder config saved."));
}
void RotaryEncoderUIUsermod::appendConfigData() {
oappend(SET_F("addInfo('Rotary-Encoder:PCF8574-address',1,'<i>(not hex!)</i>');"));
oappend(SET_F("d.extra.push({'Rotary-Encoder':{pin:[['P0',100],['P1',101],['P2',102],['P3',103],['P4',104],['P5',105],['P6',106],['P7',107]]}});"));
}
/**
* readFromConfig() is called before setup() to populate properties from values stored in cfg.json
*
* The function should return true if configuration was successfully loaded or false if there was no configuration.
*/
bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) {
// we look for JSON object: {"Rotary-Encoder":{"DT-pin":12,"CLK-pin":14,"SW-pin":13}}
JsonObject top = root[FPSTR(_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA;
int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB;
int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC;
int8_t newIRQpin = top[FPSTR(_pcfINTpin)] | pinIRQ;
bool oldPcf8574 = usePcf8574;
presetHigh = top[FPSTR(_presetHigh)] | presetHigh;
presetLow = top[FPSTR(_presetLow)] | presetLow;
presetHigh = MIN(250,MAX(0,presetHigh));
presetLow = MIN(250,MAX(0,presetLow));
enabled = top[FPSTR(_enabled)] | enabled;
applyToAll = top[FPSTR(_applyToAll)] | applyToAll;
usePcf8574 = top[FPSTR(_pcf8574)] | usePcf8574;
addrPcf8574 = top[FPSTR(_pcfAddress)] | addrPcf8574;
DEBUG_PRINT(FPSTR(_name));
if (!initDone) {
// first run: reading from cfg.json
pinA = newDTpin;
pinB = newCLKpin;
pinC = newSWpin;
DEBUG_PRINTLN(F(" config loaded."));
} else {
DEBUG_PRINTLN(F(" config (re)loaded."));
// changing parameters from settings page
if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin || pinIRQ!=newIRQpin) {
if (oldPcf8574) {
if (pinIRQ >= 0) {
detachInterrupt(pinIRQ);
pinManager.deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI);
DEBUG_PRINTLN(F("Deallocated old IRQ pin."));
}
pinIRQ = newIRQpin<100 ? newIRQpin : -1; // ignore PCF8574 pins
} else {
pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI);
pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI);
DEBUG_PRINTLN(F("Deallocated old pins."));
}
pinA = newDTpin;
pinB = newCLKpin;
pinC = newSWpin;
if (pinA<0 || pinB<0 || pinC<0) {
enabled = false;
return true;
}
setup();
}
}
// use "return !top["newestParameter"].isNull();" when updating Usermod with new features
return !top[FPSTR(_pcfINTpin)].isNull();
}
// strings to reduce flash memory usage (used more than twice)
const char RotaryEncoderUIUsermod::_name[] PROGMEM = "Rotary-Encoder";
const char RotaryEncoderUIUsermod::_enabled[] PROGMEM = "enabled";
const char RotaryEncoderUIUsermod::_DT_pin[] PROGMEM = "DT-pin";
const char RotaryEncoderUIUsermod::_CLK_pin[] PROGMEM = "CLK-pin";
const char RotaryEncoderUIUsermod::_SW_pin[] PROGMEM = "SW-pin";
const char RotaryEncoderUIUsermod::_presetHigh[] PROGMEM = "preset-high";
const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low";
const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg";
const char RotaryEncoderUIUsermod::_pcf8574[] PROGMEM = "use-PCF8574";
const char RotaryEncoderUIUsermod::_pcfAddress[] PROGMEM = "PCF8574-address";
const char RotaryEncoderUIUsermod::_pcfINTpin[] PROGMEM = "PCF8574-INT-pin";