mirror of
https://github.com/wled/WLED.git
synced 2025-04-19 12:27:17 +00:00
Merge remote-tracking branch 'upstream/main' into usermod-libs
This commit is contained in:
commit
a5b972d87e
@ -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": [],
|
||||
|
||||
|
3
.github/workflows/nightly.yml
vendored
3
.github/workflows/nightly.yml
vendored
@ -37,4 +37,5 @@ jobs:
|
||||
prerelease: true
|
||||
body: ${{ steps.changelog.outputs.changelog }}
|
||||
files: |
|
||||
./*.bin
|
||||
*.bin
|
||||
*.bin.gz
|
||||
|
@ -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"}]}`)
|
||||
|
504
lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp
Normal file
504
lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
};
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
BIN
tools/AutoCubeMap.xlsx
Normal file
Binary file not shown.
@ -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[@]})
|
||||
|
@ -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.
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
6
usermods/usermod_v2_RF433/library.json
Normal file
6
usermods/usermod_v2_RF433/library.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name:": "usermod_v2_RF433",
|
||||
"dependencies": {
|
||||
"sui77/rc-switch":"2.6.4"
|
||||
}
|
||||
}
|
18
usermods/usermod_v2_RF433/readme.md
Normal file
18
usermods/usermod_v2_RF433/readme.md
Normal 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.
|
34
usermods/usermod_v2_RF433/remote433.json
Normal file
34
usermods/usermod_v2_RF433/remote433.json
Normal 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"}}
|
||||
}
|
||||
}
|
183
usermods/usermod_v2_RF433/usermod_v2_RF433.cpp
Normal file
183
usermods/usermod_v2_RF433/usermod_v2_RF433.cpp
Normal 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);
|
297
wled00/FX.cpp
297
wled00/FX.cpp
@ -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);
|
||||
|
115
wled00/FX.h
115
wled00/FX.h
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
|
@ -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"></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"></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"></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"></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"></i></button>
|
||||
</div>
|
||||
<p class="labels hd" id="pall"><i class="icons sel-icon" onclick="tglHex()"></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"> 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');"> 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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
280
wled00/dmx_input.cpp
Normal 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
73
wled00/dmx_input.h
Normal 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;
|
||||
|
||||
};
|
@ -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
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
144
wled00/image_loader.cpp
Normal 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
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)) {
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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="));
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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."));
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user