Merge remote-tracking branch 'upstream/main' into usermod-libs

This commit is contained in:
Will Miles 2025-01-31 02:51:56 +00:00
commit a5b972d87e
58 changed files with 2263 additions and 1358 deletions

View File

@ -24,29 +24,30 @@
// risk to running the build directly on the host.
// "runArgs": ["--privileged", "-v", "/dev/bus/usb:/dev/bus/usb", "--group-add", "dialout"],
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
},
"extensions": [
"ms-python.python",
"platformio.platformio-ide"
]
}
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"platformio.platformio-ide"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

View File

@ -37,4 +37,5 @@ jobs:
prerelease: true
body: ${{ steps.changelog.outputs.changelog }}
files: |
./*.bin
*.bin
*.bin.gz

View File

@ -173,7 +173,7 @@
- v0.15.0-b2
- WS2805 support (RGB + WW + CW, 600kbps)
- Unified PSRAM use
- NeoPixelBus v2.7.9
- NeoPixelBus v2.7.9 (for future WS2805 support)
- Ubiquitous PSRAM mode for all variants of ESP32
- SSD1309_64 I2C Support for FLD Usermod (#3836 by @THATDONFC)
- Palette cycling fix (add support for `{"seg":[{"pal":"X~Y~"}]}` or `{"seg":[{"pal":"X~Yr"}]}`)

View File

@ -0,0 +1,504 @@
/* esp8266_waveform imported from platform source code
Modified for WLED to work around a fault in the NMI handling,
which can result in the system locking up and hard WDT crashes.
Imported from https://github.com/esp8266/Arduino/blob/7e0d20e2b9034994f573a236364e0aef17fd66de/cores/esp8266/core_esp8266_waveform_phase.cpp
*/
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
Copyright (c) 2020 Dirk O. Kaar.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
clock cycle time, or an interval measured in clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "core_esp8266_waveform.h"
#include <Arduino.h>
#include "debug.h"
#include "ets_sys.h"
#include <atomic>
// ----- @willmmiles begin patch -----
// Linker magic
extern "C" void usePWMFixedNMI(void) {};
// NMI crash workaround
// Sometimes the NMI fails to return, stalling the CPU. When this happens,
// the next NMI gets a return address /inside the NMI handler function/.
// We work around this by caching the last NMI return address, and restoring
// the epc3 and eps3 registers to the previous values if the observed epc3
// happens to be pointing to the _NMILevelVector function.
extern "C" void _NMILevelVector();
extern "C" void _UserExceptionVector_1(); // the next function after _NMILevelVector
static inline IRAM_ATTR void nmiCrashWorkaround() {
static uintptr_t epc3_backup, eps3_backup;
uintptr_t epc3, eps3;
__asm__ __volatile__("rsr %0,epc3; rsr %1,eps3":"=a"(epc3),"=a" (eps3));
if ((epc3 < (uintptr_t) &_NMILevelVector) || (epc3 >= (uintptr_t) &_UserExceptionVector_1)) {
// Address is good; save backup
epc3_backup = epc3;
eps3_backup = eps3;
} else {
// Address is inside the NMI handler -- restore from backup
__asm__ __volatile__("wsr %0,epc3; wsr %1,eps3"::"a"(epc3_backup),"a"(eps3_backup));
}
}
// ----- @willmmiles end patch -----
// No-op calls to override the PWM implementation
extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; }
extern "C" IRAM_ATTR bool _stopPWM_weak(int pin) { (void) pin; return false; }
extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; }
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
// Maximum servicing time for any single IRQ
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
// The latency between in-ISR rearming of the timer and the earliest firing
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
// The SDK and hardware take some time to actually get to our NMI code
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
// for UPDATEPHASE, the NMI recomputes the target timings
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, UPDATEPHASE = 3, INIT = 4};
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins.
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
int32_t adjDutyCcys; // Temporary correction for next period
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
WaveformMode mode;
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
} Waveform;
namespace {
static struct {
Waveform pins[17]; // State of all possible pins
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
// toSetBits temporaries
// cheaper than packing them in every Waveform, since we permit only one use at a time
uint32_t phaseCcy; // positive phase offset ccy count
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
uint32_t(*timer1CB)() = nullptr;
bool timer1Running = false;
uint32_t nextEventCcy;
} waveform;
}
// Interrupt on/off control
static IRAM_ATTR void timer1Interrupt();
// Non-speed critical bits
#pragma GCC optimize ("Os")
static void initTimer() {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
waveform.timer1Running = true;
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
}
static void IRAM_ATTR deinitTimer() {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
waveform.timer1Running = false;
}
extern "C" {
// Set a callback. Pass in NULL to stop it
void setTimer1Callback_weak(uint32_t (*fn)()) {
waveform.timer1CB = fn;
std::atomic_thread_fence(std::memory_order_acq_rel);
if (!waveform.timer1Running && fn) {
initTimer();
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
deinitTimer();
}
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
uint32_t periodCcys = highCcys + lowCcys;
if (periodCcys < MAXIRQTICKSCCYS) {
if (!highCcys) {
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
}
else if (!lowCcys) {
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
}
}
// sanity checks, including mixed signed/unsigned arithmetic safety
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
static_cast<int32_t>(periodCcys) <= 0 ||
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
return false;
}
Waveform& wave = waveform.pins[pin];
wave.dutyCcys = highCcys;
wave.adjDutyCcys = 0;
wave.periodCcys = periodCcys;
wave.autoPwm = autoPwm;
waveform.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
waveform.phaseCcy = phaseOffsetCcys;
std::atomic_thread_fence(std::memory_order_acquire);
const uint32_t pinBit = 1UL << pin;
if (!(waveform.enabled & pinBit)) {
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
wave.mode = WaveformMode::INIT;
if (!wave.dutyCcys) {
// If initially at zero duty cycle, force GPIO off
if (pin == 16) {
GP16O = 0;
}
else {
GPOC = pinBit;
}
}
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
std::atomic_thread_fence(std::memory_order_release);
if (!waveform.timer1Running) {
initTimer();
}
else if (T1V > IRQLATENCYCCYS) {
// Must not interfere if Timer is due shortly
timer1_write(IRQLATENCYCCYS);
}
}
else {
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
std::atomic_thread_fence(std::memory_order_release);
if (runTimeCcys) {
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
wave.mode = WaveformMode::UPDATEEXPIRY;
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
} else if (alignPhase >= 0) {
// @willmmiles new feature
wave.mode = WaveformMode::UPDATEPHASE; // recalculate start
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
}
}
std::atomic_thread_fence(std::memory_order_acq_rel);
while (waveform.toSetBits) {
esp_yield(); // Wait for waveform to update
std::atomic_thread_fence(std::memory_order_acquire);
}
return true;
}
// Stops a waveform on a pin
IRAM_ATTR int stopWaveform_weak(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!waveform.timer1Running) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
std::atomic_thread_fence(std::memory_order_acquire);
const uint32_t pinBit = 1UL << pin;
if (waveform.enabled & pinBit) {
waveform.toDisableBits = 1UL << pin;
std::atomic_thread_fence(std::memory_order_release);
// Must not interfere if Timer is due shortly
if (T1V > IRQLATENCYCCYS) {
timer1_write(IRQLATENCYCCYS);
}
while (waveform.toDisableBits) {
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
std::atomic_thread_fence(std::memory_order_acquire);
}
}
if (!waveform.enabled && !waveform.timer1CB) {
deinitTimer();
}
return true;
}
};
// Speed critical bits
#pragma GCC optimize ("O2")
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
if (ISCPUFREQ160MHZ) {
return isCPU2X ? ccys : (ccys >> 1);
}
else {
return isCPU2X ? (ccys << 1) : ccys;
}
}
static IRAM_ATTR void timer1Interrupt() {
const uint32_t isrStartCcy = ESP.getCycleCount();
//int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
// ----- @willmmiles begin patch -----
nmiCrashWorkaround();
// ----- @willmmiles end patch -----
const bool isCPU2X = CPU2X & 1;
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
// Handle enable/disable requests from main app.
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
waveform.toDisableBits = 0;
}
if (waveform.toSetBits) {
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
Waveform& wave = waveform.pins[toSetPin];
switch (wave.mode) {
case WaveformMode::INIT:
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) {
wave.nextPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X);
}
else {
wave.nextPeriodCcy = waveform.nextEventCcy;
}
if (!wave.expiryCcy) {
wave.mode = WaveformMode::INFINITE;
break;
}
// fall through
case WaveformMode::UPDATEEXPIRY:
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
wave.mode = WaveformMode::EXPIRES;
break;
// @willmmiles new feature
case WaveformMode::UPDATEPHASE:
// in WaveformMode::UPDATEPHASE, we recalculate the targets
if ((waveform.alignPhase >= 0) && (waveform.enabled & (1UL << waveform.alignPhase))) {
// Compute phase shift to realign with target
auto const newPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X);
auto const period = scaleCcys(wave.periodCcys, isCPU2X);
auto shift = ((static_cast<int32_t> (newPeriodCcy - wave.nextPeriodCcy) + period/2) % period) - (period/2);
wave.nextPeriodCcy += static_cast<uint32_t>(shift);
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
}
default:
break;
}
waveform.toSetBits = 0;
}
// Exit the loop if the next event, if any, is sufficiently distant.
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
uint32_t busyPins = waveform.enabled;
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
uint32_t now = ESP.getCycleCount();
uint32_t isrNextEventCcy = now;
while (busyPins) {
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
waveform.nextEventCcy = isrNextEventCcy;
break;
}
isrNextEventCcy = waveform.nextEventCcy;
uint32_t loopPins = busyPins;
while (loopPins) {
const int pin = __builtin_ffsl(loopPins) - 1;
const uint32_t pinBit = 1UL << pin;
loopPins ^= pinBit;
Waveform& wave = waveform.pins[pin];
/* @willmmiles - wtf? We don't want to accumulate drift
if (clockDrift) {
wave.endDutyCcy += clockDrift;
wave.nextPeriodCcy += clockDrift;
wave.expiryCcy += clockDrift;
}
*/
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
if (WaveformMode::EXPIRES == wave.mode &&
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
// Disable any waveforms that are done
waveform.enabled ^= pinBit;
busyPins ^= pinBit;
}
else {
const int32_t overshootCcys = now - waveNextEventCcy;
if (overshootCcys >= 0) {
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
if (waveform.states & pinBit) {
// active configuration and forward are 100% duty
if (wave.periodCcys == wave.dutyCcys) {
wave.nextPeriodCcy += periodCcys;
wave.endDutyCcy = wave.nextPeriodCcy;
}
else {
if (wave.autoPwm) {
wave.adjDutyCcys += overshootCcys;
}
waveform.states ^= pinBit;
if (16 == pin) {
GP16O = 0;
}
else {
GPOC = pinBit;
}
}
waveNextEventCcy = wave.nextPeriodCcy;
}
else {
wave.nextPeriodCcy += periodCcys;
if (!wave.dutyCcys) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
else {
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
if (dutyCcys <= wave.adjDutyCcys) {
dutyCcys >>= 1;
wave.adjDutyCcys -= dutyCcys;
}
else if (wave.adjDutyCcys) {
dutyCcys -= wave.adjDutyCcys;
wave.adjDutyCcys = 0;
}
wave.endDutyCcy = now + dutyCcys;
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
waveform.states |= pinBit;
if (16 == pin) {
GP16O = 1;
}
else {
GPOS = pinBit;
}
}
waveNextEventCcy = wave.endDutyCcy;
}
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
waveNextEventCcy = wave.expiryCcy;
}
}
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
busyPins ^= pinBit;
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
waveform.nextEventCcy = waveNextEventCcy;
}
}
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
isrNextEventCcy = waveNextEventCcy;
}
}
now = ESP.getCycleCount();
}
//clockDrift = 0;
}
int32_t callbackCcys = 0;
if (waveform.timer1CB) {
callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X);
}
now = ESP.getCycleCount();
int32_t nextEventCcys = waveform.nextEventCcy - now;
// Account for unknown duration of timer1CB().
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
waveform.nextEventCcy = now + callbackCcys;
nextEventCcys = callbackCcys;
}
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
int32_t deltaIrqCcys = DELTAIRQCCYS;
int32_t irqLatencyCcys = IRQLATENCYCCYS;
if (isCPU2X) {
nextEventCcys >>= 1;
deltaIrqCcys >>= 1;
irqLatencyCcys >>= 1;
}
// Firing timer too soon, the NMI occurs before ISR has returned.
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
nextEventCcys = irqLatencyCcys;
}
else {
nextEventCcys -= deltaIrqCcys;
}
// Register access is fast and edge IRQ was configured before.
T1L = nextEventCcys;
}

View File

@ -1,717 +0,0 @@
/* esp8266_waveform imported from platform source code
Modified for WLED to work around a fault in the NMI handling,
which can result in the system locking up and hard WDT crashes.
Imported from https://github.com/esp8266/Arduino/blob/7e0d20e2b9034994f573a236364e0aef17fd66de/cores/esp8266/core_esp8266_waveform_pwm.cpp
*/
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1
is set to 1-shot mode and is always loaded with the time until the next
edge of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not
the timer. This allows for removing interrupt jitter and delay as the
counter always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not
TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <Arduino.h>
#include <coredecls.h>
#include "ets_sys.h"
#include "core_esp8266_waveform.h"
#include "user_interface.h"
extern "C" {
// Linker magic
void usePWMFixedNMI() {};
// Maximum delay between IRQs
#define MAXIRQUS (10000)
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
uint32_t timeLowCycles; //
uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
uint32_t desiredLowCycles; //
uint32_t lastEdge; // Cycle when this generator last changed
} Waveform;
class WVFState {
public:
Waveform waveform[17]; // State of all possible pins
uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
uint32_t waveformNewHigh = 0;
uint32_t waveformNewLow = 0;
uint32_t (*timer1CB)() = NULL;
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
// are generating. In the common case (1 PWM) these may be the same pin and
// we can avoid looking at the other pins.
uint16_t startPin = 0;
uint16_t endPin = 0;
};
static WVFState wvfState;
// Ensure everything is read/written to RAM
#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
// Non-speed critical bits
#pragma GCC optimize ("Os")
// Interrupt on/off control
static IRAM_ATTR void timer1Interrupt();
static bool timerRunning = false;
static __attribute__((noinline)) void initTimer() {
if (!timerRunning) {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
timerRunning = true;
timer1_write(microsecondsToClockCycles(10));
}
}
static IRAM_ATTR void forceTimerInterrupt() {
if (T1L > microsecondsToClockCycles(10)) {
T1L = microsecondsToClockCycles(10);
}
}
// PWM implementation using special purpose state machine
//
// Keep an ordered list of pins with the delta in cycles between each
// element, with a terminal entry making up the remainder of the PWM
// period. With this method sum(all deltas) == PWM period clock cycles.
//
// At t=0 set all pins high and set the timeout for the 1st edge.
// On interrupt, if we're at the last element reset to t=0 state
// Otherwise, clear that pin down and set delay for next element
// and so forth.
constexpr int maxPWMs = 8;
// PWM machine state
typedef struct PWMState {
uint32_t mask; // Bitmask of active pins
uint32_t cnt; // How many entries
uint32_t idx; // Where the state machine is along the list
uint8_t pin[maxPWMs + 1];
uint32_t delta[maxPWMs + 1];
uint32_t nextServiceCycle; // Clock cycle for next step
struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
} PWMState;
static PWMState pwmState;
static uint32_t _pwmFreq = 1000;
static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq;
// If there are no more scheduled activities, shut down Timer 1.
// Otherwise, do nothing.
static IRAM_ATTR void disableIdleTimer() {
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
timerRunning = false;
}
}
// Notify the NMI that a new PWM state is available through the mailbox.
// Wait for mailbox to be emptied (either busy or delay() as needed)
static IRAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
p->pwmUpdate = nullptr;
pwmState.pwmUpdate = p;
MEMBARRIER();
forceTimerInterrupt();
while (pwmState.pwmUpdate) {
if (idle) {
esp_yield();
}
MEMBARRIER();
}
}
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
// Called when analogWriteFreq() changed to update the PWM total period
//extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak));
void _setPWMFreq_weak(uint32_t freq) {
_pwmFreq = freq;
// Convert frequency into clock cycles
uint32_t cc = microsecondsToClockCycles(1000000UL) / freq;
// Simple static adjustment to bring period closer to requested due to overhead
// Empirically determined as a constant PWM delay and a function of the number of PWMs
#if F_CPU == 80000000
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110;
#else
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75;
#endif
if (cc == _pwmPeriod) {
return; // No change
}
_pwmPeriod = cc;
if (pwmState.cnt) {
PWMState p; // The working copy since we can't edit the one in use
p.mask = 0;
p.cnt = 0;
for (uint32_t i = 0; i < pwmState.cnt; i++) {
auto pin = pwmState.pin[i];
_addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
}
// Update and wait for mailbox to be emptied
initTimer();
_notifyPWM(&p, true);
disableIdleTimer();
}
}
/*
static void _setPWMFreq_bound(uint32_t freq) __attribute__((weakref("_setPWMFreq_weak")));
void _setPWMFreq(uint32_t freq) {
_setPWMFreq_bound(freq);
}
*/
// Helper routine to remove an entry from the state machine
// and clean up any marked-off entries
static void _cleanAndRemovePWM(PWMState *p, int pin) {
uint32_t leftover = 0;
uint32_t in, out;
for (in = 0, out = 0; in < p->cnt; in++) {
if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) {
p->pin[out] = p->pin[in];
p->delta[out] = p->delta[in] + leftover;
leftover = 0;
out++;
} else {
leftover += p->delta[in];
p->mask &= ~(1<<p->pin[in]);
}
}
p->cnt = out;
// Final pin is never used: p->pin[out] = 0xff;
p->delta[out] = p->delta[in] + leftover;
}
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
//extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak));
IRAM_ATTR bool _stopPWM_weak(uint8_t pin) {
if (!((1<<pin) & pwmState.mask)) {
return false; // Pin not actually active
}
PWMState p; // The working copy since we can't edit the one in use
p = pwmState;
// In _stopPWM we just clear the mask but keep everything else
// untouched to save IRAM. The main startPWM will handle cleanup.
p.mask &= ~(1<<pin);
if (!p.mask) {
// If all have been stopped, then turn PWM off completely
p.cnt = 0;
}
// Update and wait for mailbox to be emptied, no delay (could be in ISR)
_notifyPWM(&p, false);
// Possibly shut down the timer completely if we're done
disableIdleTimer();
return true;
}
/*
static bool _stopPWM_bound(uint8_t pin) __attribute__((weakref("_stopPWM_weak")));
IRAM_ATTR bool _stopPWM(uint8_t pin) {
return _stopPWM_bound(pin);
}
*/
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
// Stash the val and range so we can re-evaluate the fraction
// should the user change PWM frequency. This allows us to
// give as great a precision as possible. We know by construction
// that the waveform for this pin will be inactive so we can borrow
// memory from that structure.
wvfState.waveform[pin].desiredHighCycles = val; // Numerator == high
wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low
uint32_t cc = (_pwmPeriod * val) / range;
// Clip to sane values in the case we go from OK to not-OK when adjusting frequencies
if (cc == 0) {
cc = 1;
} else if (cc >= _pwmPeriod) {
cc = _pwmPeriod - 1;
}
if (p.cnt == 0) {
// Starting up from scratch, special case 1st element and PWM period
p.pin[0] = pin;
p.delta[0] = cc;
// Final pin is never used: p.pin[1] = 0xff;
p.delta[1] = _pwmPeriod - cc;
} else {
uint32_t ttl = 0;
uint32_t i;
// Skip along until we're at the spot to insert
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
ttl += p.delta[i];
}
// Shift everything out by one to make space for new edge
for (int32_t j = p.cnt; j >= (int)i; j--) {
p.pin[j + 1] = p.pin[j];
p.delta[j + 1] = p.delta[j];
}
int off = cc - ttl; // The delta from the last edge to the one we're inserting
p.pin[i] = pin;
p.delta[i] = off; // Add the delta to this new pin
p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
}
p.cnt++;
p.mask |= 1<<pin;
}
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
//extern bool _setPWM_weak(int pin, uint32_t val, uint32_t range) __attribute__((weak));
bool _setPWM_weak(int pin, uint32_t val, uint32_t range) {
stopWaveform(pin);
PWMState p; // Working copy
p = pwmState;
// Get rid of any entries for this pin
_cleanAndRemovePWM(&p, pin);
// And add it to the list, in order
if (p.cnt >= maxPWMs) {
return false; // No space left
}
// Sanity check for all-on/off
uint32_t cc = (_pwmPeriod * val) / range;
if ((cc == 0) || (cc >= _pwmPeriod)) {
digitalWrite(pin, cc ? HIGH : LOW);
return true;
}
_addPWMtoList(p, pin, val, range);
// Set mailbox and wait for ISR to copy it over
initTimer();
_notifyPWM(&p, true);
disableIdleTimer();
// Potentially recalculate the PWM period if we've added another pin
_setPWMFreq(_pwmFreq);
return true;
}
/*
static bool _setPWM_bound(int pin, uint32_t val, uint32_t range) __attribute__((weakref("_setPWM_weak")));
bool _setPWM(int pin, uint32_t val, uint32_t range) {
return _setPWM_bound(pin, val, range);
}
*/
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
//extern int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weak));
int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles,
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
(void) alignPhase;
(void) phaseOffsetUS;
(void) autoPwm;
if ((pin > 16) || isFlashInterfacePin(pin) || (timeHighCycles == 0)) {
return false;
}
Waveform *wave = &wvfState.waveform[pin];
wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0;
if (runTimeCycles && !wave->expiryCycle) {
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
}
_stopPWM(pin); // Make sure there's no PWM live here
uint32_t mask = 1<<pin;
MEMBARRIER();
if (wvfState.waveformEnabled & mask) {
// Make sure no waveform changes are waiting to be applied
while (wvfState.waveformToChange) {
esp_yield(); // Wait for waveform to update
MEMBARRIER();
}
wvfState.waveformNewHigh = timeHighCycles;
wvfState.waveformNewLow = timeLowCycles;
MEMBARRIER();
wvfState.waveformToChange = mask;
// The waveform will be updated some time in the future on the next period for the signal
} else { // if (!(wvfState.waveformEnabled & mask)) {
wave->timeHighCycles = timeHighCycles;
wave->desiredHighCycles = timeHighCycles;
wave->timeLowCycles = timeLowCycles;
wave->desiredLowCycles = timeLowCycles;
wave->lastEdge = 0;
wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
wvfState.waveformToEnable |= mask;
MEMBARRIER();
initTimer();
forceTimerInterrupt();
while (wvfState.waveformToEnable) {
esp_yield(); // Wait for waveform to update
MEMBARRIER();
}
}
return true;
}
/*
static int startWaveformClockCycles_bound(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weakref("startWaveformClockCycles_weak")));
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
return startWaveformClockCycles_bound(pin, timeHighCycles, timeLowCycles, runTimeCycles, alignPhase, phaseOffsetUS, autoPwm);
}
// This version falls-thru to the proper startWaveformClockCycles call and is invariant across waveform generators
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS,
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
return startWaveformClockCycles_bound(pin,
microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS),
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
}
*/
// Set a callback. Pass in NULL to stop it
//extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak));
void setTimer1Callback_weak(uint32_t (*fn)()) {
wvfState.timer1CB = fn;
if (fn) {
initTimer();
forceTimerInterrupt();
}
disableIdleTimer();
}
/*
static void setTimer1Callback_bound(uint32_t (*fn)()) __attribute__((weakref("setTimer1Callback_weak")));
void setTimer1Callback(uint32_t (*fn)()) {
setTimer1Callback_bound(fn);
}
*/
// Stops a waveform on a pin
//extern int stopWaveform_weak(uint8_t pin) __attribute__((weak));
IRAM_ATTR int stopWaveform_weak(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!timerRunning) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
uint32_t mask = 1<<pin;
if (wvfState.waveformEnabled & mask) {
wvfState.waveformToDisable = mask;
// Cancel any pending updates for this waveform, too.
if (wvfState.waveformToChange & mask) {
wvfState.waveformToChange = 0;
}
forceTimerInterrupt();
while (wvfState.waveformToDisable) {
MEMBARRIER(); // If it wasn't written yet, it has to be by now
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
}
}
disableIdleTimer();
return true;
}
/*
static int stopWaveform_bound(uint8_t pin) __attribute__((weakref("stopWaveform_weak")));
IRAM_ATTR int stopWaveform(uint8_t pin) {
return stopWaveform_bound(pin);
}
*/
// Speed critical bits
#pragma GCC optimize ("O2")
// Normally would not want two copies like this, but due to different
// optimization levels the inline attribute gets lost if we try the
// other version.
static inline IRAM_ATTR uint32_t GetCycleCountIRQ() {
uint32_t ccount;
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
return ccount;
}
// Find the earliest cycle as compared to right now
static inline IRAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
uint32_t now = GetCycleCountIRQ();
int32_t da = a - now;
int32_t db = b - now;
return (da < db) ? a : b;
}
// ----- @willmmiles begin patch -----
// NMI crash workaround
// Sometimes the NMI fails to return, stalling the CPU. When this happens,
// the next NMI gets a return address /inside the NMI handler function/.
// We work around this by caching the last NMI return address, and restoring
// the epc3 and eps3 registers to the previous values if the observed epc3
// happens to be pointing to the _NMILevelVector function.
extern void _NMILevelVector();
extern void _UserExceptionVector_1(); // the next function after _NMILevelVector
static inline IRAM_ATTR void nmiCrashWorkaround() {
static uintptr_t epc3_backup, eps3_backup;
uintptr_t epc3, eps3;
__asm__ __volatile__("rsr %0,epc3; rsr %1,eps3":"=a"(epc3),"=a" (eps3));
if ((epc3 < (uintptr_t) &_NMILevelVector) || (epc3 >= (uintptr_t) &_UserExceptionVector_1)) {
// Address is good; save backup
epc3_backup = epc3;
eps3_backup = eps3;
} else {
// Address is inside the NMI handler -- restore from backup
__asm__ __volatile__("wsr %0,epc3; wsr %1,eps3"::"a"(epc3_backup),"a"(eps3_backup));
}
}
// ----- @willmmiles end patch -----
// The SDK and hardware take some time to actually get to our NMI code, so
// decrement the next IRQ's timer value by a bit so we can actually catch the
// real CPU cycle counter we want for the waveforms.
// The SDK also sometimes is running at a different speed the the Arduino core
// so the ESP cycle counter is actually running at a variable speed.
// adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
#if F_CPU == 80000000
#define DELTAIRQ (microsecondsToClockCycles(9)/4)
#define adjust(x) ((x) << (turbo ? 1 : 0))
#else
#define DELTAIRQ (microsecondsToClockCycles(9)/8)
#define adjust(x) ((x) >> 0)
#endif
// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
#define MINIRQTIME microsecondsToClockCycles(6)
static IRAM_ATTR void timer1Interrupt() {
// ----- @willmmiles begin patch -----
nmiCrashWorkaround();
// ----- @willmmiles end patch -----
// Flag if the core is at 160 MHz, for use by adjust()
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
// Handle enable/disable requests from main app
wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
wvfState.waveformToEnable = 0;
wvfState.waveformToDisable = 0;
// No mem barrier. Globals must be written to RAM on ISR exit.
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
} else if (!pwmState.cnt && pwmState.pwmUpdate) {
// Start up the PWM generator by copying from the mailbox
pwmState.cnt = 1;
pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
// No need for mem barrier here. Global must be written by IRQ exit
}
bool done = false;
if (wvfState.waveformEnabled || pwmState.cnt) {
do {
nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
// PWM state machine implementation
if (pwmState.cnt) {
int32_t cyclesToGo;
do {
cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
if (cyclesToGo < 0) {
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
if (pwmState.pwmUpdate) {
// Do the memory copy from temp to global and clear mailbox
pwmState = *(PWMState*)pwmState.pwmUpdate;
}
GPOS = pwmState.mask; // Set all active pins high
if (pwmState.mask & (1<<16)) {
GP16O = 1;
}
pwmState.idx = 0;
} else {
do {
// Drop the pin at this edge
if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) {
GPOC = 1<<pwmState.pin[pwmState.idx];
if (pwmState.pin[pwmState.idx] == 16) {
GP16O = 0;
}
}
pwmState.idx++;
// Any other pins at this same PWM value will have delta==0, drop them too.
} while (pwmState.delta[pwmState.idx] == 0);
}
// Preserve duty cycle over PWM period by using now+xxx instead of += delta
cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
pwmState.nextServiceCycle = GetCycleCountIRQ() + cyclesToGo;
}
nextEventCycle = earliest(nextEventCycle, pwmState.nextServiceCycle);
} while (pwmState.cnt && (cyclesToGo < 100));
}
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
uint32_t mask = 1<<i;
// If it's not on, ignore!
if (!(wvfState.waveformEnabled & mask)) {
continue;
}
Waveform *wave = &wvfState.waveform[i];
uint32_t now = GetCycleCountIRQ();
// Disable any waveforms that are done
if (wave->expiryCycle) {
int32_t expiryToGo = wave->expiryCycle - now;
if (expiryToGo < 0) {
// Done, remove!
if (i == 16) {
GP16O = 0;
}
GPOC = mask;
wvfState.waveformEnabled &= ~mask;
continue;
}
}
// Check for toggles
int32_t cyclesToGo = wave->nextServiceCycle - now;
if (cyclesToGo < 0) {
uint32_t nextEdgeCycles;
uint32_t desired = 0;
uint32_t *timeToUpdate;
wvfState.waveformState ^= mask;
if (wvfState.waveformState & mask) {
if (i == 16) {
GP16O = 1;
}
GPOS = mask;
if (wvfState.waveformToChange & mask) {
// Copy over next full-cycle timings
wave->timeHighCycles = wvfState.waveformNewHigh;
wave->desiredHighCycles = wvfState.waveformNewHigh;
wave->timeLowCycles = wvfState.waveformNewLow;
wave->desiredLowCycles = wvfState.waveformNewLow;
wave->lastEdge = 0;
wvfState.waveformToChange = 0;
}
if (wave->lastEdge) {
desired = wave->desiredLowCycles;
timeToUpdate = &wave->timeLowCycles;
}
nextEdgeCycles = wave->timeHighCycles;
} else {
if (i == 16) {
GP16O = 0;
}
GPOC = mask;
desired = wave->desiredHighCycles;
timeToUpdate = &wave->timeHighCycles;
nextEdgeCycles = wave->timeLowCycles;
}
if (desired) {
desired = adjust(desired);
int32_t err = desired - (now - wave->lastEdge);
if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
err /= 2;
*timeToUpdate += err;
}
}
nextEdgeCycles = adjust(nextEdgeCycles);
wave->nextServiceCycle = now + nextEdgeCycles;
wave->lastEdge = now;
}
nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle);
}
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
uint32_t now = GetCycleCountIRQ();
int32_t cycleDeltaNextEvent = nextEventCycle - now;
int32_t cyclesLeftTimeout = timeoutCycle - now;
done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0);
} while (!done);
} // if (wvfState.waveformEnabled)
if (wvfState.timer1CB) {
nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB());
}
int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ();
if (nextEventCycles < MINIRQTIME) {
nextEventCycles = MINIRQTIME;
}
nextEventCycles -= DELTAIRQ;
// Do it here instead of global function to save time and because we know it's edge-IRQ
T1L = nextEventCycles >> (turbo ? 1 : 0);
}
};

View File

@ -141,7 +141,7 @@ lib_deps =
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.8.0
#https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0
# for I2C interface
;Wire
# ESP-NOW library
@ -229,8 +229,16 @@ lib_deps_compat =
IRremoteESP8266 @ 2.8.2
makuna/NeoPixelBus @ 2.7.9
https://github.com/blazoncek/QuickESPNow.git#optional-debug
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1
https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0
[esp32_all_variants]
lib_deps =
willmmiles/AsyncTCP @ 1.3.1
bitbank2/AnimatedGIF@^1.4.7
https://github.com/Aircoookie/GifDecoder#bc3af18
build_flags =
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D WLED_ENABLE_GIF
[esp32]
#platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip
@ -240,10 +248,11 @@ build_unflags = ${common.build_unflags}
build_flags = -g
-DARDUINO_ARCH_ESP32
#-DCONFIG_LITTLEFS_FOR_IDF_3_2
-D CONFIG_ASYNC_TCP_USE_WDT=0
#use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x
-D LOROL_LITTLEFS
; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
${esp32_all_variants.build_flags}
tiny_partitions = tools/WLED_ESP32_2MB_noOTA.csv
default_partitions = tools/WLED_ESP32_4MB_1MB_FS.csv
extended_partitions = tools/WLED_ESP32_4MB_700k_FS.csv
@ -252,7 +261,7 @@ large_partitions = tools/WLED_ESP32_8MB.csv
extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv
lib_deps =
https://github.com/lorol/LITTLEFS.git
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${esp32_all_variants.lib_deps}
${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
# additional build flags for audioreactive - must be applied globally
@ -273,10 +282,12 @@ build_unflags = ${common.build_unflags}
build_flags = -g
-Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one
-DARDUINO_ARCH_ESP32 -DESP32
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3
${esp32_all_variants.build_flags}
-D WLED_ENABLE_DMX_INPUT
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${esp32_all_variants.lib_deps}
https://github.com/someweisguy/esp_dmx.git#47db25d
${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
@ -288,14 +299,14 @@ build_flags = -g
-DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32S2
-DCONFIG_IDF_TARGET_ESP32S2=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0
-DCO
-DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 !
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_CDC_ON_BOOT
${esp32_all_variants.build_flags}
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${esp32_all_variants.lib_deps}
${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
@ -307,15 +318,16 @@ build_flags = -g
-DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32C3
-DCONFIG_IDF_TARGET_ESP32C3=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DCO
-DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_CDC_ON_BOOT
${esp32_all_variants.build_flags}
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${esp32_all_variants.lib_deps}
${env.lib_deps}
board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs
board_build.flash_mode = qio
[esp32s3]
;; generic definitions for all ESP32-S3 boards
@ -326,13 +338,13 @@ build_flags = -g
-DARDUINO_ARCH_ESP32
-DARDUINO_ARCH_ESP32S3
-DCONFIG_IDF_TARGET_ESP32S3=1
-D CONFIG_ASYNC_TCP_USE_WDT=0
-DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0
-DCO
;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry:
;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT
${esp32_all_variants.build_flags}
lib_deps =
https://github.com/pbolduc/AsyncTCP.git @ 1.2.0
${esp32_all_variants.lib_deps}
${env.lib_deps}
board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs
@ -514,7 +526,6 @@ build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=
upload_speed = 460800
build_unflags = ${common.build_unflags}
lib_deps = ${esp32c3.lib_deps}
board_build.flash_mode = qio
[env:esp32s3dev_16MB_opi]
;; ESP32-S3 development board, with 16MB FLASH and >= 8MB PSRAM (memory_type: qio_opi)
@ -614,7 +625,6 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=
-DBOARD_HAS_PSRAM
-DLOLIN_WIFI_FIX ; seems to work much better with this
-D WLED_WATCHDOG_TIMEOUT=0
-D CONFIG_ASYNC_TCP_USE_WDT=0
-D DATA_PINS=16
-D HW_PIN_SCL=35
-D HW_PIN_SDA=33

View File

@ -529,3 +529,14 @@ monitor_filters = esp32_exception_decoder
lib_deps =
${esp32.lib_deps}
TFT_eSPI @ 2.5.33 ;; this is the last version that compiles with the WLED default framework - newer versions require platform = espressif32 @ ^6.3.2
# ------------------------------------------------------------------------------
# Usermod examples
# ------------------------------------------------------------------------------
# 433MHz RF remote example for esp32dev
[env:esp32dev_usermod_RF433]
extends = env:esp32dev
build_flags = ${env:esp32dev.build_flags} -D USERMOD_RF433
lib_deps = ${env:esp32dev.lib_deps}
sui77/rc-switch @ 2.6.4

View File

@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile

BIN
tools/AutoCubeMap.xlsx Normal file

Binary file not shown.

View File

@ -27,6 +27,7 @@ read -a JSON_TINY_TARGETS <<< $(replicate "json/nodes")
read -a JSON_SMALL_TARGETS <<< $(replicate "json/info")
read -a JSON_LARGE_TARGETS <<< $(replicate "json/si")
read -a JSON_LARGER_TARGETS <<< $(replicate "json/fxdata")
read -a INDEX_TARGETS <<< $(replicate "")
# Expand target URLS to full arguments for curl
TARGETS=(${TARGET_STR[@]})

View File

@ -9,7 +9,7 @@ Very loosely based on the existing usermod "seven segment display".
Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`.
For the auto brightness option, the usermod SN_Photoresistor has to be installed as well. See SN_Photoresistor/readme.md for instructions.
For the auto brightness option, the usermod SN_Photoresistor or BH1750_V2 has to be installed as well. See SN_Photoresistor/readme.md or BH1750_V2/readme.md for instructions.
## Settings
All settings can be controlled via the usermod settings page.
@ -28,10 +28,10 @@ Enables the blinking colon(s) if they are defined
Shows the leading zero of the hour if it exists (i.e. shows `07` instead of `7`)
### enable-auto-brightness
Enables the auto brightness feature. Can be used only when the usermod SN_Photoresistor is installed.
Enables the auto brightness feature. Can be used only when the usermods SN_Photoresistor or BH1750_V2 are installed.
### auto-brightness-min / auto-brightness-max
The lux value calculated from usermod SN_Photoresistor will be mapped to the values defined here.
The lux value calculated from usermod SN_Photoresistor or BH1750_V2 will be mapped to the values defined here.
The mapping, 0 - 1000 lux, will be mapped to auto-brightness-min and auto-brightness-max
WLED current protection will override the calculated value if it is too high.

View File

@ -95,6 +95,11 @@ private:
#else
void* ptr = nullptr;
#endif
#ifdef USERMOD_BH1750
Usermod_BH1750* bh1750 = nullptr;
#else
void* bh1750 = nullptr;
#endif
void _overlaySevenSegmentDraw() {
int displayMaskLen = static_cast<int>(umSSDRDisplayMask.length());
@ -385,6 +390,9 @@ public:
#ifdef USERMOD_SN_PHOTORESISTOR
ptr = (Usermod_SN_Photoresistor*) UsermodManager::lookup(USERMOD_ID_SN_PHOTORESISTOR);
#endif
#ifdef USERMOD_BH1750
bh1750 = (Usermod_BH1750*) UsermodManager::lookup(USERMOD_ID_BH1750);
#endif
DEBUG_PRINTLN(F("Setup done"));
}
@ -408,6 +416,20 @@ public:
umSSDRLastRefresh = millis();
}
#endif
#ifdef USERMOD_BH1750
if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) {
if (bh1750 != nullptr) {
float lux = bh1750->getIlluminance();
uint16_t brightness = map(lux, 0, 1000, umSSDRBrightnessMin, umSSDRBrightnessMax);
if (bri != brightness) {
DEBUG_PRINTF("Adjusting brightness based on lux value: %.2f lx, new brightness: %d\n", lux, brightness);
bri = brightness;
stateUpdated(1);
}
}
umSSDRLastRefresh = millis();
}
#endif
}
void handleOverlayDraw() {

View File

@ -1,111 +0,0 @@
/*
* 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 wled_eeprom.h)
* bytes 2400+ are currently ununsed, but might be used for future wled features
*/
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
byte wipeState = 0; //0: inactive 1: wiping 2: solid
unsigned long timeStaticStart = 0;
uint16_t previousUserVar0 = 0;
//comment this out if you want the turn off effect to be just fading out instead of reverse wipe
#define STAIRCASE_WIPE_OFF
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup()
{
//setup PIR sensor here, if needed
}
//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()
{
//userVar0 (U0 in HTTP API):
//has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266
//has to be set to 2 if movement is detected on the PIR that is the opposite side
//can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds)
if (userVar0 > 0)
{
if ((previousUserVar0 == 1 && userVar0 == 2) || (previousUserVar0 == 2 && userVar0 == 1)) wipeState = 3; //turn off if other PIR triggered
previousUserVar0 = userVar0;
if (wipeState == 0) {
startWipe();
wipeState = 1;
} else if (wipeState == 1) { //wiping
uint32_t cycleTime = 360 + (255 - effectSpeed)*75; //this is how long one wipe takes (minus 25 ms to make sure we switch in time)
if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete
effectCurrent = FX_MODE_STATIC;
timeStaticStart = millis();
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 2;
}
} else if (wipeState == 2) { //static
if (userVar1 > 0) //if U1 is not set, the light will stay on until second PIR or external command is triggered
{
if (millis() - timeStaticStart > userVar1*1000) wipeState = 3;
}
} else if (wipeState == 3) { //switch to wipe off
#ifdef STAIRCASE_WIPE_OFF
effectCurrent = FX_MODE_COLOR_WIPE;
strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 4;
#else
turnOff();
#endif
} else { //wiping off
if (millis() + strip.timebase > (725 + (255 - effectSpeed)*150)) turnOff(); //wipe complete
}
} else {
wipeState = 0; //reset for next time
if (previousUserVar0) {
#ifdef STAIRCASE_WIPE_OFF
userVar0 = previousUserVar0;
wipeState = 3;
#else
turnOff();
#endif
}
previousUserVar0 = 0;
}
}
void startWipe()
{
bri = briLast; //turn on
transitionDelayTemp = 0; //no transition
effectCurrent = FX_MODE_COLOR_WIPE;
strip.resetTimebase(); //make sure wipe starts from beginning
//set wipe direction
Segment& seg = strip.getSegment(0);
bool doReverse = (userVar0 == 2);
seg.setOption(1, doReverse);
colorUpdated(CALL_MODE_NOTIFICATION);
}
void turnOff()
{
#ifdef STAIRCASE_WIPE_OFF
transitionDelayTemp = 0; //turn off immediately after wipe completed
#else
transitionDelayTemp = 4000; //fade out slowly
#endif
bri = 0;
stateUpdated(CALL_MODE_NOTIFICATION);
wipeState = 0;
userVar0 = 0;
previousUserVar0 = 0;
}

View File

@ -0,0 +1,6 @@
{
"name:": "usermod_v2_RF433",
"dependencies": {
"sui77/rc-switch":"2.6.4"
}
}

View File

@ -0,0 +1,18 @@
# RF433 remote usermod
Usermod for controlling WLED using a generic 433 / 315MHz remote and simple 3-pin receiver
See <https://github.com/sui77/rc-switch/> for compatibility details
## Build
- Create a `platformio_override.ini` file at the root of the wled source directory if not already present
- Copy the `433MHz RF remote example for esp32dev` section from `platformio_override.sample.ini` into it
- Duplicate/adjust for other boards
## Usage
- Connect receiver to a free pin
- Set pin in Config->Usermods
- Info pane will show the last received button code
- Upload the remote433.json sample file in this folder to the ESP with the file editor at [http://\[wled-ip\]/edit](http://ip/edit)
- Edit as necessary, the key is the button number retrieved from the info pane, and the "cmd" can be either an [HTTP API](https://kno.wled.ge/interfaces/http-api/) or a [JSON API](https://kno.wled.ge/interfaces/json-api/) command.

View File

@ -0,0 +1,34 @@
{
"13985576": {
"cmnt": "Toggle Power using HTTP API",
"cmd": "T=2"
},
"3670817": {
"cmnt": "Force Power ON using HTTP API",
"cmd": "T=1"
},
"13985572": {
"cmnt": "Set brightness to 200 using JSON API",
"cmd": {"bri":200}
},
"3670818": {
"cmnt": "Run Preset 1 using JSON API",
"cmd": {"ps":1}
},
"13985570": {
"cmnt": "Increase brightness by 40 using HTTP API",
"cmd": "A=~40"
},
"13985569": {
"cmnt": "Decrease brightness by 40 using HTTP API",
"cmd": "A=~-40"
},
"7608836": {
"cmnt": "Start 1min timer using JSON API",
"cmd": {"nl":{"on":true,"dur":1,"mode":0}}
},
"7608840": {
"cmnt": "Select random effect on all segments using JSON API",
"cmd": {"seg":{"fx":"r"}}
}
}

View File

@ -0,0 +1,183 @@
#include "wled.h"
#include "Arduino.h"
#include <RCSwitch.h>
#define RF433_BUSWAIT_TIMEOUT 24
class RF433Usermod : public Usermod
{
private:
RCSwitch mySwitch = RCSwitch();
unsigned long lastCommand = 0;
unsigned long lastTime = 0;
bool modEnabled = true;
int8_t receivePin = -1;
static const char _modName[];
static const char _modEnabled[];
static const char _receivePin[];
bool initDone = false;
public:
void setup()
{
mySwitch.disableReceive();
if (modEnabled)
{
mySwitch.enableReceive(receivePin);
}
initDone = true;
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected()
{
}
void loop()
{
if (!modEnabled || strip.isUpdating())
return;
if (mySwitch.available())
{
unsigned long receivedCommand = mySwitch.getReceivedValue();
mySwitch.resetAvailable();
// Discard duplicates, limit long press repeat
if (lastCommand == receivedCommand && millis() - lastTime < 800)
return;
lastCommand = receivedCommand;
lastTime = millis();
DEBUG_PRINT(F("RF433 Receive: "));
DEBUG_PRINTLN(receivedCommand);
if(!remoteJson433(receivedCommand))
DEBUG_PRINTLN(F("RF433: unknown button"));
}
}
// Add last received button to info pane
void addToJsonInfo(JsonObject &root)
{
if (!initDone)
return; // prevent crash on boot applyPreset()
JsonObject user = root["u"];
if (user.isNull())
user = root.createNestedObject("u");
JsonArray switchArr = user.createNestedArray("RF433 Last Received"); // name
switchArr.add(lastCommand);
}
void addToConfig(JsonObject &root)
{
JsonObject top = root.createNestedObject(FPSTR(_modName)); // usermodname
top[FPSTR(_modEnabled)] = modEnabled;
JsonArray pinArray = top.createNestedArray("pin");
pinArray.add(receivePin);
DEBUG_PRINTLN(F(" config saved."));
}
bool readFromConfig(JsonObject &root)
{
JsonObject top = root[FPSTR(_modName)];
if (top.isNull())
{
DEBUG_PRINT(FPSTR(_modName));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
getJsonValue(top[FPSTR(_modEnabled)], modEnabled);
getJsonValue(top["pin"][0], receivePin);
DEBUG_PRINTLN(F("config (re)loaded."));
// Redo init on update
if(initDone)
setup();
return true;
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId()
{
return USERMOD_ID_RF433;
}
// this function follows the same principle as decodeIRJson() / remoteJson()
bool remoteJson433(int button)
{
char objKey[14];
bool parsed = false;
if (!requestJSONBufferLock(22)) return false;
sprintf_P(objKey, PSTR("\"%d\":"), button);
unsigned long start = millis();
while (strip.isUpdating() && millis()-start < RF433_BUSWAIT_TIMEOUT) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches
// attempt to read command from remote.json
readObjectFromFile(PSTR("/remote433.json"), objKey, pDoc);
JsonObject fdo = pDoc->as<JsonObject>();
if (fdo.isNull()) {
// the received button does not exist
releaseJSONBufferLock();
return parsed;
}
String cmdStr = fdo["cmd"].as<String>();
JsonObject jsonCmdObj = fdo["cmd"]; //object
if (jsonCmdObj.isNull()) // we could also use: fdo["cmd"].is<String>()
{
// HTTP API command
String apireq = "win"; apireq += '&'; // reduce flash string usage
if (!cmdStr.startsWith(apireq)) cmdStr = apireq + cmdStr; // if no "win&" prefix
if (!irApplyToAllSelected && cmdStr.indexOf(F("SS="))<0) {
char tmp[10];
sprintf_P(tmp, PSTR("&SS=%d"), strip.getMainSegmentId());
cmdStr += tmp;
}
fdo.clear(); // clear JSON buffer (it is no longer needed)
handleSet(nullptr, cmdStr, false); // no stateUpdated() call here
stateUpdated(CALL_MODE_BUTTON);
parsed = true;
} else {
// command is JSON object
if (jsonCmdObj[F("psave")].isNull())
deserializeState(jsonCmdObj, CALL_MODE_BUTTON_PRESET);
else {
uint8_t psave = jsonCmdObj[F("psave")].as<int>();
char pname[33];
sprintf_P(pname, PSTR("IR Preset %d"), psave);
fdo.clear();
if (psave > 0 && psave < 251) savePreset(psave, pname, fdo);
}
parsed = true;
}
releaseJSONBufferLock();
return parsed;
}
};
const char RF433Usermod::_modName[] PROGMEM = "RF433 Remote";
const char RF433Usermod::_modEnabled[] PROGMEM = "Enabled";
const char RF433Usermod::_receivePin[] PROGMEM = "RX Pin";
static RF433Usermod usermod_v2_RF433;
REGISTER_USERMOD(usermod_v2_RF433);

View File

@ -197,7 +197,7 @@ static const char _data_FX_MODE_STROBE_RAINBOW[] PROGMEM = "Strobe Rainbow@!;,!;
* if (bool rev == true) then LEDs are turned off in reverse order
*/
uint16_t color_wipe(bool rev, bool useRandomColors) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150;
uint32_t perc = strip.now % cycleTime;
unsigned prog = (perc * 65535) / cycleTime;
@ -410,7 +410,7 @@ static const char _data_FX_MODE_FADE[] PROGMEM = "Fade@!;!,!;!;01";
* Scan mode parent function
*/
uint16_t scan(bool dual) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150;
uint32_t perc = strip.now % cycleTime;
int prog = (perc * 65535) / cycleTime;
@ -642,11 +642,12 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0";
* Dissolve function
*/
uint16_t dissolve(uint32_t color) {
unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED
unsigned dataSize = sizeof(uint32_t) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
if (SEGENV.call == 0) {
memset(SEGMENT.data, 0xFF, dataSize); // start by fading pixels up
for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = SEGCOLOR(1);
SEGENV.aux0 = 1;
}
@ -654,33 +655,26 @@ uint16_t dissolve(uint32_t color) {
if (hw_random8() <= SEGMENT.intensity) {
for (size_t times = 0; times < 10; times++) { //attempt to spawn a new pixel 10 times
unsigned i = hw_random16(SEGLEN);
unsigned index = i >> 3;
unsigned bitNum = i & 0x07;
bool fadeUp = bitRead(SEGENV.data[index], bitNum);
if (SEGENV.aux0) { //dissolve to primary/palette
if (fadeUp) {
if (color == SEGCOLOR(0)) {
SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
} else {
SEGMENT.setPixelColor(i, color);
}
bitWrite(SEGENV.data[index], bitNum, false);
if (pixels[i] == SEGCOLOR(1)) {
pixels[i] = color == SEGCOLOR(0) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : color;
break; //only spawn 1 new pixel per frame per 50 LEDs
}
} else { //dissolve to secondary
if (!fadeUp) {
SEGMENT.setPixelColor(i, SEGCOLOR(1)); break;
bitWrite(SEGENV.data[index], bitNum, true);
if (pixels[i] != SEGCOLOR(1)) {
pixels[i] = SEGCOLOR(1);
break;
}
}
}
}
}
// fix for #4401
for (unsigned i = 0; i < SEGLEN; i++) SEGMENT.setPixelColor(i, pixels[i]);
if (SEGENV.step > (255 - SEGMENT.speed) + 15U) {
SEGENV.aux0 = !SEGENV.aux0;
SEGENV.step = 0;
memset(SEGMENT.data, (SEGENV.aux0 ? 0xFF : 0), dataSize); // switch fading
} else {
SEGENV.step++;
}
@ -1023,7 +1017,7 @@ static const char _data_FX_MODE_COLORFUL[] PROGMEM = "Colorful@!,Saturation;1,2,
* Emulates a traffic light.
*/
uint16_t mode_traffic_light(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
for (unsigned i=0; i < SEGLEN; i++)
SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1));
uint32_t mdelay = 500;
@ -1056,7 +1050,7 @@ static const char _data_FX_MODE_TRAFFIC_LIGHT[] PROGMEM = "Traffic Light@!,US st
*/
#define FLASH_COUNT 4
uint16_t mode_chase_flash(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1);
for (unsigned i = 0; i < SEGLEN; i++) {
@ -1086,7 +1080,7 @@ static const char _data_FX_MODE_CHASE_FLASH[] PROGMEM = "Chase Flash@!;Bg,Fx;!";
* Prim flashes running, followed by random color.
*/
uint16_t mode_chase_flash_random(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1);
for (int i = 0; i < SEGENV.aux1; i++) {
@ -1168,7 +1162,7 @@ static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream@!,Zone size;;
* K.I.T.T.
*/
uint16_t mode_larson_scanner(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
const unsigned speed = FRAMETIME * map(SEGMENT.speed, 0, 255, 96, 2); // map into useful range
const unsigned pixels = SEGLEN / speed; // how many pixels to advance per frame
@ -1226,7 +1220,7 @@ static const char _data_FX_MODE_DUAL_LARSON_SCANNER[] PROGMEM = "Scanner Dual@!,
* Firing comets from one end. "Lighthouse"
*/
uint16_t mode_comet(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned counter = (strip.now * ((SEGMENT.speed >>2) +1)) & 0xFFFF;
unsigned index = (counter * SEGLEN) >> 16;
if (SEGENV.call == 0) SEGENV.aux0 = index;
@ -1254,7 +1248,7 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!"
* Fireworks function.
*/
uint16_t mode_fireworks() {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
const uint16_t width = SEGMENT.is2D() ? SEG_W : SEGLEN;
const uint16_t height = SEG_H;
@ -1296,7 +1290,7 @@ static const char _data_FX_MODE_FIREWORKS[] PROGMEM = "Fireworks@,Frequency;!,!;
//Twinkling LEDs running. Inspired by https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Rain.h
uint16_t mode_rain() {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
const unsigned width = SEG_W;
const unsigned height = SEG_H;
SEGENV.step += FRAMETIME;
@ -1362,7 +1356,7 @@ static const char _data_FX_MODE_FIRE_FLICKER[] PROGMEM = "Fire Flicker@!,!;!;!;0
* Gradient run base function
*/
uint16_t gradient_base(bool loading) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1);
uint16_t pp = (counter * SEGLEN) >> 16;
if (SEGENV.call == 0) pp = 0;
@ -1407,7 +1401,7 @@ static const char _data_FX_MODE_LOADING[] PROGMEM = "Loading@!,Fade;!,!;!;;ix=16
* Two dots running
*/
uint16_t mode_two_dots() {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster
uint32_t it = strip.now / map(SEGMENT.speed, 0, 255, delay<<4, delay);
unsigned offset = it % SEGLEN;
@ -1827,7 +1821,7 @@ uint16_t mode_oscillate(void) {
// if the counter has increased, move the oscillator by the random step
if (it != SEGENV.step) oscillators[i].pos += oscillators[i].dir * oscillators[i].speed;
oscillators[i].size = SEGLEN/(3+SEGMENT.intensity/8);
if((oscillators[i].dir == -1) && (oscillators[i].pos <= 0)) {
if((oscillators[i].dir == -1) && (oscillators[i].pos > SEGLEN << 1)) { // use integer overflow
oscillators[i].pos = 0;
oscillators[i].dir = 1;
// make bigger steps for faster speeds
@ -1843,7 +1837,7 @@ uint16_t mode_oscillate(void) {
for (unsigned i = 0; i < SEGLEN; i++) {
uint32_t color = BLACK;
for (unsigned j = 0; j < numOscillators; j++) {
if(i >= (unsigned)oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) {
if((int)i >= (int)oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) {
color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), uint8_t(128));
}
}
@ -1858,7 +1852,7 @@ static const char _data_FX_MODE_OSCILLATE[] PROGMEM = "Oscillate";
//TODO
uint16_t mode_lightning(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned ledstart = hw_random16(SEGLEN); // Determine starting location of flash
unsigned ledlen = 1 + hw_random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1)
uint8_t bri = 255/hw_random8(1, 3);
@ -1899,52 +1893,76 @@ uint16_t mode_lightning(void) {
}
static const char _data_FX_MODE_LIGHTNING[] PROGMEM = "Lightning@!,!,,,,,Overlay;!,!;!";
// Pride2015
// Animated, ever-changing rainbows.
// by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5
uint16_t mode_pride_2015(void) {
// combined function from original pride and colorwaves
uint16_t mode_colorwaves_pride_base(bool isPride2015) {
unsigned duration = 10 + SEGMENT.speed;
unsigned sPseudotime = SEGENV.step;
unsigned sHue16 = SEGENV.aux0;
uint8_t sat8 = beatsin88_t( 87, 220, 250);
uint8_t brightdepth = beatsin88_t( 341, 96, 224);
unsigned brightnessthetainc16 = beatsin88_t( 203, (25 * 256), (40 * 256));
uint8_t sat8 = isPride2015 ? beatsin88_t(87, 220, 250) : 255;
unsigned brightdepth = beatsin88_t(341, 96, 224);
unsigned brightnessthetainc16 = beatsin88_t(203, (25 * 256), (40 * 256));
unsigned msmultiplier = beatsin88_t(147, 23, 60);
unsigned hue16 = sHue16;//gHue * 256;
unsigned hueinc16 = beatsin88_t(113, 1, 3000);
unsigned hue16 = sHue16;
unsigned hueinc16 = isPride2015 ? beatsin88_t(113, 1, 3000) :
beatsin88_t(113, 60, 300) * SEGMENT.intensity * 10 / 255;
sPseudotime += duration * msmultiplier;
sHue16 += duration * beatsin88_t( 400, 5,9);
sHue16 += duration * beatsin88_t(400, 5, 9);
unsigned brightnesstheta16 = sPseudotime;
for (unsigned i = 0 ; i < SEGLEN; i++) {
for (unsigned i = 0; i < SEGLEN; i++) {
hue16 += hueinc16;
uint8_t hue8 = hue16 >> 8;
uint8_t hue8;
brightnesstheta16 += brightnessthetainc16;
unsigned b16 = sin16_t( brightnesstheta16 ) + 32768;
if (isPride2015) {
hue8 = hue16 >> 8;
} else {
unsigned h16_128 = hue16 >> 7;
hue8 = (h16_128 & 0x100) ? (255 - (h16_128 >> 1)) : (h16_128 >> 1);
}
brightnesstheta16 += brightnessthetainc16;
unsigned b16 = sin16_t(brightnesstheta16) + 32768;
unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536;
uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536;
bri8 += (255 - brightdepth);
CRGB newcolor = CHSV(hue8, sat8, bri8);
SEGMENT.blendPixelColor(i, newcolor, 64);
if (isPride2015) {
CRGB newcolor = CHSV(hue8, sat8, bri8);
SEGMENT.blendPixelColor(i, newcolor, 64);
} else {
SEGMENT.blendPixelColor(i, SEGMENT.color_from_palette(hue8, false, PALETTE_SOLID_WRAP, 0, bri8), 128);
}
}
SEGENV.step = sPseudotime;
SEGENV.aux0 = sHue16;
return FRAMETIME;
}
// Pride2015
// Animated, ever-changing rainbows.
// by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5
uint16_t mode_pride_2015(void) {
return mode_colorwaves_pride_base(true);
}
static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;";
// ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb
// This function draws color waves with an ever-changing,
// widely-varying set of parameters, using a color palette.
uint16_t mode_colorwaves() {
return mode_colorwaves_pride_base(false);
}
static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!;;pal=26";
//eight colored dots, weaving in and out of sync with each other
uint16_t mode_juggle(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
SEGMENT.fadeToBlackBy(192 - (3*SEGMENT.intensity/4));
CRGB fastled_col;
@ -2089,7 +2107,7 @@ static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation
// feel of your fire: COOLING (used in step 1 above) (Speed = COOLING), and SPARKING (used
// in step 3 above) (Effect Intensity = Sparking).
uint16_t mode_fire_2012() {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
const unsigned strips = SEGMENT.nrOfVStrips();
if (!SEGENV.allocateData(strips * SEGLEN)) return mode_static(); //allocation failed
byte* heat = SEGENV.data;
@ -2147,53 +2165,6 @@ uint16_t mode_fire_2012() {
}
static const char _data_FX_MODE_FIRE_2012[] PROGMEM = "Fire 2012@Cooling,Spark rate,,2D Blur,Boost;;!;1;pal=35,sx=64,ix=160,m12=1,c2=128"; // bars
// ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb
// This function draws color waves with an ever-changing,
// widely-varying set of parameters, using a color palette.
uint16_t mode_colorwaves() {
unsigned duration = 10 + SEGMENT.speed;
unsigned sPseudotime = SEGENV.step;
unsigned sHue16 = SEGENV.aux0;
unsigned brightdepth = beatsin88_t(341, 96, 224);
unsigned brightnessthetainc16 = beatsin88_t( 203, (25 * 256), (40 * 256));
unsigned msmultiplier = beatsin88_t(147, 23, 60);
unsigned hue16 = sHue16;//gHue * 256;
unsigned hueinc16 = beatsin88_t(113, 60, 300)*SEGMENT.intensity*10/255; // Use the Intensity Slider for the hues
sPseudotime += duration * msmultiplier;
sHue16 += duration * beatsin88_t(400, 5, 9);
unsigned brightnesstheta16 = sPseudotime;
for (unsigned i = 0 ; i < SEGLEN; i++) {
hue16 += hueinc16;
uint8_t hue8 = hue16 >> 8;
unsigned h16_128 = hue16 >> 7;
if ( h16_128 & 0x100) {
hue8 = 255 - (h16_128 >> 1);
} else {
hue8 = h16_128 >> 1;
}
brightnesstheta16 += brightnessthetainc16;
unsigned b16 = sin16_t(brightnesstheta16) + 32768;
unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536;
uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536;
bri8 += (255 - brightdepth);
SEGMENT.blendPixelColor(i, SEGMENT.color_from_palette(hue8, false, PALETTE_SOLID_WRAP, 0, bri8), 128); // 50/50 mix
}
SEGENV.step = sPseudotime;
SEGENV.aux0 = sHue16;
return FRAMETIME;
}
static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!;;pal=26";
// colored stripes pulsing at a defined Beats-Per-Minute (BPM)
uint16_t mode_bpm() {
uint32_t stp = (strip.now / 20) & 0xFF;
@ -2369,7 +2340,7 @@ static const char _data_FX_MODE_LAKE[] PROGMEM = "Lake@!;Fx;!";
// send a meteor from begining to to the end of the strip with a trail that randomly decays.
// adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain
uint16_t mode_meteor() {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed
const bool meteorSmooth = SEGMENT.check3;
byte* trail = SEGENV.data;
@ -2436,7 +2407,7 @@ static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail,,,,Gradient,,
//Railway Crossing / Christmas Fairy lights
uint16_t mode_railway() {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned dur = (256 - SEGMENT.speed) * 40;
uint16_t rampdur = (dur * SEGMENT.intensity) >> 8;
if (SEGENV.step > dur)
@ -2537,7 +2508,7 @@ static uint16_t ripple_base(uint8_t blurAmount = 0) {
uint16_t mode_ripple(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
if(SEGMENT.custom1 || SEGMENT.check2) // blur or overlay
SEGMENT.fade_out(250);
else
@ -2549,7 +2520,7 @@ static const char _data_FX_MODE_RIPPLE[] PROGMEM = "Ripple@!,Wave #,Blur,,,,Over
uint16_t mode_ripple_rainbow(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
if (SEGENV.call ==0) {
SEGENV.aux0 = hw_random8();
SEGENV.aux1 = hw_random8();
@ -2727,7 +2698,7 @@ uint16_t mode_halloween_eyes()
uint32_t blinkEndTime;
};
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
const unsigned maxWidth = strip.isMatrix ? SEG_W : SEGLEN;
const unsigned HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEG_W>>4: SEGLEN>>5);
const unsigned HALLOWEEN_EYE_WIDTH = HALLOWEEN_EYE_SPACE/2;
@ -2912,7 +2883,7 @@ static const char _data_FX_MODE_TRI_STATIC_PATTERN[] PROGMEM = "Solid Pattern Tr
static uint16_t spots_base(uint16_t threshold)
{
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1));
unsigned maxZones = SEGLEN >> 2;
@ -2968,7 +2939,7 @@ typedef struct Ball {
* Bouncing Balls Effect
*/
uint16_t mode_bouncing_balls(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
//allocate segment data
const unsigned strips = SEGMENT.nrOfVStrips(); // adapt for 2D
const size_t maxNumBalls = 16;
@ -3146,7 +3117,7 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b
* Sinelon stolen from FASTLED examples
*/
static uint16_t sinelon_base(bool dual, bool rainbow=false) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
SEGMENT.fade_out(SEGMENT.intensity);
unsigned pos = beatsin16_t(SEGMENT.speed/10,0,SEGLEN-1);
if (SEGENV.call == 0) SEGENV.aux0 = pos;
@ -3251,7 +3222,7 @@ typedef struct Spark {
* modified from https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Popcorn.h
*/
uint16_t mode_popcorn(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
//allocate segment data
unsigned strips = SEGMENT.nrOfVStrips();
unsigned usablePopcorns = maxNumPopcorn;
@ -3426,7 +3397,7 @@ typedef struct particle {
} star;
uint16_t mode_starburst(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640
unsigned segs = strip.getActiveSegmentsNum();
if (segs <= (strip.getMaxSegments() /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs
@ -3545,7 +3516,7 @@ static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chanc
*/
uint16_t mode_exploding_fireworks(void)
{
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
const int cols = SEGMENT.is2D() ? SEG_W : 1;
const int rows = SEGMENT.is2D() ? SEG_H : SEGLEN;
@ -3683,7 +3654,7 @@ static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gr
*/
uint16_t mode_drip(void)
{
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
//allocate segment data
unsigned strips = SEGMENT.nrOfVStrips();
const int maxNumDrops = 4;
@ -3779,7 +3750,7 @@ typedef struct Tetris {
} tetris;
uint16_t mode_tetrix(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned strips = SEGMENT.nrOfVStrips(); // allow running on virtual strips (columns in 2D segment)
unsigned dataSize = sizeof(tetris);
if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed
@ -3990,7 +3961,7 @@ static const char _data_FX_MODE_HEARTBEAT[] PROGMEM = "Heartbeat@!,!;!,!;!;01;m1
// Modified for WLED, based on https://github.com/FastLED/FastLED/blob/master/examples/Pacifica/Pacifica.ino
//
// Add one layer of waves into the led array
static CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff)
static CRGB pacifica_one_layer(uint16_t i, const CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff)
{
unsigned ci = cistart;
unsigned waveangle = ioff;
@ -4086,7 +4057,7 @@ static const char _data_FX_MODE_PACIFICA[] PROGMEM = "Pacifica@!,Angle;;!;;pal=5
* Mode simulates a gradual sunrise
*/
uint16_t mode_sunrise() {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
//speed 0 - static sun
//speed 1 - 60: sunrise time in minutes
//speed 60 - 120 : sunset time in minutes - 60;
@ -4293,7 +4264,7 @@ static const char _data_FX_MODE_FLOW[] PROGMEM = "Flow@!,Zones;;!;;m12=1"; //ver
*/
uint16_t mode_chunchun(void)
{
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
SEGMENT.fade_out(254); // add a bit of trail
unsigned counter = strip.now * (6 + (SEGMENT.speed >> 4));
unsigned numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment
@ -4344,7 +4315,7 @@ typedef struct Spotlight {
*/
uint16_t mode_dancing_shadows(void)
{
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266
bool initialize = SEGENV.aux0 != numSpotlights;
SEGENV.aux0 = numSpotlights;
@ -4479,6 +4450,24 @@ uint16_t mode_washing_machine(void) {
static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,!;;!";
/*
Image effect
Draws a .gif image from filesystem on the matrix/strip
*/
uint16_t mode_image(void) {
#ifndef WLED_ENABLE_GIF
return mode_static();
#else
renderImageToSegment(SEGMENT);
return FRAMETIME;
#endif
// if (status != 0 && status != 254 && status != 255) {
// Serial.print("GIF renderer return: ");
// Serial.println(status);
// }
}
static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128";
/*
Blends random colors across palette
Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e
@ -4806,7 +4795,7 @@ static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;;sx=24,pa
// 16 bit perlinmove. Use Perlin Noise instead of sinewaves for movement. By Andrew Tuline.
// Controls are speed, # of pixels, faderate.
uint16_t mode_perlinmove(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
SEGMENT.fade_out(255-SEGMENT.custom1);
for (int i = 0; i < SEGMENT.intensity/16 + 1; i++) {
unsigned locn = inoise16(strip.now*128/(260-SEGMENT.speed)+i*15000, strip.now*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise.
@ -4842,7 +4831,7 @@ static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness vari
//////////////////////////////
// By: ldirko https://editor.soulmatelights.com/gallery/392-flow-led-stripe , modifed by: Andrew Tuline
uint16_t mode_FlowStripe(void) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
const int hl = SEGLEN * 10 / 13;
uint8_t hue = strip.now / (SEGMENT.speed+1);
uint32_t t = strip.now / (SEGMENT.intensity/8+1);
@ -5166,7 +5155,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
neighbors++;
bool colorFound = false;
int k;
for (k=0; k<9 && colorsCount[i].count != 0; k++)
for (k=0; k<9 && colorsCount[k].count != 0; k++)
if (colorsCount[k].color == prevLeds[xy]) {
colorsCount[k].count++;
colorFound = true;
@ -5475,15 +5464,15 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have
// and add them together with weightening
unsigned dx = abs(x - x1);
unsigned dy = abs(y - y1);
unsigned dist = 2 * sqrt16((dx * dx) + (dy * dy));
unsigned dist = 2 * sqrt32_bw((dx * dx) + (dy * dy));
dx = abs(x - x2);
dy = abs(y - y2);
dist += sqrt16((dx * dx) + (dy * dy));
dist += sqrt32_bw((dx * dx) + (dy * dy));
dx = abs(x - x3);
dy = abs(y - y3);
dist += sqrt16((dx * dx) + (dy * dy));
dist += sqrt32_bw((dx * dx) + (dy * dy));
// inverse result
int color = dist ? 1000 / dist : 255;
@ -6123,13 +6112,23 @@ uint16_t mode_2Dscrollingtext(void) {
if (!strlen(text)) { // fallback if empty segment name: display date and time
sprintf_P(text, PSTR("%s %d, %d %d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec);
} else {
if (text[0] == '#') for (auto &c : text) c = std::toupper(c);
if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, zero?PSTR("%02d.%02d.%04d"):PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime));
else if (!strncmp_P(text,PSTR("#DDMM"),5)) sprintf_P(text, zero?PSTR("%02d.%02d") :PSTR("%d.%d"), day(localTime), month(localTime));
else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d") :PSTR("%d/%d"), month(localTime), day(localTime));
else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s") :PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec);
else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime));
else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), AmPmHour);
else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), minute(localTime));
else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf (text, zero? ("%02d") : ("%d"), AmPmHour);
else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf (text, zero? ("%02d") : ("%d"), minute(localTime));
else if (!strncmp_P(text,PSTR("#SS"),3)) sprintf (text, ("%02d") , second(localTime));
else if (!strncmp_P(text,PSTR("#DD"),3)) sprintf (text, zero? ("%02d") : ("%d"), day(localTime));
else if (!strncmp_P(text,PSTR("#DAY"),4)) sprintf (text, ("%s") , dayShortStr(day(localTime)));
else if (!strncmp_P(text,PSTR("#DDDD"),5)) sprintf (text, ("%s") , dayStr(day(localTime)));
else if (!strncmp_P(text,PSTR("#MO"),3)) sprintf (text, zero? ("%02d") : ("%d"), month(localTime));
else if (!strncmp_P(text,PSTR("#MON"),4)) sprintf (text, ("%s") , monthShortStr(month(localTime)));
else if (!strncmp_P(text,PSTR("#MMMM"),5)) sprintf (text, ("%s") , monthStr(month(localTime)));
else if (!strncmp_P(text,PSTR("#YY"),3)) sprintf (text, ("%02d") , year(localTime)%100);
else if (!strncmp_P(text,PSTR("#YYYY"),5)) sprintf_P(text, zero?PSTR("%04d") : ("%d"), year(localTime));
}
const int numberOfLetters = strlen(text);
@ -6568,23 +6567,31 @@ static const char _data_FX_MODE_JUGGLES[] PROGMEM = "Juggles@!,# of balls;!,!;!;
// * MATRIPIX //
//////////////////////
uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline.
if (SEGLEN == 1) return mode_static();
// even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment
// effect can work on single pixels, we just lose the shifting effect
unsigned dataSize = sizeof(uint32_t) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
um_data_t *um_data = getAudioData();
int volumeRaw = *(int16_t*)um_data->u_data[1];
if (SEGENV.call == 0) {
SEGMENT.fill(BLACK);
for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer
}
uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16;
if(SEGENV.aux0 != secondHand) {
SEGENV.aux0 = secondHand;
uint8_t pixBri = volumeRaw * SEGMENT.intensity / 64;
for (unsigned i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left
SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri));
int pixBri = volumeRaw * SEGMENT.intensity / 64;
unsigned k = SEGLEN-1;
// loop will not execute if SEGLEN equals 1
for (unsigned i = 0; i < k; i++) {
pixels[i] = pixels[i+1]; // shift left
SEGMENT.setPixelColor(i, pixels[i]);
}
pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri);
SEGMENT.setPixelColor(k, pixels[k]);
}
return FRAMETIME;
@ -6596,7 +6603,7 @@ static const char _data_FX_MODE_MATRIPIX[] PROGMEM = "Matripix@!,Brightness;!,!;
// * MIDNOISE //
//////////////////////
uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline.
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
// Changing xdist to SEGENV.aux0 and ydist to SEGENV.aux1.
um_data_t *um_data = getAudioData();
@ -6687,7 +6694,7 @@ static const char _data_FX_MODE_NOISEMETER[] PROGMEM = "Noisemeter@Fade rate,Wid
// * PIXELWAVE //
//////////////////////
uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline.
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
// even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment
if (SEGENV.call == 0) {
@ -6755,7 +6762,7 @@ static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels
//////////////////////
// Puddles/Puddlepeak By Andrew Tuline. Merged by @dedehai
uint16_t mode_puddles_base(bool peakdetect) {
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
unsigned size = 0;
uint8_t fadeVal = map(SEGMENT.speed, 0, 255, 224, 254);
unsigned pos = hw_random16(SEGLEN); // Set a random starting position.
@ -6805,7 +6812,7 @@ static const char _data_FX_MODE_PUDDLES[] PROGMEM = "Puddles@Fade rate,Puddle si
// * PIXELS //
//////////////////////
uint16_t mode_pixels(void) { // Pixels. By Andrew Tuline.
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
if (!SEGENV.allocateData(32*sizeof(uint8_t))) return mode_static(); //allocation failed
uint8_t *myVals = reinterpret_cast<uint8_t*>(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low.
@ -6833,7 +6840,7 @@ static const char _data_FX_MODE_PIXELS[] PROGMEM = "Pixels@Fade rate,# of pixels
// ** Blurz //
//////////////////////
uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline.
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
// even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment
um_data_t *um_data = getAudioData();
@ -6897,7 +6904,7 @@ static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;01f;m12=2,
// ** Freqmap //
////////////////////
uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate.
if (SEGLEN == 1) return mode_static();
if (SEGLEN <= 1) return mode_static();
// Start frequency = 60 Hz and log10(60) = 1.78
// End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10
@ -7139,8 +7146,11 @@ static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;!,!;!;01f;m12=
// Combines peak detection with FFT_MajorPeak and FFT_Magnitude.
uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline
// effect can work on single pixels, we just lose the shifting effect
um_data_t *um_data = getAudioData();
unsigned dataSize = sizeof(uint32_t) * SEGLEN;
if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed
uint32_t* pixels = reinterpret_cast<uint32_t*>(SEGENV.data);
um_data_t *um_data = getAudioData();
uint8_t samplePeak = *(uint8_t*)um_data->u_data[3];
float FFT_MajorPeak = *(float*) um_data->u_data[4];
uint8_t *maxVol = (uint8_t*)um_data->u_data[6];
@ -7150,7 +7160,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin
if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception)
if (SEGENV.call == 0) {
SEGMENT.fill(BLACK);
for (unsigned i = 0; i < SEGLEN; i++) pixels[i] = BLACK; // may not be needed as resetIfRequired() clears buffer
SEGENV.aux0 = 255;
SEGMENT.custom1 = *binNum;
SEGMENT.custom2 = *maxVol * 2;
@ -7167,13 +7177,18 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin
uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly.
if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow
unsigned k = SEGLEN-1;
if (samplePeak) {
SEGMENT.setPixelColor(SEGLEN-1, CHSV(92,92,92));
pixels[k] = (uint32_t)CRGB(CHSV(92,92,92));
} else {
SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (uint8_t)my_magnitude));
pixels[k] = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (uint8_t)my_magnitude);
}
SEGMENT.setPixelColor(k, pixels[k]);
// loop will not execute if SEGLEN equals 1
for (unsigned i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left
for (unsigned i = 0; i < k; i++) {
pixels[i] = pixels[i+1]; // shift left
SEGMENT.setPixelColor(i, pixels[i]);
}
}
return FRAMETIME;
@ -7737,7 +7752,9 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS);
addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE);
addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL);
#ifdef WLED_ENABLE_GIF
addEffect(FX_MODE_IMAGE, &mode_image, _data_FX_MODE_IMAGE);
#endif
addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE);
addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE);
addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE);

View File

@ -79,9 +79,9 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define MAX_NUM_SEGMENTS 32
#endif
#if defined(ARDUINO_ARCH_ESP32S2)
#define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*768 // 24k by default (S2 is short on free RAM)
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*768) // 24k by default (S2 is short on free RAM)
#else
#define MAX_SEGMENT_DATA MAX_NUM_SEGMENTS*1280 // 40k by default
#define MAX_SEGMENT_DATA (MAX_NUM_SEGMENTS*1280) // 40k by default
#endif
#endif
@ -184,7 +184,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_TWO_DOTS 50
#define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity)
#define FX_MODE_RUNNING_DUAL 52
// #define FX_MODE_HALLOWEEN 53 // removed in 0.14!
#define FX_MODE_IMAGE 53
#define FX_MODE_TRICOLOR_CHASE 54
#define FX_MODE_TRICOLOR_WIPE 55
#define FX_MODE_TRICOLOR_FADE 56
@ -325,6 +325,30 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define MODE_COUNT 187
#define BLEND_STYLE_FADE 0x00 // universal
#define BLEND_STYLE_FAIRY_DUST 0x01 // universal
#define BLEND_STYLE_SWIPE_RIGHT 0x02 // 1D or 2D
#define BLEND_STYLE_SWIPE_LEFT 0x03 // 1D or 2D
#define BLEND_STYLE_PINCH_OUT 0x04 // 1D or 2D
#define BLEND_STYLE_INSIDE_OUT 0x05 // 1D or 2D
#define BLEND_STYLE_SWIPE_UP 0x06 // 2D
#define BLEND_STYLE_SWIPE_DOWN 0x07 // 2D
#define BLEND_STYLE_OPEN_H 0x08 // 2D
#define BLEND_STYLE_OPEN_V 0x09 // 2D
// as there are many push variants to optimise if statements they are groupped together
#define BLEND_STYLE_PUSH_RIGHT 0x10 // 1D or 2D (& 0b00010000)
#define BLEND_STYLE_PUSH_LEFT 0x11 // 1D or 2D (& 0b00010000)
#define BLEND_STYLE_PUSH_UP 0x12 // 2D (& 0b00010000)
#define BLEND_STYLE_PUSH_DOWN 0x13 // 2D (& 0b00010000)
#define BLEND_STYLE_PUSH_TL 0x14 // 2D (& 0b00010000)
#define BLEND_STYLE_PUSH_TR 0x15 // 2D (& 0b00010000)
#define BLEND_STYLE_PUSH_BR 0x16 // 2D (& 0b00010000)
#define BLEND_STYLE_PUSH_BL 0x17 // 2D (& 0b00010000)
#define BLEND_STYLE_PUSH_MASK 0x10
#define BLEND_STYLE_COUNT 18
typedef enum mapping1D2D {
M12_Pixels = 0,
M12_pBar = 1,
@ -333,7 +357,7 @@ typedef enum mapping1D2D {
M12_sPinwheel = 4
} mapping1D2D_t;
// segment, 80 bytes
// segment, 68 bytes
typedef struct Segment {
public:
uint16_t start; // start index / start X coordinate 2D (left)
@ -436,6 +460,9 @@ typedef struct Segment {
static uint16_t _transitionprogress; // current transition progress 0 - 0xFFFF
#ifndef WLED_DISABLE_MODE_BLEND
static bool _modeBlend; // mode/effect blending semaphore
// clipping
static uint16_t _clipStart, _clipStop;
static uint8_t _clipStartY, _clipStopY;
#endif
// transition data, valid only if transitional==true, holds values during transition (72 bytes)
@ -446,6 +473,7 @@ typedef struct Segment {
#else
uint32_t _colorT[NUM_COLORS];
#endif
uint8_t _palTid; // previous palette
uint8_t _briT; // temporary brightness
uint8_t _cctT; // temporary CCT
CRGBPalette16 _palT; // temporary palette
@ -460,7 +488,7 @@ typedef struct Segment {
{}
} *_t;
[[gnu::hot]] void _setPixelColorXY_raw(int& x, int& y, uint32_t& col); // set pixel without mapping (internal use only)
[[gnu::hot]] void _setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const; // set pixel without mapping (internal use only)
public:
@ -518,7 +546,7 @@ typedef struct Segment {
//if (data) Serial.printf(" %d->(%p)", (int)_dataLen, data);
//Serial.println();
#endif
if (name) { delete[] name; name = nullptr; }
if (name) { free(name); name = nullptr; }
stopTransition();
deallocateData();
}
@ -534,7 +562,6 @@ typedef struct Segment {
inline bool isSelected() const { return selected; }
inline bool isInTransition() const { return _t != nullptr; }
inline bool isActive() const { return stop > start; }
inline bool is2D() const { return (width()>1 && height()>1); }
inline bool hasRGB() const { return _isRGB; }
inline bool hasWhite() const { return _hasW; }
inline bool isCCT() const { return _isCCT; }
@ -588,10 +615,10 @@ typedef struct Segment {
inline void handleTransition() { updateTransitionProgress(); if (progress() == 0xFFFFU) stopTransition(); }
#ifndef WLED_DISABLE_MODE_BLEND
void swapSegenv(tmpsegd_t &tmpSegD); // copies segment data into specifed buffer, if buffer is not a transition buffer, segment data is overwritten from transition buffer
void restoreSegenv(tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer
void restoreSegenv(const tmpsegd_t &tmpSegD); // restores segment data from buffer, if buffer is not transition buffer, changed values are copied to transition buffer
#endif
[[gnu::hot]] void updateTransitionProgress(); // set current progression of transition
inline uint16_t progress() const { return _transitionprogress; }; // transition progression between 0-65535
inline uint16_t progress() const { return Segment::_transitionprogress; } // transition progression between 0-65535
[[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition)
uint8_t currentMode() const; // currently active effect/mode (while in transition)
[[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition)
@ -599,15 +626,19 @@ typedef struct Segment {
// 1D strip
[[gnu::hot]] uint16_t virtualLength() const;
[[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color
inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); }
inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); }
[[gnu::hot]] void setPixelColor(int i, uint32_t c) const; // set relative pixel within segment with color
inline void setPixelColor(unsigned n, uint32_t c) const { setPixelColor(int(n), c); }
inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(int n, CRGB c) const { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); }
#ifdef WLED_USE_AA_PIXELS
void setPixelColor(float i, uint32_t c, bool aa = true);
inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); }
inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); }
void setPixelColor(float i, uint32_t c, bool aa = true) const;
inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) const { setPixelColor(i, RGBW32(r,g,b,w), aa); }
inline void setPixelColor(float i, CRGB c, bool aa = true) const { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); }
#endif
#ifndef WLED_DISABLE_MODE_BLEND
static inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; };
#endif
bool isPixelClipped(int i) const;
[[gnu::hot]] uint32_t getPixelColor(int i) const;
// 1D support functions (some implement 2D as well)
void blur(uint8_t, bool smear = false);
@ -642,17 +673,19 @@ typedef struct Segment {
#endif
}
#ifndef WLED_DISABLE_2D
[[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment
[[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); }
inline bool is2D() const { return (width()>1 && height()>1); }
[[gnu::hot]] int XY(int x, int y) const; // support function to get relative index within segment
[[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c) const; // set relative pixel within segment with color
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColorXY(int(x), int(y), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); }
#ifdef WLED_USE_AA_PIXELS
void setPixelColorXY(float x, float y, uint32_t c, bool aa = true);
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); }
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); }
void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) const;
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) const { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); }
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); }
#endif
[[gnu::hot]] bool isPixelXYClipped(int x, int y) const;
[[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const;
// 2D support functions
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); }
@ -678,7 +711,8 @@ typedef struct Segment {
void wu_pixel(uint32_t x, uint32_t y, CRGB c);
inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); }
#else
inline uint16_t XY(int x, int y) { return x; }
inline constexpr bool is2D() const { return false; }
inline int XY(int x, int y) const { return x; }
inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); }
inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); }
@ -689,6 +723,7 @@ typedef struct Segment {
inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); }
inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); }
#endif
inline bool isPixelXYClipped(int x, int y) { return isPixelClipped(x); }
inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); }
inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
@ -733,9 +768,7 @@ class WS2812FX { // 96 bytes
public:
WS2812FX() :
paletteFade(0),
paletteBlend(0),
cctBlending(0),
now(millis()),
timebase(0),
isMatrix(false),
@ -778,7 +811,7 @@ class WS2812FX { // 96 bytes
}
~WS2812FX() {
if (customMappingTable) delete[] customMappingTable;
if (customMappingTable) free(customMappingTable);
_mode.clear();
_modeData.clear();
_segments.clear();
@ -804,7 +837,7 @@ class WS2812FX { // 96 bytes
resetSegments(), // marks all segments for reset
makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs
fixInvalidSegments(), // fixes incorrect segment configuration
setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c
setPixelColor(unsigned i, uint32_t c) const, // paints absolute strip pixel with index n and color c
show(), // initiates LED output
setTargetFps(unsigned fps),
setupEffectData(); // add default effects to the list; defined in FX.cpp
@ -812,9 +845,9 @@ class WS2812FX { // 96 bytes
inline void resetTimebase() { timebase = 0UL - millis(); }
inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } }
inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); }
inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); }
inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline)
inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); }
inline void setPixelColor(unsigned n, CRGB c) const { setPixelColor(n, c.red, c.green, c.blue); }
inline void fill(uint32_t c) const { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline)
inline void trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments.
inline void setShowCallback(show_callback cb) { _callback = cb; }
inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms)
@ -823,8 +856,7 @@ class WS2812FX { // 96 bytes
inline void resume() { _suspend = false; } // will resume strip.service() execution
bool
paletteFade,
checkSegmentAlignment(),
checkSegmentAlignment() const,
hasRGBWBus() const,
hasCCTBus() const,
deserializeMap(unsigned n = 0);
@ -838,7 +870,6 @@ class WS2812FX { // 96 bytes
uint8_t
paletteBlend,
cctBlending,
getActiveSegmentsNum() const,
getFirstSelectedSegId() const,
getLastActiveSegmentId() const,
@ -869,7 +900,7 @@ class WS2812FX { // 96 bytes
};
unsigned long now, timebase;
uint32_t getPixelColor(unsigned) const;
uint32_t getPixelColor(unsigned i) const;
inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call
@ -918,11 +949,11 @@ class WS2812FX { // 96 bytes
void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration
// outsmart the compiler :) by correctly overloading
inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline void setPixelColorXY(int x, int y, uint32_t c) const { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); }
inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); }
inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); }
inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); }
inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); }
// end 2D support
@ -936,7 +967,7 @@ class WS2812FX { // 96 bytes
};
std::vector<segment> _segments;
friend class Segment;
friend struct Segment;
private:
volatile bool _suspend;

View File

@ -50,8 +50,8 @@ void WS2812FX::setUpMatrix() {
customMappingSize = 0; // prevent use of mapping if anything goes wrong
if (customMappingTable) delete[] customMappingTable;
customMappingTable = new uint16_t[getLengthTotal()];
if (customMappingTable) free(customMappingTable);
customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal()));
if (customMappingTable) {
customMappingSize = getLengthTotal();
@ -68,7 +68,7 @@ void WS2812FX::setUpMatrix() {
// content of the file is just raw JSON array in the form of [val1,val2,val3,...]
// there are no other "key":"value" pairs in it
// allowed values are: -1 (missing pixel/no LED attached), 0 (inactive/unused pixel), 1 (active/used pixel)
char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json")); // reduce flash footprint
char fileName[32]; strcpy_P(fileName, PSTR("/2d-gaps.json"));
bool isFile = WLED_FS.exists(fileName);
size_t gapSize = 0;
int8_t *gapTable = nullptr;
@ -85,7 +85,7 @@ void WS2812FX::setUpMatrix() {
JsonArray map = pDoc->as<JsonArray>();
gapSize = map.size();
if (!map.isNull() && gapSize >= matrixSize) { // not an empty map
gapTable = new int8_t[gapSize];
gapTable = static_cast<int8_t*>(malloc(gapSize));
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
gapTable[i] = constrain(map[i], -1, 1);
}
@ -113,7 +113,7 @@ void WS2812FX::setUpMatrix() {
}
// delete gap array as we no longer need it
if (gapTable) delete[] gapTable;
if (gapTable) free(gapTable);
#ifdef WLED_DEBUG
DEBUG_PRINT(F("Matrix ledmap:"));
@ -146,7 +146,7 @@ void WS2812FX::setUpMatrix() {
#ifndef WLED_DISABLE_2D
// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element)
uint16_t IRAM_ATTR_YN Segment::XY(int x, int y)
int IRAM_ATTR_YN Segment::XY(int x, int y) const
{
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
@ -154,13 +154,13 @@ uint16_t IRAM_ATTR_YN Segment::XY(int x, int y)
}
// raw setColor function without checks (checks are done in setPixelColorXY())
void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col)
void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const
{
const int baseX = start + x;
const int baseY = startY + y;
#ifndef WLED_DISABLE_MODE_BLEND
// if blending modes, blend with underlying pixel
if (_modeBlend) col = color_blend16(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress());
if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) col = color_blend16(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress());
#endif
strip.setPixelColorXY(baseX, baseY, col);
@ -179,14 +179,57 @@ void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col)
}
}
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col)
// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false)
// if clipping start > stop the clipping range is inverted
// _modeBlend==true -> old effect during transition
// _modeBlend==false -> new effect during transition
bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const {
#ifndef WLED_DISABLE_MODE_BLEND
if (_clipStart != _clipStop && blendingStyle != BLEND_STYLE_FADE) {
const bool invertX = _clipStart > _clipStop;
const bool invertY = _clipStartY > _clipStopY;
const int startX = invertX ? _clipStop : _clipStart;
const int stopX = invertX ? _clipStart : _clipStop;
const int startY = invertY ? _clipStopY : _clipStartY;
const int stopY = invertY ? _clipStartY : _clipStopY;
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth())
const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight())
if (len < 2) return false;
const unsigned shuffled = hashInt(x + y * width) % len;
const unsigned pos = (shuffled * 0xFFFFU) / len;
return progress() > pos;
}
bool xInside = (x >= startX && x < stopX); if (invertX) xInside = !xInside;
bool yInside = (y >= startY && y < stopY); if (invertY) yInside = !yInside;
const bool clip = (invertX && invertY) ? !_modeBlend : _modeBlend;
if (xInside && yInside) return clip; // covers window & corners (inverted)
return !clip;
}
#endif
return false;
}
void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const
{
if (!isActive()) return; // not active
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
const int vH = vHeight(); // segment height in logical pixels (is always >= 1)
// negative values of x & y cast into unsigend will become very large values and will therefore be greater than vW/vH
if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return; // if pixel would fall out of virtual segment just exit
#ifndef WLED_DISABLE_MODE_BLEND
unsigned prog = 0xFFFF - progress();
if (!prog && !_modeBlend && (blendingStyle & BLEND_STYLE_PUSH_MASK)) {
unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF;
unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF;
if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x += dX;
else x -= dX;
if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY;
else y += dY;
}
#endif
if (x >= vW || y >= vH || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit
// if color is unscaled
if (!_colorScaled) col = color_fade(col, _segBri);
@ -215,7 +258,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col)
#ifdef WLED_USE_AA_PIXELS
// anti-aliased version of setPixelColorXY()
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const
{
if (!isActive()) return; // not active
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
@ -259,9 +302,24 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
// returns RGBW values of pixel
uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const {
if (!isActive()) return 0; // not active
const int vW = vWidth();
const int vH = vHeight();
if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return 0; // if pixel would fall out of virtual segment just exit
#ifndef WLED_DISABLE_MODE_BLEND
unsigned prog = 0xFFFF - progress();
if (!prog && !_modeBlend && (blendingStyle & BLEND_STYLE_PUSH_MASK)) {
unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF;
unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF;
if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX;
else x += dX;
if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY;
else y += dY;
}
#endif
if (x >= vW || y >= vH || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit
if (reverse ) x = vW - x - 1;
if (reverse_y) y = vH - y - 1;
if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed
@ -276,7 +334,7 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) {
if (!isActive()) return; // not active
const unsigned cols = vWidth();
const unsigned rows = vHeight();
uint32_t lastnew;
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
uint32_t last;
if (blur_x) {
const uint8_t keepx = smear ? 255 : 255 - blur_x;

View File

@ -84,6 +84,10 @@ uint16_t Segment::_transitionprogress = 0xFFFF;
#ifndef WLED_DISABLE_MODE_BLEND
bool Segment::_modeBlend = false;
uint16_t Segment::_clipStart = 0;
uint16_t Segment::_clipStop = 0;
uint8_t Segment::_clipStartY = 0;
uint8_t Segment::_clipStopY = 1;
#endif
// copy constructor
@ -94,7 +98,7 @@ Segment::Segment(const Segment &orig) {
name = nullptr;
data = nullptr;
_dataLen = 0;
if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
}
@ -113,7 +117,7 @@ Segment& Segment::operator= (const Segment &orig) {
//DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
// clean destination
if (name) { delete[] name; name = nullptr; }
if (name) { free(name); name = nullptr; }
stopTransition();
deallocateData();
// copy source
@ -122,7 +126,7 @@ Segment& Segment::operator= (const Segment &orig) {
data = nullptr;
_dataLen = 0;
// copy source data
if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); }
if (orig.name) { name = static_cast<char*>(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); }
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
}
return *this;
@ -132,7 +136,7 @@ Segment& Segment::operator= (const Segment &orig) {
Segment& Segment::operator= (Segment &&orig) noexcept {
//DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this);
if (this != &orig) {
if (name) { delete[] name; name = nullptr; } // free old name
if (name) { free(name); name = nullptr; } // free old name
stopTransition();
deallocateData(); // free old runtime data
memcpy((void*)this, (void*)&orig, sizeof(Segment));
@ -195,6 +199,9 @@ void Segment::resetIfRequired() {
if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData())
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
reset = false;
#ifdef WLED_ENABLE_GIF
endImagePlayback(this);
#endif
}
CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
@ -253,29 +260,26 @@ void Segment::startTransition(uint16_t dur) {
if (isInTransition()) return; // already in transition no need to store anything
// starting a transition has to occur before change so we get current values 1st
_t = new Transition(dur); // no previous transition running
_t = new(std::nothrow) Transition(dur); // no previous transition running
if (!_t) return; // failed to allocate data
//DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t);
loadPalette(_t->_palT, palette);
_t->_palTid = palette;
_t->_briT = on ? opacity : 0;
_t->_cctT = cct;
#ifndef WLED_DISABLE_MODE_BLEND
if (modeBlending) {
swapSegenv(_t->_segT);
_t->_modeT = mode;
_t->_segT._dataLenT = 0;
_t->_segT._dataT = nullptr;
if (_dataLen > 0 && data) {
_t->_segT._dataT = (byte *)malloc(_dataLen);
if (_t->_segT._dataT) {
//DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT);
memcpy(_t->_segT._dataT, data, _dataLen);
_t->_segT._dataLenT = _dataLen;
}
swapSegenv(_t->_segT);
_t->_modeT = mode;
_t->_segT._dataLenT = 0;
_t->_segT._dataT = nullptr;
if (_dataLen > 0 && data) {
_t->_segT._dataT = (byte *)malloc(_dataLen);
if (_t->_segT._dataT) {
//DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT);
memcpy(_t->_segT._dataT, data, _dataLen);
_t->_segT._dataLenT = _dataLen;
}
} else {
for (size_t i=0; i<NUM_COLORS; i++) _t->_segT._colorT[i] = colors[i];
}
#else
for (size_t i=0; i<NUM_COLORS; i++) _t->_colorT[i] = colors[i];
@ -296,6 +300,7 @@ void Segment::stopTransition() {
delete _t;
_t = nullptr;
}
_transitionprogress = 0xFFFFU; // stop means stop - transition has ended
}
// transition progression between 0-65535
@ -326,7 +331,7 @@ void Segment::swapSegenv(tmpsegd_t &tmpSeg) {
tmpSeg._callT = call;
tmpSeg._dataT = data;
tmpSeg._dataLenT = _dataLen;
if (_t && &tmpSeg != &(_t->_segT)) {
if (isInTransition() && &tmpSeg != &(_t->_segT)) {
// swap SEGENV with transitional data
options = _t->_segT._optionsT;
for (size_t i=0; i<NUM_COLORS; i++) colors[i] = _t->_segT._colorT[i];
@ -347,9 +352,9 @@ void Segment::swapSegenv(tmpsegd_t &tmpSeg) {
}
}
void Segment::restoreSegenv(tmpsegd_t &tmpSeg) {
void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) {
//DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data);
if (_t && &(_t->_segT) != &tmpSeg) {
if (isInTransition() && &(_t->_segT) != &tmpSeg) {
// update possibly changed variables to keep old effect running correctly
_t->_segT._aux0T = aux0;
_t->_segT._aux1T = aux1;
@ -379,29 +384,53 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) {
#endif
uint8_t Segment::currentBri(bool useCct) const {
unsigned prog = progress();
unsigned prog = isInTransition() ? progress() : 0xFFFFU;
uint32_t curBri = useCct ? cct : (on ? opacity : 0);
if (prog < 0xFFFFU) {
unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog;
curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog);
#ifndef WLED_DISABLE_MODE_BLEND
uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0);
// _modeBlend==true -> old effect
if (blendingStyle != BLEND_STYLE_FADE) return _modeBlend ? tmpBri : curBri; // not fade/blend transition, each effect uses its brightness
#else
uint8_t tmpBri = useCct ? _t->_cctT : _t->_briT;
#endif
curBri *= prog;
curBri += tmpBri * (0xFFFFU - prog);
return curBri / 0xFFFFU;
}
return (useCct ? cct : (on ? opacity : 0));
return curBri;
}
uint8_t Segment::currentMode() const {
#ifndef WLED_DISABLE_MODE_BLEND
unsigned prog = progress();
if (modeBlending && prog < 0xFFFFU) return _t->_modeT;
#endif
unsigned prog = isInTransition() ? progress() : 0xFFFFU;
if (prog == 0xFFFFU) return mode;
if (blendingStyle != BLEND_STYLE_FADE) {
// workaround for on/off transition to respect blending style
uint8_t modeT = (bri != briT) && bri ? FX_MODE_STATIC : _t->_modeT; // On/Off transition active (bri!=briT) and final bri>0 : old mode is STATIC
uint8_t modeS = (bri != briT) && !bri ? FX_MODE_STATIC : mode; // On/Off transition active (bri!=briT) and final bri==0 : new mode is STATIC
return _modeBlend ? modeT : modeS; // _modeBlend==true -> old effect
}
return _modeBlend ? _t->_modeT : mode; // _modeBlend==true -> old effect
#else
return mode;
#endif
}
uint32_t Segment::currentColor(uint8_t slot) const {
if (slot >= NUM_COLORS) slot = 0;
unsigned prog = progress();
if (prog == 0xFFFFU) return colors[slot];
#ifndef WLED_DISABLE_MODE_BLEND
return isInTransition() ? color_blend16(_t->_segT._colorT[slot], colors[slot], progress()) : colors[slot];
if (blendingStyle != BLEND_STYLE_FADE) {
// workaround for on/off transition to respect blending style
uint32_t colT = (bri != briT) && bri ? BLACK : _t->_segT._colorT[slot]; // On/Off transition active (bri!=briT) and final bri>0 : old color is BLACK
uint32_t colS = (bri != briT) && !bri ? BLACK : colors[slot]; // On/Off transition active (bri!=briT) and final bri==0 : new color is BLACK
return _modeBlend ? colT : colS; // _modeBlend==true -> old effect
}
return color_blend16(_t->_segT._colorT[slot], colors[slot], prog);
#else
return isInTransition() ? color_blend16(_t->_colorT[slot], colors[slot], progress()) : colors[slot];
return color_blend16(_t->_colorT[slot], colors[slot], prog);
#endif
}
@ -411,44 +440,49 @@ void Segment::beginDraw() {
_vHeight = virtualHeight();
_vLength = virtualLength();
_segBri = currentBri();
unsigned prog = isInTransition() ? progress() : 0xFFFFU; // transition progress; 0xFFFFU = no transition active
// adjust gamma for effects
for (unsigned i = 0; i < NUM_COLORS; i++) {
#ifndef WLED_DISABLE_MODE_BLEND
uint32_t col = isInTransition() ? color_blend16(_t->_segT._colorT[i], colors[i], progress()) : colors[i];
uint32_t col = isInTransition() ? color_blend16(_t->_segT._colorT[i], colors[i], prog) : colors[i];
#else
uint32_t col = isInTransition() ? color_blend16(_t->_colorT[i], colors[i], progress()) : colors[i];
uint32_t col = isInTransition() ? color_blend16(_t->_colorT[i], colors[i], prog) : colors[i];
#endif
_currentColors[i] = gamma32(col);
}
// load palette into _currentPalette
loadPalette(_currentPalette, palette);
unsigned prog = progress();
if (strip.paletteFade && prog < 0xFFFFU) {
// blend palettes
// there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time)
// minimum blend time is 100ms maximum is 65535ms
unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48);
_currentPalette = _t->_palT; // copy transitioning/temporary palette
if (prog < 0xFFFFU) {
#ifndef WLED_DISABLE_MODE_BLEND
if (blendingStyle > BLEND_STYLE_FADE) {
//if (_modeBlend) loadPalette(_currentPalette, _t->_palTid); // not fade/blend transition, each effect uses its palette
if (_modeBlend) _currentPalette = _t->_palT; // not fade/blend transition, each effect uses its palette
} else
#endif
{
// blend palettes
// there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time)
// minimum blend time is 100ms maximum is 65535ms
unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends;
for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48);
_currentPalette = _t->_palT; // copy transitioning/temporary palette
}
}
}
// relies on WS2812FX::service() to call it for each frame
void Segment::handleRandomPalette() {
// is it time to generate a new palette?
if ((uint16_t)((uint16_t)(millis() / 1000U) - _lastPaletteChange) > randomPaletteChangeTime){
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = (uint16_t)(millis() / 1000U);
_lastPaletteBlend = (uint16_t)((uint16_t)millis() - 512); // starts blending immediately
if ((uint16_t)(millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) {
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = (uint16_t)(millis()/1000U);
_lastPaletteBlend = (uint16_t)(millis())-512; // starts blending immediately
}
// if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
if (strip.paletteFade) {
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
// in reality there need to be 255 blends to fully blend two entirely different palettes
if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = (uint16_t)millis();
}
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
// in reality there need to be 255 blends to fully blend two entirely different palettes
if ((uint16_t)millis() - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = (uint16_t)millis();
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
}
@ -523,7 +557,8 @@ Segment &Segment::setColor(uint8_t slot, uint32_t c) {
if (slot == 0 && c == BLACK) return *this; // on/off segment cannot have primary color black
if (slot == 1 && c != BLACK) return *this; // on/off segment cannot have secondary color non black
}
if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change
//DEBUG_PRINTF_P(PSTR("- Starting color transition: %d [0x%X]\n"), slot, c);
startTransition(strip.getTransition()); // start transition prior to change
colors[slot] = c;
stateChanged = true; // send UDP/WS broadcast
return *this;
@ -536,7 +571,7 @@ Segment &Segment::setCCT(uint16_t k) {
k = (k - 1900) >> 5;
}
if (cct != k) {
//DEBUGFX_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k);
//DEBUG_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k);
startTransition(strip.getTransition()); // start transition prior to change
cct = k;
stateChanged = true; // send UDP/WS broadcast
@ -546,7 +581,7 @@ Segment &Segment::setCCT(uint16_t k) {
Segment &Segment::setOpacity(uint8_t o) {
if (opacity != o) {
//DEBUGFX_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o);
//DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o);
startTransition(strip.getTransition()); // start transition prior to change
opacity = o;
stateChanged = true; // send UDP/WS broadcast
@ -556,7 +591,7 @@ Segment &Segment::setOpacity(uint8_t o) {
Segment &Segment::setOption(uint8_t n, bool val) {
bool prevOn = on;
if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change
if (n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change
if (val) options |= 0x01 << n;
else options &= ~(0x01 << n);
if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast
@ -570,7 +605,8 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
// if we have a valid mode & is not reserved
if (fx != mode) {
#ifndef WLED_DISABLE_MODE_BLEND
if (modeBlending) startTransition(strip.getTransition()); // set effect transitions
//DEBUG_PRINTF_P(PSTR("- Starting effect transition: %d\n"), fx);
startTransition(strip.getTransition()); // set effect transitions
#endif
mode = fx;
int sOpt;
@ -605,7 +641,8 @@ Segment &Segment::setPalette(uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes
if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes
if (pal != palette) {
if (strip.paletteFade) startTransition(strip.getTransition());
//DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal);
startTransition(strip.getTransition());
palette = pal;
stateChanged = true; // send UDP/WS broadcast
}
@ -678,7 +715,7 @@ uint16_t Segment::virtualLength() const {
vLen = max(vW,vH); // get the longest dimension
break;
case M12_pArc:
vLen = sqrt16(vH*vH + vW*vW); // use diagonal
vLen = sqrt32_bw(vH*vH + vW*vW); // use diagonal
break;
case M12_sPinwheel:
vLen = getPinwheelLength(vW, vH);
@ -696,7 +733,34 @@ uint16_t Segment::virtualLength() const {
return vLength;
}
void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false)
// if clipping start > stop the clipping range is inverted
// _modeBlend==true -> old effect during transition
// _modeBlend==false -> new effect during transition
bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const {
#ifndef WLED_DISABLE_MODE_BLEND
if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) {
bool invert = _clipStart > _clipStop; // ineverted start & stop
int start = invert ? _clipStop : _clipStart;
int stop = invert ? _clipStart : _clipStop;
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
unsigned len = stop - start;
if (len < 2) return false;
unsigned shuffled = hashInt(i) % len;
unsigned pos = (shuffled * 0xFFFFU) / len;
return (progress() <= pos) ^ _modeBlend;
}
const bool iInside = (i >= start && i < stop);
//if (!invert && iInside) return _modeBlend;
//if ( invert && !iInside) return _modeBlend;
//return !_modeBlend;
return !iInside ^ invert ^ _modeBlend; // thanks @willmmiles (https://github.com/Aircoookie/WLED/pull/3877#discussion_r1554633876)
}
#endif
return false;
}
void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const
{
if (!isActive() || i < 0) return; // not active or invalid index
#ifndef WLED_DISABLE_2D
@ -828,6 +892,18 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
}
#endif
#ifndef WLED_DISABLE_MODE_BLEND
// if we blend using "push" style we need to "shift" new mode to left or right
if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) {
unsigned prog = 0xFFFF - progress();
unsigned dI = prog * vL / 0xFFFF;
if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI;
else i += dI;
}
#endif
if (i >= vL || i < 0 || isPixelClipped(i)) return; // handle clipping on 1D
unsigned len = length();
// if color is unscaled
if (!_colorScaled) col = color_fade(col, _segBri);
@ -853,14 +929,16 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
indexMir += offset; // offset/phase
if (indexMir >= stop) indexMir -= len; // wrap
#ifndef WLED_DISABLE_MODE_BLEND
if (_modeBlend) tmpCol = color_blend16(strip.getPixelColor(indexMir), col, uint16_t(0xFFFFU - progress()));
// _modeBlend==true -> old effect
if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend16(strip.getPixelColor(indexMir), col, 0xFFFFU - progress());
#endif
strip.setPixelColor(indexMir, tmpCol);
}
indexSet += offset; // offset/phase
if (indexSet >= stop) indexSet -= len; // wrap
#ifndef WLED_DISABLE_MODE_BLEND
if (_modeBlend) tmpCol = color_blend16(strip.getPixelColor(indexSet), col, uint16_t(0xFFFFU - progress()));
// _modeBlend==true -> old effect
if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend16(strip.getPixelColor(indexSet), col, 0xFFFFU - progress());
#endif
strip.setPixelColor(indexSet, tmpCol);
}
@ -869,7 +947,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col)
#ifdef WLED_USE_AA_PIXELS
// anti-aliased normalized version of setPixelColor()
void Segment::setPixelColor(float i, uint32_t col, bool aa)
void Segment::setPixelColor(float i, uint32_t col, bool aa) const
{
if (!isActive()) return; // not active
int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows)
@ -906,6 +984,9 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
{
if (!isActive()) return 0; // not active
int vL = vLength();
if (i >= vL || i < 0) return 0;
#ifndef WLED_DISABLE_2D
if (is2D()) {
const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
@ -921,7 +1002,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
break; }
case M12_pArc:
if (i >= vW && i >= vH) {
unsigned vI = sqrt16(i*i/2);
unsigned vI = sqrt32_bw(i*i/2);
return getPixelColorXY(vI,vI); // use diagonal
}
case M12_pCorner:
@ -962,7 +1043,18 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const
}
#endif
if (reverse) i = vLength() - i - 1;
#ifndef WLED_DISABLE_MODE_BLEND
if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) {
unsigned prog = 0xFFFF - progress();
unsigned dI = prog * vL / 0xFFFF;
if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI;
else i += dI;
}
#endif
if (i >= vL || i < 0 || isPixelClipped(i)) return 0; // handle clipping on 1D
if (reverse) i = vL - i - 1;
i *= groupLength();
i += start;
// offset/phase
@ -1134,7 +1226,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) {
uint8_t seep = blur_amount >> 1;
unsigned vlength = vLength();
uint32_t carryover = BLACK;
uint32_t lastnew;
uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration
uint32_t last;
uint32_t curnew = BLACK;
for (unsigned i = 0; i < vlength; i++) {
@ -1195,7 +1287,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_
if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1);
// paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined)
if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end"
CRGBW palcol = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global
CRGBW palcol = ColorFromPaletteWLED(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global
palcol.w = W(color);
return palcol.color32;
@ -1372,21 +1464,78 @@ void WS2812FX::service() {
// The blending will largely depend on the effect behaviour since actual output (LEDs) may be
// overwritten by later effect. To enable seamless blending for every effect, additional LED buffer
// would need to be allocated for each effect and then blended together for each pixel.
[[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition
seg.beginDraw(); // set up parameters for get/setPixelColor()
frameDelay = (*_mode[seg.mode])(); // run new/current mode
#ifndef WLED_DISABLE_MODE_BLEND
if (modeBlending && seg.mode != tmpMode) {
Segment::setClippingRect(0, 0); // disable clipping (just in case)
if (seg.isInTransition()) {
// set clipping rectangle
// new mode is run inside clipping area and old mode outside clipping area
unsigned p = seg.progress();
unsigned w = seg.is2D() ? Segment::vWidth() : Segment::vLength();
unsigned h = Segment::vHeight();
unsigned dw = p * w / 0xFFFFU + 1;
unsigned dh = p * h / 0xFFFFU + 1;
unsigned orgBS = blendingStyle;
if (w*h == 1) blendingStyle = BLEND_STYLE_FADE; // disable belending for single pixel segments (use fade instead)
switch (blendingStyle) {
case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped())
Segment::setClippingRect(0, w, 0, h);
break;
case BLEND_STYLE_SWIPE_RIGHT: // left-to-right
case BLEND_STYLE_PUSH_RIGHT: // left-to-right
Segment::setClippingRect(0, dw, 0, h);
break;
case BLEND_STYLE_SWIPE_LEFT: // right-to-left
case BLEND_STYLE_PUSH_LEFT: // right-to-left
Segment::setClippingRect(w - dw, w, 0, h);
break;
case BLEND_STYLE_PINCH_OUT: // corners
Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!!
break;
case BLEND_STYLE_INSIDE_OUT: // outward
Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2);
break;
case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D)
case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D)
Segment::setClippingRect(0, w, 0, dh);
break;
case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D)
case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D)
Segment::setClippingRect(0, w, h - dh, h);
break;
case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D
Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h);
break;
case BLEND_STYLE_OPEN_V: // vertical-outward (2D)
Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2);
break;
case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D)
Segment::setClippingRect(0, dw, 0, dh);
break;
case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D)
Segment::setClippingRect(w - dw, w, 0, dh);
break;
case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D)
Segment::setClippingRect(w - dw, w, h - dh, h);
break;
case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D)
Segment::setClippingRect(0, dw, h - dh, h);
break;
}
frameDelay = (*_mode[seg.currentMode()])(); // run new/current mode
// now run old/previous mode
Segment::tmpsegd_t _tmpSegData;
Segment::modeBlend(true); // set semaphore
seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state)
seg.beginDraw(); // set up parameters for get/setPixelColor()
unsigned d2 = (*_mode[tmpMode])(); // run old mode
frameDelay = min(frameDelay, (unsigned)(*_mode[seg.currentMode()])()); // run old mode
seg.call++; // increment old mode run counter
seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state)
frameDelay = min(frameDelay,d2); // use shortest delay
Segment::modeBlend(false); // unset semaphore
}
blendingStyle = orgBS; // restore blending style if it was modified for single pixel segment
} else
#endif
frameDelay = (*_mode[seg.mode])(); // run effect mode (not in transition)
seg.call++;
if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition
BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments
@ -1396,6 +1545,7 @@ void WS2812FX::service() {
}
_segment_index++;
}
Segment::setClippingRect(0, 0); // disable clipping for overlays
_isServicing = false;
_triggered = false;
@ -1413,7 +1563,7 @@ void WS2812FX::service() {
#endif
}
void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) {
void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) const {
i = getMappedPixelIndex(i);
if (i >= _length) return;
BusManager::setPixelColor(i, col);
@ -1694,9 +1844,9 @@ void WS2812FX::fixInvalidSegments() {
//true if all segments align with a bus, or if a segment covers the total length
//irrelevant in 2D set-up
bool WS2812FX::checkSegmentAlignment() {
bool WS2812FX::checkSegmentAlignment() const {
bool aligned = false;
for (segment &seg : _segments) {
for (const segment &seg : _segments) {
for (unsigned b = 0; b<BusManager::getNumBusses(); b++) {
Bus *bus = BusManager::getBus(b);
if (seg.start == bus->getStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true;
@ -1808,8 +1958,8 @@ bool WS2812FX::deserializeMap(unsigned n) {
Segment::maxHeight = min(max(root[F("height")].as<int>(), 1), 128);
}
if (customMappingTable) delete[] customMappingTable;
customMappingTable = new uint16_t[getLengthTotal()];
if (customMappingTable) free(customMappingTable);
customMappingTable = static_cast<uint16_t*>(malloc(sizeof(uint16_t)*getLengthTotal()));
if (customMappingTable) {
DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName);

View File

@ -16,6 +16,9 @@
#define LEDC_MUTEX_UNLOCK()
#endif
#endif
#ifdef ESP8266
#include "core_esp8266_waveform.h"
#endif
#include "const.h"
#include "pin_manager.h"
#include "bus_wrapper.h"
@ -27,7 +30,7 @@ extern bool cctICused;
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
//udp.cpp
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, byte *buffer, uint8_t bri=255, bool isRGBW=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri=255, bool isRGBW=false);
// enable additional debug output
#if defined(WLED_DEBUG_HOST)
@ -121,7 +124,7 @@ uint8_t *Bus::allocateData(size_t size) {
}
BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
BusDigital::BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count, bc.reversed, (bc.refreshReq || bc.type == TYPE_TM1814))
, _skip(bc.skipAmount) //sacrificial pixels
, _colorOrder(bc.colorOrder)
@ -448,7 +451,7 @@ void BusDigital::cleanup() {
#endif
#endif
BusPwm::BusPwm(BusConfig &bc)
BusPwm::BusPwm(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering
{
if (!isPWM(bc.type)) return;
@ -462,10 +465,7 @@ BusPwm::BusPwm(BusConfig &bc)
for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true};
if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return;
#ifdef ESP8266
analogWriteRange((1<<_depth)-1);
analogWriteFreq(_frequency);
#else
#ifdef ARDUINO_ARCH_ESP32
// for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer
_ledcStart = PinManager::allocateLedc(numPins);
if (_ledcStart == 255) { //no more free LEDC channels
@ -556,13 +556,19 @@ uint32_t BusPwm::getPixelColor(unsigned pix) const {
void BusPwm::show() {
if (!_valid) return;
const unsigned numPins = getPins();
#ifdef ESP8266
const unsigned analogPeriod = F_CPU / _frequency;
const unsigned maxBri = analogPeriod; // compute to clock cycle accuracy
constexpr bool dithering = false;
constexpr unsigned bitShift = 8; // 256 clocks for dead time, ~3us at 80MHz
#else
// if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor)
// https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1)
const bool dithering = _needsRefresh; // avoid working with bitfield
const unsigned numPins = getPins();
const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8)
[[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits)
#endif
// use CIE brightness formula (linear + cubic) to approximate human eye perceived brightness
// see: https://en.wikipedia.org/wiki/Lightness
unsigned pwmBri = _bri;
@ -582,20 +588,25 @@ void BusPwm::show() {
// Phase shifting requires that LEDC timers are synchronised (see setup()). For PWM CCT (and H-bridge) it is
// also mandatory that both channels use the same timer (pinManager takes care of that).
for (unsigned i = 0; i < numPins; i++) {
unsigned duty = (_data[i] * pwmBri) / 255;
#ifdef ESP8266
if (_reversed) duty = maxBri - duty;
analogWrite(_pins[i], duty);
#else
int deadTime = 0;
unsigned duty = (_data[i] * pwmBri) / 255;
unsigned deadTime = 0;
if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) {
// add dead time between signals (when using dithering, two full 8bit pulses are required)
deadTime = (1+dithering) << bitShift;
// we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap
if (_bri >= 254 && duty >= maxBri / 2 && duty < maxBri) duty -= deadTime << 1; // shorten duty of larger signal except if full on
if (_reversed) deadTime = -deadTime; // need to invert dead time to make phaseshift go the opposite way so low signals dont overlap
if (_bri >= 254 && duty >= maxBri / 2 && duty < maxBri) {
duty -= deadTime << 1; // shorten duty of larger signal except if full on
}
}
if (_reversed) duty = maxBri - duty;
if (_reversed) {
if (i) hPoint += duty; // align start at time zero
duty = maxBri - duty;
}
#ifdef ESP8266
//stopWaveform(_pins[i]); // can cause the waveform to miss a cycle. instead we risk crossovers.
startWaveformClockCycles(_pins[i], duty, analogPeriod - duty, 0, i ? _pins[0] : -1, hPoint, false);
#else
unsigned channel = _ledcStart + i;
unsigned gr = channel/8; // high/low speed group
unsigned ch = channel%8; // group channel
@ -604,9 +615,11 @@ void BusPwm::show() {
LEDC.channel_group[gr].channel[ch].duty.duty = duty << ((!dithering)*4); // lowest 4 bits are used for dithering, shift by 4 bits if not using dithering
LEDC.channel_group[gr].channel[ch].hpoint.hpoint = hPoint >> bitShift; // hPoint is at _depth resolution (needs shifting if dithering)
ledc_update_duty((ledc_mode_t)gr, (ledc_channel_t)ch);
hPoint += duty + deadTime; // offset to cascade the signals
if (hPoint >= maxBri) hPoint = 0; // offset it out of bounds, reset
#endif
if (!_reversed) hPoint += duty;
hPoint += deadTime; // offset to cascade the signals
if (hPoint >= maxBri) hPoint -= maxBri; // offset is out of bounds, reset
}
}
@ -646,7 +659,7 @@ void BusPwm::deallocatePins() {
}
BusOnOff::BusOnOff(BusConfig &bc)
BusOnOff::BusOnOff(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed)
, _onoffdata(0)
{
@ -699,7 +712,7 @@ std::vector<LEDType> BusOnOff::getLEDTypes() {
};
}
BusNetwork::BusNetwork(BusConfig &bc)
BusNetwork::BusNetwork(const BusConfig &bc)
: Bus(bc.type, bc.start, bc.autoWhite, bc.count)
, _broadcastLock(false)
{
@ -778,7 +791,7 @@ void BusNetwork::cleanup() {
//utility to get the approx. memory usage of a given BusConfig
uint32_t BusManager::memUsage(BusConfig &bc) {
uint32_t BusManager::memUsage(const BusConfig &bc) {
if (Bus::isOnOff(bc.type) || Bus::isPWM(bc.type)) return OUTPUT_MAX_PINS;
unsigned len = bc.count + bc.skipAmount;
@ -803,7 +816,7 @@ uint32_t BusManager::memUsage(unsigned maxChannels, unsigned maxCount, unsigned
return (maxChannels * maxCount * minBuses * multiplier);
}
int BusManager::add(BusConfig &bc) {
int BusManager::add(const BusConfig &bc) {
if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1;
if (Bus::isVirtual(bc.type)) {
busses[numBusses] = new BusNetwork(bc);

View File

@ -198,7 +198,7 @@ class Bus {
class BusDigital : public Bus {
public:
BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com);
BusDigital(const BusConfig &bc, uint8_t nr, const ColorOrderMap &com);
~BusDigital() { cleanup(); }
void show() override;
@ -250,7 +250,7 @@ class BusDigital : public Bus {
class BusPwm : public Bus {
public:
BusPwm(BusConfig &bc);
BusPwm(const BusConfig &bc);
~BusPwm() { cleanup(); }
void setPixelColor(unsigned pix, uint32_t c) override;
@ -277,7 +277,7 @@ class BusPwm : public Bus {
class BusOnOff : public Bus {
public:
BusOnOff(BusConfig &bc);
BusOnOff(const BusConfig &bc);
~BusOnOff() { cleanup(); }
void setPixelColor(unsigned pix, uint32_t c) override;
@ -296,7 +296,7 @@ class BusOnOff : public Bus {
class BusNetwork : public Bus {
public:
BusNetwork(BusConfig &bc);
BusNetwork(const BusConfig &bc);
~BusNetwork() { cleanup(); }
bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out
@ -379,12 +379,12 @@ class BusManager {
BusManager() {};
//utility to get the approx. memory usage of a given BusConfig
static uint32_t memUsage(BusConfig &bc);
static uint32_t memUsage(const BusConfig &bc);
static uint32_t memUsage(unsigned channels, unsigned count, unsigned buses = 1);
static uint16_t currentMilliamps() { return _milliAmpsUsed + MA_FOR_ESP; }
static uint16_t ablMilliampsMax() { return _milliAmpsMax; }
static int add(BusConfig &bc);
static int add(const BusConfig &bc);
static void useParallelOutput(); // workaround for inaccessible PolyBus
//do not call this method from system context (network callback)

View File

@ -90,12 +90,12 @@ void doublePressAction(uint8_t b)
#endif
}
bool isButtonPressed(uint8_t i)
bool isButtonPressed(uint8_t b)
{
if (btnPin[i]<0) return false;
unsigned pin = btnPin[i];
if (btnPin[b]<0) return false;
unsigned pin = btnPin[b];
switch (buttonType[i]) {
switch (buttonType[b]) {
case BTN_TYPE_NONE:
case BTN_TYPE_RESERVED:
break;
@ -113,7 +113,7 @@ bool isButtonPressed(uint8_t i)
#ifdef SOC_TOUCH_VERSION_2 //ESP32 S2 and S3 provide a function to check touch state (state is updated in interrupt)
if (touchInterruptGetLastStatus(pin)) return true;
#else
if (digitalPinToTouchChannel(btnPin[i]) >= 0 && touchRead(pin) <= touchThreshold) return true;
if (digitalPinToTouchChannel(btnPin[b]) >= 0 && touchRead(pin) <= touchThreshold) return true;
#endif
#endif
break;

View File

@ -114,8 +114,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(strip.correctWB, hw_led["cct"]);
CJSON(strip.cctFromRgb, hw_led[F("cr")]);
CJSON(cctICused, hw_led[F("ic")]);
CJSON(strip.cctBlending, hw_led[F("cb")]);
Bus::setCCTBlend(strip.cctBlending);
uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend();
Bus::setCCTBlend(cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
CJSON(useGlobalLedBuffer, hw_led[F("ld")]);
@ -444,12 +444,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
JsonObject light_tr = light["tr"];
CJSON(fadeTransition, light_tr["mode"]);
CJSON(modeBlending, light_tr["fx"]);
int tdd = light_tr["dur"] | -1;
if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100;
strip.setTransition(fadeTransition ? transitionDelayDefault : 0);
CJSON(strip.paletteFade, light_tr["pal"]);
strip.setTransition(transitionDelayDefault);
CJSON(randomPaletteChangeTime, light_tr[F("rpc")]);
CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]);
@ -522,6 +519,14 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
tdd = if_live[F("timeout")] | -1;
if (tdd >= 0) realtimeTimeoutMs = tdd * 100;
#ifdef WLED_ENABLE_DMX_INPUT
CJSON(dmxInputTransmitPin, if_live_dmx[F("inputRxPin")]);
CJSON(dmxInputReceivePin, if_live_dmx[F("inputTxPin")]);
CJSON(dmxInputEnablePin, if_live_dmx[F("inputEnablePin")]);
CJSON(dmxInputPort, if_live_dmx[F("dmxInputPort")]);
#endif
CJSON(arlsForceMaxBri, if_live[F("maxbri")]);
CJSON(arlsDisableGammaCorrection, if_live[F("no-gc")]); // false
CJSON(arlsOffset, if_live[F("offset")]); // 0
@ -819,7 +824,7 @@ void serializeConfig() {
hw_led["cct"] = strip.correctWB;
hw_led[F("cr")] = strip.cctFromRgb;
hw_led[F("ic")] = cctICused;
hw_led[F("cb")] = strip.cctBlending;
hw_led[F("cb")] = Bus::getCCTBlend();
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override
hw_led[F("ld")] = useGlobalLedBuffer;
@ -937,10 +942,7 @@ void serializeConfig() {
light_gc["val"] = gammaCorrectVal;
JsonObject light_tr = light.createNestedObject("tr");
light_tr["mode"] = fadeTransition;
light_tr["fx"] = modeBlending;
light_tr["dur"] = transitionDelayDefault / 100;
light_tr["pal"] = strip.paletteFade;
light_tr[F("rpc")] = randomPaletteChangeTime;
light_tr[F("hrp")] = useHarmonicRandomPalette;
@ -1001,6 +1003,12 @@ void serializeConfig() {
if_live_dmx[F("addr")] = DMXAddress;
if_live_dmx[F("dss")] = DMXSegmentSpacing;
if_live_dmx["mode"] = DMXMode;
#ifdef WLED_ENABLE_DMX_INPUT
if_live_dmx[F("inputRxPin")] = dmxInputTransmitPin;
if_live_dmx[F("inputTxPin")] = dmxInputReceivePin;
if_live_dmx[F("inputEnablePin")] = dmxInputEnablePin;
if_live_dmx[F("dmxInputPort")] = dmxInputPort;
#endif
if_live[F("timeout")] = realtimeTimeoutMs / 100;
if_live[F("maxbri")] = arlsForceMaxBri;

View File

@ -122,7 +122,7 @@ void setRandomColor(byte* rgb)
* generates a random palette based on harmonic color theory
* takes a base palette as the input, it will choose one color of the base palette and keep it
*/
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette)
{
CHSV palettecolors[4]; // array of colors for the new palette
uint8_t keepcolorposition = hw_random8(4); // color position of current random palette to keep
@ -391,7 +391,7 @@ void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www
rgb[2] = byte(255.0f*b);
}
void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
void colorRGBtoXY(const byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy)
{
float X = rgb[0] * 0.664511f + rgb[1] * 0.154324f + rgb[2] * 0.162028f;
float Y = rgb[0] * 0.283881f + rgb[1] * 0.668433f + rgb[2] * 0.047685f;
@ -402,7 +402,7 @@ void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.develo
#endif // WLED_DISABLE_HUESYNC
//RRGGBB / WWRRGGBB order for hex
void colorFromDecOrHexString(byte* rgb, char* in)
void colorFromDecOrHexString(byte* rgb, const char* in)
{
if (in[0] == 0) return;
char first = in[0];

View File

@ -204,6 +204,7 @@
#define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h"
#define USERMOD_ID_PIXELS_DICE_TRAY 54 //Usermod "pixels_dice_tray.h"
#define USERMOD_ID_DEEP_SLEEP 55 //Usermod "usermod_deep_sleep.h"
#define USERMOD_ID_RF433 56 //Usermod "usermod_v2_RF433.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
@ -249,6 +250,7 @@
#define REALTIME_MODE_ARTNET 6
#define REALTIME_MODE_TPM2NET 7
#define REALTIME_MODE_DDP 8
#define REALTIME_MODE_DMX 9
//realtime override modes
#define REALTIME_OVERRIDE_NONE 0
@ -558,8 +560,25 @@
#endif
#endif
//#define MIN_HEAP_SIZE (8k for AsyncWebServer)
#define MIN_HEAP_SIZE 8192
//#define MIN_HEAP_SIZE
#define MIN_HEAP_SIZE 2048
// Web server limits
#ifdef ESP8266
// Minimum heap to consider handling a request
#define WLED_REQUEST_MIN_HEAP (8*1024)
// Estimated maximum heap required by any one request
#define WLED_REQUEST_HEAP_USAGE (6*1024)
#else
// ESP32 TCP stack needs much more RAM than ESP8266
// Minimum heap remaining before queuing a request
#define WLED_REQUEST_MIN_HEAP (12*1024)
// Estimated maximum heap required by any one request
#define WLED_REQUEST_HEAP_USAGE (12*1024)
#endif
// Maximum number of requests in queue; absolute cap on web server resource usage.
// Websockets do not count against this limit.
#define WLED_REQUEST_MAX_QUEUE 6
// Maximum size of node map (list of other WLED instances)
#ifdef ESP8266

View File

@ -128,7 +128,7 @@
<div style="padding: 8px 0;" id="btns">
<button class="btn btn-xs" title="File editor" type="button" id="edit" onclick="window.location.href=getURL('/edit')"><i class="icons btn-icon">&#xe2c6;</i></button>
<button class="btn btn-xs" title="Pixel Magic Tool" type="button" id="pxmb" onclick="window.location.href=getURL('/pxmagic.htm')"><i class="icons btn-icon">&#xe410;</i></button>
<button class="btn btn-xs" title="Add custom palette" type="button" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon">&#xe18a;</i></button>
<button class="btn btn-xs" title="Add custom palette" type="button" id="adPal" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon">&#xe18a;</i></button>
<button class="btn btn-xs" title="Remove last custom palette" type="button" id="rmPal" onclick="palettesData=null;localStorage.removeItem('wledPalx');requestJson({rmcpal:true});setTimeout(loadPalettes,250,loadPalettesData);"><i class="icons btn-icon">&#xe037;</i></button>
</div>
<p class="labels hd" id="pall"><i class="icons sel-icon" onclick="tglHex()">&#xe2b3;</i> Color palette</p>
@ -266,7 +266,29 @@
<div id="segutil2">
<button class="btn btn-s" id="rsbtn" onclick="rSegs()">Reset segments</button>
</div>
<p>Transition: <input id="tt" type="number" min="0" max="65.5" step="0.1" value="0.7">&nbsp;s</p>
<p>Transition: <input id="tt" type="number" min="0" max="65.5" step="0.1" value="0.7" onchange="parseFloat(this.value)===0?gId('bsp').classList.add('hide'):gId('bsp').classList.remove('hide');">&nbsp;s</p>
<p id="bsp">Blend:
<select id="bs" class="sel-sg" onchange="requestJson({'bs':parseInt(this.value)})">
<option value="0">Fade</option>
<option value="1">Fairy Dust</option>
<option value="2">Swipe right</option>
<option value="3">Swipe left</option>
<option value="16">Push right</option>
<option value="17">Push left</option>
<option value="4">Pinch-out</option>
<option value="5">Inside-out</option>
<option value="6" data-type="2D">Swipe up</option>
<option value="7" data-type="2D">Swipe down</option>
<option value="8" data-type="2D">Open H</option>
<option value="9" data-type="2D">Open V</option>
<option value="18" data-type="2D">Push up</option>
<option value="19" data-type="2D">Push down</option>
<option value="20" data-type="2D">Push TL</option>
<option value="21" data-type="2D">Push TR</option>
<option value="22" data-type="2D">Push BR</option>
<option value="23" data-type="2D">Push BL</option>
</select>
</p>
<p id="ledmap" class="hide"></p>
</div>

View File

@ -677,8 +677,10 @@ function parseInfo(i) {
isM = mw>0 && mh>0;
if (!isM) {
gId("filter2D").classList.add('hide');
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';});
} else {
gId("filter2D").classList.remove('hide');
gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';});
}
// if (i.noaudio) {
// gId("filterVol").classList.add("hide");
@ -1437,6 +1439,9 @@ function readState(s,command=false)
tr = s.transition;
gId('tt').value = tr/10;
gId('bs').value = s.bs || 0;
if (tr===0) gId('bsp').classList.add('hide')
else gId('bsp').classList.remove('hide')
populateSegments(s);
var selc=0;
@ -1698,6 +1703,7 @@ function requestJson(command=null)
var tn = parseInt(t.value*10);
if (tn != tr) command.transition = tn;
}
//command.bs = parseInt(gId('bs').value);
req = JSON.stringify(command);
if (req.length > 1340) useWs = false; // do not send very long requests over websocket
if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false; // esp8266 can only handle 500 bytes

View File

@ -832,12 +832,7 @@ Swap: <select id="xw${s}" name="XW${s}">
Use Gamma value: <input name="GV" type="number" class="m" placeholder="2.8" min="1" max="3" step="0.1" required><br><br>
Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
<h3>Transitions</h3>
Enable transitions: <input type="checkbox" name="TF" onchange="gId('tran').style.display=this.checked?'inline':'none';"><br>
<span id="tran">
Effect blending: <input type="checkbox" name="EB"><br>
Default transition time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
Palette transitions: <input type="checkbox" name="PF"><br>
</span>
Default transition time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
<i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br>
Use harmonic <i>Random Cycle</i> Palette: <input type="checkbox" name="TH"><br>
<h3>Timed light</h3>

View File

@ -40,6 +40,8 @@
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}
function hideDMXInput(){gId("dmxInput").style.display="none";}
function hideNoDMXInput(){gId("dmxInputOff").style.display="none";}
</script>
<style>@import url("style.css");</style>
</head>
@ -151,6 +153,19 @@ Timeout: <input name="ET" type="number" min="1" max="65000" required> ms<br>
Force max brightness: <input type="checkbox" name="FB"><br>
Disable realtime gamma correction: <input type="checkbox" name="RG"><br>
Realtime LED offset: <input name="WO" type="number" min="-255" max="255" required>
<div id="dmxInput">
<h4>Wired DMX Input Pins</h4>
DMX RX: <input name="IDMR" type="number" min="-1" max="99">RO<br/>
DMX TX: <input name="IDMT" type="number" min="-1" max="99">DI<br/>
DMX Enable: <input name="IDME" type="number" min="-1" max="99">RE+DE<br/>
DMX Port: <input name="IDMP" type="number" min="1" max="2"><br/>
</div>
<div id="dmxInputOff">
<br><em style="color:darkorange">This firmware build does not include DMX Input support. <br></em>
</div>
<div id="dmxOnOff2">
<br><em style="color:darkorange">This firmware build does not include DMX output support. <br></em>
</div>
<hr class="sml">
<h3>Alexa Voice Assistant</h3>
<div id="NoAlexa" class="hide">

280
wled00/dmx_input.cpp Normal file
View File

@ -0,0 +1,280 @@
#include "wled.h"
#ifdef WLED_ENABLE_DMX_INPUT
#ifdef ESP8266
#error DMX input is only supported on ESP32
#endif
#include "dmx_input.h"
#include <rdm/responder.h>
void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context)
{
DMXInput *dmx = static_cast<DMXInput *>(context);
if (!dmx) {
DEBUG_PRINTLN("DMX: Error: no context in rdmPersonalityChangedCb");
return;
}
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint8_t personality = dmx_get_current_personality(dmx->inputPortNum);
DMXMode = std::min(DMX_MODE_PRESET, std::max(DMX_MODE_SINGLE_RGB, int(personality)));
doSerializeConfig = true;
DEBUG_PRINTF("DMX personality changed to to: %d\n", DMXMode);
}
}
void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context)
{
DMXInput *dmx = static_cast<DMXInput *>(context);
if (!dmx) {
DEBUG_PRINTLN("DMX: Error: no context in rdmAddressChangedCb");
return;
}
if (header->cc == RDM_CC_SET_COMMAND_RESPONSE) {
const uint16_t addr = dmx_get_start_address(dmx->inputPortNum);
DMXAddress = std::min(512, int(addr));
doSerializeConfig = true;
DEBUG_PRINTF("DMX start addr changed to: %d\n", DMXAddress);
}
}
static dmx_config_t createConfig()
{
dmx_config_t config;
config.pd_size = 255;
config.dmx_start_address = DMXAddress;
config.model_id = 0;
config.product_category = RDM_PRODUCT_CATEGORY_FIXTURE;
config.software_version_id = VERSION;
strcpy(config.device_label, "WLED_MM");
const std::string versionString = "WLED_V" + std::to_string(VERSION);
strncpy(config.software_version_label, versionString.c_str(), 32);
config.software_version_label[32] = '\0'; // zero termination in case versionString string was longer than 32 chars
config.personalities[0].description = "SINGLE_RGB";
config.personalities[0].footprint = 3;
config.personalities[1].description = "SINGLE_DRGB";
config.personalities[1].footprint = 4;
config.personalities[2].description = "EFFECT";
config.personalities[2].footprint = 15;
config.personalities[3].description = "MULTIPLE_RGB";
config.personalities[3].footprint = std::min(512, int(strip.getLengthTotal()) * 3);
config.personalities[4].description = "MULTIPLE_DRGB";
config.personalities[4].footprint = std::min(512, int(strip.getLengthTotal()) * 3 + 1);
config.personalities[5].description = "MULTIPLE_RGBW";
config.personalities[5].footprint = std::min(512, int(strip.getLengthTotal()) * 4);
config.personalities[6].description = "EFFECT_W";
config.personalities[6].footprint = 18;
config.personalities[7].description = "EFFECT_SEGMENT";
config.personalities[7].footprint = std::min(512, strip.getSegmentsNum() * 15);
config.personalities[8].description = "EFFECT_SEGMENT_W";
config.personalities[8].footprint = std::min(512, strip.getSegmentsNum() * 18);
config.personalities[9].description = "PRESET";
config.personalities[9].footprint = 1;
config.personality_count = 10;
// rdm personalities are numbered from 1, thus we can just set the DMXMode directly.
config.current_personality = DMXMode;
return config;
}
void dmxReceiverTask(void *context)
{
DMXInput *instance = static_cast<DMXInput *>(context);
if (instance == nullptr) {
return;
}
if (instance->installDriver()) {
while (true) {
instance->updateInternal();
}
}
}
bool DMXInput::installDriver()
{
const auto config = createConfig();
DEBUG_PRINTF("DMX port: %u\n", inputPortNum);
if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) {
DEBUG_PRINTF("Error: Failed to install dmx driver\n");
return false;
}
DEBUG_PRINTF("Listening for DMX on pin %u\n", rxPin);
DEBUG_PRINTF("Sending DMX on pin %u\n", txPin);
DEBUG_PRINTF("DMX enable pin is: %u\n", enPin);
dmx_set_pin(inputPortNum, txPin, rxPin, enPin);
rdm_register_dmx_start_address(inputPortNum, rdmAddressChangedCb, this);
rdm_register_dmx_personality(inputPortNum, rdmPersonalityChangedCb, this);
initialized = true;
return true;
}
void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum)
{
#ifdef WLED_ENABLE_DMX_OUTPUT
//TODO add again once dmx output has been merged
// if(inputPortNum == dmxOutputPort)
// {
// DEBUG_PRINTF("DMXInput: Error: Input port == output port");
// return;
// }
#endif
if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) {
this->inputPortNum = inputPortNum;
}
else {
DEBUG_PRINTF("DMXInput: Error: invalid inputPortNum: %d\n", inputPortNum);
return;
}
if (rxPin > 0 && enPin > 0 && txPin > 0) {
const managed_pin_type pins[] = {
{(int8_t)txPin, false}, // these are not used as gpio pins, thus isOutput is always false.
{(int8_t)rxPin, false},
{(int8_t)enPin, false}};
const bool pinsAllocated = PinManager::allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT);
if (!pinsAllocated) {
DEBUG_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n");
DEBUG_PRINTF("rx in use by: %s\n", PinManager::getPinOwner(rxPin));
DEBUG_PRINTF("tx in use by: %s\n", PinManager::getPinOwner(txPin));
DEBUG_PRINTF("en in use by: %s\n", PinManager::getPinOwner(enPin));
return;
}
this->rxPin = rxPin;
this->txPin = txPin;
this->enPin = enPin;
// put dmx receiver into seperate task because it should not be blocked
// pin to core 0 because wled is running on core 1
xTaskCreatePinnedToCore(dmxReceiverTask, "DMX_RCV_TASK", 10240, this, 2, &task, 0);
if (!task) {
DEBUG_PRINTF("Error: Failed to create dmx rcv task");
}
}
else {
DEBUG_PRINTLN("DMX input disabled due to rxPin, enPin or txPin not set");
return;
}
}
void DMXInput::updateInternal()
{
if (!initialized) {
return;
}
checkAndUpdateConfig();
dmx_packet_t packet;
unsigned long now = millis();
if (dmx_receive(inputPortNum, &packet, DMX_TIMEOUT_TICK)) {
if (!packet.err) {
if(!connected) {
DEBUG_PRINTLN("DMX Input - connected");
}
connected = true;
identify = isIdentifyOn();
if (!packet.is_rdm) {
const std::lock_guard<std::mutex> lock(dmxDataLock);
dmx_read(inputPortNum, dmxdata, packet.size);
}
}
else {
connected = false;
}
}
else {
if(connected) {
DEBUG_PRINTLN("DMX Input - disconnected");
}
connected = false;
}
}
void DMXInput::update()
{
if (identify) {
turnOnAllLeds();
}
else if (connected) {
const std::lock_guard<std::mutex> lock(dmxDataLock);
handleDMXData(1, 512, dmxdata, REALTIME_MODE_DMX, 0);
}
}
void DMXInput::turnOnAllLeds()
{
// TODO not sure if this is the correct way?
const uint16_t numPixels = strip.getLengthTotal();
for (uint16_t i = 0; i < numPixels; ++i)
{
strip.setPixelColor(i, 255, 255, 255, 255);
}
strip.setBrightness(255, true);
strip.show();
}
void DMXInput::disable()
{
if (initialized) {
dmx_driver_disable(inputPortNum);
}
}
void DMXInput::enable()
{
if (initialized) {
dmx_driver_enable(inputPortNum);
}
}
bool DMXInput::isIdentifyOn() const
{
uint8_t identify = 0;
const bool gotIdentify = rdm_get_identify_device(inputPortNum, &identify);
// gotIdentify should never be false because it is a default parameter in rdm
// but just in case we check for it anyway
return bool(identify) && gotIdentify;
}
void DMXInput::checkAndUpdateConfig()
{
/**
* The global configuration variables are modified by the web interface.
* If they differ from the driver configuration, we have to update the driver
* configuration.
*/
const uint8_t currentPersonality = dmx_get_current_personality(inputPortNum);
if (currentPersonality != DMXMode) {
DEBUG_PRINTF("DMX personality has changed from %d to %d\n", currentPersonality, DMXMode);
dmx_set_current_personality(inputPortNum, DMXMode);
}
const uint16_t currentAddr = dmx_get_start_address(inputPortNum);
if (currentAddr != DMXAddress) {
DEBUG_PRINTF("DMX address has changed from %d to %d\n", currentAddr, DMXAddress);
dmx_set_start_address(inputPortNum, DMXAddress);
}
}
#endif

73
wled00/dmx_input.h Normal file
View File

@ -0,0 +1,73 @@
#pragma once
#include <cstdint>
#include <esp_dmx.h>
#include <atomic>
#include <mutex>
/*
* Support for DMX/RDM input via serial (e.g. max485) on ESP32
* ESP32 Library from:
* https://github.com/someweisguy/esp_dmx
*/
class DMXInput
{
public:
void init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPortNum);
void update();
/**disable dmx receiver (do this before disabling the cache)*/
void disable();
void enable();
private:
/// @return true if rdm identify is active
bool isIdentifyOn() const;
/**
* Checks if the global dmx config has changed and updates the changes in rdm
*/
void checkAndUpdateConfig();
/// overrides everything and turns on all leds
void turnOnAllLeds();
/// installs the dmx driver
/// @return false on fail
bool installDriver();
/// is called by the dmx receive task regularly to receive new dmx data
void updateInternal();
// is invoked whenver the dmx start address is changed via rdm
friend void rdmAddressChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context);
// is invoked whenever the personality is changed via rdm
friend void rdmPersonalityChangedCb(dmx_port_t dmxPort, const rdm_header_t *header,
void *context);
/// The internal dmx task.
/// This is the main loop of the dmx receiver. It never returns.
friend void dmxReceiverTask(void * context);
uint8_t inputPortNum = 255;
uint8_t rxPin = 255;
uint8_t txPin = 255;
uint8_t enPin = 255;
/// is written to by the dmx receive task.
byte dmxdata[DMX_PACKET_SIZE];
/// True once the dmx input has been initialized successfully
bool initialized = false; // true once init finished successfully
/// True if dmx is currently connected
std::atomic<bool> connected{false};
std::atomic<bool> identify{false};
/// Timestamp of the last time a dmx frame was received
unsigned long lastUpdate = 0;
/// Taskhandle of the dmx task that is running in the background
TaskHandle_t task;
/// Guards access to dmxData
std::mutex dmxDataLock;
};

View File

@ -1,7 +1,7 @@
#include "wled.h"
/*
* Support for DMX Output via MAX485.
* Support for DMX output via serial (e.g. MAX485).
* Change the output pin in src/dependencies/ESPDMX.cpp, if needed (ESP8266)
* Change the output pin in src/dependencies/SparkFunDMX.cpp, if needed (ESP32)
* ESP8266 Library from:
@ -12,7 +12,7 @@
#ifdef WLED_ENABLE_DMX
void handleDMX()
void handleDMXOutput()
{
// don't act, when in DMX Proxy mode
if (e131ProxyUniverse != 0) return;
@ -68,11 +68,14 @@ void handleDMX()
dmx.update(); // update the DMX bus
}
void initDMX() {
void initDMXOutput() {
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2)
dmx.init(512); // initialize with bus length
#else
dmx.initWrite(512); // initialize with bus length
#endif
}
#else
void initDMXOutput(){}
void handleDMXOutput() {}
#endif

View File

@ -116,6 +116,11 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
// update status info
realtimeIP = clientIP;
handleDMXData(uni, dmxChannels, e131_data, mde, previousUniverses);
}
void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses) {
byte wChannel = 0;
unsigned totalLen = strip.getLengthTotal();
unsigned availDMXLen = 0;
@ -130,7 +135,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
}
// DMX data in Art-Net packet starts at index 0, for E1.31 at index 1
if (protocol == P_ARTNET && dataOffset > 0) {
if (mde == REALTIME_MODE_ARTNET && dataOffset > 0) {
dataOffset--;
}
@ -211,7 +216,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
else
dataOffset = DMXAddress;
// Modify address for Art-Net data
if (protocol == P_ARTNET && dataOffset > 0)
if (mde == REALTIME_MODE_ARTNET && dataOffset > 0)
dataOffset--;
// Skip out of universe addresses
if (dataOffset > dmxChannels - dmxEffectChannels + 1)
@ -285,7 +290,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){
}
} else {
// All subsequent universes start at the first channel.
dmxOffset = (protocol == P_ARTNET) ? 0 : 1;
dmxOffset = (mde == REALTIME_MODE_ARTNET) ? 0 : 1;
const unsigned dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0;
unsigned ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed;
previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse;

View File

@ -161,12 +161,12 @@ class NeoGammaWLEDMethod {
};
#define gamma32(c) NeoGammaWLEDMethod::Correct32(c)
#define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c)
[[gnu::hot]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
[[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend);
inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); };
[[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
[[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
[[gnu::hot]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette);
[[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false);
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
[[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND);
CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
CRGBPalette16 generateRandomPalette();
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
@ -176,33 +176,38 @@ inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) <<
void colorKtoRGB(uint16_t kelvin, byte* rgb);
void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb
void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO
void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO
void colorFromDecOrHexString(byte* rgb, char* in);
void colorRGBtoXY(const byte* rgb, float* xy); // only defined if huesync disabled TODO
void colorFromDecOrHexString(byte* rgb, const char* in);
bool colorFromHexString(byte* rgb, const char* in);
uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb);
uint16_t approximateKelvinFromRGB(uint32_t rgb);
void setRandomColor(byte* rgb);
//dmx.cpp
void initDMX();
void handleDMX();
//dmx_output.cpp
void initDMXOutput();
void handleDMXOutput();
//dmx_input.cpp
void initDMXInput();
void handleDMXInput();
//e131.cpp
void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol);
void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8_t mde, uint8_t previousUniverses);
void handleArtnetPollReply(IPAddress ipAddress);
void prepareArtnetPollReply(ArtPollReply* reply);
void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t portAddress);
//file.cpp
bool handleFileRead(AsyncWebServerRequest*, String path);
bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content);
bool writeObjectToFile(const char* file, const char* key, JsonDocument* content);
bool writeObjectToFileUsingId(const char* file, uint16_t id, const JsonDocument* content);
bool writeObjectToFile(const char* file, const char* key, const JsonDocument* content);
bool readObjectFromFileUsingId(const char* file, uint16_t id, JsonDocument* dest);
bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest);
void updateFSInfo();
void closeFile();
inline bool writeObjectToFileUsingId(const String &file, uint16_t id, JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); };
inline bool writeObjectToFile(const String &file, const char* key, JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
inline bool writeObjectToFileUsingId(const String &file, uint16_t id, const JsonDocument* content) { return writeObjectToFileUsingId(file.c_str(), id, content); };
inline bool writeObjectToFile(const String &file, const char* key, const JsonDocument* content) { return writeObjectToFile(file.c_str(), key, content); };
inline bool readObjectFromFileUsingId(const String &file, uint16_t id, JsonDocument* dest) { return readObjectFromFileUsingId(file.c_str(), id, dest); };
inline bool readObjectFromFile(const String &file, const char* key, JsonDocument* dest) { return readObjectFromFile(file.c_str(), key, dest); };
@ -214,6 +219,19 @@ void onHueConnect(void* arg, AsyncClient* client);
void sendHuePoll();
void onHueData(void* arg, AsyncClient* client, void *data, size_t len);
#include "FX.h" // must be below colors.cpp declarations (potentially due to duplicate declarations of e.g. color_blend)
//image_loader.cpp
#ifdef WLED_ENABLE_GIF
bool fileSeekCallback(unsigned long position);
unsigned long filePositionCallback(void);
int fileReadCallback(void);
int fileReadBlockCallback(void * buffer, int numberOfBytes);
int fileSizeCallback(void);
byte renderImageToSegment(Segment &seg);
void endImagePlayback(Segment* seg);
#endif
//improv.cpp
enum ImprovRPCType {
Command_Wifi = 0x01,
@ -243,11 +261,11 @@ void handleIR();
bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0);
bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0);
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
void serializeInfo(JsonObject root);
void serializeModeNames(JsonArray root);
void serializeModeData(JsonArray root);
void serializeModeNames(JsonArray arr);
void serializeModeData(JsonArray fxdata);
void serveJson(AsyncWebServerRequest* request);
#ifdef WLED_ENABLE_JSONLIVE
bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
@ -318,7 +336,8 @@ void deletePreset(byte index);
bool getPresetName(byte index, String& name);
//remote.cpp
void handleRemote(uint8_t *data, size_t len);
void handleWiZdata(uint8_t *incomingData, size_t len);
void handleRemote();
//set.cpp
bool isAsterisksOnly(const char* str, byte maxLen);
@ -327,7 +346,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=tru
//udp.cpp
void notify(byte callMode, bool followUp=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri=255, bool isRGBW=false);
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri=255, bool isRGBW=false);
void realtimeLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC);
void exitRealtime();
void handleNotifications();
@ -458,10 +477,10 @@ void userLoop();
#include "soc/wdev_reg.h"
#define HW_RND_REGISTER REG_READ(WDEV_RND_REG)
#endif
int getNumVal(const String* req, uint16_t pos);
[[gnu::pure]] int getNumVal(const String* req, uint16_t pos);
void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255);
bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); // getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
bool getBoolVal(JsonVariant elem, bool dflt);
bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form)
[[gnu::pure]] bool getBoolVal(const JsonVariant &elem, bool dflt);
bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255);
size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val);
size_t printSetFormValue(Print& settingsScript, const char* key, int val);
@ -469,8 +488,8 @@ size_t printSetFormValue(Print& settingsScript, const char* key, const char* val
size_t printSetFormIndex(Print& settingsScript, const char* key, int index);
size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val);
void prepareHostname(char* hostname);
bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t module=255);
[[gnu::pure]] bool isAsterisksOnly(const char* str, byte maxLen);
bool requestJSONBufferLock(uint8_t moduleID=255);
void releaseJSONBufferLock();
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen);
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr);
@ -482,8 +501,9 @@ uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest = 0, uint16_t hig
uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest = 0, uint8_t highest = 255, uint32_t timebase = 0, uint8_t phase_offset = 0);
um_data_t* simulateSound(uint8_t simulationId);
void enumerateLedmaps();
uint8_t get_random_wheel_index(uint8_t pos);
float mapf(float x, float in_min, float in_max, float out_min, float out_max);
[[gnu::hot]] uint8_t get_random_wheel_index(uint8_t pos);
[[gnu::hot, gnu::pure]] float mapf(float x, float in_min, float in_max, float out_min, float out_max);
uint32_t hashInt(uint32_t s);
// fast (true) random numbers using hardware RNG, all functions return values in the range lowerlimit to upperlimit-1
// note: for true random numbers with high entropy, do not call faster than every 200ns (5MHz)
@ -543,6 +563,7 @@ float asin_t(float x);
template <typename T> T atan_t(T x);
float floor_t(float x);
float fmod_t(float num, float denom);
uint32_t sqrt32_bw(uint32_t x);
#define sin_t sin_approx
#define cos_t cos_approx
#define tan_t tan_approx

View File

@ -176,7 +176,7 @@ static void writeSpace(size_t l)
if (knownLargestSpace < l) knownLargestSpace = l;
}
bool appendObjectToFile(const char* key, JsonDocument* content, uint32_t s, uint32_t contentLen = 0)
static bool appendObjectToFile(const char* key, const JsonDocument* content, uint32_t s, uint32_t contentLen = 0)
{
#ifdef WLED_DEBUG_FS
DEBUGFS_PRINTLN(F("Append"));
@ -255,14 +255,14 @@ bool appendObjectToFile(const char* key, JsonDocument* content, uint32_t s, uint
return true;
}
bool writeObjectToFileUsingId(const char* file, uint16_t id, JsonDocument* content)
bool writeObjectToFileUsingId(const char* file, uint16_t id, const JsonDocument* content)
{
char objKey[10];
sprintf(objKey, "\"%d\":", id);
return writeObjectToFile(file, objKey, content);
}
bool writeObjectToFile(const char* file, const char* key, JsonDocument* content)
bool writeObjectToFile(const char* file, const char* key, const JsonDocument* content)
{
uint32_t s = 0; //timing
#ifdef WLED_DEBUG_FS

144
wled00/image_loader.cpp Normal file
View File

@ -0,0 +1,144 @@
#include "wled.h"
#ifdef WLED_ENABLE_GIF
#include "GifDecoder.h"
/*
* Functions to render images from filesystem to segments, used by the "Image" effect
*/
File file;
char lastFilename[34] = "/";
GifDecoder<320,320,12,true> decoder;
bool gifDecodeFailed = false;
unsigned long lastFrameDisplayTime = 0, currentFrameDelay = 0;
bool fileSeekCallback(unsigned long position) {
return file.seek(position);
}
unsigned long filePositionCallback(void) {
return file.position();
}
int fileReadCallback(void) {
return file.read();
}
int fileReadBlockCallback(void * buffer, int numberOfBytes) {
return file.read((uint8_t*)buffer, numberOfBytes);
}
int fileSizeCallback(void) {
return file.size();
}
bool openGif(const char *filename) {
file = WLED_FS.open(filename, "r");
if (!file) return false;
return true;
}
Segment* activeSeg;
uint16_t gifWidth, gifHeight;
void screenClearCallback(void) {
activeSeg->fill(0);
}
void updateScreenCallback(void) {}
void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t blue) {
// simple nearest-neighbor scaling
int16_t outY = y * activeSeg->height() / gifHeight;
int16_t outX = x * activeSeg->width() / gifWidth;
// set multiple pixels if upscaling
for (int16_t i = 0; i < (activeSeg->width()+(gifWidth-1)) / gifWidth; i++) {
for (int16_t j = 0; j < (activeSeg->height()+(gifHeight-1)) / gifHeight; j++) {
activeSeg->setPixelColorXY(outX + i, outY + j, gamma8(red), gamma8(green), gamma8(blue));
}
}
}
#define IMAGE_ERROR_NONE 0
#define IMAGE_ERROR_NO_NAME 1
#define IMAGE_ERROR_SEG_LIMIT 2
#define IMAGE_ERROR_UNSUPPORTED_FORMAT 3
#define IMAGE_ERROR_FILE_MISSING 4
#define IMAGE_ERROR_DECODER_ALLOC 5
#define IMAGE_ERROR_GIF_DECODE 6
#define IMAGE_ERROR_FRAME_DECODE 7
#define IMAGE_ERROR_WAITING 254
#define IMAGE_ERROR_PREV 255
// renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment
byte renderImageToSegment(Segment &seg) {
if (!seg.name) return IMAGE_ERROR_NO_NAME;
// disable during effect transition, causes flickering, multiple allocations and depending on image, part of old FX remaining
if (seg.mode != seg.currentMode()) return IMAGE_ERROR_WAITING;
if (activeSeg && activeSeg != &seg) return IMAGE_ERROR_SEG_LIMIT; // only one segment at a time
activeSeg = &seg;
if (strncmp(lastFilename +1, seg.name, 32) != 0) { // segment name changed, load new image
strncpy(lastFilename +1, seg.name, 32);
gifDecodeFailed = false;
if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) {
gifDecodeFailed = true;
return IMAGE_ERROR_UNSUPPORTED_FORMAT;
}
if (file) file.close();
openGif(lastFilename);
if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; }
decoder.setScreenClearCallback(screenClearCallback);
decoder.setUpdateScreenCallback(updateScreenCallback);
decoder.setDrawPixelCallback(drawPixelCallback);
decoder.setFileSeekCallback(fileSeekCallback);
decoder.setFilePositionCallback(filePositionCallback);
decoder.setFileReadCallback(fileReadCallback);
decoder.setFileReadBlockCallback(fileReadBlockCallback);
decoder.setFileSizeCallback(fileSizeCallback);
decoder.alloc();
DEBUG_PRINTLN(F("Starting decoding"));
if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; }
DEBUG_PRINTLN(F("Decoding started"));
}
if (gifDecodeFailed) return IMAGE_ERROR_PREV;
if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; }
//if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; }
// speed 0 = half speed, 128 = normal, 255 = full FX FPS
// TODO: 0 = 4x slow, 64 = 2x slow, 128 = normal, 192 = 2x fast, 255 = 4x fast
uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128;
// TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions
if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING;
decoder.getSize(&gifWidth, &gifHeight);
int result = decoder.decodeFrame(false);
if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; }
currentFrameDelay = decoder.getFrameDelay_ms();
unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate
currentFrameDelay = tooSlowBy > currentFrameDelay ? 0 : currentFrameDelay - tooSlowBy;
lastFrameDisplayTime = millis();
return IMAGE_ERROR_NONE;
}
void endImagePlayback(Segment *seg) {
DEBUG_PRINTLN(F("Image playback end called"));
if (!activeSeg || activeSeg != seg) return;
if (file) file.close();
decoder.dealloc();
gifDecodeFailed = false;
activeSeg = nullptr;
lastFilename[1] = '\0';
DEBUG_PRINTLN(F("Image playback ended"));
}
#endif

View File

@ -68,7 +68,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (elem["n"]) {
// name field exists
if (seg.name) { //clear old name
delete[] seg.name;
free(seg.name);
seg.name = nullptr;
}
@ -77,7 +77,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
if (name != nullptr) len = strlen(name);
if (len > 0) {
if (len > WLED_MAX_SEGNAME_LEN) len = WLED_MAX_SEGNAME_LEN;
seg.name = new char[len+1];
seg.name = static_cast<char*>(malloc(len+1));
if (seg.name) strlcpy(seg.name, name, WLED_MAX_SEGNAME_LEN+1);
} else {
// but is empty (already deleted above)
@ -86,7 +86,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
} else if (start != seg.start || stop != seg.stop) {
// clearing or setting segment without name field
if (seg.name) {
delete[] seg.name;
free(seg.name);
seg.name = nullptr;
}
}
@ -332,15 +332,20 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
tr = root[F("transition")] | -1;
if (tr >= 0) {
transitionDelay = tr * 100;
if (fadeTransition) strip.setTransition(transitionDelay);
strip.setTransition(transitionDelay);
}
}
#ifndef WLED_DISABLE_MODE_BLEND
blendingStyle = root[F("bs")] | blendingStyle;
blendingStyle = constrain(blendingStyle, 0, BLEND_STYLE_COUNT-1);
#endif
// temporary transition (applies only once)
tr = root[F("tt")] | -1;
if (tr >= 0) {
jsonTransitionOnce = true;
if (fadeTransition) strip.setTransition(tr * 100);
strip.setTransition(tr * 100);
}
tr = root[F("tb")] | -1;
@ -493,7 +498,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
return stateResponse;
}
void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset, bool segmentBounds)
void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset, bool segmentBounds)
{
root["id"] = id;
if (segmentBounds) {
@ -568,6 +573,9 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme
root["on"] = (bri > 0);
root["bri"] = briLast;
root[F("transition")] = transitionDelay/100; //in 100ms
#ifndef WLED_DISABLE_MODE_BLEND
root[F("bs")] = blendingStyle;
#endif
}
if (!forPreset) {
@ -761,7 +769,7 @@ void serializeInfo(JsonObject root)
root[F("freeheap")] = ESP.getFreeHeap();
#if defined(ARDUINO_ARCH_ESP32)
if (psramSafe && psramFound()) root[F("psram")] = ESP.getFreePsram();
if (psramFound()) root[F("psram")] = ESP.getFreePsram();
#endif
root[F("uptime")] = millis()/1000 + rolloverMillis*4294967;
@ -1052,7 +1060,7 @@ void serveJson(AsyncWebServerRequest* request)
}
if (!requestJSONBufferLock(17)) {
serveJsonError(request, 503, ERR_NOBUF);
request->deferResponse();
return;
}
// releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer)

View File

@ -71,10 +71,9 @@ byte scaledBri(byte in)
}
//applies global brightness
//applies global temporary brightness (briT) to strip
void applyBri() {
if (!realtimeMode || !arlsForceMaxBri)
{
if (!(realtimeMode && arlsForceMaxBri)) {
//DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld);
strip.setBrightness(scaledBri(briT));
}
@ -86,6 +85,7 @@ void applyFinalBri() {
briOld = bri;
briT = bri;
applyBri();
strip.trigger(); // force one last update
}
@ -129,30 +129,23 @@ void stateUpdated(byte callMode) {
// notify usermods of state change
UsermodManager::onStateChange(callMode);
if (fadeTransition) {
if (strip.getTransition() == 0) {
jsonTransitionOnce = false;
transitionActive = false;
applyFinalBri();
strip.trigger();
return;
}
if (transitionActive) {
briOld = briT;
} else
strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true;
transitionStartTime = millis();
} else {
if (strip.getTransition() == 0) {
jsonTransitionOnce = false;
transitionActive = false;
applyFinalBri();
strip.trigger();
return;
}
if (transitionActive) {
briOld = briT;
} else
strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true;
transitionStartTime = millis();
}
void updateInterfaces(uint8_t callMode)
{
void updateInterfaces(uint8_t callMode) {
if (!interfaceUpdateCallMode || millis() - lastInterfaceUpdate < INTERFACE_UPDATE_COOLDOWN) return;
sendDataWs();
@ -173,8 +166,7 @@ void updateInterfaces(uint8_t callMode)
}
void handleTransitions()
{
void handleTransitions() {
//handle still pending interface update
updateInterfaces(interfaceUpdateCallMode);
@ -205,8 +197,7 @@ void colorUpdated(byte callMode) {
}
void handleNightlight()
{
void handleNightlight() {
unsigned long now = millis();
if (now < 100 && lastNlUpdate > 0) lastNlUpdate = 0; // take care of millis() rollover
if (now - lastNlUpdate < 100) return; // allow only 10 NL updates per second
@ -286,7 +277,6 @@ void handleNightlight()
}
//utility for FastLED to use our custom timer
uint32_t get_millisecond_timer()
{
uint32_t get_millisecond_timer() {
return strip.now;
}

View File

@ -22,7 +22,7 @@ bool parseLx(int lxValue, byte* rgbw)
} else if ((lxValue >= 200000000) && (lxValue <= 201006500)) {
// Loxone Lumitech
ok = true;
float tmpBri = floor((lxValue - 200000000) / 10000); ;
float tmpBri = floor((lxValue - 200000000) / 10000);
uint16_t ct = (lxValue - 200000000) - (((uint8_t)tmpBri) * 10000);
tmpBri *= 2.55f;

View File

@ -68,8 +68,8 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
}
if (index == 0) { // start (1st partial packet or the only packet)
if (payloadStr) delete[] payloadStr; // fail-safe: release buffer
payloadStr = new char[total+1]; // allocate new buffer
if (payloadStr) free(payloadStr); // fail-safe: release buffer
payloadStr = static_cast<char*>(malloc(total+1)); // allocate new buffer
}
if (payloadStr == nullptr) return; // buffer not allocated
@ -94,7 +94,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
} else {
// Non-Wled Topic used here. Probably a usermod subscribed to this topic.
UsermodManager::onMqttMessage(topic, payloadStr);
delete[] payloadStr;
free(payloadStr);
payloadStr = nullptr;
return;
}
@ -124,7 +124,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp
// topmost topic (just wled/MAC)
parseMQTTBriPayload(payloadStr);
}
delete[] payloadStr;
free(payloadStr);
payloadStr = nullptr;
}

View File

@ -224,7 +224,7 @@ void sendNTPPacket()
ntpUdp.endPacket();
}
static bool isValidNtpResponse(byte * ntpPacket) {
static bool isValidNtpResponse(const byte* ntpPacket) {
// Perform a few validity checks on the packet
// based on https://github.com/taranais/NTPClient/blob/master/NTPClient.cpp
if((ntpPacket[0] & 0b11000000) == 0b11000000) return false; //reject LI=UNSYNC

View File

@ -141,7 +141,9 @@ bool PinManager::allocateMultiplePins(const managed_pin_type * mptArray, byte ar
bool PinManager::allocatePin(byte gpio, bool output, PinOwner tag)
{
// HW I2C & SPI pins have to be allocated using allocateMultiplePins variant since there is always SCL/SDA pair
if (!isPinOk(gpio, output) || (gpio >= WLED_NUM_PINS) || tag==PinOwner::HW_I2C || tag==PinOwner::HW_SPI) {
// DMX_INPUT pins have to be allocated using allocateMultiplePins variant since there is always RX/TX/EN triple
if (!isPinOk(gpio, output) || (gpio >= WLED_NUM_PINS) || tag==PinOwner::HW_I2C || tag==PinOwner::HW_SPI
|| tag==PinOwner::DMX_INPUT) {
#ifdef WLED_DEBUG
if (gpio < 255) { // 255 (-1) is the "not defined GPIO"
if (!isPinOk(gpio, output)) {

View File

@ -35,15 +35,16 @@ enum struct PinOwner : uint8_t {
Ethernet = 0x81,
BusDigital = 0x82,
BusOnOff = 0x83,
BusPwm = 0x84, // 'BusP' == PWM output using BusPwm
Button = 0x85, // 'Butn' == button from configuration
IR = 0x86, // 'IR' == IR receiver pin from configuration
Relay = 0x87, // 'Rly' == Relay pin from configuration
SPI_RAM = 0x88, // 'SpiR' == SPI RAM
DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
BusPwm = 0x84, // 'BusP' == PWM output using BusPwm
Button = 0x85, // 'Butn' == button from configuration
IR = 0x86, // 'IR' == IR receiver pin from configuration
Relay = 0x87, // 'Rly' == Relay pin from configuration
SPI_RAM = 0x88, // 'SpiR' == SPI RAM
DebugOut = 0x89, // 'Dbg' == debug output always IO1
DMX = 0x8A, // 'DMX' == hard-coded to IO2
HW_I2C = 0x8B, // 'I2C' == hardware I2C pins (4&5 on ESP8266, 21&22 on ESP32)
HW_SPI = 0x8C, // 'SPI' == hardware (V)SPI pins (13,14&15 on ESP8266, 5,18&23 on ESP32)
DMX_INPUT = 0x8D, // 'DMX_INPUT' == DMX input via serial
// Use UserMod IDs from const.h here
UM_Unspecified = USERMOD_ID_UNSPECIFIED, // 0x01
UM_Example = USERMOD_ID_EXAMPLE, // 0x02 // Usermod "usermod_v2_example.h"

View File

@ -61,7 +61,7 @@ int16_t loadPlaylist(JsonObject playlistObj, byte presetId) {
if (playlistLen == 0) return -1;
if (playlistLen > 100) playlistLen = 100;
playlistEntries = new PlaylistEntry[playlistLen];
playlistEntries = new(std::nothrow) PlaylistEntry[playlistLen];
if (playlistEntries == nullptr) return -1;
byte it = 0;
@ -146,7 +146,7 @@ if (millis() - presetCycledTime > (100 * playlistEntryDur) || doAdvancePlaylist)
}
jsonTransitionOnce = true;
strip.setTransition(fadeTransition ? playlistEntries[playlistIndex].tr * 100 : 0);
strip.setTransition(playlistEntries[playlistIndex].tr * 100);
playlistEntryDur = playlistEntries[playlistIndex].dur;
applyPresetFromPlaylist(playlistEntries[playlistIndex].preset);
doAdvancePlaylist = false;

View File

@ -76,8 +76,8 @@ static void doSaveState() {
// clean up
saveLedmap = -1;
presetToSave = 0;
delete[] saveName;
delete[] quickLoad;
free(saveName);
free(quickLoad);
saveName = nullptr;
quickLoad = nullptr;
playlistSave = false;
@ -216,8 +216,8 @@ void handlePresets()
//called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)]
void savePreset(byte index, const char* pname, JsonObject sObj)
{
if (!saveName) saveName = new char[33];
if (!quickLoad) quickLoad = new char[9];
if (!saveName) saveName = static_cast<char*>(malloc(33));
if (!quickLoad) quickLoad = static_cast<char*>(malloc(9));
if (!saveName || !quickLoad) return;
if (index == 0 || (index > 250 && index < 255)) return;
@ -263,8 +263,8 @@ void savePreset(byte index, const char* pname, JsonObject sObj)
presetsModifiedTime = toki.second(); //unix time
updateFSInfo();
}
delete[] saveName;
delete[] quickLoad;
free(saveName);
free(quickLoad);
saveName = nullptr;
quickLoad = nullptr;
} else {

View File

@ -1,6 +1,8 @@
#include "wled.h"
#ifndef WLED_DISABLE_ESPNOW
#define ESPNOW_BUSWAIT_TIMEOUT 24 // one frame timeout to wait for bus to finish updating
#define NIGHT_MODE_DEACTIVATED -1
#define NIGHT_MODE_BRIGHTNESS 5
@ -38,6 +40,7 @@ typedef struct WizMoteMessageStructure {
static uint32_t last_seq = UINT32_MAX;
static int brightnessBeforeNightMode = NIGHT_MODE_DEACTIVATED;
static int16_t ESPNowButton = -1; // set in callback if new button value is received
// Pulled from the IR Remote logic but reduced to 10 steps with a constant of 3
static const byte brightnessSteps[] = {
@ -121,6 +124,9 @@ static bool remoteJson(int button)
sprintf_P(objKey, PSTR("\"%d\":"), button);
unsigned long start = millis();
while (strip.isUpdating() && millis()-start < ESPNOW_BUSWAIT_TIMEOUT) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches
// attempt to read command from remote.json
readObjectFromFile(PSTR("/remote.json"), objKey, pDoc);
JsonObject fdo = pDoc->as<JsonObject>();
@ -176,7 +182,7 @@ static bool remoteJson(int button)
}
// Callback function that will be executed when data is received
void handleRemote(uint8_t *incomingData, size_t len) {
void handleWiZdata(uint8_t *incomingData, size_t len) {
message_structure_t *incoming = reinterpret_cast<message_structure_t *>(incomingData);
if (strcmp(last_signal_src, linked_remote) != 0) {
@ -202,8 +208,15 @@ void handleRemote(uint8_t *incomingData, size_t len) {
DEBUG_PRINT(F("] button: "));
DEBUG_PRINTLN(incoming->button);
if (!remoteJson(incoming->button))
switch (incoming->button) {
ESPNowButton = incoming->button; // save state, do not process in callback (can cause glitches)
last_seq = cur_seq;
}
// process ESPNow button data (acesses FS, should not be called while update to avoid glitches)
void handleRemote() {
if(ESPNowButton >= 0) {
if (!remoteJson(ESPNowButton))
switch (ESPNowButton) {
case WIZMOTE_BUTTON_ON : setOn(); break;
case WIZMOTE_BUTTON_OFF : setOff(); break;
case WIZMOTE_BUTTON_ONE : presetWithFallback(1, FX_MODE_STATIC, 0); break;
@ -219,9 +232,10 @@ void handleRemote(uint8_t *incomingData, size_t len) {
case WIZ_SMART_BUTTON_BRIGHT_DOWN : brightnessDown(); break;
default: break;
}
last_seq = cur_seq;
}
ESPNowButton = -1;
}
#else
void handleRemote(uint8_t *incomingData, size_t len) {}
void handleRemote() {}
#endif

View File

@ -134,8 +134,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.correctWB = request->hasArg(F("CCT"));
strip.cctFromRgb = request->hasArg(F("CR"));
cctICused = request->hasArg(F("IC"));
strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending);
uint8_t cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(cctBlending);
Bus::setGlobalAWMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt());
useGlobalLedBuffer = request->hasArg(F("LD"));
@ -209,7 +209,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
// actual finalization is done in WLED::loop() (removing old busses and adding new)
// this may happen even before this loop is finished so we do "doInitBusses" after the loop
if (busConfigs[s] != nullptr) delete busConfigs[s];
busConfigs[s] = new BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax);
busConfigs[s] = new(std::nothrow) BusConfig(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax);
busesChanged = true;
}
//doInitBusses = busesChanged; // we will do that below to ensure all input data is processed
@ -326,11 +326,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table
fadeTransition = request->hasArg(F("TF"));
modeBlending = request->hasArg(F("EB"));
t = request->arg(F("TD")).toInt();
if (t >= 0) transitionDelayDefault = t;
strip.paletteFade = request->hasArg(F("PF"));
t = request->arg(F("TP")).toInt();
randomPaletteChangeTime = MIN(255,MAX(1,t));
useHarmonicRandomPalette = request->hasArg(F("TH"));
@ -420,6 +417,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
t = request->arg(F("WO")).toInt();
if (t >= -255 && t <= 255) arlsOffset = t;
#ifdef WLED_ENABLE_DMX_INPUT
dmxInputTransmitPin = request->arg(F("IDMT")).toInt();
dmxInputReceivePin = request->arg(F("IDMR")).toInt();
dmxInputEnablePin = request->arg(F("IDME")).toInt();
dmxInputPort = request->arg(F("IDMP")).toInt();
if(dmxInputPort <= 0 || dmxInputPort > 2) dmxInputPort = 2;
#endif
#ifndef WLED_DISABLE_ALEXA
alexaEnabled = request->hasArg(F("AL"));
strlcpy(alexaInvocationName, request->arg(F("AI")).c_str(), 33);
@ -623,7 +628,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
//USERMODS
if (subPage == SUBPAGE_UM)
{
if (!requestJSONBufferLock(5)) return;
if (!requestJSONBufferLock(5)) {
request->deferResponse();
return;
}
// global I2C & SPI pins
int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt();
@ -982,18 +990,18 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set color from HEX or 32bit DEC
pos = req.indexOf(F("CL="));
if (pos > 0) {
colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str());
colorFromDecOrHexString(colIn, req.substring(pos + 3).c_str());
col0Changed = true;
}
pos = req.indexOf(F("C2="));
if (pos > 0) {
colorFromDecOrHexString(colInSec, (char*)req.substring(pos + 3).c_str());
colorFromDecOrHexString(colInSec, req.substring(pos + 3).c_str());
col1Changed = true;
}
pos = req.indexOf(F("C3="));
if (pos > 0) {
byte tmpCol[4];
colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str());
colorFromDecOrHexString(tmpCol, req.substring(pos + 3).c_str());
col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]);
selseg.setColor(2, col2); // defined above (SS= or main)
col2Changed = true;
@ -1140,7 +1148,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("TT="));
if (pos > 0) transitionDelay = getNumVal(&req, pos);
if (fadeTransition) strip.setTransition(transitionDelay);
strip.setTransition(transitionDelay);
//set time (unix timestamp)
pos = req.indexOf(F("ST="));

View File

@ -206,7 +206,7 @@ void notify(byte callMode, bool followUp)
notificationCount = followUp ? notificationCount + 1 : 0;
}
void parseNotifyPacket(uint8_t *udpIn) {
static void parseNotifyPacket(const uint8_t *udpIn) {
//ignore notification if received within a second after sending a notification ourselves
if (millis() - notificationSentTime < 1000) return;
if (udpIn[1] > 199) return; //do not receive custom versions
@ -225,10 +225,8 @@ void parseNotifyPacket(uint8_t *udpIn) {
// set transition time before making any segment changes
if (version > 3) {
if (fadeTransition) {
jsonTransitionOnce = true;
strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00));
}
jsonTransitionOnce = true;
strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00));
}
//apply colors from notification to main segment, only if not syncing full segments
@ -810,7 +808,7 @@ static size_t sequenceNumber = 0; // this needs to be shared across all ou
static const size_t ART_NET_HEADER_SIZE = 12;
static const byte ART_NET_HEADER[] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e};
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, uint8_t *buffer, uint8_t bri, bool isRGBW) {
uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri, bool isRGBW) {
if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap
WiFiUDP ddpUdp;
@ -963,7 +961,7 @@ void espNowReceiveCB(uint8_t* address, uint8_t* data, uint8_t len, signed int rs
// handle WiZ Mote data
if (data[0] == 0x91 || data[0] == 0x81 || data[0] == 0x80) {
handleRemote(data, len);
handleWiZdata(data, len);
return;
}

View File

@ -73,7 +73,7 @@ bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
}
bool getBoolVal(JsonVariant elem, bool dflt) {
bool getBoolVal(const JsonVariant &elem, bool dflt) {
if (elem.is<const char*>() && elem.as<const char*>()[0] == 't') {
return !dflt;
} else {
@ -151,7 +151,7 @@ bool isAsterisksOnly(const char* str, byte maxLen)
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
bool requestJSONBufferLock(uint8_t module)
bool requestJSONBufferLock(uint8_t moduleID)
{
if (pDoc == nullptr) {
DEBUG_PRINTLN(F("ERROR: JSON buffer not allocated!"));
@ -175,14 +175,14 @@ bool requestJSONBufferLock(uint8_t module)
#endif
// If the lock is still held - by us, or by another task
if (jsonBufferLock) {
DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), module, jsonBufferLock);
DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), moduleID, jsonBufferLock);
#ifdef ARDUINO_ARCH_ESP32
xSemaphoreGiveRecursive(jsonBufferLockMutex);
#endif
return false;
}
jsonBufferLock = module ? module : 255;
jsonBufferLock = moduleID ? moduleID : 255;
DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock);
pDoc->clear();
return true;
@ -265,16 +265,16 @@ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxL
if (mode < strip.getModeCount()) {
String lineBuffer = FPSTR(strip.getModeData(mode));
if (lineBuffer.length() > 0) {
unsigned start = lineBuffer.indexOf('@');
unsigned stop = lineBuffer.indexOf(';', start);
int start = lineBuffer.indexOf('@'); // String::indexOf() returns an int, not an unsigned; -1 means "not found"
int stop = lineBuffer.indexOf(';', start);
if (start>0 && stop>0) {
String names = lineBuffer.substring(start, stop); // include @
unsigned nameBegin = 1, nameEnd, nameDefault;
int nameBegin = 1, nameEnd, nameDefault;
if (slider < 10) {
for (size_t i=0; i<=slider; i++) {
const char *tmpstr;
dest[0] = '\0'; //clear dest buffer
if (nameBegin == 0) break; // there are no more names
if (nameBegin <= 0) break; // there are no more names
nameEnd = names.indexOf(',', nameBegin);
if (i == slider) {
nameDefault = names.indexOf('=', nameBegin); // find default value
@ -470,7 +470,7 @@ um_data_t* simulateSound(uint8_t simulationId)
for (int i = 0; i<16; i++)
fftResult[i] = beatsin8_t(120 / (i+1), 0, 255);
// fftResult[i] = (beatsin8_t(120, 0, 255) + (256/16 * i)) % 256;
volumeSmth = fftResult[8];
volumeSmth = fftResult[8];
break;
case UMS_WeWillRockYou:
if (ms%2000 < 200) {
@ -507,7 +507,7 @@ um_data_t* simulateSound(uint8_t simulationId)
case UMS_10_13:
for (int i = 0; i<16; i++)
fftResult[i] = inoise8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
volumeSmth = fftResult[8];
volumeSmth = fftResult[8];
break;
case UMS_14_3:
for (int i = 0; i<16; i++)
@ -538,7 +538,7 @@ void enumerateLedmaps() {
#ifndef ESP8266
if (ledmapNames[i-1]) { //clear old name
delete[] ledmapNames[i-1];
free(ledmapNames[i-1]);
ledmapNames[i-1] = nullptr;
}
#endif
@ -556,7 +556,7 @@ void enumerateLedmaps() {
const char *name = root["n"].as<const char*>();
if (name != nullptr) len = strlen(name);
if (len > 0 && len < 33) {
ledmapNames[i-1] = new char[len+1];
ledmapNames[i-1] = static_cast<char*>(malloc(len+1));
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33);
}
}
@ -564,7 +564,7 @@ void enumerateLedmaps() {
char tmp[33];
snprintf_P(tmp, 32, s_ledmap_tmpl, i);
len = strlen(tmp);
ledmapNames[i-1] = new char[len+1];
ledmapNames[i-1] = static_cast<char*>(malloc(len+1));
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33);
}
}
@ -595,6 +595,13 @@ float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
uint32_t hashInt(uint32_t s) {
// borrowed from https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
s = ((s >> 16) ^ s) * 0x45d9f3b;
s = ((s >> 16) ^ s) * 0x45d9f3b;
return (s >> 16) ^ s;
}
// 32 bit random number generator, inlining uses more code, use hw_random16() if speed is critical (see fcn_declare.h)
uint32_t hw_random(uint32_t upperlimit) {
uint32_t rnd = hw_random();
@ -608,4 +615,4 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
}
uint32_t diff = upperlimit - lowerlimit;
return hw_random(diff) + lowerlimit;
}
}

View File

@ -65,7 +65,10 @@ void WLED::loop()
handleNotifications();
handleTransitions();
#ifdef WLED_ENABLE_DMX
handleDMX();
handleDMXOutput();
#endif
#ifdef WLED_ENABLE_DMX_INPUT
dmxInput.update();
#endif
#ifdef WLED_DEBUG
@ -84,6 +87,9 @@ void WLED::loop()
#ifndef WLED_DISABLE_INFRARED
handleIR();
#endif
#ifndef WLED_DISABLE_ESPNOW
handleRemote();
#endif
#ifndef WLED_DISABLE_ALEXA
handleAlexa();
#endif
@ -297,6 +303,7 @@ void WLED::loop()
DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%lu\n"), avgStripMillis/loops, maxStripMillis);
}
strip.printSize();
server.printStatus(DEBUGOUT);
loops = 0;
maxLoopMillis = 0;
maxUsermodMillis = 0;
@ -521,7 +528,10 @@ void WLED::setup()
}
#endif
#ifdef WLED_ENABLE_DMX
initDMX();
initDMXOutput();
#endif
#ifdef WLED_ENABLE_DMX_INPUT
dmxInput.init(dmxInputReceivePin, dmxInputTransmitPin, dmxInputEnablePin, dmxInputPort);
#endif
#ifdef WLED_ENABLE_ADALIGHT
@ -772,7 +782,6 @@ int8_t WLED::findWiFi(bool doScan) {
void WLED::initConnection()
{
DEBUG_PRINTF_P(PSTR("initConnection() called @ %lus.\n"), millis()/1000);
#ifdef WLED_ENABLE_WEBSOCKETS
ws.onEvent(wsEvent);
#endif
@ -801,6 +810,7 @@ void WLED::initConnection()
if (!WLED_WIFI_CONFIGURED) {
DEBUG_PRINTLN(F("No connection configured."));
if (!apActive) initAP(); // instantly go to ap mode
return;
} else if (!apActive) {
if (apBehavior == AP_BEHAVIOR_ALWAYS) {
DEBUG_PRINTLN(F("Access point ALWAYS enabled."));

View File

@ -144,6 +144,10 @@
#endif
#endif
#ifdef WLED_ENABLE_DMX_INPUT
#include "dmx_input.h"
#endif
#include "src/dependencies/e131/ESPAsyncE131.h"
#ifndef WLED_DISABLE_MQTT
#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h"
@ -459,7 +463,15 @@ WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to f
WLED_GLOBAL uint16_t DMXStart _INIT(10); // start address of the first fixture
WLED_GLOBAL uint16_t DMXStartLED _INIT(0); // LED from which DMX fixtures start
#endif
WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consecutive universes)
#ifdef WLED_ENABLE_DMX_INPUT
WLED_GLOBAL int dmxInputTransmitPin _INIT(0);
WLED_GLOBAL int dmxInputReceivePin _INIT(0);
WLED_GLOBAL int dmxInputEnablePin _INIT(0);
WLED_GLOBAL int dmxInputPort _INIT(2);
WLED_GLOBAL DMXInput dmxInput;
#endif
WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes)
WLED_GLOBAL uint16_t e131Port _INIT(5568); // DMX in port. E1.31 default is 5568, Art-Net is 6454
WLED_GLOBAL byte e131Priority _INIT(0); // E1.31 port priority (if != 0 priority handling is active)
WLED_GLOBAL E131Priority highPriority _INIT(3); // E1.31 highest priority tracking, init = timeout in seconds
@ -576,8 +588,7 @@ WLED_GLOBAL bool wasConnected _INIT(false);
WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same
// transitions
WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color
WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending
WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style
WLED_GLOBAL bool transitionActive _INIT(false);
WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration
WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json)
@ -863,7 +874,7 @@ WLED_GLOBAL bool ledStatusState _INIT(false); // the current LED state
#endif
// server library objects
WLED_GLOBAL AsyncWebServer server _INIT_N(((80)));
WLED_GLOBAL AsyncWebServer server _INIT_N(((80, {0, WLED_REQUEST_MAX_QUEUE, WLED_REQUEST_MIN_HEAP, WLED_REQUEST_HEAP_USAGE})));
#ifdef WLED_ENABLE_WEBSOCKETS
WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws")));
#endif

View File

@ -224,7 +224,7 @@ void loadSettingsFromEEPROM()
if (lastEEPROMversion > 7)
{
strip.paletteFade = EEPROM.read(374);
//strip.paletteFade = EEPROM.read(374);
strip.paletteBlend = EEPROM.read(382);
for (int i = 0; i < 8; ++i)

View File

@ -220,3 +220,27 @@ float fmod_t(float num, float denom) {
#endif
return res;
}
// bit-wise integer square root calculation (exact)
uint32_t sqrt32_bw(uint32_t x) {
uint32_t res = 0;
uint32_t bit;
uint32_t num = x; // use 32bit for faster calculation
if(num < 1 << 10) bit = 1 << 10; // speed optimization for small numbers < 32^2
else if (num < 1 << 20) bit = 1 << 20; // speed optimization for medium numbers < 1024^2
else bit = 1 << 30; // start with highest power of 4 <= 2^32
while (bit > num) bit >>= 2; // reduce iterations
while (bit != 0) {
if (num >= res + bit) {
num -= res + bit;
res = (res >> 1) + bit;
} else {
res >>= 1;
}
bit >>= 2;
}
return res;
}

View File

@ -113,8 +113,8 @@ void handleSerial()
//only send response if TX pin is unused for other purposes
if (verboseResponse && serialCanTX) {
pDoc->clear();
JsonObject state = pDoc->createNestedObject("state");
serializeState(state);
JsonObject stateDoc = pDoc->createNestedObject("state");
serializeState(stateDoc);
JsonObject info = pDoc->createNestedObject("info");
serializeInfo(info);

View File

@ -21,7 +21,7 @@ static const char s_accessdenied[] PROGMEM = "Access Denied";
static const char _common_js[] PROGMEM = "/common.js";
//Is this an IP?
static bool isIp(String str) {
static bool isIp(const String &str) {
for (size_t i = 0; i < str.length(); i++) {
int c = str.charAt(i);
if (c != '.' && (c < '0' || c > '9')) {
@ -152,9 +152,9 @@ static String msgProcessor(const String& var)
return String();
}
static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
static void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool isFinal) {
if (!correctPIN) {
if (final) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg));
if (isFinal) request->send(401, FPSTR(CONTENT_TYPE_PLAIN), FPSTR(s_unlock_cfg));
return;
}
if (!index) {
@ -170,7 +170,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename,
if (len) {
request->_tempFile.write(data,len);
}
if (final) {
if (isFinal) {
request->_tempFile.close();
if (filename.indexOf(F("cfg.json")) >= 0) { // check for filename with or without slash
doReboot = true;
@ -288,7 +288,7 @@ void initServer()
bool isConfig = false;
if (!requestJSONBufferLock(14)) {
serveJsonError(request, 503, ERR_NOBUF);
request->deferResponse();
return;
}
@ -359,7 +359,7 @@ void initServer()
server.on(F("/upload"), HTTP_POST, [](AsyncWebServerRequest *request) {},
[](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data,
size_t len, bool final) {handleUpload(request, filename, index, data, len, final);}
size_t len, bool isFinal) {handleUpload(request, filename, index, data, len, isFinal);}
);
createEditHandler(correctPIN);
@ -389,7 +389,7 @@ void initServer()
serveMessage(request, 200, F("Update successful!"), F("Rebooting..."), 131);
doReboot = true;
}
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool isFinal){
if (!correctPIN || otaLock) return;
if(!index){
DEBUG_PRINTLN(F("OTA Update Start"));
@ -406,7 +406,7 @@ void initServer()
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
}
if(!Update.hasError()) Update.write(data, len);
if(final){
if(isFinal){
if(Update.end(true)){
DEBUG_PRINTLN(F("Update Success"));
} else {

View File

@ -26,7 +26,7 @@ void XML_response(Print& dest)
);
}
static void extractPin(Print& settingsScript, JsonObject &obj, const char *key) {
static void extractPin(Print& settingsScript, const JsonObject &obj, const char *key) {
if (obj[key].is<JsonArray>()) {
JsonArray pins = obj[key].as<JsonArray>();
for (JsonVariant pv : pins) {
@ -38,7 +38,7 @@ static void extractPin(Print& settingsScript, JsonObject &obj, const char *key)
}
// print used pins by scanning JsonObject (1 level deep)
static void fillUMPins(Print& settingsScript, JsonObject &mods)
static void fillUMPins(Print& settingsScript, const JsonObject &mods)
{
for (JsonPair kv : mods) {
// kv.key() is usermod name or subobject key
@ -285,7 +285,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("CCT"),strip.correctWB);
printSetFormCheckbox(settingsScript,PSTR("IC"),cctICused);
printSetFormCheckbox(settingsScript,PSTR("CR"),strip.cctFromRgb);
printSetFormValue(settingsScript,PSTR("CB"),strip.cctBlending);
printSetFormValue(settingsScript,PSTR("CB"),Bus::getCCTBlend());
printSetFormValue(settingsScript,PSTR("FR"),strip.getTargetFps());
printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode());
printSetFormCheckbox(settingsScript,PSTR("LD"),useGlobalLedBuffer);
@ -369,10 +369,7 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("GB"),gammaCorrectBri);
printSetFormCheckbox(settingsScript,PSTR("GC"),gammaCorrectCol);
dtostrf(gammaCorrectVal,3,1,nS); printSetFormValue(settingsScript,PSTR("GV"),nS);
printSetFormCheckbox(settingsScript,PSTR("TF"),fadeTransition);
printSetFormCheckbox(settingsScript,PSTR("EB"),modeBlending);
printSetFormValue(settingsScript,PSTR("TD"),transitionDelayDefault);
printSetFormCheckbox(settingsScript,PSTR("PF"),strip.paletteFade);
printSetFormValue(settingsScript,PSTR("TP"),randomPaletteChangeTime);
printSetFormCheckbox(settingsScript,PSTR("TH"),useHarmonicRandomPalette);
printSetFormValue(settingsScript,PSTR("BF"),briMultiplier);
@ -436,6 +433,18 @@ void getSettingsJS(byte subPage, Print& settingsScript)
printSetFormCheckbox(settingsScript,PSTR("ES"),e131SkipOutOfSequence);
printSetFormCheckbox(settingsScript,PSTR("EM"),e131Multicast);
printSetFormValue(settingsScript,PSTR("EU"),e131Universe);
#ifdef WLED_ENABLE_DMX
settingsScript.print(SET_F("hideNoDMX();")); // hide "not compiled in" message
#endif
#ifndef WLED_ENABLE_DMX_INPUT
settingsScript.print(SET_F("hideDMXInput();")); // hide "dmx input" settings
#else
settingsScript.print(SET_F("hideNoDMXInput();")); //hide "not compiled in" message
printSetFormValue(settingsScript,SET_F("IDMT"),dmxInputTransmitPin);
printSetFormValue(settingsScript,SET_F("IDMR"),dmxInputReceivePin);
printSetFormValue(settingsScript,SET_F("IDME"),dmxInputEnablePin);
printSetFormValue(settingsScript,SET_F("IDMP"),dmxInputPort);
#endif
printSetFormValue(settingsScript,PSTR("DA"),DMXAddress);
printSetFormValue(settingsScript,PSTR("XX"),DMXSegmentSpacing);
printSetFormValue(settingsScript,PSTR("PY"),e131Priority);